ci: format

This commit is contained in:
Barrett Ruth 2026-03-27 17:19:09 -04:00
parent c4da2cda2a
commit 5ee2cc567a
No known key found for this signature in database
GPG key ID: A6C96C9349D2FC81
6 changed files with 2173 additions and 2394 deletions

View file

@ -112,10 +112,7 @@ 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( vim.fn.setreg('+', ('%s/src/branch/%s/%s#L%s'):format(base, branch, file, lines))
'+',
('%s/src/branch/%s/%s#L%s'):format(base, branch, file, lines)
)
end end
---@param loc string ---@param loc string
@ -123,10 +120,7 @@ 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( vim.fn.setreg('+', ('%s/src/commit/%s/%s#L%s'):format(base, commit, file, lines))
'+',
('%s/src/commit/%s/%s#L%s'):format(base, commit, file, lines)
)
end end
---@param num string ---@param num string
@ -252,9 +246,11 @@ end
function M:create_pr_web_cmd() function M:create_pr_web_cmd()
local branch = vim.trim(vim.fn.system('git branch --show-current')) local branch = vim.trim(vim.fn.system('git branch --show-current'))
local base_url = forge.remote_web_url() local base_url = forge.remote_web_url()
local default = vim.trim(vim.fn.system( local default = vim.trim(
vim.fn.system(
"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/||'"
)) )
)
if default == '' then if default == '' then
default = 'main' default = 'main'
end end
@ -265,7 +261,8 @@ end
---@return string[] ---@return string[]
function M:default_branch_cmd() function M:default_branch_cmd()
return { return {
'sh', '-c', 'sh',
'-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/||'",
} }
end end
@ -297,10 +294,9 @@ end
---@param num string ---@param num string
---@return forge.PRState ---@return forge.PRState
function M:pr_state(num) function M:pr_state(num)
local result = vim.system( local result = vim
{ 'tea', 'pr', num, '--fields', 'state,mergeable', '--output', 'json' }, .system({ 'tea', 'pr', num, '--fields', 'state,mergeable', '--output', 'json' }, { text = true })
{ text = true } :wait()
):wait()
local ok, data = pcall(vim.json.decode, result.stdout or '{}') local ok, data = pcall(vim.json.decode, result.stdout or '{}')
if not ok or type(data) ~= 'table' then if not ok or type(data) ~= 'table' then
data = {} data = {}

View file

@ -182,12 +182,7 @@ function M:check_log_cmd(run_id, failed_only)
return { return {
'sh', 'sh',
'-c', '-c',
('gh run view %s -R %s %s | tail -n %d'):format( ('gh run view %s -R %s %s | tail -n %d'):format(run_id, nwo(), flag, lines),
run_id,
nwo(),
flag,
lines
),
} }
end end
@ -330,14 +325,16 @@ end
---@return forge.RepoInfo ---@return forge.RepoInfo
function M:repo_info() function M:repo_info()
local result = vim.system({ local result = vim
.system({
'gh', 'gh',
'repo', 'repo',
'view', 'view',
nwo(), nwo(),
'--json', '--json',
'viewerPermission,squashMergeAllowed,rebaseMergeAllowed,mergeCommitAllowed', 'viewerPermission,squashMergeAllowed,rebaseMergeAllowed,mergeCommitAllowed',
}, { text = true }):wait() }, { text = true })
:wait()
local ok, data = pcall(vim.json.decode, result.stdout or '{}') local ok, data = pcall(vim.json.decode, result.stdout or '{}')
if not ok or type(data) ~= 'table' then if not ok or type(data) ~= 'table' then
@ -363,14 +360,16 @@ end
---@param num string ---@param num string
---@return forge.PRState ---@return forge.PRState
function M:pr_state(num) function M:pr_state(num)
local result = vim.system({ local result = vim
.system({
'gh', 'gh',
'pr', 'pr',
'view', 'view',
num, num,
'--json', '--json',
'state,mergeable,reviewDecision,isDraft', 'state,mergeable,reviewDecision,isDraft',
}, { text = true }):wait() }, { text = true })
:wait()
local ok, data = pcall(vim.json.decode, result.stdout or '{}') local ok, data = pcall(vim.json.decode, result.stdout or '{}')
if not ok or type(data) ~= 'table' then if not ok or type(data) ~= 'table' then

View file

@ -125,10 +125,7 @@ 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( vim.fn.setreg('+', ('%s/-/blob/%s/%s#L%s'):format(base, branch, file, lines))
'+',
('%s/-/blob/%s/%s#L%s'):format(base, branch, file, lines)
)
end end
---@param loc string ---@param loc string
@ -136,10 +133,7 @@ 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( vim.fn.setreg('+', ('%s/-/blob/%s/%s#L%s'):format(base, commit, file, lines))
'+',
('%s/-/blob/%s/%s#L%s'):format(base, commit, file, lines)
)
end end
---@param num string ---@param num string
@ -169,9 +163,7 @@ function M:pr_for_branch_cmd(branch)
return { return {
'sh', 'sh',
'-c', '-c',
("glab mr list --source-branch '%s' -F json | jq -r '.[0].iid // empty'"):format( ("glab mr list --source-branch '%s' -F json | jq -r '.[0].iid // empty'"):format(branch),
branch
),
} }
end end
@ -234,8 +226,7 @@ end
function M:run_log_cmd(id, failed_only) function M:run_log_cmd(id, failed_only)
local lines = forge.config().ci.lines local lines = forge.config().ci.lines
local jq_filter = failed_only local jq_filter = failed_only and '[.[] | select(.status=="failed")][0].id // .[0].id'
and '[.[] | select(.status=="failed")][0].id // .[0].id'
or '.[0].id' or '.[0].id'
return { return {
'sh', 'sh',
@ -249,8 +240,7 @@ function M:run_log_cmd(id, failed_only)
end end
function M:run_tail_cmd(id) function M:run_tail_cmd(id)
local jq_filter = local jq_filter = '[.[] | select(.status=="running" or .status=="pending")][0].id // .[0].id'
'[.[] | select(.status=="running" or .status=="pending")][0].id // .[0].id'
return { return {
'sh', 'sh',
'-c', '-c',
@ -312,10 +302,15 @@ end
---@return string[] ---@return string[]
function M:create_pr_cmd(title, body, base, draft, reviewers) function M:create_pr_cmd(title, body, base, draft, reviewers)
local cmd = { local cmd = {
'glab', 'mr', 'create', 'glab',
'--title', title, 'mr',
'--description', body, 'create',
'--target-branch', base, '--title',
title,
'--description',
body,
'--target-branch',
base,
'--yes', '--yes',
} }
if draft then if draft then
@ -336,7 +331,8 @@ end
---@return string[] ---@return string[]
function M:default_branch_cmd() function M:default_branch_cmd()
return { return {
'sh', '-c', 'sh',
'-c',
"glab repo view -F json | jq -r '.default_branch'", "glab repo view -F json | jq -r '.default_branch'",
} }
end end
@ -358,18 +354,13 @@ end
---@return forge.RepoInfo ---@return forge.RepoInfo
function M:repo_info() function M:repo_info()
local result = vim.system( local result = vim.system({ 'glab', 'api', 'projects/:id' }, { text = true }):wait()
{ 'glab', 'api', 'projects/:id' },
{ text = true }
)
:wait()
local ok, data = pcall(vim.json.decode, result.stdout or '{}') local ok, data = pcall(vim.json.decode, result.stdout or '{}')
if not ok or type(data) ~= 'table' then if not ok or type(data) ~= 'table' then
data = {} data = {}
end end
local perms = type(data.permissions) == 'table' and data.permissions or {} local perms = type(data.permissions) == 'table' and data.permissions or {}
local pa = type(perms.project_access) == 'table' and perms.project_access local pa = type(perms.project_access) == 'table' and perms.project_access or {}
or {}
local ga = type(perms.group_access) == 'table' and perms.group_access or {} local ga = type(perms.group_access) == 'table' and perms.group_access or {}
local access = pa.access_level or 0 local access = pa.access_level or 0
local group_access = ga.access_level or 0 local group_access = ga.access_level or 0
@ -402,10 +393,9 @@ end
---@param num string ---@param num string
---@return forge.PRState ---@return forge.PRState
function M:pr_state(num) function M:pr_state(num)
local result = vim.system( local result = vim
{ 'glab', 'mr', 'view', num, '--output', 'json' }, .system({ 'glab', 'mr', 'view', num, '--output', 'json' }, { text = true })
{ text = true } :wait()
):wait()
local ok, data = pcall(vim.json.decode, result.stdout or '{}') local ok, data = pcall(vim.json.decode, result.stdout or '{}')
if not ok or type(data) ~= 'table' then if not ok or type(data) ~= 'table' then
data = {} data = {}

View file

@ -162,11 +162,8 @@ local function detect_from_remote(remote)
return 'gitlab' return 'gitlab'
end end
if if
( (remote:find('codeberg') or remote:find('gitea') or remote:find('forgejo'))
remote:find('codeberg') and vim.fn.executable('tea') == 1
or remote:find('gitea')
or remote:find('forgejo')
) and vim.fn.executable('tea') == 1
then then
return 'codeberg' return 'codeberg'
end end
@ -477,10 +474,8 @@ function M.format_check(check)
end end
local elapsed = '' local elapsed = ''
if check.startedAt and check.completedAt and check.completedAt ~= '' then if check.startedAt and check.completedAt and check.completedAt ~= '' then
local ok_s, ts = local ok_s, ts = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.startedAt)
pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.startedAt) local ok_e, te = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.completedAt)
local ok_e, te =
pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.completedAt)
if ok_s and ok_e and ts > 0 and te > 0 then if ok_s and ok_e and ts > 0 and te > 0 then
local secs = te - ts local secs = te - ts
if secs >= 60 then if secs >= 60 then
@ -490,12 +485,7 @@ function M.format_check(check)
end end
end end
end end
return ('%s%s\27[0m %s \27[2m%s\27[0m'):format( return ('%s%s\27[0m %s \27[2m%s\27[0m'):format(color, icon, pad_or_truncate(name, 35), elapsed)
color,
icon,
pad_or_truncate(name, 35),
elapsed
)
end end
---@param run forge.CIRun ---@param run forge.CIRun
@ -507,12 +497,7 @@ function M.format_run(run)
icon, color = '*', '\27[32m' icon, color = '*', '\27[32m'
elseif s == 'failure' or s == 'failed' then elseif s == 'failure' or s == 'failed' then
icon, color = 'x', '\27[31m' icon, color = 'x', '\27[31m'
elseif elseif s == 'in_progress' or s == 'running' or s == 'pending' or s == 'queued' then
s == 'in_progress'
or s == 'running'
or s == 'pending'
or s == 'queued'
then
icon, color = '~', '\27[33m' icon, color = '~', '\27[33m'
elseif s == 'cancelled' or s == 'canceled' or s == 'skipped' then elseif s == 'cancelled' or s == 'canceled' or s == 'skipped' then
icon, color = '-', '\27[2m' icon, color = '-', '\27[2m'
@ -546,8 +531,7 @@ end
function M.filter_checks(checks, filter) function M.filter_checks(checks, filter)
if not filter or filter == 'all' then if not filter or filter == 'all' then
table.sort(checks, function(a, b) table.sort(checks, function(a, b)
local order = local order = { fail = 1, pending = 2, pass = 3, skipping = 4, cancel = 5 }
{ fail = 1, pending = 2, pass = 3, skipping = 4, cancel = 5 }
local oa = order[(a.bucket or ''):lower()] or 9 local oa = order[(a.bucket or ''):lower()] or 9
local ob = order[(b.bucket or ''):lower()] or 9 local ob = order[(b.bucket or ''):lower()] or 9
return oa < ob return oa < ob
@ -590,10 +574,9 @@ end
---@param base string ---@param base string
---@return string title, string body ---@return string title, string body
local function fill_from_commits(branch, base) local function fill_from_commits(branch, base)
local result = vim.system( local result = vim
{ 'git', 'log', 'origin/' .. base .. '..HEAD', '--format=%s%n%b%x00' }, .system({ 'git', 'log', 'origin/' .. base .. '..HEAD', '--format=%s%n%b%x00' }, { text = true })
{ text = true } :wait()
):wait()
local raw = vim.trim(result.stdout or '') local raw = vim.trim(result.stdout or '')
if raw == '' then if raw == '' then
local clean = branch:gsub('^%w+/', ''):gsub('[/-]', ' ') local clean = branch:gsub('^%w+/', ''):gsub('[/-]', ' ')
@ -682,8 +665,7 @@ local function discover_template(f, repo_root)
if tstat then if tstat then
local fd = vim.uv.fs_open(tpath, 'r', 438) local fd = vim.uv.fs_open(tpath, 'r', 438)
if fd then if fd then
local content = local content = vim.uv.fs_read(fd, tstat.size, 0)
vim.uv.fs_read(fd, tstat.size, 0)
vim.uv.fs_close(fd) vim.uv.fs_close(fd)
if content then if content then
return vim.trim(content) return vim.trim(content)
@ -708,10 +690,7 @@ end
---@param buf integer? ---@param buf integer?
local function push_and_create(f, branch, title, body, pr_base, pr_draft, pr_reviewers, buf) local function push_and_create(f, branch, title, body, pr_base, pr_draft, pr_reviewers, buf)
M.log_now('pushing and creating ' .. f.labels.pr_one .. '...') M.log_now('pushing and creating ' .. f.labels.pr_one .. '...')
vim.system( vim.system({ 'git', 'push', '-u', 'origin', branch }, { text = true }, function(push_result)
{ 'git', 'push', '-u', 'origin', branch },
{ text = true },
function(push_result)
if push_result.code ~= 0 then if push_result.code ~= 0 then
local msg = vim.trim(push_result.stderr or '') local msg = vim.trim(push_result.stderr or '')
if msg == '' then if msg == '' then
@ -750,8 +729,7 @@ local function push_and_create(f, branch, title, body, pr_base, pr_draft, pr_rev
end) end)
end end
) )
end end)
)
end end
---@param f forge.Forge ---@param f forge.Forge
@ -783,9 +761,7 @@ local function open_compose_buffer(f, branch, base, draft)
local comment_start = #lines + 1 local comment_start = #lines + 1
local pr_kind = f.labels.pr_full:gsub('s$', '') local pr_kind = f.labels.pr_full:gsub('s$', '')
local diff_stat = vim.fn.system( local diff_stat = vim.fn.system('git diff --stat origin/' .. base .. '..HEAD'):gsub('%s+$', '')
'git diff --stat origin/' .. base .. '..HEAD'
):gsub('%s+$', '')
---@type {line: integer, col: integer, end_col: integer, hl: string}[] ---@type {line: integer, col: integer, end_col: integer, hl: string}[]
local marks = {} local marks = {}
@ -938,9 +914,7 @@ local function open_compose_buffer(f, branch, base, draft)
vim.api.nvim_buf_delete(buf, { force = true }) vim.api.nvim_buf_delete(buf, { force = true })
return return
end end
local pr_body = vim.trim( local pr_body = vim.trim(table.concat(content_lines, '\n', 3))
table.concat(content_lines, '\n', 3)
)
if pr_body == '' then if pr_body == '' then
M.log_now('aborting: empty body', vim.log.levels.WARN) M.log_now('aborting: empty body', vim.log.levels.WARN)
vim.bo[buf].modified = false vim.bo[buf].modified = false
@ -956,11 +930,7 @@ local function open_compose_buffer(f, branch, base, draft)
vim.api.nvim_win_set_cursor(0, { 1, 0 }) vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.cmd('normal! 0vg_') vim.cmd('normal! 0vg_')
vim.api.nvim_feedkeys( vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-G>', true, false, true), 'n', false)
vim.api.nvim_replace_termcodes('<C-G>', true, false, true),
'n',
false
)
end end
---@class forge.CreatePROpts ---@class forge.CreatePROpts
@ -991,11 +961,7 @@ function M.create_pr(opts)
vim.schedule(function() vim.schedule(function()
if num ~= '' and num ~= 'null' then if num ~= '' and num ~= 'null' then
M.log_now( M.log_now(
('%s #%s already exists for branch %s'):format( ('%s #%s already exists for branch %s'):format(f.labels.pr_one, num, branch),
f.labels.pr_one,
num,
branch
),
vim.log.levels.WARN vim.log.levels.WARN
) )
return return
@ -1003,10 +969,7 @@ function M.create_pr(opts)
if opts.web then if opts.web then
M.log_now('pushing...') M.log_now('pushing...')
vim.system( vim.system({ 'git', 'push', '-u', 'origin', branch }, { text = true }, function(push_result)
{ 'git', 'push', '-u', 'origin', branch },
{ text = true },
function(push_result)
vim.schedule(function() vim.schedule(function()
if push_result.code ~= 0 then if push_result.code ~= 0 then
M.log_now('push failed', vim.log.levels.ERROR) M.log_now('push failed', vim.log.levels.ERROR)
@ -1017,46 +980,32 @@ function M.create_pr(opts)
vim.system(web_cmd) vim.system(web_cmd)
end end
end) end)
end end)
)
return return
end end
M.log_now('resolving base branch...') M.log_now('resolving base branch...')
vim.system( vim.system(f:default_branch_cmd(), { text = true }, function(base_result)
f:default_branch_cmd(),
{ text = true },
function(base_result)
local base = vim.trim(base_result.stdout or '') local base = vim.trim(base_result.stdout or '')
if base == '' then if base == '' then
base = 'main' base = 'main'
end end
vim.schedule(function() vim.schedule(function()
local has_diff = vim.system( local has_diff = vim
{ 'git', 'diff', '--quiet', 'origin/' .. base .. '..HEAD' }, .system({ 'git', 'diff', '--quiet', 'origin/' .. base .. '..HEAD' }, { text = true })
{ text = true } :wait().code ~= 0
):wait().code ~= 0
if not has_diff then if not has_diff then
M.log_now( M.log_now('no changes from origin/' .. base, vim.log.levels.WARN)
'no changes from origin/' .. base,
vim.log.levels.WARN
)
return return
end end
if opts.instant then if opts.instant then
local title, body = fill_from_commits(branch, base) local title, body = fill_from_commits(branch, base)
push_and_create(f, branch, title, body, base, opts.draft or false) push_and_create(f, branch, title, body, base, opts.draft or false)
else else
open_compose_buffer( open_compose_buffer(f, branch, base, opts.draft or false)
f,
branch,
base,
opts.draft or false
)
end end
end) end)
end end)
)
end) end)
end) end)
end end

View file

@ -61,10 +61,7 @@ local function checks_picker(f, num, filter, cached_checks)
require('fzf-lua').fzf_exec(lines, { require('fzf-lua').fzf_exec(lines, {
fzf_args = fzf_args, fzf_args = fzf_args,
prompt = ('Checks (#%s, %s)> '):format( prompt = ('Checks (#%s, %s)> '):format(num, labels[filter] or filter),
num,
labels[filter] or filter
),
fzf_opts = { fzf_opts = {
['--ansi'] = '', ['--ansi'] = '',
['--no-multi'] = '', ['--no-multi'] = '',
@ -100,12 +97,7 @@ local function checks_picker(f, num, filter, cached_checks)
vim.cmd('noautocmd botright new') vim.cmd('noautocmd botright new')
vim.fn.termopen(cmd) vim.fn.termopen(cmd)
vim.api.nvim_feedkeys( vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes( vim.api.nvim_replace_termcodes('<C-\\><C-n>G', true, false, true),
'<C-\\><C-n>G',
true,
false,
true
),
'n', 'n',
false false
) )
@ -145,17 +137,13 @@ local function checks_picker(f, num, filter, cached_checks)
end end
if cached_checks then if cached_checks then
forge_mod.log( forge_mod.log(('checks picker (%s #%s, cached)'):format(f.labels.pr_one, num))
('checks picker (%s #%s, cached)'):format(f.labels.pr_one, num)
)
open_picker(cached_checks) open_picker(cached_checks)
return return
end end
if f.checks_json_cmd then if f.checks_json_cmd then
forge_mod.log_now( forge_mod.log_now(('fetching checks for %s #%s...'):format(f.labels.pr_one, num))
('fetching checks for %s #%s...'):format(f.labels.pr_one, num)
)
vim.system(f:checks_json_cmd(num), { text = true }, function(result) vim.system(f:checks_json_cmd(num), { text = true }, function(result)
vim.schedule(function() vim.schedule(function()
local ok, checks = pcall(vim.json.decode, result.stdout or '[]') local ok, checks = pcall(vim.json.decode, result.stdout or '[]')
@ -192,14 +180,9 @@ local function run_forge_cmd(kind, num, label, cmd, success_msg, fail_msg)
vim.system(cmd, { text = true }, function(result) vim.system(cmd, { text = true }, function(result)
vim.schedule(function() vim.schedule(function()
if result.code == 0 then if result.code == 0 then
vim.notify( vim.notify(('[forge]: %s %s #%s'):format(success_msg, kind, num))
('[forge]: %s %s #%s'):format(success_msg, kind, num)
)
else else
vim.notify( vim.notify('[forge]: ' .. cmd_error(result, fail_msg), vim.log.levels.ERROR)
'[forge]: ' .. cmd_error(result, fail_msg),
vim.log.levels.ERROR
)
end end
vim.cmd.redraw() vim.cmd.redraw()
end) end)
@ -211,23 +194,9 @@ end
---@param is_open boolean ---@param is_open boolean
local function issue_toggle_state(f, num, is_open) local function issue_toggle_state(f, num, is_open)
if is_open then if is_open then
run_forge_cmd( run_forge_cmd('issue', num, 'closing', f:close_issue_cmd(num), 'closed', 'close failed')
'issue',
num,
'closing',
f:close_issue_cmd(num),
'closed',
'close failed'
)
else else
run_forge_cmd( run_forge_cmd('issue', num, 'reopening', f:reopen_issue_cmd(num), 'reopened', 'reopen failed')
'issue',
num,
'reopening',
f:reopen_issue_cmd(num),
'reopened',
'reopen failed'
)
end end
end end
@ -255,14 +224,7 @@ local function pr_manage_picker(f, num)
if can_write and is_open then if can_write and is_open then
add('Approve', function() add('Approve', function()
run_forge_cmd( run_forge_cmd(kind, num, 'approving', f:approve_cmd(num), 'approved', 'approve failed')
kind,
num,
'approving',
f:approve_cmd(num),
'approved',
'approve failed'
)
end) end)
end end
@ -283,43 +245,20 @@ local function pr_manage_picker(f, num)
if is_open then if is_open then
add('Close', function() add('Close', function()
run_forge_cmd( run_forge_cmd(kind, num, 'closing', f:close_cmd(num), 'closed', 'close failed')
kind,
num,
'closing',
f:close_cmd(num),
'closed',
'close failed'
)
end) end)
else else
add('Reopen', function() add('Reopen', function()
run_forge_cmd( run_forge_cmd(kind, num, 'reopening', f:reopen_cmd(num), 'reopened', 'reopen failed')
kind,
num,
'reopening',
f:reopen_cmd(num),
'reopened',
'reopen failed'
)
end) end)
end end
local draft_cmd = f:draft_toggle_cmd(num, pr_state.is_draft) local draft_cmd = f:draft_toggle_cmd(num, pr_state.is_draft)
if draft_cmd then if draft_cmd then
local draft_label = pr_state.is_draft and 'Mark as ready' local draft_label = pr_state.is_draft and 'Mark as ready' or 'Mark as draft'
or 'Mark as draft' local draft_done = pr_state.is_draft and 'marked as ready' or 'marked as draft'
local draft_done = pr_state.is_draft and 'marked as ready'
or 'marked as draft'
add(draft_label, function() add(draft_label, function()
run_forge_cmd( run_forge_cmd(kind, num, 'toggling draft', draft_cmd, draft_done, 'draft toggle failed')
kind,
num,
'toggling draft',
draft_cmd,
draft_done,
'draft toggle failed'
)
end) end)
end end
@ -348,8 +287,7 @@ local function close_review_view()
pcall(vim.cmd, 'diffoff!') pcall(vim.cmd, 'diffoff!')
end end
local review_augroup = local review_augroup = vim.api.nvim_create_augroup('ForgeReview', { clear = true })
vim.api.nvim_create_augroup('ForgeReview', { clear = true })
local function end_review() local function end_review()
local review = require('forge').review local review = require('forge').review
@ -369,10 +307,7 @@ local function toggle_review_mode()
if not ok then if not ok then
return return
end end
local file = commands.review_file_at_line( local file = commands.review_file_at_line(vim.api.nvim_get_current_buf(), vim.fn.line('.'))
vim.api.nvim_get_current_buf(),
vim.fn.line('.')
)
review.mode = 'split' review.mode = 'split'
if file then if file then
vim.cmd('edit ' .. vim.fn.fnameescape(file)) vim.cmd('edit ' .. vim.fn.fnameescape(file))
@ -396,12 +331,7 @@ local function start_review(base, mode)
local review = require('forge').review local review = require('forge').review
review.base = base review.base = base
review.mode = mode or 'unified' review.mode = mode or 'unified'
vim.keymap.set( vim.keymap.set('n', 's', toggle_review_mode, { desc = 'toggle review split/unified' })
'n',
's',
toggle_review_mode,
{ desc = 'toggle review split/unified' }
)
vim.api.nvim_clear_autocmds({ group = review_augroup }) vim.api.nvim_clear_autocmds({ group = review_augroup })
vim.api.nvim_create_autocmd('BufWipeout', { vim.api.nvim_create_autocmd('BufWipeout', {
group = review_augroup, group = review_augroup,
@ -423,14 +353,9 @@ local function pr_actions(f, num)
vim.system(f:checkout_cmd(num), { text = true }, function(result) vim.system(f:checkout_cmd(num), { text = true }, function(result)
vim.schedule(function() vim.schedule(function()
if result.code == 0 then if result.code == 0 then
vim.notify( vim.notify(('[forge]: checked out %s #%s'):format(kind, num))
('[forge]: checked out %s #%s'):format(kind, num)
)
else else
vim.notify( vim.notify('[forge]: ' .. cmd_error(result, 'checkout failed'), vim.log.levels.ERROR)
'[forge]: ' .. cmd_error(result, 'checkout failed'),
vim.log.levels.ERROR
)
end end
vim.cmd.redraw() vim.cmd.redraw()
end) end)
@ -450,37 +375,24 @@ local function pr_actions(f, num)
end end
local root = vim.trim(vim.fn.system('git rev-parse --show-toplevel')) local root = vim.trim(vim.fn.system('git rev-parse --show-toplevel'))
local wt_path = vim.fs.normalize(root .. '/../' .. branch) local wt_path = vim.fs.normalize(root .. '/../' .. branch)
forge_mod.log_now( forge_mod.log_now(('fetching %s #%s into worktree...'):format(kind, num))
('fetching %s #%s into worktree...'):format(kind, num)
)
vim.system(fetch_cmd, { text = true }, function() vim.system(fetch_cmd, { text = true }, function()
vim.system( vim.system({ 'git', 'worktree', 'add', wt_path, branch }, { text = true }, function(result)
{ 'git', 'worktree', 'add', wt_path, branch },
{ text = true },
function(result)
vim.schedule(function() vim.schedule(function()
if result.code == 0 then if result.code == 0 then
vim.notify( vim.notify(('[forge]: worktree at %s'):format(wt_path))
('[forge]: worktree at %s'):format(wt_path)
)
else else
vim.notify( vim.notify('[forge]: ' .. cmd_error(result, 'worktree failed'), vim.log.levels.ERROR)
'[forge]: '
.. cmd_error(result, 'worktree failed'),
vim.log.levels.ERROR
)
end end
vim.cmd.redraw() vim.cmd.redraw()
end) end)
end end)
)
end) end)
end end
actions['ctrl-d'] = function() actions['ctrl-d'] = function()
local forge_mod = require('forge') local forge_mod = require('forge')
local repo_root = local repo_root = vim.trim(vim.fn.system('git rev-parse --show-toplevel'))
vim.trim(vim.fn.system('git rev-parse --show-toplevel'))
forge_mod.log_now(('reviewing %s #%s...'):format(kind, num)) forge_mod.log_now(('reviewing %s #%s...'):format(kind, num))
vim.system(f:checkout_cmd(num), { text = true }, function(co_result) vim.system(f:checkout_cmd(num), { text = true }, function(co_result)
@ -490,10 +402,7 @@ local function pr_actions(f, num)
end) end)
end end
vim.system( vim.system(f:pr_base_cmd(num), { text = true }, function(base_result)
f:pr_base_cmd(num),
{ text = true },
function(base_result)
vim.schedule(function() vim.schedule(function()
local base = vim.trim(base_result.stdout or '') local base = vim.trim(base_result.stdout or '')
if base == '' or base_result.code ~= 0 then if base == '' or base_result.code ~= 0 then
@ -503,21 +412,11 @@ local function pr_actions(f, num)
start_review(range) start_review(range)
local ok, commands = pcall(require, 'diffs.commands') local ok, commands = pcall(require, 'diffs.commands')
if ok then if ok then
commands.greview( commands.greview(range, { repo_root = repo_root })
range,
{ repo_root = repo_root }
)
end end
forge_mod.log( forge_mod.log(('review ready for %s #%s against %s'):format(kind, num, base))
('review ready for %s #%s against %s'):format( end)
kind,
num,
base
)
)
end) end)
end
)
end) end)
end end
@ -550,10 +449,7 @@ local function forge_picker(kind, state, f)
local function open_pr_list(prs) local function open_pr_list(prs)
local lines = {} local lines = {}
for _, pr in ipairs(prs) do for _, pr in ipairs(prs) do
table.insert( table.insert(lines, forge_mod.format_pr(pr, pr_fields, show_state))
lines,
forge_mod.format_pr(pr, pr_fields, show_state)
)
end end
local function with_pr_num(selected, fn) local function with_pr_num(selected, fn)
local num = selected[1] and selected[1]:match('[#!](%d+)') local num = selected[1] and selected[1]:match('[#!](%d+)')
@ -628,23 +524,16 @@ local function forge_picker(kind, state, f)
if cached then if cached then
open_pr_list(cached) open_pr_list(cached)
else else
forge_mod.log_now( forge_mod.log_now(('fetching %s list (%s)...'):format(f.labels.pr, state))
('fetching %s list (%s)...'):format(f.labels.pr, state) vim.system(f:list_pr_json_cmd(state), { text = true }, function(result)
)
vim.system(
f:list_pr_json_cmd(state),
{ text = true },
function(result)
vim.schedule(function() vim.schedule(function()
local ok, prs = local ok, prs = pcall(vim.json.decode, result.stdout or '[]')
pcall(vim.json.decode, result.stdout or '[]')
if ok and prs then if ok and prs then
forge_mod.set_list(cache_key, prs) forge_mod.set_list(cache_key, prs)
open_pr_list(prs) open_pr_list(prs)
end end
end) end)
end end)
)
end end
else else
local issue_fields = f:issue_json_fields() local issue_fields = f:issue_json_fields()
@ -662,14 +551,7 @@ local function forge_picker(kind, state, f)
local n = tostring(issue[num_field] or '') local n = tostring(issue[num_field] or '')
local s = (issue[state_field] or ''):lower() local s = (issue[state_field] or ''):lower()
state_map[n] = s == 'open' or s == 'opened' state_map[n] = s == 'open' or s == 'opened'
table.insert( table.insert(lines, forge_mod.format_issue(issue, issue_fields, issue_show_state))
lines,
forge_mod.format_issue(
issue,
issue_fields,
issue_show_state
)
)
end end
local function with_issue_num(selected, fn) local function with_issue_num(selected, fn)
local num = selected[1] and selected[1]:match('[#!](%d+)') local num = selected[1] and selected[1]:match('[#!](%d+)')
@ -722,20 +604,15 @@ local function forge_picker(kind, state, f)
open_issue_list(cached) open_issue_list(cached)
else else
forge_mod.log_now('fetching issue list (' .. state .. ')...') forge_mod.log_now('fetching issue list (' .. state .. ')...')
vim.system( vim.system(f:list_issue_json_cmd(state), { text = true }, function(result)
f:list_issue_json_cmd(state),
{ text = true },
function(result)
vim.schedule(function() vim.schedule(function()
local ok, issues = local ok, issues = pcall(vim.json.decode, result.stdout or '[]')
pcall(vim.json.decode, result.stdout or '[]')
if ok and issues then if ok and issues then
forge_mod.set_list(cache_key, issues) forge_mod.set_list(cache_key, issues)
open_issue_list(issues) open_issue_list(issues)
end end
end) end)
end end)
)
end end
end end
end end
@ -785,12 +662,7 @@ local function ci_picker(f, branch)
forge_mod.log_now('fetching CI/CD logs...') forge_mod.log_now('fetching CI/CD logs...')
local s = run.status:lower() local s = run.status:lower()
local cmd local cmd
if if s == 'in_progress' or s == 'running' or s == 'pending' or s == 'queued' then
s == 'in_progress'
or s == 'running'
or s == 'pending'
or s == 'queued'
then
cmd = f:run_tail_cmd(run.id) cmd = f:run_tail_cmd(run.id)
elseif s == 'failure' or s == 'failed' then elseif s == 'failure' or s == 'failed' then
cmd = f:run_log_cmd(run.id, true) cmd = f:run_log_cmd(run.id, true)
@ -800,12 +672,7 @@ local function ci_picker(f, branch)
vim.cmd('noautocmd botright new') vim.cmd('noautocmd botright new')
vim.fn.termopen(cmd) vim.fn.termopen(cmd)
vim.api.nvim_feedkeys( vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes( vim.api.nvim_replace_termcodes('<C-\\><C-n>G', true, false, true),
'<C-\\><C-n>G',
true,
false,
true
),
'n', 'n',
false false
) )
@ -834,25 +701,17 @@ local function ci_picker(f, branch)
if f.list_runs_json_cmd then if f.list_runs_json_cmd then
forge_mod.log_now('fetching CI runs...') forge_mod.log_now('fetching CI runs...')
vim.system( vim.system(f:list_runs_json_cmd(branch), { text = true }, function(result)
f:list_runs_json_cmd(branch),
{ text = true },
function(result)
vim.schedule(function() vim.schedule(function()
local ok, runs = local ok, runs = pcall(vim.json.decode, result.stdout or '[]')
pcall(vim.json.decode, result.stdout or '[]')
if ok and runs and #runs > 0 then if ok and runs and #runs > 0 then
open_picker(runs) open_picker(runs)
else else
vim.notify( vim.notify('[forge]: no CI runs found', vim.log.levels.INFO)
'[forge]: no CI runs found',
vim.log.levels.INFO
)
vim.cmd.redraw() vim.cmd.redraw()
end end
end) end)
end end)
)
elseif f.list_runs_cmd then elseif f.list_runs_cmd then
require('fzf-lua').fzf_exec(f:list_runs_cmd(branch), { require('fzf-lua').fzf_exec(f:list_runs_cmd(branch), {
fzf_args = fzf_args, fzf_args = fzf_args,
@ -960,31 +819,19 @@ local git_picker = function()
['default'] = function(selected) ['default'] = function(selected)
with_sha(selected, function(sha) with_sha(selected, function(sha)
forge_mod.log_now('checking out ' .. sha .. '...') forge_mod.log_now('checking out ' .. sha .. '...')
vim.system( vim.system({ 'git', 'checkout', sha }, { text = true }, function(result)
{ 'git', 'checkout', sha },
{ text = true },
function(result)
vim.schedule(function() vim.schedule(function()
if result.code == 0 then if result.code == 0 then
vim.notify( vim.notify(('[forge]: checked out %s (detached)'):format(sha))
('[forge]: checked out %s (detached)'):format(
sha
)
)
else else
vim.notify( vim.notify(
'[forge]: ' '[forge]: ' .. cmd_error(result, 'checkout failed'),
.. cmd_error(
result,
'checkout failed'
),
vim.log.levels.ERROR vim.log.levels.ERROR
) )
end end
vim.cmd.redraw() vim.cmd.redraw()
end) end)
end end)
)
end) end)
end, end,
['ctrl-d'] = function(selected) ['ctrl-d'] = function(selected)
@ -1051,8 +898,7 @@ local git_picker = function()
require('fzf-lua').git_worktrees() require('fzf-lua').git_worktrees()
end) end)
local prompt = f and (f.name:sub(1, 1):upper() .. f.name:sub(2)) .. '> ' local prompt = f and (f.name:sub(1, 1):upper() .. f.name:sub(2)) .. '> ' or 'Git> '
or 'Git> '
require('fzf-lua').fzf_exec(items, { require('fzf-lua').fzf_exec(items, {
fzf_args = fzf_args, fzf_args = fzf_args,
@ -1073,8 +919,7 @@ vim.api.nvim_create_autocmd('FileType', {
pattern = 'qf', pattern = 'qf',
callback = function() callback = function()
local info = vim.fn.getwininfo(vim.api.nvim_get_current_win())[1] local info = vim.fn.getwininfo(vim.api.nvim_get_current_win())[1]
local items = info.loclist == 1 and vim.fn.getloclist(0) local items = info.loclist == 1 and vim.fn.getloclist(0) or vim.fn.getqflist()
or vim.fn.getqflist()
if #items == 0 then if #items == 0 then
return return
end end