local log = require("log") local repo = require("git.repo") local M = {} ---@param buf integer ---@param worktree string ---@param path string local function attach_index_writer(buf, worktree, path) vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() local body = table.concat( vim.api.nvim_buf_get_lines(buf, 0, -1, false), "\n" ) .. "\n" local hash = vim.system( { "git", "hash-object", "-w", "--stdin" }, { cwd = worktree, stdin = body, text = true } ):wait() if hash.code ~= 0 then log.error("git hash-object failed: %s", hash.stderr or "") return end local sha = vim.trim(hash.stdout or "") local mode = "100644" local ls = vim.system( { "git", "ls-files", "-s", "--", path }, { cwd = worktree, text = true } ):wait() if ls.code == 0 and ls.stdout then local m = ls.stdout:match("^(%d+)") if m then mode = m end end local upd = vim.system({ "git", "update-index", "--cacheinfo", mode .. "," .. sha .. "," .. path, }, { cwd = worktree, text = true }):wait() if upd.code ~= 0 then log.error("git update-index failed: %s", upd.stderr or "") return end vim.bo[buf].modified = false end, }) end ---@param worktree string ---@param revspec string anything `git show` accepts (e.g. `HEAD:foo`, `:foo`, blob SHA) ---@return string[] local function read_show(worktree, revspec) local result = vim.system( { "git", "show", revspec }, { cwd = worktree, text = true } ) :wait() local content = result.code == 0 and (result.stdout or "") or "" local lines = vim.split(content, "\n", { plain = true, trimempty = false }) if #lines > 0 and lines[#lines] == "" then table.remove(lines) end return lines end ---@param worktree string ---@param ref string '' for index, 'HEAD' or a sha for committed refs ---@param path string ---@param is_index boolean? true to hook :w to update the git index ---@return integer function M.git_show_buf(worktree, ref, path, is_index) local lines = read_show(worktree, ref .. ":" .. path) local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.bo[buf].buftype = is_index and "acwrite" or "nofile" vim.bo[buf].bufhidden = "wipe" vim.bo[buf].swapfile = false if is_index then attach_index_writer(buf, worktree, path) else vim.bo[buf].modifiable = false end vim.bo[buf].modified = false return buf end ---@param worktree string ---@param blob string the blob SHA (full or abbreviated) ---@return integer function M.git_show_blob(worktree, blob) local lines = read_show(worktree, blob) local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.bo[buf].buftype = "nofile" vim.bo[buf].bufhidden = "wipe" vim.bo[buf].swapfile = false vim.bo[buf].modifiable = false vim.bo[buf].modified = false return buf end ---@return integer function M.empty_buf() local buf = vim.api.nvim_create_buf(false, true) vim.bo[buf].buftype = "nofile" vim.bo[buf].bufhidden = "wipe" vim.bo[buf].swapfile = false vim.bo[buf].modifiable = false vim.bo[buf].modified = false return buf end ---@param abs_path string ---@return integer function M.load_file_buf(abs_path) for _, buf in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_loaded(buf) and vim.api.nvim_buf_get_name(buf) == abs_path then return buf end end local buf = vim.fn.bufadd(abs_path) vim.fn.bufload(buf) return buf end ---@param buf integer ---@param name string local function set_buf_name_and_filetype(buf, name) pcall(vim.api.nvim_buf_set_name, buf, name) local ft = vim.filetype.match({ buf = buf }) if ft then vim.bo[buf].filetype = ft end end ---@class ow.Git.SplitOpts ---@field ref string '' for index, 'HEAD' for HEAD ---@field vertical boolean ---@param opts ow.Git.SplitOpts function M.split(opts) local cur_buf = vim.api.nvim_get_current_buf() local cur_path = vim.api.nvim_buf_get_name(cur_buf) if cur_path == "" then log.warning("no file in current buffer") return end local _, worktree = repo.resolve(cur_path) if not worktree then log.warning("not in a git repository") return end local rel = vim.fs.relpath(worktree, cur_path) if not rel then log.warning("file is outside the worktree") return end local is_index = opts.ref == "" local other = M.git_show_buf(worktree, opts.ref, rel, is_index) local label = is_index and "index" or opts.ref set_buf_name_and_filetype(other, "git://" .. label .. "/" .. rel) local split_cmd = opts.vertical and "leftabove vertical sbuffer " or "leftabove sbuffer " vim.cmd(split_cmd .. other) vim.cmd("diffthis") vim.cmd("wincmd p") vim.cmd("diffthis") end return M