Feat: Allow Creation of MRs for Forked Target (#303)

feat: Adds MR creation for project forks
This commit is contained in:
Harrison (Harry) Cramer
2024-06-10 15:04:31 -04:00
committed by GitHub
parent 816b87cf91
commit 53d5647380
7 changed files with 123 additions and 185 deletions

152
README.md
View File

@@ -113,157 +113,9 @@ For more settings, please see `:h gitlab.nvim.connecting-to-gitlab`
## Configuring the Plugin
Here is the default setup function. All of these values are optional, and if you call this function with no values the defaults will be used:
The plugin expects you to call `setup()` and pass in a table of options. All of these values are optional, and if you call this function with no values the defaults will be used.
```lua
require("gitlab").setup({
port = nil, -- The port of the Go server, which runs in the background, if omitted or `nil` the port will be chosen automatically
log_path = vim.fn.stdpath("cache") .. "/gitlab.nvim.log", -- Log path for the Go server
config_path = nil, -- Custom path for `.gitlab.nvim` file, please read the "Connecting to Gitlab" section
debug = {
go_request = false,
go_response = false,
},
attachment_dir = nil, -- The local directory for files (see the "summary" section)
reviewer_settings = {
diffview = {
imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen|
},
},
connection_settings = {
insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection
},
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
popup = { -- The popup for comment creation, editing, and replying
keymaps = {
next_field = "<Tab>", -- Cycle to the next field. Accepts count.
prev_field = "<S-Tab>", -- Cycle to the previous field. Accepts count.
},
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
perform_linewise_action = "<leader>l", -- Once in normal mode, does the linewise action (see logs for this job, etc)
width = "40%",
height = "60%",
border = "rounded", -- One of "rounded", "single", "double", "solid"
opacity = 1.0, -- From 0.0 (fully transparent) to 1.0 (fully opaque)
comment = nil, -- Individual popup overrides, e.g. { width = "60%", height = "80%", border = "single", opacity = 0.85 },
edit = nil,
note = nil,
pipeline = nil,
reply = nil,
squash_message = nil,
temp_registers = {}, -- List of registers for backing up popup content (see `:h gitlab.nvim.temp-registers`)
},
discussion_tree = { -- The discussion tree that holds all comments
auto_open = true, -- Automatically open when the reviewer is opened
switch_view = "S", -- Toggles between the notes and discussions views
default_view = "discussions" -- Show "discussions" or "notes" by default
blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc)
jump_to_file = "o", -- Jump to comment location in file
jump_to_reviewer = "m", -- Jump to the location in the reviewer window
edit_comment = "e", -- Edit comment
delete_comment = "dd", -- Delete comment
refresh_data = "a", -- Refreshes the data in the view by hitting Gitlab's APIs again
reply = "r", -- Reply to comment
toggle_node = "t", -- Opens or closes the discussion
add_emoji = "Ea" -- Add an emoji to the note/comment
add_emoji = "Ed" -- Remove an emoji from a note/comment
toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions
toggle_resolved_discussions = "R", -- Open or close all resolved discussions
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling
publish_draft = "P", -- Publishes the currently focused note/comment
toggle_resolved = "p" -- Toggles the resolved status of the whole discussion
position = "left", -- "top", "right", "bottom" or "left"
open_in_browser = "b" -- Jump to the URL of the current note/discussion
copy_node_url = "u", -- Copy the URL of the current node to clipboard
size = "20%", -- Size of split
relative = "editor", -- Position of tree split relative to "editor" or "window"
resolved = '', -- Symbol to show next to resolved discussions
unresolved = '-', -- Symbol to show next to unresolved discussions
tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file
toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name"
draft_mode = false, -- Whether comments are posted as drafts as part of a review
toggle_draft_mode = "D" -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
winbar = nil -- Custom function to return winbar title, should return a string. Provided with WinbarTable (defined in annotations.lua)
-- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar.
},
choose_merge_request = {
open_reviewer = true, -- Open the reviewer window automatically after switching merge requests
},
info = { -- Show additional fields in the summary view
enabled = true,
horizontal = false, -- Display metadata to the left of the summary rather than underneath
fields = { -- The fields listed here will be displayed, in whatever order you choose
"author",
"created_at",
"updated_at",
"merge_status",
"draft",
"conflicts",
"assignees",
"reviewers",
"pipeline",
"branch",
"target_branch",
"delete_branch",
"squash",
"labels",
},
},
discussion_signs = {
enabled = true, -- Show diagnostics for gitlab comments in the reviewer
skip_resolved_discussion = false, -- Show diagnostics for resolved discussions
severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT
virtual_text = false, -- Whether to show the comment text inline as floating virtual text
priority = 100, -- Higher will override LSP warnings, etc
icons = {
comment = "→|",
range = " |",
},
},
pipeline = {
created = "",
pending = "",
preparing = "",
scheduled = "",
running = "",
canceled = "",
skipped = "",
success = "",
failed = "",
},
create_mr = {
target = nil, -- Default branch to target when creating an MR
template_file = nil, -- Default MR template in .gitlab/merge_request_templates
delete_branch = false, -- Whether the source branch will be marked for deletion
squash = false, -- Whether the commits will be marked for squashing
title_input = { -- Default settings for MR title input window
width = 40,
border = "rounded",
},
},
colors = {
discussion_tree = {
username = "Keyword",
date = "Comment",
chevron = "DiffviewNonText",
directory = "Directory",
directory_icon = "DiffviewFolderSign",
file_name = "Normal",
}
}
})
```
## Usage
First, check out the branch that you want to review locally.
```
git checkout feature-branch
```
Then open Neovim. To begin, try running the `summary` command or the `review` command.
For a list of all these settings please run `:h gitlab.nvim` which is stored in `doc/gitlab.nvim.txt`
## Keybindings

View File

@@ -11,11 +11,12 @@ import (
)
type CreateMrRequest struct {
Title string `json:"title"`
Description string `json:"description"`
TargetBranch string `json:"target_branch"`
DeleteBranch bool `json:"delete_branch"`
Squash bool `json:"squash"`
Title string `json:"title"`
Description string `json:"description"`
TargetBranch string `json:"target_branch"`
DeleteBranch bool `json:"delete_branch"`
Squash bool `json:"squash"`
TargetProjectID int `json:"forked_project_id,omitempty"`
}
/* createMr creates a merge request */
@@ -59,6 +60,10 @@ func (a *api) createMr(w http.ResponseWriter, r *http.Request) {
Squash: &createMrRequest.Squash,
}
if createMrRequest.TargetProjectID != 0 {
opts.TargetProjectID = gitlab.Ptr(createMrRequest.TargetProjectID)
}
_, res, err := a.client.CreateMergeRequest(a.projectInfo.ProjectId, &opts)
if err != nil {

View File

@@ -160,6 +160,9 @@ you call this function with no values the defaults will be used:
imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen|
},
},
connection_settings = {
insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection
},
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
popup = { -- The popup for comment creation, editing, and replying
keymaps = {
@@ -192,12 +195,14 @@ you call this function with no values the defaults will be used:
refresh_data = "a", -- Refreshes the data in the view by hitting Gitlab's APIs again
reply = "r", -- Reply to comment
toggle_node = "t", -- Opens or closes the discussion
add_emoji = "Ea" -- Add an emoji to the note/comment
add_emoji = "Ed" -- Remove an emoji from a note/comment
toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions
toggle_resolved_discussions = "R", -- Open or close all resolved discussions
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling
toggle_resolved = "p" -- Toggles the resolved status of the whole discussion
publish_draft = "P", -- Publishes the currently focused note/comment
toggle_resolved = "p" -- Toggles the resolved status of the whole discussion
position = "left", -- "top", "right", "bottom" or "left"
open_in_browser = "b" -- Jump to the URL of the current note/discussion
copy_node_url = "u", -- Copy the URL of the current node to clipboard
@@ -208,7 +213,7 @@ you call this function with no values the defaults will be used:
tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file
toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name"
draft_mode = false, -- Whether comments are posted as drafts as part of a review
toggle_draft_mode = "D" -- Toggle between draft mode and regular mode, where comments are posted immediately
toggle_draft_mode = "D" -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
winbar = nil -- Custom function to return winbar title, should return a string. Provided with WinbarTable (defined in annotations.lua)
-- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar.
},
@@ -218,7 +223,7 @@ you call this function with no values the defaults will be used:
info = { -- Show additional fields in the summary view
enabled = true,
horizontal = false, -- Display metadata to the left of the summary rather than underneath
fields = { -- The fields listed here will be displayed, in whatever order you choose
fields = { -- The fields listed here will be displayed, in whatever order you choose
"author",
"created_at",
"updated_at",
@@ -240,6 +245,7 @@ you call this function with no values the defaults will be used:
skip_resolved_discussion = false, -- Show diagnostics for resolved discussions
severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT
virtual_text = false, -- Whether to show the comment text inline as floating virtual text
use_diagnostic_signs = true, -- Show diagnostic sign (depending on the `severity` setting, e.g., I for INFO) along with the comment icon
priority = 100, -- Higher will override LSP warnings, etc
icons = {
comment = "→|",
@@ -262,6 +268,10 @@ you call this function with no values the defaults will be used:
template_file = nil, -- Default MR template in .gitlab/merge_request_templates
delete_branch = false, -- Whether the source branch will be marked for deletion
squash = false, -- Whether the commits will be marked for squashing
fork = {
enabled = false, -- If making an MR from a fork
forked_project_id = nil -- The ID of the project you are merging into. If nil, will be prompted.
},
title_input = { -- Default settings for MR title input window
width = 40,
border = "rounded",
@@ -275,7 +285,7 @@ you call this function with no values the defaults will be used:
directory = "Directory",
directory_icon = "DiffviewFolderSign",
file_name = "Normal",
}
}
}
})
<
@@ -413,6 +423,7 @@ have been added to a review. These are the default settings:
skip_resolved_discussion = false, -- Show diagnostics for resolved discussions
severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT
virtual_text = false, -- Whether to show the comment text inline as floating virtual text
use_diagnostic_signs = true, -- Show diagnostic sign (depending on the `severity` setting, e.g., I for INFO) along with the comment icon
priority = 100, -- Higher will override LSP warnings, etc
icons = {
comment = "→|",
@@ -420,14 +431,19 @@ have been added to a review. These are the default settings:
},
},
When the cursor is on diagnostic line you can view discussion thread by using `vim.diagnostic.show()`
When the cursor is on a diagnostic line you can view the discussion thread by
using `vim.diagnostic.show()`.
You can also jump to discussion tree for the given comment:
You can also jump to the discussion tree for the given comment:
>lua
require("gitlab").move_to_discussion_tree_from_diagnostic()
Since nvim 0.10 you can use these two function anywhere in the diagnostic
range. In previous versions, you have to move the cursor to the first line of
the diagnostic.
You may skip resolved discussions by toggling `discussion_signs.skip_resolved_discussion`
in your setup function to true. By default, discussions from this plugin
in your setup function to `true`. By default, discussions from this plugin
are shown at the INFO severity level (see :h vim.diagnostic.severity).

View File

@@ -14,6 +14,7 @@ local miscellaneous = require("gitlab.actions.miscellaneous")
---@field target? string
---@field title? string
---@field description? string
---@field forked_project_id number?
---@field template_file? string
---@field delete_branch boolean?
---@field squash boolean?
@@ -29,6 +30,8 @@ local M = {
target = "",
title = "",
description = "",
forked_project_id = state.settings.create_mr.fork.enabled and state.settings.create_mr.fork.forked_project_id
or nil,
},
}
@@ -37,6 +40,7 @@ M.reset_state = function()
M.mr.title = ""
M.mr.target = ""
M.mr.description = ""
M.mr.forked_project_id = nil
end
---1. If the user has already begun writing an MR, prompt them to
@@ -157,8 +161,12 @@ M.add_title = function(mr)
prompt = "",
default_value = "",
on_close = function() end,
on_submit = function(_value)
M.open_confirmation_popup(mr)
on_submit = function()
if state.settings.create_mr.fork.enabled and state.settings.create_mr.fork.forked_project_id == nil then
M.open_fork_popup(mr)
else
M.open_confirmation_popup(mr)
end
end,
on_change = function(value)
mr.title = value
@@ -167,6 +175,33 @@ M.add_title = function(mr)
input:mount()
end
---Sets the ID of the base project when working from a fork
---@param mr Mr
M.open_fork_popup = function(mr)
local input = Input({
position = "50%",
relative = "editor",
size = state.settings.create_mr.title_input.width,
border = {
style = state.settings.create_mr.title_input.border,
text = {
top = "Forked Project ID",
},
},
}, {
prompt = "",
default_value = "",
on_close = function() end,
on_submit = function()
M.open_confirmation_popup(mr)
end,
on_change = function(value)
mr.forked_project_id = tonumber(value)
end,
})
input:mount()
end
---5. Show the final popup.
---The function will render a popup containing the MR title and MR description,
---target branch, and the "delete_branch" and "squash" options. All fields are editable.
@@ -179,11 +214,13 @@ M.open_confirmation_popup = function(mr)
return
end
local layout, title_popup, description_popup, target_popup, delete_branch_popup, squash_popup = M.create_layout()
local layout, title_popup, description_popup, target_popup, delete_branch_popup, squash_popup, forked_project_id_popup =
M.create_layout()
local popups = {
title_popup,
description_popup,
forked_project_id_popup,
delete_branch_popup,
squash_popup,
target_popup,
@@ -199,12 +236,14 @@ M.open_confirmation_popup = function(mr)
local target = vim.fn.trim(u.get_buffer_text(M.target_bufnr))
local delete_branch = u.string_to_bool(u.get_buffer_text(M.delete_branch_bufnr))
local squash = u.string_to_bool(u.get_buffer_text(M.squash_bufnr))
local forked_project_id = tonumber(u.get_buffer_text(M.forked_project_id_bufnr))
M.mr = {
title = title,
description = description,
target = target,
delete_branch = delete_branch,
squash = squash,
forked_project_id = forked_project_id,
}
layout:unmount()
M.layout_visible = false
@@ -220,6 +259,10 @@ M.open_confirmation_popup = function(mr)
vim.api.nvim_buf_set_lines(M.target_bufnr, 0, -1, false, { mr.target })
vim.api.nvim_buf_set_lines(M.delete_branch_bufnr, 0, -1, false, { u.bool_to_string(delete_branch) })
vim.api.nvim_buf_set_lines(M.squash_bufnr, 0, -1, false, { u.bool_to_string(squash) })
if state.settings.create_mr.fork.enabled then
local forked_id = state.settings.create_mr.fork.forked_project_id or mr.forked_project_id
vim.api.nvim_buf_set_lines(M.forked_project_id_bufnr, 0, -1, false, { tostring(forked_id) })
end
u.switch_can_edit_buf(M.delete_branch_bufnr, false)
u.switch_can_edit_buf(M.squash_bufnr, false)
@@ -236,6 +279,7 @@ M.open_confirmation_popup = function(mr)
state.set_popup_keymaps(target_popup, M.create_mr, M.select_new_target, popup_opts)
state.set_popup_keymaps(delete_branch_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts)
state.set_popup_keymaps(squash_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts)
state.set_popup_keymaps(forked_project_id_popup, M.create_mr, nil, popup_opts)
miscellaneous.set_cycle_popups_keymaps(popups)
vim.api.nvim_set_current_buf(M.description_bufnr)
@@ -261,6 +305,7 @@ M.create_mr = function()
local target = u.get_buffer_text(M.target_bufnr):gsub("\n", " ")
local delete_branch = u.string_to_bool(u.get_buffer_text(M.delete_branch_bufnr))
local squash = u.string_to_bool(u.get_buffer_text(M.squash_bufnr))
local forked_project_id = tonumber(u.get_buffer_text(M.forked_project_id_bufnr))
local body = {
title = title,
@@ -268,8 +313,11 @@ M.create_mr = function()
target_branch = target,
delete_branch = delete_branch,
squash = squash,
forked_project_id = forked_project_id,
}
vim.print(body)
job.run_job("/create_mr", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)
M.reset_state()
@@ -291,18 +339,23 @@ M.create_layout = function()
local squash_title = vim.o.columns > 110 and "Squash commits" or "Squash"
local squash_popup = Popup(u.create_box_popup_state(squash_title, false))
M.squash_bufnr = squash_popup.bufnr
local forked_project_id_popup = Popup(u.create_box_popup_state("Forked Project ID", false))
M.forked_project_id_bufnr = forked_project_id_popup.bufnr
local internal_layout
internal_layout = Layout.Box({
local boxes = {}
if state.settings.create_mr.fork.enabled then
table.insert(boxes, Layout.Box(forked_project_id_popup, { size = { width = 20 } }))
end
table.insert(boxes, Layout.Box(delete_branch_popup, { size = { width = #delete_title + 4 } }))
table.insert(boxes, Layout.Box(squash_popup, { size = { width = #squash_title + 4 } }))
table.insert(boxes, Layout.Box(target_branch_popup, { grow = 1 }))
local internal_layout = Layout.Box({
Layout.Box({
Layout.Box(title_popup, { grow = 1 }),
}, { size = 3 }),
Layout.Box(description_popup, { grow = 1 }),
Layout.Box({
Layout.Box(delete_branch_popup, { size = { width = #delete_title + 4 } }),
Layout.Box(squash_popup, { size = { width = #squash_title + 4 } }),
Layout.Box(target_branch_popup, { grow = 1 }),
}, { size = 3 }),
Layout.Box(boxes, { size = 3 }),
}, { dir = "col" })
local layout = Layout({
@@ -316,7 +369,13 @@ M.create_layout = function()
layout:mount()
return layout, title_popup, description_popup, target_branch_popup, delete_branch_popup, squash_popup
return layout,
title_popup,
description_popup,
target_branch_popup,
delete_branch_popup,
squash_popup,
forked_project_id_popup
end
return M

View File

@@ -15,11 +15,14 @@ M.clear_diagnostics = function()
end
-- Display options for the diagnostic
local display_opts = {
virtual_text = state.settings.discussion_signs.virtual_text,
severity_sort = true,
underline = false,
}
local create_display_opts = function()
return {
virtual_text = state.settings.discussion_signs.virtual_text,
severity_sort = true,
underline = false,
signs = state.settings.discussion_signs.use_diagnostic_signs,
}
end
---Takes some range information and data about a discussion
---and creates a diagnostic to be placed in the reviewer
@@ -121,10 +124,10 @@ M.refresh_diagnostics = function()
end
local new_diagnostics = M.parse_new_diagnostics(filtered_discussions)
set_diagnostics_in_new_sha(diagnostics_namespace, new_diagnostics, display_opts)
set_diagnostics_in_new_sha(diagnostics_namespace, new_diagnostics, create_display_opts())
local old_diagnostics = M.parse_old_diagnostics(filtered_discussions)
set_diagnostics_in_old_sha(diagnostics_namespace, old_diagnostics, display_opts)
set_diagnostics_in_old_sha(diagnostics_namespace, old_diagnostics, create_display_opts())
end)
if not ok then

View File

@@ -2,7 +2,6 @@ local u = require("gitlab.utils")
local state = require("gitlab.state")
local List = require("gitlab.utils.list")
local discussion_sign_name = require("gitlab.indicators.diagnostics").discussion_sign_name
local namespace = require("gitlab.indicators.diagnostics").diagnostics_namespace
local M = {}
M.clear_signs = function()
@@ -32,9 +31,8 @@ M.set_signs = function(diagnostics, bufnr)
for _, diagnostic in ipairs(diagnostics) do
---@type SignTable[]
local existing_signs =
vim.fn.sign_getplaced(vim.api.nvim_get_current_buf(), { group = "gitlab_discussion" })[1].signs
vim.fn.sign_getplaced(vim.api.nvim_get_current_buf(), { group = discussion_sign_name })[1].signs
local sign_id = string.format("%s__%d", namespace, diagnostic.lnum)
if diagnostic.end_lnum then
local linenr = diagnostic.lnum + 1
while linenr <= diagnostic.end_lnum do
@@ -44,7 +42,7 @@ M.set_signs = function(diagnostics, bufnr)
end)
if conflicting_comment_sign == nil then
vim.fn.sign_place(
sign_id,
linenr,
discussion_sign_name,
"DiagnosticSign" .. M.severity .. gitlab_range,
bufnr,
@@ -55,7 +53,7 @@ M.set_signs = function(diagnostics, bufnr)
end
vim.fn.sign_place(
sign_id,
diagnostic.lnum + 1,
discussion_sign_name,
"DiagnosticSign" .. M.severity .. gitlab_comment,
bufnr,

View File

@@ -116,6 +116,10 @@ M.settings = {
template_file = nil,
delete_branch = false,
squash = false,
fork = {
enabled = false,
forked_project_id = nil,
},
title_input = {
width = 40,
border = "rounded",
@@ -149,6 +153,7 @@ M.settings = {
skip_resolved_discussion = false,
severity = vim.diagnostic.severity.INFO,
virtual_text = false,
use_diagnostic_signs = true,
icons = {
comment = "→|",
range = " |",