Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/lua.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ jobs:
neovim: true
version: ${{ matrix.nvim_version }}
- name: Install luajit
uses: leafo/gh-actions-lua@v10
uses: leafo/gh-actions-lua@v11
with:
luaVersion: "luajit-openresty"
- name: Install luarocks
uses: leafo/gh-actions-luarocks@v4
uses: leafo/gh-actions-luarocks@v5
with:
luarocksVersion: "3.8.0"
- name: Run tests
shell: bash
run: |
Expand Down
5 changes: 3 additions & 2 deletions after/syntax/gitlab.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ endif
let expanders = '^\s*\%(' . g:gitlab_discussion_tree_expander_open . '\|' . g:gitlab_discussion_tree_expander_closed . '\)'
let username = '@[a-zA-Z0-9.]\+'

" Covers times like '14 days ago', 'just now', as well as 'October 3, 2024'
" Covers times like '14 days ago', 'just now', as well as 'October 3, 2024', and '02/28/2025 at 00:50'
let time_ago = '\d\+ \w\+ ago'
let formatted_date = '\w\+ \{1,2}\d\{1,2}, \d\{4}'
let date = '\%(' . time_ago . '\|' . formatted_date . '\|just now\)'
let absolute_time = '\d\{2}/\d\{2}/\d\{4} at \d\{2}:\d\{2}'
let date = '\%(' . time_ago . '\|' . formatted_date . '\|' . absolute_time . '\|just now\)'

let published = date . ' \%(' . g:gitlab_discussion_tree_resolved . '\|' . g:gitlab_discussion_tree_unresolved . '\|' . g:gitlab_discussion_tree_unlinked . '\)\?'
let state = ' \%(' . published . '\|' . g:gitlab_discussion_tree_draft . '\)'
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ func NewClient() (*Client, error) {

retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr
retryClient.RetryMax = 0
gitlabOptions = append(gitlabOptions, gitlab.WithHTTPClient(retryClient.HTTPClient))
gitlabOptions = append(gitlabOptions, gitlab.WithoutRetries())

client, err := gitlab.NewClient(pluginOptions.AuthToken, gitlabOptions...)

Expand Down
8 changes: 7 additions & 1 deletion cmd/app/comment_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,19 @@ type RequestWithPosition interface {
func buildCommentPosition(commentWithPositionData RequestWithPosition) *gitlab.PositionOptions {
positionData := commentWithPositionData.GetPositionData()

// If the file has been renamed, then this is a relevant part of the payload
oldFileName := positionData.OldFileName
if oldFileName == "" {
oldFileName = positionData.FileName
}

opt := &gitlab.PositionOptions{
PositionType: &positionData.Type,
StartSHA: &positionData.StartCommitSHA,
HeadSHA: &positionData.HeadCommitSHA,
BaseSHA: &positionData.BaseCommitSHA,
NewPath: &positionData.FileName,
OldPath: &positionData.OldFileName,
OldPath: &oldFileName,
NewLine: positionData.NewLine,
OldLine: positionData.OldLine,
}
Expand Down
2 changes: 2 additions & 0 deletions doc/gitlab.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ you call this function with no values the defaults will be used:
switch_view = "c", -- Toggle between the notes and discussions views
toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name"
publish_draft = "P", -- Publish the currently focused note/comment
toggle_date_format = "dt", -- Toggle between date formats: relative (e.g., "5 days ago", "just now", "October 13, 2024" for dates more than a month ago) and absolute (e.g., "03/01/2024 at 11:43")
toggle_draft_mode = "D", -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
toggle_sort_method = "st", -- Toggle whether discussions are sorted by the "latest_reply", or by "original_comment", see `:h gitlab.nvim.toggle_sort_method`
toggle_node = "t", -- Open or close the discussion
Expand Down Expand Up @@ -267,6 +268,7 @@ you call this function with no values the defaults will be used:
draft = "✎", -- Symbol to show next to draft comments/notes
tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file
draft_mode = false, -- Whether comments are posted as drafts as part of a review
relative_date = true, -- Whether to show relative time like "5 days ago" or absolute time like "03/01/2025 at 01:43"
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.
},
Expand Down
6 changes: 3 additions & 3 deletions lua/gitlab/actions/comment.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ M.create_comment_layout = function(opts)
title = "Note"
user_settings = popup_settings.note
else
-- TODO: investigate why `old_file_name` is in fact the new name for renamed files!
local file_name = M.location.reviewer_data.old_file_name ~= "" and M.location.reviewer_data.old_file_name
or M.location.reviewer_data.file_name
local file_name = (M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "")
and M.location.reviewer_data.file_name
or M.location.reviewer_data.old_file_name
Comment on lines +156 to +158
Copy link

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This inline boolean expression is hard to read and maintain. Consider using a simple if/else block to clearly express which path should be used.

Suggested change
local file_name = (M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "")
and M.location.reviewer_data.file_name
or M.location.reviewer_data.old_file_name
local file_name
if M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "" then
file_name = M.location.reviewer_data.file_name
else
file_name = M.location.reviewer_data.old_file_name
end

Copilot uses AI. Check for mistakes.
title =
popup.create_title("Comment", file_name, M.location.visual_range.start_line, M.location.visual_range.end_line)
user_settings = popup_settings.comment
Expand Down
8 changes: 6 additions & 2 deletions lua/gitlab/actions/common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ M.build_note_header = function(note)
if note.note then
return "@" .. state.USER.username .. " " .. state.settings.discussion_tree.draft
end
return "@" .. note.author.username .. " " .. u.time_since(note.created_at)
local time = state.settings.discussion_tree.relative_date and u.time_since(note.created_at)
or u.format_to_local(note.created_at, vim.fn.strftime("%z"))
return "@" .. note.author.username .. " " .. time
end

M.switch_can_edit_bufs = function(bool, ...)
Expand Down Expand Up @@ -240,7 +242,9 @@ M.get_line_numbers_for_range = function(old_line, new_line, start_line_code, end
return (old_line - range), old_line, false
elseif new_line ~= nil then
local range = new_end_line - new_start_line
return (new_line - range), new_line, true
-- Force start_line to be greater than 0
local start_line = (new_line - range > 0) and (new_line - range) or 1
return start_line, new_line, true
else
u.notify("Error getting new or old line for range", vim.log.levels.ERROR)
return 1, 1, false
Expand Down
19 changes: 18 additions & 1 deletion lua/gitlab/actions/discussions/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,16 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
})
end

if keymaps.discussion_tree.toggle_date_format then
vim.keymap.set("n", keymaps.discussion_tree.toggle_date_format, function()
M.toggle_date_format()
end, {
buffer = bufnr,
desc = "Toggle date format",
nowait = keymaps.discussion_tree.toggle_date_format_nowait,
})
end

if keymaps.discussion_tree.toggle_resolved then
vim.keymap.set("n", keymaps.discussion_tree.toggle_resolved, function()
if M.is_current_node_note(tree) and not M.is_draft_note(tree) then
Expand Down Expand Up @@ -725,7 +735,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)

if keymaps.help then
vim.keymap.set("n", keymaps.help, function()
help.open()
help.open({ discussion_tree = true })
end, { buffer = bufnr, desc = "Open help popup", nowait = keymaps.help_nowait })
end

Expand Down Expand Up @@ -809,6 +819,13 @@ M.toggle_sort_method = function()
M.rebuild_view(false, true)
end

---Toggle between displaying relative time (e.g., "5 days ago") and absolute time (e.g., "04/10/2025 at 22:49")
M.toggle_date_format = function()
state.settings.discussion_tree.relative_date = not state.settings.discussion_tree.relative_date
M.rebuild_unlinked_discussion_tree()
M.rebuild_discussion_tree()
end

---Indicates whether the node under the cursor is a draft note or not
---@param tree NuiTree
---@return boolean
Expand Down
24 changes: 19 additions & 5 deletions lua/gitlab/actions/draft_notes/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-- under lua/gitlab/actions/discussions/init.lua
local common = require("gitlab.actions.common")
local discussion_tree = require("gitlab.actions.discussions.tree")
local git = require("gitlab.git")
local job = require("gitlab.job")
local NuiTree = require("nui.tree")
local List = require("gitlab.utils.list")
Expand Down Expand Up @@ -85,12 +86,20 @@ end

---Publishes all draft notes and comments. Re-renders all discussion views.
M.confirm_publish_all_drafts = function()
if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
return
end
local body = { publish_all = true }
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)
state.DRAFT_NOTES = {}
local discussions = require("gitlab.actions.discussions")
discussions.rebuild_view(false, true)
require("gitlab.actions.discussions").rebuild_view(false, true)
end, function()
require("gitlab.actions.discussions").rebuild_view(false, true)
u.notify(
"Draft(s) may have been published despite the error. Check the discussion tree. Try publishing drafts individually.",
vim.log.levels.WARN
)
end)
end

Expand All @@ -99,6 +108,9 @@ end
---and re-render it.
---@param tree NuiTree
M.confirm_publish_draft = function(tree)
if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
return
end
local current_node = tree:get_node()
local note_node = common.get_note_node(tree, current_node)
local root_node = common.get_root_node(tree, current_node)
Expand All @@ -111,12 +123,13 @@ M.confirm_publish_draft = function(tree)
---@type integer
local note_id = note_node.is_root and root_node.id or note_node.id
local body = { note = note_id }
local unlinked = tree.bufnr == require("gitlab.actions.discussions").unlinked_bufnr
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)

local discussions = require("gitlab.actions.discussions")
local unlinked = tree.bufnr == discussions.unlinked_bufnr
M.rebuild_view(unlinked)
end, function()
M.rebuild_view(unlinked)
u.notify("Draft may have been published despite the error. Check the discussion tree.", vim.log.levels.WARN)
end)
end

Expand All @@ -142,6 +155,7 @@ M.build_root_draft_note = function(note)
id = note.id,
root_note_id = note.id,
file_name = (type(note.position) == "table" and note.position.new_path or nil),
old_file_name = (type(note.position) == "table" and note.position.old_path or nil),
new_line = (type(note.position) == "table" and note.position.new_line or nil),
old_line = (type(note.position) == "table" and note.position.old_line or nil),
resolvable = false,
Expand Down
33 changes: 20 additions & 13 deletions lua/gitlab/actions/help.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ local state = require("gitlab.state")
local List = require("gitlab.utils.list")
local Popup = require("nui.popup")

M.open = function()
---@class HelpPopupOpts
---@field discussion_tree boolean|nil Whether help popup is for the discussion tree

--- @param opts HelpPopupOpts|nil Table with options for the help popup
M.open = function(opts)
local help_opts = opts or {}
local bufnr = vim.api.nvim_get_current_buf()
local keymaps = vim.api.nvim_buf_get_keymap(bufnr, "n")
local help_content_lines = List.new(keymaps):reduce(function(agg, keymap)
Expand All @@ -17,26 +22,28 @@ M.open = function()
return agg
end, {})

table.insert(help_content_lines, "")
table.insert(
help_content_lines,
string.format(
"%s = draft; %s = unlinked comment; %s = resolved",
state.settings.discussion_tree.draft,
state.settings.discussion_tree.unlinked,
state.settings.discussion_tree.resolved
if help_opts.discussion_tree then
table.insert(help_content_lines, "")
table.insert(
help_content_lines,
string.format(
"%s = draft; %s = unlinked comment; %s = resolved",
state.settings.discussion_tree.draft,
state.settings.discussion_tree.unlinked,
state.settings.discussion_tree.resolved
)
)
)
end

local longest_line = u.get_longest_string(help_content_lines)
local opts = { "Help", state.settings.popup.help, longest_line + 3, #help_content_lines, 70 }
local help_popup = Popup(popup.create_popup_state(unpack(opts)))
local popup_opts = { "Help", state.settings.popup.help, longest_line + 3, #help_content_lines, 70 }
local help_popup = Popup(popup.create_popup_state(unpack(popup_opts)))

help_popup:on(event.BufLeave, function()
help_popup:unmount()
end)

popup.set_up_autocommands(help_popup, nil, vim.api.nvim_get_current_win(), opts)
popup.set_up_autocommands(help_popup, nil, vim.api.nvim_get_current_win(), popup_opts)

help_popup:mount()

Expand Down
2 changes: 1 addition & 1 deletion lua/gitlab/colors.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ local function get_colors_for_group(group)
local normal_bg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), "bg")
return { fg = normal_fg, bg = normal_bg }
end
vim.api.nvim_create_autocmd("VimEnter", {
vim.api.nvim_create_autocmd({ "VimEnter", "ColorScheme" }, {
callback = function()
vim.api.nvim_set_hl(0, "GitlabUsername", get_colors_for_group(discussion.username))
vim.api.nvim_set_hl(0, "GitlabMention", get_colors_for_group(discussion.mention))
Expand Down
61 changes: 40 additions & 21 deletions lua/gitlab/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ local M = {}
---@param command table
---@return string|nil, string|nil
local run_system = function(command)
-- Load here to prevent loop
local u = require("gitlab.utils")
local result = vim.fn.trim(vim.fn.system(command))
if vim.v.shell_error ~= 0 then
u.notify(result, vim.log.levels.ERROR)
require("gitlab.utils").notify(result, vim.log.levels.ERROR)
return nil, result
end
return result, nil
Expand Down Expand Up @@ -52,10 +50,28 @@ M.switch_branch = function(branch)
return run_system({ "git", "checkout", "-q", branch })
end

---Fetches the name of the remote tracking branch for the current branch
---@return string|nil, string|nil
---Returns the name of the remote-tracking branch for the current branch or nil if it can't be found
---@return string|nil
M.get_remote_branch = function()
return run_system({ "git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}" })
local remote_branch, err = run_system({ "git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}" })
if err or remote_branch == "" then
require("gitlab.utils").notify("Could not get remote branch: " .. err, vim.log.levels.ERROR)
return nil
end
return remote_branch
end

---Fetch the remote branch
---@param remote_branch string The name of the repo and branch to fetch (e.g., "origin/some_branch")
---@return boolean fetch_successfull False if an error occurred while fetching, true otherwise.
M.fetch_remote_branch = function(remote_branch)
local remote, branch = string.match(remote_branch, "([^/]+)/(.*)")
local _, fetch_err = run_system({ "git", "fetch", remote, branch })
if fetch_err ~= nil then
require("gitlab.utils").notify("Error fetching remote-tracking branch: " .. fetch_err, vim.log.levels.ERROR)
return false
end
return true
end

---Determines whether the tracking branch is ahead of or behind the current branch, and warns the user if so
Expand All @@ -64,6 +80,10 @@ end
---@param log_level number
---@return boolean
M.get_ahead_behind = function(current_branch, remote_branch, log_level)
if not M.fetch_remote_branch(remote_branch) then
return false
end

local u = require("gitlab.utils")
local result, err =
run_system({ "git", "rev-list", "--left-right", "--count", current_branch .. "..." .. remote_branch })
Expand Down Expand Up @@ -104,17 +124,22 @@ M.get_ahead_behind = function(current_branch, remote_branch, log_level)
return true -- Checks passed, branch is up-to-date
end

---Return the name of the current branch
---@return string|nil, string|nil
---Return the name of the current branch or nil if it can't be retrieved
---@return string|nil
M.get_current_branch = function()
return run_system({ "git", "branch", "--show-current" })
local current_branch, err = run_system({ "git", "branch", "--show-current" })
if err or current_branch == "" then
require("gitlab.utils").notify("Could not get current branch: " .. err, vim.log.levels.ERROR)
return nil
end
return current_branch
end

---Return the list of possible merge targets.
---@return table|nil
M.get_all_merge_targets = function()
local current_branch, err = M.get_current_branch()
if not current_branch or err ~= nil then
local current_branch = M.get_current_branch()
if current_branch == nil then
return
end
return List.new(M.get_all_remote_branches()):filter(function(branch)
Expand Down Expand Up @@ -158,19 +183,13 @@ end
---@param log_level integer
---@return boolean
M.check_current_branch_up_to_date_on_remote = function(log_level)
local u = require("gitlab.utils")

-- Get current branch
local current_branch, err_current_branch = M.get_current_branch()
if err_current_branch or not current_branch then
u.notify("Could not get current branch: " .. err_current_branch, vim.log.levels.ERROR)
local current_branch = M.get_current_branch()
if current_branch == nil then
return false
end

-- Get remote tracking branch
local remote_branch, err_remote_branch = M.get_remote_branch()
if err_remote_branch or not remote_branch then
u.notify("Could not get remote branch: " .. err_remote_branch, vim.log.levels.ERROR)
local remote_branch = M.get_remote_branch()
if remote_branch == nil then
return false
end

Expand Down
3 changes: 3 additions & 0 deletions lua/gitlab/indicators/diagnostics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ M.place_diagnostics = function(bufnr)
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
if vim.api.nvim_buf_get_name(bufnr) == "diffview://null" then
return
end

local ok, err = pcall(function()
local file_discussions = List.new(M.placeable_discussions):filter(function(discussion_or_note)
Expand Down
Loading
Loading