refactor(git): introduce Revision class, normalize naming, slim docs

This commit is contained in:
2026-04-30 09:44:24 +02:00
parent 775add9b15
commit d95de4bc1d
10 changed files with 244 additions and 428 deletions
+7 -72
View File
@@ -1,54 +1,9 @@
local M = {}
local URI_PREFIX = "git://"
---@param revspec string
---@return string
function M.uri(revspec)
return URI_PREFIX .. revspec
end
---Extract the revspec from a `git://<revspec>` buffer name. Returns
---nil if the name doesn't carry the scheme.
---@param name string
---@return string?
function M.parse_uri(name)
return name:match("^" .. URI_PREFIX .. "(.+)$")
end
---@class ow.Git.ParsedRevspec
---@field stage 0|1|2|3? index stage when the revspec is `:<path>` / `:0:<path>` / `:N:<path>`, nil otherwise
---@field path string? path component when the revspec carries one, nil for bare object refs
---Classify a `git://<revspec>` revspec into its stage / path components.
---Recognised forms:
--- * `:<path>` and `:0:<path>` -> stage 0 (the resolved index entry)
--- * `:1:<path>` / `:2:<path>` / `:3:<path>` -> merge stages base / ours / theirs
--- * `<commit-ref>:<path>` -> stage = nil, path set
--- * bare object ref (no `:`) -> stage = nil, path = nil
---@param revspec string
---@return ow.Git.ParsedRevspec
function M.parse_revspec(revspec)
local stage, path = revspec:match("^:([0123]):(.+)$")
if stage then
return {
stage = tonumber(stage) --[[@as (0|1|2|3)?]],
path = path,
}
end
path = revspec:match("^:([^:]+)$")
if path then
return { stage = 0, path = path }
end
path = (revspec:match("^[^:]+:(.+)$"))
return { stage = nil, path = path }
end
---@class ow.Git.ScratchOpts
---@field name string?
---@field bufhidden ("hide"|"wipe")? defaults to "wipe"
---@field bufhidden ("hide"|"wipe")?
---Configure a fresh buffer as a read-only scratch and optionally name it.
---@param buf integer
---@param opts ow.Git.ScratchOpts
local function setup_scratch(buf, opts)
@@ -62,8 +17,6 @@ local function setup_scratch(buf, opts)
end
end
---Set a buffer's name and re-run filetype detection from it. Wrapped
---in `pcall` because a buffer with that name may already exist (E95).
---@param buf integer
---@param name string
function M.set_buf_name(buf, name)
@@ -74,10 +27,6 @@ function M.set_buf_name(buf, name)
end
end
---Place a buffer in the current window or a new split per `split`.
---`false` replaces the current buffer, dropping a `'` mark first so
---`''` jumps back. A direction string opens a split on that side. Nil
---falls back to `'splitbelow'` for the direction.
---@param buf integer
---@param split (false|"above"|"below"|"left"|"right")?
---@return integer win
@@ -95,11 +44,8 @@ function M.place_buf(buf, split)
end
---@class ow.Git.NewScratchOpts : ow.Git.ScratchOpts
---@field split (false|"above"|"below"|"left"|"right")? defaults to splitbelow-aware horizontal. `false` places the buffer in the current window (drops a `'` mark first so the user can jump back).
---@field split (false|"above"|"below"|"left"|"right")?
---Create a fresh read-only scratch buffer and place it. Default split
---direction is horizontal, honouring `splitbelow`. Caller flips
---`modifiable`, fills the buffer, and sets `filetype` once content lands.
---@param opts ow.Git.NewScratchOpts?
---@return integer buf
---@return integer win
@@ -134,9 +80,6 @@ function M.debug(fmt, ...)
vim.notify(fmt:format(...), vim.log.levels.DEBUG)
end
---Split a string on newlines, dropping the trailing empty element that an
---input ending in `\n` produces. Convenient for slicing subprocess stdout
---into a list of lines without a phantom blank at the end.
---@param content string
---@return string[]
function M.split_lines(content)
@@ -164,9 +107,9 @@ function M.debounce(fn, delay)
local fired_gen = 0
local cb_main = vim.schedule_wrap(function()
-- Identity check: the libuv fire may have been superseded by a
-- re-arm or a cancel between the timer firing and this scheduled
-- callback running.
-- Identity check: the libuv fire may have been superseded by
-- a re-arm or a cancel between the timer firing and this
-- scheduled callback running.
if fired_gen ~= gen or args == nil then
return
end
@@ -217,17 +160,9 @@ end
---@class ow.Git.ExecOpts
---@field cwd string?
---@field stdin string?
---@field silent boolean? suppress the auto-log on non-zero exit
---@field on_done fun(stdout: string?)? if set, run async and deliver stdout (or nil on failure) here on the main loop instead of returning sync
---@field silent boolean?
---@field on_done fun(stdout: string?)?
---Run a system command. Default is sync: returns stdout on success or
---nil on failure (logging stderr unless `opts.silent`). When
---`opts.on_done` is set, runs async via `vim.schedule_wrap` and
---delivers the same stdout-or-nil value to that callback instead.
---
---Async mode returns nil immediately. Callers that need access to the
---raw stderr / exit code in the failure path should opt out of this
---helper and use `vim.system` directly.
---@param cmd string[]
---@param opts ow.Git.ExecOpts?
---@return string?