diff --git a/lua/git/cmd.lua b/lua/git/cmd.lua index ad2dead..2aa6aff 100644 --- a/lua/git/cmd.lua +++ b/lua/git/cmd.lua @@ -74,7 +74,7 @@ end ---@param path string local function show_file_in_split(worktree, user_ref, path) local label = repo.rev_parse(worktree, user_ref, true) or user_ref - local uri = "git://" .. label .. "//" .. path + local uri = "git://" .. label .. ":" .. path local buf = vim.fn.bufadd(uri) vim.b[buf].git_worktree = worktree vim.cmd("split " .. vim.fn.fnameescape(uri)) @@ -119,11 +119,7 @@ local function run_in_split(worktree, args, conf) 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) .. "//" - ) + pcall(vim.api.nvim_buf_set_name, buf, "git://" .. (sha or user_ref)) vim.bo[buf].filetype = conf.ft else vim.bo[buf].filetype = conf.ft diff --git a/lua/git/diff.lua b/lua/git/diff.lua index 10a8245..8560250 100644 --- a/lua/git/diff.lua +++ b/lua/git/diff.lua @@ -57,32 +57,31 @@ local function attach_index_writer(buf, worktree, path) }) end ----Return a buffer holding the content at `:`. `ref` is ----"index", "HEAD", or a commit revspec. +---Return a buffer holding the content addressed by a git revspec. The +---URI is `git://` and BufReadCmd loads via `git cat-file -p`. ---@param worktree string ----@param ref string ----@param path string +---@param revspec string any revspec git understands (e.g. `HEAD:foo`, `:foo`, `:1:foo`, ``, `:foo`) ---@return integer -function M.git_show_buf(worktree, ref, path) - local name = "git://" .. ref .. "//" .. path +function M.git_show_buf(worktree, revspec) + local name = "git://" .. revspec local buf = vim.fn.bufadd(name) vim.b[buf].git_worktree = worktree vim.fn.bufload(buf) return buf end ----BufReadCmd handler for `git:////` URIs. Worktree comes from ----`vim.b[buf].git_worktree` if set, else from cwd. Ref of "index" maps ----to `git show :`; "worktree" leaves the buffer empty (placeholder ----for missing files); anything else is a revspec. +---BufReadCmd handler for `git://` URIs. Loads content via +---`git cat-file -p `. Worktree comes from `vim.b[buf] +---.git_worktree` if set, else from cwd. Index entries (revspec form +---`:` for stage 0) are made writable via `attach_index_writer`, +---so `:w` updates the index. Other revspecs are read-only. ---@param buf integer function M.read_uri(buf) local name = vim.api.nvim_buf_get_name(buf) - local ref, path = name:match("^git://(.-)//(.*)$") - if not ref or path == "" then + local revspec = name:match("^git://(.+)$") + if not revspec then return end - ---@cast path -nil local worktree = vim.b[buf].git_worktree or select(2, repo.resolve_cwd()) if not worktree then @@ -94,27 +93,26 @@ function M.read_uri(buf) vim.bo[buf].swapfile = false vim.bo[buf].bufhidden = "wipe" - if ref == "worktree" then - vim.bo[buf].buftype = "nofile" - vim.bo[buf].modifiable = false - vim.bo[buf].modified = false - vim.api.nvim_exec_autocmds("BufReadPost", { buffer = buf }) - return - end - - local revspec = ref == "index" and (":" .. path) or (ref .. ":" .. path) - local stdout = util.exec({ "git", "show", revspec }, { cwd = worktree }) + local stdout = util.exec( + { "git", "cat-file", "-p", revspec }, + { cwd = worktree } + ) if stdout then vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout)) end - if ref == "index" then + -- Stage-0 index entries (`:` with no further `:`) are + -- editable; `:w` rewrites the index entry via `attach_index_writer`. + -- Anything else (HEAD:, :, :1:, :2:, :3:, bare object refs) + -- is read-only. + local index_path = revspec:match("^:([^:]+)$") + if index_path then vim.bo[buf].buftype = "acwrite" -- Re-running BufReadCmd (e.g. on `:edit`) would otherwise stack -- another BufWriteCmd on the same buffer, so each `:w` runs -- hash-object + update-index N times. if not vim.b[buf].git_index_writer then - attach_index_writer(buf, worktree, path) + attach_index_writer(buf, worktree, index_path) vim.b[buf].git_index_writer = true end else @@ -221,8 +219,9 @@ function M.split(opts) return end - local label = opts.ref == "" and "index" or opts.ref - local uri = "git://" .. label .. "//" .. rel + -- Stage 0 (index) is `:`; named refs are `:`. + local revspec = opts.ref == "" and (":" .. rel) or (opts.ref .. ":" .. rel) + local uri = "git://" .. revspec -- Stash the worktree on the buffer so the BufReadCmd handler doesn't -- fall back to cwd resolution (wrong when cwd != worktree). local buf = vim.fn.bufadd(uri) diff --git a/lua/git/init.lua b/lua/git/init.lua index 4be10f8..6eabbaf 100644 --- a/lua/git/init.lua +++ b/lua/git/init.lua @@ -63,7 +63,7 @@ function M.setup() end vim.filetype.add({ pattern = { - ["git://.-//(.+)"] = function(_, bufnr, inner) + ["git://.*:([^:]+)$"] = function(_, bufnr, inner) return vim.filetype.match({ filename = inner, buf = bufnr }) end, }, diff --git a/lua/git/object.lua b/lua/git/object.lua index 15a1643..565b527 100644 --- a/lua/git/object.lua +++ b/lua/git/object.lua @@ -87,11 +87,14 @@ end ---@param ref string the commit ref the blob represents (e.g. `` or `^`) ---@return integer local function blob_buf(worktree, blob, path, ref) - local name = "git://" .. ref .. "//" .. path + local revspec = ref .. ":" .. path if is_zero(blob) then - return diff.empty_buf({ name = name, bufhidden = "hide" }) + return diff.empty_buf({ + name = "git://" .. revspec, + bufhidden = "hide", + }) end - return diff.git_show_buf(worktree, ref, path) + return diff.git_show_buf(worktree, revspec) end ---@param worktree string @@ -142,7 +145,7 @@ end 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 = "git://" .. sha .. "//" + 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 @@ -218,7 +221,7 @@ function M.open_object(worktree, ref, opts) local split = opts and opts.split local sha = repo.rev_parse(worktree, ref, true) or ref - local name = "git://" .. sha .. "//" + 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 @@ -279,7 +282,8 @@ function M.open_under_cursor() line:match("^%d+ (%w+) (%x+)\t(.+)$") if entry_sha then if entry_type == "blob" then - local buf = diff.git_show_buf(ctx.worktree, ctx.ref, entry_name) + local buf = + diff.git_show_buf(ctx.worktree, ctx.ref .. ":" .. entry_name) vim.cmd.normal({ "m'", bang = true }) vim.api.nvim_set_current_buf(buf) else diff --git a/lua/git/show.lua b/lua/git/show.lua index 5b437af..a3391e2 100644 --- a/lua/git/show.lua +++ b/lua/git/show.lua @@ -16,7 +16,7 @@ local M = {} function M.show(worktree, ref, opts) local split = opts and opts.split local sha = repo.rev_parse(worktree, ref, true) or ref - local name = "git://" .. sha .. "//" + local name = "git://" .. sha -- Commit SHAs are immutable so a previously-opened buffer is still -- valid. Reuse it instead of refetching. local existing = vim.fn.bufnr(name) diff --git a/lua/git/sidebar.lua b/lua/git/sidebar.lua index f8fad0d..b5d65f2 100644 --- a/lua/git/sidebar.lua +++ b/lua/git/sidebar.lua @@ -484,15 +484,19 @@ end ---@return ow.Git.DiffSide local function head_pane(worktree, path) return { - buf = diff.git_show_buf(worktree, "HEAD", path), - name = "git://HEAD//" .. path, + buf = diff.git_show_buf(worktree, "HEAD:" .. path), + name = "git://HEAD:" .. path, } end +---@param worktree string ---@param path string ---@return ow.Git.DiffSide -local function head_empty_pane(path) - return { buf = diff.empty_buf(), name = "git://HEAD//" .. path } +local function absent_pane(worktree, path) + return { + buf = diff.empty_buf(), + name = "[absent] " .. vim.fs.joinpath(worktree, path), + } end ---@param worktree string @@ -505,12 +509,6 @@ local function worktree_pane(worktree, path) } end ----@param path string ----@return ow.Git.DiffSide -local function worktree_empty_pane(path) - return { buf = diff.empty_buf(), name = "git://worktree//" .. path } -end - ---@param s ow.Git.SidebarState ---@param entry ow.Git.FileEntry ---@return ow.Git.DiffSide @@ -519,10 +517,12 @@ local function index_pane(s, entry) entry.section == "Untracked" or (entry.section == "Staged" and entry.x == "D") ) + if not in_index then + return absent_pane(s.worktree, entry.path) + end return { - buf = in_index and diff.git_show_buf(s.worktree, "index", entry.path) - or diff.empty_buf(), - name = "git://index//" .. entry.path, + buf = diff.git_show_buf(s.worktree, ":" .. entry.path), + name = "git://:" .. entry.path, } end @@ -534,7 +534,7 @@ local function other_pane(s, entry) local worktree = s.worktree if entry.section == "Staged" then if entry.x == "A" then - return head_empty_pane(p) + return absent_pane(worktree, p) end if entry.x == "D" then return head_pane(worktree, p) @@ -544,7 +544,7 @@ local function other_pane(s, entry) end if entry.section == "Unstaged" then if entry.y == "D" then - return worktree_empty_pane(p) + return absent_pane(worktree, p) end return worktree_pane(worktree, p) end