From 781496dc13fa620eee2c7e6f238f05b64f57179e Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Wed, 29 Apr 2026 14:06:40 +0200 Subject: [PATCH] refactor(git): gitlog as gitlog:// URI singleton, fix synthetic-URI cwd resolve --- lua/git/init.lua | 7 ++++ lua/git/log.lua | 89 +++++++++++++++++++++++++++++++++------------- lua/git/object.lua | 9 ++++- lua/git/repo.lua | 9 ++--- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/lua/git/init.lua b/lua/git/init.lua index 259f039..8c93b56 100644 --- a/lua/git/init.lua +++ b/lua/git/init.lua @@ -43,6 +43,13 @@ function M.setup() object.read_uri(args.buf) end, }) + vim.api.nvim_create_autocmd("BufReadCmd", { + pattern = "gitlog://*", + group = group, + callback = function(args) + log.read_uri(args.buf) + end, + }) vim.api.nvim_create_autocmd( { "BufReadPost", "BufNewFile", "BufWritePost", "FileChangedShellPost" }, { diff --git a/lua/git/log.lua b/lua/git/log.lua index 3130685..35bf7ef 100644 --- a/lua/git/log.lua +++ b/lua/git/log.lua @@ -5,24 +5,12 @@ local M = {} local LOG_FORMAT = "%h %ad {%an}%d %s" local DEFAULT_MAX_COUNT = 1000 +local URI_PREFIX = "gitlog://" ----@class ow.Git.LogOpts ----@field max_count integer? cap on commits to show. Nil uses the default, <= 0 means "all" - ----@param opts ow.Git.LogOpts? -function M.open(opts) - opts = opts or {} - local max_count = opts.max_count - if max_count == nil then - max_count = DEFAULT_MAX_COUNT - end - - local _, worktree = repo.resolve_cwd() - if not worktree then - util.warning("not in a git repository") - return - end - +---@param worktree string +---@param max_count integer +---@return string? +local function fetch(worktree, max_count) local cmd = { "git", "log", @@ -35,21 +23,74 @@ function M.open(opts) if max_count > 0 then table.insert(cmd, "--max-count=" .. max_count) end + return util.exec(cmd, { cwd = worktree }) +end - local stdout = util.exec(cmd, { cwd = worktree }) +---@param buf integer +local function populate(buf) + local worktree = vim.b[buf].git_worktree + local max_count = vim.b[buf].git_log_max_count or DEFAULT_MAX_COUNT + local stdout = fetch(worktree, max_count) if not stdout then return end - - -- `hide` keeps the log around when the user navigates into a commit - -- via `` (which replaces the current buffer); `` jumps back. - local buf = util.new_scratch({ bufhidden = "hide" }) - vim.b[buf].git_worktree = worktree 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 = "gitlog" +end + +---BufReadCmd handler for `gitlog://` URIs. +---@param buf integer +function M.read_uri(buf) + local name = vim.api.nvim_buf_get_name(buf) + local worktree = name:sub(#URI_PREFIX + 1) + if worktree == "" then + return + end + + vim.b[buf].git_worktree = worktree + vim.bo[buf].swapfile = false + vim.bo[buf].bufhidden = "hide" + vim.bo[buf].buftype = "nofile" + -- Skip the assignment when ft is already set so re-runs don't + -- re-fire `FileType` autocmds (ftplugin reload, treesitter attach). + if vim.bo[buf].filetype ~= "gitlog" then + vim.bo[buf].filetype = "gitlog" + end + + populate(buf) +end + +---@class ow.Git.LogOpts +---@field max_count integer? cap on commits to show. Nil uses the default, <= 0 means "all" + +---@param opts ow.Git.LogOpts? +function M.open(opts) + opts = opts or {} + local _, worktree = repo.resolve_cwd() + if not worktree then + util.warning("not in a git repository") + return + end + + local buf = vim.fn.bufadd(URI_PREFIX .. worktree) + vim.b[buf].git_worktree = worktree + vim.b[buf].git_log_max_count = opts.max_count or DEFAULT_MAX_COUNT + local was_loaded = vim.api.nvim_buf_is_loaded(buf) + + local win = vim.fn.bufwinid(buf) + if win == -1 then + util.place_buf(buf, nil) + else + vim.api.nvim_set_current_win(win) + end + + -- `place_buf` triggers `bufload` -> `read_uri` for a fresh buffer. + -- An already-loaded buffer needs an explicit refresh. + if was_loaded then + populate(buf) + end end return M diff --git a/lua/git/object.lua b/lua/git/object.lua index d6855ad..a713806 100644 --- a/lua/git/object.lua +++ b/lua/git/object.lua @@ -182,7 +182,11 @@ function M.read_uri(buf) vim.b[buf].git_worktree = worktree vim.bo[buf].swapfile = false - vim.bo[buf].bufhidden = "wipe" + -- `unload` frees content when the buffer is hidden, but keeps the + -- bufnr in the buffer list so the jumplist's `(bufnr, line, col)` + -- entries stay valid. `` back to a hidden URI buffer triggers + -- the BufReadCmd again, refreshing content via `cat-file -p`. + vim.bo[buf].bufhidden = "unload" ---@type string? local stdout = pending_content[buf] @@ -228,6 +232,9 @@ function M.read_uri(buf) end if stdout then + -- Reload paths (`:e`, `` to an unloaded buf) re-enter + -- with `modifiable = false` from the prior load. + vim.bo[buf].modifiable = true vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout)) end diff --git a/lua/git/repo.lua b/lua/git/repo.lua index e975b50..ddf3e0b 100644 --- a/lua/git/repo.lua +++ b/lua/git/repo.lua @@ -84,14 +84,15 @@ function M.resolve(path) return vim.fs.normalize(gitdir), worktree end ----Resolve the gitdir/worktree from the current buffer's file path, falling ----back to `vim.fn.getcwd()` when the buffer is unnamed. Returns nil for ----both when not inside a git repo. +---Resolve the gitdir/worktree from the current buffer's file path, +---falling back to `vim.fn.getcwd()` when the buffer is unnamed or +---carries a synthetic URI (`git://`, `gitlog://`) that isn't a real +---filesystem path. Returns nil for both when not inside a git repo. ---@return string? gitdir ---@return string? worktree function M.resolve_cwd() local path = vim.api.nvim_buf_get_name(0) - if path == "" then + if path == "" or path:match("^%a+://") then path = vim.fn.getcwd() end return M.resolve(path)