local util = require("git.util") local M = {} ---@param path string ---@return string? gitdir ---@return string? worktree function M.resolve(path) local found = vim.fs.find(".git", { upward = true, path = path })[1] if not found then return nil end local worktree = vim.fs.dirname(found) local stat = vim.uv.fs_stat(found) if not stat then return nil end if stat.type == "directory" then return found, worktree end local f = io.open(found, "r") if not f then return nil end local content = f:read("*a") f:close() local gitdir = content:match("gitdir:%s*(%S+)") if not gitdir then util.warning(".git file at %s has no `gitdir:` line", found) return nil end if not gitdir:match("^/") then gitdir = vim.fs.joinpath(worktree, gitdir) end return vim.fs.normalize(gitdir), worktree end ---@return string? gitdir ---@return string? worktree function M.resolve_cwd() local path = vim.api.nvim_buf_get_name(0) if path == "" or path:match("^%a+://") then path = vim.fn.getcwd() end return M.resolve(path) end ---@param path string ---@return string? function M.head(path) local gitdir = M.resolve(path) if not gitdir then return nil end local f = io.open(vim.fs.joinpath(gitdir, "HEAD"), "r") if not f then return nil end local first = f:read("*l") f:close() if not first then return nil end local branch = first:match("^ref:%s*refs/heads/(%S+)") if branch then return branch end local sha = first:match("^(%x+)") if sha then return sha:sub(1, 7) end return nil end ---@param worktree string ---@return string[] function M.list_refs(worktree) local out = util.exec({ "git", "for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/tags", "refs/remotes", }, { cwd = worktree, silent = true }) if not out then return {} end local refs = util.split_lines(out) table.insert(refs, 1, "HEAD") return refs end ---@param arg_lead string ---@return string[] function M.complete_rev(arg_lead) local _, worktree = M.resolve_cwd() if not worktree then return {} end local matches = {} for _, ref in ipairs(M.list_refs(worktree)) do if ref:sub(1, #arg_lead) == arg_lead then table.insert(matches, ref) end end return matches end ---@param worktree string ---@param rev string ---@param short boolean ---@return string? function M.rev_parse(worktree, rev, short) local cmd = { "git", "rev-parse", "--verify", "--quiet" } if short then table.insert(cmd, "--short") end table.insert(cmd, rev) local stdout = util.exec(cmd, { cwd = worktree, silent = true }) local trimmed = stdout and vim.trim(stdout) or "" return trimmed ~= "" and trimmed or nil end return M