Skip to content

Commit 681c29e

Browse files
authored
Feat: Check feature branch is up-to-date on remote (#278)
feat: check the up-to-date status of the remote feature branch before creating a MR, starting a review, or opening the MR summary
1 parent 9ec134d commit 681c29e

File tree

5 files changed

+85
-36
lines changed

5 files changed

+85
-36
lines changed

lua/gitlab/actions/create_mr.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ end
4343
--- continue working on it.
4444
---@param args? Mr
4545
M.start = function(args)
46+
if not git.current_branch_up_to_date_on_remote("ERROR") then
47+
return
48+
end
49+
4650
if M.started then
4751
vim.ui.select({ "Yes", "No" }, { prompt = "Continue your previous MR?" }, function(choice)
4852
if choice == "Yes" then

lua/gitlab/actions/summary.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
-- send edits to the description back to Gitlab
44
local Layout = require("nui.layout")
55
local Popup = require("nui.popup")
6+
local git = require("gitlab.git")
67
local job = require("gitlab.job")
78
local common = require("gitlab.actions.common")
89
local u = require("gitlab.utils")
@@ -70,6 +71,8 @@ M.summary = function()
7071

7172
vim.api.nvim_set_current_buf(description_popup.bufnr)
7273
end)
74+
75+
git.current_branch_up_to_date_on_remote("WARN")
7376
end
7477

7578
-- Builds a lua list of strings that contain metadata about the current MR. Only builds the

lua/gitlab/git.lua

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local List = require("gitlab.utils.list")
2+
13
local M = {}
24

35
M.has_clean_tree = function()
@@ -12,4 +14,71 @@ M.switch_branch = function(branch)
1214
return vim.fn.trim(vim.fn.system({ "git", "checkout", "-q", branch }))
1315
end
1416

17+
---Return the name of the current branch
18+
---@return string|nil
19+
M.get_current_branch = function()
20+
local handle = io.popen("git branch --show-current 2>&1")
21+
if handle then
22+
return handle:read()
23+
else
24+
require("gitlab.utils").notify("Error running 'git branch' command.", vim.log.levels.ERROR)
25+
end
26+
end
27+
28+
---Return the list of names of all remote-tracking branches or an empty list.
29+
---@return table
30+
M.get_all_remote_branches = function()
31+
local all_branches = {}
32+
local handle = io.popen("git branch -r 2>&1")
33+
if not handle then
34+
require("gitlab.utils").notify("Error running 'git branch' command.", vim.log.levels.ERROR)
35+
return all_branches
36+
end
37+
38+
for line in handle:lines() do
39+
table.insert(all_branches, line)
40+
end
41+
handle:close()
42+
43+
return List.new(all_branches)
44+
:map(function(line)
45+
-- Trim "origin/"
46+
return line:match("origin/(%S+)")
47+
end)
48+
:filter(function(branch)
49+
-- Don't include the HEAD pointer
50+
return not branch:match("^HEAD$")
51+
end)
52+
end
53+
54+
---Returns true if `branch` is up-to-date on remote, false otherwise.
55+
---@return boolean|nil
56+
M.current_branch_up_to_date_on_remote = function(log_level)
57+
local current_branch = M.get_current_branch()
58+
local handle = io.popen("git branch -r --contains " .. current_branch .. " 2>&1")
59+
if not handle then
60+
require("gitlab.utils").notify("Error running 'git branch' command.", vim.log.levels.ERROR)
61+
return nil
62+
end
63+
64+
local remote_branches_with_current_head = {}
65+
for line in handle:lines() do
66+
table.insert(remote_branches_with_current_head, line)
67+
end
68+
handle:close()
69+
70+
local current_head_on_remote = List.new(remote_branches_with_current_head):filter(function(line)
71+
return line == " origin/" .. current_branch
72+
end)
73+
local remote_up_to_date = #current_head_on_remote == 1
74+
75+
if not remote_up_to_date then
76+
require("gitlab.utils").notify(
77+
"You have local commits that are not on origin. Have you forgotten to push?",
78+
vim.log.levels[log_level]
79+
)
80+
end
81+
return remote_up_to_date
82+
end
83+
1584
return M

lua/gitlab/reviewer/init.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ M.open = function()
8686
discussions.close()
8787
require("gitlab").toggle_discussions() -- Fetches data and opens discussions
8888
end
89+
90+
git.current_branch_up_to_date_on_remote("WARN")
8991
end
9092

9193
-- Closes the reviewer and cleans up

lua/gitlab/utils/init.lua

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local git = require("gitlab.git")
12
local List = require("gitlab.utils.list")
23
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
34
local M = {}
@@ -656,46 +657,16 @@ M.make_comma_separated_readable = function(str)
656657
return string.gsub(str, ",", ", ")
657658
end
658659

659-
---Return the name of the current branch
660-
---@return string|nil
661-
M.get_current_branch = function()
662-
local handle = io.popen("git branch --show-current 2>&1")
663-
if handle then
664-
return handle:read()
665-
else
666-
M.notify("Error running 'git branch' command.", vim.log.levels.ERROR)
667-
end
668-
end
669-
670-
---Return the list of names of all remote-tracking branches
660+
---Return the list of possible merge targets.
661+
---@return table|nil
671662
M.get_all_merge_targets = function()
672-
local handle = io.popen("git branch -r 2>&1")
673-
if not handle then
674-
M.notify("Error running 'git branch' command.", vim.log.levels.ERROR)
675-
return
676-
end
677-
678-
local current_branch = M.get_current_branch()
663+
local current_branch = git.get_current_branch()
679664
if not current_branch then
680665
return
681666
end
682-
683-
local lines = {}
684-
for line in handle:lines() do
685-
table.insert(lines, line)
686-
end
687-
handle:close()
688-
689-
-- Trim "origin/" and don't include the HEAD pointer
690-
local branches = List.new(lines)
691-
:map(function(line)
692-
return line:match("origin/(%S+)")
693-
end)
694-
:filter(function(branch)
695-
return not branch:match("^HEAD$") and branch ~= current_branch
696-
end)
697-
698-
return branches
667+
return List.new(git.get_all_remote_branches()):filter(function(branch)
668+
return branch ~= current_branch
669+
end)
699670
end
700671

701672
---Select a git branch and perform callback with the branch as an argument

0 commit comments

Comments
 (0)