Files
gitlab.nvim/lua/gitlab/reviewer/diffview.lua
Harrison (Harry) Cramer 64b36ac51d Feat: Adds Ability to Merge MR (#147)
This adds the ability to merge an MR from within `gitlab.nvim` directly. If the reviewer is open, it'll be closed automatically. Users may configure whether they'd like to squash commits on the merge, as well as whether they'd like to delete the original source branch on a merge.

If squashing, users are prompted to provide an optional custom squash message for the squash commit.
2023-12-17 14:28:21 -05:00

270 lines
8.7 KiB
Lua

-- This Module contains all of the reviewer code for diffview
local u = require("gitlab.utils")
local state = require("gitlab.state")
local async_ok, async = pcall(require, "diffview.async")
local diffview_lib = require("diffview.lib")
local M = {
bufnr = nil,
tabnr = nil,
}
M.open = function()
local diff_refs = state.INFO.diff_refs
if diff_refs == nil then
u.notify("Gitlab did not provide diff refs required to review this MR", vim.log.levels.ERROR)
return
end
if diff_refs.base_sha == "" or diff_refs.head_sha == "" then
u.notify("Merge request contains no changes", vim.log.levels.ERROR)
return
end
vim.api.nvim_command(string.format("DiffviewOpen %s..%s", diff_refs.base_sha, diff_refs.head_sha))
M.tabnr = vim.api.nvim_get_current_tabpage()
if state.INFO.has_conflicts then
u.notify("This merge request has conflicts!", vim.log.levels.WARN)
end
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.close", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewClosed" },
group = group,
callback = function()
--Check if our diffview tab was closed
if vim.api.nvim_tabpage_is_valid(M.tabnr) then
M.tabnr = nil
end
end,
})
if state.settings.discussion_tree.auto_open then
local discussions = require("gitlab.actions.discussions")
discussions.close()
discussions.toggle()
end
end
M.close = function()
vim.cmd("DiffviewClose")
local discussions = require("gitlab.actions.discussions")
discussions.close()
end
M.jump = function(file_name, new_line, old_line)
if M.tabnr == nil then
u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
return
end
vim.api.nvim_set_current_tabpage(M.tabnr)
vim.cmd("DiffviewFocusFiles")
local view = diffview_lib.get_current_view()
if view == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
local files = view.panel:ordered_file_list()
local layout = view.cur_layout
for _, file in ipairs(files) do
if file.path == file_name then
if not async_ok then
u.notify("Could not load Diffview async", vim.log.levels.ERROR)
return
end
async.await(view:set_file(file))
if new_line ~= nil then
layout.b:focus()
vim.api.nvim_win_set_cursor(0, { tonumber(new_line), 0 })
elseif old_line ~= nil then
layout.a:focus()
vim.api.nvim_win_set_cursor(0, { tonumber(old_line), 0 })
end
break
end
end
end
---Get the location of a line within the diffview. If range is specified, then also the location
---of the lines in range.
---@param range LineRange | nil Line range to get location for
---@return ReviewerInfo | nil nil is returned only if error was encountered
M.get_location = function(range)
if M.tabnr == nil then
u.notify("Diffview reviewer must be initialized first")
return
end
local bufnr = vim.api.nvim_get_current_buf()
local current_line = vim.api.nvim_win_get_cursor(0)[1]
-- check if we are in the diffview tab
local tabnr = vim.api.nvim_get_current_tabpage()
if tabnr ~= M.tabnr then
u.notify("Line location can only be determined within reviewer window")
return
end
-- check if we are in the diffview buffer
local view = diffview_lib.get_current_view()
if view == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
local layout = view.cur_layout
local result = {}
local type
local is_new
if layout.a.file.bufnr == bufnr then
result.file_name = layout.a.file.path
result.old_line = current_line
type = "old"
is_new = false
elseif layout.b.file.bufnr == bufnr then
result.file_name = layout.b.file.path
result.new_line = current_line
type = "new"
is_new = true
else
u.notify("Line location can only be determined within reviewer window")
return
end
local hunks = u.parse_hunk_headers(result.file_name, state.INFO.target_branch)
if hunks == nil then
u.notify("Could not parse hunks", vim.log.levels.ERROR)
return
end
local current_line_info
if is_new then
current_line_info = u.get_lines_from_hunks(hunks, result.new_line, is_new)
else
current_line_info = u.get_lines_from_hunks(hunks, result.old_line, is_new)
end
-- If single line comment is outside of changed lines then we need to specify both new line and old line
-- otherwise the API returns error.
-- https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff
if not current_line_info.in_hunk then
result.old_line = current_line_info.old_line
result.new_line = current_line_info.new_line
end
if range == nil then
return result
end
result.range_info = { start = {}, ["end"] = {} }
if current_line == range.start_line then
result.range_info.start.old_line = current_line_info.old_line
result.range_info.start.new_line = current_line_info.new_line
result.range_info.start.type = type
else
local start_line_info = u.get_lines_from_hunks(hunks, range.start_line, is_new)
result.range_info.start.old_line = start_line_info.old_line
result.range_info.start.new_line = start_line_info.new_line
result.range_info.start.type = type
end
if current_line == range.end_line then
result.range_info["end"].old_line = current_line_info.old_line
result.range_info["end"].new_line = current_line_info.new_line
result.range_info["end"].type = type
else
local end_line_info = u.get_lines_from_hunks(hunks, range.end_line, is_new)
result.range_info["end"].old_line = end_line_info.old_line
result.range_info["end"].new_line = end_line_info.new_line
result.range_info["end"].type = type
end
return result
end
---Return content between start_line and end_line
---@param start_line integer
---@param end_line integer
---@return string[]
M.get_lines = function(start_line, end_line)
return vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
end
---Get currently shown file
M.get_current_file = function()
local view = diffview_lib.get_current_view()
if not view then
return
end
return view.panel.cur_file.path
end
---Place a sign in currently reviewed file. Use new line for identifing lines after changes, old
---line for identifing lines before changes and both if line was not changed.
---@param signs table table of signs. See :h sign_placelist
---@param type string "new" if diagnostic should be in file after changes else "old"
M.place_sign = function(signs, type)
local view = diffview_lib.get_current_view()
if not view then
return
end
if type == "new" then
for _, sign in ipairs(signs) do
sign.buffer = view.cur_layout.b.file.bufnr
end
elseif type == "old" then
for _, sign in ipairs(signs) do
sign.buffer = view.cur_layout.a.file.bufnr
end
end
vim.fn.sign_placelist(signs)
end
---Set diagnostics in currently reviewed file.
---@param namespace integer namespace for diagnostics
---@param diagnostics table see :h vim.diagnostic.set
---@param type string "new" if diagnostic should be in file after changes else "old"
---@param opts table? see :h vim.diagnostic.set
M.set_diagnostics = function(namespace, diagnostics, type, opts)
local view = diffview_lib.get_current_view()
if not view then
return
end
if type == "new" and view.cur_layout.b.file.bufnr then
vim.diagnostic.set(namespace, view.cur_layout.b.file.bufnr, diagnostics, opts)
elseif type == "old" and view.cur_layout.a.file.bufnr then
vim.diagnostic.set(namespace, view.cur_layout.a.file.bufnr, diagnostics, opts)
end
end
---Diffview exposes events which can be used to setup autocommands.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_file_changed = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.file_changed", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewDiffBufWinEnter", "DiffviewViewEnter" },
group = group,
callback = function(...)
if M.tabnr == vim.api.nvim_get_current_tabpage() then
callback(...)
end
end,
})
end
---Diffview exposes events which can be used to setup autocommands.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_reviewer_leave = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.leave", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewLeave", "DiffviewViewClosed" },
group = group,
callback = function(...)
if M.tabnr == vim.api.nvim_get_current_tabpage() then
callback(...)
end
end,
})
end
return M