refactor(git): unify object opening through read_uri's diff-on-commit path
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ vim.keymap.set("n", "<CR>", function()
|
|||||||
.nvim_get_current_line()
|
.nvim_get_current_line()
|
||||||
:match("^[*|/\\_ ]*(%x%x%x%x%x%x%x+)")
|
:match("^[*|/\\_ ]*(%x%x%x%x%x%x%x+)")
|
||||||
if sha then
|
if sha then
|
||||||
require("git.object").open_commit(worktree, sha, { split = false })
|
require("git.object").open_object(worktree, sha, { split = false })
|
||||||
else
|
else
|
||||||
-- "n" mode = no remap, so this doesn't recurse into our mapping.
|
-- "n" mode = no remap, so this doesn't recurse into our mapping.
|
||||||
vim.api.nvim_feedkeys(cr, "n", false)
|
vim.api.nvim_feedkeys(cr, "n", false)
|
||||||
|
|||||||
+63
-102
@@ -193,11 +193,51 @@ function M.read_uri(buf)
|
|||||||
{ cwd = worktree }
|
{ cwd = worktree }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local parsed = util.parse_revspec(revspec)
|
||||||
|
|
||||||
|
-- Bare-ref objects that dereference to a commit (commits, stashes,
|
||||||
|
-- annotated tags pointing at a commit, lightweight tags) get their
|
||||||
|
-- `diff-tree -p` patch appended so the buffer is navigable: the
|
||||||
|
-- `<CR>` parser walks `diff --git` blocks. `^{commit}` is git's
|
||||||
|
-- standard "deref to commit" suffix; rev-parse fails for non-commit
|
||||||
|
-- objects (trees, blobs, tags pointing at non-commits) so they
|
||||||
|
-- naturally skip the append. `-m --first-parent` collapses merges
|
||||||
|
-- and stashes into one diff per file (vs `diff --cc` combined
|
||||||
|
-- diffs, which the parser can't follow).
|
||||||
|
if stdout and parsed.path == nil then
|
||||||
|
local commit_sha =
|
||||||
|
repo.rev_parse(worktree, revspec .. "^{commit}", true)
|
||||||
|
if commit_sha then
|
||||||
|
local patch = util.exec({
|
||||||
|
"git",
|
||||||
|
"diff-tree",
|
||||||
|
"-p",
|
||||||
|
"-m",
|
||||||
|
"--first-parent",
|
||||||
|
"--root",
|
||||||
|
"--no-commit-id",
|
||||||
|
commit_sha,
|
||||||
|
}, { cwd = worktree })
|
||||||
|
if patch then
|
||||||
|
stdout = (stdout:gsub("\n*$", "\n\n")) .. patch
|
||||||
|
end
|
||||||
|
vim.b[buf].git_parent_ref =
|
||||||
|
repo.rev_parse(worktree, commit_sha .. "^", true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if stdout then
|
if stdout then
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout))
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout))
|
||||||
end
|
end
|
||||||
|
|
||||||
local parsed = util.parse_revspec(revspec)
|
-- `b:git_ref` anchors `<CR>`-driven navigation in this buffer. Set
|
||||||
|
-- here once per load instead of having every caller do it.
|
||||||
|
local ref_sha = repo.rev_parse(worktree, revspec, true)
|
||||||
|
if ref_sha then
|
||||||
|
vim.b[buf].git_ref = ref_sha
|
||||||
|
end
|
||||||
|
|
||||||
if parsed.stage == 0 and parsed.path then
|
if parsed.stage == 0 and parsed.path then
|
||||||
vim.bo[buf].buftype = "acwrite"
|
vim.bo[buf].buftype = "acwrite"
|
||||||
-- Re-running BufReadCmd (e.g. on `:edit`) would otherwise stack
|
-- Re-running BufReadCmd (e.g. on `:edit`) would otherwise stack
|
||||||
@@ -218,12 +258,15 @@ function M.read_uri(buf)
|
|||||||
-- `://` to `:/`) before matching, breaking any pattern keyed on the
|
-- `://` to `:/`) before matching, breaking any pattern keyed on the
|
||||||
-- raw scheme as well as any built-in pattern that doesn't catch a
|
-- raw scheme as well as any built-in pattern that doesn't catch a
|
||||||
-- recognisable extension on the mangled form (.Xresources is the
|
-- recognisable extension on the mangled form (.Xresources is the
|
||||||
-- canonical example).
|
-- canonical example). Bare-ref content (commit/tag headers + tree
|
||||||
|
-- listings) uses the built-in `git` filetype.
|
||||||
if parsed.path then
|
if parsed.path then
|
||||||
local ft = vim.filetype.match({ filename = parsed.path, buf = buf })
|
local ft = vim.filetype.match({ filename = parsed.path, buf = buf })
|
||||||
if ft then
|
if ft then
|
||||||
vim.bo[buf].filetype = ft
|
vim.bo[buf].filetype = ft
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
vim.bo[buf].filetype = "git"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- BufReadCmd suppresses the normal BufReadPost dispatch, so
|
-- BufReadCmd suppresses the normal BufReadPost dispatch, so
|
||||||
@@ -277,120 +320,38 @@ local function open_section(ctx, section)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@class ow.Git.OpenObjectOpts
|
---@class ow.Git.OpenObjectOpts
|
||||||
---@field split (false|"above"|"below"|"left"|"right")? forwarded to `util.new_scratch`. Default opens a new horizontal split.
|
---@field split (false|"above"|"below"|"left"|"right")? forwarded to `util.place_buf`. Default opens a new horizontal split.
|
||||||
|
|
||||||
---Place a `git://<revspec>` URI buffer in a window per `opts.split`.
|
---Open any git object. Accepts a bare ref (commit / tree / blob / tag
|
||||||
---`bufadd` dedups against existing buffers; `bufload` no-ops if loaded.
|
---sha, branch, tag name, `stash@{N}`, etc.) or `<commit-ref>:<path>`
|
||||||
---@param worktree string
|
---form. Resolves the revspec to a sha so the URI stays stable if the
|
||||||
---@param uri string
|
---ref later moves, primes the `read_uri` cache with the `cat-file -p`
|
||||||
---@param sha string written to `b:git_ref` so `<CR>` navigation in the buffer can resolve relative paths
|
---output (one subprocess instead of two — preflight + load), then goes
|
||||||
---@param opts ow.Git.OpenObjectOpts?
|
---through the unified URI pipeline. `read_uri` then appends the
|
||||||
---@param default_ft string? applied if filetype detection didn't pick anything (bare-sha URIs have no path for the `filetype.add` pattern to match)
|
---`diff-tree -p` patch for any revspec that dereferences to a commit
|
||||||
local function open_uri(worktree, uri, sha, opts, default_ft)
|
---(commits, stashes, annotated tags pointing at commits).
|
||||||
local buf = vim.fn.bufadd(uri)
|
|
||||||
vim.b[buf].git_worktree = worktree
|
|
||||||
vim.b[buf].git_ref = sha
|
|
||||||
vim.fn.bufload(buf)
|
|
||||||
if default_ft and vim.bo[buf].filetype == "" then
|
|
||||||
vim.bo[buf].filetype = default_ft
|
|
||||||
end
|
|
||||||
util.place_buf(buf, opts and opts.split)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Open a commit's body via `git cat-file -p` for the header (raw object
|
|
||||||
---form, flush-left message) plus `git diff-tree -p` for the patch. The
|
|
||||||
---`-m --first-parent` flags collapse merges and stashes into single
|
|
||||||
---`diff --git` blocks per file, so `M.open_under_cursor`'s `<CR>` parser
|
|
||||||
---can navigate them. (`git show` would emit `diff --cc` combined diffs
|
|
||||||
---in those cases, which the parser can't follow.) Used by the gitlog
|
|
||||||
---`<CR>` flow.
|
|
||||||
---@param worktree string
|
|
||||||
---@param ref string
|
|
||||||
---@param opts ow.Git.OpenObjectOpts?
|
|
||||||
function M.open_commit(worktree, ref, opts)
|
|
||||||
local split = opts and opts.split
|
|
||||||
local sha = repo.rev_parse(worktree, ref, true) or ref
|
|
||||||
local name = util.uri(sha)
|
|
||||||
local existing = vim.fn.bufnr(name)
|
|
||||||
if existing ~= -1 and vim.api.nvim_buf_is_loaded(existing) then
|
|
||||||
util.place_buf(existing, split)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local header = util.exec(
|
|
||||||
{ "git", "cat-file", "-p", ref },
|
|
||||||
{ cwd = worktree }
|
|
||||||
)
|
|
||||||
if not header then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- `--root` lets initial commits show their full tree.
|
|
||||||
local patch = util.exec({
|
|
||||||
"git",
|
|
||||||
"diff-tree",
|
|
||||||
"-p",
|
|
||||||
"-m",
|
|
||||||
"--first-parent",
|
|
||||||
"--root",
|
|
||||||
"--no-commit-id",
|
|
||||||
ref,
|
|
||||||
}, { cwd = worktree })
|
|
||||||
if not patch then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local parent = repo.rev_parse(worktree, ref .. "^", true)
|
|
||||||
local buf, _ = util.new_scratch({ name = name, split = split })
|
|
||||||
vim.b[buf].git_worktree = worktree
|
|
||||||
vim.b[buf].git_ref = sha
|
|
||||||
vim.b[buf].git_parent_ref = parent
|
|
||||||
vim.bo[buf].modifiable = true
|
|
||||||
-- Normalise to exactly one blank line between the message body and
|
|
||||||
-- the patch, regardless of trailing newlines on the header.
|
|
||||||
local content = (header:gsub("\n*$", "\n\n")) .. patch
|
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(content))
|
|
||||||
vim.bo[buf].modifiable = false
|
|
||||||
vim.bo[buf].modified = false
|
|
||||||
vim.bo[buf].filetype = "git"
|
|
||||||
end
|
|
||||||
|
|
||||||
---Open any git object. Accepts either a bare ref (commit/tree/blob/tag
|
|
||||||
---SHA, branch name, etc.) or `<commit-ref>:<path>` form. Commits route
|
|
||||||
---to `M.open_commit` for the message + diff view; everything else flows
|
|
||||||
---through the BufReadCmd loader at the `git://<revspec>` URI.
|
|
||||||
---@param worktree string
|
---@param worktree string
|
||||||
---@param ref string
|
---@param ref string
|
||||||
---@param opts ow.Git.OpenObjectOpts?
|
---@param opts ow.Git.OpenObjectOpts?
|
||||||
function M.open_object(worktree, ref, opts)
|
function M.open_object(worktree, ref, opts)
|
||||||
-- Path-form: resolve the commit-ref to a sha so the URI stays stable
|
|
||||||
-- if the ref later moves, and the `filetype.add` pattern can pick the
|
|
||||||
-- ft from the path segment.
|
|
||||||
local commit_ref, path = ref:match("^(.-):(.+)$")
|
local commit_ref, path = ref:match("^(.-):(.+)$")
|
||||||
|
local revspec
|
||||||
if commit_ref then
|
if commit_ref then
|
||||||
local sha = repo.rev_parse(worktree, commit_ref, true) or commit_ref
|
local sha = repo.rev_parse(worktree, commit_ref, true) or commit_ref
|
||||||
open_uri(worktree, util.uri(sha .. ":" .. path), sha, opts)
|
revspec = sha .. ":" .. path
|
||||||
return
|
else
|
||||||
|
revspec = repo.rev_parse(worktree, ref, true) or ref
|
||||||
end
|
end
|
||||||
|
local content = util.exec(
|
||||||
local type_out = util.exec(
|
{ "git", "cat-file", "-p", revspec },
|
||||||
{ "git", "cat-file", "-t", ref },
|
|
||||||
{ cwd = worktree, silent = true }
|
{ cwd = worktree, silent = true }
|
||||||
)
|
)
|
||||||
local obj_type = type_out and vim.trim(type_out) or ""
|
if not content then
|
||||||
if obj_type == "" then
|
|
||||||
util.warning("not a git object: %s", ref)
|
util.warning("not a git object: %s", ref)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if obj_type == "commit" then
|
local buf = M.buf_for(worktree, revspec, content)
|
||||||
M.open_commit(worktree, ref, opts)
|
util.place_buf(buf, opts and opts.split)
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Trees, blobs, tags. The bare-sha URI has no path, so the
|
|
||||||
-- `filetype.add` pattern doesn't match; default to `git` so
|
|
||||||
-- tree / tag header lines syntax-highlight.
|
|
||||||
local sha = repo.rev_parse(worktree, ref, true) or ref
|
|
||||||
open_uri(worktree, util.uri(sha), sha, opts, "git")
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user