From 259ee2df8aab48ed88203f26ecc0e99f1604110d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 8 Oct 2023 19:19:16 +0200 Subject: [PATCH 01/28] This can be removed. Fixed in 44bd94625c6fa95d33f7a2ddf077b50a59cc75c6. --- lua/neogit/config.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index da2c0882d..2785d6adf 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -228,7 +228,6 @@ function M.get_default_values() "NeogitPullPopup--rebase", "NeogitLogPopup--", "NeogitCommitPopup--allow-empty", - "NeogitRevertPopup--no-edit", -- TODO: Fix incompatible switches with default enables }, mappings = { finder = { From 4712c14f8b88d3b42a38c3f2d825972010ee6691 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:15 +0200 Subject: [PATCH 02/28] Remove this from ignored settings --- lua/neogit/config.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 2785d6adf..5c44f2ccd 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -226,7 +226,6 @@ function M.get_default_values() "NeogitPushPopup--force-with-lease", "NeogitPushPopup--force", "NeogitPullPopup--rebase", - "NeogitLogPopup--", "NeogitCommitPopup--allow-empty", }, mappings = { From f768a3a14a4ec17b5e0056cecd0b95e2ce141fb3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:29 +0200 Subject: [PATCH 03/28] If opts has a setup function, call it --- lua/neogit/lib/popup/builder.lua | 4 ++++ lua/neogit/popups/log/init.lua | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 4a184bee0..071104f95 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -194,6 +194,10 @@ function M:option(key, cli, value, description, opts) opts.separator = "=" end + if opts.setup then + opts.setup(self) + end + table.insert(self.state.args, { type = "option", id = opts.key_prefix .. key, diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index 89a973ffd..a64827bf1 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -29,6 +29,16 @@ function M.create() key_prefix = "-", separator = "", fn = actions.limit_to_files, + setup = function(popup) + local value = require("neogit.lib.state").get { "NeogitLogPopup", "" } + value = vim.split(value, " ", { trimempty = true }) + value = require("neogit.lib.util").map(value, function(v) + local result, _ = v:gsub([["]], "") + return result + end) + + popup.state.env.files = value + end, }) :switch("f", "follow", "Follow renames when showing single-file log") :arg_heading("Commit Ordering") From a82a7c878f5173068cc55cf88822c2fd9ea9f94a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:44 +0200 Subject: [PATCH 04/28] Nullify settings if empty string is passed --- lua/neogit/lib/state.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/state.lua b/lua/neogit/lib/state.lua index 6c6d466b0..5b0e762f2 100644 --- a/lua/neogit/lib/state.lua +++ b/lua/neogit/lib/state.lua @@ -79,15 +79,21 @@ local function gen_key(key_table) end ---Set option and write to disk ----@param key table +---@param key string[] ---@param value any function M.set(key, value) if not M.enabled() then return end - if not vim.tbl_contains(config.values.ignored_settings, gen_key(key)) then - M.state[gen_key(key)] = value + local cache_key = gen_key(key) + if not vim.tbl_contains(config.values.ignored_settings, cache_key) then + if value == "" then + M.state[cache_key] = nil + else + M.state[cache_key] = value + end + M.write() end end From 9d0a8053a35f245baaaaba727ec62f5f3c7037c9 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Thu, 16 Nov 2023 08:04:09 +0100 Subject: [PATCH 05/28] Show HEAD as `NeogitBranchHead` in Log Graph In the log graph it's non intuitive where the currently checked out branch is. To make this more customizable for the user, we add the `NeogitBranchHead` highlight group. --- doc/neogit.txt | 1 + lua/neogit/buffers/common.lua | 9 ++++++--- lua/neogit/lib/hl.lua | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index d7f3cc7b0..402c357f0 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -200,6 +200,7 @@ exist, they will be created with sensible defaults based on your colorscheme. STATUS BUFFER NeogitBranch Header showing currently checked out branch +NeogitBranchHead Currently checkoued branch (in log graph) NeogitRemote Header showing current branch's remote (if set) NeogitObjectId Object's SHA hash NeogitStash Stash name diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d714ee6cf..19e070c66 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -120,16 +120,19 @@ M.CommitEntry = Component.new(function(commit, args) remote_name, local_name = local_name, remote_name end + local is_head = string.match(commit.ref_name, "HEAD") ~= nil + local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" + if local_name and remote_name and vim.endswith(remote_name, local_name) then local remote = remote_name:match("^([^/]*)/.*$") table.insert(ref, text(remote .. "/", { highlight = "NeogitRemote" })) - table.insert(ref, text(local_name, { highlight = "NeogitBranch" })) + table.insert(ref, text(local_name, { highlight = branch_highlight })) table.insert(ref, text(" ")) else if local_name then table.insert( ref, - text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or "NeogitBranch" }) + text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or branch_highlight }) ) table.insert(ref, text(" ")) end @@ -137,7 +140,7 @@ M.CommitEntry = Component.new(function(commit, args) if remote_name then table.insert( ref, - text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or "NeogitBranch" }) + text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or branch_highlight }) ) table.insert(ref, text(" ")) end diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 2b966f893..f53970d4f 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -164,6 +164,7 @@ function M.setup() NeogitCommandCodeNormal = { link = "String" }, NeogitCommandCodeError = { link = "Error" }, NeogitBranch = { fg = palette.blue, bold = true }, + NeogitBranchHead = { fg = palette.blue, bold = true, underline = true }, NeogitRemote = { fg = palette.green, bold = true }, NeogitUnmergedInto = { fg = palette.bg_purple, bold = true }, NeogitUnpushedTo = { fg = palette.bg_purple, bold = true }, From 91d422599eb9b70aeb6f1748b60f703f52775f44 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Fri, 17 Nov 2023 08:41:31 +0100 Subject: [PATCH 06/28] FIX: Don't highlight `origin/HEAD` with `NeogitBranchHead` --- lua/neogit/buffers/common.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 19e070c66..badb38489 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -120,7 +120,7 @@ M.CommitEntry = Component.new(function(commit, args) remote_name, local_name = local_name, remote_name end - local is_head = string.match(commit.ref_name, "HEAD") ~= nil + local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" if local_name and remote_name and vim.endswith(remote_name, local_name) then From be708dbbca233d5fc12fe8a17c43982920958401 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 08:09:13 +0100 Subject: [PATCH 07/28] Reformulate highlight groups in docs --- doc/neogit.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 402c357f0..8cd549819 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -199,9 +199,9 @@ these yourself before the plugin loads, that will be respected. If they do not exist, they will be created with sensible defaults based on your colorscheme. STATUS BUFFER -NeogitBranch Header showing currently checked out branch -NeogitBranchHead Currently checkoued branch (in log graph) -NeogitRemote Header showing current branch's remote (if set) +NeogitBranch Local branches +NeogitBranchHead Accent highlight for current HEAD in LogBuffer +NeogitRemote Remote branches NeogitObjectId Object's SHA hash NeogitStash Stash name NeogitFold Folded text highlight From 6cfce41a3c26f6eb297a1f290cf45729be29242d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:56:33 +0100 Subject: [PATCH 08/28] Update status message when discarding selection --- lua/neogit/status.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 31145bfac..758ac32f7 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -924,7 +924,9 @@ local unstage = operation("unstage", function() end) local function discard_message(files, hunk_count) - if hunk_count > 0 then + if vim.api.nvim_get_mode() == "V" then + return string.format("Discard selection?") + elseif hunk_count > 0 then return string.format("Discard %d hunks?", hunk_count) elseif #files > 1 then return string.format("Discard %d files?", #files) From b84833fe7360828dd411239754d3d58686146637 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 11:00:26 +0100 Subject: [PATCH 09/28] Don't string format --- lua/neogit/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 758ac32f7..aadc0478b 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -925,7 +925,7 @@ end) local function discard_message(files, hunk_count) if vim.api.nvim_get_mode() == "V" then - return string.format("Discard selection?") + return "Discard selection?" elseif hunk_count > 0 then return string.format("Discard %d hunks?", hunk_count) elseif #files > 1 then From e27f76b4514c2754af08a9c7acd7b3ed2ae683fd Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 11:19:29 +0100 Subject: [PATCH 10/28] Add --tags option to pull popup --- lua/neogit/popups/pull/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index 313a2a442..dcba8a281 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -23,6 +23,7 @@ function M.create() :switch("f", "ff-only", "Fast-forward only") :switch("r", "rebase", "Rebase local commits") :switch("a", "autostash", "Autostash") + :switch("t", "tags", "Fetch tags") :group_heading_if(current, "Pull into " .. current .. " from") :group_heading_if(not current, "Pull from") :action_if(current, "p", git.branch.pushRemote_label(), actions.from_pushremote) From a246eeb7f01756f9b972de865c4f7e2d6d6accb7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:56:36 +0100 Subject: [PATCH 11/28] Mark TODOs --- doc/neogit.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index d7f3cc7b0..7e2d65173 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -501,9 +501,11 @@ Branch Popup *neogit_branch_popup* Variables: *neogit_branch_popup_variables* • branch..description + (TODO) • branch..merge branch..remote + (TODO) • branch..rebase Cycles branch..rebase value between true, false, and the value of From a0d464d1933111bb0dd90013dc676e1faef0baa1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:56:59 +0100 Subject: [PATCH 12/28] Add docs for Pull popup --- doc/neogit.txt | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 7e2d65173..dc372aae4 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -945,7 +945,62 @@ Remote: ============================================================================== Pull Popup *neogit_pull_popup* -(TODO) +Variables: *neogit_pull_popup_variables* + • branch..rebase + When true, rebase the branch on top of the fetched branch, instead + of merging the default branch from the default remote when "git pull" is + run. See "pull.rebase" for doing this in a non branch-specific manner. + +Arguments: *neogit_pull_popup_args* + • --ff-only + Only update to the new history if there is no divergent local history. + This is the default when no method for reconciling divergent histories is + provided (via the --rebase=* flags). + + • --rebase + When enabled, rebase the current branch on top of the upstream branch + after fetching. If there is a remote-tracking branch corresponding to the + upstream branch and the upstream branch was rebased since last fetched, + the rebase uses that information to avoid rebasing non-local changes. + + See pull.rebase, branch..rebase and branch.autoSetupRebase if you + want to make git pull always use --rebase instead of merging. + + Note: + This is a potentially dangerous mode of operation. It rewrites history, + which does not bode well when you published that history already. Do not + use this option unless you understand the consequences. + + • --autostash + Automatically create a temporary stash entry before the operation begins, + record it in the special ref MERGE_AUTOSTASH and apply it after the + operation ends. This means that you can run the operation on a dirty + worktree. However, use with care: the final stash application after a + successful merge might result in non-trivial conflicts. + + • --tags + Fetch all tags from the remote (i.e., fetch remote tags refs/tags/* into + local tags with the same name), in addition to whatever else would + otherwise be fetched. + +Actions: *neogit_pull_popup_actions* + • Pull into from pushRemote *neogit_pull_pushRemote* + Pulls into the current branch from `branch..pushRemote`. If that is + unset, the user will be prompted to select a remote branch, which will + pulled from and used to set `branch..pushRemote. + + • Pull into from @{upstream} *neogit_pull_upstream* + Pulls into the current branch from it's upstream counterpart. If that is + unset, the user will be prompted to select a remote branch, which will + pulled from and set as the upstream. + + • Pull into from elsewhere *neogit_pull_elsewhere* + Prompts the user to select a branch (local or remote), which is then + pulled in the current branch. + + • Set Variables *neogit_pull_set_variables* + Opens Branch Config popup for the current branch. + See |neogit_branch_config_popup|. ============================================================================== Push Popup *neogit_push_popup* From 011d536de2840ad44d47948d3ac56ef25a266f78 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:57:06 +0100 Subject: [PATCH 13/28] Allow pulling from ALL branches, not just remote --- lua/neogit/popups/pull/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index c63c02ed4..8f2dbff0f 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -62,7 +62,7 @@ function M.from_upstream(popup) end function M.from_elsewhere(popup) - local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()) + local target = FuzzyFinderBuffer.new(git.branch.get_all_branches(false)) :open_async { prompt_prefix = "pull > " } if not target then return From 7213e19e2f48ea1a44c7b9d3064496eae4ed6a53 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:59:21 +0100 Subject: [PATCH 14/28] Typo --- doc/neogit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index dc372aae4..185370163 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -987,7 +987,7 @@ Actions: *neogit_pull_popup_actions* • Pull into from pushRemote *neogit_pull_pushRemote* Pulls into the current branch from `branch..pushRemote`. If that is unset, the user will be prompted to select a remote branch, which will - pulled from and used to set `branch..pushRemote. + pulled from and used to set `branch..pushRemote`. • Pull into from @{upstream} *neogit_pull_upstream* Pulls into the current branch from it's upstream counterpart. If that is From 3ec601d8c26b46f84cbf7e22838d45a478a09acc Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Fri, 17 Nov 2023 08:33:53 +0100 Subject: [PATCH 15/28] Parse `tag: ...` and render with `NeogitTagName` in Log Graph The `commit.ref_name` might also contain a tag name, which the log graph is currently rendering in the `NeogitBranch` highlight group. The refname of HEAD with a tag placed on it might look like this: > HEAD -> main, tag: 1.0.0, origin/main, origin/HEAD A refname of a tag not associated to any branch might look like this: > tag: 1.0.0 This makes the parsing of `local_name`, `remote_name` a bit more sophisticated, since we don't know the exact order of them. Naive solution might be to just split `ref_name` by comma and pattern match: * contains `tag: ...` -> `tag_name` * contains `/` -> `remote_name` * otherwise -> `local_name` --- lua/neogit/buffers/common.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index badb38489..323533f61 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -113,11 +113,21 @@ M.CommitEntry = Component.new(function(commit, args) -- Parse out ref names if args.decorate and commit.ref_name ~= "" then local ref_name, _ = commit.ref_name:gsub("HEAD %-> ", "") - local remote_name, local_name = unpack(vim.split(ref_name, ", ")) - -- Sometimes the log output will list remote/local names in reverse order - if (local_name and local_name:match("/")) and (remote_name and not remote_name:match("/")) then - remote_name, local_name = local_name, remote_name + local names = vim.split(ref_name, ", ") + local local_name = nil + local remote_name = nil + local tag_name = nil + for _, name in ipairs(names) do + if name:match("^tag: .*") ~= nil then + tag_name = name:gsub("tag: ", "") + end + if local_name == nil and not name:match("/") then + local_name = name + end + if remote_name == nil and name:match("/") then + remote_name = name + end end local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil @@ -145,6 +155,11 @@ M.CommitEntry = Component.new(function(commit, args) table.insert(ref, text(" ")) end end + + if tag_name ~= nil then + table.insert(ref, text(tag_name, { highlight = "NeogitTagName" })) + table.insert(ref, text(" ")) + end end if commit.rel_date:match(" years?,") then From b96051c7dc02ffed5493847e03beb736c9b5d856 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 11:23:39 +0100 Subject: [PATCH 16/28] Properly interprete branch names, remotes & tags Parsing the the `ref_name` manually by regex is finicky and error prone. To better identify, which of the parts of `ref_name` are remotes and which are branch names one can not rely on finding the `/`, since branches might also contain `/`. Also the order of the fields within the `ref_name` can vary. For a robust solution I extracted this parsing into the `lib/git/log.lua` module and added unit tests as well. This simplifies the logic of the logging function as well. --- lua/neogit/buffers/common.lua | 51 +++++------------- lua/neogit/lib/git/log.lua | 44 ++++++++++++++++ tests/specs/neogit/lib/git/log_spec.lua | 70 +++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 39 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 323533f61..abdf176bd 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,6 +1,8 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local log = require("neogit.lib.git.log") +local remote = require("neogit.lib.git.remote") local text = Ui.text local col = Ui.col @@ -113,51 +115,22 @@ M.CommitEntry = Component.new(function(commit, args) -- Parse out ref names if args.decorate and commit.ref_name ~= "" then local ref_name, _ = commit.ref_name:gsub("HEAD %-> ", "") - - local names = vim.split(ref_name, ", ") - local local_name = nil - local remote_name = nil - local tag_name = nil - for _, name in ipairs(names) do - if name:match("^tag: .*") ~= nil then - tag_name = name:gsub("tag: ", "") - end - if local_name == nil and not name:match("/") then - local_name = name - end - if remote_name == nil and name:match("/") then - remote_name = name - end - end - local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - if local_name and remote_name and vim.endswith(remote_name, local_name) then - local remote = remote_name:match("^([^/]*)/.*$") - table.insert(ref, text(remote .. "/", { highlight = "NeogitRemote" })) - table.insert(ref, text(local_name, { highlight = branch_highlight })) - table.insert(ref, text(" ")) - else - if local_name then - table.insert( - ref, - text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or branch_highlight }) - ) - table.insert(ref, text(" ")) + local items = log.interprete(ref_name, remote.list()) + for branch, remotes in pairs(items.branches) do + if #remotes == 1 then + table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) end - - if remote_name then - table.insert( - ref, - text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or branch_highlight }) - ) - table.insert(ref, text(" ")) + if #remotes > 1 then + table.insert(ref, text("{" .. table.concat(remotes, ",") .. "}/", { highlight = "NeogitRemote" })) end + table.insert(ref, text(branch, { highlight = branch_highlight })) + table.insert(ref, text(" ")) end - - if tag_name ~= nil then - table.insert(ref, text(tag_name, { highlight = "NeogitTagName" })) + for _, tag in pairs(items.tags) do + table.insert(ref, text(tag, { highlight = "NeogitTagName" })) table.insert(ref, text(" ")) end end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index ac7e114b1..d22fdcaeb 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -438,4 +438,48 @@ function M.verify_commit(commit) return cli["verify-commit"].args(commit).call_sync_ignoring_exit_code():trim().stderr end +---@class CommitBranchInfo +---@field branches table Mapping from (local) branch names to list of remotes where this branch is present (empty for local branches) +---@field tags string[] List of tags placed on this commit + +--- Parse information of branches, tags and remotes from a given commit's ref output +--- @param ref string comma separated list of branches, tags and remotes, e.g.: +--- * "origin/main, main, origin/HEAD, tag: 1.2.3, fork/develop" +--- @param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git.remote").list()` +--- @return CommitBranchInfo +function M.interprete(ref, remotes) + local parts = vim.split(ref, ", ") + local result = { + branches = {}, + tags = {}, + } + for _, part in ipairs(parts) do + if part:match("^tag: .*") ~= nil then + local tag = part:gsub("tag: ", "") + table.insert(result.tags, tag) + -- No need to annotate tags with remotes, probably too cluttered + goto continue + end + local has_remote = false + for _, remote in ipairs(remotes) do + if part:match("^" .. remote .. "/") then + has_remote = true + local name = part:gsub("^" .. remote .. "/", "") + if name == "HEAD" then + goto continue + end + if result.branches[name] == nil then + result.branches[name] = {} + end + table.insert(result.branches[name], remote) + end + end + if not has_remote and result.branches[part] == nil then + result.branches[part] = {} + end + ::continue:: + end + return result +end + return M diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 73036cf7f..80d02954b 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -2,6 +2,7 @@ local status = require("neogit.status") local plenary_async = require("plenary.async") local git_harness = require("tests.util.git_harness") local util = require("tests.util.util") +local remote = require("neogit.lib.git.remote") local subject = require("neogit.lib.git.log") @@ -321,4 +322,73 @@ describe("lib.git.log.parse", function() assert.are.same(v, expected[k]) end end) + + it("lib.git.log.interprete extracts local branch name", function() + local remotes = remote.list() + assert.are.same({ branches = { main = {} }, tags = {} }, subject.interprete("main", remotes)) + assert.are.same( + { branches = { main = {}, develop = {} }, tags = {} }, + subject.interprete("main, develop", remotes) + ) + end) + + it("lib.git.log.interprete extracts local & remote branch names", function() + local remotes = { "origin" } + assert.are.same( + { branches = { main = { "origin" } }, tags = {} }, + subject.interprete("origin/main", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + subject.interprete("origin/main, origin/develop", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + subject.interprete("origin/main, main, origin/develop", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, + subject.interprete("main, origin/main, origin/develop, develop, foo", remotes) + ) + end) + + it("lib.git.log.interprete can deal with multiple remotes", function() + local remotes = { "origin", "fork" } + assert.are.same( + { branches = { main = { "origin", "fork" } }, tags = {} }, + subject.interprete("origin/main, main, fork/main", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, + subject.interprete("origin/main, develop, origin/develop, fork/develop, foo", remotes) + ) + end) + + it("lib.git.log.interprete can deal with slashes in branch names", function() + local remotes = { "origin" } + assert.are.same( + { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, + subject.interprete("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) + ) + end) + + it("lib.git.log.interprete ignores HEAD references", function() + local remotes = { "origin", "fork" } + assert.are.same( + { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, + subject.interprete("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) + ) + end) + + it("lib.git.log.interprete parses tags", function() + local remotes = { "origin" } + assert.are.same({ + branches = {}, + tags = { "0.1.0" }, + }, subject.interprete("tag: 0.1.0", remotes)) + assert.are.same({ + branches = {}, + tags = { "0.5.7", "foo-bar" }, + }, subject.interprete("tag: 0.5.7, tag: foo-bar", remotes)) + end) end) From ee34f04cafb82e02e98473d4baffaf898d913b52 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 15:04:46 +0100 Subject: [PATCH 17/28] fix lint because selene doesn't support `goto`s =( --- lua/neogit/lib/git/log.lua | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index d22fdcaeb..3ceed1e55 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -454,30 +454,33 @@ function M.interprete(ref, remotes) tags = {}, } for _, part in ipairs(parts) do + local skip = false if part:match("^tag: .*") ~= nil then local tag = part:gsub("tag: ", "") table.insert(result.tags, tag) -- No need to annotate tags with remotes, probably too cluttered - goto continue + skip = true end local has_remote = false for _, remote in ipairs(remotes) do - if part:match("^" .. remote .. "/") then - has_remote = true - local name = part:gsub("^" .. remote .. "/", "") - if name == "HEAD" then - goto continue + if not skip then + if part:match("^" .. remote .. "/") then + has_remote = true + local name = part:gsub("^" .. remote .. "/", "") + if name == "HEAD" then + skip = true + else + if result.branches[name] == nil then + result.branches[name] = {} + end + table.insert(result.branches[name], remote) + end end - if result.branches[name] == nil then - result.branches[name] = {} - end - table.insert(result.branches[name], remote) end end - if not has_remote and result.branches[part] == nil then + if not skip and not has_remote and result.branches[part] == nil then result.branches[part] = {} end - ::continue:: end return result end From 3d3e06f77ed7d8762a5ca31739919e0588958252 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 08:21:14 +0100 Subject: [PATCH 18/28] Rename `interprete()` -> `branch_info()` --- lua/neogit/buffers/common.lua | 2 +- lua/neogit/lib/git/log.lua | 2 +- tests/specs/neogit/lib/git/log_spec.lua | 36 ++++++++++++------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index abdf176bd..78638670c 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -118,7 +118,7 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = log.interprete(ref_name, remote.list()) + local items = log.branch_info(ref_name, remote.list()) for branch, remotes in pairs(items.branches) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 3ceed1e55..e9a44e477 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -447,7 +447,7 @@ end --- * "origin/main, main, origin/HEAD, tag: 1.2.3, fork/develop" --- @param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git.remote").list()` --- @return CommitBranchInfo -function M.interprete(ref, remotes) +function M.branch_info(ref, remotes) local parts = vim.split(ref, ", ") local result = { branches = {}, diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 80d02954b..f699cc40c 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -323,72 +323,72 @@ describe("lib.git.log.parse", function() end end) - it("lib.git.log.interprete extracts local branch name", function() + it("lib.git.log.branch_info extracts local branch name", function() local remotes = remote.list() - assert.are.same({ branches = { main = {} }, tags = {} }, subject.interprete("main", remotes)) + assert.are.same({ branches = { main = {} }, tags = {} }, subject.branch_info("main", remotes)) assert.are.same( { branches = { main = {}, develop = {} }, tags = {} }, - subject.interprete("main, develop", remotes) + subject.branch_info("main, develop", remotes) ) end) - it("lib.git.log.interprete extracts local & remote branch names", function() + it("lib.git.log.branch_info extracts local & remote branch names", function() local remotes = { "origin" } assert.are.same( { branches = { main = { "origin" } }, tags = {} }, - subject.interprete("origin/main", remotes) + subject.branch_info("origin/main", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, - subject.interprete("origin/main, origin/develop", remotes) + subject.branch_info("origin/main, origin/develop", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, - subject.interprete("origin/main, main, origin/develop", remotes) + subject.branch_info("origin/main, main, origin/develop", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, - subject.interprete("main, origin/main, origin/develop, develop, foo", remotes) + subject.branch_info("main, origin/main, origin/develop, develop, foo", remotes) ) end) - it("lib.git.log.interprete can deal with multiple remotes", function() + it("lib.git.log.branch_info can deal with multiple remotes", function() local remotes = { "origin", "fork" } assert.are.same( { branches = { main = { "origin", "fork" } }, tags = {} }, - subject.interprete("origin/main, main, fork/main", remotes) + subject.branch_info("origin/main, main, fork/main", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, - subject.interprete("origin/main, develop, origin/develop, fork/develop, foo", remotes) + subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes) ) end) - it("lib.git.log.interprete can deal with slashes in branch names", function() + it("lib.git.log.branch_info can deal with slashes in branch names", function() local remotes = { "origin" } assert.are.same( { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, - subject.interprete("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) + subject.branch_info("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) ) end) - it("lib.git.log.interprete ignores HEAD references", function() + it("lib.git.log.branch_info ignores HEAD references", function() local remotes = { "origin", "fork" } assert.are.same( { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, - subject.interprete("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) + subject.branch_info("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) ) end) - it("lib.git.log.interprete parses tags", function() + it("lib.git.log.branch_info parses tags", function() local remotes = { "origin" } assert.are.same({ branches = {}, tags = { "0.1.0" }, - }, subject.interprete("tag: 0.1.0", remotes)) + }, subject.branch_info("tag: 0.1.0", remotes)) assert.are.same({ branches = {}, tags = { "0.5.7", "foo-bar" }, - }, subject.interprete("tag: 0.5.7, tag: foo-bar", remotes)) + }, subject.branch_info("tag: 0.5.7, tag: foo-bar", remotes)) end) end) From 2eb57b0328837c4b19026c1f6f9a85f93382f785 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 08:22:39 +0100 Subject: [PATCH 19/28] Better greppable imports for `neogit.lib.git` --- lua/neogit/buffers/common.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 78638670c..f4c620e8f 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,8 +1,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") -local log = require("neogit.lib.git.log") -local remote = require("neogit.lib.git.remote") +local git = require("neogit.lib.git") local text = Ui.text local col = Ui.col @@ -118,7 +117,7 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = log.branch_info(ref_name, remote.list()) + local items = git.log.branch_info(ref_name, git.remote.list()) for branch, remotes in pairs(items.branches) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) From 0fd955849186779a9a4fa7f0e41f88fbe08ec9eb Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 10:09:57 +0100 Subject: [PATCH 20/28] Fix ordering of tracked/untracked branches in log graph Since lua tables are not sorted, the exact order in which the branch names are placed in the log graph can vary and depdends on the output of `git log...`. For example in Git version 2.34 it would: > e2c2a1c * origin/tracked-branch untracked-branch MESSAGE whereas in Git 2.39 it would be reversed: > e2c2a1c * untracked-branch origin/tracked-branch MESSAGE To keep the behaviour consistent between git versions, I introduced the notion of `tracked` and `untracked` branches in the `git.log.branch_info()`. * `untracked`: List of all branches which have no remote counterpart (i.e. are local only) * `tracked`: Mapping of local branch names (keys) which a list of remote names (values) which contain such branches. --- lua/neogit/buffers/common.lua | 10 +++++-- lua/neogit/lib/git/log.lua | 23 ++++++++++----- tests/specs/neogit/lib/git/log_spec.lua | 38 +++++++++++++------------ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index f4c620e8f..268e8e2f9 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -117,8 +117,12 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = git.log.branch_info(ref_name, git.remote.list()) - for branch, remotes in pairs(items.branches) do + local info = git.log.branch_info(ref_name, git.remote.list()) + for _, branch in ipairs(info.untracked) do + table.insert(ref, text(branch, { highlight = "NeogitBranch" })) + table.insert(ref, text(" ")) + end + for branch, remotes in pairs(info.tracked) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) end @@ -128,7 +132,7 @@ M.CommitEntry = Component.new(function(commit, args) table.insert(ref, text(branch, { highlight = branch_highlight })) table.insert(ref, text(" ")) end - for _, tag in pairs(items.tags) do + for _, tag in pairs(info.tags) do table.insert(ref, text(tag, { highlight = "NeogitTagName" })) table.insert(ref, text(" ")) end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index e9a44e477..62557d110 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -439,7 +439,8 @@ function M.verify_commit(commit) end ---@class CommitBranchInfo ----@field branches table Mapping from (local) branch names to list of remotes where this branch is present (empty for local branches) +---@field untracked string[] List of local branches on without any remote counterparts +---@field tracked table Mapping from (local) branch names to list of remotes where this branch is present ---@field tags string[] List of tags placed on this commit --- Parse information of branches, tags and remotes from a given commit's ref output @@ -450,9 +451,11 @@ end function M.branch_info(ref, remotes) local parts = vim.split(ref, ", ") local result = { - branches = {}, + untracked = {}, + tracked = {}, tags = {}, } + local untracked = {} for _, part in ipairs(parts) do local skip = false if part:match("^tag: .*") ~= nil then @@ -470,16 +473,22 @@ function M.branch_info(ref, remotes) if name == "HEAD" then skip = true else - if result.branches[name] == nil then - result.branches[name] = {} + if result.tracked[name] == nil then + result.tracked[name] = {} end - table.insert(result.branches[name], remote) + table.insert(result.tracked[name], remote) end end end end - if not skip and not has_remote and result.branches[part] == nil then - result.branches[part] = {} + -- if not skip and not has_remote and result.tracked[part] == nil then + if not skip and not has_remote then + table.insert(untracked, part) + end + end + for _, branch in ipairs(untracked) do + if result.tracked[branch] == nil then + table.insert(result.untracked, branch) end end return result diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index f699cc40c..948fd3c74 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -325,9 +325,9 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info extracts local branch name", function() local remotes = remote.list() - assert.are.same({ branches = { main = {} }, tags = {} }, subject.branch_info("main", remotes)) + assert.are.same({ untracked = { "main" }, tracked = {}, tags = {} }, subject.branch_info("main", remotes)) assert.are.same( - { branches = { main = {}, develop = {} }, tags = {} }, + { untracked = { "main", "develop" }, tracked = {}, tags = {} }, subject.branch_info("main, develop", remotes) ) end) @@ -335,19 +335,19 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info extracts local & remote branch names", function() local remotes = { "origin" } assert.are.same( - { branches = { main = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, origin/develop", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, main, origin/develop", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = { "foo" } }, subject.branch_info("main, origin/main, origin/develop, develop, foo", remotes) ) end) @@ -355,19 +355,20 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info can deal with multiple remotes", function() local remotes = { "origin", "fork" } assert.are.same( - { branches = { main = { "origin", "fork" } }, tags = {} }, + { tracked = { main = { "origin", "fork" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, main, fork/main", remotes) ) - assert.are.same( - { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, - subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes) - ) + assert.are.same({ + tracked = { main = { "origin" }, develop = { "origin", "fork" } }, + tags = {}, + untracked = { "foo" }, + }, subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes)) end) it("lib.git.log.branch_info can deal with slashes in branch names", function() local remotes = { "origin" } assert.are.same( - { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, + { tracked = { ["feature/xyz"] = { "origin" } }, untracked = { "foo/bar/baz" }, tags = {} }, subject.branch_info("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) ) end) @@ -375,19 +376,20 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info ignores HEAD references", function() local remotes = { "origin", "fork" } assert.are.same( - { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, + { tracked = { main = { "origin", "fork" } }, untracked = { "develop" }, tags = {} }, subject.branch_info("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) ) end) it("lib.git.log.branch_info parses tags", function() local remotes = { "origin" } + assert.are.same( + { tracked = {}, untracked = {}, tags = { "0.1.0" } }, + subject.branch_info("tag: 0.1.0", remotes) + ) assert.are.same({ - branches = {}, - tags = { "0.1.0" }, - }, subject.branch_info("tag: 0.1.0", remotes)) - assert.are.same({ - branches = {}, + tracked = {}, + untracked = {}, tags = { "0.5.7", "foo-bar" }, }, subject.branch_info("tag: 0.5.7, tag: foo-bar", remotes)) end) From b21711f91b6c86f8d694200a87bca854ea31827c Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Mon, 20 Nov 2023 00:54:50 +0100 Subject: [PATCH 21/28] remove dependencies on cwd --- lua/neogit.lua | 8 +-- lua/neogit/integrations/diffview.lua | 42 ++-------------- lua/neogit/lib/fs.lua | 6 +-- lua/neogit/lib/git/cli.lua | 50 +++++-------------- lua/neogit/lib/git/config.lua | 4 +- lua/neogit/lib/git/index.lua | 8 +-- lua/neogit/lib/git/init.lua | 2 +- lua/neogit/lib/git/merge.lua | 7 +-- lua/neogit/lib/git/rebase.lua | 7 +-- lua/neogit/lib/git/repository.lua | 61 +++++++++++------------ lua/neogit/lib/git/sequencer.lua | 13 ++--- lua/neogit/lib/git/status.lua | 1 - lua/neogit/popups/ignore/actions.lua | 2 +- lua/neogit/popups/init.lua | 2 +- lua/neogit/process.lua | 3 -- lua/neogit/status.lua | 24 ++++----- tests/README.md | 2 +- tests/specs/neogit/lib/git/cli_spec.lua | 4 +- tests/specs/neogit/popups/branch_spec.lua | 1 + tests/specs/neogit/popups/remote_spec.lua | 2 + 20 files changed, 86 insertions(+), 163 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index d37aa40a6..c6a32d494 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -67,7 +67,7 @@ function M.open(opts) end if not opts.cwd then - opts.cwd = require("neogit.lib.git.cli").git_root() + opts.cwd = require("neogit.lib.git.cli").git_root_of_cwd() end if not did_setup then @@ -76,7 +76,7 @@ function M.open(opts) return end - if not cli.git_is_repository_sync(opts.cwd) then + if not cli.is_inside_worktree(opts.cwd) then if input.get_confirmation( string.format("Initialize repository in %s?", opts.cwd or vim.fn.getcwd()), @@ -170,10 +170,6 @@ function M.complete(arglead) end, { "kind=", "cwd=", "commit" }) end -function M.get_repo() - return require("neogit.lib.git").repo -end - function M.get_log_file_path() return vim.fn.stdpath("cache") .. "/neogit.log" end diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index aab3da75d..343662da2 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -9,6 +9,7 @@ local dv_lib = require("diffview.lib") local dv_utils = require("diffview.utils") local neogit = require("neogit") +local repo = require("neogit.lib.git.repository") local status = require("neogit.status") local a = require("plenary.async") @@ -35,47 +36,12 @@ local function cb(name) return string.format(":lua require('neogit.integrations.diffview').diffview_mappings['%s']()", name) end ----Resolves a cwd local file to git root relative -local function root_prefix(git_root, cwd, path) - local t = {} - for part in string.gmatch(cwd .. "/" .. path, "[^/\\]+") do - if part == ".." then - if #t > 0 and t[#t] ~= ".." then - table.remove(t, #t) - else - table.insert(t, "..") - end - else - table.insert(t, part) - end - end - - local git_root_parts = {} - for part in git_root:gmatch("[^/\\]+") do - table.insert(git_root_parts, part) - end - - local s = {} - local skipping = true - for i = 1, #t do - if not skipping or git_root_parts[i] ~= t[i] then - table.insert(s, t[i]) - skipping = false - end - end - - path = table.concat(s, "/") - return path -end - local function get_local_diff_view(selected_file_name) local left = Rev(RevType.STAGE) local right = Rev(RevType.LOCAL) - local git_root = neogit.cli.git_root_sync() local function update_files() local files = {} - local repo = neogit.get_repo() local sections = { conflicting = { items = vim.tbl_filter(function(o) @@ -89,9 +55,7 @@ local function get_local_diff_view(selected_file_name) files[kind] = {} for _, item in ipairs(section.items) do local file = { - -- use the repo.cwd instead of current as it may change since the - -- status was refreshed - path = root_prefix(git_root, repo.cwd, item.name), + path = item.name, status = item.mode and item.mode:sub(1, 1), stats = (item.diff and item.diff.stats) and { additions = item.diff.stats.additions or 0, @@ -112,7 +76,7 @@ local function get_local_diff_view(selected_file_name) local files = update_files() local view = CDiffView { - git_root = git_root, + git_root = repo.git_root, left = left, right = right, files = files, diff --git a/lua/neogit/lib/fs.lua b/lua/neogit/lib/fs.lua index 23bc9fc0b..126a8abcc 100644 --- a/lua/neogit/lib/fs.lua +++ b/lua/neogit/lib/fs.lua @@ -3,11 +3,7 @@ local cli = require("neogit.lib.git.cli") local M = {} function M.relpath_from_repository(path) - local result = cli["ls-files"].others.cached.modified.deleted.full_name - .cwd("") - .args(path) - .show_popup(false) - .call() + local result = cli["ls-files"].others.cached.modified.deleted.full_name.args(path).show_popup(false).call() return result.stdout[1] end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index b1ef82884..224c9848f 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -521,8 +521,11 @@ local configurations = { ["verify-commit"] = config {}, } --- TODO: Consider returning a Path object, since consumers of this function tend to need that anyways. -local function git_root() +-- NOTE: Use require("neogit.lib.git.repository").git_root instead of calling this function. +-- repository.git_root is used by all other library functions, so it's most likely the one you want to use. +-- git_root_of_cwd() returns the git repo of the cwd, which can change anytime +-- after git_root_of_cwd() has been called. +local function git_root_of_cwd() local process = process.new({ cmd = { "git", "rev-parse", "--show-toplevel" }, ignore_code = true }):spawn_blocking() @@ -533,15 +536,7 @@ local function git_root() end end -local git_root_sync = function() - return util.trim(vim.fn.system("git rev-parse --show-toplevel")) -end - -local git_dir_path_sync = function() - return util.trim(vim.fn.system("git rev-parse --git-dir")) -end - -local git_is_repository_sync = function(cwd) +local is_inside_worktree = function(cwd) if not cwd then vim.fn.system("git rev-parse --is-inside-work-tree") else @@ -634,13 +629,6 @@ local mt_builder = { end end - if action == "cwd" then - return function(cwd) - tbl[k_state].cwd = cwd - return tbl - end - end - if action == "prefix" then return function(x) tbl[k_state].prefix = x @@ -791,7 +779,6 @@ local function new_builder(subcommand) input = nil, show_popup = true, in_pty = false, - cwd = nil, env = {}, } @@ -825,9 +812,10 @@ local function new_builder(subcommand) logger.trace(string.format("[CLI]: Executing '%s': '%s'", subcommand, table.concat(cmd, " "))) + local repo = require("neogit.lib.git.repository") return process.new { cmd = cmd, - cwd = state.cwd, + cwd = repo.git_root, env = state.env, pty = state.in_pty, verbose = verbose, @@ -962,7 +950,6 @@ local function new_parallel_builder(calls) calls = calls, show_popup = true, in_pty = true, - cwd = nil, } local function call() @@ -970,15 +957,13 @@ local function new_parallel_builder(calls) return end - if not state.cwd then - state.cwd = git_root() - end - if not state.cwd or state.cwd == "" then + local repo = require("neogit.lib.git.repository") + if not repo.git_root then return end for _, c in ipairs(state.calls) do - c.cwd(state.cwd).show_popup(state.show_popup) + c.show_popup(state.show_popup) end local processes = {} @@ -993,13 +978,6 @@ local function new_parallel_builder(calls) call = call, }, { __index = function(tbl, action) - if action == "cwd" then - return function(cwd) - state.cwd = cwd - return tbl - end - end - if action == "show_popup" then return function(show_popup) state.show_popup = show_popup @@ -1031,10 +1009,8 @@ local meta = { local cli = setmetatable({ history = history, insert = handle_new_cmd, - git_root = git_root, - git_root_sync = git_root_sync, - git_dir_path_sync = git_dir_path_sync, - git_is_repository_sync = git_is_repository_sync, + git_root_of_cwd = git_root_of_cwd, + is_inside_worktree = is_inside_worktree, in_parallel = function(...) local calls = { ... } return new_parallel_builder(calls) diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index 91762f27d..f786d4ea4 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -70,10 +70,10 @@ end ---@type table local config_cache = {} local cache_key = nil -local git_root = cli.git_root() local function make_cache_key() - local stat = vim.loop.fs_stat(git_root .. "/.git/config") + local repo = require("neogit.lib.git.repository") + local stat = vim.loop.fs_stat(repo.git_root .. "/.git/config") if stat then return stat.mtime.sec end diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index c708472e2..1ff33d6d9 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -82,7 +82,7 @@ end ---@param opts table ---@return table function M.apply(patch, opts) - opts = opts or { reverse = false, cached = false, index = false, use_git_root = false } + opts = opts or { reverse = false, cached = false, index = false } local cmd = cli.apply @@ -98,12 +98,6 @@ function M.apply(patch, opts) cmd = cmd.index end - if opts.use_git_root then - local repository = require("neogit.lib.git.repository") - - cmd = cmd.cwd(repository.git_root) - end - return cmd.with_patch(patch).call() end diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index a8d1aa0fb..1d5d0232c 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -32,7 +32,7 @@ M.init_repo = function() status.cwd_changed = true vim.cmd.lcd(directory) - if cli.git_is_repository_sync() then + if cli.is_inside_worktree() then if not input.get_confirmation( string.format("Reinitialize existing repository %s?", directory), diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index b614b4f60..47cc1d834 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -31,20 +31,21 @@ function M.abort() end function M.update_merge_status(state) - if state.git_root == "" then + local repo = require("neogit.lib.git.repository") + if repo.git_root == "" then return end state.merge = { head = nil, msg = "", items = {} } - local merge_head = state.git_path("MERGE_HEAD") + local merge_head = repo:git_path("MERGE_HEAD") if not merge_head:exists() then return end state.merge.head = merge_head:read():match("([^\r\n]+)") - local message = state.git_path("MERGE_MSG") + local message = repo:git_path("MERGE_MSG") if message:exists() then state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows end diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 9899e334e..457aa814f 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -46,15 +46,16 @@ function M.skip() end function M.update_rebase_status(state) - if state.git_root == "" then + local repo = require("neogit.lib.git.repository") + if repo.git_root == "" then return end state.rebase = { items = {}, head = nil, current = nil } local rebase_file - local rebase_merge = state.git_path("rebase-merge") - local rebase_apply = state.git_path("rebase-apply") + local rebase_merge = repo:git_path("rebase-merge") + local rebase_apply = repo:git_path("rebase-apply") if rebase_merge:exists() then rebase_file = rebase_merge diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index db67066c8..d151f115b 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -1,23 +1,12 @@ local a = require("plenary.async") local logger = require("neogit.logger") +local Path = require("plenary.path") +local cli = require("neogit.lib.git.cli") --- git-status outputs files relative to the cwd. --- --- Save the working directory to allow resolution to absolute paths since the --- cwd may change after the status is refreshed and used, especially if using --- rooter plugins with lsp integration --- stylua: ignore start local function empty_state() - local root = require("neogit.lib.git.cli").git_root() - local Path = require("plenary.path") - return { - git_path = function(...) - return Path.new(root):joinpath(".git", ...) - end, - cwd = vim.fn.getcwd(), - git_root = root, - head = { + git_root = require("neogit.lib.git.cli").git_root_of_cwd(), + head = { branch = nil, commit_message = nil, tag = { @@ -25,27 +14,27 @@ local function empty_state() distance = nil, }, }, - upstream = { - branch = nil, + upstream = { + branch = nil, commit_message = nil, - remote = nil, - ref = nil, - unmerged = { items = {} }, - unpulled = { items = {} }, + remote = nil, + ref = nil, + unmerged = { items = {} }, + unpulled = { items = {} }, }, - pushRemote = { + pushRemote = { commit_message = nil, - unmerged = { items = {} }, - unpulled = { items = {} }, + unmerged = { items = {} }, + unpulled = { items = {} }, }, - untracked = { items = {} }, - unstaged = { items = {} }, - staged = { items = {} }, - stashes = { items = {} }, - recent = { items = {} }, - rebase = { items = {}, head = nil }, - sequencer = { items = {}, head = nil }, - merge = { items = {}, head = nil, msg = nil }, + untracked = { items = {} }, + unstaged = { items = {} }, + staged = { items = {} }, + stashes = { items = {} }, + recent = { items = {} }, + rebase = { items = {}, head = nil }, + sequencer = { items = {}, head = nil }, + merge = { items = {}, head = nil, msg = nil }, } end -- stylua: ignore end @@ -68,6 +57,10 @@ end function M.refresh(self, lib) local refreshes = {} + if lib then + self.state.git_root = cli.git_root_of_cwd() + end + if lib and type(lib) == "table" then if lib.status then self.lib.update_status(self.state) @@ -157,6 +150,10 @@ function M.refresh(self, lib) logger.debug("[REPO]: Refreshes completed") end +function M.git_path(self, ...) + return Path.new(self.state.git_root):joinpath(".git", ...) +end + if not M.initialized then logger.debug("[REPO]: Initializing Repository") M.initialized = true diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 620277f24..6cb1764b2 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -20,23 +20,24 @@ function M.pick_or_revert_in_progress() end function M.update_sequencer_status(state) + local repo = require("neogit.lib.git.repository") state.sequencer = { items = {}, head = nil, head_oid = nil } - local revert_head = state.git_path("REVERT_HEAD") - local cherry_head = state.git_path("CHERRY_PICK_HEAD") + local revert_head = repo:git_path("REVERT_HEAD") + local cherry_head = repo:git_path("CHERRY_PICK_HEAD") if cherry_head:exists() then state.sequencer.head = "CHERRY_PICK_HEAD" - state.sequencer.head_oid = state.git_path("CHERRY_PICK_HEAD"):read() + state.sequencer.head_oid = repo:git_path("CHERRY_PICK_HEAD"):read() state.sequencer.cherry_pick = true elseif revert_head:exists() then state.sequencer.head = "REVERT_HEAD" - state.sequencer.head_oid = state.git_path("REVERT_HEAD"):read() + state.sequencer.head_oid = repo:git_path("REVERT_HEAD"):read() state.sequencer.revert = true end - local todo = state.git_path("sequencer/todo") - local orig = state.git_path("ORIG_HEAD") + local todo = repo:git_path("sequencer/todo") + local orig = repo:git_path("ORIG_HEAD") if todo:exists() then for line in todo:iter() do if not line:match("^#") then diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 87904490c..a4a497dca 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -160,7 +160,6 @@ local function update_status(state) else head.tag = { name = nil, distance = nil } end - state.cwd = cwd state.head = head state.upstream = upstream state.untracked.items = untracked_files diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 3f71e1ea0..7b57e25e3 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -51,7 +51,7 @@ M.shared_subdirectory = operation("ignore_subdirectory", function(popup) end) M.private_local = operation("ignore_private", function(popup) - local ignore_file = git.repo.git_path("info", "exclude") + local ignore_file = git.repo:git_path("info", "exclude") local rules = make_rules(popup, git.repo.git_root) add_rules(ignore_file, rules) diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index 54c96de2e..1624d718c 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -90,7 +90,7 @@ function M.mappings_table() paths = util.filter_map(require("neogit.status").get_selection().items, function(v) return v.absolute_path end), - git_root = git.repo.state.git_root, + git_root = git.repo.git_root, } end), }, diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index f5c3bf605..05f8ef398 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -271,9 +271,6 @@ function Process:spawn(cb) -- An empty table is treated as an array self.env = self.env or {} self.env.TERM = "xterm-256color" - if self.cwd == "" then - self.cwd = nil - end local start = vim.loop.now() self.start = start diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index aadc0478b..3ed2abe6d 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -537,7 +537,7 @@ local function refresh(which, reason) a.util.scheduler() local s, f, h = save_cursor_location() - if cli.git_root() ~= "" then + if cli.git_root_of_cwd() ~= "" then git.repo:refresh(which) refresh_status_buffer() vim.api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) @@ -632,7 +632,9 @@ local function close(skip_close) M.status_buffer:close() end - M.watcher:stop() + if M.watcher then + M.watcher:stop() + end notification.delete_all() M.status_buffer = nil vim.o.autochdir = M.prev_autochdir @@ -841,7 +843,7 @@ local stage = operation("stage", function() for _, hunk in ipairs(hunks) do -- Apply works for both tracked and untracked local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to) - git.index.apply(patch, { cached = true, use_git_root = true }) + git.index.apply(patch, { cached = true }) end else git.status.stage { item.name } @@ -850,10 +852,7 @@ local stage = operation("stage", function() if #hunks > 0 then for _, hunk in ipairs(hunks) do -- Apply works for both tracked and untracked - git.index.apply( - git.index.generate_patch(item, hunk, hunk.from, hunk.to), - { cached = true, use_git_root = true } - ) + git.index.apply(git.index.generate_patch(item, hunk, hunk.from, hunk.to), { cached = true }) end else table.insert(files, item.name) @@ -901,7 +900,7 @@ local unstage = operation("unstage", function() -- Apply works for both tracked and untracked git.index.apply( git.index.generate_patch(item, hunk, hunk.from, hunk.to, true), - { cached = true, reverse = true, use_git_root = true } + { cached = true, reverse = true } ) end else @@ -967,9 +966,9 @@ local discard = operation("discard", function() if section_name == "staged" then --- Apply both to the worktree and the staging area - git.index.apply(patch, { index = true, reverse = true, use_git_root = true }) + git.index.apply(patch, { index = true, reverse = true }) else - git.index.apply(patch, { reverse = true, use_git_root = true }) + git.index.apply(patch, { reverse = true }) end end) end @@ -978,7 +977,7 @@ local discard = operation("discard", function() table.insert(t, function() if section_name == "untracked" then a.util.scheduler() - vim.fn.delete(cli.git_root() .. "/" .. item.name) + vim.fn.delete(git.repo.git_root .. "/" .. item.name) elseif section_name == "unstaged" then git.index.checkout { item.name } elseif section_name == "staged" then @@ -1238,7 +1237,6 @@ local cmd_func_map = function() end end, ["GoToFile"] = a.void(function() - -- local repo_root = cli.git_root() a.util.scheduler() local section, item = get_current_section_item() if not section then @@ -1448,7 +1446,7 @@ function M.create(kind, cwd) refresh(true, "Buffer.create") end, after = function() - M.watcher = watcher.new(git.repo.git_path():absolute()) + M.watcher = watcher.new(git.repo:git_path():absolute()) end, } end diff --git a/tests/README.md b/tests/README.md index fbdd5aec7..7c3c4be97 100644 --- a/tests/README.md +++ b/tests/README.md @@ -91,7 +91,7 @@ describe("git cli", function() it( "finds the correct git root for a non symlinked directory", in_prepared_repo(function(root_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, root_dir) end) ) diff --git a/tests/specs/neogit/lib/git/cli_spec.lua b/tests/specs/neogit/lib/git/cli_spec.lua index f446b8f64..a02164742 100644 --- a/tests/specs/neogit/lib/git/cli_spec.lua +++ b/tests/specs/neogit/lib/git/cli_spec.lua @@ -8,7 +8,7 @@ describe("git cli", function() it( "finds the correct git root for a non symlinked directory", in_prepared_repo(function(root_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, root_dir) end) ) @@ -35,7 +35,7 @@ describe("git cli", function() vim.fn.system(cmd) vim.api.nvim_set_current_dir(symlink_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, git_dir) end) ) diff --git a/tests/specs/neogit/popups/branch_spec.lua b/tests/specs/neogit/popups/branch_spec.lua index fb81daace..81592d27f 100644 --- a/tests/specs/neogit/popups/branch_spec.lua +++ b/tests/specs/neogit/popups/branch_spec.lua @@ -172,6 +172,7 @@ describe("branch popup", function() input.confirmed = true local remote = harness.prepare_repository() + async.util.block_on(status.reset) util.system("git remote add upstream " .. remote) util.system([[ git stash --include-untracked diff --git a/tests/specs/neogit/popups/remote_spec.lua b/tests/specs/neogit/popups/remote_spec.lua index 5e4a36bc9..9c36972a0 100644 --- a/tests/specs/neogit/popups/remote_spec.lua +++ b/tests/specs/neogit/popups/remote_spec.lua @@ -1,4 +1,5 @@ require("plenary.async").tests.add_to_env() +local async = require("plenary.async") local eq = assert.are.same local operations = require("neogit.operations") local harness = require("tests.util.git_harness") @@ -19,6 +20,7 @@ describe("remote popup", function() in_prepared_repo(function() local remote_a = harness.prepare_repository() local remote_b = harness.prepare_repository() + async.util.block_on(status.reset) input.values = { "foo", remote_a } act("Ma") From b4f942728b7d79d835ee734c84ca295003e7111b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 23:04:04 +0100 Subject: [PATCH 22/28] Cleanup and notes --- lua/neogit/popups/log/init.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index a5c98dd37..9e4ca548b 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -31,14 +31,16 @@ function M.create() separator = "", fn = actions.limit_to_files, setup = function(popup) - local value = require("neogit.lib.state").get { "NeogitLogPopup", "" } - value = vim.split(value, " ", { trimempty = true }) - value = require("neogit.lib.util").map(value, function(v) - local result, _ = v:gsub([["]], "") - return result - end) - - popup.state.env.files = value + local state = require("neogit.lib.state").get { "NeogitLogPopup", "" } + if state then + -- State for this option is saved with quotes around each filepath, which cannot + -- get passed into the CLI lib. So, to handle things internally, we need to strip + -- them out when loading the popup. + popup.state.env.files = vim.tbl_map(function(value) + local result, _ = value:gsub([["]], "") + return result + end, vim.split(state, " ", { trimempty = true })) + end end, }) :switch("f", "follow", "Follow renames when showing single-file log") From 8c2b1440af2f945f7ce04b82015bc6e8d34fae7b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 23:35:14 +0100 Subject: [PATCH 23/28] Standardize prompt prefixes --- lua/neogit/lib/finder.lua | 8 ++++++-- lua/neogit/lib/git/branch.lua | 2 +- lua/neogit/popups/branch/actions.lua | 6 +++--- lua/neogit/popups/branch_config/actions.lua | 2 +- lua/neogit/popups/fetch/actions.lua | 6 +++--- lua/neogit/popups/ignore/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 4 ++-- lua/neogit/popups/push/actions.lua | 10 +++++----- lua/neogit/popups/tag/actions.lua | 2 +- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index b197d2e30..d2be9d037 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -171,6 +171,10 @@ Finder.__index = Finder ---@param opts FinderOpts ---@return Finder function Finder:new(opts) + if opts.prompt_prefix then + opts.prompt_prefix = string.format(" %s > ", opts.prompt_prefix) + end + local this = { opts = vim.tbl_deep_extend("keep", opts, default_opts()), entries = {}, @@ -210,7 +214,7 @@ function Finder:find(on_select) elseif config.check_integration("fzf_lua") then local fzf_lua = require("fzf-lua") fzf_lua.fzf_exec(self.entries, { - prompt = self.opts.prompt_prefix, + prompt = string.format(" %s > ", self.opts.prompt_prefix), fzf_opts = fzf_opts(self.opts), winopts = { height = self.opts.layout_config.height, @@ -219,7 +223,7 @@ function Finder:find(on_select) }) else vim.ui.select(self.entries, { - prompt = self.opts.prompt_prefix, + prompt = string.format(" %s > ", self.opts.prompt_prefix), format_item = function(entry) return entry end, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 44889fbd9..27608f367 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -158,7 +158,7 @@ function M.set_pushRemote() elseif pushDefault:is_set() then pushRemote = pushDefault:read() else - pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote > " } + pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote" } end if pushRemote then diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 463dedd23..b8e5e20a8 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -82,7 +82,7 @@ M.checkout_local_branch = operation("checkout_local_branch", function(popup) end) local target = FuzzyFinderBuffer.new(util.merge(local_branches, remote_branches)):open_async { - prompt_prefix = " branch > ", + prompt_prefix = "branch", } if target then @@ -116,7 +116,7 @@ M.checkout_create_branch = operation("checkout_create_branch", function() end name, _ = name:gsub("%s", "-") - local base_branch = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = " base branch > " } + local base_branch = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "base branch" } if not base_branch then return end @@ -180,7 +180,7 @@ M.reset_branch = operation("reset_branch", function() local current = git.branch.current() local branches = git.branch.get_all_branches(false) local to = FuzzyFinderBuffer.new(branches):open_async { - prompt_prefix = string.format(" reset %s to > ", current), + prompt_prefix = string.format("reset %s to", current), } if not to then diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index da3fba957..2799abce1 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -46,7 +46,7 @@ function M.merge_config(branch) local branches = util.merge(local_branches, remote_branches) return a.void(function(popup, c) - local target = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "Upstream > " } + local target = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "upstream" } if not target then return end diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index bd812fac3..ffc469860 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -9,7 +9,7 @@ local util = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local function select_remote() - return FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote > " } + return FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote" } end local function fetch_from(name, remote, branch, args) @@ -86,7 +86,7 @@ function M.fetch_another_branch(popup) end) local branch = FuzzyFinderBuffer.new(branches):open_async { - prompt_prefix = remote .. "/{branch} > ", + prompt_prefix = remote .. "/{branch}", } if not branch then return @@ -107,7 +107,7 @@ function M.fetch_refspec(popup) end) notification.delete_all() - local refspec = FuzzyFinderBuffer.new(refspecs):open_async { prompt_prefix = "refspec > " } + local refspec = FuzzyFinderBuffer.new(refspecs):open_async { prompt_prefix = "refspec" } if not refspec then return end diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 7b57e25e3..cc2168198 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -22,7 +22,7 @@ end ---@param rules string[] local function add_rules(path, rules) local selected = FuzzyFinderBuffer.new(rules) - :open_async { allow_multi = true, prompt_prefix = " File or pattern to ignore > " } + :open_async { allow_multi = true, prompt_prefix = "File or pattern to ignore" } if not selected or #selected == 0 then return diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index 8f2dbff0f..1122795a9 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -49,7 +49,7 @@ function M.from_upstream(popup) if not upstream then set_upstream = true upstream = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { - prompt_prefix = "set upstream > ", + prompt_prefix = "set upstream", } if not upstream then @@ -63,7 +63,7 @@ end function M.from_elsewhere(popup) local target = FuzzyFinderBuffer.new(git.branch.get_all_branches(false)) - :open_async { prompt_prefix = "pull > " } + :open_async { prompt_prefix = "pull" } if not target then return end diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index d61b3cbc2..3fa07044d 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -57,7 +57,7 @@ function M.to_upstream(popup) set_upstream = true branch = git.branch.current() remote = git.branch.upstream_remote() - or FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote > " } + or FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote" } end if remote then @@ -69,7 +69,7 @@ end function M.to_elsewhere(popup) local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { - prompt_prefix = "push > ", + prompt_prefix = "push", } if target then @@ -87,7 +87,7 @@ function M.push_other(popup) table.insert(sources, 1, popup.state.env.commit) end - local source = FuzzyFinderBuffer.new(sources):open_async { prompt_prefix = "push > " } + local source = FuzzyFinderBuffer.new(sources):open_async { prompt_prefix = "push" } if not source then return end @@ -98,7 +98,7 @@ function M.push_other(popup) end local destination = FuzzyFinderBuffer.new(destinations) - :open_async { prompt_prefix = "push " .. source .. " to > " } + :open_async { prompt_prefix = "push " .. source .. " to" } if not destination then return end @@ -114,7 +114,7 @@ function M.push_tags(popup) if #remotes == 1 then remote = remotes[1] else - remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "push tags to > " } + remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "push tags to" } end if remote then diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index 710b12b27..183994841 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -62,7 +62,7 @@ end ---@param _ table function M.prune(_) local selected_remote = FuzzyFinderBuffer.new(git.remote.list()):open_async { - prompt_prefix = " Prune tags using remote > ", + prompt_prefix = "Prune tags using remote", } if (selected_remote or "") == "" then From 37311954752416b92c0fd3ee2a1778c35ac20cae Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:30:55 +0100 Subject: [PATCH 24/28] pass filenames through fnameescape before trying to edit them --- lua/neogit/status.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 3ed2abe6d..4fac5342a 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1029,30 +1029,25 @@ end ---@see section_has_hunks local function handle_section_item(item) local path = item.absolute_path - if not path then notification.error("Cannot open file. No path found.") return end local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) - local hunk = M.get_item_hunks(item, cursor_row, cursor_row, false)[1] notification.delete_all() M.status_buffer:close() - local relpath = vim.fn.fnamemodify(path, ":.") - if not vim.o.hidden and vim.bo.buftype == "" and not vim.bo.readonly and vim.fn.bufname() ~= "" then vim.cmd("update") end - vim.cmd("e " .. relpath) + vim.cmd(string.format("edit %s", vim.fn.fnameescape(vim.fn.fnamemodify(path, ":~:.")))) if hunk then local line_offset = cursor_row - hunk.first - local row = hunk.disk_from + line_offset - 1 for i = 1, line_offset do if string.sub(hunk.lines[i], 1, 1) == "-" then From ce3b82b1fe9998bac30cd0280e7df6ba5cfbaa5e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:47:27 +0100 Subject: [PATCH 25/28] Restructure --- lua/neogit/status.lua | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 4fac5342a..6f97a6d44 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1028,14 +1028,25 @@ end ---@param item File ---@see section_has_hunks local function handle_section_item(item) - local path = item.absolute_path - if not path then + if not item.absolute_path then notification.error("Cannot open file. No path found.") return end + local row, col local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) local hunk = M.get_item_hunks(item, cursor_row, cursor_row, false)[1] + if hunk then + local line_offset = cursor_row - hunk.first + row = hunk.disk_from + line_offset - 1 + for i = 1, line_offset do + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + -- adjust for diff sign column + col = math.floor(0, cursor_col - 1) + end notification.delete_all() M.status_buffer:close() @@ -1044,18 +1055,10 @@ local function handle_section_item(item) vim.cmd("update") end - vim.cmd(string.format("edit %s", vim.fn.fnameescape(vim.fn.fnamemodify(path, ":~:.")))) + local path = vim.fn.fnameescape(vim.fn.fnamemodify(item.absolute_path, ":~:.")) + vim.cmd(string.format("edit %s", path)) - if hunk then - local line_offset = cursor_row - hunk.first - local row = hunk.disk_from + line_offset - 1 - for i = 1, line_offset do - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - -- adjust for diff sign column - local col = cursor_col == 0 and 0 or cursor_col - 1 + if row and col then vim.api.nvim_win_set_cursor(0, { row, col }) end end From 07d36edd2175f262932a61a8355d64b56994ca97 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:50:45 +0100 Subject: [PATCH 26/28] Max, not floor --- lua/neogit/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 6f97a6d44..57a4b5572 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1045,7 +1045,7 @@ local function handle_section_item(item) end end -- adjust for diff sign column - col = math.floor(0, cursor_col - 1) + col = math.max(0, cursor_col - 1) end notification.delete_all() From 0ac1a52e88813d6ef3c2e49dfdc8226c4948a8cc Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 24 Nov 2023 01:03:26 +0100 Subject: [PATCH 27/28] Fix bug: non-zero processes cause crashes Apparently running vim.notify with error level inside on_exit causes the the vim job to error. --- lua/neogit/process.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 05f8ef398..a4d682605 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -344,7 +344,7 @@ function Process:spawn(cb) table.concat(output, "\n") ) - notification.error(message) + notification.warn(message) end -- vim.schedule(Process.show_console) end From 228774db88ab4f363b0a998ca48317abbde461c2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 13:50:56 +0100 Subject: [PATCH 28/28] Add pull popup mapping to log/reflog/commit views --- lua/neogit/buffers/commit_view/init.lua | 1 + lua/neogit/buffers/log_view/init.lua | 2 ++ lua/neogit/buffers/reflog_view/init.lua | 2 ++ 3 files changed, 5 insertions(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 945af594f..706aea21a 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -169,6 +169,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.commit_info.oid } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end, diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 8522a02a7..b6a860d46 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -66,6 +66,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["d"] = function() if not config.check_integration("diffview") then notification.error("Diffview integration must be enabled for log diff") @@ -101,6 +102,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end, diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index c52125dd3..41936b91f 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -61,6 +61,7 @@ function M:open(_) [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -87,6 +88,7 @@ function M:open(_) [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end,