refactor(git): convert blocking subprocess calls to async
This commit is contained in:
+35
-28
@@ -1,3 +1,4 @@
|
|||||||
|
local git = require("git")
|
||||||
local log = require("log")
|
local log = require("log")
|
||||||
local repo = require("git.repo")
|
local repo = require("git.repo")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
@@ -39,11 +40,7 @@ local function prefetch_cmds()
|
|||||||
vim.system(
|
vim.system(
|
||||||
{ "git", "--list-cmds=main,others,alias" },
|
{ "git", "--list-cmds=main,others,alias" },
|
||||||
{ text = true },
|
{ text = true },
|
||||||
function(result)
|
vim.schedule_wrap(populate_cached_cmds)
|
||||||
vim.schedule(function()
|
|
||||||
populate_cached_cmds(result)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -75,33 +72,41 @@ end
|
|||||||
---@param args string[]
|
---@param args string[]
|
||||||
---@param conf ow.Git.SplitHandler
|
---@param conf ow.Git.SplitHandler
|
||||||
local function run_in_split(worktree, args, conf)
|
local function run_in_split(worktree, args, conf)
|
||||||
vim.cmd("new")
|
local buf = git.new_scratch()
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
vim.bo[buf].buftype = "nofile"
|
|
||||||
vim.bo[buf].bufhidden = "hide"
|
|
||||||
vim.bo[buf].swapfile = false
|
|
||||||
vim.bo[buf].modifiable = false
|
|
||||||
vim.b[buf].git_worktree = worktree
|
vim.b[buf].git_worktree = worktree
|
||||||
if conf.needs_ref then
|
if conf.needs_ref then
|
||||||
local user_ref = first_positional(args, 2) or "HEAD"
|
local user_ref = first_positional(args, 2) or "HEAD"
|
||||||
local sha = repo.rev_parse(worktree, user_ref, true)
|
local function apply_name(label)
|
||||||
if sha then
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
vim.b[buf].git_ref = sha
|
return
|
||||||
vim.b[buf].git_parent_ref =
|
|
||||||
repo.rev_parse(worktree, user_ref .. "^", true)
|
|
||||||
end
|
|
||||||
pcall(
|
|
||||||
vim.api.nvim_buf_set_name,
|
|
||||||
buf,
|
|
||||||
"git://" .. (sha or user_ref) .. "/"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
pcall(vim.api.nvim_buf_set_name, buf, "git://" .. label .. "/")
|
||||||
vim.bo[buf].filetype = conf.ft
|
vim.bo[buf].filetype = conf.ft
|
||||||
|
end
|
||||||
|
repo.rev_parse(worktree, user_ref, true, function(sha)
|
||||||
|
if not sha then
|
||||||
|
apply_name(user_ref)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
repo.rev_parse(worktree, user_ref .. "^", true, function(parent)
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.b[buf].git_ref = sha
|
||||||
|
vim.b[buf].git_parent_ref = parent
|
||||||
|
apply_name(sha)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
vim.bo[buf].filetype = conf.ft
|
||||||
|
end
|
||||||
|
|
||||||
local cmd = { "git" }
|
local cmd = { "git" }
|
||||||
vim.list_extend(cmd, args)
|
vim.list_extend(cmd, args)
|
||||||
vim.system(cmd, { cwd = worktree, text = true }, function(obj)
|
vim.system(
|
||||||
vim.schedule(function()
|
cmd,
|
||||||
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
if not vim.api.nvim_buf_is_valid(buf) then
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -124,7 +129,7 @@ local function run_in_split(worktree, args, conf)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
@@ -132,8 +137,10 @@ end
|
|||||||
local function run_to_messages(worktree, args)
|
local function run_to_messages(worktree, args)
|
||||||
local cmd = { "git" }
|
local cmd = { "git" }
|
||||||
vim.list_extend(cmd, args)
|
vim.list_extend(cmd, args)
|
||||||
vim.system(cmd, { cwd = worktree, text = true }, function(obj)
|
vim.system(
|
||||||
vim.schedule(function()
|
cmd,
|
||||||
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
local out = vim.trim(obj.stdout or "")
|
local out = vim.trim(obj.stdout or "")
|
||||||
local err = vim.trim(obj.stderr or "")
|
local err = vim.trim(obj.stderr or "")
|
||||||
local chunks = {}
|
local chunks = {}
|
||||||
@@ -156,7 +163,7 @@ local function run_to_messages(worktree, args)
|
|||||||
vim.api.nvim_echo(chunks, true, {})
|
vim.api.nvim_echo(chunks, true, {})
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param args string[]
|
---@param args string[]
|
||||||
|
|||||||
+8
-2
@@ -67,8 +67,10 @@ function M.commit(opts)
|
|||||||
if amend then
|
if amend then
|
||||||
table.insert(cmd, "--amend")
|
table.insert(cmd, "--amend")
|
||||||
end
|
end
|
||||||
local result = vim.system(cmd, { cwd = worktree, text = true })
|
vim.system(
|
||||||
:wait()
|
cmd,
|
||||||
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(result)
|
||||||
if result.code ~= 0 then
|
if result.code ~= 0 then
|
||||||
log.error(
|
log.error(
|
||||||
"git commit failed: %s",
|
"git commit failed: %s",
|
||||||
@@ -80,7 +82,11 @@ function M.commit(opts)
|
|||||||
if out ~= "" then
|
if out ~= "" then
|
||||||
log.info("%s", out)
|
log.info("%s", out)
|
||||||
end
|
end
|
||||||
|
if vim.api.nvim_buf_is_valid(buf) then
|
||||||
vim.api.nvim_buf_delete(buf, { force = true })
|
vim.api.nvim_buf_delete(buf, { force = true })
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|||||||
+37
-20
@@ -59,47 +59,62 @@ local function attach_index_writer(buf, worktree, path)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Run `git show <revspec>` asynchronously and call `callback(lines?)` on the
|
||||||
|
---main loop. `lines` is nil on failure (the error is logged).
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param revspec string anything `git show` accepts (e.g. `HEAD:foo`, `:foo`, blob SHA)
|
---@param revspec string anything `git show` accepts (e.g. `HEAD:foo`, `:foo`, blob SHA)
|
||||||
---@return string[]
|
---@param callback fun(lines: string[]?)
|
||||||
local function read_show(worktree, revspec)
|
local function read_show_async(worktree, revspec, callback)
|
||||||
local result = vim.system(
|
vim.system(
|
||||||
{ "git", "show", revspec },
|
{ "git", "show", revspec },
|
||||||
{ cwd = worktree, text = true }
|
{ cwd = worktree, text = true },
|
||||||
)
|
vim.schedule_wrap(function(result)
|
||||||
:wait()
|
|
||||||
if result.code ~= 0 then
|
if result.code ~= 0 then
|
||||||
log.error(
|
log.error(
|
||||||
"git show %s failed: %s",
|
"git show %s failed: %s",
|
||||||
revspec,
|
revspec,
|
||||||
vim.trim(result.stderr or "")
|
vim.trim(result.stderr or "")
|
||||||
)
|
)
|
||||||
return {}
|
callback(nil)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
return util.split_lines(result.stdout or "")
|
callback(util.split_lines(result.stdout or ""))
|
||||||
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Internal builder: run `git show <revspec>`, drop the result into a fresh
|
---Internal builder: create a scratch buffer immediately and asynchronously
|
||||||
---scratch buffer, and (when `is_index` is true) wire up the BufWriteCmd
|
---fill it with the content of `git show <revspec>`. Returning the buffer
|
||||||
---that writes back to the git index for `index_path`.
|
---synchronously lets callers wire up windows / `:diffthis` right away; the
|
||||||
|
---diff updates when the content arrives. The buffer starts non-modifiable
|
||||||
|
---and the index `BufWriteCmd` is only attached after a successful load, so
|
||||||
|
---a premature `:w` can't blow away the index entry with empty content.
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param revspec string
|
---@param revspec string
|
||||||
---@param is_index boolean
|
---@param is_index boolean
|
||||||
---@param index_path string? required when is_index is true
|
---@param index_path string? required when is_index is true
|
||||||
---@return integer
|
---@return integer
|
||||||
local function build_show_buf(worktree, revspec, is_index, index_path)
|
local function build_show_buf(worktree, revspec, is_index, index_path)
|
||||||
local lines = read_show(worktree, revspec)
|
|
||||||
local buf = vim.api.nvim_create_buf(false, true)
|
local buf = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
vim.bo[buf].buftype = "nofile"
|
||||||
vim.bo[buf].buftype = is_index and "acwrite" or "nofile"
|
|
||||||
vim.bo[buf].bufhidden = "wipe"
|
vim.bo[buf].bufhidden = "wipe"
|
||||||
vim.bo[buf].swapfile = false
|
vim.bo[buf].swapfile = false
|
||||||
|
vim.bo[buf].modifiable = false
|
||||||
|
vim.bo[buf].modified = false
|
||||||
|
read_show_async(worktree, revspec, function(lines)
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.bo[buf].modifiable = true
|
||||||
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines or {})
|
||||||
if is_index then
|
if is_index then
|
||||||
|
vim.bo[buf].buftype = "acwrite"
|
||||||
attach_index_writer(buf, worktree, assert(index_path))
|
attach_index_writer(buf, worktree, assert(index_path))
|
||||||
else
|
else
|
||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
end
|
end
|
||||||
vim.bo[buf].modified = false
|
vim.bo[buf].modified = false
|
||||||
|
end)
|
||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -192,12 +207,14 @@ function M.split(opts)
|
|||||||
local label = is_index and "index" or opts.ref
|
local label = is_index and "index" or opts.ref
|
||||||
M.set_buf_name_and_filetype(other, "git://" .. label .. "/" .. rel)
|
M.set_buf_name_and_filetype(other, "git://" .. label .. "/" .. rel)
|
||||||
|
|
||||||
local split_cmd = opts.vertical and "leftabove vertical sbuffer "
|
local cur_win = vim.api.nvim_get_current_win()
|
||||||
or "leftabove sbuffer "
|
local other_win = vim.api.nvim_open_win(other, true, {
|
||||||
vim.cmd(split_cmd .. other)
|
split = opts.vertical and "left" or "above",
|
||||||
vim.cmd("diffthis")
|
win = cur_win,
|
||||||
vim.cmd("wincmd p")
|
})
|
||||||
vim.cmd("diffthis")
|
vim.wo[other_win].diff = true
|
||||||
|
vim.api.nvim_set_current_win(cur_win)
|
||||||
|
vim.wo[cur_win].diff = true
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -25,6 +25,33 @@ function M.head(path)
|
|||||||
return repo.head(path)
|
return repo.head(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class ow.Git.NewScratchOpts
|
||||||
|
---@field name string?
|
||||||
|
---@field bufhidden ("hide"|"wipe")? defaults to "hide"
|
||||||
|
---@field split ("above"|"below"|"left"|"right")? defaults to splitbelow-aware horizontal
|
||||||
|
|
||||||
|
---Open a split with a fresh non-modifiable scratch buffer. Default split
|
||||||
|
---direction is horizontal, honouring `splitbelow`. Caller flips
|
||||||
|
---`modifiable`, fills the buffer, and sets `filetype` once content lands.
|
||||||
|
---@param opts ow.Git.NewScratchOpts?
|
||||||
|
---@return integer buf
|
||||||
|
---@return integer win
|
||||||
|
function M.new_scratch(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local buf = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.bo[buf].buftype = "nofile"
|
||||||
|
vim.bo[buf].bufhidden = opts.bufhidden or "hide"
|
||||||
|
vim.bo[buf].swapfile = false
|
||||||
|
vim.bo[buf].modifiable = false
|
||||||
|
vim.bo[buf].modified = false
|
||||||
|
if opts.name then
|
||||||
|
pcall(vim.api.nvim_buf_set_name, buf, opts.name)
|
||||||
|
end
|
||||||
|
local split = opts.split or (vim.o.splitbelow and "below" or "above")
|
||||||
|
local win = vim.api.nvim_open_win(buf, true, { split = split })
|
||||||
|
return buf, win
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
for name, link in pairs(HIGHLIGHTS) do
|
for name, link in pairs(HIGHLIGHTS) do
|
||||||
vim.api.nvim_set_hl(0, name, { link = link, default = true })
|
vim.api.nvim_set_hl(0, name, { link = link, default = true })
|
||||||
|
|||||||
+17
-12
@@ -1,3 +1,4 @@
|
|||||||
|
local git = require("git")
|
||||||
local log = require("log")
|
local log = require("log")
|
||||||
local repo = require("git.repo")
|
local repo = require("git.repo")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
@@ -13,7 +14,11 @@ function M.show()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local result = vim.system({
|
local buf = git.new_scratch()
|
||||||
|
vim.b[buf].git_worktree = worktree
|
||||||
|
|
||||||
|
vim.system(
|
||||||
|
{
|
||||||
"git",
|
"git",
|
||||||
"log",
|
"log",
|
||||||
"--graph",
|
"--graph",
|
||||||
@@ -21,24 +26,24 @@ function M.show()
|
|||||||
"--decorate",
|
"--decorate",
|
||||||
"--date=short",
|
"--date=short",
|
||||||
"--format=format:" .. LOG_FORMAT,
|
"--format=format:" .. LOG_FORMAT,
|
||||||
}, { cwd = worktree, text = true }):wait()
|
},
|
||||||
if result.code ~= 0 then
|
{ cwd = worktree, text = true },
|
||||||
log.error("git log failed: %s", result.stderr or "")
|
vim.schedule_wrap(function(result)
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if result.code ~= 0 then
|
||||||
|
log.error("git log failed: %s", vim.trim(result.stderr or ""))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local lines = util.split_lines(result.stdout or "")
|
local lines = util.split_lines(result.stdout or "")
|
||||||
|
vim.bo[buf].modifiable = true
|
||||||
vim.cmd("new")
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
vim.bo[buf].buftype = "nofile"
|
|
||||||
vim.bo[buf].bufhidden = "hide"
|
|
||||||
vim.bo[buf].swapfile = false
|
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
vim.bo[buf].modified = false
|
vim.bo[buf].modified = false
|
||||||
vim.b[buf].git_worktree = worktree
|
|
||||||
vim.bo[buf].filetype = "gitlog"
|
vim.bo[buf].filetype = "gitlog"
|
||||||
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
+42
-42
@@ -1,7 +1,9 @@
|
|||||||
local log = require("log")
|
local log = require("log")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
|
|
||||||
local UNMERGED = {
|
local M = {}
|
||||||
|
|
||||||
|
M.UNMERGED = {
|
||||||
DD = true,
|
DD = true,
|
||||||
AU = true,
|
AU = true,
|
||||||
UD = true,
|
UD = true,
|
||||||
@@ -14,7 +16,7 @@ local UNMERGED = {
|
|||||||
---@param code string porcelain v1 XY code
|
---@param code string porcelain v1 XY code
|
||||||
---@return string? char
|
---@return string? char
|
||||||
---@return string? hl_group
|
---@return string? hl_group
|
||||||
local function indicator(code)
|
function M.indicator(code)
|
||||||
if code == "" then
|
if code == "" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -24,7 +26,7 @@ local function indicator(code)
|
|||||||
if code == "!!" then
|
if code == "!!" then
|
||||||
return "!", "GitIgnored"
|
return "!", "GitIgnored"
|
||||||
end
|
end
|
||||||
if UNMERGED[code] then
|
if M.UNMERGED[code] then
|
||||||
return "U", "GitUnmerged"
|
return "U", "GitUnmerged"
|
||||||
end
|
end
|
||||||
local x, y = code:sub(1, 1), code:sub(2, 2)
|
local x, y = code:sub(1, 1), code:sub(2, 2)
|
||||||
@@ -43,7 +45,7 @@ end
|
|||||||
---@param code string
|
---@param code string
|
||||||
---@return string?
|
---@return string?
|
||||||
local function format(code)
|
local function format(code)
|
||||||
local char, hl = indicator(code)
|
local char, hl = M.indicator(code)
|
||||||
if not char then
|
if not char then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -53,7 +55,7 @@ end
|
|||||||
---@param path string
|
---@param path string
|
||||||
---@return string? gitdir
|
---@return string? gitdir
|
||||||
---@return string? worktree
|
---@return string? worktree
|
||||||
local function resolve(path)
|
function M.resolve(path)
|
||||||
local found = vim.fs.find(".git", { upward = true, path = path })[1]
|
local found = vim.fs.find(".git", { upward = true, path = path })[1]
|
||||||
if not found then
|
if not found then
|
||||||
return nil
|
return nil
|
||||||
@@ -88,12 +90,12 @@ end
|
|||||||
---both when not inside a git repo.
|
---both when not inside a git repo.
|
||||||
---@return string? gitdir
|
---@return string? gitdir
|
||||||
---@return string? worktree
|
---@return string? worktree
|
||||||
local function resolve_cwd()
|
function M.resolve_cwd()
|
||||||
local path = vim.api.nvim_buf_get_name(0)
|
local path = vim.api.nvim_buf_get_name(0)
|
||||||
if path == "" then
|
if path == "" then
|
||||||
path = vim.fn.getcwd()
|
path = vim.fn.getcwd()
|
||||||
end
|
end
|
||||||
return resolve(path)
|
return M.resolve(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class ow.Git.Repo
|
---@class ow.Git.Repo
|
||||||
@@ -152,15 +154,17 @@ local function do_refresh(repo)
|
|||||||
-- path lines, so it ignores `##` lines below. Running with `--branch`
|
-- path lines, so it ignores `##` lines below. Running with `--branch`
|
||||||
-- lets the sidebar reuse this single subprocess via the GitRefresh
|
-- lets the sidebar reuse this single subprocess via the GitRefresh
|
||||||
-- data payload instead of spawning its own.
|
-- data payload instead of spawning its own.
|
||||||
vim.system({
|
vim.system(
|
||||||
|
{
|
||||||
"git",
|
"git",
|
||||||
"-c",
|
"-c",
|
||||||
"core.quotePath=false",
|
"core.quotePath=false",
|
||||||
"status",
|
"status",
|
||||||
"--porcelain=v1",
|
"--porcelain=v1",
|
||||||
"--branch",
|
"--branch",
|
||||||
}, { cwd = repo.worktree, text = true }, function(obj)
|
},
|
||||||
vim.schedule(function()
|
{ cwd = repo.worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
local statuses = {}
|
local statuses = {}
|
||||||
if obj.code == 0 then
|
if obj.code == 0 then
|
||||||
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
|
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
|
||||||
@@ -210,7 +214,7 @@ local function do_refresh(repo)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param gitdir string
|
---@param gitdir string
|
||||||
@@ -244,7 +248,7 @@ local function register(buf)
|
|||||||
if path == "" then
|
if path == "" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local gitdir, worktree = resolve(path)
|
local gitdir, worktree = M.resolve(path)
|
||||||
if not gitdir or not worktree then
|
if not gitdir or not worktree then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -259,7 +263,7 @@ local function register(buf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
local function unregister(buf)
|
function M.unregister(buf)
|
||||||
local repo = repo_by_buf[buf]
|
local repo = repo_by_buf[buf]
|
||||||
if not repo then
|
if not repo then
|
||||||
return
|
return
|
||||||
@@ -273,7 +277,7 @@ local function unregister(buf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
local function refresh_buf(buf)
|
function M.refresh_buf(buf)
|
||||||
if not vim.api.nvim_buf_is_valid(buf) or vim.bo[buf].buftype ~= "" then
|
if not vim.api.nvim_buf_is_valid(buf) or vim.bo[buf].buftype ~= "" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -285,7 +289,7 @@ local function refresh_buf(buf)
|
|||||||
repo:refresh()
|
repo:refresh()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function stop_all()
|
function M.stop_all()
|
||||||
for _, repo in pairs(repo_by_gitdir) do
|
for _, repo in pairs(repo_by_gitdir) do
|
||||||
repo:stop_watcher()
|
repo:stop_watcher()
|
||||||
end
|
end
|
||||||
@@ -293,8 +297,8 @@ end
|
|||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string?
|
---@return string?
|
||||||
local function head(path)
|
function M.head(path)
|
||||||
local gitdir = resolve(path)
|
local gitdir = M.resolve(path)
|
||||||
if not gitdir then
|
if not gitdir then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -318,39 +322,35 @@ local function head(path)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---Resolve a git revision to its object SHA. Returns nil if the ref can't be
|
---Resolve a git revision to its object SHA. Calls `callback(sha?)` on the
|
||||||
---resolved (root-commit's `^`, blob's `^`, malformed ref, etc.). When `short`
|
---main loop with nil if the ref can't be parsed (root-commit's `^`, blob's
|
||||||
---is true, the result is abbreviated via `core.abbrev` (auto-extended by git
|
---`^`, malformed ref, etc.). When `short` is true, the result is abbreviated
|
||||||
---to keep the prefix unique in the current repo).
|
---via `core.abbrev` (auto-extended by git to keep the prefix unique in the
|
||||||
|
---current repo).
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param ref string
|
---@param ref string
|
||||||
---@param short? boolean
|
---@param short boolean
|
||||||
---@return string?
|
---@param callback fun(sha: string?)
|
||||||
local function rev_parse(worktree, ref, short)
|
function M.rev_parse(worktree, ref, short, callback)
|
||||||
local cmd = { "git", "rev-parse", "--verify", "--quiet" }
|
local cmd = { "git", "rev-parse", "--verify", "--quiet" }
|
||||||
if short then
|
if short then
|
||||||
table.insert(cmd, "--short")
|
table.insert(cmd, "--short")
|
||||||
end
|
end
|
||||||
table.insert(cmd, ref)
|
table.insert(cmd, ref)
|
||||||
local result = vim.system(cmd, { cwd = worktree, text = true }):wait()
|
vim.system(
|
||||||
if result.code ~= 0 then
|
cmd,
|
||||||
return nil
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(result)
|
||||||
|
local sha
|
||||||
|
if result.code == 0 then
|
||||||
|
local trimmed = vim.trim(result.stdout or "")
|
||||||
|
if trimmed ~= "" then
|
||||||
|
sha = trimmed
|
||||||
end
|
end
|
||||||
local sha = vim.trim(result.stdout or "")
|
|
||||||
if sha == "" then
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
return sha
|
callback(sha)
|
||||||
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return M
|
||||||
UNMERGED = UNMERGED,
|
|
||||||
head = head,
|
|
||||||
indicator = indicator,
|
|
||||||
refresh_buf = refresh_buf,
|
|
||||||
resolve = resolve,
|
|
||||||
resolve_cwd = resolve_cwd,
|
|
||||||
rev_parse = rev_parse,
|
|
||||||
stop_all = stop_all,
|
|
||||||
unregister = unregister,
|
|
||||||
}
|
|
||||||
|
|||||||
+50
-27
@@ -1,4 +1,5 @@
|
|||||||
local diff = require("git.diff")
|
local diff = require("git.diff")
|
||||||
|
local git = require("git")
|
||||||
local log = require("log")
|
local log = require("log")
|
||||||
local repo = require("git.repo")
|
local repo = require("git.repo")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
@@ -111,8 +112,8 @@ end
|
|||||||
---@param ref string
|
---@param ref string
|
||||||
local function show_blob(worktree, blob, path, ref)
|
local function show_blob(worktree, blob, path, ref)
|
||||||
local buf = blob_buf(worktree, blob, path, ref)
|
local buf = blob_buf(worktree, blob, path, ref)
|
||||||
vim.cmd("normal! m'")
|
vim.cmd.normal({ "m'", bang = true })
|
||||||
vim.cmd("buffer " .. buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param ctx ow.Git.ShowContext
|
---@param ctx ow.Git.ShowContext
|
||||||
@@ -127,53 +128,75 @@ local function show_diff(ctx, section)
|
|||||||
blob_buf(ctx.worktree, section.pre_blob, section.pre_path, parent)
|
blob_buf(ctx.worktree, section.pre_blob, section.pre_path, parent)
|
||||||
local right =
|
local right =
|
||||||
blob_buf(ctx.worktree, section.post_blob, section.post_path, ctx.ref)
|
blob_buf(ctx.worktree, section.post_blob, section.post_path, ctx.ref)
|
||||||
vim.cmd("normal! m'")
|
vim.cmd.normal({ "m'", bang = true })
|
||||||
vim.cmd("buffer " .. left)
|
local left_win = vim.api.nvim_get_current_win()
|
||||||
vim.cmd("diffthis")
|
vim.api.nvim_set_current_buf(left)
|
||||||
vim.cmd("rightbelow vertical sbuffer " .. right)
|
vim.wo[left_win].diff = true
|
||||||
vim.cmd("diffthis")
|
local right_win =
|
||||||
vim.cmd("wincmd p")
|
vim.api.nvim_open_win(right, true, { split = "right", win = left_win })
|
||||||
|
vim.wo[right_win].diff = true
|
||||||
|
vim.api.nvim_set_current_win(left_win)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param ref string
|
---@param ref string
|
||||||
function M.open_commit(worktree, ref)
|
function M.open_commit(worktree, ref)
|
||||||
local sha = repo.rev_parse(worktree, ref, true) or ref
|
repo.rev_parse(worktree, ref, true, function(resolved)
|
||||||
|
local sha = resolved or ref
|
||||||
local name = "git://" .. sha .. "/"
|
local name = "git://" .. sha .. "/"
|
||||||
-- Reuse a previously-opened buffer for the same commit; commit SHAs
|
-- Reuse a previously-opened buffer for the same commit; commit SHAs
|
||||||
-- are immutable so the content is stable.
|
-- are immutable so the content is stable.
|
||||||
local existing = vim.fn.bufnr(name)
|
local existing = vim.fn.bufnr(name)
|
||||||
if existing ~= -1 and vim.api.nvim_buf_is_loaded(existing) then
|
if existing ~= -1 and vim.api.nvim_buf_is_loaded(existing) then
|
||||||
vim.cmd("normal! m'")
|
vim.api.nvim_open_win(existing, true, {
|
||||||
vim.cmd("buffer " .. existing)
|
split = vim.o.splitbelow and "below" or "above",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local result = vim.system(
|
local buf, win = git.new_scratch({ name = name })
|
||||||
|
vim.b[buf].git_worktree = worktree
|
||||||
|
vim.b[buf].git_ref = sha
|
||||||
|
|
||||||
|
vim.system(
|
||||||
{ "git", "show", ref },
|
{ "git", "show", ref },
|
||||||
{ cwd = worktree, text = true }
|
{ cwd = worktree, text = true },
|
||||||
)
|
vim.schedule_wrap(function(result)
|
||||||
:wait()
|
|
||||||
if result.code ~= 0 then
|
if result.code ~= 0 then
|
||||||
log.error("git show %s failed: %s", ref, result.stderr or "")
|
log.error(
|
||||||
|
"git show %s failed: %s",
|
||||||
|
ref,
|
||||||
|
vim.trim(result.stderr or "")
|
||||||
|
)
|
||||||
|
-- Tear down the empty placeholder window+buffer so a
|
||||||
|
-- retry runs a fresh fetch instead of hitting the
|
||||||
|
-- cached-buffer branch and reopening an empty pane.
|
||||||
|
if vim.api.nvim_win_is_valid(win) then
|
||||||
|
pcall(vim.api.nvim_win_close, win, true)
|
||||||
|
end
|
||||||
|
if vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local lines = util.split_lines(result.stdout or "")
|
local lines = util.split_lines(result.stdout or "")
|
||||||
local parent = repo.rev_parse(worktree, ref .. "^", true)
|
repo.rev_parse(worktree, ref .. "^", true, function(parent)
|
||||||
local buf = vim.api.nvim_create_buf(false, true)
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
vim.bo[buf].buftype = "nofile"
|
return
|
||||||
vim.bo[buf].bufhidden = "hide"
|
end
|
||||||
vim.bo[buf].swapfile = false
|
vim.b[buf].git_parent_ref = parent
|
||||||
|
vim.bo[buf].modifiable = true
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
vim.bo[buf].modified = false
|
vim.bo[buf].modified = false
|
||||||
pcall(vim.api.nvim_buf_set_name, buf, name)
|
|
||||||
vim.b[buf].git_worktree = worktree
|
|
||||||
vim.b[buf].git_ref = sha
|
|
||||||
vim.b[buf].git_parent_ref = parent
|
|
||||||
vim.bo[buf].filetype = "git"
|
vim.bo[buf].filetype = "git"
|
||||||
vim.cmd("normal! m'")
|
end)
|
||||||
vim.cmd("buffer " .. buf)
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return boolean dispatched true if the cursor was on an actionable line
|
---@return boolean dispatched true if the cursor was on an actionable line
|
||||||
|
|||||||
+44
-44
@@ -1,4 +1,5 @@
|
|||||||
local diff = require("git.diff")
|
local diff = require("git.diff")
|
||||||
|
local git = require("git")
|
||||||
local log = require("log")
|
local log = require("log")
|
||||||
local repo = require("git.repo")
|
local repo = require("git.repo")
|
||||||
|
|
||||||
@@ -240,13 +241,15 @@ local function enrich_with_log(worktree, branch, groups, callback)
|
|||||||
end
|
end
|
||||||
local pending = #fetches
|
local pending = #fetches
|
||||||
for _, f in ipairs(fetches) do
|
for _, f in ipairs(fetches) do
|
||||||
vim.system({
|
vim.system(
|
||||||
|
{
|
||||||
"git",
|
"git",
|
||||||
"log",
|
"log",
|
||||||
"--format=%h %s",
|
"--format=%h %s",
|
||||||
f.range,
|
f.range,
|
||||||
}, { cwd = worktree, text = true }, function(log_obj)
|
},
|
||||||
vim.schedule(function()
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(log_obj)
|
||||||
if log_obj.code == 0 then
|
if log_obj.code == 0 then
|
||||||
for line in (log_obj.stdout or ""):gmatch("[^\r\n]+") do
|
for line in (log_obj.stdout or ""):gmatch("[^\r\n]+") do
|
||||||
local sha, subject = line:match("^(%S+)%s+(.+)$")
|
local sha, subject = line:match("^(%S+)%s+(.+)$")
|
||||||
@@ -270,7 +273,7 @@ local function enrich_with_log(worktree, branch, groups, callback)
|
|||||||
callback(branch, groups)
|
callback(branch, groups)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -287,15 +290,17 @@ local function fetch_status(worktree, prefetched_stdout, callback)
|
|||||||
enrich_with_log(worktree, branch, groups, callback)
|
enrich_with_log(worktree, branch, groups, callback)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.system({
|
vim.system(
|
||||||
|
{
|
||||||
"git",
|
"git",
|
||||||
"-c",
|
"-c",
|
||||||
"core.quotePath=false",
|
"core.quotePath=false",
|
||||||
"status",
|
"status",
|
||||||
"--porcelain=v1",
|
"--porcelain=v1",
|
||||||
"--branch",
|
"--branch",
|
||||||
}, { cwd = worktree, text = true }, function(obj)
|
},
|
||||||
vim.schedule(function()
|
{ cwd = worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
||||||
local branch = { ahead = 0, behind = 0 }
|
local branch = { ahead = 0, behind = 0 }
|
||||||
@@ -313,7 +318,7 @@ local function fetch_status(worktree, prefetched_stdout, callback)
|
|||||||
local branch, groups = parse_porcelain(obj.stdout or "")
|
local branch, groups = parse_porcelain(obj.stdout or "")
|
||||||
enrich_with_log(worktree, branch, groups, callback)
|
enrich_with_log(worktree, branch, groups, callback)
|
||||||
end)
|
end)
|
||||||
end)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
@@ -598,9 +603,7 @@ end
|
|||||||
---@param win integer
|
---@param win integer
|
||||||
---@param enabled boolean
|
---@param enabled boolean
|
||||||
local function set_diff(win, enabled)
|
local function set_diff(win, enabled)
|
||||||
vim.api.nvim_win_call(win, function()
|
vim.wo[win].diff = enabled
|
||||||
vim.cmd(enabled and "diffthis" or "diffoff")
|
|
||||||
end)
|
|
||||||
if enabled then
|
if enabled then
|
||||||
vim.wo[win].foldenable = true
|
vim.wo[win].foldenable = true
|
||||||
vim.wo[win].foldlevel = 0
|
vim.wo[win].foldlevel = 0
|
||||||
@@ -642,6 +645,21 @@ local function entry_key(entry)
|
|||||||
return entry.section .. "|" .. entry.path .. "|" .. (entry.orig or "")
|
return entry.section .. "|" .. entry.path .. "|" .. (entry.orig or "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Split `target_win` and put its buffer into the new window. Matches the
|
||||||
|
---semantics of `:set_current_win(target); :rightbelow/leftabove vertical
|
||||||
|
---split` (the new window inherits the target's buffer; caller swaps it
|
||||||
|
---afterwards via `nvim_win_set_buf`).
|
||||||
|
---@param target_win integer
|
||||||
|
---@param dir "left"|"right"
|
||||||
|
---@return integer
|
||||||
|
local function vsplit_at(target_win, dir)
|
||||||
|
return vim.api.nvim_open_win(
|
||||||
|
vim.api.nvim_win_get_buf(target_win),
|
||||||
|
true,
|
||||||
|
{ split = dir, win = target_win }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
---@param s ow.Git.StatusState
|
---@param s ow.Git.StatusState
|
||||||
---@param entry ow.Git.StatusEntry
|
---@param entry ow.Git.StatusEntry
|
||||||
---@param focus_left boolean
|
---@param focus_left boolean
|
||||||
@@ -673,23 +691,17 @@ local function show_diff(s, entry, focus_left)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if left_win and not right_win then
|
if left_win and not right_win then
|
||||||
vim.api.nvim_set_current_win(left_win)
|
right_win = vsplit_at(left_win, "right")
|
||||||
vim.cmd("rightbelow vertical split")
|
|
||||||
right_win = vim.api.nvim_get_current_win()
|
|
||||||
reset_diff_win(right_win)
|
reset_diff_win(right_win)
|
||||||
elseif right_win and not left_win then
|
elseif right_win and not left_win then
|
||||||
vim.api.nvim_set_current_win(right_win)
|
left_win = vsplit_at(right_win, "left")
|
||||||
vim.cmd("leftabove vertical split")
|
|
||||||
left_win = vim.api.nvim_get_current_win()
|
|
||||||
reset_diff_win(left_win)
|
reset_diff_win(left_win)
|
||||||
elseif not (left_win or right_win) then
|
elseif not (left_win or right_win) then
|
||||||
local default_main = find_default_main_win(sidebar_win)
|
local default_main = find_default_main_win(sidebar_win)
|
||||||
if default_main then
|
if default_main then
|
||||||
right_win = default_main
|
right_win = default_main
|
||||||
reset_diff_win(right_win)
|
reset_diff_win(right_win)
|
||||||
vim.api.nvim_set_current_win(default_main)
|
left_win = vsplit_at(right_win, "left")
|
||||||
vim.cmd("leftabove vertical split")
|
|
||||||
left_win = vim.api.nvim_get_current_win()
|
|
||||||
reset_diff_win(left_win)
|
reset_diff_win(left_win)
|
||||||
else
|
else
|
||||||
-- No reusable default-empty window. Open the diff pair by
|
-- No reusable default-empty window. Open the diff pair by
|
||||||
@@ -697,12 +709,9 @@ local function show_diff(s, entry, focus_left)
|
|||||||
-- when there are other windows to absorb the split; if the
|
-- when there are other windows to absorb the split; if the
|
||||||
-- sidebar is the only window in the tab, the split has to take
|
-- sidebar is the only window in the tab, the split has to take
|
||||||
-- from the sidebar itself, so restore the width explicitly.
|
-- from the sidebar itself, so restore the width explicitly.
|
||||||
vim.api.nvim_set_current_win(sidebar_win)
|
right_win = vsplit_at(sidebar_win, "right")
|
||||||
vim.cmd("rightbelow vertical split")
|
|
||||||
right_win = vim.api.nvim_get_current_win()
|
|
||||||
reset_diff_win(right_win)
|
reset_diff_win(right_win)
|
||||||
vim.cmd("leftabove vertical split")
|
left_win = vsplit_at(right_win, "left")
|
||||||
left_win = vim.api.nvim_get_current_win()
|
|
||||||
reset_diff_win(left_win)
|
reset_diff_win(left_win)
|
||||||
vim.api.nvim_win_set_width(sidebar_win, SIDEBAR_WIDTH)
|
vim.api.nvim_win_set_width(sidebar_win, SIDEBAR_WIDTH)
|
||||||
end
|
end
|
||||||
@@ -756,13 +765,11 @@ local function action_stage()
|
|||||||
vim.system(
|
vim.system(
|
||||||
{ "git", "add", "--", entry.path },
|
{ "git", "add", "--", entry.path },
|
||||||
{ cwd = s.worktree },
|
{ cwd = s.worktree },
|
||||||
function(obj)
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
vim.schedule(function()
|
|
||||||
log.error("git add failed: %s", vim.trim(obj.stderr or ""))
|
log.error("git add failed: %s", vim.trim(obj.stderr or ""))
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -780,16 +787,18 @@ local function action_unstage()
|
|||||||
table.insert(cmd, entry.orig)
|
table.insert(cmd, entry.orig)
|
||||||
end
|
end
|
||||||
table.insert(cmd, entry.path)
|
table.insert(cmd, entry.path)
|
||||||
vim.system(cmd, { cwd = s.worktree }, function(obj)
|
vim.system(
|
||||||
|
cmd,
|
||||||
|
{ cwd = s.worktree },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
vim.schedule(function()
|
|
||||||
log.error(
|
log.error(
|
||||||
"git restore --staged failed: %s",
|
"git restore --staged failed: %s",
|
||||||
vim.trim(obj.stderr or "")
|
vim.trim(obj.stderr or "")
|
||||||
)
|
)
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function action_discard()
|
local function action_discard()
|
||||||
@@ -828,16 +837,14 @@ local function action_discard()
|
|||||||
vim.system(
|
vim.system(
|
||||||
{ "git", "checkout", "--", entry.path },
|
{ "git", "checkout", "--", entry.path },
|
||||||
{ cwd = s.worktree },
|
{ cwd = s.worktree },
|
||||||
function(obj)
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
vim.schedule(function()
|
|
||||||
log.error(
|
log.error(
|
||||||
"git checkout failed: %s",
|
"git checkout failed: %s",
|
||||||
vim.trim(obj.stderr or "")
|
vim.trim(obj.stderr or "")
|
||||||
)
|
)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -875,15 +882,8 @@ local function open(worktree)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local previous_win = vim.api.nvim_get_current_win()
|
local previous_win = vim.api.nvim_get_current_win()
|
||||||
vim.cmd("leftabove vertical new")
|
local bufnr, win = git.new_scratch({ split = "left", bufhidden = "wipe" })
|
||||||
local win = vim.api.nvim_get_current_win()
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
|
|
||||||
vim.bo[bufnr].buftype = "nofile"
|
|
||||||
vim.bo[bufnr].bufhidden = "wipe"
|
|
||||||
vim.bo[bufnr].swapfile = false
|
|
||||||
vim.bo[bufnr].filetype = "gitstatus"
|
vim.bo[bufnr].filetype = "gitstatus"
|
||||||
vim.bo[bufnr].modifiable = false
|
|
||||||
|
|
||||||
vim.wo[win].number = false
|
vim.wo[win].number = false
|
||||||
vim.wo[win].relativenumber = false
|
vim.wo[win].relativenumber = false
|
||||||
|
|||||||
Reference in New Issue
Block a user