mirror of
https://github.com/harivansh-afk/forge.nvim.git
synced 2026-04-15 05:02:09 +00:00
feat: add capabilities system and per-forge compatibility
Problem: forge.nvim silently ignored unsupported features on non-GitHub forges. Codeberg `pr_for_branch_cmd` blocked all PR creation, CI picker had zero actions, `repo_info` was hardcoded, and the compose buffer showed draft/reviewer fields that did nothing. Solution: add `forge.Capabilities` declaration (`draft`, `reviewers`, `per_pr_checks`, `ci_json`) to each source. Compose buffer hides unsupported fields. Per-PR checks falls back to repo-wide CI with a notification. Fix Codeberg `pr_for_branch_cmd` to filter by branch via jq, implement `repo_info` and `list_runs_json_cmd` via Gitea API, add `default_branch_cmd` fallback, and add yank notifications for GitLab/Codeberg.
This commit is contained in:
parent
9be38e3b00
commit
2af47b6cf4
6 changed files with 132 additions and 36 deletions
|
|
@ -12,6 +12,12 @@ local M = {
|
||||||
pr_full = 'Pull Requests',
|
pr_full = 'Pull Requests',
|
||||||
ci = 'CI/CD',
|
ci = 'CI/CD',
|
||||||
},
|
},
|
||||||
|
capabilities = {
|
||||||
|
draft = false,
|
||||||
|
reviewers = false,
|
||||||
|
per_pr_checks = false,
|
||||||
|
ci_json = true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param state string
|
---@param state string
|
||||||
|
|
@ -105,7 +111,9 @@ function M:yank_branch(loc)
|
||||||
local branch = vim.trim(vim.fn.system('git branch --show-current'))
|
local branch = vim.trim(vim.fn.system('git branch --show-current'))
|
||||||
local base = forge.remote_web_url()
|
local base = forge.remote_web_url()
|
||||||
local file, lines = loc:match('^(.+):(.+)$')
|
local file, lines = loc:match('^(.+):(.+)$')
|
||||||
vim.fn.setreg('+', ('%s/src/branch/%s/%s#L%s'):format(base, branch, file, lines))
|
local url = ('%s/src/branch/%s/%s#L%s'):format(base, branch, file, lines)
|
||||||
|
vim.fn.setreg('+', url)
|
||||||
|
forge.log('URL copied')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param loc string
|
---@param loc string
|
||||||
|
|
@ -113,7 +121,9 @@ function M:yank_commit(loc)
|
||||||
local commit = vim.trim(vim.fn.system('git rev-parse HEAD'))
|
local commit = vim.trim(vim.fn.system('git rev-parse HEAD'))
|
||||||
local base = forge.remote_web_url()
|
local base = forge.remote_web_url()
|
||||||
local file, lines = loc:match('^(.+):(.+)$')
|
local file, lines = loc:match('^(.+):(.+)$')
|
||||||
vim.fn.setreg('+', ('%s/src/commit/%s/%s#L%s'):format(base, commit, file, lines))
|
local url = ('%s/src/commit/%s/%s#L%s'):format(base, commit, file, lines)
|
||||||
|
vim.fn.setreg('+', url)
|
||||||
|
forge.log('URL copied')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param num string
|
---@param num string
|
||||||
|
|
@ -128,19 +138,16 @@ function M:pr_base_cmd(num)
|
||||||
return { 'tea', 'pr', num, '--fields', 'base', '--output', 'simple' }
|
return { 'tea', 'pr', num, '--fields', 'base', '--output', 'simple' }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param _branch string
|
---@param branch string
|
||||||
---@return string[]
|
---@return string[]
|
||||||
function M:pr_for_branch_cmd(_branch)
|
function M:pr_for_branch_cmd(branch)
|
||||||
return {
|
return {
|
||||||
'tea',
|
'sh',
|
||||||
'pr',
|
'-c',
|
||||||
'list',
|
('tea pr list --state open --output json --fields index,head | jq -r \'[.[] | select(.head=="%s" or .head.name=="%s")][0].index // empty\''):format(
|
||||||
'--fields',
|
branch,
|
||||||
'index,head',
|
branch
|
||||||
'--output',
|
),
|
||||||
'simple',
|
|
||||||
'--state',
|
|
||||||
'open',
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -170,8 +177,30 @@ function M:check_tail_cmd(run_id)
|
||||||
return { 'tea', 'actions', 'runs', 'logs', run_id, '--follow' }
|
return { 'tea', 'actions', 'runs', 'logs', run_id, '--follow' }
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:list_runs_cmd(_branch)
|
function M:list_runs_json_cmd(branch)
|
||||||
return 'tea actions runs list'
|
local limit = tostring(forge.config().display.limits.runs)
|
||||||
|
local cmd = 'tea api "/repos/:owner/:repo/actions/runs?limit=' .. limit
|
||||||
|
if branch then
|
||||||
|
cmd = cmd .. '&branch=' .. branch
|
||||||
|
end
|
||||||
|
cmd = cmd .. '" 2>/dev/null | jq -r ".workflow_runs // []"'
|
||||||
|
return { 'sh', '-c', cmd }
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:normalize_run(entry)
|
||||||
|
local status = entry.status or ''
|
||||||
|
if status == 'completed' then
|
||||||
|
status = entry.conclusion or 'unknown'
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
id = tostring(entry.id or ''),
|
||||||
|
name = entry.name or '',
|
||||||
|
branch = entry.head_branch or '',
|
||||||
|
status = status,
|
||||||
|
event = entry.event or '',
|
||||||
|
url = entry.html_url or '',
|
||||||
|
created_at = entry.created_at or '',
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:run_log_cmd(id, failed_only)
|
function M:run_log_cmd(id, failed_only)
|
||||||
|
|
@ -256,7 +285,8 @@ function M:default_branch_cmd()
|
||||||
return {
|
return {
|
||||||
'sh',
|
'sh',
|
||||||
'-c',
|
'-c',
|
||||||
"git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'",
|
"git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'"
|
||||||
|
.. " || tea api /repos/:owner/:repo 2>/dev/null | jq -r '.default_branch // empty'",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -278,10 +308,35 @@ end
|
||||||
|
|
||||||
---@return forge.RepoInfo
|
---@return forge.RepoInfo
|
||||||
function M:repo_info()
|
function M:repo_info()
|
||||||
return {
|
local result = vim.system({ 'tea', 'api', '/repos/:owner/:repo' }, { text = true }):wait()
|
||||||
permission = 'ADMIN',
|
local ok, data = pcall(vim.json.decode, result.stdout or '{}')
|
||||||
merge_methods = { 'squash', 'rebase', 'merge' },
|
if not ok or type(data) ~= 'table' then
|
||||||
}
|
return { permission = 'READ', merge_methods = { 'merge' } }
|
||||||
|
end
|
||||||
|
|
||||||
|
local perms = type(data.permissions) == 'table' and data.permissions or {}
|
||||||
|
local permission = 'READ'
|
||||||
|
if perms.admin then
|
||||||
|
permission = 'ADMIN'
|
||||||
|
elseif perms.push then
|
||||||
|
permission = 'WRITE'
|
||||||
|
end
|
||||||
|
|
||||||
|
local methods = {}
|
||||||
|
if data.allow_merge_commits ~= false then
|
||||||
|
table.insert(methods, 'merge')
|
||||||
|
end
|
||||||
|
if data.allow_squash_merge ~= false then
|
||||||
|
table.insert(methods, 'squash')
|
||||||
|
end
|
||||||
|
if data.allow_rebase ~= false then
|
||||||
|
table.insert(methods, 'rebase')
|
||||||
|
end
|
||||||
|
if #methods == 0 then
|
||||||
|
table.insert(methods, 'merge')
|
||||||
|
end
|
||||||
|
|
||||||
|
return { permission = permission, merge_methods = methods }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param num string
|
---@param num string
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@ local M = {
|
||||||
pr_full = 'Pull Requests',
|
pr_full = 'Pull Requests',
|
||||||
ci = 'CI/CD',
|
ci = 'CI/CD',
|
||||||
},
|
},
|
||||||
|
capabilities = {
|
||||||
|
draft = true,
|
||||||
|
reviewers = true,
|
||||||
|
per_pr_checks = true,
|
||||||
|
ci_json = true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local function nwo()
|
local function nwo()
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@ local M = {
|
||||||
pr_full = 'Merge Requests',
|
pr_full = 'Merge Requests',
|
||||||
ci = 'CI/CD',
|
ci = 'CI/CD',
|
||||||
},
|
},
|
||||||
|
capabilities = {
|
||||||
|
draft = true,
|
||||||
|
reviewers = true,
|
||||||
|
per_pr_checks = false,
|
||||||
|
ci_json = true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param state string
|
---@param state string
|
||||||
|
|
@ -112,7 +118,9 @@ function M:yank_branch(loc)
|
||||||
local branch = vim.trim(vim.fn.system('git branch --show-current'))
|
local branch = vim.trim(vim.fn.system('git branch --show-current'))
|
||||||
local base = forge.remote_web_url()
|
local base = forge.remote_web_url()
|
||||||
local file, lines = loc:match('^(.+):(.+)$')
|
local file, lines = loc:match('^(.+):(.+)$')
|
||||||
vim.fn.setreg('+', ('%s/-/blob/%s/%s#L%s'):format(base, branch, file, lines))
|
local url = ('%s/-/blob/%s/%s#L%s'):format(base, branch, file, lines)
|
||||||
|
vim.fn.setreg('+', url)
|
||||||
|
forge.log('URL copied')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param loc string
|
---@param loc string
|
||||||
|
|
@ -120,7 +128,9 @@ function M:yank_commit(loc)
|
||||||
local commit = vim.trim(vim.fn.system('git rev-parse HEAD'))
|
local commit = vim.trim(vim.fn.system('git rev-parse HEAD'))
|
||||||
local base = forge.remote_web_url()
|
local base = forge.remote_web_url()
|
||||||
local file, lines = loc:match('^(.+):(.+)$')
|
local file, lines = loc:match('^(.+):(.+)$')
|
||||||
vim.fn.setreg('+', ('%s/-/blob/%s/%s#L%s'):format(base, commit, file, lines))
|
local url = ('%s/-/blob/%s/%s#L%s'):format(base, commit, file, lines)
|
||||||
|
vim.fn.setreg('+', url)
|
||||||
|
forge.log('URL copied')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param num string
|
---@param num string
|
||||||
|
|
|
||||||
|
|
@ -216,11 +216,18 @@ end
|
||||||
---@field permission string
|
---@field permission string
|
||||||
---@field merge_methods string[]
|
---@field merge_methods string[]
|
||||||
|
|
||||||
|
---@class forge.Capabilities
|
||||||
|
---@field draft boolean
|
||||||
|
---@field reviewers boolean
|
||||||
|
---@field per_pr_checks boolean
|
||||||
|
---@field ci_json boolean
|
||||||
|
|
||||||
---@class forge.Forge
|
---@class forge.Forge
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field cli string
|
---@field cli string
|
||||||
---@field kinds { issue: string, pr: string }
|
---@field kinds { issue: string, pr: string }
|
||||||
---@field labels { issue: string, pr: string, pr_one: string, pr_full: string, ci: string }
|
---@field labels { issue: string, pr: string, pr_one: string, pr_full: string, ci: string }
|
||||||
|
---@field capabilities forge.Capabilities
|
||||||
---@field list_pr_json_cmd fun(self: forge.Forge, state: string): string[]
|
---@field list_pr_json_cmd fun(self: forge.Forge, state: string): string[]
|
||||||
---@field list_issue_json_cmd fun(self: forge.Forge, state: string): string[]
|
---@field list_issue_json_cmd fun(self: forge.Forge, state: string): string[]
|
||||||
---@field pr_json_fields fun(self: forge.Forge): { number: string, title: string, branch: string, state: string, author: string, created_at: string }
|
---@field pr_json_fields fun(self: forge.Forge): { number: string, title: string, branch: string, state: string, author: string, created_at: string }
|
||||||
|
|
@ -929,9 +936,9 @@ local function open_compose_buffer(f, branch, base, draft)
|
||||||
---@param ln integer
|
---@param ln integer
|
||||||
---@param start integer
|
---@param start integer
|
||||||
---@param len integer
|
---@param len integer
|
||||||
---@param hl string
|
---@param hl_group string
|
||||||
local function mark(ln, start, len, hl)
|
local function mark(ln, start, len, hl_group)
|
||||||
table.insert(marks, { line = ln, col = start, end_col = start + len, hl = hl })
|
table.insert(marks, { line = ln, col = start, end_col = start + len, hl = hl_group })
|
||||||
end
|
end
|
||||||
|
|
||||||
add_line('<!--')
|
add_line('<!--')
|
||||||
|
|
@ -947,15 +954,19 @@ local function open_compose_buffer(f, branch, base, draft)
|
||||||
mark(ln, #creating_prefix, #f.name, 'ForgeComposeForge')
|
mark(ln, #creating_prefix, #f.name, 'ForgeComposeForge')
|
||||||
|
|
||||||
add_line('')
|
add_line('')
|
||||||
local draft_val = draft and 'yes' or ''
|
if f.capabilities.draft then
|
||||||
local draft_prefix = ' Draft: '
|
local draft_val = draft and 'yes' or ''
|
||||||
ln = add_line('%s%s', draft_prefix, draft_val)
|
local draft_prefix = ' Draft: '
|
||||||
if draft_val ~= '' then
|
ln = add_line('%s%s', draft_prefix, draft_val)
|
||||||
mark(ln, #draft_prefix, #draft_val, 'ForgeComposeDraft')
|
if draft_val ~= '' then
|
||||||
|
mark(ln, #draft_prefix, #draft_val, 'ForgeComposeDraft')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local reviewers_prefix = ' Reviewers: '
|
if f.capabilities.reviewers then
|
||||||
add_line('%s', reviewers_prefix)
|
local reviewers_prefix = ' Reviewers: '
|
||||||
|
add_line('%s', reviewers_prefix)
|
||||||
|
end
|
||||||
|
|
||||||
local stat_start, stat_end
|
local stat_start, stat_end
|
||||||
if diff_stat ~= '' then
|
if diff_stat ~= '' then
|
||||||
|
|
@ -990,8 +1001,8 @@ local function open_compose_buffer(f, branch, base, draft)
|
||||||
end
|
end
|
||||||
for pos, run in line:gmatch('()([+-]+)') do
|
for pos, run in line:gmatch('()([+-]+)') do
|
||||||
if pos > pipe then
|
if pos > pipe then
|
||||||
local hl = run:sub(1, 1) == '+' and 'ForgeComposeAdded' or 'ForgeComposeRemoved'
|
local stat_hl = run:sub(1, 1) == '+' and 'ForgeComposeAdded' or 'ForgeComposeRemoved'
|
||||||
mark(i, pos - 1, #run, hl)
|
mark(i, pos - 1, #run, stat_hl)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,14 @@ local function pr_actions(f, num)
|
||||||
{
|
{
|
||||||
name = 'ci',
|
name = 'ci',
|
||||||
fn = function()
|
fn = function()
|
||||||
M.checks(f, num)
|
if f.capabilities.per_pr_checks then
|
||||||
|
M.checks(f, num)
|
||||||
|
else
|
||||||
|
require('forge').log(
|
||||||
|
('per-%s checks unavailable on %s, showing repo CI'):format(kind, f.name)
|
||||||
|
)
|
||||||
|
M.ci(f)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,14 @@ local function dispatch(args)
|
||||||
elseif action == 'worktree' then
|
elseif action == 'worktree' then
|
||||||
pickers.pr_actions(f, num)._by_name.worktree()
|
pickers.pr_actions(f, num)._by_name.worktree()
|
||||||
elseif action == 'ci' then
|
elseif action == 'ci' then
|
||||||
pickers.checks(f, num)
|
if f.capabilities.per_pr_checks then
|
||||||
|
pickers.checks(f, num)
|
||||||
|
else
|
||||||
|
require('forge').log(
|
||||||
|
('per-%s checks unavailable on %s, showing repo CI'):format(f.labels.pr_one, f.name)
|
||||||
|
)
|
||||||
|
pickers.ci(f)
|
||||||
|
end
|
||||||
elseif action == 'browse' then
|
elseif action == 'browse' then
|
||||||
f:view_web(f.kinds.pr, num)
|
f:view_web(f.kinds.pr, num)
|
||||||
elseif action == 'manage' then
|
elseif action == 'manage' then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue