From b9a6295f19ae072eba1968cf512f15722ec53097 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 21 May 2025 22:47:29 +0300 Subject: [PATCH 01/12] config param to turn off --force-if-includes --- lua/neogit/config.lua | 3 +++ lua/neogit/popups/push/actions.lua | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 724edeec0..f407a2cff 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -355,6 +355,7 @@ end ---@field disable_hint? boolean Remove the top hint in the Status buffer ---@field disable_context_highlighting? boolean Disable context highlights based on cursor position ---@field disable_signs? boolean Special signs to draw for sections etc. in Neogit +---@field force_if_includes? boolean Add --force-if-includes if --force-with-lease is set ---@field prompt_force_push? boolean Offer to force push when branches diverge ---@field prompt_amend_commit? boolean Request confirmation when amending already published commits ---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit @@ -407,6 +408,7 @@ function M.get_default_values() disable_hint = false, disable_context_highlighting = false, disable_signs = false, + force_if_includes = true, prompt_force_push = true, prompt_amend_commit = true, graph_style = "ascii", @@ -1219,6 +1221,7 @@ function M.validate_config() validate_type(config.disable_context_highlighting, "disable_context_highlighting", "boolean") validate_type(config.disable_signs, "disable_signs", "boolean") validate_type(config.git_executable, "git_executable", "string") + validate_type(config.force_if_includes, "force_if_includes", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 4fc0c628c..36286e12d 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -22,7 +22,7 @@ local function push_to(args, remote, branch, opts) table.insert(args, "--set-upstream") end - if vim.tbl_contains(args, "--force-with-lease") then + if config.values.force_if_includes and vim.tbl_contains(args, "--force-with-lease") then table.insert(args, "--force-if-includes") end From 842a6791770f6b3348bc00053a0b2550660e9161 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 11 Mar 2026 20:08:01 +0300 Subject: [PATCH 02/12] remove the second close binding for the log popup --- lua/neogit/buffers/log_view/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index d285e0378..6f10a13be 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -198,7 +198,6 @@ function M:open() vim.cmd("echo ''") end end, - [""] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["GoToFile"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() From a0504eb242e4b68f72378c65f8d7ff0397a30f36 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 5 Jun 2025 06:37:05 +0300 Subject: [PATCH 03/12] option to turn off hard reset backups --- lua/neogit/config.lua | 3 +++ lua/neogit/lib/git/reset.lua | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index f407a2cff..bdd664ff5 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -356,6 +356,7 @@ end ---@field disable_context_highlighting? boolean Disable context highlights based on cursor position ---@field disable_signs? boolean Special signs to draw for sections etc. in Neogit ---@field force_if_includes? boolean Add --force-if-includes if --force-with-lease is set +---@field hard_reset_backup? boolean Do a backup commit before a hard reset ---@field prompt_force_push? boolean Offer to force push when branches diverge ---@field prompt_amend_commit? boolean Request confirmation when amending already published commits ---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit @@ -409,6 +410,7 @@ function M.get_default_values() disable_context_highlighting = false, disable_signs = false, force_if_includes = true, + hard_reset_backup = true, prompt_force_push = true, prompt_amend_commit = true, graph_style = "ascii", @@ -1222,6 +1224,7 @@ function M.validate_config() validate_type(config.disable_signs, "disable_signs", "boolean") validate_type(config.git_executable, "git_executable", "string") validate_type(config.force_if_includes, "force_if_includes", "boolean") + validate_type(config.hard_reset_backup, "hard_reset_backup", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index c5dbd9017..244ed4019 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -1,4 +1,5 @@ local git = require("neogit.lib.git") +local config = require("neogit.config") ---@class NeogitGitReset local M = {} @@ -20,7 +21,9 @@ end ---@param target string ---@return boolean function M.hard(target) - git.index.create_backup() + if config.values.hard_reset_backup then + git.index.create_backup() + end local result = git.cli.reset.hard.args(target).call() return result:success() From 89631c1af07dfc0f5806f3daecae1f09b3786bf1 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 5 Jun 2025 06:39:01 +0300 Subject: [PATCH 04/12] prevent asking for an upstream if it's set --- lua/neogit/popups/rebase/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 4076dd0dc..c6e032e6d 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -31,7 +31,7 @@ function M.onto_pushRemote(popup) end function M.onto_upstream(popup) - local upstream = git.branch.upstream(git.branch.current()) + local upstream = git.branch.upstream() if not upstream then upstream = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() end From 35d4fbf7a8674450198555d1562122188b51af7e Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 11 Mar 2026 20:35:32 +0300 Subject: [PATCH 05/12] stacking buffers --- lua/neogit/config.lua | 36 ++++++++++++++++++------------------ lua/neogit/lib/buffer.lua | 26 +++++++++++++++++++++----- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index bdd664ff5..bb7ee4aba 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -90,6 +90,7 @@ end ---@alias WindowKind ---| "replace" Like :enew +---| "stack" Like "replace" but keeping the buffers and returning back to them on close ---| "tab" Open in a new tab ---| "split" Open in a split ---| "split_above" Like :top split @@ -782,27 +783,26 @@ function M.validate_config() -- More complex validation functions go below local function validate_kind(val, name) - if - validate_type(val, name, "string") - and not vim.tbl_contains({ - "split", - "vsplit", - "split_above", - "split_above_all", - "split_below", - "split_below_all", - "vsplit_left", - "tab", - "floating", - "floating_console", - "replace", - "auto", - }, val) - then + local valid_types = { + "split", + "vsplit", + "split_above", + "split_above_all", + "split_below", + "split_below_all", + "vsplit_left", + "tab", + "floating", + "floating_console", + "replace", + "stack", + "auto", + } + if validate_type(val, name, "string") and not vim.tbl_contains(valid_types, val) then err( name, string.format( - "Expected `%s` to be one of 'split', 'vsplit', 'split_above', 'vsplit_left', tab', 'floating', 'replace' or 'auto', got '%s'", + "Expected `%s` to be one of " .. table.concat(valid_types, ", ") .. " got '%s'", name, val ) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 6414a7057..7fbce6c57 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -214,6 +214,16 @@ function Buffer:close(force) force = false end + if self.kind == "stack" then + if self.old_buf and api.nvim_buf_is_loaded(self.old_buf) then + api.nvim_set_current_buf(self.old_buf) + self.old_buf = nil + end + + api.nvim_buf_delete(self.handle, { force = force }) + return + end + if self.kind == "replace" then if self.old_cwd then api.nvim_set_current_dir(self.old_cwd) @@ -257,7 +267,7 @@ function Buffer:hide() -- `silent!` as this might throw errors if 'hidden' is disabled. vim.cmd("silent! 1only") vim.cmd("try | tabn # | catch /.*/ | tabp | endtry") - elseif self.kind == "replace" then + elseif self.kind == "replace" or self.kind == "stack" then if self.old_cwd then api.nvim_set_current_dir(self.old_cwd) self.old_cwd = nil @@ -300,9 +310,14 @@ function Buffer:show() ---@return integer window handle local function open() local win - if self.kind == "replace" then - self.old_buf = api.nvim_get_current_buf() - self.old_cwd = vim.uv.cwd() + if self.kind == "replace" or self.kind == "stack" then + if self.kind == "stack" then + self.old_buf = self.old_buf or api.nvim_get_current_buf() + else + self.old_buf = api.nvim_get_current_buf() + self.old_cwd = vim.uv.cwd() + end + api.nvim_set_current_buf(self.handle) win = api.nvim_get_current_win() elseif self.kind == "tab" then @@ -710,10 +725,11 @@ function Buffer.create(config) logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window " .. win .. " as " .. buffer.kind) end + local default_bufhidden = config.kind == "stack" and "hide" or "wipe" logger.debug("[BUFFER:" .. buffer.handle .. "] Setting buffer options") buffer:set_buffer_option("swapfile", false) buffer:set_buffer_option("modeline", false) - buffer:set_buffer_option("bufhidden", config.bufhidden or "wipe") + buffer:set_buffer_option("bufhidden", config.bufhidden or default_bufhidden) buffer:set_buffer_option("modifiable", config.modifiable or false) buffer:set_buffer_option("modified", config.modifiable or false) buffer:set_buffer_option("readonly", config.readonly or false) From fb8c73f5a1bb86fea1809b3d922d4e26b29c785d Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 11 Mar 2026 20:37:09 +0300 Subject: [PATCH 06/12] ability to refresh log view buffers --- lua/neogit/buffers/log_view/init.lua | 19 ++++++++++++++++++ lua/neogit/lib/buffer.lua | 29 ++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 6f10a13be..4566beaa3 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -5,6 +5,8 @@ local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") local util = require("neogit.lib.util") +local git = require("neogit.lib.git") +local Watcher = require("neogit.watcher") local a = require("plenary.async") local notification = require("neogit.lib.notification") local git = require("neogit.lib.git") @@ -18,6 +20,7 @@ local git = require("neogit.lib.git") ---@field header string ---@field fetch_func fun(offset: number): CommitLogEntry[] ---@field refresh_lock Semaphore +---@field root string local M = {} M.__index = M @@ -39,6 +42,7 @@ function M.new(commits, internal_args, files, fetch_func, header, remotes) buffer = nil, refresh_lock = a.control.Semaphore.new(1), header = header, + root = git.cli.worktree_root("."), } setmetatable(instance, M) @@ -312,16 +316,31 @@ function M:open() vim.cmd("norm! k") end end, + [status_maps["RefreshBuffer"]] = function() self:redraw() end, }, }, render = function() return ui.View(self.commits, self.remotes, self.internal_args) end, after = function(buffer) + Watcher.instance(self.root):register(self) -- First line is empty, so move cursor to second line. buffer:move_cursor(2) end, + on_detach = function() + Watcher.instance(self.root):unregister(self) + end, } end +-- watcher interface +function M:id() + return "LogViewBuffer" +end + +function M:redraw() + self.commits = self.fetch_func(0) + self.buffer:redraw() +end + return M diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7fbce6c57..3aa002fb6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -821,8 +821,8 @@ function Buffer.create(config) end if config.render then - logger.debug("[BUFFER:" .. buffer.handle .. "] Rendering buffer") - buffer.ui:render(unpack(config.render(buffer))) + buffer.render_fn = config.render + buffer:draw() end for event, callback in pairs(config.autocmds or {}) do @@ -995,6 +995,31 @@ function Buffer.create(config) return buffer end +function Buffer:draw() + if self.render_fn then + logger.debug("[BUFFER:" .. self.handle .. "] Rendering buffer") + self.ui:render(unpack(self.render_fn(self))) + end +end + +function Buffer:redraw() + if not self.handle then + logger.debug("[BUFFER:" .. self.handle .. "] Buffer no longer exists - bail") + return + end + + if not self:is_focused() then + logger.debug("[BUFFER:" .. self.handle .. "] Buffer is no longer focused - bail") + return + end + + local cursor = self.ui:get_cursor_location() + local view = self:save_view() + + self:draw() + self:restore_view(view, self.ui:resolve_cursor_location(cursor)) +end + ---@param name string ---@return Buffer function Buffer.from_name(name) From 7286bb7868228f878e797ff3c835b51c1171f9ca Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 11 Mar 2026 19:51:16 +0300 Subject: [PATCH 07/12] attempt at multi-repo --- lua/neogit/buffers/log_view/init.lua | 24 ++---- lua/neogit/buffers/status/actions.lua | 111 ++++++++++++++------------ lua/neogit/buffers/status/init.lua | 38 ++++----- lua/neogit/lib/git/repository.lua | 37 ++++++--- 4 files changed, 114 insertions(+), 96 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 4566beaa3..a0c1c0b8f 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -6,7 +6,6 @@ local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") local util = require("neogit.lib.util") local git = require("neogit.lib.git") -local Watcher = require("neogit.watcher") local a = require("plenary.async") local notification = require("neogit.lib.notification") local git = require("neogit.lib.git") @@ -20,7 +19,6 @@ local git = require("neogit.lib.git") ---@field header string ---@field fetch_func fun(offset: number): CommitLogEntry[] ---@field refresh_lock Semaphore ----@field root string local M = {} M.__index = M @@ -42,7 +40,7 @@ function M.new(commits, internal_args, files, fetch_func, header, remotes) buffer = nil, refresh_lock = a.control.Semaphore.new(1), header = header, - root = git.cli.worktree_root("."), + repo = git.repo, -- TODO: pass one in args } setmetatable(instance, M) @@ -63,25 +61,16 @@ function M:close() self.buffer:close() self.buffer = nil end - - M.instance = nil -end - ----@return boolean -function M.is_open() - return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end function M:open() - if M.is_open() then - M.instance.buffer:focus() + if self.buffer and self.buffer:is_visible() then + self.buffer:focus() return end - M.instance = self - self.buffer = Buffer.create { - name = "NeogitLogView", + name = "NeogitLogView [" .. string.match(self.repo.worktree_root, "([^/\\]+)$") .. "]", filetype = "NeogitLogView", kind = config.values.log_view.kind, context_highlight = false, @@ -323,12 +312,12 @@ function M:open() return ui.View(self.commits, self.remotes, self.internal_args) end, after = function(buffer) - Watcher.instance(self.root):register(self) + self.repo:register_watch_buffer(self) -- First line is empty, so move cursor to second line. buffer:move_cursor(2) end, on_detach = function() - Watcher.instance(self.root):unregister(self) + self.repo:unregister_watch_buffer(self) end, } end @@ -339,6 +328,7 @@ function M:id() end function M:redraw() + git.repository.make_current(self.repo) self.commits = self.fetch_func(0) self.buffer:redraw() end diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 38a73adec..5182ad2cf 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -73,6 +73,17 @@ local function open(type, path, cursor) jump.open(type, path, cursor, "[Status - Open]") end +local function open_popup(status, name, f) + f = f or function(c) + c() + end + + return popups.open(name, function(c) + git.repository.make_current(status.repo) + f(c) + end) +end + local M = {} ---@param self StatusBuffer @@ -306,7 +317,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_branch_popup = function(self) - return popups.open("branch", function(p) + return open_popup(self, "branch", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } end) end @@ -314,7 +325,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_cherry_pick_popup = function(self) - return popups.open("cherry_pick", function(p) + return open_popup(self, "cherry_pick", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } end) end @@ -322,7 +333,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_commit_popup = function(self) - return popups.open("commit", function(p) + return open_popup(self, "commit", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -333,7 +344,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_merge_popup = function(self) - return popups.open("merge", function(p) + return open_popup(self, "merge", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -344,7 +355,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_push_popup = function(self) - return popups.open("push", function(p) + return open_popup(self, "push", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -355,7 +366,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_rebase_popup = function(self) - return popups.open("rebase", function(p) + return open_popup(self, "rebase", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -366,7 +377,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_revert_popup = function(self) - return popups.open("revert", function(p) + return open_popup(self, "revert", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } end) end @@ -374,7 +385,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_reset_popup = function(self) - return popups.open("reset", function(p) + return open_popup(self, "reset", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -385,7 +396,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_tag_popup = function(self) - return popups.open("tag", function(p) + return open_popup(self, "tag", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then p { commit = commits[1] } @@ -396,7 +407,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_stash_popup = function(self) - return popups.open("stash", function(p) + return open_popup(self, "stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() p { name = stash and stash:match("^stash@{%d+}") } end) @@ -405,7 +416,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_diff_popup = function(self) - return popups.open("diff", function(p) + return open_popup(self, "diff", function(p) local section = self.buffer.ui:get_selection().section local item = self.buffer.ui:get_yankable_under_cursor() p { section = { name = section and section.name }, item = { name = item } } @@ -415,7 +426,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_ignore_popup = function(self) - return popups.open("ignore", function(p) + return open_popup(self, "ignore", function(p) p { paths = self.buffer.ui:get_filepaths_in_selection(), worktree_root = git.repo.worktree_root } end) end @@ -423,39 +434,39 @@ end ---@param self StatusBuffer ---@return fun(): nil M.v_bisect_popup = function(self) - return popups.open("bisect", function(p) + return open_popup(self, "bisect", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } end) end ---@param _self StatusBuffer ---@return fun(): nil -M.v_remote_popup = function(_self) - return popups.open("remote") +M.v_remote_popup = function(self) + return open_popup(self, "remote") end ---@param _self StatusBuffer ---@return fun(): nil -M.v_fetch_popup = function(_self) - return popups.open("fetch") +M.v_fetch_popup = function(self) + return open_popup(self, "fetch") end ---@param _self StatusBuffer ---@return fun(): nil -M.v_pull_popup = function(_self) - return popups.open("pull") +M.v_pull_popup = function(self) + return open_popup(self, "pull") end ---@param _self StatusBuffer ---@return fun(): nil -M.v_help_popup = function(_self) - return popups.open("help") +M.v_help_popup = function(self) + return open_popup(self, "help") end ---@param _self StatusBuffer ---@return fun(): nil -M.v_log_popup = function(_self) - return popups.open("log") +M.v_log_popup = function(self) + return open_popup(self, "log") end ---@param self StatusBuffer @@ -468,8 +479,8 @@ end ---@param _self StatusBuffer ---@return fun(): nil -M.v_worktree_popup = function(_self) - return popups.open("worktree") +M.v_worktree_popup = function(self) + return open_popup(self, "worktree") end ---@param self StatusBuffer @@ -1135,6 +1146,8 @@ end ---@return fun(): nil M.n_stage = function(self) return a.void(function() + git.repository.instance(self.root) + local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() local section = self.buffer.ui:get_current_section() local selection = self.buffer.ui:get_selection() @@ -1361,7 +1374,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_branch_popup = function(self) - return popups.open("branch", function(p) + return open_popup(self, "branch", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end) end @@ -1369,7 +1382,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_bisect_popup = function(self) - return popups.open("bisect", function(p) + return open_popup(self, "bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end) end @@ -1377,7 +1390,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_cherry_pick_popup = function(self) - return popups.open("cherry_pick", function(p) + return open_popup(self, "cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end) end @@ -1385,7 +1398,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_commit_popup = function(self) - return popups.open("commit", function(p) + return open_popup(self, "commit", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1393,7 +1406,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_merge_popup = function(self) - return popups.open("merge", function(p) + return open_popup(self, "merge", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1401,7 +1414,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_push_popup = function(self) - return popups.open("push", function(p) + return open_popup(self, "push", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1409,7 +1422,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_rebase_popup = function(self) - return popups.open("rebase", function(p) + return open_popup(self, "rebase", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1417,7 +1430,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_revert_popup = function(self) - return popups.open("revert", function(p) + return open_popup(self, "revert", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end) end @@ -1425,7 +1438,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_reset_popup = function(self) - return popups.open("reset", function(p) + return open_popup(self, "reset", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1433,7 +1446,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_tag_popup = function(self) - return popups.open("tag", function(p) + return open_popup(self, "tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end) end @@ -1441,7 +1454,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_stash_popup = function(self) - return popups.open("stash", function(p) + return open_popup(self, "stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() p { name = stash and stash:match("^stash@{%d+}") } end) @@ -1450,7 +1463,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_diff_popup = function(self) - return popups.open("diff", function(p) + return open_popup(self, "diff", function(p) local section = self.buffer.ui:get_selection().section local item = self.buffer.ui:get_yankable_under_cursor() p { @@ -1463,7 +1476,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_ignore_popup = function(self) - return popups.open("ignore", function(p) + return open_popup(self, "ignore", function(p) local path = self.buffer.ui:get_hunk_or_filename_under_cursor() p { paths = { path and path.escaped_path }, @@ -1475,7 +1488,7 @@ end ---@param self StatusBuffer ---@return fun(): nil M.n_help_popup = function(self) - return popups.open("help", function(p) + return open_popup(self, "help", function(p) -- Since any other popup can be launched from help, build an ENV for any of them. local path = self.buffer.ui:get_hunk_or_filename_under_cursor() local section = self.buffer.ui:get_selection().section @@ -1522,26 +1535,26 @@ end ---@param _self StatusBuffer ---@return fun(): nil -M.n_remote_popup = function(_self) - return popups.open("remote") +M.n_remote_popup = function(self) + return open_popup(self, "remote") end ---@param _self StatusBuffer ---@return fun(): nil -M.n_fetch_popup = function(_self) - return popups.open("fetch") +M.n_fetch_popup = function(self) + return open_popup(self, "fetch") end ---@param _self StatusBuffer ---@return fun(): nil -M.n_pull_popup = function(_self) - return popups.open("pull") +M.n_pull_popup = function(self) + return open_popup(self, "pull") end ---@param _self StatusBuffer ---@return fun(): nil -M.n_log_popup = function(_self) - return popups.open("log") +M.n_log_popup = function(self) + return open_popup(self, "log") end ---@param self StatusBuffer @@ -1554,8 +1567,8 @@ end ---@param _self StatusBuffer ---@return fun(): nil -M.n_worktree_popup = function(_self) - return popups.open("worktree") +M.n_worktree_popup = function(self) + return open_popup(self, "worktree") end ---@param self StatusBuffer diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 6e1e598b4..9e9725bc1 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -3,7 +3,6 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") local popups = require("neogit.popups") local git = require("neogit.lib.git") -local Watcher = require("neogit.watcher") local a = require("plenary.async") local logger = require("neogit.logger") -- TODO: Add logging local event = require("neogit.lib.event") @@ -16,7 +15,6 @@ local event = require("neogit.lib.event") ---@field buffer Buffer instance ---@field config NeogitConfig ---@field root string ----@field cwd string local M = {} M.__index = M @@ -74,33 +72,33 @@ end ---@return StatusBuffer function M.instance(dir) local dir = dir or vim.uv.cwd() - assert(dir, "cannot locate a status buffer with no cwd") + assert(dir, "cannot locate a status buffer with no dir") return instances[vim.fs.normalize(dir)] end ---@param config NeogitConfig ---@param root string ----@param cwd string ---@return StatusBuffer -function M.new(config, root, cwd) - if M.instance(cwd) then - logger.debug("Found instance for cwd " .. cwd) - return M.instance(cwd) +function M.new(config, root) + if M.instance(root) then + logger.debug("Found instance for root " .. root) + return M.instance(root) end local instance = { config = config, root = root, - cwd = vim.fs.normalize(cwd), buffer = nil, fold_state = nil, cursor_state = nil, view_state = nil, + name = string.match(root, "([^/\\]+)$"), + repo = git.repository.instance(root), } setmetatable(instance, M) - M.register(instance, cwd) + M.register(instance, root) return instance end @@ -134,9 +132,8 @@ function M:open(kind) local mappings = config.get_reversed_status_maps() self.buffer = Buffer.create { - name = "NeogitStatus", + name = "NeogitStatus [" .. self.name .. "]", filetype = "NeogitStatus", - cwd = self.cwd, context_highlight = not config.values.disable_context_highlighting, kind = kind or config.values.kind or "tab", disable_line_numbers = config.values.disable_line_numbers, @@ -144,7 +141,7 @@ function M:open(kind) foldmarkers = not config.values.disable_signs, active_item_highlight = true, on_detach = function() - Watcher.instance(self.root):unregister(self) + self.repo:unregister_watch_buffer(self) if self.prev_autochdir then vim.o.autochdir = self.prev_autochdir @@ -247,12 +244,12 @@ function M:open(kind) vim.o.autochdir = false end, render = function() - return ui.Status(git.repo.state, self.config) + return ui.Status(self.repo.state, self.config) end, ---@param buffer Buffer ---@param _win any after = function(buffer, _win) - Watcher.instance(self.root):register(self) + self.repo:register_watch_buffer(self) buffer:move_cursor(buffer.ui:first_section().first) vim.b.neogit_git_dir = git.repo.git_dir end, @@ -293,8 +290,7 @@ function M:chdir(dir) vim.schedule(function() logger.debug("[STATUS] Changing Dir: " .. dir) vim.api.nvim_set_current_dir(dir) - require("neogit.lib.git.repository").instance(dir) - self.new(config.values, git.repo.worktree_root, dir):open("replace"):dispatch_refresh() + self.new(config.values, dir):open("replace"):dispatch_refresh() end) end @@ -315,7 +311,7 @@ function M:refresh(partial, reason) view = self.buffer:save_view() end - git.repo:dispatch_refresh { + self.repo:dispatch_refresh { source = "status", partial = partial, callback = function() @@ -335,7 +331,7 @@ function M:redraw(cursor, view) end logger.debug("[STATUS] Rendering UI") - self.buffer.ui:render(unpack(ui.Status(git.repo.state, self.config))) + self.buffer.ui:render(unpack(ui.Status(self.repo.state, self.config))) if self.fold_state and self.buffer then logger.debug("[STATUS] Restoring fold state") @@ -369,8 +365,8 @@ function M:deferred_refresh(reason, wait) end function M:reset() - logger.debug("[STATUS] Resetting repo and refreshing - CWD: " .. vim.uv.cwd()) - git.repo:reset() + logger.debug("[STATUS] Resetting repo and refreshing - root: " .. self.repo.worktree_root) + self.repo:reset() self:refresh(nil, "reset") end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index a8e55fcc4..6c08ae276 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -4,6 +4,7 @@ local Path = require("plenary.path") local git = require("neogit.lib.git") local ItemFilter = require("neogit.lib.item_filter") local util = require("neogit.lib.util") +local Watcher = require("neogit.watcher") local modules = { "status", @@ -185,22 +186,26 @@ Repo.__index = Repo local instances = {} local lastDir = vim.uv.cwd() +function Repo.instance_for(dir, refresh) + local cwd = vim.fs.normalize(dir) + if not instances[cwd] then + logger.debug("[REPO]: Registered Repository for: " .. cwd) + instances[cwd] = Repo.new(cwd) + if refresh then instances[cwd]:dispatch_refresh() end + end + + return instances[cwd] +end + ---@param dir? string ---@return NeogitRepo -function Repo.instance(dir) +function Repo.instance(dir, refresh) if dir and dir ~= lastDir then lastDir = dir end assert(lastDir, "No last dir") - local cwd = vim.fs.normalize(lastDir) - if not instances[cwd] then - logger.debug("[REPO]: Registered Repository for: " .. cwd) - instances[cwd] = Repo.new(cwd) - instances[cwd]:dispatch_refresh() - end - - return instances[cwd] + return Repo.instance_for(lastDir, refresh) end -- Use Repo.instance when calling directly to ensure it's registered @@ -337,6 +342,7 @@ function Repo:refresh(opts) self:run_callbacks(start) end) + lastDir = self.worktree_root a.util.run_all(self:tasks(filter, self:current_state(start)), on_complete) end @@ -344,4 +350,17 @@ Repo.dispatch_refresh = a.void(function(self, opts) self:refresh(opts) end) +function Repo.make_current(repo) + -- TODO: use repo object instead of git.repo (which is Repo.instance() for last used dir) + lastDir = repo.worktree_root +end + +function Repo:register_watch_buffer(buf) + Watcher.instance(self.worktree_root):register(buf) +end + +function Repo:unregister_watch_buffer(buf) + Watcher.instance(self.worktree_root):unregister(buf) +end + return Repo From 67b74392d054f3ea870bcadfaa0d9e97e31b09fd Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 12 Mar 2026 13:00:05 +0300 Subject: [PATCH 08/12] handle all git refreshes via repo.refresh_handlers --- lua/neogit/buffers/log_view/init.lua | 19 ++++++++++++------- lua/neogit/buffers/status/init.lua | 27 ++++++++++++++++++++------- lua/neogit/lib/git/repository.lua | 19 +++++++++++++++---- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index a0c1c0b8f..739bd1003 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -9,6 +9,7 @@ local git = require("neogit.lib.git") local a = require("plenary.async") local notification = require("neogit.lib.notification") local git = require("neogit.lib.git") +local logger = require("neogit.logger") -- TODO: Add logging ---@class LogViewBuffer ---@field commits CommitLogEntry[] @@ -312,25 +313,29 @@ function M:open() return ui.View(self.commits, self.remotes, self.internal_args) end, after = function(buffer) - self.repo:register_watch_buffer(self) + self.repo:add_refresh_handler(buffer.name, function() self:redraw() end) -- First line is empty, so move cursor to second line. buffer:move_cursor(2) end, on_detach = function() - self.repo:unregister_watch_buffer(self) + self.repo:remove_refresh_handler(self.buffer.name) end, } end --- watcher interface -function M:id() - return "LogViewBuffer" -end - function M:redraw() + local fold_state = self.buffer.ui:get_fold_state() + local cursor_state = self.buffer:cursor_line() + local view_state = self.buffer:save_view() + git.repository.make_current(self.repo) self.commits = self.fetch_func(0) self.buffer:redraw() + logger.info("[LogView] Refresh complete") + + logger.debug("[LogView] Restoring view state") + self.buffer.ui:set_fold_state(fold_state) + self.buffer:restore_view(view_state, cursor_state) end return M diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 9e9725bc1..b8dd8c3eb 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -141,7 +141,7 @@ function M:open(kind) foldmarkers = not config.values.disable_signs, active_item_highlight = true, on_detach = function() - self.repo:unregister_watch_buffer(self) + self.repo:remove_refresh_handler(self.buffer.name) if self.prev_autochdir then vim.o.autochdir = self.prev_autochdir @@ -249,7 +249,11 @@ function M:open(kind) ---@param buffer Buffer ---@param _win any after = function(buffer, _win) - self.repo:register_watch_buffer(self) + self.repo:add_refresh_handler(buffer.name, function() + self:redraw() -- cursor, view) + event.send("StatusRefreshed") + logger.info("[STATUS] Refresh complete") + end) buffer:move_cursor(buffer.ui:first_section().first) vim.b.neogit_git_dir = git.repo.git_dir end, @@ -309,16 +313,14 @@ function M:refresh(partial, reason) if self.buffer and self.buffer:is_focused() then cursor = self.buffer.ui:get_cursor_location() view = self.buffer:save_view() + + self.cursor_location = self.buffer.ui:get_cursor_location() + self.view_state = self.buffer:save_view() end self.repo:dispatch_refresh { source = "status", partial = partial, - callback = function() - self:redraw(cursor, view) - event.send("StatusRefreshed") - logger.info("[STATUS] Refresh complete") - end, } end @@ -330,6 +332,11 @@ function M:redraw(cursor, view) return end + if not self.buffer:is_focused() then + logger.debug("[STATUS] Buffer is not focused - bail") + return + end + logger.debug("[STATUS] Rendering UI") self.buffer.ui:render(unpack(ui.Status(self.repo.state, self.config))) @@ -344,6 +351,12 @@ function M:redraw(cursor, view) self.buffer:restore_view(self.view_state, self.cursor_state) self.view_state = nil self.cursor_state = nil + elseif self.cursor_location and self.view_state and self.buffer then + logger.debug("[STATUS] Restoring cursor location and view state") + local cursor_state = self.buffer.ui:resolve_cursor_location(self.cursor_location) + self.buffer:restore_view(self.view_state, cursor_state) + self.view_state = nil + self.cursor_location = nil elseif cursor and view and self.buffer then self.buffer:restore_view(view, self.buffer.ui:resolve_cursor_location(cursor)) end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 6c08ae276..696a53ff9 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -221,6 +221,7 @@ function Repo.new(dir) worktree_git_dir = git.cli.worktree_git_dir(dir), git_dir = git.cli.git_dir(dir), refresh_callbacks = {}, + refresh_handlers = {}, running = util.weak_table(), interrupt = util.weak_table(), tmp_state = util.weak_table("v"), @@ -340,6 +341,11 @@ function Repo:refresh(opts) logger.debug("[REPO]: (" .. start .. ") Refreshes complete in " .. timestamp() - start .. " ms") self:set_state(start) self:run_callbacks(start) + + for name, fn in pairs(self.refresh_handlers) do + logger.debug("[REPO]: Running handler for " .. name) + fn() + end end) lastDir = self.worktree_root @@ -355,12 +361,17 @@ function Repo.make_current(repo) lastDir = repo.worktree_root end -function Repo:register_watch_buffer(buf) - Watcher.instance(self.worktree_root):register(buf) +function Repo:add_refresh_handler(source, fn) + self.refresh_handlers[source] = fn + Watcher.instance(self.worktree_root):start() end -function Repo:unregister_watch_buffer(buf) - Watcher.instance(self.worktree_root):unregister(buf) +function Repo:remove_refresh_handler(source) + self.refresh_handlers[source] = nil + + if next(self.refresh_handlers) == nil then + Watcher.instance(self.worktree_root):stop() + end end return Repo From 0ed946f26c4b7476c61c9e5fa22ac658a3d63038 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 12 Mar 2026 13:00:27 +0300 Subject: [PATCH 09/12] support multi-windows per buffer --- lua/neogit/lib/buffer.lua | 40 ++++++++++++++++++++++++++++++-------- lua/neogit/lib/ui/init.lua | 21 +++++++++++--------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 3aa002fb6..1a3114bd6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -182,7 +182,9 @@ function Buffer:move_cursor(line) end -- pcall used in case the line is out of bounds - pcall(api.nvim_win_set_cursor, self.win_handle, position) + for i, w in ipairs(fn.win_findbuf(self.handle)) do + pcall(api.nvim_win_set_cursor, w, position) + end end ---@param line nil|number|number[] @@ -441,8 +443,9 @@ function Buffer:get_option(name) end function Buffer:get_window_option(name) - if self.win_handle ~= nil then - return api.nvim_get_option_value(name, { win = self.win_handle }) + local win = fn.win_findbuf(self.handle)[1] + if win ~= nil then + return api.nvim_get_option_value(name, { win = win }) end end @@ -458,8 +461,8 @@ function Buffer:set_buffer_option(name, value) end function Buffer:set_window_option(name, value) - if self.win_handle ~= nil then - api.nvim_set_option_value(name, value, { scope = "local", win = self.win_handle }) + for i, w in ipairs(fn.win_findbuf(self.handle)) do + api.nvim_set_option_value(name, value, { scope = "local", win = w }) end end @@ -556,14 +559,33 @@ function Buffer:call(f, ...) end function Buffer:win_call(f, ...) - if self.win_handle and api.nvim_win_is_valid(self.win_handle) then + for i, w in ipairs(fn.win_findbuf(self.handle)) do local args = { ... } - api.nvim_win_call(self.win_handle, function() + api.nvim_win_call(w, function() f(unpack(args)) end) end end +function Buffer:with_windows(before, after, render) + local windows = fn.win_findbuf(self.handle) + local data = {} + + for i, w in ipairs(windows) do + api.nvim_win_call(w, function() + data[w] = before() + end) + end + + render() + + for i, w in ipairs(windows) do + api.nvim_win_call(w, function() + after(data[w]) + end) + end +end + function Buffer:chan_send(data) assert(self.chan, "Terminal channel not open") assert(data, "data cannot be nil") @@ -589,7 +611,9 @@ function Buffer:close_terminal_channel() end function Buffer:win_exec(cmd) - fn.win_execute(self.win_handle, cmd) + for i, w in ipairs(fn.win_findbuf(self.handle)) do + fn.win_execute(w, cmd) + end end function Buffer:exists() diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 225cc5af5..613958bbe 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -687,7 +687,7 @@ function Ui:update() self.node_index = renderer:node_index() self.item_index = renderer:item_index() - self.buf:win_call(function() + self.buf:with_windows(function() -- Store the cursor and top line positions to be restored later local cursor_line = self.buf:cursor_line() local scrolloff = vim.api.nvim_get_option_value("scrolloff", { win = 0 }) @@ -703,6 +703,17 @@ function Ui:update() end end + return { cursor_line = cursor_line, top_line_nofold = top_line_nofold } + end, function(args) + -- First restore the top line, then restore the cursor after + -- Only move the viewport if there are fewer lines available on the screen than are in the buffer + if vim.fn.line("$") > vim.fn.line("w$") then + self.buf:move_top_line(math.min(args.top_line_nofold, #renderer.buffer.line)) + end + + self.buf:move_cursor(math.min(args.cursor_line, #renderer.buffer.line)) + end, + function() self.buf:unlock() self.buf:clear() self.buf:clear_namespace("default") @@ -732,14 +743,6 @@ function Ui:update() end self.buf:lock() - - -- First restore the top line, then restore the cursor after - -- Only move the viewport if there are fewer lines available on the screen than are in the buffer - if vim.fn.line("$") > vim.fn.line("w$") then - self.buf:move_top_line(math.min(top_line_nofold, #renderer.buffer.line)) - end - - self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) end) end From e598ceef0747ce0ee33cdbbb5e4e4d9d8d15cdbd Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Wed, 21 May 2025 22:46:22 +0300 Subject: [PATCH 10/12] optional simple header for log view --- lua/neogit/buffers/log_view/init.lua | 5 +++-- lua/neogit/buffers/log_view/ui.lua | 4 ++-- lua/neogit/config.lua | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 739bd1003..5116fec30 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -75,7 +75,7 @@ function M:open() filetype = "NeogitLogView", kind = config.values.log_view.kind, context_highlight = false, - header = self.header, + header = not config.values.simple_headers and self.header or nil, scroll_header = false, active_item_highlight = true, status_column = not config.values.disable_signs and "" or nil, @@ -310,7 +310,8 @@ function M:open() }, }, render = function() - return ui.View(self.commits, self.remotes, self.internal_args) + local header = config.values.simple_headers and self.header or nil + return ui.View(self.commits, self.remotes, self.internal_args, header) end, after = function(buffer) self.repo:add_refresh_handler(buffer.name, function() self:redraw() end) diff --git a/lua/neogit/buffers/log_view/ui.lua b/lua/neogit/buffers/log_view/ui.lua index e4f8e129f..e2a6f1200 100644 --- a/lua/neogit/buffers/log_view/ui.lua +++ b/lua/neogit/buffers/log_view/ui.lua @@ -14,7 +14,7 @@ local M = {} ---@param remotes string[] ---@param args table ---@return table -function M.View(commits, remotes, args) +function M.View(commits, remotes, args, header) args.details = true local graph = util.filter_map(commits, function(commit) @@ -25,7 +25,7 @@ function M.View(commits, remotes, args) end end) - table.insert(graph, 1, col { row { text("") } }) + table.insert(graph, 1, col { row { text(header or "") } }) table.insert( graph, diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index bb7ee4aba..35d781081 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -356,6 +356,7 @@ end ---@field disable_hint? boolean Remove the top hint in the Status buffer ---@field disable_context_highlighting? boolean Disable context highlights based on cursor position ---@field disable_signs? boolean Special signs to draw for sections etc. in Neogit +---@field simple_headers? boolean Use text headers instead of windows ---@field force_if_includes? boolean Add --force-if-includes if --force-with-lease is set ---@field hard_reset_backup? boolean Do a backup commit before a hard reset ---@field prompt_force_push? boolean Offer to force push when branches diverge @@ -410,6 +411,7 @@ function M.get_default_values() disable_hint = false, disable_context_highlighting = false, disable_signs = false, + simple_headers = false, force_if_includes = true, hard_reset_backup = true, prompt_force_push = true, @@ -1223,6 +1225,7 @@ function M.validate_config() validate_type(config.disable_context_highlighting, "disable_context_highlighting", "boolean") validate_type(config.disable_signs, "disable_signs", "boolean") validate_type(config.git_executable, "git_executable", "string") + validate_type(config.simple_headers, "simple_headers", "boolean") validate_type(config.force_if_includes, "force_if_includes", "boolean") validate_type(config.hard_reset_backup, "hard_reset_backup", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") From 55093e0a0cd7da8e433a1870cad39b68a01c4a0d Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 12 Mar 2026 13:12:33 +0300 Subject: [PATCH 11/12] option to turn off ancestor check during rebases --- lua/neogit/config.lua | 3 +++ lua/neogit/popups/rebase/actions.lua | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 35d781081..57141619f 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -359,6 +359,7 @@ end ---@field simple_headers? boolean Use text headers instead of windows ---@field force_if_includes? boolean Add --force-if-includes if --force-with-lease is set ---@field hard_reset_backup? boolean Do a backup commit before a hard reset +---@field rebase_check_ancestor? boolean Check that rebase target is ancestor of HEAD ---@field prompt_force_push? boolean Offer to force push when branches diverge ---@field prompt_amend_commit? boolean Request confirmation when amending already published commits ---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit @@ -414,6 +415,7 @@ function M.get_default_values() simple_headers = false, force_if_includes = true, hard_reset_backup = true, + rebase_check_ancestor = true, prompt_force_push = true, prompt_amend_commit = true, graph_style = "ascii", @@ -1228,6 +1230,7 @@ function M.validate_config() validate_type(config.simple_headers, "simple_headers", "boolean") validate_type(config.force_if_includes, "force_if_includes", "boolean") validate_type(config.hard_reset_backup, "hard_reset_backup", "boolean") + validate_type(config.rebase_check_ancestor, "rebase_check_ancestor", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index c6e032e6d..87579106e 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") +local config = require("neogit.config") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -55,7 +56,7 @@ function M.interactively(popup) "Select a commit with to rebase it and all commits above it, or to abort" ) if commit then - if not git.log.is_ancestor(commit, "HEAD") then + if config.values.rebase_check_ancestor and not git.log.is_ancestor(commit, "HEAD") then notification.warn("Commit isn't an ancestor of HEAD") return end From ab9576346c1dee8d1d76cbfbec7c0aa94e9031e1 Mon Sep 17 00:00:00 2001 From: Anton Rogov Date: Thu, 12 Mar 2026 13:12:53 +0300 Subject: [PATCH 12/12] option to turn off shifting to parent commit during rebases --- lua/neogit/config.lua | 3 +++ lua/neogit/popups/rebase/actions.lua | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 57141619f..407720a6f 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -360,6 +360,7 @@ end ---@field force_if_includes? boolean Add --force-if-includes if --force-with-lease is set ---@field hard_reset_backup? boolean Do a backup commit before a hard reset ---@field rebase_check_ancestor? boolean Check that rebase target is ancestor of HEAD +---@field rebase_use_parent_commit? boolean Use parent commit of the rebase taret ---@field prompt_force_push? boolean Offer to force push when branches diverge ---@field prompt_amend_commit? boolean Request confirmation when amending already published commits ---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit @@ -416,6 +417,7 @@ function M.get_default_values() force_if_includes = true, hard_reset_backup = true, rebase_check_ancestor = true, + rebase_use_parent_commit = true, prompt_force_push = true, prompt_amend_commit = true, graph_style = "ascii", @@ -1231,6 +1233,7 @@ function M.validate_config() validate_type(config.force_if_includes, "force_if_includes", "boolean") validate_type(config.hard_reset_backup, "hard_reset_backup", "boolean") validate_type(config.rebase_check_ancestor, "rebase_check_ancestor", "boolean") + validate_type(config.rebase_use_parent_commit, "rebase_use_parent_commit", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 87579106e..6783c1df9 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -82,11 +82,13 @@ function M.interactively(popup) end end - local parent = git.log.parent(commit) - if parent then - commit = commit .. "^" - else - table.insert(args, "--root") + if config.values.rebase_use_parent_commit then + local parent = git.log.parent(commit) + if parent then + commit = commit .. "^" + else + table.insert(args, "--root") + end end git.rebase.rebase_interactive(commit, args)