Skip to content

Bugfixes, Etc. #502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 25, 2025
Merged
Changes from all 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
11 changes: 7 additions & 4 deletions .github/workflows/lua.yaml
Original file line number Diff line number Diff line change
@@ -45,12 +45,15 @@ jobs:
with:
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: hishamhm/gh-actions-luarocks@master
with:
luaRocksVersion: "3.12.0"
- name: build
run: |
luarocks install busted
- name: Run tests
shell: bash
run: |
5 changes: 3 additions & 2 deletions after/syntax/gitlab.vim
Original file line number Diff line number Diff line change
@@ -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 . '\)'
15 changes: 12 additions & 3 deletions cmd/app/client.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"

"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
"github.com/hashicorp/go-retryablehttp"
@@ -66,10 +67,18 @@ func NewClient() (*Client, error) {
},
}

if proxy := pluginOptions.ConnectionSettings.Proxy; proxy != "" {
u, err := url.Parse(proxy)
if err != nil {
return nil, fmt.Errorf("parse proxy url: %w", err)
}
tr.Proxy = http.ProxyURL(u)
}

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...)

@@ -99,11 +108,11 @@ func InitProjectSettings(c *Client, gitInfo git.GitData) (*ProjectInfo, error) {
project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt)

if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err)
return nil, fmt.Errorf("error getting project at %s: %w", gitInfo.RemoteUrl, err)
}

if project == nil {
return nil, fmt.Errorf(fmt.Sprintf("Could not find project at %s", gitInfo.RemoteUrl), err)
return nil, fmt.Errorf("could not find project at %s", gitInfo.RemoteUrl)
}

projectId := fmt.Sprint(project.ID)
8 changes: 7 additions & 1 deletion cmd/app/comment_helpers.go
Original file line number Diff line number Diff line change
@@ -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,
}
1 change: 1 addition & 0 deletions cmd/app/config.go
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ type PluginOptions struct {
} `json:"debug"`
ChosenMrIID int `json:"chosen_mr_iid"`
ConnectionSettings struct {
Proxy string `json:"proxy"`
Insecure bool `json:"insecure"`
Remote string `json:"remote"`
} `json:"connection_settings"`
3 changes: 3 additions & 0 deletions doc/gitlab.nvim.txt
Original file line number Diff line number Diff line change
@@ -161,6 +161,7 @@ you call this function with no values the defaults will be used:
},
},
connection_settings = {
proxy = "", -- Configure a proxy URL to use when connecting to GitLab. Supports URL schemes: http, https, socks5
insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection
remote = "origin", -- The default remote that your MRs target
},
@@ -213,6 +214,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
@@ -267,6 +269,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.
},
65 changes: 25 additions & 40 deletions lua-test.sh
Original file line number Diff line number Diff line change
@@ -1,59 +1,44 @@
#!/usr/bin/env bash
#
# Setup and run tests for lua part of gitlab.nvim.
#
# In order to run tests you need to have `luarocks` and `git` installed. This script will check if
# environment is already setup, if not it will initialize current directory with `luarocks`,
# install `busted` framework and download plugin dependencies.
#
# Requires `luarocks`, `git`, and `nvim` installed.
#
set -e

LUA_VERSION="5.1"
set -euo pipefail

PLUGINS_FOLDER="tests/plugins"
PLUGINS=(
"https://github.com/MunifTanjim/nui.nvim"
"https://github.com/nvim-lua/plenary.nvim"
"https://github.com/sindrets/diffview.nvim"
"https://github.com/MunifTanjim/nui.nvim"
"https://github.com/nvim-lua/plenary.nvim"
"https://github.com/sindrets/diffview.nvim"
)

if ! command -v luarocks > /dev/null 2>&1; then
echo "You need to have luarocks installed in order to run tests."
exit 1
fi

if ! command -v git > /dev/null 2>&1; then
echo "You need to have git installed in order to run tests."
exit 1
if ! command -v luarocks >/dev/null 2>&1; then
echo "Error: luarocks not found. Please install LuaRocks." >&2
exit 1
fi

if ! luarocks --lua-version=$LUA_VERSION which busted > /dev/null 2>&1; then
echo "Installing busted."
luarocks init
luarocks config --scope project lua_version "$LUA_VERSION"
luarocks install --lua-version="$LUA_VERSION" busted
if ! command -v git >/dev/null 2>&1; then
echo "Error: git not found. Please install Git." >&2
exit 1
fi

for arg in "$@"; do
if [[ $arg =~ "--coverage" ]] && ! luarocks --lua-version=$LUA_VERSION which luacov > /dev/null 2>&1; then
luarocks install --lua-version="$LUA_VERSION" luacov
# lcov reporter for luacov - lcov format is supported by `nvim-coverage`
luarocks install --lua-version="$LUA_VERSION" luacov-reporter-lcov
if ! command -v nvim >/dev/null 2>&1; then
echo "Error: nvim not found. Please install Neovim." >&2
exit 1
fi
done

# Clone test plugin dependencies
mkdir -p "$PLUGINS_FOLDER"
for plugin in "${PLUGINS[@]}"; do
plugin_name=${plugin##*/}
plugin_folder="$PLUGINS_FOLDER/$plugin_name"

# Check if plugin was already downloaded
if [[ -d "$plugin_folder/.git" ]]; then
# We could also try to pull here but I am not sure if that wouldn't slow down tests too much.
continue
fi

plugin_name="${plugin##*/}"
plugin_folder="$PLUGINS_FOLDER/$plugin_name"
if [[ ! -d "$plugin_folder/.git" ]]; then
echo "Cloning $plugin..."
git clone --depth 1 "$plugin" "$plugin_folder"

fi
done

nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"
# Run tests
echo "Running tests with Neovim..."
nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"
6 changes: 3 additions & 3 deletions lua/gitlab/actions/comment.lua
Original file line number Diff line number Diff line change
@@ -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
Preview

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
8 changes: 6 additions & 2 deletions lua/gitlab/actions/common.lua
Original file line number Diff line number Diff line change
@@ -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, ...)
@@ -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
19 changes: 18 additions & 1 deletion lua/gitlab/actions/discussions/init.lua
Original file line number Diff line number Diff line change
@@ -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
@@ -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

@@ -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
24 changes: 19 additions & 5 deletions lua/gitlab/actions/draft_notes/init.lua
Original file line number Diff line number Diff line change
@@ -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")
@@ -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

@@ -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)
@@ -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

@@ -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,
Loading