From 504c4a5ac1554264b7a941c163b6940ce76ef3f2 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Tue, 28 Apr 2026 11:09:38 +0200 Subject: [PATCH] feat(git): navigate any git object from cat-file headers --- lua/git/object.lua | 73 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/lua/git/object.lua b/lua/git/object.lua index 882a619..d14f76f 100644 --- a/lua/git/object.lua +++ b/lua/git/object.lua @@ -193,6 +193,63 @@ function M.open_commit(worktree, ref, opts) vim.bo[buf].filetype = "git" end +---Open any git object in a buffer. Commits get the full +---`M.open_commit` view (cat-file + diff-tree). Trees, blobs, and tags +---dump `git cat-file -p` output as-is. The object type is detected via +---`git cat-file -t`. +---@param worktree string +---@param ref string +---@param opts ow.Git.OpenCommitOpts? +function M.open_object(worktree, ref, opts) + local type_out = util.exec( + { "git", "cat-file", "-t", ref }, + { cwd = worktree, silent = true } + ) + local obj_type = type_out and vim.trim(type_out) or "" + if obj_type == "" then + util.warning("not a git object: %s", ref) + return + end + + if obj_type == "commit" then + M.open_commit(worktree, ref, opts) + return + end + + local split = opts and opts.split + local sha = repo.rev_parse(worktree, ref, true) or ref + local name = "git://" .. sha .. "//" + local existing = vim.fn.bufnr(name) + if existing ~= -1 and vim.api.nvim_buf_is_loaded(existing) then + if split == false then + vim.cmd.normal({ "m'", bang = true }) + vim.api.nvim_set_current_buf(existing) + else + vim.api.nvim_open_win(existing, true, { + split = split or (vim.o.splitbelow and "below" or "above"), + }) + end + return + end + + local stdout = util.exec( + { "git", "cat-file", "-p", ref }, + { cwd = worktree } + ) + if not stdout then + return + end + + local buf, _ = git.new_scratch({ name = name, split = split }) + vim.b[buf].git_worktree = worktree + vim.b[buf].git_ref = sha + vim.bo[buf].modifiable = true + vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout)) + vim.bo[buf].modifiable = false + vim.bo[buf].modified = false + vim.bo[buf].filetype = "git" +end + ---@return boolean dispatched true if the cursor was on an actionable line function M.open_under_cursor() local ctx = context() @@ -202,12 +259,16 @@ function M.open_under_cursor() local line = vim.api.nvim_get_current_line() - -- Cat-file header navigation. `parent ` opens the referenced - -- commit. (`git show` doesn't emit a `parent` line, so this only - -- fires inside `M.open_commit` buffers.) - local parent_sha = line:match("^parent (%x+)$") - if parent_sha then - M.open_commit(ctx.worktree, parent_sha, { split = false }) + -- Cat-file header navigation: `parent ` (commit), `tree ` + -- (commit / tag), `object ` (tag's referent) all reference + -- another git object. Tree-entry lines ` \t` + -- navigate to a child blob or subtree. + local sha = line:match("^parent (%x+)$") + or line:match("^tree (%x+)$") + or line:match("^object (%x+)$") + or line:match("^%d+ %w+ (%x+)\t.+$") + if sha then + M.open_object(ctx.worktree, sha, { split = false }) return true end