diff --git a/lua/git/object.lua b/lua/git/object.lua index eaa502c..22e8876 100644 --- a/lua/git/object.lua +++ b/lua/git/object.lua @@ -337,10 +337,45 @@ local function side_buf(r, blob, path) end ---@param r ow.Git.Repo +---@param commit string +---@param parent boolean +---@param path string +---@return integer? +local function commit_side_buf(r, commit, parent, path) + local rev = parent and (commit .. "^") or commit + local sha = r:rev_parse(rev, false) + if not sha then + return nil + end + if not r:rev_parse(sha .. ":" .. path, false) then + return nil + end + return M.buf_for(r, Revision.new({ base = sha, path = path })) +end + +---@param r ow.Git.Repo +---@param commit string? +---@param parent boolean ---@param blob string? ---@param path string -local function load_side(r, blob, path) - local buf = side_buf(r, blob, path) +---@return integer? +local function side(r, commit, parent, blob, path) + if commit then + local buf = commit_side_buf(r, commit, parent, path) + if buf then + return buf + end + end + return side_buf(r, blob, path) +end + +---@param r ow.Git.Repo +---@param commit string? +---@param parent boolean +---@param blob string? +---@param path string +local function load_side(r, commit, parent, blob, path) + local buf = side(r, commit, parent, blob, path) if not buf then util.error("no content for %s", path) return @@ -350,14 +385,15 @@ local function load_side(r, blob, path) end ---@param r ow.Git.Repo +---@param commit string? ---@param section ow.Git.DiffSection -local function open_section(r, section) +local function open_section(r, commit, section) if not section.blob_a or not section.blob_b then util.error("no index line, cannot determine blob SHAs") return end - local left = side_buf(r, section.blob_a, section.path_a) - local right = side_buf(r, section.blob_b, section.path_b) + local left = side(r, commit, true, section.blob_a, section.path_a) + local right = side(r, commit, false, section.blob_b, section.path_b) if left and right then vim.cmd.normal({ "m'", bang = true }) vim.api.nvim_set_current_buf(right) @@ -377,6 +413,21 @@ local function open_section(r, section) vim.api.nvim_set_current_buf(buf) end +---@param r ow.Git.Repo +---@param buf integer +---@return string? +local function commit_in_buf(r, buf) + local name = vim.api.nvim_buf_get_name(buf) + if not vim.startswith(name, M.URI_PREFIX) then + return nil + end + local rev = M.parse_uri(name) + if not rev or not rev.base or rev.path then + return nil + end + return r:rev_parse(rev.base .. "^{commit}", false) +end + ---@class ow.Git.Object.OpenOpts ---@field split (false|"above"|"below"|"left"|"right")? @@ -423,7 +474,7 @@ function M.open_under_cursor() line:match("^%d+ (%w+) (%x+)\t(.+)$") if entry_sha then if entry_type == "blob" then - load_side(r, entry_sha, entry_name --[[@as string]]) + load_side(r, nil, false, entry_sha, entry_name --[[@as string]]) else M.open(r, entry_sha, { split = false }) end @@ -435,24 +486,26 @@ function M.open_under_cursor() return false end + local commit = commit_in_buf(r, vim.api.nvim_get_current_buf()) + if line:match("^diff %-%-git ") then - open_section(r, section) + open_section(r, commit, section) return true end if line:match("^%-%-%- ") then - load_side(r, section.blob_a, section.path_a) + load_side(r, commit, true, section.blob_a, section.path_a) return true end if line:match("^%+%+%+ ") then - load_side(r, section.blob_b, section.path_b) + load_side(r, commit, false, section.blob_b, section.path_b) return true end local prefix = line:sub(1, 1) if prefix == "+" then - load_side(r, section.blob_b, section.path_b) + load_side(r, commit, false, section.blob_b, section.path_b) return true elseif prefix == "-" then - load_side(r, section.blob_a, section.path_a) + load_side(r, commit, true, section.blob_a, section.path_a) return true end return false diff --git a/test/git/blame_test.lua b/test/git/blame_test.lua index 28a4510..b75fd79 100644 --- a/test/git/blame_test.lua +++ b/test/git/blame_test.lua @@ -473,19 +473,20 @@ t.test( local object = require("git.object") local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" }) local sha = h.git(dir, "rev-parse", "HEAD").stdout + local tree = h.git(dir, "rev-parse", "HEAD^{tree}").stdout local blob = h.git(dir, "rev-parse", "HEAD:a.txt").stdout local r = assert(require("git.core.repo").resolve(dir)) - object.open(r, sha, { split = false }) - local commit_buf = vim.api.nvim_get_current_buf() + object.open(r, tree, { split = false }) + local tree_buf = vim.api.nvim_get_current_buf() local found ---@type integer? - for i, l in ipairs(vim.api.nvim_buf_get_lines(commit_buf, 0, -1, false)) do - if l == "+++ b/a.txt" then + for i, l in ipairs(vim.api.nvim_buf_get_lines(tree_buf, 0, -1, false)) do + if l:match("^%d+ blob %x+\ta%.txt$") then found = i break end end - local lnum = assert(found, "expected a +++ b/a.txt line") + local lnum = assert(found, "expected an a.txt tree entry") vim.api.nvim_win_set_cursor(0, { lnum, 0 }) t.truthy(object.open_under_cursor()) local gbuf = vim.api.nvim_get_current_buf() diff --git a/test/git/object_test.lua b/test/git/object_test.lua index b90f9ee..bbe9a82 100644 --- a/test/git/object_test.lua +++ b/test/git/object_test.lua @@ -202,18 +202,58 @@ t.test("open_under_cursor on a 'parent ' line opens the parent", function() t.eq(vim.api.nvim_buf_get_name(0), "git://" .. parent_sha) end) -t.test("open_under_cursor on a '+++ b/' line loads the blob", function() - local dir = h.make_repo({ ["a.txt"] = "first\n" }) - local r = assert(require("git.core.repo").resolve(dir)) - local blob_sha = h.git(dir, "rev-parse", "HEAD:a.txt").stdout +t.test( + "open_under_cursor on a '+++ b/' line opens the file at the commit", + function() + local dir = h.make_repo({ ["a.txt"] = "first\n" }) + local r = assert(require("git.core.repo").resolve(dir)) + local commit_sha = h.git(dir, "rev-parse", "HEAD").stdout - object.open(r, "HEAD", { split = false }) - local lnum = assert(find_line(0, "+++ b/a.txt"), "expected a +++ line") - vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + object.open(r, "HEAD", { split = false }) + local lnum = assert(find_line(0, "+++ b/a.txt"), "expected a +++ line") + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) - t.truthy(object.open_under_cursor()) - t.eq(vim.api.nvim_buf_get_name(0), "git://" .. blob_sha) -end) + t.truthy(object.open_under_cursor()) + t.eq(vim.api.nvim_buf_get_name(0), "git://" .. commit_sha .. ":a.txt") + end +) + +t.test( + "open_under_cursor on a '--- a/' line opens the parent at that path", + function() + local dir = h.make_repo({ ["a.txt"] = "first\n" }) + t.write(dir, "a.txt", "second\n") + h.git(dir, "add", "a.txt") + h.git(dir, "commit", "-q", "-m", "second") + local r = assert(require("git.core.repo").resolve(dir)) + local parent_sha = h.git(dir, "rev-parse", "HEAD~").stdout + + object.open(r, "HEAD", { split = false }) + local lnum = assert(find_line(0, "--- a/a.txt"), "expected a --- line") + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + + t.truthy(object.open_under_cursor()) + t.eq(vim.api.nvim_buf_get_name(0), "git://" .. parent_sha .. ":a.txt") + end +) + +t.test( + "open_under_cursor on a tree-entry blob still uses the bare blob URI", + function() + local dir = h.make_repo({ ["a.txt"] = "first\n" }) + local r = assert(require("git.core.repo").resolve(dir)) + local tree_sha = h.git(dir, "rev-parse", "HEAD^{tree}").stdout + local blob_sha = h.git(dir, "rev-parse", "HEAD:a.txt").stdout + + object.open(r, tree_sha, { split = false }) + local lnum = + assert(find_line(0, "100644 blob "), "expected a tree entry line") + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + + t.truthy(object.open_under_cursor()) + t.eq(vim.api.nvim_buf_get_name(0), "git://" .. blob_sha) + end +) t.test("open_under_cursor returns false on a non-dispatchable line", function() local dir = h.make_repo({ a = "first\n" })