189 lines
5.3 KiB
Lua
189 lines
5.3 KiB
Lua
local Revision = require("git.revision")
|
|
local repo = require("git.repo")
|
|
local util = require("git.util")
|
|
|
|
local M = {}
|
|
|
|
---@class ow.Git.Diff.Side
|
|
---@field buf integer
|
|
---@field name string?
|
|
|
|
---@class ow.Git.Diff.Pair
|
|
---@field left ow.Git.Diff.Side
|
|
---@field right ow.Git.Diff.Side
|
|
|
|
---@param win integer
|
|
---@param enabled boolean
|
|
function M.set_diff(win, enabled)
|
|
vim.api.nvim_win_call(win, function()
|
|
vim.cmd(enabled and "diffthis" or "diffoff")
|
|
end)
|
|
end
|
|
|
|
---@param left integer
|
|
---@param right integer
|
|
---@param vertical boolean
|
|
function M.open(left, right, vertical)
|
|
local left_name = vim.api.nvim_buf_get_name(left)
|
|
vim.cmd.normal({ "m'", bang = true })
|
|
vim.api.nvim_set_current_buf(right)
|
|
vim.cmd.diffsplit({
|
|
args = { left_name },
|
|
mods = { split = "aboveleft", vertical = vertical, keepjumps = true },
|
|
magic = { file = false },
|
|
})
|
|
end
|
|
|
|
---@param left_win integer
|
|
---@param right_win integer
|
|
---@param pair ow.Git.Diff.Pair
|
|
function M.update_pair(left_win, right_win, pair)
|
|
M.set_diff(left_win, false)
|
|
M.set_diff(right_win, false)
|
|
vim.api.nvim_win_set_buf(left_win, pair.left.buf)
|
|
vim.api.nvim_win_set_buf(right_win, pair.right.buf)
|
|
for _, side in ipairs({ pair.left, pair.right }) do
|
|
if side.name then
|
|
util.set_buf_name(side.buf, side.name)
|
|
end
|
|
end
|
|
M.set_diff(left_win, true)
|
|
M.set_diff(right_win, true)
|
|
vim.api.nvim_win_set_cursor(left_win, { 1, 0 })
|
|
vim.api.nvim_win_set_cursor(right_win, { 1, 0 })
|
|
vim.cmd.syncbind()
|
|
end
|
|
|
|
---@param buf_a integer
|
|
---@param buf_b integer
|
|
---@param a_left boolean
|
|
---@param vertical boolean
|
|
local function place_pair(buf_a, buf_b, a_left, vertical)
|
|
if a_left then
|
|
M.open(buf_a, buf_b, vertical)
|
|
else
|
|
M.open(buf_b, buf_a, vertical)
|
|
end
|
|
end
|
|
|
|
---@param opts ow.Git.Diff.SplitOpts
|
|
---@param buf integer
|
|
---@param rev ow.Git.Revision
|
|
local function uri_split(opts, buf, rev)
|
|
local r = repo.resolve(buf)
|
|
if not r then
|
|
util.error("git URI buffer has no worktree")
|
|
return
|
|
end
|
|
if not rev.path then
|
|
util.error("git URI has no path, cannot diff against worktree")
|
|
return
|
|
end
|
|
local object = require("git.object")
|
|
|
|
if opts.rev and opts.rev:find(":", 1, true) then
|
|
local content = util.exec(
|
|
{ "git", "cat-file", "-p", opts.rev },
|
|
{ cwd = r.worktree, silent = true }
|
|
)
|
|
if not content then
|
|
util.error("invalid rev: %s", opts.rev)
|
|
return
|
|
end
|
|
place_pair(
|
|
buf,
|
|
object.buf_for(r, Revision.parse(opts.rev), content),
|
|
false,
|
|
opts.vertical
|
|
)
|
|
return
|
|
end
|
|
|
|
if not opts.rev then
|
|
local worktree_path = vim.fs.joinpath(r.worktree, rev.path)
|
|
if not vim.uv.fs_stat(worktree_path) then
|
|
util.error("worktree file does not exist: %s", rev.path)
|
|
return
|
|
end
|
|
local worktree_buf = vim.fn.bufadd(worktree_path)
|
|
vim.fn.bufload(worktree_buf)
|
|
place_pair(buf, worktree_buf, true, opts.vertical)
|
|
return
|
|
end
|
|
|
|
if rev.stage == 1 then
|
|
util.error("gD on merge base is ambiguous, use :Gdiffsplit <rev>")
|
|
return
|
|
end
|
|
|
|
local mapping = {
|
|
[2] = { Revision.new({ stage = 3, path = rev.path }), true },
|
|
[3] = { Revision.new({ stage = 2, path = rev.path }), false },
|
|
[0] = { Revision.new({ base = "HEAD", path = rev.path }), false },
|
|
}
|
|
local m = mapping[rev.stage]
|
|
or { Revision.new({ stage = 0, path = rev.path }), true }
|
|
local other_rev, left = m[1], m[2]
|
|
local content = util.exec(
|
|
{ "git", "cat-file", "-p", other_rev:format() },
|
|
{ cwd = r.worktree, silent = true }
|
|
)
|
|
if not content then
|
|
util.error("invalid rev: %s", other_rev:format())
|
|
return
|
|
end
|
|
place_pair(buf, object.buf_for(r, other_rev, content), left, opts.vertical)
|
|
end
|
|
|
|
---@class ow.Git.Diff.SplitOpts
|
|
---@field rev string?
|
|
---@field vertical boolean
|
|
|
|
---@param opts ow.Git.Diff.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)
|
|
|
|
local cur_rev = require("git.object").parse_uri(cur_path)
|
|
if cur_rev then
|
|
return uri_split(opts, cur_buf, cur_rev)
|
|
end
|
|
|
|
if cur_path == "" then
|
|
util.error("no file in current buffer")
|
|
return
|
|
end
|
|
if vim.bo[cur_buf].buftype ~= "" then
|
|
util.error("cannot diff this buffer (not a worktree file)")
|
|
return
|
|
end
|
|
cur_path = vim.fn.resolve(cur_path)
|
|
local r = repo.resolve(cur_path)
|
|
if not r then
|
|
util.error("not in a git repository")
|
|
return
|
|
end
|
|
local rel = vim.fs.relpath(r.worktree, cur_path)
|
|
|
|
local rev
|
|
if not opts.rev then
|
|
rev = Revision.new({ stage = 0, path = rel })
|
|
elseif opts.rev:find(":", 1, true) then
|
|
rev = Revision.parse(opts.rev)
|
|
else
|
|
rev = Revision.new({ base = opts.rev, path = rel })
|
|
end
|
|
local content = util.exec(
|
|
{ "git", "cat-file", "-p", rev:format() },
|
|
{ cwd = r.worktree, silent = true }
|
|
)
|
|
if not content then
|
|
util.error("invalid rev: %s", rev:format())
|
|
return
|
|
end
|
|
local buf = require("git.object").buf_for(r, rev, content)
|
|
place_pair(buf, cur_buf, true, opts.vertical)
|
|
end
|
|
|
|
return M
|