From 5e928fe79f5fbceba0c5b30efe6fe0984181ae4b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 18 Oct 2024 23:21:28 +0200 Subject: [PATCH 001/437] Respect "push.autoSetupRemote" - when pushing a branch to pushremote, if the value is true, add "--set-upstream" flag to CLI call. Closes https://github.com/NeogitOrg/neogit/issues/1508 --- lua/neogit/lib/git/push.lua | 18 ++++++++++++++++++ lua/neogit/popups/push/actions.lua | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index db142273b..adf9eb9b8 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -13,6 +13,24 @@ function M.push_interactive(remote, branch, args) return git.cli.push.args(remote or "", branch or "").arg_list(args).call { pty = true } end +---@param branch string +---@return boolean +function M.auto_setup_remote(branch) + local push_autoSetupRemote = git.config.get("push.autoSetupRemote"):read() + or git.config.get_global("push.autoSetupRemote"):read() + + local push_default = git.config.get("push.default"):read() + or git.config.get_global("push.default"):read() + + local branch_remote = git.config.get("branch." .. branch .. ".remote"):read() + + return ( + push_autoSetupRemote + and (push_default == "current" or push_default == "simple" or push_default == "upstream") + and not branch_remote + ) == true +end + local function update_unmerged(state) local status = git.branch.status() diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 03e782a12..8bd30f6e8 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -11,7 +11,7 @@ local M = {} local function push_to(args, remote, branch, opts) opts = opts or {} - if opts.set_upstream then + if opts.set_upstream or git.push.auto_setup_remote(branch) then table.insert(args, "--set-upstream") end From 6171dfa130ce0abf0839d1bf23e6d72dab52a891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Sat, 15 Jun 2024 13:55:11 +0200 Subject: [PATCH 002/437] Add config feature 'initial_branch_name' This config feature allows users to specify an initial branch name that will be used as a `get_user_input` default when Neogit asks for a name for a new branch. This is useful when working in teams with a branch naming convention. For example, teams may choose to prefix branches with a GitHub username. In that case, configuring Neogit with: ``` initial_branch_name = "myname/" ``` ...will result in the popup being prepopulated with `myname/`, after which I can specify the branch name. --- README.md | 2 ++ doc/neogit.txt | 1 + lua/neogit/config.lua | 3 +++ lua/neogit/popups/branch/actions.lua | 10 +++++++--- tests/specs/neogit/config_spec.lua | 5 +++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6bf878492..210c5b57c 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ neogit.setup { -- Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt -- Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options sort_branches = "-committerdate", + -- Default for new branch name prompts + initial_branch_name = "", -- Change the default way of opening neogit kind = "tab", -- Disable line numbers and relative line numbers diff --git a/doc/neogit.txt b/doc/neogit.txt index 8cc136c1b..9bb288c56 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -113,6 +113,7 @@ TODO: Detail what these do fetch_after_checkout = false, auto_refresh = true, sort_branches = "-committerdate", + initial_branch_name = "", kind = "tab", disable_line_numbers = true, -- The time after which an output console is shown for slow running commands diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 01f8d8476..8984da404 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -303,6 +303,7 @@ end ---@field use_per_project_settings? boolean Scope persisted settings on a per-project basis ---@field remember_settings? boolean Whether neogit should persist flags from popups, e.g. git push flags ---@field sort_branches? string Value used for `--sort` for the `git branch` command +---@field initial_branch_name? string Default for new branch name prompts ---@field kind? WindowKind The default type of window neogit should open in ---@field disable_line_numbers? boolean Whether to disable line numbers ---@field disable_relative_line_numbers? boolean Whether to disable line numbers @@ -359,6 +360,7 @@ function M.get_default_values() fetch_after_checkout = false, sort_branches = "-committerdate", kind = "tab", + initial_branch_name = "", disable_line_numbers = true, disable_relative_line_numbers = true, -- The time after which an output console is shown for slow running commands @@ -1087,6 +1089,7 @@ function M.validate_config() validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") validate_type(config.sort_branches, "sort_branches", "string") + validate_type(config.initial_branch_name, "initial_branch_name", "string") validate_type(config.notification_icon, "notification_icon", "string") validate_type(config.console_timeout, "console_timeout", "number") validate_kind(config.kind, "kind") diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 190c5230f..5a1be245b 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -43,14 +43,18 @@ local function checkout_branch(target, args) end end +local function get_branch_name_user_input(prompt, default) + default = default or config.values.initial_branch_name + return input.get_user_input(prompt, { strip_spaces = true, default = default }) +end + local function spin_off_branch(checkout) if git.status.is_dirty() and not checkout then notification.info("Staying on HEAD due to uncommitted changes") checkout = true end - local name = - input.get_user_input(("%s branch"):format(checkout and "Spin-off" or "Spin-out"), { strip_spaces = true }) + local name = get_branch_name_user_input(("%s branch"):format(checkout and "Spin-off" or "Spin-out")) if not name then return end @@ -215,7 +219,7 @@ function M.rename_branch() return end - local new_name = input.get_user_input(("Rename '%s' to"):format(selected_branch), { strip_spaces = true }) + local new_name = get_branch_name_user_input(("Rename '%s' to"):format(selected_branch)) if not new_name then return end diff --git a/tests/specs/neogit/config_spec.lua b/tests/specs/neogit/config_spec.lua index 3a51eb951..942401e06 100644 --- a/tests/specs/neogit/config_spec.lua +++ b/tests/specs/neogit/config_spec.lua @@ -56,6 +56,11 @@ describe("Neogit config", function() assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) end) + it("should return invalid when initial_branch_name isn't a string", function() + config.values.initial_branch_name = false + assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) + end) + it("should return invalid when kind isn't a string", function() config.values.kind = true assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) From 217a5b1124f3b15ad4bb4bb2f6f02f705212527c Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 18 Oct 2024 23:33:49 +0200 Subject: [PATCH 003/437] Set foldcolumn=0 for _all_ neogit buffers. --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e902869c9..7ca300735 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -695,6 +695,7 @@ function Buffer.create(config) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) buffer:set_window_option("foldtext", "") + buffer:set_window_option("foldcolumn", "0") buffer:set_window_option("listchars", "") buffer:set_window_option("list", false) buffer:call(function() @@ -801,7 +802,6 @@ function Buffer.create(config) end if config.foldmarkers then - vim.opt_local.foldcolumn = "0" vim.opt_local.signcolumn = "auto" logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up foldmarkers") From aa3a343c58c378e91d2457f19952f9f2ee3aacc3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 18 Oct 2024 23:34:52 +0200 Subject: [PATCH 004/437] lint --- lua/neogit/buffers/status/actions.lua | 17 +++++++---------- lua/neogit/lib/git/push.lua | 3 +-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 6dff99b97..2820a93c2 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1360,17 +1360,14 @@ M.n_command = function(self) proc:show_console() - runner.call( - proc, - { - pty = true, - callback = function() - if self then - self:dispatch_refresh() - end + runner.call(proc, { + pty = true, + callback = function() + if self then + self:dispatch_refresh() end - } - ) + end, + }) end) end diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index adf9eb9b8..5728fadbe 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -19,8 +19,7 @@ function M.auto_setup_remote(branch) local push_autoSetupRemote = git.config.get("push.autoSetupRemote"):read() or git.config.get_global("push.autoSetupRemote"):read() - local push_default = git.config.get("push.default"):read() - or git.config.get_global("push.default"):read() + local push_default = git.config.get("push.default"):read() or git.config.get_global("push.default"):read() local branch_remote = git.config.get("branch." .. branch .. ".remote"):read() From fd7fa2027334683227731ff1569b3dab79ae3ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Sat, 19 Oct 2024 11:33:10 +0200 Subject: [PATCH 005/437] Add helptext documentation for the Stash popup --- doc/neogit.txt | 63 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8cc136c1b..ebcee2be6 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1552,7 +1552,68 @@ Actions: *neogit_reset_popup_actions* ============================================================================== Stash Popup *neogit_stash_popup* -(TODO) +The stash popup actions will affect the current index (staged changes) and the +working tree (unstaged changes and untracked files). When the cursor is on a +stash in the stash list, actions under the "Use" column (pop, apply and drop) +will affect the stash under the cursor. + +Actions: *neogit_stash_popup_actions* + • Stash both *neogit_stash_both* + Stash both the index and the working tree. + + • Stash index *neogit_stash_index* + Stash the index only, excluding unstaged changes and untracked files. + + • Stash worktree *neogit_stash_worktree* + (Not yet implemented) + + • Stash keeping index *neogit_stash_keeping_index* + Stash both the index and the working tree, but still leave behind the + index. + + • Stash push *neogit_stash_push* + Select a changed file to push it to the stash. + + • Snapshot both *neogit_snapshot_both* + (Not yet implemented) + + • Snapshot index *neogit_snapshot_index* + (Not yet implemented) + + • Snapshot worktree *neogit_snapshot_worktree* + (Not yet implemented) + + • Snapshot to wip ref *neogit_snapshot_to_wip_ref* + (Not yet implemented) + + • Pop *neogit_stash_pop* + Apply a stash to the working tree. If there are conflicts, leave the stash + intact. If there aren't any conflicts, remove the stash from the list. + + • Apply *neogit_stash_apply* + Apply a stash to the working tree without removing it from the stash list. + + • Drop *neogit_stash_drop* + Remove a stash from the stash list. + + • List *neogit_stash_list* + Display the list of all stashes, and show a diff if a stash is selected. + + • Show *neogit_stash_show* + (Not yet implemented) + + • Branch *neogit_stash_branch* + (Not yet implemented) + + • Branch here *neogit_stash_branch_here* + (Not yet implemented) + + • Rename *neogit_stash_rename* + Rename an existing stash. + + • Format patch *neogit_stash_format_patch* + (Not yet implemented) + ============================================================================== Ignore Popup *neogit_ignore_popup* From bedb2a52d957c1c66f33111e6607272e4c62f547 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 13:43:55 +0200 Subject: [PATCH 006/437] Add lefthook --- Gemfile | 2 ++ Gemfile.lock | 2 ++ lefthook.yml | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 lefthook.yml diff --git a/Gemfile b/Gemfile index f88bcb270..e906795c1 100644 --- a/Gemfile +++ b/Gemfile @@ -18,3 +18,5 @@ gem "rubocop-performance" gem "rubocop-rspec" gem "super_diff" gem "tmpdir" + +gem "lefthook", "~> 1.7" diff --git a/Gemfile.lock b/Gemfile.lock index 905e68bdf..290088715 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -43,6 +43,7 @@ GEM reline (>= 0.4.2) json (2.7.2) language_server-protocol (3.17.0.3) + lefthook (1.7.22) logger (1.6.0) minitest (5.25.1) msgpack (1.7.2) @@ -134,6 +135,7 @@ DEPENDENCIES debug fuubar git + lefthook (~> 1.7) neovim pastel quickfix_formatter diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 000000000..780689519 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,19 @@ +skip_output: + - meta +pre-push: + parallel: true + commands: + # lua-test: + # run: TEMP_DIR=$TEMP_DIR TEST_FILES=$TEST_FILES GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null NVIM_APPNAME=neogit-test nvim --headless -S "./tests/init.lua" + # e2e-test: + # run: CI=1 bundle exec rspec + ruby-lint: + files: "rg --files" + glob: "*.rb" + run: bundle exec rubocop {files} + lua-lint: + run: selene --config selene/config.toml lua + lua-format: + run: stylua --check ./lua/**/*.lua + spelling-lint: + run: typos From 7627fe1998ae549894f8a44c39dbd042dc6e7a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Sat, 19 Oct 2024 12:04:37 +0200 Subject: [PATCH 007/437] Add helptext documentation for the Diff popup --- doc/neogit.txt | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8cc136c1b..4dc8cb17b 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1051,7 +1051,44 @@ Actions: *neogit_commit_popup_actions* ============================================================================== Diff Popup *neogit_diff_popup* -(TODO) +The diff popup actions allow inspection of changes in the index (staged +changes), the working tree (unstaged changes and untracked files), any +ref range. + +For these actions to become available, Neogit needs to be configured to enable +`diffview.nvim` integration. See |neogit_setup_plugin| and +https://github.com/sindrets/diffview.nvim. + + • Diff this *neogit_diff_this* + Show the diff for the file referred to by a hunk under the cursor. + + • Diff range *neogit_diff_range* + View a diff between a specified range of commits. Neogit presents a select + for an `A` and a `B` ref and allows specifying the type of range. + + A two-dot range `A..B` shows all of the commits that `B` has that `A` + doesn't have. A three-dot range `A...B` shows all of the commits that `A` + and `B` have independently, excluding the commits shared by both refs. + + + • Diff paths *neogit_diff_paths* + (Not yet implemented) + + • Diff unstaged *neogit_diff_unstaged* + Show the diff for the working tree, without untracked files. + + • Diff staged *neogit_diff_staged* + Show the diff for the index. + + • Diff worktree *neogit_diff_worktree* + Show the full diff of the working tree, including untracked files. + + • Show commit *neogit_show_commit* + Display the diff of the commits within a branch. + + • Show stash *neogit_show_stash* + Display the diff of a specific stash. + ============================================================================== Fetch Popup *neogit_fetch_popup* From 5163ee421bcf5a88f81ee4242d747df92dc971d3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:36:11 +0200 Subject: [PATCH 008/437] Add semaphore to concurrent ruby test runner to limit number of rspec processes running at a time. Prevents tests randomly failing because we're saturating the cpu --- bin/specs | 112 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 33 deletions(-) diff --git a/bin/specs b/bin/specs index 4539bfb51..b7c1c8d6b 100755 --- a/bin/specs +++ b/bin/specs @@ -11,11 +11,72 @@ gemfile do gem "debug" end +require "async" +require "async/barrier" +require "async/semaphore" + COLOR = Pastel.new def now = Process.clock_gettime(Process::CLOCK_MONOTONIC) -tests = Dir["spec/**/*_spec.rb"] -length = tests.max_by(&:size).size +class Runner # rubocop:disable Style/Documentation + def initialize(test, spinner, length) + @test = test + @spinner = spinner + @length = length + @title = test.gsub("spec/", "") + end + + def register + spinner.update(test: title, padding: " " * (length - test.length)) + spinner.auto_spin + self + end + + def call(results, failures) + start! + output, wait = run + results[test] = JSON.parse(output) + + time = results[test].dig("summary", "duration").round(3) + + if wait.value.success? + register_success!(time) + else + failures << test + register_failure!(time) + end + end + + private + + attr_reader :title, :spinner, :test, :length + + def start! + spinner.update(test: COLOR.blue(title)) + end + + def run + stdin, stdout, wait = Open3.popen2({ "CI" => "1" }, "bundle exec rspec #{test} --format json --order random") + stdin.close + output = stdout.read.lines.last + stdout.close + + [output, wait] + end + + def register_success!(time) + spinner.update(test: COLOR.green(title)) + spinner.success(COLOR.green(time)) + end + + def register_failure!(time) + spinner.update(test: COLOR.red(title)) + spinner.error(COLOR.red(time)) + end +end + +tests = Dir["spec/**/*_spec.rb"] +length = tests.max_by(&:size).size spinners = TTY::Spinner::Multi.new( COLOR.blue(":spinner Running #{tests.size} specs"), format: :bouncing_ball, @@ -27,37 +88,22 @@ failures = [] start = now -Sync do |parent| # rubocop:disable Metrics/BlockLength - tests.map do |test| - parent.async do - spinner = spinners.register( - ":test:padding\t", - success_mark: COLOR.green.bold("+"), - error_mark: COLOR.red.bold("x") - ) - - title = test.gsub("spec/", "") - spinner.update(test: title, padding: " " * (length - test.length)) - spinner.auto_spin - - stdin, stdout, wait = Open3.popen2({ "CI" => "1" }, "bundle exec rspec #{test} --format json --order random") - stdin.close - - output = stdout.read.lines.last - results[test] = JSON.parse(output) - stdout.close - - time = results[test].dig("summary", "duration").round(3) - - if wait.value.success? - spinner.update(test: COLOR.green(title)) - spinner.success(COLOR.green(time)) - else - failures << test - spinner.update(test: COLOR.red(title)) - spinner.error(COLOR.red(time)) - end - end +barrier = Async::Barrier.new +Sync do + semaphore = Async::Semaphore.new(Etc.nprocessors - 2, parent: barrier) + + runners = tests.map do |test| + spinner = spinners.register( + ":test:padding\t", + success_mark: COLOR.green.bold("+"), + error_mark: COLOR.red.bold("x") + ) + + Runner.new(test, spinner, length).register + end + + runners.map do |runner| + semaphore.async { runner.call(results, failures) } end.map(&:wait) end From f30ca8097d8341e8c00083b959221fc0c92503fb Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:37:01 +0200 Subject: [PATCH 009/437] Fix: when env TEST_FILES is _unset_, provide default value. --- tests/init.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/init.lua b/tests/init.lua index e8dd5130a..700ad858a 100644 --- a/tests/init.lua +++ b/tests/init.lua @@ -11,10 +11,9 @@ else util.ensure_installed("nvim-lua/plenary.nvim", util.neogit_test_base_dir) end -require("plenary.test_harness").test_directory( - os.getenv("TEST_FILES") == "" and "tests/specs" or os.getenv("TEST_FILES"), - { - minimal_init = "tests/minimal_init.lua", - sequential = true, - } -) +local directory = os.getenv("TEST_FILES") == "" and "tests/specs" or os.getenv("TEST_FILES") or "tests/specs" + +require("plenary.test_harness").test_directory(directory, { + minimal_init = "tests/minimal_init.lua", + sequential = true, +}) From 8869e4e02ec6a306e69b6e687f75894bc65e5183 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:37:36 +0200 Subject: [PATCH 010/437] Lint: Rubocop --- spec/popups/branch_config_popup_spec.rb | 34 ++++++++++++------------- spec/popups/commit_popup_spec.rb | 20 +++++++-------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/spec/popups/branch_config_popup_spec.rb b/spec/popups/branch_config_popup_spec.rb index c6e28b812..57dee8d30 100644 --- a/spec/popups/branch_config_popup_spec.rb +++ b/spec/popups/branch_config_popup_spec.rb @@ -46,11 +46,11 @@ end end - describe "rebase" do - end + # describe "rebase" do + # end - describe "pullRemote" do - end + # describe "pullRemote" do + # end end describe "Actions" do @@ -67,21 +67,21 @@ end end - describe "remote.pushDefault" do - end + # describe "remote.pushDefault" do + # end - describe "neogit.baseBranch" do - end + # describe "neogit.baseBranch" do + # end - describe "neogit.askSetPushDefault" do - end + # describe "neogit.askSetPushDefault" do + # end end - describe "Branch creation" do - describe "autoSetupMerge" do - end - - describe "autoSetupRebase" do - end - end + # describe "Branch creation" do + # describe "autoSetupMerge" do + # end + # + # describe "autoSetupRebase" do + # end + # end end diff --git a/spec/popups/commit_popup_spec.rb b/spec/popups/commit_popup_spec.rb index 313a2fbf9..9bc3b1749 100644 --- a/spec/popups/commit_popup_spec.rb +++ b/spec/popups/commit_popup_spec.rb @@ -149,19 +149,19 @@ end end - describe "Fixup" do - end + # describe "Fixup" do + # end - describe "Squash" do - end + # describe "Squash" do + # end - describe "Augment" do - end + # describe "Augment" do + # end - describe "Instant Fixup" do - end + # describe "Instant Fixup" do + # end - describe "Instant Squash" do - end + # describe "Instant Squash" do + # end end end From 62c2ed186fa735e2685ae3f0b09318c33b3a6347 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:38:00 +0200 Subject: [PATCH 011/437] Lint: stylua/selene --- lua/neogit/client.lua | 4 +-- lua/neogit/lib/git/cli.lua | 1 - plugin/neogit.lua | 52 ++++++++++++++++---------------------- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 735d8d36b..ee94e956a 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -102,9 +102,7 @@ function M.editor(target, client, show_diff) kind = config.values.commit_editor.kind elseif target:find("MERGE_MSG$") then kind = config.values.merge_editor.kind - elseif target:find("TAG_EDITMSG$") then - kind = "popup" - elseif target:find("EDIT_DESCRIPTION$") then + elseif target:find("TAG_EDITMSG$") or target:find("EDIT_DESCRIPTION$") then kind = "popup" elseif target:find("git%-rebase%-todo$") then kind = config.values.rebase_editor.kind diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 6d34b502c..64e5dc8bd 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1,4 +1,3 @@ -local logger = require("neogit.logger") local git = require("neogit.lib.git") local process = require("neogit.process") local util = require("neogit.lib.util") diff --git a/plugin/neogit.lua b/plugin/neogit.lua index 4f0e4b4f7..465eb55b8 100644 --- a/plugin/neogit.lua +++ b/plugin/neogit.lua @@ -16,35 +16,27 @@ api.nvim_create_user_command("NeogitResetState", function() require("neogit.lib.state")._reset() end, { nargs = "*", desc = "Reset any saved flags" }) -api.nvim_create_user_command( - "NeogitLogCurrent", - function(args) - local action = require("neogit").action - local path = vim.fn.expand(args.fargs[1] or "%") +api.nvim_create_user_command("NeogitLogCurrent", function(args) + local action = require("neogit").action + local path = vim.fn.expand(args.fargs[1] or "%") - if args.range > 0 then - action("log", "log_current", { "-L" .. args.line1 .. "," .. args.line2 .. ":" .. path })() - else - action("log", "log_current", { "--", path })() - end - end, - { - nargs = "?", - desc = "Open git log (current) for specified file, or current file if unspecified. Optionally accepts a range.", - range = "%", - complete = "file" - } -) + if args.range > 0 then + action("log", "log_current", { "-L" .. args.line1 .. "," .. args.line2 .. ":" .. path })() + else + action("log", "log_current", { "--", path })() + end +end, { + nargs = "?", + desc = "Open git log (current) for specified file, or current file if unspecified. Optionally accepts a range.", + range = "%", + complete = "file", +}) -api.nvim_create_user_command( - "NeogitCommit", - function(args) - local commit = args.fargs[1] or "HEAD" - local CommitViewBuffer = require("neogit.buffers.commit_view") - CommitViewBuffer.new(commit):open() - end, - { - nargs = "?", - desc = "Open git commit view for specified commit, or HEAD", - } -) +api.nvim_create_user_command("NeogitCommit", function(args) + local commit = args.fargs[1] or "HEAD" + local CommitViewBuffer = require("neogit.buffers.commit_view") + CommitViewBuffer.new(commit):open() +end, { + nargs = "?", + desc = "Open git commit view for specified commit, or HEAD", +}) From 3f250e0751ec349b898a5309494dab68d94779b9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:38:13 +0200 Subject: [PATCH 012/437] Fix: always return false here if there is no branch --- lua/neogit/lib/git/push.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index 5728fadbe..c12359ebf 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -13,9 +13,13 @@ function M.push_interactive(remote, branch, args) return git.cli.push.args(remote or "", branch or "").arg_list(args).call { pty = true } end ----@param branch string +---@param branch string|nil ---@return boolean function M.auto_setup_remote(branch) + if not branch then + return false + end + local push_autoSetupRemote = git.config.get("push.autoSetupRemote"):read() or git.config.get_global("push.autoSetupRemote"):read() From aaf50b348b87fc24adf583e34f6eee5868473619 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:38:32 +0200 Subject: [PATCH 013/437] Update: Lefthook --- lefthook.yml | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 780689519..5ef06c0f0 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,19 +1,29 @@ skip_output: - meta pre-push: - parallel: true + only: + - ref: master + files: "rg --files" + follow: true commands: - # lua-test: - # run: TEMP_DIR=$TEMP_DIR TEST_FILES=$TEST_FILES GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null NVIM_APPNAME=neogit-test nvim --headless -S "./tests/init.lua" - # e2e-test: - # run: CI=1 bundle exec rspec - ruby-lint: - files: "rg --files" + 1-rubocop: glob: "*.rb" run: bundle exec rubocop {files} - lua-lint: - run: selene --config selene/config.toml lua - lua-format: - run: stylua --check ./lua/**/*.lua - spelling-lint: - run: typos + 2-selene: + glob: "{lua,plugin}/**/*.lua" + run: selene --config selene/config.toml {files} + 3-stylua: + glob: "*.lua" + run: stylua --check {files} + 4-typos: + run: typos {files} + 5-lua-test: + glob: "tests/specs/**/*_spec.lua" + run: nvim --headless -S "./tests/init.lua" || echo {files} + env: + - CI: 1 + - GIT_CONFIG_GLOBAL: /dev/null + - GIT_CONFIG_SYSTEM: /dev/null + - NVIM_APPNAME: neogit-test + 6-rspec: + run: bin/specs {files} From fab2507c6a43390bf8813d973dca515b1a69d1be Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:41:53 +0200 Subject: [PATCH 014/437] Update: run lefthook in parallel --- lefthook.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 5ef06c0f0..939d00410 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -4,20 +4,20 @@ pre-push: only: - ref: master files: "rg --files" - follow: true + parallel: true commands: - 1-rubocop: + rubocop: glob: "*.rb" run: bundle exec rubocop {files} - 2-selene: + selene: glob: "{lua,plugin}/**/*.lua" run: selene --config selene/config.toml {files} - 3-stylua: + stylua: glob: "*.lua" run: stylua --check {files} - 4-typos: + typos: run: typos {files} - 5-lua-test: + lua-test: glob: "tests/specs/**/*_spec.lua" run: nvim --headless -S "./tests/init.lua" || echo {files} env: @@ -25,5 +25,5 @@ pre-push: - GIT_CONFIG_GLOBAL: /dev/null - GIT_CONFIG_SYSTEM: /dev/null - NVIM_APPNAME: neogit-test - 6-rspec: + rspec: run: bin/specs {files} From c41a654d6148e1858d98e37cea371993eac0b126 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 19 Oct 2024 22:43:29 +0200 Subject: [PATCH 015/437] Increase process buffer limit from 300 to 1000 lines before truncating --- lua/neogit/buffers/process/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 7b59f8483..003ef12c3 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -72,7 +72,7 @@ end function M:append(data) self.lines = self.lines + 1 - if self.lines > 300 then + if self.lines > 1000 then if not self.truncated then self.content = table.concat({ self.content, "\r\n[Output too long - Truncated]" }, "\r\n") self.truncated = true From 12fdc8cc1c9116bf43a1fcc1f98cac115544ab88 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 20 Oct 2024 16:19:49 +0200 Subject: [PATCH 016/437] Ensure ufo.detach is a function before calling it --- lua/neogit/lib/buffer.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7ca300735..48079555b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -365,9 +365,10 @@ function Buffer:show() end) -- Workaround UFO getting folds wrong. - local ufo, _ = pcall(require, "ufo") - if ufo then - require("ufo").detach(self.handle) + local ok, ufo = pcall(require, "ufo") + if ok and type(ufo.detach) == "function" then + logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") + ufo.detach(self.handle) end self.win_handle = win From d598bdec0ff92b3692f81ec30ec23ec0647226ed Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 20 Oct 2024 16:25:34 +0200 Subject: [PATCH 017/437] Clean up how disable-relative-line-numbers and disable-line-numbers are used in buffer class --- lua/neogit/lib/buffer.lua | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 48079555b..6d6c9a073 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -16,12 +16,8 @@ local Path = require("plenary.path") ---@field ui Ui ---@field kind string ---@field name string ----@field disable_line_numbers boolean ----@field disable_relative_line_numbers boolean local Buffer = { kind = "split", - disable_line_numbers = true, - disable_relative_line_numbers = true, } Buffer.__index = Buffer @@ -354,16 +350,6 @@ function Buffer:show() win = content_window end - api.nvim_win_call(win, function() - if self.disable_line_numbers then - vim.cmd("setlocal nonu") - end - - if self.disable_relative_line_numbers then - vim.cmd("setlocal nornu") - end - end) - -- Workaround UFO getting folds wrong. local ok, ufo = pcall(require, "ufo") if ok and type(ufo.detach) == "function" then @@ -623,9 +609,6 @@ function Buffer.create(config) buffer.name = config.name buffer.kind = config.kind or "split" - buffer.disable_line_numbers = (config.disable_line_numbers == nil) or config.disable_line_numbers - buffer.disable_relative_line_numbers = (config.disable_relative_line_numbers == nil) - or config.disable_relative_line_numbers if config.load then logger.debug("[BUFFER:" .. buffer.handle .. "] Loading content from file: " .. config.name) @@ -707,6 +690,14 @@ function Buffer.create(config) vim.opt_local.fillchars:append("fold: ") end) + if (config.disable_line_numbers == nil) or config.disable_line_numbers then + buffer:set_window_option("number", false) + end + + if (config.disable_relative_line_numbers == nil) or config.disable_relative_line_numbers then + buffer:set_window_option("relativenumber", false) + end + buffer:set_window_option("spell", config.spell_check or false) buffer:set_window_option("wrap", false) buffer:set_window_option("foldmethod", "manual") From 217efb7b74a79c38e004f987ce08b2b8d119930b Mon Sep 17 00:00:00 2001 From: fpohtmeh Date: Mon, 21 Oct 2024 00:00:33 +0300 Subject: [PATCH 018/437] Fix relative numbers in status view --- lua/neogit/buffers/status/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 983a8db50..2839850c2 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -97,6 +97,7 @@ function M:open(kind) context_highlight = not config.values.disable_context_highlighting, kind = kind or config.values.kind or "tab", disable_line_numbers = config.values.disable_line_numbers, + disable_relative_line_numbers = config.values.disable_relative_line_numbers, foldmarkers = not config.values.disable_signs, on_detach = function() Watcher.instance(self.root):unregister(self) From 481c5fbf163ecd770c0eb809829672b1bcccadb1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 09:40:33 +0200 Subject: [PATCH 019/437] Improve how git console streams data. Remove upper limit on length, since we no longer internally buffer the lines. --- lua/neogit/buffers/process/init.lua | 44 +++++++++++++---------------- lua/neogit/lib/buffer.lua | 22 ++++++++++++++- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 003ef12c3..e58dcf128 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -3,13 +3,14 @@ local config = require("neogit.config") local status_maps = require("neogit.config").get_reversed_status_maps() ---@class ProcessBuffer ----@field lines integer +---@field content string[] ---@field truncated boolean ---@field buffer Buffer ---@field open fun(self) ---@field hide fun(self) ---@field close fun(self) ---@field focus fun(self) +---@field flush_content fun(self) ---@field show fun(self) ---@field is_visible fun(self): boolean ---@field append fun(self, data: string) @@ -23,10 +24,9 @@ M.__index = M ---@param process ProcessOpts function M:new(process) local instance = { - content = string.format("> %s\r\n", table.concat(process.cmd, " ")), + content = { string.format("> %s\r\n", table.concat(process.cmd, " ")) }, process = process, buffer = nil, - lines = 0, truncated = false, } @@ -58,37 +58,28 @@ function M:show() end self.buffer:show() - self:refresh() + self:flush_content() end function M:is_visible() return self.buffer and self.buffer:is_valid() and self.buffer:is_visible() end -function M:refresh() - self.buffer:chan_send(self.content) - self.buffer:move_cursor(self.buffer:line_count()) -end - function M:append(data) - self.lines = self.lines + 1 - if self.lines > 1000 then - if not self.truncated then - self.content = table.concat({ self.content, "\r\n[Output too long - Truncated]" }, "\r\n") - self.truncated = true - - if self:is_visible() then - self:refresh() - end - end - - return - end - - self.content = table.concat({ self.content, data }, "\r\n") + assert(data, "no data to append") if self:is_visible() then - self:refresh() + self:flush_content() + self.buffer:chan_send(data) + else + table.insert(self.content, data) + end +end + +function M:flush_content() + if #self.content > 0 then + self.buffer:chan_send(table.concat(self.content, "\r\n")) + self.content = {} end end @@ -112,6 +103,7 @@ function M:open() buftype = false, kind = config.values.preview_buffer.kind, on_detach = function() + self.buffer:close_terminal_channel() self.buffer = nil end, autocmds = { @@ -131,6 +123,8 @@ function M:open() }, } + self.buffer:open_terminal_channel() + return self end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 6d6c9a073..4fc8d8dcb 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -502,7 +502,27 @@ function Buffer:win_call(f, ...) end function Buffer:chan_send(data) - api.nvim_chan_send(api.nvim_open_term(self.handle, {}), data) + assert(self.chan, "Terminal channel not open") + assert(data, "data cannot be nil") + api.nvim_chan_send(self.chan, data .. "\r\n") +end + +function Buffer:open_terminal_channel() + assert(self.chan == nil, "Terminal channel already open") + + self.chan = api.nvim_open_term(self.handle, {}) + assert(self.chan > 0, "Failed to open terminal channel") + + self:unlock() + self:set_lines(0, -1, false, {}) + self:lock() +end + +function Buffer:close_terminal_channel() + assert(self.chan, "No terminal channel to close") + + fn.chanclose(self.chan) + self.chan = nil end function Buffer:win_exec(cmd) From e8629671b48ef84f818163d4f50897196412c030 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 09:48:21 +0200 Subject: [PATCH 020/437] Add support for spinners (partial lines) in git console output --- lua/neogit/buffers/process/init.lua | 15 ++++++++++++--- lua/neogit/lib/buffer.lua | 2 +- lua/neogit/process.lua | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index e58dcf128..71ecb6920 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -13,7 +13,8 @@ local status_maps = require("neogit.config").get_reversed_status_maps() ---@field flush_content fun(self) ---@field show fun(self) ---@field is_visible fun(self): boolean ----@field append fun(self, data: string) +---@field append fun(self, data: string) Appends a complete line to the buffer +---@field append_partial fun(self, data: string) Appends a partial line - for things like spinners. ---@field new fun(self, table): ProcessBuffer ---@see Buffer ---@see Ui @@ -70,15 +71,23 @@ function M:append(data) if self:is_visible() then self:flush_content() - self.buffer:chan_send(data) + self.buffer:chan_send(data .. "\r\n") else table.insert(self.content, data) end end +function M:append_partial(data) + assert(data, "no data to append") + + if self:is_visible() then + self.buffer:chan_send(data) + end +end + function M:flush_content() if #self.content > 0 then - self.buffer:chan_send(table.concat(self.content, "\r\n")) + self.buffer:chan_send(table.concat(self.content, "\r\n") .. "\r\n") self.content = {} end end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 4fc8d8dcb..e1ec92721 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -504,7 +504,7 @@ end function Buffer:chan_send(data) assert(self.chan, "Terminal channel not open") assert(data, "data cannot be nil") - api.nvim_chan_send(self.chan, data .. "\r\n") + api.nvim_chan_send(self.chan, data) end function Buffer:open_terminal_channel() diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index d950808b1..15f6d1809 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -266,6 +266,8 @@ function Process:spawn(cb) if self.on_partial_line then self:on_partial_line(line) end + + self.buffer:append_partial(line) end local stdout_on_line = function(line) From 78e52ba661ddc902a2cd00f068d7cb7ec202ca1c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 22:57:45 +0200 Subject: [PATCH 021/437] Fix: Don't close popup when fuzzy finder loads. Suspend event. --- lua/neogit/popups/log/actions.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 8bf49a835..5396d485c 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -141,10 +141,12 @@ function M.limit_to_files() return "" end - local files = FuzzyFinderBuffer.new(git.files.all_tree()):open_async { + vim.o.eventignore = "WinLeave" + local files = FuzzyFinderBuffer.new(git.files.all_tree({ with_dir = true })):open_async { allow_multi = true, refocus_status = false, } + vim.o.eventignore = "" if not files or vim.tbl_isempty(files) then popup.state.env.files = nil From 557d57b5335675c9a57c7e68ee22b376b23a6a73 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 22:59:04 +0200 Subject: [PATCH 022/437] Add annotations --- lua/neogit/lib/git/files.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index 36a880ad2..c9ef56164 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -3,12 +3,14 @@ local git = require("neogit.lib.git") ---@class NeogitGitFiles local M = {} +---@return string[] function M.all() return git.cli["ls-files"].full_name.deleted.modified.exclude_standard.deduplicate.call({ hidden = true, }).stdout end +---@return string[] function M.untracked() return git.cli["ls-files"].others.exclude_standard.call({ hidden = true }).stdout end @@ -17,10 +19,12 @@ function M.all_tree() return git.cli["ls-tree"].full_tree.name_only.recursive.args("HEAD").call({ hidden = true }).stdout end +---@return string[] function M.diff(commit) return git.cli.diff.name_only.args(commit .. "...").call({ hidden = true }).stdout end +---@return string function M.relpath_from_repository(path) local result = git.cli["ls-files"].others.cached.modified.deleted.full_name .args(path) @@ -29,10 +33,14 @@ function M.relpath_from_repository(path) return result.stdout[1] end +---@param path string +---@return boolean function M.is_tracked(path) return git.cli["ls-files"].error_unmatch.files(path).call({ hidden = true, ignore_error = true }).code == 0 end +---@param paths string[] +---@return boolean function M.untrack(paths) return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).code == 0 end From f88d554005df47916f053399f936b90810ab7e79 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 22:59:26 +0200 Subject: [PATCH 023/437] Allow all_tree to take options, including directories for each file. this is used by the log popup `--` files filter, so users can filter by directory as well as file --- lua/neogit/lib/git/files.lua | 23 +++++++++++++++++++++-- lua/neogit/popups/log/actions.lua | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index c9ef56164..fb4cf7d78 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -1,4 +1,6 @@ local git = require("neogit.lib.git") +local util = require("neogit.lib.util") +local Path = require("plenary.path") ---@class NeogitGitFiles local M = {} @@ -15,8 +17,25 @@ function M.untracked() return git.cli["ls-files"].others.exclude_standard.call({ hidden = true }).stdout end -function M.all_tree() - return git.cli["ls-tree"].full_tree.name_only.recursive.args("HEAD").call({ hidden = true }).stdout +---@param opts { with_dir: boolean } +---@return string[] +function M.all_tree(opts) + opts = opts or {} + local files = git.cli["ls-tree"].full_tree.name_only.recursive.args("HEAD").call({ hidden = true }).stdout + + if opts.with_dir then + local dirs = {} + + for _, path in ipairs(files) do + local dir = vim.fs.dirname(path) .. Path.path.sep + dirs[dir] = true + end + + files = util.merge(files, vim.tbl_keys(dirs)) + table.sort(files) + end + + return files end ---@return string[] diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 5396d485c..5a0c87268 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -142,7 +142,7 @@ function M.limit_to_files() end vim.o.eventignore = "WinLeave" - local files = FuzzyFinderBuffer.new(git.files.all_tree({ with_dir = true })):open_async { + local files = FuzzyFinderBuffer.new(git.files.all_tree { with_dir = true }):open_async { allow_multi = true, refocus_status = false, } From 9f504c720b5a5d5a08625bf6197c8ef799b2205c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 21 Oct 2024 23:50:14 +0200 Subject: [PATCH 024/437] Be more defensive when opening buffers: if focus is on a floating window, they cannot be split and will crash. To remedy this, if we fail to open the buffer in the requested form, just use a floating window. --- lua/neogit/lib/buffer.lua | 166 ++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e1ec92721..261426cf8 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -269,85 +269,95 @@ function Buffer:show() end end - local win - local kind = self.kind - - if kind == "replace" then - self.old_buf = api.nvim_get_current_buf() - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() - elseif kind == "tab" then - vim.cmd("tab sb " .. self.handle) - win = api.nvim_get_current_win() - elseif kind == "split" or kind == "split_below" then - win = api.nvim_open_win(self.handle, true, { split = "below" }) - elseif kind == "split_above" then - win = api.nvim_open_win(self.handle, true, { split = "above" }) - elseif kind == "split_above_all" then - win = api.nvim_open_win(self.handle, true, { split = "above", win = -1 }) - elseif kind == "split_below_all" then - win = api.nvim_open_win(self.handle, true, { split = "below", win = -1 }) - elseif kind == "vsplit" then - win = api.nvim_open_win(self.handle, true, { split = "right", vertical = true }) - elseif kind == "vsplit_left" then - win = api.nvim_open_win(self.handle, true, { split = "left", vertical = true }) - elseif kind == "floating" then - -- Creates the border window - local vim_height = vim.o.lines - local vim_width = vim.o.columns - - local width = math.floor(vim_width * 0.8) + 3 - local height = math.floor(vim_height * 0.7) - local col = vim_width * 0.1 - 1 - local row = vim_height * 0.15 - - local content_window = api.nvim_open_win(self.handle, true, { - relative = "editor", - width = width, - height = height, - col = col, - row = row, - style = "minimal", - focusable = false, - border = "rounded", - }) - - api.nvim_win_set_cursor(content_window, { 1, 0 }) - win = content_window - elseif kind == "floating_console" then - local content_window = api.nvim_open_win(self.handle, true, { - anchor = "SW", - relative = "editor", - width = vim.o.columns, - height = math.floor(vim.o.lines * 0.3), - col = 0, - row = vim.o.lines - 2, - style = "minimal", - focusable = false, - border = { "─", "─", "─", "", "", "", "", "" }, - title = " Git Console ", - }) + ---@return integer window handle + local function open() + local win + if self.kind == "replace" then + self.old_buf = api.nvim_get_current_buf() + api.nvim_set_current_buf(self.handle) + win = api.nvim_get_current_win() + elseif self.kind == "tab" then + vim.cmd("tab sb " .. self.handle) + win = api.nvim_get_current_win() + elseif self.kind == "split" or self.kind == "split_below" then + win = api.nvim_open_win(self.handle, true, { split = "below" }) + elseif self.kind == "split_above" then + win = api.nvim_open_win(self.handle, true, { split = "above" }) + elseif self.kind == "split_above_all" then + win = api.nvim_open_win(self.handle, true, { split = "above", win = -1 }) + elseif self.kind == "split_below_all" then + win = api.nvim_open_win(self.handle, true, { split = "below", win = -1 }) + elseif self.kind == "vsplit" then + win = api.nvim_open_win(self.handle, true, { split = "right", vertical = true }) + elseif self.kind == "vsplit_left" then + win = api.nvim_open_win(self.handle, true, { split = "left", vertical = true }) + elseif self.kind == "floating" then + -- Creates the border window + local vim_height = vim.o.lines + local vim_width = vim.o.columns + + local width = math.floor(vim_width * 0.8) + 3 + local height = math.floor(vim_height * 0.7) + local col = vim_width * 0.1 - 1 + local row = vim_height * 0.15 + + local content_window = api.nvim_open_win(self.handle, true, { + relative = "editor", + width = width, + height = height, + col = col, + row = row, + style = "minimal", + focusable = false, + border = "rounded", + }) + + api.nvim_win_set_cursor(content_window, { 1, 0 }) + win = content_window + elseif self.kind == "floating_console" then + local content_window = api.nvim_open_win(self.handle, true, { + anchor = "SW", + relative = "editor", + width = vim.o.columns, + height = math.floor(vim.o.lines * 0.3), + col = 0, + row = vim.o.lines - 2, + style = "minimal", + focusable = false, + border = { "─", "─", "─", "", "", "", "", "" }, + title = " Git Console ", + }) + + api.nvim_win_set_cursor(content_window, { 1, 0 }) + win = content_window + elseif self.kind == "popup" then + -- local title, _ = self.name:gsub("^Neogit", ""):gsub("Popup$", "") + + local content_window = api.nvim_open_win(self.handle, true, { + anchor = "SW", + relative = "editor", + width = vim.o.columns, + height = math.floor(vim.o.lines * 0.3), + col = 0, + row = vim.o.lines - 2, + style = "minimal", + border = { "─", "─", "─", "", "", "", "", "" }, + -- title = (" %s Actions "):format(title), + -- title_pos = "center", + }) + + api.nvim_win_set_cursor(content_window, { 1, 0 }) + win = content_window + end - api.nvim_win_set_cursor(content_window, { 1, 0 }) - win = content_window - elseif kind == "popup" then - -- local title, _ = self.name:gsub("^Neogit", ""):gsub("Popup$", "") - - local content_window = api.nvim_open_win(self.handle, true, { - anchor = "SW", - relative = "editor", - width = vim.o.columns, - height = math.floor(vim.o.lines * 0.3), - col = 0, - row = vim.o.lines - 2, - style = "minimal", - border = { "─", "─", "─", "", "", "", "", "" }, - -- title = (" %s Actions "):format(title), - -- title_pos = "center", - }) + return win + end - api.nvim_win_set_cursor(content_window, { 1, 0 }) - win = content_window + -- With focus on a popup window, any kind of "split" buffer will crash. Floating windows cannot be split. + local ok, win = pcall(open) + if not ok then + self.kind = "floating" + win = open() end -- Workaround UFO getting folds wrong. @@ -638,7 +648,7 @@ function Buffer.create(config) local win if config.open ~= false then win = buffer:show() - logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window " .. win) + logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window " .. win .. " as " .. buffer.kind) end logger.debug("[BUFFER:" .. buffer.handle .. "] Setting buffer options") From d33cd6934503b2a10d8b54bf48355fbe40ad5060 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 22 Oct 2024 22:39:18 +0200 Subject: [PATCH 025/437] Fix: Win-call any fn.* functions within buffer class to ensure they apply to the correct context --- lua/neogit/lib/buffer.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 261426cf8..8eac48ca7 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -87,11 +87,13 @@ end ---@param view table output of Buffer:save_view() ---@param cursor? number function Buffer:restore_view(view, cursor) - if cursor then - view.lnum = math.min(fn.line("$"), cursor) - end + self:win_call(function() + if cursor then + view.lnum = math.min(fn.line("$"), cursor) + end - fn.winrestview(view) + fn.winrestview(view) + end) end function Buffer:write() From 610f6a02542b51ea5489850124621936f2d3835a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 22 Oct 2024 23:36:11 +0200 Subject: [PATCH 026/437] Fix refs buffer spec --- spec/buffers/refs_buffer_spec.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/buffers/refs_buffer_spec.rb b/spec/buffers/refs_buffer_spec.rb index d0802440d..50216688a 100644 --- a/spec/buffers/refs_buffer_spec.rb +++ b/spec/buffers/refs_buffer_spec.rb @@ -4,14 +4,8 @@ RSpec.describe "Refs Buffer", :git, :nvim do it "renders, raising no errors" do - nvim.keys("lr") + nvim.keys("y") expect(nvim.errors).to be_empty - expect(nvim.filetype).to eq("NeogitReflogView") - end - - it "can open CommitView" do - nvim.keys("lr") - expect(nvim.errors).to be_empty - expect(nvim.filetype).to eq("NeogitCommitView") + expect(nvim.filetype).to eq("NeogitRefsView") end end From 1c6f02040e128f9f8133905c2b22dd120d3030d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 22 Oct 2024 23:36:20 +0200 Subject: [PATCH 027/437] Raise timeout for git hooks console so it doesn't look jank --- 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 15f6d1809..3b0925118 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -115,7 +115,7 @@ function Process:start_timer() local timer = vim.loop.new_timer() self.timer = timer - local timeout = assert(self.git_hook and 100 or config.values.console_timeout, "no timeout") + local timeout = assert(self.git_hook and 800 or config.values.console_timeout, "no timeout") timer:start( timeout, 0, From 217050a50c167474a1cffa869650d7fa8c708878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Wed, 23 Oct 2024 08:57:03 +0200 Subject: [PATCH 028/437] Fix 'initial_branch_name' on create_branch It was working for rename and spin-off, but not for create. Probably a bad merge. --- lua/neogit/popups/branch/actions.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 5a1be245b..ed3d34f76 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -114,10 +114,10 @@ local function create_branch(popup, prompt, checkout, name) end local name = name - or input.get_user_input("Create branch", { - strip_spaces = true, - default = popup.state.env.suggested_branch_name or suggested_branch_name, - }) + or get_branch_name_user_input( + "Create branch", + popup.state.env.suggested_branch_name or suggested_branch_name + ) if not name then return end From 53d34b64fb75af85f4fb62e26c5b3c62a8655672 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 23 Oct 2024 09:18:19 +0200 Subject: [PATCH 029/437] Add diffview to CI --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86d4776f1..9d8a96069 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,7 @@ jobs: git config --global core.compression 0 git clone https://github.com/nvim-lua/plenary.nvim tmp/plenary git clone https://github.com/nvim-telescope/telescope.nvim tmp/telescope + git clone https://github.com/sindrets/diffview.nvim tmp/diffview - name: E2E Test run: | From 4686a6d32ecb4fb364138530b31fe77ba87d9711 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 23 Oct 2024 09:21:19 +0200 Subject: [PATCH 030/437] Manage test deps via ruby --- .github/workflows/test.yml | 7 ------- spec/spec_helper.rb | 2 +- spec/support/dependencies.rb | 9 +++++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9d8a96069..ff5c047d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,13 +28,6 @@ jobs: neovim: true version: ${{ matrix.release }} - - name: Install Dependencies - run: | - git config --global core.compression 0 - git clone https://github.com/nvim-lua/plenary.nvim tmp/plenary - git clone https://github.com/nvim-telescope/telescope.nvim tmp/telescope - git clone https://github.com/sindrets/diffview.nvim tmp/diffview - - name: E2E Test run: | bundle exec rspec diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 16d021f8a..3b5bffec2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,7 @@ ENV["GIT_CONFIG_GLOBAL"] = "" -PROJECT_DIR = File.expand_path(File.join(__dir__, "..")) +PROJECT_DIR = File.expand_path(File.join(__dir__, "..")) unless defined?(PROJECT_DIR) Dir[File.join(File.expand_path("."), "spec", "support", "**", "*.rb")].each { |f| require f } diff --git a/spec/support/dependencies.rb b/spec/support/dependencies.rb index eb6a53db0..7bed36513 100644 --- a/spec/support/dependencies.rb +++ b/spec/support/dependencies.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -return if ENV["CI"] - def dir_name(name) name.match(%r{[^/]+/(?[^\.]+)})[:dir_name] end @@ -16,8 +14,11 @@ def ensure_installed(name, build: nil) puts "Downloading dependency #{name} to #{dir}" Dir.mkdir(dir) - Git.clone("git@github.com:#{name}.git", dir) - Dir.chdir(dir) { system(build) } if build.present? + Git.clone("git@github.com:#{name}.git", dir, config: ["core.compression=0"], filter: "tree:0") + return unless build.present? + + puts "Building #{name} via #{build}" + Dir.chdir(dir) { system(build) } end ensure_installed "nvim-lua/plenary.nvim" From 967c8c3bba43f9e68bde614a97915572a92edc03 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 23 Oct 2024 09:41:05 +0200 Subject: [PATCH 031/437] Use system so we run via shell --- spec/support/dependencies.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/support/dependencies.rb b/spec/support/dependencies.rb index 7bed36513..053eed47d 100644 --- a/spec/support/dependencies.rb +++ b/spec/support/dependencies.rb @@ -13,8 +13,7 @@ def ensure_installed(name, build: nil) return if Dir.exist?(dir) && !Dir.empty?(dir) puts "Downloading dependency #{name} to #{dir}" - Dir.mkdir(dir) - Git.clone("git@github.com:#{name}.git", dir, config: ["core.compression=0"], filter: "tree:0") + `git -c core.compression=0 clone ssh://git@github.com:#{name}.git #{dir}` return unless build.present? puts "Building #{name} via #{build}" From 74e69b32f816ad878cca6d6bf64a79c991dba15b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 23 Oct 2024 09:46:04 +0200 Subject: [PATCH 032/437] Just use the workflow :roll: --- .github/workflows/test.yml | 10 ++++++++++ spec/support/dependencies.rb | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff5c047d5..0a397c918 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,16 @@ jobs: neovim: true version: ${{ matrix.release }} + - name: Install Dependencies + run: | + git config --global core.compression 0 + git clone https://github.com/nvim-lua/plenary.nvim tmp/plenary + git clone https://github.com/nvim-telescope/telescope.nvim tmp/telescope + git clone https://github.com/sindrets/diffview.nvim tmp/diffview + git clone https://github.com/nvim-telescope/telescope-fzf-native.nvim tmp/telescope-fzf-native + cd tmp/telescope-fzf-native + make + - name: E2E Test run: | bundle exec rspec diff --git a/spec/support/dependencies.rb b/spec/support/dependencies.rb index 053eed47d..de5b3cc46 100644 --- a/spec/support/dependencies.rb +++ b/spec/support/dependencies.rb @@ -13,7 +13,8 @@ def ensure_installed(name, build: nil) return if Dir.exist?(dir) && !Dir.empty?(dir) puts "Downloading dependency #{name} to #{dir}" - `git -c core.compression=0 clone ssh://git@github.com:#{name}.git #{dir}` + Dir.mkdir(dir) + Git.clone("git@github.com:#{name}.git", dir, filter: "tree:0") return unless build.present? puts "Building #{name} via #{build}" From cdb62d42ff8c010180f47d4fc3788ba21709ddf0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 24 Oct 2024 09:21:14 +0200 Subject: [PATCH 033/437] Capture backup of index prior to performing a hard reset. update docs for hard reset --- doc/neogit.txt | 6 ++++-- lua/neogit/lib/git/reset.lua | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index fe33284e8..88d347b39 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1570,8 +1570,10 @@ Actions: *neogit_reset_popup_actions* • Hard *neogit_reset_hard* Resets the index and working tree. Any changes to tracked files in the - working tree since are discarded. Any untracked files or - directories in the way of writing any tracked files are simply deleted. + working tree since are discarded, however a reflog entry will be + created with their current state, so changes can be restored if needed. + Any untracked files or directories in the way of writing any tracked files + are simply deleted. • Keep *neogit_reset_keep* Resets index entries and updates files in the working tree that are diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index 259ada4bf..f0f4abcd3 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -4,6 +4,20 @@ local git = require("neogit.lib.git") ---@class NeogitGitReset local M = {} +local function timestamp() + local now = os.date("!*t") + return string.format("%s-%s-%sT%s.%s.%s", now.year, now.month, now.day, now.hour, now.min, now.sec) +end + +-- https://gist.github.com/chx/3a694c2a077451e3d446f85546bb9278 +-- Capture state of index prior to reset +local function backup_index() + git.cli.add.update.call { hidden = true, await = true } + git.cli.commit.message("Hard reset backup").call { hidden = true, await = true, pty = true } + git.cli["update-ref"].args("refs/reset-backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } + git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } +end + local function fire_reset_event(data) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitReset", modeline = false, data = data }) end @@ -29,6 +43,8 @@ function M.soft(commit) end function M.hard(commit) + backup_index() + local result = git.cli.reset.hard.args(commit).call { await = true } if result.code ~= 0 then notification.error("Reset Failed") From 3409117dd289afbadd7f68f6aa42521464594c11 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 24 Oct 2024 09:21:52 +0200 Subject: [PATCH 034/437] Filter out history limiting options from reflog buffer data query - they cause it to fail --- lua/neogit/lib/git/reflog.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index 26f5d4740..d859cc199 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -57,6 +57,9 @@ function M.list(refname, options) "%cr", -- Commit Date (Relative) }, "%x1E") + util.remove_item_from_table(options, "--simplify-by-decoration") + util.remove_item_from_table(options, "--follow") + return parse( git.cli.reflog.show .format(format) From 0878e460bf9e8d8e0eb2edd45d75ca698e3d85c6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 24 Oct 2024 11:27:23 +0200 Subject: [PATCH 035/437] refactor: extract index function to index lib --- lua/neogit/lib/git/index.lua | 14 ++++++++++++++ lua/neogit/lib/git/reset.lua | 16 +--------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index d929d08b2..cc2c9e3c5 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -157,4 +157,18 @@ function M.update() :spawn_async() end +local function timestamp() + local now = os.date("!*t") + return string.format("%s-%s-%sT%s.%s.%s", now.year, now.month, now.day, now.hour, now.min, now.sec) +end + +-- https://gist.github.com/chx/3a694c2a077451e3d446f85546bb9278 +-- Capture state of index as reflog entry +function M.create_backup() + git.cli.add.update.call { hidden = true, await = true } + git.cli.commit.message("Hard reset backup").call { hidden = true, await = true, pty = true } + git.cli["update-ref"].args("refs/backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } + git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } +end + return M diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index f0f4abcd3..2834c82bb 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -4,20 +4,6 @@ local git = require("neogit.lib.git") ---@class NeogitGitReset local M = {} -local function timestamp() - local now = os.date("!*t") - return string.format("%s-%s-%sT%s.%s.%s", now.year, now.month, now.day, now.hour, now.min, now.sec) -end - --- https://gist.github.com/chx/3a694c2a077451e3d446f85546bb9278 --- Capture state of index prior to reset -local function backup_index() - git.cli.add.update.call { hidden = true, await = true } - git.cli.commit.message("Hard reset backup").call { hidden = true, await = true, pty = true } - git.cli["update-ref"].args("refs/reset-backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } - git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } -end - local function fire_reset_event(data) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitReset", modeline = false, data = data }) end @@ -43,7 +29,7 @@ function M.soft(commit) end function M.hard(commit) - backup_index() + git.index.create_backup() local result = git.cli.reset.hard.args(commit).call { await = true } if result.code ~= 0 then From 2485f177cf2f30bda8554f0e338e222e5d2ce7ba Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 24 Oct 2024 22:24:14 +0200 Subject: [PATCH 036/437] Do not try to render graph in this log call --- lua/neogit/lib/git/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 4b3c61857..a252f946b 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -520,7 +520,7 @@ function M.reflog_message(skip) end M.abbreviated_size = util.memoize(function() - local commits = M.list({ "HEAD", "--max-count=1" }, {}, {}, true) + local commits = M.list({ "HEAD", "--max-count=1" }, nil, {}, true) if vim.tbl_isempty(commits) then return 7 else From 3f82e300bb06641dceedcf53d361feabd19dd693 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 12:10:07 +0200 Subject: [PATCH 037/437] Replace flog graph with gitgraph.nvim so we can support color --- lua/neogit/lib/git/log.lua | 4 +- lua/neogit/lib/graph.lua | 1533 +++++++++++++++++++++++--------- lua/neogit/popups/log/init.lua | 3 +- 3 files changed, 1102 insertions(+), 438 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index a252f946b..efced7152 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -353,7 +353,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) .files(unpack(files)) .call({ hidden = hidden, ignore_error = hidden }).stdout - local commits = record.decode(output) + local commits = record.decode(output) ---@type CommitLogEntry[] if vim.tbl_isempty(commits) then return {} end @@ -361,7 +361,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) local graph_output if graph then if config.values.graph_style == "unicode" then - graph_output = require("neogit.lib.graph").build(commits) + graph_output = require("neogit.lib.graph").build(commits, graph_color) elseif config.values.graph_style == "ascii" then util.remove_item_from_table(options, "--show-signature") graph_output = M.graph(options, files, graph_color) diff --git a/lua/neogit/lib/graph.lua b/lua/neogit/lib/graph.lua index b2309b72a..3de61afdf 100644 --- a/lua/neogit/lib/graph.lua +++ b/lua/neogit/lib/graph.lua @@ -1,560 +1,1225 @@ --- Modified version of graphing algorithm from https://github.com/rbong/vim-flog +-- Modified version of graphing algorithm from https://github.com/isakbm/gitgraph.nvim +-- +-- MIT License +-- +-- Copyright (c) 2024 Isak Buhl-Mortensen +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. local M = {} -local graph_error = "flog: internal error drawing graph" - --- stylua: ignore start -local current_commit_str = "• " -local commit_branch_str = "│ " -local commit_empty_str = " " -local complex_merge_str_1 = "┬┊" -local complex_merge_str_2 = "╰┤" -local merge_all_str = "┼" -local merge_jump_str = "┊" -local merge_up_down_left_str = "┤" -local merge_up_down_right_str = "├" -local merge_up_down_str = "│" -local merge_up_left_right_str = "┴" -local merge_up_left_str = "╯" -local merge_up_right_str = "╰" -local merge_up_str = " " -local merge_down_left_right_str = "┬" -local merge_down_left_str = "╮" -local merge_down_right_str = "╭" -local merge_left_right_str = "─" -local merge_empty_str = " " -local missing_parent_str = "┊ " -local missing_parent_branch_str = "│ " -local missing_parent_empty_str = " " - --- Returns an iterator for traversing UTF-8 encoded strings, yielding each --- character as a substring. The iterator ensures correct handling of --- multi-byte UTF-8 characters, decoding them and returning them as separate --- characters. --- --- See also: --- https://github.com/gijit/gi/blob/7052cfb07ca8b52afaa6c2a3deee53952784bd5d/pkg/utf8/utf8.lua#L80C1-L81C47 --- -local function utf8_iter(s) - local i = 1 - return function() - local b = string.byte(s, i) - - if not b then - return nil -- string end - end - - -- {{{ - -- 00000000-01111111 00-7F 000-127 US-ASCII (single byte) - -- 10000000-10111111 80-BF 128-191 Second, third, or fourth byte of a multi-byte sequence - -- 11000000-11000001 C0-C1 192-193 Overlong encoding: start of a 2-byte sequence, but code point <= 127 - -- 11000010-11011111 C2-DF 194-223 Start of 2-byte sequence - -- 11100000-11101111 E0-EF 224-239 Start of 3-byte sequence - -- 11110000-11110100 F0-F4 240-244 Start of 4-byte sequence - -- 11110101-11110111 F5-F7 245-247 Restricted by RFC 3629: start of 4-byte sequence for codepoint above 10FFFF - -- 11111000-11111011 F8-FB 248-251 Restricted by RFC 3629: start of 5-byte sequence - -- 11111100-11111101 FC-FD 252-253 Restricted by RFC 3629: start of 6-byte sequence - -- 11111110-11111111 FE-FF 254-255 Invalid: not defined by original UTF-8 specification - -- }}} - local w = (b >= 192 and b <= 223 and 2) or - (b >= 224 and b <= 239 and 3) or - (b >= 240 and b <= 247 and 4) or 1 - - local c = string.sub(s, i, i + w - 1) - i = i + w - return c +-- heuristic to check if this row contains a "bi-crossing" of branches +-- +-- a bi-crossing is when we have more than one branch "propagating" horizontally +-- on a connector row +-- +-- this can only happen when the commit on the row +-- above the connector row is a merge commit +-- but it doesn't always happen +-- +-- in addition to needing a merge commit on the row above +-- we need the span (interval) of the "emphasized" connector cells +-- (they correspond to connectors to the parents of the merge commit) +-- we need that span to overlap with at least one connector cell that +-- is destined for the commit on the next row +-- (the commit before the merge commit) +-- in addition, we need there to be more than one connector cell +-- destined to the next commit +-- +-- here is an example +-- +-- +-- j i i ⓮ │ │ j -> g h +-- g i i h ?─?─?─╮ +-- g i h │ ⓚ │ i +-- +-- +-- overlap: +-- +-- g-----h 1 4 +-- i-i 2 3 +-- +-- NOTE how `i` is the commit that the `i` cells are destined for +-- notice how there is more than on `i` in the connector row +-- and that it lies in the span of g-h +-- +-- some more examples +-- +-- ------------------------------------- +-- +-- S T S │ ⓮ │ T -> R S +-- S R S ?─?─? +-- S R ⓚ │ S +-- +-- +-- overlap: +-- +-- S-R 1 2 +-- S---S 1 3 +-- +-- ------------------------------------- +-- +-- +-- c b a b ⓮ │ │ │ c -> Z a +-- Z b a b ?─?─?─? +-- Z b a │ ⓚ │ b +-- +-- overlap: +-- +-- Z---a 1 3 +-- b---b 2 4 +-- +-- ------------------------------------- +-- +-- finally a negative example where there is no problem +-- +-- +-- W V V ⓮ │ │ W -> S V +-- S V V ⓸─⓵─╯ +-- S V │ ⓚ V +-- +-- no overlap: +-- +-- S-V 1 2 +-- V-V 2 3 +-- +-- the reason why there is no problem (bi-crossing) above +-- follows from the fact that the span from V <- V only +-- touches the span S -> V it does not overlap it, so +-- figuratively we have S -> V <- V which is fine +-- +-- TODO: +-- FIXME: need to test if we handle two bi-connectors in succession +-- correctly +-- +---@param commit_row I.Row +---@param connector_row I.Row +---@param next_commit I.Commit? +---@return boolean -- whether or not this is a bi crossing +---@return boolean -- whether or not it can be resolved safely by edge lifting +local function get_is_bi_crossing(commit_row, connector_row, next_commit) + if not next_commit then + return false, false end -end --- stylua: ignore end -function M.build(commits) - commits = require("neogit.lib.util").filter_map(commits, function(item) - if item.oid then - return item + local prev = commit_row.commit + assert(prev, "expected a prev commit") + + if #prev.parents < 2 then + return false, false -- bi-crossings only happen when prev is a merge commit + end + + local row = connector_row + + ---@param k integer + local function interval_upd(x, k) + if k < x.start then + x.start = k end - end) + if k > x.stop then + x.stop = k + end + end + + -- compute the emphasized interval (merge commit parent interval) + local emi = { start = #row.cells, stop = 1 } + for k, cell in ipairs(row.cells) do + if cell.commit and cell.emphasis then + interval_upd(emi, k) + end + end - -- Init commit parsing data - local commit_hashes = {} - for _, commit in ipairs(commits) do - commit_hashes[commit.oid] = 1 + -- compute connector interval + local coi = { start = #row.cells, stop = 1 } + for k, cell in ipairs(row.cells) do + if cell.commit and cell.commit.hash == next_commit.hash then + interval_upd(coi, k) + end end - local vim_out = {} - local vim_out_index = 1 - - -- Init graph data - local branch_hashes = {} - local branch_indexes = {} - local nbranches = 0 + -- unsafe if starts of intervals overlap and are equal to direct parent location + local safe = not (emi.start == coi.start and prev.j == emi.start) + + -- return earily when connector interval is trivial + if coi.start == coi.stop then + return false, safe + end - -- Draw graph - for _, commit in ipairs(commits) do - -- Get commit data - local commit_hash = commit.oid - local parents = vim.split(commit.parent, " ") - local parent_hashes = {} - local nparents = #parents + -- print('emi:', vim.inspect(emi)) + -- print('coi:', vim.inspect(coi)) - for _, parent in ipairs(parents) do - parent_hashes[parent] = 1 + -- check overlap + do + -- are intervals identical, then that counts as overlap + if coi.start == emi.start and coi.stop == emi.stop then + return true, safe + end + end + for _, k in pairs(emi) do + -- emi endpoints inside coi ? + if coi.start < k and k < coi.stop then + return true, safe end + end + for _, k in pairs(coi) do + -- coi endpoints inside emi ? + if emi.start < k and k < emi.stop then + return true, safe + end + end - -- Init commit output + return false, safe +end - -- The prefix that goes before the first commit line - local commit_prefix = {} - -- The number of strings in commit lines - local ncommit_strings = 0 - -- The merge line that goes after the commit - local merge_line = {} - -- The complex merge line that goes after the merge - local complex_merge_line = {} - -- The number of strings in merge lines - local nmerge_strings = 0 - -- The two lines indicating missing parents after the complex line - local missing_parents_line_1 = {} - local missing_parents_line_2 = {} - -- The number of strings in missing parent lines - local nmissing_parents_strings = 0 +---@param next I.Commit +---@param prev_commit_row I.Row +---@param prev_connector_row I.Row +---@param commit_row I.Row +---@param connector_row I.Row +local function resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_row, connector_row, next) + -- if false then + -- if false then -- get_is_bi_crossing(graph, next_commit, #graph) then + -- print 'we have a bi crossing' + -- void all repeated reservations of `next` from + -- this and the previous row + local prev_row = commit_row + local this_row = connector_row + assert(prev_row and this_row, "expecting two prior rows due to bi-connector") + + --- example of what this does + --- + --- input: + --- + --- j i i │ │ │ + --- j i i ⓮ │ │ <- prev + --- g i i h ⓸─⓵─ⓥ─╮ <- bi connector + --- + --- output: + --- + --- j i i │ ⓶─╯ + --- j i ⓮ │ <- prev + --- g i h ⓸─│───╮ <- bi connector + --- + ---@param row I.Row + ---@return integer + local function void_repeats(row) + local start_voiding = false + local ctr = 0 + for k, cell in ipairs(row.cells) do + if cell.commit and cell.commit.hash == next.hash then + if not start_voiding then + start_voiding = true + elseif not row.cells[k].emphasis then + -- else + + row.cells[k] = { connector = " " } -- void it + ctr = ctr + 1 + end + end + end + return ctr + end - -- Init visual data + void_repeats(prev_row) + void_repeats(this_row) + + -- we must also take care when the prev prev has a repeat where + -- the repeat is not the direct parent of its child + -- + -- G ⓯ + -- e d c ⓸─ⓢ─╮ + -- E D C F │ │ │ ⓯ + -- e D C c b a d ⓶─⓵─│─⓴─ⓢ─ⓢ─? <--- to resolve this + -- E D C C B A ⓮ │ │ │ │ │ + -- c D C C b A ⓸─│─ⓥ─ⓥ─⓷ │ + -- C D B A │ ⓮ │ │ + -- C c b a ⓶─ⓥ─────⓵─⓷ + -- C B A ⓮ │ │ + -- b B a ⓸───────ⓥ─⓷ + -- B A ⓚ │ + -- a A ⓶─────────╯ + -- A ⓚ + local prev_prev_row = prev_connector_row -- graph[#graph - 2] + local prev_prev_prev_row = prev_commit_row -- graph[#graph - 3] + assert(prev_prev_row and prev_prev_prev_row) + do + local start_voiding = false + local ctr = 0 + ---@type I.Cell? + local replacer = nil + for k, cell in ipairs(prev_prev_row.cells) do + if cell.commit and cell.commit.hash == next.hash then + if not start_voiding then + start_voiding = true + replacer = cell + elseif k ~= prev_prev_prev_row.commit.j then + local ppcell = prev_prev_prev_row.cells[k] + if (not ppcell) or (ppcell and ppcell.connector == " ") then + prev_prev_row.cells[k] = { connector = " " } -- void it + replacer.emphasis = true + ctr = ctr + 1 + end + end + end + end + end - -- The number of columns in the commit output - local ncommit_cols = 0 - -- The number of visual parents - local nvisual_parents = 0 - -- The number of complex merges (octopus) - local ncomplex_merges = 0 - -- The number of missing parents - local nmissing_parents = 0 + -- assert(prev_rep_ctr == this_rep_ctr) + + -- newly introduced tracking cells can be squeezed in + -- + -- before: + -- + -- j i i │ ⓶─╯ + -- j i ⓮ │ + -- g i h ⓸─│───╮ + -- + -- after: + -- + -- j i i │ ⓶─╯ + -- j i ⓮ │ + -- g i h ⓸─│─╮ + -- + -- can think of this as scooting the cell to the left + -- when the cell was just introduced + -- TODO: implement this at some point + -- for k, cell in ipairs(this_row.cells) do + -- if cell.commit and not prev_row.cells[k].commit and not this_row.cells[k - 2] then + -- end + -- end +end - -- Init graph data +---@class I.Row +---@field cells I.Cell[] +---@field commit I.Commit? -- there's a single comit for every even row + +---@class I.Cell +---@field is_commit boolean? -- when true this cell is a real commit +---@field commit I.Commit? -- a cell is associated with a commit, but the empty column gaps don't have them +---@field symbol string? +---@field connector string? -- a cell is eventually given a connector +---@field emphasis boolean? -- when true indicates that this is a direct parent of cell on previous row + +---@class I.Commit +---@field hash string +---@field msg string +---@field branch_names string[] +---@field tags string[] +---@field debug string? +---@field author_date string +---@field author_name string +---@field i integer +---@field j integer +---@field parents string[] +---@field children string[] + +---@class I.Highlight +---@field hg string +---@field row integer +---@field start integer +---@field stop integer + +local sym = { + merge_commit = "", + commit = "", + merge_commit_end = "", + commit_end = "", + GVER = "", + GHOR = "", + GCLD = "", + GCRD = "╭", + GCLU = "", + GCRU = "", + GLRU = "", + GLRD = "", + GLUD = "", + GRUD = "", + GFORKU = "", + GFORKD = "", + GRUDCD = "", + GRUDCU = "", + GLUDCD = "", + GLUDCU = "", + GLRDCL = "", + GLRDCR = "", + GLRUCL = "", + GLRUCR = "", +} + +local BRANCH_COLORS = { + "Red", + "Yellow", + "Green", + "Cyan", + "Blue", + "Purple", + "Orange" +} + +local NUM_BRANCH_COLORS = #BRANCH_COLORS + +local util = require("neogit.lib.util") + +---@param commits CommitLogEntry[] +---@param color boolean? +function M.build(commits, color) + local GVER = sym.GVER + local GHOR = sym.GHOR + local GCLD = sym.GCLD + local GCRD = sym.GCRD + local GCLU = sym.GCLU + local GCRU = sym.GCRU + local GLRU = sym.GLRU + local GLRD = sym.GLRD + local GLUD = sym.GLUD + local GRUD = sym.GRUD + + local GFORKU = sym.GFORKU + local GFORKD = sym.GFORKD + + local GRUDCD = sym.GRUDCD + local GRUDCU = sym.GRUDCU + local GLUDCD = sym.GLUDCD + local GLUDCU = sym.GLUDCU + + local GLRDCL = sym.GLRDCL + local GLRDCR = sym.GLRDCR + local GLRUCL = sym.GLRUCL + local GLRUCR = sym.GLRUCR + + local GRCM = sym.commit + local GMCM = sym.merge_commit + local GRCME = sym.commit_end + local GMCME = sym.merge_commit_end + + local raw_commits = util.filter_map(commits, function(item) + if item.oid then + return { + msg = item.subject, + branch_names = {}, + tags = {}, + author_date = item.author_date, + hash = item.oid, + parents = vim.split(item.parent, " ") + } + end + end) - -- The number of passed merges - local nmerges_left = 0 - -- The number of upcoming merges (parents + commit) - local nmerges_right = nparents + 1 - -- The index of the commit branch - local commit_branch_index = branch_indexes[commit_hash] - -- The index of the moved parent branch (there is only one) - local moved_parent_branch_index = nil - -- The number of branches on the commit line - local ncommit_branches = nbranches + (commit_branch_index and 0 or 1) + local commits = {} ---@type table + local sorted_commits = {} ---@type string[] + + for _, rc in ipairs(raw_commits) do + local commit = { + msg = rc.msg, + branch_names = rc.branch_names, + tags = rc.tags, + author_date = rc.author_date, + author_name = rc.author_name, + hash = rc.hash, + i = -1, + j = -1, + parents = rc.parents, + children = {}, + } + + sorted_commits[#sorted_commits + 1] = commit.hash + commits[rc.hash] = commit + end - -- Init indexes + do + for _, c_hash in ipairs(sorted_commits) do + local c = commits[c_hash] - -- The current branch - local branch_index = 1 - -- The current parent - local parent_index = 1 + for _, h in ipairs(c.parents) do + local p = commits[h] + if p then + p.children[#p.children + 1] = c.hash + else + -- create a virtual parent, it is not added to the list of commit hashes + commits[h] = { + hash = h, + author_name = "virtual", + msg = "virtual parent", + author_date = "unknown", + parents = {}, + children = { c.hash }, + branch_names = {}, + tags = {}, + i = -1, + j = -1, + } + end + end + end + end + + ---@param cells I.Cell[] + ---@return I.Cell[] + local function propagate(cells) + local new_cells = {} + for _, cell in ipairs(cells) do + if cell.connector then + new_cells[#new_cells + 1] = { connector = " " } + elseif cell.commit then + assert(cell.commit) + new_cells[#new_cells + 1] = { commit = cell.commit } + else + new_cells[#new_cells + 1] = { connector = " " } + end + end + return new_cells + end - -- Find the first empty parent - while parent_index <= nparents and branch_indexes[parents[parent_index]] do - parent_index = parent_index + 1 + ---@param cells I.Cell[] + ---@param hash string + ---@param start integer? + ---@return integer? + local function find(cells, hash, start) + local start = start or 1 + for idx = start, #cells, 2 do + local c = cells[idx] + if c.commit and c.commit.hash == hash then + return idx + end end + return nil + end - -- Traverse old and new branches + ---@param cells I.Cell[] + ---@param start integer? + ---@return integer + local function next_vacant_j(cells, start) + local start = start or 1 + for i = start, #cells, 2 do + local cell = cells[i] + if cell.connector == " " then + return i + end + end + return #cells + 1 + end - while branch_index <= nbranches or nmerges_right > 0 do - -- Get branch data + --- returns the generated row and the integer (j) location of the commit + ---@param c I.Commit + ---@param prev_row I.Row? + ---@return I.Row, integer + local function generate_commit_row(c, prev_row) + local j = nil ---@type integer? - local branch_hash = branch_hashes[branch_index] - local is_commit = branch_index == commit_branch_index + local rowc = {} ---@type I.Cell[] - -- Set merge info before updates + if prev_row then + rowc = propagate(prev_row.cells) + j = find(prev_row.cells, c.hash) + end - local merge_up = branch_hash or moved_parent_branch_index == branch_index - local merge_left = nmerges_left > 0 and nmerges_right > 0 - local is_complex = false - local is_missing_parent = false + -- if reserved location use it + if j then + c.j = j + rowc[j] = { commit = c, is_commit = true } - -- Handle commit + -- clear any supurfluous reservations + for k = j + 1, #rowc do + local v = rowc[k] + if v.commit and v.commit.hash == c.hash then + rowc[k] = { connector = " " } + end + end + else + j = next_vacant_j(rowc) + c.j = j + rowc[j] = { commit = c, is_commit = true } + rowc[j + 1] = { connector = " " } + end - if not branch_hash and not commit_branch_index then - -- Found empty branch and commit does not have a branch - -- Add the commit in the empty spot + return { cells = rowc, commit = c }, j + end - commit_branch_index = branch_index - is_commit = true + ---@param prev_commit_row I.Row + ---@param prev_connector_row I.Row + ---@param commit_row I.Row + ---@param commit_loc integer + ---@param curr_commit I.Commit + ---@param next_commit I.Commit? + ---@return I.Row + local function generate_connector_row( + prev_commit_row, + prev_connector_row, + commit_row, + commit_loc, + curr_commit, + next_commit + ) + -- connector row (reservation row) + -- + -- first we propagate + local connector_cells = propagate(commit_row.cells) + + -- connector row + -- + -- now we proceed to add the parents of the commit we just added + if #curr_commit.parents > 0 then + ---@param rem_parents string[] + local function reserve_remainder(rem_parents) + -- + -- reserve the rest of the parents in slots to the right of us + -- + -- ... another alternative is to reserve rest of the parents of c if they have not already been reserved + -- for i = 2, #c.parents do + for _, h in ipairs(rem_parents) do + local j = find(commit_row.cells, h, commit_loc) + if not j then + local j = next_vacant_j(connector_cells, commit_loc) + connector_cells[j] = { commit = commits[h], emphasis = true } + connector_cells[j + 1] = { connector = " " } + else + connector_cells[j].emphasis = true + end + end end - if is_commit then - -- Count commit merge - nmerges_right = nmerges_right - 1 - nmerges_left = nmerges_left + 1 - - if branch_hash then - -- End of branch - - -- Remove branch - branch_hashes[commit_branch_index] = nil - branch_indexes[commit_hash] = nil - - -- Trim trailing empty branches - while nbranches > 0 and not branch_hashes[nbranches] do - nbranches = nbranches - 1 + -- we start by peeking at next commit and seeing if it is one of our parents + -- we only do this if one of our propagating branches is already destined for this commit + ---@type I.Cell? + local tracker = nil + if next_commit then + for _, cell in ipairs(connector_cells) do + if cell.commit and cell.commit.hash == next_commit.hash then + tracker = cell + break end + end + end - -- Clear branch hash - branch_hash = nil + local next_p_idx = nil -- default to picking first parent + if tracker and next_commit then + -- this loop updates next_p_idx to the next commit if they are identical + for k, h in ipairs(curr_commit.parents) do + if h == next_commit.hash then + next_p_idx = k + break + end end + end + + -- next_p_idx = nil - if parent_index > nparents and nmerges_right == 1 then - -- There is only one remaining parent, to the right - -- Move it under the commit + -- add parents + if next_p_idx then + assert(tracker) + -- if next commit is our parent then we do some complex logic + if #curr_commit.parents == 1 then + -- simply place parent at our location + connector_cells[commit_loc].commit = commits[curr_commit.parents[1]] + connector_cells[commit_loc].emphasis = true + else + -- void the cell at our location (will be replaced by our parents in a moment) + connector_cells[commit_loc] = { connector = " " } + + -- put emphasis on tracker for the special parent + tracker.emphasis = true + + -- only reserve parents that are different from next commit + ---@type string[] + local rem_parents = {} + for k, h in ipairs(curr_commit.parents) do + if k ~= next_p_idx then + rem_parents[#rem_parents + 1] = h + end + end + + assert(#rem_parents == #curr_commit.parents - 1, "unexpected amount of rem parents") + reserve_remainder(rem_parents) - -- Find parent to right - parent_index = nparents - while (branch_indexes[parents[parent_index]] or -1) < branch_index do - parent_index = parent_index - 1 + -- we fill this with the next commit if it is empty, a bit hacky + if connector_cells[commit_loc].connector == " " then + connector_cells[commit_loc].commit = tracker.commit + connector_cells[commit_loc].emphasis = true + connector_cells[commit_loc].connector = nil + tracker.emphasis = false end + end + else + -- simply add first parent at our location and then reserve the rest + connector_cells[commit_loc].commit = commits[curr_commit.parents[1]] + connector_cells[commit_loc].emphasis = true - -- Get parent data - local parent_hash = parents[parent_index] - local parent_branch_index = branch_indexes[parent_hash] + local rem_parents = {} + for k = 2, #curr_commit.parents do + rem_parents[#rem_parents + 1] = curr_commit.parents[k] + end - -- Remove old parent branch - branch_hashes[parent_branch_index] = nil - branch_indexes[parent_hash] = nil + reserve_remainder(rem_parents) + end - -- Trim trailing empty branches - while nbranches > 0 and not branch_hashes[nbranches] do - nbranches = nbranches - 1 - end + local connector_row = { cells = connector_cells } ---@type I.Row + + -- handle bi-connector rows + local is_bi_crossing, bi_crossing_safely_resolveable = get_is_bi_crossing( + commit_row, + connector_row, + next_commit + ) + + if is_bi_crossing and bi_crossing_safely_resolveable and next_commit then + resolve_bi_crossing( + prev_commit_row, + prev_connector_row, + commit_row, + connector_row, + next_commit + ) + end - -- Record the old index - moved_parent_branch_index = parent_branch_index + return connector_row + else + -- if we're here then it means that this commit has no common ancestors with other commits + -- ... a different family ... see test `different family` - -- Count upcoming moved parent as another merge - nmerges_right = nmerges_right + 1 + -- we must remove the already propagated connector for the current commit since it has no parents + for i = 1, #connector_cells, 2 do + local cell = connector_cells[i] + if cell.commit and cell.commit.hash == curr_commit.hash then + connector_cells[i] = { connector = " " } end end - -- Handle parents + local connector_row = { cells = connector_cells } - if not branch_hash and parent_index <= nparents then - -- New parent + return connector_row + end + end - -- Get parent data - local parent_hash = parents[parent_index] + ---@param commits table + ---@param sorted_commits string[] + ---@return I.Row[] + local function straight_j(commits, sorted_commits) + local graph = {} ---@type I.Row[] + + for i, c_hash in ipairs(sorted_commits) do + -- get the input parameters + local curr_commit = commits[c_hash] + local next_commit = commits[sorted_commits[i + 1]] + local prev_commit_row = graph[#graph - 1] + local prev_connector_row = graph[#graph] + + -- generate commit and connector row for the current commit + local commit_row, commit_loc = generate_commit_row(curr_commit, prev_connector_row) + local connector_row = nil ---@type I.Row + if i < #sorted_commits then + connector_row = generate_connector_row( + prev_commit_row, + prev_connector_row, + commit_row, + commit_loc, + curr_commit, + next_commit + ) + end - -- Set branch to parent - branch_indexes[parent_hash] = branch_index - branch_hashes[branch_index] = parent_hash + -- write the result + graph[#graph + 1] = commit_row + if connector_row then + graph[#graph + 1] = connector_row + end + end - -- Update branch has - branch_hash = parent_hash + return graph + end - -- Update the number of branches - if branch_index > nbranches then - nbranches = branch_index - end + local graph = straight_j(commits, sorted_commits) - -- Jump to next available parent - parent_index = parent_index + 1 - while parent_index <= nparents and branch_indexes[parents[parent_index]] do - parent_index = parent_index + 1 - end + ---@param graph I.Row[] + ---@return string[] + ---@return I.Highlight[] + local function graph_to_lines(graph) + ---@type table[] + local lines = {} - -- Count new parent merge - nmerges_right = nmerges_right - 1 - nmerges_left = nmerges_left + 1 + ---@type I.Highlight[] + local highlights = {} - -- Determine if parent is missing - if branch_hash and not commit_hashes[parent_hash] then - is_missing_parent = true - nmissing_parents = nmissing_parents + 1 - end + ---@param cell I.Cell + ---@return string + local function commit_cell_symb(cell) + assert(cell.is_commit) - -- Record the visual parent - nvisual_parents = nvisual_parents + 1 - elseif - branch_index == moved_parent_branch_index or (nmerges_right > 0 and parent_hashes[branch_hash]) - then - -- Existing parents - - -- Count existing parent merge - nmerges_right = nmerges_right - 1 - nmerges_left = nmerges_left + 1 - - -- Determine if parent has a complex merge - is_complex = merge_left and nmerges_right > 0 - if is_complex then - ncomplex_merges = ncomplex_merges + 1 - end + if #cell.commit.parents > 1 then + -- merge commit + return #cell.commit.children == 0 and GMCME or GMCM + else + -- regular commit + return #cell.commit.children == 0 and GRCME or GRCM + end + end - -- Determine if parent is missing - if branch_hash and not commit_hashes[branch_hash] then - is_missing_parent = true - nmissing_parents = nmissing_parents + 1 + ---@param row I.Row + ---@return table + local function row_to_str(row) + local row_strs = {} + for j = 1, #row.cells do + local cell = row.cells[j] + if cell.connector then + cell.symbol = cell.connector -- TODO: connector and symbol should not be duplicating data? + else + assert(cell.commit) + cell.symbol = commit_cell_symb(cell) end + row_strs[#row_strs + 1] = cell.symbol + end + -- return table.concat(row_strs) + return row_strs + end - if branch_index ~= moved_parent_branch_index then - -- Record the visual parent - nvisual_parents = nvisual_parents + 1 + ---@param row I.Row + ---@param row_idx integer + ---@return I.Highlight[] + local function row_to_highlights(row, row_idx) + local row_hls = {} + local offset = 1 -- WAS 0 + + for j = 1, #row.cells do + local cell = row.cells[j] + + local width = cell.symbol and vim.fn.strdisplaywidth(cell.symbol) or 1 + local start = offset + local stop = start + width + + offset = offset + width + + if cell.commit then + local hg = BRANCH_COLORS[(j % NUM_BRANCH_COLORS + 1)] + row_hls[#row_hls + 1] = { + hg = hg, + row = row_idx, + start = start, + stop = stop + } + elseif cell.symbol == GHOR then + -- take color from first right cell that attaches to this connector + for k = j + 1, #row.cells do + local rcell = row.cells[k] + + -- TODO: would be nice with a better way than this hacky method of + -- to figure out where our vertical branch is + local continuations = { + GCLD, + GCLU, + -- + GFORKD, + GFORKU, + -- + GLUDCD, + GLUDCU, + -- + GLRDCL, + GLRUCL, + } + + if rcell.commit and vim.tbl_contains(continuations, rcell.symbol) then + local hg = BRANCH_COLORS[(rcell.commit.j % NUM_BRANCH_COLORS + 1)] + row_hls[#row_hls + 1] = { + hg = hg, + row = row_idx, + start = start, + stop = stop + } + + break + end + end end end - -- Draw commit lines + return row_hls + end + + local width = 0 + for _, row in ipairs(graph) do + if #row.cells > width then + width = #row.cells + end + end - if branch_index <= ncommit_branches then - -- Update commit visual info + for idx = 1, #graph do + local proper_row = graph[idx] - ncommit_cols = ncommit_cols + 2 - ncommit_strings = ncommit_strings + 1 + local row_str_arr = {} - if is_commit then - -- Draw current commit + ---@param stuff table|string + local function add_to_row(stuff) + row_str_arr[#row_str_arr + 1] = stuff + end - commit_prefix[ncommit_strings] = current_commit_str - elseif merge_up then - -- Draw unrelated branch + local c = proper_row.commit + if c then + add_to_row(c.hash) -- Commit row + add_to_row(row_to_str(proper_row)) + else + local c = graph[idx - 1].commit + assert(c) + + local row = row_to_str(proper_row) + local valid = false + for _, char in ipairs(row) do + if char ~= " " and char ~= GVER then + valid = true + break + end + end - commit_prefix[ncommit_strings] = commit_branch_str + if valid then + add_to_row("") -- Connection Row else - -- Draw empty branch - - commit_prefix[ncommit_strings] = commit_empty_str + add_to_row("strip") -- Useless Connection Row end + + add_to_row(row) end - -- Update merge visual info + for _, hl in ipairs(row_to_highlights(proper_row, idx)) do + highlights[#highlights + 1] = hl + end - nmerge_strings = nmerge_strings + 1 + lines[#lines + 1] = row_str_arr + end - -- Draw merge lines + return lines, highlights + end - if is_complex then - -- Draw merge lines for complex merge + -- store stage 1 graph + -- + ---@param c I.Cell? + ---@return string? + local function hash(c) + return c and c.commit and c.commit.hash + end - merge_line[nmerge_strings] = complex_merge_str_1 - complex_merge_line[nmerge_strings] = complex_merge_str_2 - else - -- Draw non-complex merge lines + -- inserts vertical and horizontal pipes + for i = 2, #graph - 1 do + local row = graph[i] - -- Update merge info after drawing commit + ---@param cells I.Cell[] + local function count_emph(cells) + local n = 0 + for _, c in ipairs(cells) do + if c.commit and c.emphasis then + n = n + 1 + end + end + return n + end - merge_up = merge_up or is_commit or branch_index == moved_parent_branch_index - local merge_right = nmerges_left > 0 and nmerges_right > 0 + local num_emphasized = count_emph(graph[i].cells) - -- Draw left character + -- vertical connections + for j = 1, #row.cells, 2 do + local this = graph[i].cells[j] + local below = graph[i + 1].cells[j] - if branch_index > 1 then - if merge_left then - -- Draw left merge line - merge_line[nmerge_strings] = merge_left_right_str - else - -- No merge to left - -- Draw empty space - merge_line[nmerge_strings] = merge_empty_str - end - -- Complex merge line always has empty space here - complex_merge_line[nmerge_strings] = merge_empty_str + local tch, bch = hash(this), hash(below) - -- Update visual merge info + if not this.is_commit and not this.connector then + -- local ch = row.commit and row.commit.hash + -- local row_commit_is_child = ch and vim.tbl_contains(this.commit.children, ch) + -- local trivial_continuation = (not row_commit_is_child) and (new_columns < 1 or ach == tch or acc == GVER) + -- local trivial_continuation = (new_columns < 1 or ach == tch or acc == GVER) + local ignore_this = (num_emphasized > 1 and (this.emphasis or false)) - nmerge_strings = nmerge_strings + 1 - end + if not ignore_this and bch == tch then -- and trivial_continuation then + local has_repeats = false + local first_repeat = nil + for k = 1, #row.cells, 2 do + local cell_k, cell_j = row.cells[k], row.cells[j] + local rkc, rjc = + (not cell_k.connector and cell_k.commit), (not cell_j.connector and cell_j.commit) - -- Draw right character - - if merge_up then - if branch_hash then - if merge_left then - if merge_right then - if is_commit then - -- Merge up, down, left, right - merge_line[nmerge_strings] = merge_all_str - else - -- Jump over - merge_line[nmerge_strings] = merge_jump_str - end - else - -- Merge up, down, left - merge_line[nmerge_strings] = merge_up_down_left_str - end - else - if merge_right then - -- Merge up, down, right - merge_line[nmerge_strings] = merge_up_down_right_str - else - -- Merge up, down - merge_line[nmerge_strings] = merge_up_down_str - end - end - else - if merge_left then - if merge_right then - -- Merge up, left, right - merge_line[nmerge_strings] = merge_up_left_right_str - else - -- Merge up, left - merge_line[nmerge_strings] = merge_up_left_str - end - else - if merge_right then - -- Merge up, right - merge_line[nmerge_strings] = merge_up_right_str - else - -- Merge up - merge_line[nmerge_strings] = merge_up_str - end + -- local rkc, rjc = row.cells[k].commit, row.cells[j].commit + + if k ~= j and (rkc and rjc) and rkc.hash == rjc.hash then + has_repeats = true + first_repeat = k + break end end - else - if branch_hash then - if merge_left then - if merge_right then - -- Merge down, left, right - merge_line[nmerge_strings] = merge_down_left_right_str - else - -- Merge down, left - merge_line[nmerge_strings] = merge_down_left_str - end - else - if merge_right then - -- Merge down, right - merge_line[nmerge_strings] = merge_down_right_str - else - -- Merge down - -- Not possible to merge down only - error(graph_error) - end - end + + if not has_repeats then + local cell = graph[i].cells[j] + cell.connector = GVER else - if merge_left then - if merge_right then - -- Merge left, right - merge_line[nmerge_strings] = merge_left_right_str - else - -- Merge left - -- Not possible to merge left only - error(graph_error) - end - else - if merge_right then - -- Merge right - -- Not possible to merge right only - error(graph_error) - else - -- No merges - merge_line[nmerge_strings] = merge_empty_str - end + local k = first_repeat + local this_k = graph[i].cells[k] + local below_k = graph[i + 1].cells[k] + + local bkc, tkc = + (not below_k.connector and below_k.commit), (not this_k.connector and this_k.commit) + + -- local bkc, tkc = below_k.commit, this_k.commit + if (bkc and tkc) and bkc.hash == tkc.hash then + local cell = graph[i].cells[j] + cell.connector = GVER end end end + end + end - -- Draw complex right char + do + -- we expect number of rows to be odd always !! since the last + -- row is a commit row without a connector row following it + assert(#graph % 2 == 1) + local last_row = graph[#graph] + for j = 1, #last_row.cells, 2 do + local cell = last_row.cells[j] + if cell.commit and not cell.is_commit then + cell.connector = GVER + end + end + end - if branch_hash then - complex_merge_line[nmerge_strings] = merge_up_down_str - else - complex_merge_line[nmerge_strings] = merge_empty_str + -- horizontal connections + -- + -- a stopped connector is one that has a void cell below it + -- + local stopped = {} + for j = 1, #row.cells, 2 do + local this = graph[i].cells[j] + local below = graph[i + 1].cells[j] + if not this.connector and (not below or below.connector == " ") then + assert(this.commit) + stopped[#stopped + 1] = j + end + end + + -- now lets get the intervals between the stopped connetors + -- and other connectors of the same commit hash + local intervals = {} + for _, j in ipairs(stopped) do + local curr = 1 + for k = curr, j do + local cell_k, cell_j = row.cells[k], row.cells[j] + local rkc, rjc = (not cell_k.connector and cell_k.commit), (not cell_j.connector and cell_j.commit) + if (rkc and rjc) and (rkc.hash == rjc.hash) then + if k < j then + intervals[#intervals + 1] = { start = k, stop = j } + end + curr = j + break end end + end - -- Update visual missing parents info + -- add intervals for the connectors of merge children + -- these are where we have multiple connector commit hashes + -- for a single merge child, that is, more than one connector + -- + -- TODO: this method presented here is probably universal and covers + -- also for the previously computed intervals ... two birds one stone? + do + local low = #row.cells + local high = 1 + for j = 1, #row.cells, 2 do + local c = row.cells[j] + if c.emphasis then + if j > high then + high = j + end + if j < low then + low = j + end + end + end - nmissing_parents_strings = nmissing_parents_strings + 1 + if high > low then + intervals[#intervals + 1] = { start = low, stop = high } + end + end - -- Draw missing parents lines + if i % 2 == 0 then + for _, interval in ipairs(intervals) do + local a, b = interval.start, interval.stop + for j = a + 1, b - 1 do + local this = graph[i].cells[j] + if this.connector == " " then + this.connector = GHOR + end + end + end + end + end - if is_missing_parent then - missing_parents_line_1[nmissing_parents_strings] = missing_parent_str - missing_parents_line_2[nmissing_parents_strings] = missing_parent_empty_str - elseif branch_hash then - missing_parents_line_1[nmissing_parents_strings] = missing_parent_branch_str - missing_parents_line_2[nmissing_parents_strings] = missing_parent_branch_str - else - missing_parents_line_1[nmissing_parents_strings] = missing_parent_empty_str - missing_parents_line_2[nmissing_parents_strings] = missing_parent_empty_str + -- print '---- stage 2 -------' + + -- insert symbols on connector rows + -- + -- note that there are 8 possible connections + -- under the assumption that any connector cell + -- has at least 2 neighbors but no more than 3 + -- + -- there are 4 ways to make the connections of three neighbors + -- there are 6 ways to make the connections of two neighbors + -- however two of them are the vertical and horizontal connections + -- that have already been taken care of + -- + + local symb_map = { + -- two neighbors (no straights) + -- - 8421 + [10] = GCLU, -- '1010' + [9] = GCLD, -- '1001' + [6] = GCRU, -- '0110' + [5] = GCRD, -- '0101' + -- three neighbors + [14] = GLRU, -- '1110' + [13] = GLRD, -- '1101' + [11] = GLUD, -- '1011' + [7] = GRUD, -- '0111' + } + + for i = 2, #graph, 2 do + local row = graph[i] + local above = graph[i - 1] + local below = graph[i + 1] + + -- local is_bi_crossing = get_is_bi_crossing(graph, i) + + for j = 1, #row.cells, 2 do + local this = row.cells[j] + + if this.connector == GVER then + -- because they are already taken care of + goto continue end - -- Remove missing parent + local lc = row.cells[j - 1] + local rc = row.cells[j + 1] + local uc = above and above.cells[j] + local dc = below and below.cells[j] - if is_missing_parent and branch_index ~= moved_parent_branch_index then - -- Remove branch - branch_hashes[branch_index] = nil - branch_indexes[branch_hash] = nil + local l = lc and (lc.connector ~= " " or lc.commit) or false + local r = rc and (rc.connector ~= " " or rc.commit) or false + local u = uc and (uc.connector ~= " " or uc.commit) or false + local d = dc and (dc.connector ~= " " or dc.commit) or false - -- Trim trailing empty branches - while nbranches > 0 and not branch_hashes[nbranches] do - nbranches = nbranches - 1 + -- number of neighbors + local nn = 0 + + local symb_n = 0 + for i, b in ipairs { l, r, u, d } do + if b then + nn = nn + 1 + symb_n = symb_n + bit.lshift(1, 4 - i) end end - -- Increment + local symbol = symb_map[symb_n] or "?" - branch_index = branch_index + 1 - end + if i == #graph and symbol == "?" then + symbol = GVER + end + + local commit_dir_above = above.commit and above.commit.j == j + + ---@type 'l' | 'r' | nil -- placement of commit horizontally, only relevant if this is a connector row and if the cell is not immediately above or below the commit + local clh_above = nil + local commit_above = above.commit and above.commit.j ~= j + if commit_above then + clh_above = above.commit.j < j and "l" or "r" + end - -- Output + if clh_above and symbol == GLRD then + if clh_above == "l" then + symbol = GLRDCL -- '<' + elseif clh_above == "r" then + symbol = GLRDCR -- '>' + end + elseif symbol == GLRU then + -- because nothing else is possible with our + -- current implicit graph building rules? + symbol = GLRUCL -- '<' + end - -- Calculate whether certain lines should be outputted + local merge_dir_above = commit_dir_above and #above.commit.parents > 1 - local should_out_merge = ( - nparents > 1 - or moved_parent_branch_index - or (nparents == 0 and nbranches == 0) - or (nparents == 1 and branch_indexes[parents[1]] ~= commit_branch_index) - ) + if symbol == GLUD then + symbol = merge_dir_above and GLUDCU or GLUDCD + end - local should_out_complex = should_out_merge and ncomplex_merges > 0 - local should_out_missing_parents = nmissing_parents > 0 + if symbol == GRUD then + symbol = merge_dir_above and GRUDCU or GRUDCD + end - -- Initialize commit objects - -- local vim_commit_body = {} - local vim_commit_suffix = {} - local vim_commit_suffix_index = 1 + if nn == 4 then + symbol = merge_dir_above and GFORKD or GFORKU + end - vim_out[vim_out_index] = { text = table.concat(commit_prefix, ""), color = "Purple", oid = commit_hash } - vim_out_index = vim_out_index + 1 + if row.cells[j].commit then + row.cells[j].connector = symbol + end - -- Add merge lines - if should_out_merge then - vim_commit_suffix[vim_commit_suffix_index] = table.concat(merge_line, "") - vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + ::continue:: + -- + end + end - vim_out_index = vim_out_index + 1 - vim_commit_suffix_index = vim_commit_suffix_index + 1 + local lines, highlights = graph_to_lines(graph) - if should_out_complex then - vim_commit_suffix[vim_commit_suffix_index] = table.concat(complex_merge_line, "") - vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + -- Transform graph into what neogit needs to render + -- + local result = {} + local hl = {} + for _, highlight in ipairs(highlights) do + local row = highlight.row + if not hl[row] then + hl[row] = {} + end - vim_out_index = vim_out_index + 1 - vim_commit_suffix_index = vim_commit_suffix_index + 1 - end + for i = highlight.start, highlight.stop do + hl[row][i] = highlight end + end - -- Add missing parents lines - if should_out_missing_parents then - vim_commit_suffix[vim_commit_suffix_index] = table.concat(missing_parents_line_1, "") - vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } - vim_out_index = vim_out_index + 1 - vim_commit_suffix_index = vim_commit_suffix_index + 1 + for row, line in ipairs(lines) do + local graph_row = {} + local oid = line[1] + local parts = line[2] - vim_commit_suffix[vim_commit_suffix_index] = table.concat(missing_parents_line_2, "") - vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + for i, part in ipairs(parts) do + local current_highlight = hl[row][i] or {} - vim_out_index = vim_out_index + 1 - vim_commit_suffix_index = vim_commit_suffix_index + 1 + table.insert( + graph_row, + { + oid = oid ~= "" and oid, + text = part, + color = not color and "Purple" or current_highlight.hg, + } + ) end - end - local graph = {} - for _, line in ipairs(vim_out) do - local g = {} - for c in utf8_iter(line.text) do - table.insert(g, { text = c, color = line.color, oid = line.oid }) + if oid ~= "strip" then + table.insert(result, graph_row) end - table.insert(graph, g) end - return graph + return result end return M diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index 64ffa55ab..cd7588e69 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -64,8 +64,7 @@ function M.create() incompatible = { "reverse" }, dependant = { "color" }, }) - :switch_if( - config.values.graph_style == "ascii", + :switch( "c", "color", "Show graph in color", From d523ae3122cab555fabf240599b7eb743e1c6994 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 12:28:36 +0200 Subject: [PATCH 038/437] Add both flog style "unicode" graph and gitgraph style graph for kitty. --- lua/neogit/config.lua | 1 + lua/neogit/lib/git/log.lua | 4 +- lua/neogit/lib/{graph.lua => graph/kitty.lua} | 12 +- lua/neogit/lib/graph/unicode.lua | 560 ++++++++++++++++++ lua/neogit/popups/log/init.lua | 3 +- 5 files changed, 572 insertions(+), 8 deletions(-) rename lua/neogit/lib/{graph.lua => graph/kitty.lua} (98%) create mode 100644 lua/neogit/lib/graph/unicode.lua diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 8984da404..bb548a3f4 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -272,6 +272,7 @@ end ---@alias NeogitGraphStyle ---| "ascii" ---| "unicode" +---| "kitty" ---@class NeogitConfigStatusOptions ---@field recent_commit_count? integer The number of recent commits to display diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index efced7152..b822e304f 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -361,7 +361,9 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) local graph_output if graph then if config.values.graph_style == "unicode" then - graph_output = require("neogit.lib.graph").build(commits, graph_color) + graph_output = require("neogit.lib.graph.unicode").build(commits) + elseif config.values.graph_style == "kitty" then + graph_output = require("neogit.lib.graph.kitty").build(commits, graph_color) elseif config.values.graph_style == "ascii" then util.remove_item_from_table(options, "--show-signature") graph_output = M.graph(options, files, graph_color) diff --git a/lua/neogit/lib/graph.lua b/lua/neogit/lib/graph/kitty.lua similarity index 98% rename from lua/neogit/lib/graph.lua rename to lua/neogit/lib/graph/kitty.lua index 3de61afdf..ed0999941 100644 --- a/lua/neogit/lib/graph.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -804,7 +804,7 @@ function M.build(commits, color) offset = offset + width if cell.commit then - local hg = BRANCH_COLORS[(j % NUM_BRANCH_COLORS + 1)] + local hg = (cell.emphasis and "Bold" or "") .. BRANCH_COLORS[(j % NUM_BRANCH_COLORS + 1)] row_hls[#row_hls + 1] = { hg = hg, row = row_idx, @@ -833,7 +833,7 @@ function M.build(commits, color) } if rcell.commit and vim.tbl_contains(continuations, rcell.symbol) then - local hg = BRANCH_COLORS[(rcell.commit.j % NUM_BRANCH_COLORS + 1)] + local hg = (cell.emphasis and "Bold" or "") .. BRANCH_COLORS[(rcell.commit.j % NUM_BRANCH_COLORS + 1)] row_hls[#row_hls + 1] = { hg = hg, row = row_idx, @@ -885,7 +885,7 @@ function M.build(commits, color) end if valid then - add_to_row("") -- Connection Row + add_to_row("") -- Connection Row else add_to_row("strip") -- Useless Connection Row end @@ -1097,8 +1097,6 @@ function M.build(commits, color) local above = graph[i - 1] local below = graph[i + 1] - -- local is_bi_crossing = get_is_bi_crossing(graph, i) - for j = 1, #row.cells, 2 do local this = row.cells[j] @@ -1130,7 +1128,7 @@ function M.build(commits, color) local symbol = symb_map[symb_n] or "?" - if i == #graph and symbol == "?" then + if (i == #graph or i == #graph - 1) and symbol == "?" then symbol = GVER end @@ -1180,6 +1178,8 @@ function M.build(commits, color) local lines, highlights = graph_to_lines(graph) + -- + -- BEGIN NEOGIT COMPATIBILITY CODE -- Transform graph into what neogit needs to render -- local result = {} diff --git a/lua/neogit/lib/graph/unicode.lua b/lua/neogit/lib/graph/unicode.lua new file mode 100644 index 000000000..b2309b72a --- /dev/null +++ b/lua/neogit/lib/graph/unicode.lua @@ -0,0 +1,560 @@ +-- Modified version of graphing algorithm from https://github.com/rbong/vim-flog + +local M = {} + +local graph_error = "flog: internal error drawing graph" + +-- stylua: ignore start +local current_commit_str = "• " +local commit_branch_str = "│ " +local commit_empty_str = " " +local complex_merge_str_1 = "┬┊" +local complex_merge_str_2 = "╰┤" +local merge_all_str = "┼" +local merge_jump_str = "┊" +local merge_up_down_left_str = "┤" +local merge_up_down_right_str = "├" +local merge_up_down_str = "│" +local merge_up_left_right_str = "┴" +local merge_up_left_str = "╯" +local merge_up_right_str = "╰" +local merge_up_str = " " +local merge_down_left_right_str = "┬" +local merge_down_left_str = "╮" +local merge_down_right_str = "╭" +local merge_left_right_str = "─" +local merge_empty_str = " " +local missing_parent_str = "┊ " +local missing_parent_branch_str = "│ " +local missing_parent_empty_str = " " + +-- Returns an iterator for traversing UTF-8 encoded strings, yielding each +-- character as a substring. The iterator ensures correct handling of +-- multi-byte UTF-8 characters, decoding them and returning them as separate +-- characters. +-- +-- See also: +-- https://github.com/gijit/gi/blob/7052cfb07ca8b52afaa6c2a3deee53952784bd5d/pkg/utf8/utf8.lua#L80C1-L81C47 +-- +local function utf8_iter(s) + local i = 1 + return function() + local b = string.byte(s, i) + + if not b then + return nil -- string end + end + + -- {{{ + -- 00000000-01111111 00-7F 000-127 US-ASCII (single byte) + -- 10000000-10111111 80-BF 128-191 Second, third, or fourth byte of a multi-byte sequence + -- 11000000-11000001 C0-C1 192-193 Overlong encoding: start of a 2-byte sequence, but code point <= 127 + -- 11000010-11011111 C2-DF 194-223 Start of 2-byte sequence + -- 11100000-11101111 E0-EF 224-239 Start of 3-byte sequence + -- 11110000-11110100 F0-F4 240-244 Start of 4-byte sequence + -- 11110101-11110111 F5-F7 245-247 Restricted by RFC 3629: start of 4-byte sequence for codepoint above 10FFFF + -- 11111000-11111011 F8-FB 248-251 Restricted by RFC 3629: start of 5-byte sequence + -- 11111100-11111101 FC-FD 252-253 Restricted by RFC 3629: start of 6-byte sequence + -- 11111110-11111111 FE-FF 254-255 Invalid: not defined by original UTF-8 specification + -- }}} + local w = (b >= 192 and b <= 223 and 2) or + (b >= 224 and b <= 239 and 3) or + (b >= 240 and b <= 247 and 4) or 1 + + local c = string.sub(s, i, i + w - 1) + i = i + w + return c + end +end +-- stylua: ignore end + +function M.build(commits) + commits = require("neogit.lib.util").filter_map(commits, function(item) + if item.oid then + return item + end + end) + + -- Init commit parsing data + local commit_hashes = {} + for _, commit in ipairs(commits) do + commit_hashes[commit.oid] = 1 + end + + local vim_out = {} + local vim_out_index = 1 + + -- Init graph data + local branch_hashes = {} + local branch_indexes = {} + local nbranches = 0 + + -- Draw graph + for _, commit in ipairs(commits) do + -- Get commit data + local commit_hash = commit.oid + local parents = vim.split(commit.parent, " ") + local parent_hashes = {} + local nparents = #parents + + for _, parent in ipairs(parents) do + parent_hashes[parent] = 1 + end + + -- Init commit output + + -- The prefix that goes before the first commit line + local commit_prefix = {} + -- The number of strings in commit lines + local ncommit_strings = 0 + -- The merge line that goes after the commit + local merge_line = {} + -- The complex merge line that goes after the merge + local complex_merge_line = {} + -- The number of strings in merge lines + local nmerge_strings = 0 + -- The two lines indicating missing parents after the complex line + local missing_parents_line_1 = {} + local missing_parents_line_2 = {} + -- The number of strings in missing parent lines + local nmissing_parents_strings = 0 + + -- Init visual data + + -- The number of columns in the commit output + local ncommit_cols = 0 + -- The number of visual parents + local nvisual_parents = 0 + -- The number of complex merges (octopus) + local ncomplex_merges = 0 + -- The number of missing parents + local nmissing_parents = 0 + + -- Init graph data + + -- The number of passed merges + local nmerges_left = 0 + -- The number of upcoming merges (parents + commit) + local nmerges_right = nparents + 1 + -- The index of the commit branch + local commit_branch_index = branch_indexes[commit_hash] + -- The index of the moved parent branch (there is only one) + local moved_parent_branch_index = nil + -- The number of branches on the commit line + local ncommit_branches = nbranches + (commit_branch_index and 0 or 1) + + -- Init indexes + + -- The current branch + local branch_index = 1 + -- The current parent + local parent_index = 1 + + -- Find the first empty parent + while parent_index <= nparents and branch_indexes[parents[parent_index]] do + parent_index = parent_index + 1 + end + + -- Traverse old and new branches + + while branch_index <= nbranches or nmerges_right > 0 do + -- Get branch data + + local branch_hash = branch_hashes[branch_index] + local is_commit = branch_index == commit_branch_index + + -- Set merge info before updates + + local merge_up = branch_hash or moved_parent_branch_index == branch_index + local merge_left = nmerges_left > 0 and nmerges_right > 0 + local is_complex = false + local is_missing_parent = false + + -- Handle commit + + if not branch_hash and not commit_branch_index then + -- Found empty branch and commit does not have a branch + -- Add the commit in the empty spot + + commit_branch_index = branch_index + is_commit = true + end + + if is_commit then + -- Count commit merge + nmerges_right = nmerges_right - 1 + nmerges_left = nmerges_left + 1 + + if branch_hash then + -- End of branch + + -- Remove branch + branch_hashes[commit_branch_index] = nil + branch_indexes[commit_hash] = nil + + -- Trim trailing empty branches + while nbranches > 0 and not branch_hashes[nbranches] do + nbranches = nbranches - 1 + end + + -- Clear branch hash + branch_hash = nil + end + + if parent_index > nparents and nmerges_right == 1 then + -- There is only one remaining parent, to the right + -- Move it under the commit + + -- Find parent to right + parent_index = nparents + while (branch_indexes[parents[parent_index]] or -1) < branch_index do + parent_index = parent_index - 1 + end + + -- Get parent data + local parent_hash = parents[parent_index] + local parent_branch_index = branch_indexes[parent_hash] + + -- Remove old parent branch + branch_hashes[parent_branch_index] = nil + branch_indexes[parent_hash] = nil + + -- Trim trailing empty branches + while nbranches > 0 and not branch_hashes[nbranches] do + nbranches = nbranches - 1 + end + + -- Record the old index + moved_parent_branch_index = parent_branch_index + + -- Count upcoming moved parent as another merge + nmerges_right = nmerges_right + 1 + end + end + + -- Handle parents + + if not branch_hash and parent_index <= nparents then + -- New parent + + -- Get parent data + local parent_hash = parents[parent_index] + + -- Set branch to parent + branch_indexes[parent_hash] = branch_index + branch_hashes[branch_index] = parent_hash + + -- Update branch has + branch_hash = parent_hash + + -- Update the number of branches + if branch_index > nbranches then + nbranches = branch_index + end + + -- Jump to next available parent + parent_index = parent_index + 1 + while parent_index <= nparents and branch_indexes[parents[parent_index]] do + parent_index = parent_index + 1 + end + + -- Count new parent merge + nmerges_right = nmerges_right - 1 + nmerges_left = nmerges_left + 1 + + -- Determine if parent is missing + if branch_hash and not commit_hashes[parent_hash] then + is_missing_parent = true + nmissing_parents = nmissing_parents + 1 + end + + -- Record the visual parent + nvisual_parents = nvisual_parents + 1 + elseif + branch_index == moved_parent_branch_index or (nmerges_right > 0 and parent_hashes[branch_hash]) + then + -- Existing parents + + -- Count existing parent merge + nmerges_right = nmerges_right - 1 + nmerges_left = nmerges_left + 1 + + -- Determine if parent has a complex merge + is_complex = merge_left and nmerges_right > 0 + if is_complex then + ncomplex_merges = ncomplex_merges + 1 + end + + -- Determine if parent is missing + if branch_hash and not commit_hashes[branch_hash] then + is_missing_parent = true + nmissing_parents = nmissing_parents + 1 + end + + if branch_index ~= moved_parent_branch_index then + -- Record the visual parent + nvisual_parents = nvisual_parents + 1 + end + end + + -- Draw commit lines + + if branch_index <= ncommit_branches then + -- Update commit visual info + + ncommit_cols = ncommit_cols + 2 + ncommit_strings = ncommit_strings + 1 + + if is_commit then + -- Draw current commit + + commit_prefix[ncommit_strings] = current_commit_str + elseif merge_up then + -- Draw unrelated branch + + commit_prefix[ncommit_strings] = commit_branch_str + else + -- Draw empty branch + + commit_prefix[ncommit_strings] = commit_empty_str + end + end + + -- Update merge visual info + + nmerge_strings = nmerge_strings + 1 + + -- Draw merge lines + + if is_complex then + -- Draw merge lines for complex merge + + merge_line[nmerge_strings] = complex_merge_str_1 + complex_merge_line[nmerge_strings] = complex_merge_str_2 + else + -- Draw non-complex merge lines + + -- Update merge info after drawing commit + + merge_up = merge_up or is_commit or branch_index == moved_parent_branch_index + local merge_right = nmerges_left > 0 and nmerges_right > 0 + + -- Draw left character + + if branch_index > 1 then + if merge_left then + -- Draw left merge line + merge_line[nmerge_strings] = merge_left_right_str + else + -- No merge to left + -- Draw empty space + merge_line[nmerge_strings] = merge_empty_str + end + -- Complex merge line always has empty space here + complex_merge_line[nmerge_strings] = merge_empty_str + + -- Update visual merge info + + nmerge_strings = nmerge_strings + 1 + end + + -- Draw right character + + if merge_up then + if branch_hash then + if merge_left then + if merge_right then + if is_commit then + -- Merge up, down, left, right + merge_line[nmerge_strings] = merge_all_str + else + -- Jump over + merge_line[nmerge_strings] = merge_jump_str + end + else + -- Merge up, down, left + merge_line[nmerge_strings] = merge_up_down_left_str + end + else + if merge_right then + -- Merge up, down, right + merge_line[nmerge_strings] = merge_up_down_right_str + else + -- Merge up, down + merge_line[nmerge_strings] = merge_up_down_str + end + end + else + if merge_left then + if merge_right then + -- Merge up, left, right + merge_line[nmerge_strings] = merge_up_left_right_str + else + -- Merge up, left + merge_line[nmerge_strings] = merge_up_left_str + end + else + if merge_right then + -- Merge up, right + merge_line[nmerge_strings] = merge_up_right_str + else + -- Merge up + merge_line[nmerge_strings] = merge_up_str + end + end + end + else + if branch_hash then + if merge_left then + if merge_right then + -- Merge down, left, right + merge_line[nmerge_strings] = merge_down_left_right_str + else + -- Merge down, left + merge_line[nmerge_strings] = merge_down_left_str + end + else + if merge_right then + -- Merge down, right + merge_line[nmerge_strings] = merge_down_right_str + else + -- Merge down + -- Not possible to merge down only + error(graph_error) + end + end + else + if merge_left then + if merge_right then + -- Merge left, right + merge_line[nmerge_strings] = merge_left_right_str + else + -- Merge left + -- Not possible to merge left only + error(graph_error) + end + else + if merge_right then + -- Merge right + -- Not possible to merge right only + error(graph_error) + else + -- No merges + merge_line[nmerge_strings] = merge_empty_str + end + end + end + end + + -- Draw complex right char + + if branch_hash then + complex_merge_line[nmerge_strings] = merge_up_down_str + else + complex_merge_line[nmerge_strings] = merge_empty_str + end + end + + -- Update visual missing parents info + + nmissing_parents_strings = nmissing_parents_strings + 1 + + -- Draw missing parents lines + + if is_missing_parent then + missing_parents_line_1[nmissing_parents_strings] = missing_parent_str + missing_parents_line_2[nmissing_parents_strings] = missing_parent_empty_str + elseif branch_hash then + missing_parents_line_1[nmissing_parents_strings] = missing_parent_branch_str + missing_parents_line_2[nmissing_parents_strings] = missing_parent_branch_str + else + missing_parents_line_1[nmissing_parents_strings] = missing_parent_empty_str + missing_parents_line_2[nmissing_parents_strings] = missing_parent_empty_str + end + + -- Remove missing parent + + if is_missing_parent and branch_index ~= moved_parent_branch_index then + -- Remove branch + branch_hashes[branch_index] = nil + branch_indexes[branch_hash] = nil + + -- Trim trailing empty branches + while nbranches > 0 and not branch_hashes[nbranches] do + nbranches = nbranches - 1 + end + end + + -- Increment + + branch_index = branch_index + 1 + end + + -- Output + + -- Calculate whether certain lines should be outputted + + local should_out_merge = ( + nparents > 1 + or moved_parent_branch_index + or (nparents == 0 and nbranches == 0) + or (nparents == 1 and branch_indexes[parents[1]] ~= commit_branch_index) + ) + + local should_out_complex = should_out_merge and ncomplex_merges > 0 + local should_out_missing_parents = nmissing_parents > 0 + + -- Initialize commit objects + -- local vim_commit_body = {} + local vim_commit_suffix = {} + local vim_commit_suffix_index = 1 + + vim_out[vim_out_index] = { text = table.concat(commit_prefix, ""), color = "Purple", oid = commit_hash } + vim_out_index = vim_out_index + 1 + + -- Add merge lines + if should_out_merge then + vim_commit_suffix[vim_commit_suffix_index] = table.concat(merge_line, "") + vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + + vim_out_index = vim_out_index + 1 + vim_commit_suffix_index = vim_commit_suffix_index + 1 + + if should_out_complex then + vim_commit_suffix[vim_commit_suffix_index] = table.concat(complex_merge_line, "") + vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + + vim_out_index = vim_out_index + 1 + vim_commit_suffix_index = vim_commit_suffix_index + 1 + end + end + + -- Add missing parents lines + if should_out_missing_parents then + vim_commit_suffix[vim_commit_suffix_index] = table.concat(missing_parents_line_1, "") + vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + + vim_out_index = vim_out_index + 1 + vim_commit_suffix_index = vim_commit_suffix_index + 1 + + vim_commit_suffix[vim_commit_suffix_index] = table.concat(missing_parents_line_2, "") + vim_out[vim_out_index] = { text = vim_commit_suffix[vim_commit_suffix_index], color = "Purple" } + + vim_out_index = vim_out_index + 1 + vim_commit_suffix_index = vim_commit_suffix_index + 1 + end + end + + local graph = {} + for _, line in ipairs(vim_out) do + local g = {} + for c in utf8_iter(line.text) do + table.insert(g, { text = c, color = line.color, oid = line.oid }) + end + table.insert(graph, g) + end + + return graph +end + +return M diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index cd7588e69..7d896c2ea 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -64,7 +64,8 @@ function M.create() incompatible = { "reverse" }, dependant = { "color" }, }) - :switch( + :switch_if( + config.values.graph_style == "ascii" or config.values.graph_style == "kitty", "c", "color", "Show graph in color", From 552f918cbeec9afaddfdf3de8feea000494fe046 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 12:44:26 +0200 Subject: [PATCH 039/437] Fix: when using rebase->reword --- lua/neogit/lib/git/rebase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 2493597c4..f17b48144 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -224,7 +224,7 @@ function M.update_rebase_status(state) if done:exists() then for line in done:iter() do if line:match("^[^#]") and line ~= "" then - local oid = line:match("^%w+ (%x+)") + local oid = line:match("^%w+ (%x+)") or line:match("^fixup %-C (%x+)") table.insert(state.rebase.items, { action = line:match("^(%w+) "), oid = oid, From 0fb268cb7083d74d02c0af0fe5812bc15e6cb0bf Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 12:51:42 +0200 Subject: [PATCH 040/437] lint --- lua/neogit/lib/graph/kitty.lua | 239 +++++++++++++++------------------ 1 file changed, 111 insertions(+), 128 deletions(-) diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index ed0999941..93de089d2 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -255,9 +255,9 @@ local function resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_r -- B A ⓚ │ -- a A ⓶─────────╯ -- A ⓚ - local prev_prev_row = prev_connector_row -- graph[#graph - 2] + local prev_prev_row = prev_connector_row -- graph[#graph - 2] local prev_prev_prev_row = prev_commit_row -- graph[#graph - 3] - assert(prev_prev_row and prev_prev_prev_row) + assert(prev_prev_row and prev_prev_prev_row, "assertion failed") do local start_voiding = false local ctr = 0 @@ -335,46 +335,46 @@ end ---@field start integer ---@field stop integer -local sym = { - merge_commit = "", - commit = "", +local sym = { + merge_commit = "", + commit = "", merge_commit_end = "", - commit_end = "", - GVER = "", - GHOR = "", - GCLD = "", - GCRD = "╭", - GCLU = "", - GCRU = "", - GLRU = "", - GLRD = "", - GLUD = "", - GRUD = "", - GFORKU = "", - GFORKD = "", - GRUDCD = "", - GRUDCU = "", - GLUDCD = "", - GLUDCU = "", - GLRDCL = "", - GLRDCR = "", - GLRUCL = "", - GLRUCR = "", + commit_end = "", + GVER = "", + GHOR = "", + GCLD = "", + GCRD = "╭", + GCLU = "", + GCRU = "", + GLRU = "", + GLRD = "", + GLUD = "", + GRUD = "", + GFORKU = "", + GFORKD = "", + GRUDCD = "", + GRUDCU = "", + GLUDCD = "", + GLUDCU = "", + GLRDCL = "", + GLRDCR = "", + GLRUCL = "", + GLRUCR = "", } -local BRANCH_COLORS = { +local BRANCH_COLORS = { "Red", "Yellow", "Green", "Cyan", "Blue", "Purple", - "Orange" + "Orange", } local NUM_BRANCH_COLORS = #BRANCH_COLORS -local util = require("neogit.lib.util") +local util = require("neogit.lib.util") ---@param commits CommitLogEntry[] ---@param color boolean? @@ -401,7 +401,7 @@ function M.build(commits, color) local GLRDCL = sym.GLRDCL local GLRDCR = sym.GLRDCR local GLRUCL = sym.GLRUCL - local GLRUCR = sym.GLRUCR + -- local GLRUCR = sym.GLRUCR local GRCM = sym.commit local GMCM = sym.merge_commit @@ -416,7 +416,7 @@ function M.build(commits, color) tags = {}, author_date = item.author_date, hash = item.oid, - parents = vim.split(item.parent, " ") + parents = vim.split(item.parent, " "), } end end) @@ -475,9 +475,10 @@ function M.build(commits, color) local new_cells = {} for _, cell in ipairs(cells) do if cell.connector then - new_cells[#new_cells + 1] = { connector = " " } + -- new_cells[#new_cells + 1] = { connector = " " } + new_cells[#new_cells + 1] = { connector = cell.connector } elseif cell.commit then - assert(cell.commit) + assert(cell.commit, "assertion failed") new_cells[#new_cells + 1] = { commit = cell.commit } else new_cells[#new_cells + 1] = { connector = " " } @@ -622,7 +623,7 @@ function M.build(commits, color) -- add parents if next_p_idx then - assert(tracker) + assert(tracker, "assertion failed") -- if next commit is our parent then we do some complex logic if #curr_commit.parents == 1 then -- simply place parent at our location @@ -671,20 +672,11 @@ function M.build(commits, color) local connector_row = { cells = connector_cells } ---@type I.Row -- handle bi-connector rows - local is_bi_crossing, bi_crossing_safely_resolveable = get_is_bi_crossing( - commit_row, - connector_row, - next_commit - ) + local is_bi_crossing, bi_crossing_safely_resolveable = + get_is_bi_crossing(commit_row, connector_row, next_commit) if is_bi_crossing and bi_crossing_safely_resolveable and next_commit then - resolve_bi_crossing( - prev_commit_row, - prev_connector_row, - commit_row, - connector_row, - next_commit - ) + resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_row, connector_row, next_commit) end return connector_row @@ -758,7 +750,7 @@ function M.build(commits, color) ---@param cell I.Cell ---@return string local function commit_cell_symb(cell) - assert(cell.is_commit) + assert(cell.is_commit, "assertion failed") if #cell.commit.parents > 1 then -- merge commit @@ -778,7 +770,7 @@ function M.build(commits, color) if cell.connector then cell.symbol = cell.connector -- TODO: connector and symbol should not be duplicating data? else - assert(cell.commit) + assert(cell.commit, "assertion failed") cell.symbol = commit_cell_symb(cell) end row_strs[#row_strs + 1] = cell.symbol @@ -809,7 +801,7 @@ function M.build(commits, color) hg = hg, row = row_idx, start = start, - stop = stop + stop = stop, } elseif cell.symbol == GHOR then -- take color from first right cell that attaches to this connector @@ -833,12 +825,13 @@ function M.build(commits, color) } if rcell.commit and vim.tbl_contains(continuations, rcell.symbol) then - local hg = (cell.emphasis and "Bold" or "") .. BRANCH_COLORS[(rcell.commit.j % NUM_BRANCH_COLORS + 1)] + local hg = (cell.emphasis and "Bold" or "") + .. BRANCH_COLORS[(rcell.commit.j % NUM_BRANCH_COLORS + 1)] row_hls[#row_hls + 1] = { hg = hg, row = row_idx, start = start, - stop = stop + stop = stop, } break @@ -873,7 +866,7 @@ function M.build(commits, color) add_to_row(row_to_str(proper_row)) else local c = graph[idx - 1].commit - assert(c) + assert(c, "assertion failed") local row = row_to_str(proper_row) local valid = false @@ -885,7 +878,7 @@ function M.build(commits, color) end if valid then - add_to_row("") -- Connection Row + add_to_row("") -- Connection Row else add_to_row("strip") -- Useless Connection Row end @@ -983,7 +976,7 @@ function M.build(commits, color) do -- we expect number of rows to be odd always !! since the last -- row is a commit row without a connector row following it - assert(#graph % 2 == 1) + assert(#graph % 2 == 1, "assertion failed") local last_row = graph[#graph] for j = 1, #last_row.cells, 2 do local cell = last_row.cells[j] @@ -1002,7 +995,7 @@ function M.build(commits, color) local this = graph[i].cells[j] local below = graph[i + 1].cells[j] if not this.connector and (not below or below.connector == " ") then - assert(this.commit) + assert(this.commit, "assertion failed") stopped[#stopped + 1] = j end end @@ -1082,14 +1075,14 @@ function M.build(commits, color) -- two neighbors (no straights) -- - 8421 [10] = GCLU, -- '1010' - [9] = GCLD, -- '1001' - [6] = GCRU, -- '0110' - [5] = GCRD, -- '0101' + [9] = GCLD, -- '1001' + [6] = GCRU, -- '0110' + [5] = GCRD, -- '0101' -- three neighbors [14] = GLRU, -- '1110' [13] = GLRD, -- '1101' [11] = GLUD, -- '1011' - [7] = GRUD, -- '0111' + [7] = GRUD, -- '0111' } for i = 2, #graph, 2 do @@ -1100,79 +1093,73 @@ function M.build(commits, color) for j = 1, #row.cells, 2 do local this = row.cells[j] - if this.connector == GVER then - -- because they are already taken care of - goto continue - end - - local lc = row.cells[j - 1] - local rc = row.cells[j + 1] - local uc = above and above.cells[j] - local dc = below and below.cells[j] - - local l = lc and (lc.connector ~= " " or lc.commit) or false - local r = rc and (rc.connector ~= " " or rc.commit) or false - local u = uc and (uc.connector ~= " " or uc.commit) or false - local d = dc and (dc.connector ~= " " or dc.commit) or false - - -- number of neighbors - local nn = 0 - - local symb_n = 0 - for i, b in ipairs { l, r, u, d } do - if b then - nn = nn + 1 - symb_n = symb_n + bit.lshift(1, 4 - i) + if this.connector ~= GVER then + local lc = row.cells[j - 1] + local rc = row.cells[j + 1] + local uc = above and above.cells[j] + local dc = below and below.cells[j] + + local l = lc and (lc.connector ~= " " or lc.commit) or false + local r = rc and (rc.connector ~= " " or rc.commit) or false + local u = uc and (uc.connector ~= " " or uc.commit) or false + local d = dc and (dc.connector ~= " " or dc.commit) or false + + -- number of neighbors + local nn = 0 + + local symb_n = 0 + for i, b in ipairs { l, r, u, d } do + if b then + nn = nn + 1 + symb_n = symb_n + bit.lshift(1, 4 - i) + end end - end - local symbol = symb_map[symb_n] or "?" + local symbol = symb_map[symb_n] or "?" - if (i == #graph or i == #graph - 1) and symbol == "?" then - symbol = GVER - end + if (i == #graph or i == #graph - 1) and symbol == "?" then + symbol = GVER + end - local commit_dir_above = above.commit and above.commit.j == j + local commit_dir_above = above.commit and above.commit.j == j - ---@type 'l' | 'r' | nil -- placement of commit horizontally, only relevant if this is a connector row and if the cell is not immediately above or below the commit - local clh_above = nil - local commit_above = above.commit and above.commit.j ~= j - if commit_above then - clh_above = above.commit.j < j and "l" or "r" - end + ---@type 'l' | 'r' | nil -- placement of commit horizontally, only relevant if this is a connector row and if the cell is not immediately above or below the commit + local clh_above = nil + local commit_above = above.commit and above.commit.j ~= j + if commit_above then + clh_above = above.commit.j < j and "l" or "r" + end - if clh_above and symbol == GLRD then - if clh_above == "l" then - symbol = GLRDCL -- '<' - elseif clh_above == "r" then - symbol = GLRDCR -- '>' + if clh_above and symbol == GLRD then + if clh_above == "l" then + symbol = GLRDCL -- '<' + elseif clh_above == "r" then + symbol = GLRDCR -- '>' + end + elseif symbol == GLRU then + -- because nothing else is possible with our + -- current implicit graph building rules? + symbol = GLRUCL -- '<' end - elseif symbol == GLRU then - -- because nothing else is possible with our - -- current implicit graph building rules? - symbol = GLRUCL -- '<' - end - local merge_dir_above = commit_dir_above and #above.commit.parents > 1 + local merge_dir_above = commit_dir_above and #above.commit.parents > 1 - if symbol == GLUD then - symbol = merge_dir_above and GLUDCU or GLUDCD - end + if symbol == GLUD then + symbol = merge_dir_above and GLUDCU or GLUDCD + end - if symbol == GRUD then - symbol = merge_dir_above and GRUDCU or GRUDCD - end + if symbol == GRUD then + symbol = merge_dir_above and GRUDCU or GRUDCD + end - if nn == 4 then - symbol = merge_dir_above and GFORKD or GFORKU - end + if nn == 4 then + symbol = merge_dir_above and GFORKD or GFORKU + end - if row.cells[j].commit then - row.cells[j].connector = symbol + if row.cells[j].commit then + row.cells[j].connector = symbol + end end - - ::continue:: - -- end end @@ -1195,7 +1182,6 @@ function M.build(commits, color) end end - for row, line in ipairs(lines) do local graph_row = {} local oid = line[1] @@ -1204,14 +1190,11 @@ function M.build(commits, color) for i, part in ipairs(parts) do local current_highlight = hl[row][i] or {} - table.insert( - graph_row, - { - oid = oid ~= "" and oid, - text = part, - color = not color and "Purple" or current_highlight.hg, - } - ) + table.insert(graph_row, { + oid = oid ~= "" and oid, + text = part, + color = not color and "Purple" or current_highlight.hg, + }) end if oid ~= "strip" then From 2347e703df1a704e3e0cbc4ff71924983febfd63 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 13:02:53 +0200 Subject: [PATCH 041/437] Improve type --- lua/neogit/lib/git/log.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b822e304f..c1b82fccc 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -21,6 +21,8 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)" ---@field committer_date string when the committer committed ---@field description string a list of lines ---@field commit_arg string the passed argument of the git command +---@field subject string +---@field parent string ---@field diffs any[] ---Parses the provided list of lines into a CommitLogEntry From 1cf14aa197883c23108423635f08dc3227335539 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 13:03:27 +0200 Subject: [PATCH 042/437] spelling --- lua/neogit/lib/graph/kitty.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index 93de089d2..d88b57199 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -156,7 +156,7 @@ local function get_is_bi_crossing(commit_row, connector_row, next_commit) -- unsafe if starts of intervals overlap and are equal to direct parent location local safe = not (emi.start == coi.start and prev.j == emi.start) - -- return earily when connector interval is trivial + -- return early when connector interval is trivial if coi.start == coi.stop then return false, safe end @@ -307,7 +307,7 @@ end ---@class I.Row ---@field cells I.Cell[] ----@field commit I.Commit? -- there's a single comit for every even row +---@field commit I.Commit? -- there's a single commit for every even row ---@class I.Cell ---@field is_commit boolean? -- when true this cell is a real commit From de8a0db074dfbf82c4ca3b29d79f37a8fcb03854 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 13:41:41 +0200 Subject: [PATCH 043/437] Limit colors for better reading --- lua/neogit/lib/graph/kitty.lua | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index d88b57199..57e15a86d 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -255,7 +255,7 @@ local function resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_r -- B A ⓚ │ -- a A ⓶─────────╯ -- A ⓚ - local prev_prev_row = prev_connector_row -- graph[#graph - 2] + local prev_prev_row = prev_connector_row -- graph[#graph - 2] local prev_prev_prev_row = prev_commit_row -- graph[#graph - 3] assert(prev_prev_row and prev_prev_prev_row, "assertion failed") do @@ -365,11 +365,9 @@ local sym = { local BRANCH_COLORS = { "Red", "Yellow", - "Green", - "Cyan", "Blue", "Purple", - "Orange", + "Cyan", } local NUM_BRANCH_COLORS = #BRANCH_COLORS @@ -878,7 +876,7 @@ function M.build(commits, color) end if valid then - add_to_row("") -- Connection Row + add_to_row("") -- Connection Row else add_to_row("strip") -- Useless Connection Row end @@ -1075,14 +1073,14 @@ function M.build(commits, color) -- two neighbors (no straights) -- - 8421 [10] = GCLU, -- '1010' - [9] = GCLD, -- '1001' - [6] = GCRU, -- '0110' - [5] = GCRD, -- '0101' + [9] = GCLD, -- '1001' + [6] = GCRU, -- '0110' + [5] = GCRD, -- '0101' -- three neighbors [14] = GLRU, -- '1110' [13] = GLRD, -- '1101' [11] = GLUD, -- '1011' - [7] = GRUD, -- '0111' + [7] = GRUD, -- '0111' } for i = 2, #graph, 2 do From 29531998d44f7923d893f33e016ce9a0dc890c7c Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 14:54:58 +0200 Subject: [PATCH 044/437] Lift CLI calls out of component and into action. Components should _not_ be doing any queries, only rendering the props passed in --- .../buffers/commit_select_view/init.lua | 7 +++++-- lua/neogit/buffers/commit_select_view/ui.lua | 7 ++++--- lua/neogit/buffers/common.lua | 16 ++++++++------ lua/neogit/buffers/log_view/init.lua | 7 +++++-- lua/neogit/buffers/log_view/ui.lua | 7 ++++--- lua/neogit/lib/graph/kitty.lua | 12 +++++------ lua/neogit/popups/cherry_pick/actions.lua | 1 + lua/neogit/popups/commit/actions.lua | 6 ++++-- lua/neogit/popups/log/actions.lua | 21 ++++++++++++------- lua/neogit/popups/rebase/actions.lua | 3 ++- lua/neogit/popups/revert/actions.lua | 1 + 11 files changed, 56 insertions(+), 32 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 802a30b66..006207319 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -7,17 +7,20 @@ local status_maps = require("neogit.config").get_reversed_status_maps() ---@class CommitSelectViewBuffer ---@field commits CommitLogEntry[] +---@field remotes string[] ---@field header string|nil local M = {} M.__index = M ---Opens a popup for selecting a commit ---@param commits CommitLogEntry[]|nil +---@param remotes string[] ---@param header? string ---@return CommitSelectViewBuffer -function M.new(commits, header) +function M.new(commits, remotes, header) local instance = { commits = commits, + remotes = remotes, header = header, buffer = nil, } @@ -114,7 +117,7 @@ function M:open(action) end end, render = function() - return ui.View(self.commits) + return ui.View(self.commits, self.remotes) end, } end diff --git a/lua/neogit/buffers/commit_select_view/ui.lua b/lua/neogit/buffers/commit_select_view/ui.lua index 8d5188d38..71841f8e7 100644 --- a/lua/neogit/buffers/commit_select_view/ui.lua +++ b/lua/neogit/buffers/commit_select_view/ui.lua @@ -6,13 +6,14 @@ local Graph = require("neogit.buffers.common").CommitGraph local M = {} ---@param commits CommitLogEntry[] +---@param remotes string[] ---@return table -function M.View(commits) +function M.View(commits, remotes) return util.filter_map(commits, function(commit) if commit.oid then - return Commit(commit, { graph = true, decorate = true }) + return Commit(commit, remotes, { graph = true, decorate = true }) else - return Graph(commit) + return Graph(commit, #commits[1].abbreviated_commit + 1) end end) end diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index f13c4e4de..cb13937e6 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -137,14 +137,18 @@ local highlight_for_signature = { N = "NeogitSignatureNone", } -M.CommitEntry = Component.new(function(commit, args) +---@param commit CommitLogEntry +---@param remotes string[] +---@param args table +M.CommitEntry = Component.new(function(commit, remotes, args) local ref = {} local ref_last = {} - - local info = git.log.branch_info(commit.ref_name, git.remote.list()) + local info = { head = nil, locals = {}, remotes = {}, tags = {} } -- Parse out ref names if args.decorate and commit.ref_name ~= "" then + info = git.log.branch_info(commit.ref_name, remotes) + -- Render local only branches first for name, _ in pairs(info.locals) do if name:match("^refs/") then @@ -189,7 +193,7 @@ M.CommitEntry = Component.new(function(commit, args) local details if args.details then - details = col.padding_left(git.log.abbreviated_size() + 1) { + details = col.padding_left(#commit.abbreviated_commit + 1) { row(util.merge(graph, { text(" "), text("Author: ", { highlight = "NeogitSubtleText" }), @@ -267,8 +271,8 @@ M.CommitEntry = Component.new(function(commit, args) }, { oid = commit.oid, foldable = args.details == true, folded = true, remote = info.remotes[1] }) end) -M.CommitGraph = Component.new(function(commit, _) - return col.tag("graph").padding_left(git.log.abbreviated_size() + 1) { row(build_graph(commit.graph)) } +M.CommitGraph = Component.new(function(commit, padding) + return col.tag("graph").padding_left(padding) { row(build_graph(commit.graph)) } end) M.Grid = Component.new(function(props) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 63ae984fd..6004e0cab 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -9,6 +9,7 @@ local a = require("plenary.async") ---@class LogViewBuffer ---@field commits CommitLogEntry[] +---@field remotes string[] ---@field internal_args table ---@field files string[] ---@field buffer Buffer @@ -24,11 +25,13 @@ M.__index = M ---@param files string[]|nil list of files to filter by ---@param fetch_func fun(offset: number): CommitLogEntry[] ---@param header string +---@param remotes string[] ---@return LogViewBuffer -function M.new(commits, internal_args, files, fetch_func, header) +function M.new(commits, internal_args, files, fetch_func, header, remotes) local instance = { files = files, commits = commits, + remotes = remotes, internal_args = internal_args, fetch_func = fetch_func, buffer = nil, @@ -289,7 +292,7 @@ function M:open() }, }, render = function() - return ui.View(self.commits, self.internal_args) + return ui.View(self.commits, self.remotes, self.internal_args) end, after = function(buffer) -- First line is empty, so move cursor to second line. diff --git a/lua/neogit/buffers/log_view/ui.lua b/lua/neogit/buffers/log_view/ui.lua index 131f5b8c8..e4f8e129f 100644 --- a/lua/neogit/buffers/log_view/ui.lua +++ b/lua/neogit/buffers/log_view/ui.lua @@ -11,16 +11,17 @@ local row = Ui.row local M = {} ---@param commits CommitLogEntry[] +---@param remotes string[] ---@param args table ---@return table -function M.View(commits, args) +function M.View(commits, remotes, args) args.details = true local graph = util.filter_map(commits, function(commit) if commit.oid then - return Commit(commit, args) + return Commit(commit, remotes, args) elseif args.graph then - return Graph(commit) + return Graph(commit, #commits[1].abbreviated_commit + 1) end end) diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index 57e15a86d..fa5ab6ca6 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -255,7 +255,7 @@ local function resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_r -- B A ⓚ │ -- a A ⓶─────────╯ -- A ⓚ - local prev_prev_row = prev_connector_row -- graph[#graph - 2] + local prev_prev_row = prev_connector_row -- graph[#graph - 2] local prev_prev_prev_row = prev_commit_row -- graph[#graph - 3] assert(prev_prev_row and prev_prev_prev_row, "assertion failed") do @@ -876,7 +876,7 @@ function M.build(commits, color) end if valid then - add_to_row("") -- Connection Row + add_to_row("") -- Connection Row else add_to_row("strip") -- Useless Connection Row end @@ -1073,14 +1073,14 @@ function M.build(commits, color) -- two neighbors (no straights) -- - 8421 [10] = GCLU, -- '1010' - [9] = GCLD, -- '1001' - [6] = GCRU, -- '0110' - [5] = GCRD, -- '0101' + [9] = GCLD, -- '1001' + [6] = GCRU, -- '0110' + [5] = GCRD, -- '0101' -- three neighbors [14] = GLRU, -- '1110' [13] = GLRD, -- '1101' [11] = GLUD, -- '1011' - [7] = GRUD, -- '0111' + [7] = GRUD, -- '0111' } for i = 2, #graph, 2 do diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index 28a7ea833..f44d77f74 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -13,6 +13,7 @@ local function get_commits(popup) else commits = CommitSelectViewBuffer.new( git.log.list { "--max-count=256" }, + git.remote.list(), "Select one or more commits to cherry pick with , or to abort" ):open_async() end diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 9d188830e..d2bb9b0f9 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -50,7 +50,8 @@ local function commit_special(popup, method, opts) end end - local commit = popup.state.env.commit or CommitSelectViewBuffer.new(git.log.list()):open_async()[1] + local commit = popup.state.env.commit + or CommitSelectViewBuffer.new(git.log.list(), git.remote.list()):open_async()[1] if not commit then return end @@ -69,7 +70,7 @@ local function commit_special(popup, method, opts) if choice == "c" then opts.rebase = false elseif choice == "s" then - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] + commit = CommitSelectViewBuffer.new(git.log.list(), git.remote.list()):open_async()[1] else return end @@ -173,6 +174,7 @@ function M.absorb(popup) local commit = popup.state.env.commit or CommitSelectViewBuffer.new( git.log.list { "HEAD" }, + git.remote.list(), "Select a base commit for the absorb stack with , or to abort" ) :open_async()[1] diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 5a0c87268..a70bd35b1 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -39,7 +39,8 @@ function M.log_current(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, {}), - "Commits in " .. git.branch.current() + "Commits in " .. git.branch.current(), + git.remote.list() ):open() end @@ -50,7 +51,8 @@ function M.log_related(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in " .. table.concat(flags, ", ") + "Commits in " .. table.concat(flags, ", "), + git.remote.list() ):open() end @@ -61,7 +63,8 @@ function M.log_head(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in HEAD" + "Commits in HEAD", + git.remote.list() ):open() end @@ -72,7 +75,8 @@ function M.log_local_branches(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in --branches" + "Commits in --branches", + git.remote.list() ):open() end @@ -86,7 +90,8 @@ function M.log_other(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in " .. branch + "Commits in " .. branch, + git.remote.list() ):open() end end @@ -98,7 +103,8 @@ function M.log_all_branches(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in --branches --remotes" + "Commits in --branches --remotes", + git.remote.list() ):open() end @@ -109,7 +115,8 @@ function M.log_all_references(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, flags), - "Commits in --all" + "Commits in --all", + git.remote.list() ):open() end diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index dd1fcb0b2..466d35359 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -9,7 +9,7 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} local function base_commit(popup, list, header) - return popup.state.env.commit or CommitSelectViewBuffer.new(list, header):open_async()[1] + return popup.state.env.commit or CommitSelectViewBuffer.new(list, git.remote.list(), header):open_async()[1] end function M.onto_base(popup) @@ -136,6 +136,7 @@ function M.subset(popup) else start = CommitSelectViewBuffer.new( git.log.list { "HEAD" }, + git.remote.list(), "Select a commit with to rebase it and commits above it onto " .. newbase .. ", or to abort" ) :open_async()[1] diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index bf0a2638a..93057766a 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -14,6 +14,7 @@ local function get_commits(popup) else commits = CommitSelectViewBuffer.new( git.log.list { "--max-count=256" }, + git.remote.list(), "Select one or more commits to revert with , or to abort" ):open_async() end From d13d18b1708702d3587658e501b975a05dc5d7a7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 15:15:12 +0200 Subject: [PATCH 045/437] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 210c5b57c..ab4d98cfa 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,9 @@ Neogit follows semantic versioning. See [CONTRIBUTING.md](https://github.com/NeogitOrg/neogit/blob/master/CONTRIBUTING.md) for more details. -## Credit +## Special Thanks + +[kolja](https://github.com/kolja) for the Neogit Logo +[gitgraph.nvim](https://github.com/isakbm/gitgraph.nvim) for the "kitty" git graph renderer +[vim-flog](https://github.com/rbong/vim-flog) for the "unicode" git graph renderer -Thank you to [kolja](https://github.com/kolja) for the Neogit Logo From 20f84056c8ae570cc2997b80ec85afa17eae1b2b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 15:15:43 +0200 Subject: [PATCH 046/437] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab4d98cfa..3b84ce9d6 100644 --- a/README.md +++ b/README.md @@ -483,7 +483,7 @@ See [CONTRIBUTING.md](https://github.com/NeogitOrg/neogit/blob/master/CONTRIBUTI ## Special Thanks -[kolja](https://github.com/kolja) for the Neogit Logo -[gitgraph.nvim](https://github.com/isakbm/gitgraph.nvim) for the "kitty" git graph renderer -[vim-flog](https://github.com/rbong/vim-flog) for the "unicode" git graph renderer +- [kolja](https://github.com/kolja) for the Neogit Logo +- [gitgraph.nvim](https://github.com/isakbm/gitgraph.nvim) for the "kitty" git graph renderer +- [vim-flog](https://github.com/rbong/vim-flog) for the "unicode" git graph renderer From 570c39181784f7a2e1bc9cedbfb11846022eea54 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 25 Oct 2024 23:52:27 +0200 Subject: [PATCH 047/437] Fix: Method signature needs another argument here. --- lua/neogit/buffers/log_view/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 6004e0cab..c73c033e6 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -252,7 +252,7 @@ function M:open() local permit = self.refresh_lock:acquire() self.commits = util.merge(self.commits, self.fetch_func(self:commit_count())) - self.buffer.ui:render(unpack(ui.View(self.commits, self.internal_args))) + self.buffer.ui:render(unpack(ui.View(self.commits, self.remotes, self.internal_args))) permit:forget() end), From 51450329e04860f71a31d8c7ef5178fce6e60524 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 26 Oct 2024 23:16:49 +0200 Subject: [PATCH 048/437] Display submodule status in status buffer. --- lua/neogit/buffers/status/ui.lua | 16 +++++++++++ lua/neogit/lib/git/status.lua | 47 +++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 92cb04e82..bfc26046e 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -273,12 +273,27 @@ local SectionItemFile = function(section, config) text.highlight("NeogitSubtleText")((" %s -> %s"):format(item.file_mode.head, item.file_mode.worktree)) end + local submodule = text("") + if item.submodule then + local submodule_text + if item.submodule.commit_changed then + submodule_text = " (new commits)" + elseif item.submodule.has_tracked_changes then + submodule_text = " (modified content)" + elseif item.submodule.has_untracked_changes then + submodule_text = " (untracked content)" + end + + submodule = text.highlight("NeogitTagName")(submodule_text) + end + return col.tag("Item")({ row { text.highlight(highlight)(mode_text), text(name), text.highlight("NeogitSubtleText")(unmerged_types[item.mode] or ""), file_mode_change, + submodule, }, }, { foldable = true, @@ -687,6 +702,7 @@ function M.Status(state, config) }, } end + -- stylua: ignore end return M diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index dfad7f7af..bf0b1d40a 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -11,9 +11,35 @@ local logger = require("neogit.logger") ---@field escaped_path string ---@field original_name string|nil ---@field file_mode {head: number, index: number, worktree: number}|nil +---@field submodule SubmoduleStatus|nil + +---@class SubmoduleStatus +---@field commit_changed boolean C +---@field has_tracked_changes boolean M +---@field has_untracked_changes boolean U + +---@param status string +-- A 4 character field describing the submodule state. +-- "N..." when the entry is not a submodule. +-- "S" when the entry is a submodule. +-- is "C" if the commit changed; otherwise ".". +-- is "M" if it has tracked changes; otherwise ".". +-- is "U" if there are untracked changes; otherwise ".". +local function parse_submodule_status(status) + local a, b, c, d = status:match("(.)(.)(.)(.)") + if a == "N" then + return nil + else + return { + commit_changed = b == "C", + has_tracked_changes = c == "M", + has_untracked_changes = d == "U", + } + end +end ---@return StatusItem -local function update_file(section, cwd, file, mode, name, original_name, file_mode) +local function update_file(section, cwd, file, mode, name, original_name, file_mode, submodule) local absolute_path = Path:new(cwd, name):absolute() local escaped_path = vim.fn.fnameescape(vim.fn.fnamemodify(absolute_path, ":~:.")) @@ -24,6 +50,7 @@ local function update_file(section, cwd, file, mode, name, original_name, file_m absolute_path = absolute_path, escaped_path = escaped_path, file_mode = file_mode, + submodule = submodule, } if file and rawget(file, "diff") then @@ -98,8 +125,9 @@ local function update_status(state, filter) update_file("untracked", state.git_root, old_files.untracked_files[rest], "?", rest) ) elseif kind == "1" then - local mode_staged, mode_unstaged, _, mH, mI, mW, hH, _, name = rest:match(match_1) + local mode_staged, mode_unstaged, submodule, mH, mI, mW, hH, _, name = rest:match(match_1) local file_mode = { head = mH, index = mI, worktree = mW } + local submodule = parse_submodule_status(submodule) if mode_staged ~= "." then if hH:match("^0+$") then @@ -115,7 +143,8 @@ local function update_status(state, filter) mode_staged, name, nil, - file_mode + file_mode, + submodule ) ) end @@ -130,13 +159,15 @@ local function update_status(state, filter) mode_unstaged, name, nil, - file_mode + file_mode, + submodule ) ) end elseif kind == "2" then - local mode_staged, mode_unstaged, _, mH, mI, mW, _, _, _, name, orig_name = rest:match(match_2) + local mode_staged, mode_unstaged, submodule, mH, mI, mW, _, _, _, name, orig_name = rest:match(match_2) local file_mode = { head = mH, index = mI, worktree = mW } + local submodule = parse_submodule_status(submodule) if mode_staged ~= "." then table.insert( @@ -148,7 +179,8 @@ local function update_status(state, filter) mode_staged, name, orig_name, - file_mode + file_mode, + submodule ) ) end @@ -163,7 +195,8 @@ local function update_status(state, filter) mode_unstaged, name, orig_name, - file_mode + file_mode, + submodule ) ) end From 5d93f7117ee84a2ed67bb2b5d4a867fd1a98e569 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 27 Oct 2024 13:21:05 +0100 Subject: [PATCH 049/437] Add type annotations --- lua/neogit/lib/git/diff.lua | 154 +++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 47 deletions(-) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 7bfaec2f0..15bea3182 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -6,6 +6,43 @@ local logger = require("neogit.logger") local insert = table.insert local sha256 = vim.fn.sha256 +---@class NeogitGitDiff +---@field parse fun(raw_diff: string[], raw_stats: string[]): Diff +---@field build fun(section: string, file: StatusItem) +---@field staged_stats fun(): DiffStagedStats +--- +---@class Diff +---@field kind string +---@field lines string[] +---@field file string +---@field info table +---@field stats table +---@field hunks Hunk +--- +---@class DiffStats +---@field additions number +---@field deletions number +--- +---@class Hunk +---@field index_from number +---@field index_len number +---@field diff_from number +---@field diff_to number +---@field first number First line number in buffer +---@field last number Last line number in buffer +--- +---@class DiffStagedStats +---@field summary string +---@field files DiffStagedStatsFile +--- +---@class DiffStagedStatsFile +---@field path string|nil +---@field changes string|nil +---@field insertions string|nil +---@field deletions string|nil + +---@param raw string|string[] +---@return DiffStats local function parse_diff_stats(raw) if type(raw) == "string" then raw = vim.split(raw, ", ") @@ -33,6 +70,8 @@ local function parse_diff_stats(raw) return stats end +---@param output string[] +---@return string[], number local function build_diff_header(output) local header = {} local start_idx = 1 @@ -50,6 +89,9 @@ local function build_diff_header(output) return header, start_idx end +---@param header string[] +---@param kind string +---@return string local function build_file(header, kind) if kind == "modified" then return header[3]:match("%-%-%- a/(.*)") @@ -64,6 +106,8 @@ local function build_file(header, kind) end end +---@param header string[] +---@return string, string[] local function build_kind(header) local kind = "" local info = {} @@ -83,6 +127,9 @@ local function build_kind(header) return kind, info end +---@param output string[] +---@param start_idx number +---@return string[] local function build_lines(output, start_idx) local lines = {} @@ -97,18 +144,13 @@ local function build_lines(output, start_idx) return lines end +---@param content string[] +---@return string local function hunk_hash(content) return sha256(table.concat(content, "\n")) end ----@class Hunk ----@field index_from number ----@field index_len number ----@field diff_from number ----@field diff_to number ----@field first number First line number in buffer ----@field last number Last line number in buffer - +---@param lines string[] ---@return Hunk local function build_hunks(lines) local hunks = {} @@ -171,6 +213,9 @@ local function build_hunks(lines) return hunks end +---@param raw_diff string[] +---@param raw_stats string[] +---@return Diff local function parse_diff(raw_diff, raw_stats) local header, start_idx = build_diff_header(raw_diff) local lines = build_lines(raw_diff, start_idx) @@ -179,7 +224,7 @@ local function parse_diff(raw_diff, raw_stats) local file = build_file(header, kind) local stats = parse_diff_stats(raw_stats or {}) - return { + return { ---@type Diff kind = kind, lines = lines, file = file, @@ -205,6 +250,8 @@ local function build_metatable(f, raw_output_fn) end -- Doing a git-diff with untracked files will exit(1) if a difference is observed, which we can ignore. +---@param name string +---@return fun(): table local function raw_untracked(name) return function() local diff = git.cli.diff.no_ext_diff.no_index @@ -216,6 +263,8 @@ local function raw_untracked(name) end end +---@param name string +---@return fun(): table local function raw_unstaged(name) return function() local diff = git.cli.diff.no_ext_diff.files(name).call({ hidden = true }).stdout @@ -225,6 +274,8 @@ local function raw_unstaged(name) end end +---@param name string +---@return fun(): table local function raw_staged_unmerged(name) return function() local diff = git.cli.diff.no_ext_diff.files(name).call({ hidden = true }).stdout @@ -234,6 +285,8 @@ local function raw_staged_unmerged(name) end end +---@param name string +---@return fun(): table local function raw_staged(name) return function() local diff = git.cli.diff.no_ext_diff.cached.files(name).call({ hidden = true }).stdout @@ -243,6 +296,8 @@ local function raw_staged(name) end end +---@param name string +---@return fun(): table local function raw_staged_renamed(name, original) return function() local diff = git.cli.diff.no_ext_diff.cached.files(name, original).call({ hidden = true }).stdout @@ -253,6 +308,8 @@ local function raw_staged_renamed(name, original) end end +---@param section string +---@param file StatusItem local function build(section, file) if section == "untracked" then build_metatable(file, raw_untracked(file.name)) @@ -269,48 +326,51 @@ local function build(section, file) end end ----@class NeogitGitDiff -return { - parse = parse_diff, - staged_stats = function() - local raw = git.cli.diff.no_ext_diff.cached.stat.call({ hidden = true }).stdout - local files = {} - local summary - - local idx = 1 - local function advance() - idx = idx + 1 - end +---@return DiffStagedStats +local function staged_stats() + local raw = git.cli.diff.no_ext_diff.cached.stat.call({ hidden = true }).stdout + local files = {} + local summary - local function peek() - return raw[idx] - end + local idx = 1 + local function advance() + idx = idx + 1 + end - while true do - local line = peek() - if not line then - break - end + local function peek() + return raw[idx] + end - if line:match("^ %d+ file[s ]+changed,") then - summary = vim.trim(line) - break - else - table.insert(files, { - path = vim.trim(line:match("^ ([^ ]+)")), - changes = line:match("|%s+(%d+)"), - insertions = line:match("|%s+%d+ (%+*)"), - deletions = line:match("|%s+%d+ %+*(%-*)$"), - }) - - advance() - end + while true do + local line = peek() + if not line then + break end - return { - summary = summary, - files = files, - } - end, + if line:match("^ %d+ file[s ]+changed,") then + summary = vim.trim(line) + break + else + local file = { ---@type DiffStagedStatsFile + path = vim.trim(line:match("^ ([^ ]+)")), + changes = line:match("|%s+(%d+)"), + insertions = line:match("|%s+%d+ (%+*)"), + deletions = line:match("|%s+%d+ %+*(%-*)$"), + } + + insert(files, file) + advance() + end + end + + return { + summary = summary, + files = files, + } +end + +return { ---@type NeogitGitDiff + parse = parse_diff, + staged_stats = staged_stats, build = build, } From be267c6b2fa869a28a6e8ea2009d4e9fb77e6486 Mon Sep 17 00:00:00 2001 From: Nicky Meuleman Date: Mon, 28 Oct 2024 00:36:34 +0100 Subject: [PATCH 050/437] fix: windowkind types Add types to the WindowKind alias referenced: https://github.com/NeogitOrg/neogit/blob/master/lua/neogit.lua#L231 --- lua/neogit/config.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index bb548a3f4..be39a94dd 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -79,10 +79,16 @@ function M.get_user_mappings(set) end ---@alias WindowKind +---| "replace" Like :enew +---| "tab" Open in a new tab ---| "split" Open in a split +---| "split_above" Like :top split +---| "split_above_all" Like :top split +---| "split_below" Like :below split +---| "split_below_all" Like :below split ---| "vsplit" Open in a vertical split ---| "floating" Open in a floating window ----| "tab" Open in a new tab +---| "auto" vsplit if window would have 80 cols, otherwise split ---@class NeogitCommitBufferConfig Commit buffer options ---@field kind WindowKind The type of window that should be opened From 210c1000c4ae5a0d588a128a3c3b6244215554b1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 10:50:24 +0100 Subject: [PATCH 051/437] Add type annotation for cli builder --- lua/neogit/buffers/process/init.lua | 2 +- lua/neogit/lib/git/branch.lua | 2 +- lua/neogit/lib/git/cli.lua | 448 +++++++++++++++++++++++----- lua/neogit/popups/fetch/actions.lua | 2 +- lua/neogit/runner.lua | 1 + 5 files changed, 374 insertions(+), 81 deletions(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 71ecb6920..7f8b1efd6 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -22,7 +22,7 @@ local M = {} M.__index = M ---@return ProcessBuffer ----@param process ProcessOpts +---@param process Process function M:new(process) local instance = { content = { string.format("> %s\r\n", table.concat(process.cmd, " ")) }, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 8dcd5920e..e2b084775 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -63,7 +63,7 @@ function M.track(name, args) end function M.get_local_branches(include_current) - local branches = git.cli.branch.list(config.values.sort_branches).call({ hidden = true }).stdout + local branches = git.cli.branch.sort(config.values.sort_branches).call({ hidden = true }).stdout return parse_branches(branches, include_current) end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 64e5dc8bd..ff909e96c 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -4,69 +4,378 @@ local util = require("neogit.lib.util") local Path = require("plenary.path") local runner = require("neogit.runner") +---@class GitCommandSetup +---@field flags table|nil +---@field options table|nil +---@field aliases table|nil +---@field short_opts table|nil + ---@class GitCommand ---@field flags table ---@field options table ---@field aliases table ---@field short_opts table ----@field args fun(...): table ----@field arg_list fun(table): table + +---@class GitCommandBuilder +---@field args fun(...): self appends all params to cli as argument +---@field arguments fun(...): self alias for `args` +---@field arg_list fun(table): self unpacks table and uses items as cli arguments +---@field files fun(...): self any filepaths to append to the cli call +---@field paths fun(...): self alias for `files` +---@field input fun(string): self string to send to process via STDIN +---@field stdin fun(string): self alias for `input` +---@field prefix fun(string): self prefix for CLI call +---@field env fun(table): self key/value pairs to set as ENV variables for process +---@field in_pty fun(boolean): self should this be run in a PTY or not? +---@field call fun(CliCallOptions): ProcessResult + +---@class CliCallOptions +---@field hidden boolean Is the command hidden from user? +---@field trim boolean remove blank lines from output? +---@field remove_ansi boolean remove ansi escape-characters from output? +---@field await boolean run synchronously if true +---@field long boolean is the command expected to be long running? (like git bisect, commit, rebase, etc) +---@field pty boolean run command in PTY? +---@field on_error fun(res: ProcessResult): boolean function to call if the process exits with status > 0. Used to +--- determine how to handle the error, if user should be alerted or not + + +---@class GitCommandShow: GitCommandBuilder +---@field stat self +---@field oneline self +---@field no_patch self +---@field format fun(string): self +---@field file fun(name: string, rev: string|nil): self + +---@class GitCommandNameRev: GitCommandBuilder +---@field name_only self +---@field no_undefined self +---@field refs fun(string): self +---@field exclude fun(string): self + +---@class GitCommandInit: GitCommandBuilder + +---@class GitCommandCheckoutIndex: GitCommandBuilder +---@field all self +---@field force self + +---@class GitCommandWorktree: GitCommandBuilder +---@field add self +---@field list self +---@field move self +---@field remove self + +---@class GitCommandRm: GitCommandBuilder +---@field cached self + +---@class GitCommandStatus: GitCommandBuilder +---@field short self +---@field branch self +---@field verbose self +---@field null_separated self +---@field porcelain fun(string): self + +---@class GitCommandLog: GitCommandBuilder +---@field oneline self +---@field branches self +---@field remotes self +---@field all self +---@field graph self +---@field color self +---@field pretty fun(string): self +---@field max_count fun(string): self +---@field format fun(string): self + +---@class GitCommandConfig: GitCommandBuilder +---@field _local self +---@field global self +---@field list self +---@field _get self PRIVATE - use alias +---@field _add self PRIVATE - use alias +---@field _unset self PRIVATE - use alias +---@field null self +---@field set fun(key: string, value: string): self +---@field unset fun(key: string): self +---@field get fun(path: string): self + +---@class GitCommandDescribe: GitCommandBuilder +---@field long self +---@field tags self + +---@class GitCommandDiff: GitCommandBuilder +---@field cached self +---@field stat self +---@field shortstat self +---@field patch self +---@field name_only self +---@field ext_diff self +---@field index self +---@field check self + +---@class GitCommandStash: GitCommandBuilder +---@field apply self +---@field drop self +---@field push self +---@field store self +---@field index self +---@field staged self +---@field keep_index self +---@field message fun(text: string): self + +---@class GitCommandTag: GitCommandBuilder +---@field n self +---@field list self +---@field delete self + +---@class GitCommandRebase: GitCommandBuilder +---@field interactive self +---@field onto self +---@field todo self +---@field continue self +---@field abort self +---@field skip self +---@field autosquash self +---@field autostash self +---@field commit fun(rev: string): self + +---@class GitCommandMerge: GitCommandBuilder +---@field continue self +---@field abort self + +---@class GitCommandMergeBase: GitCommandBuilder +---@field is_ancestor self + +---@class GitCommandReset: GitCommandBuilder +---@field hard self +---@field mixed self +---@field soft self +---@field keep self +---@field merge self + +---@class GitCommandCheckout: GitCommandBuilder +---@field b fun(): self +---@field _track self PRIVATE - use alias +---@field detach self +---@field ours self +---@field theirs self +---@field merge self +---@field track fun(branch: string): self +---@field rev fun(rev: string): self +---@field branch fun(branch: string): self +---@field commit fun(commit: string): self +---@field new_branch fun(new_branch: string): self +---@field new_branch_with_start_point fun(branch: string, start_point: string): self + +---@class GitCommandRemote: GitCommandBuilder +---@field push self +---@field add self +---@field rm self +---@field rename self +---@field prune self +---@field get_url fun(remote: string): self + +---@class GitCommandApply: GitCommandBuilder +---@field ignore_space_change self +---@field cached self +---@field reverse self +---@field index self +---@field with_patch fun(string): self alias for input + +---@class GitCommandAdd: GitCommandBuilder +---@field update self +---@field all self + +---@class GitCommandAbsorb: GitCommandBuilder +---@field verbose self +---@field and_rebase self +---@field base fun(commit: string): self + +---@class GitCommandCommit: GitCommandBuilder +---@field all self +---@field no_verify self +---@field amend self +---@field only self +---@field dry_run self +---@field no_edit self +---@field edit self +---@field allow_empty self +---@field with_message fun(message: string): self Passes message via STDIN +---@field message fun(message: string): self Passes message via CLI + +---@class GitCommandPush: GitCommandBuilder +---@field delete self +---@field remote fun(remote: string): self +---@field to fun(to: string): self + +---@class GitCommandPull: GitCommandBuilder +---@field no_commit self + +---@class GitCommandCherry: GitCommandBuilder +---@field verbose self + +---@class GitCommandBranch: GitCommandBuilder +---@field all self +---@field delete self +---@field remotes self +---@field force self +---@field current self +---@field edit_description self +---@field very_verbose self +---@field move self +---@field sort fun(sort: string): self +---@field name fun(name: string): self + +---@class GitCommandFetch: GitCommandBuilder +---@field recurse_submodules self +---@field verbose self +---@field jobs fun(n: number): self + +---@class GitCommandReadTree: GitCommandBuilder +---@field merge self +---@field index_output fun(path: string): self +---@field tree fun(tree: string): self + +---@class GitCommandWriteTree: GitCommandBuilder + +---@class GitCommandCommitTree: GitCommandBuilder +---@field no_gpg_sign self +---@field parent fun(parent: string): self +---@field message fun(message: string): self +---@field parents fun(...): self +---@field tree fun(tree: string): self + +---@class GitCommandUpdateIndex: GitCommandBuilder +---@field add self +---@field remove self +---@field refresh self + +---@class GitCommandShowRef: GitCommandBuilder +---@field verify self + +---@class GitCommandShowBranch: GitCommandBuilder +---@field all self + +---@class GitCommandReflog: GitCommandBuilder +---@field show self +---@field format fun(format: string): self +---@field date fun(mode: string): self + +---@class GitCommandUpdateRef: GitCommandBuilder +---@field create_reflog self +---@field message fun(text: string): self + +---@class GitCommandLsFiles: GitCommandBuilder +---@field others self +---@field deleted self +---@field modified self +---@field cached self +---@field deduplicate self +---@field exclude_standard self +---@field full_name self +---@field error_unmatch self + +---@class GitCommandLsTree: GitCommandBuilder +---@field full_tree self +---@field name_only self +---@field recursive self + +---@class GitCommandLsRemote: GitCommandBuilder +---@field tags self +---@field remote fun(remote: string): self + +---@class GitCommandForEachRef: GitCommandBuilder +---@field format self +---@field sort self + +---@class GitCommandRevList: GitCommandBuilder +---@field merges self +---@field parents self +---@field max_count fun(n: number): self + +---@class GitCommandRevParse: GitCommandBuilder +---@field verify self +---@field quiet self +---@field short self +---@field revs_only self +---@field no_revs self +---@field flags self +---@field no_flags self +---@field symbolic self +---@field symbolic_full_name self +---@field abbrev_ref fun(ref: string): self + +---@class GitCommandCherryPick: GitCommandBuilder +---@field no_commit self +---@field continue self +---@field skip self +---@field abort self + +---@class GitCommandVerifyCommit: GitCommandBuilder + +---@class GitCommandBisect: GitCommandBuilder + ---@class NeogitGitCLI ----@field show GitCommand ----@field name-rev GitCommand ----@field init GitCommand ----@field checkout-index GitCommand ----@field worktree GitCommand ----@field rm GitCommand ----@field status GitCommand ----@field log GitCommand ----@field config GitCommand ----@field describe GitCommand ----@field diff GitCommand ----@field stash GitCommand ----@field tag GitCommand ----@field rebase GitCommand ----@field merge GitCommand ----@field merge-base GitCommand ----@field reset GitCommand ----@field checkout GitCommand ----@field remote GitCommand ----@field apply GitCommand ----@field add GitCommand ----@field absorb GitCommand ----@field commit GitCommand ----@field push GitCommand ----@field pull GitCommand ----@field cherry GitCommand ----@field branch GitCommand ----@field fetch GitCommand ----@field read-tree GitCommand ----@field write-tree GitCommand ----@field commit-tree GitCommand ----@field update-index GitCommand ----@field show-ref GitCommand ----@field show-branch GitCommand ----@field update-ref GitCommand ----@field ls-files GitCommand ----@field ls-tree GitCommand ----@field ls-remote GitCommand ----@field for-each-ref GitCommand ----@field rev-list GitCommand ----@field rev-parse GitCommand ----@field cherry-pick GitCommand ----@field verify-commit GitCommand ----@field bisect GitCommand +---@field absorb GitCommandAbsorb +---@field add GitCommandAdd +---@field apply GitCommandApply +---@field bisect GitCommandBisect +---@field branch GitCommandBranch +---@field checkout GitCommandCheckout +---@field checkout-index GitCommandCheckoutIndex +---@field cherry GitCommandCherry +---@field cherry-pick GitCommandCherryPick +---@field commit GitCommandCommit +---@field commit-tree GitCommandCommitTree +---@field config GitCommandConfig +---@field describe GitCommandDescribe +---@field diff GitCommandDiff +---@field fetch GitCommandFetch +---@field for-each-ref GitCommandForEachRef +---@field init GitCommandInit +---@field log GitCommandLog +---@field ls-files GitCommandLsFiles +---@field ls-remote GitCommandLsRemote +---@field ls-tree GitCommandLsTree +---@field merge GitCommandMerge +---@field merge-base GitCommandMergeBase +---@field name-rev GitCommandNameRev +---@field pull GitCommandPull +---@field push GitCommandPush +---@field read-tree GitCommandReadTree +---@field rebase GitCommandRebase +---@field reflog GitCommandReflog +---@field remote GitCommandRemote +---@field reset GitCommandReset +---@field rev-list GitCommandRevList +---@field rev-parse GitCommandRevParse +---@field rm GitCommandRm +---@field show GitCommandShow +---@field show-branch GitCommandShowBranch +---@field show-ref GitCommandShowRef +---@field stash GitCommandStash +---@field status GitCommandStatus +---@field tag GitCommandTag +---@field update-index GitCommandUpdateIndex +---@field update-ref GitCommandUpdateRef +---@field verify-commit GitCommandVerifyCommit +---@field worktree GitCommandWorktree +---@field write-tree GitCommandWriteTree ---@field git_root fun(dir: string):string ---@field is_inside_worktree fun(dir: string):boolean +---@param setup GitCommandSetup|nil +---@return GitCommand local function config(setup) setup = setup or {} - setup.flags = setup.flags or {} - setup.options = setup.options or {} - setup.aliases = setup.aliases or {} - setup.short_opts = setup.short_opts or {} - return setup + + local command = {} + command.flags = setup.flags or {} + command.options = setup.options or {} + command.aliases = setup.aliases or {} + command.short_opts = setup.short_opts or {} + + return command end local configurations = { @@ -149,13 +458,6 @@ local configurations = { max_count = "--max-count", format = "--format", }, - aliases = { - for_range = function(tbl) - return function(range) - return tbl.args(range) - end - end, - }, }, config = config { @@ -408,14 +710,11 @@ local configurations = { end end, message = function(tbl) - return function(text) - return tbl.args("-m", text) + return function(message) + return tbl.args("-m", message) end end, }, - options = { - commit_message_file = "--file", - }, }, push = config { @@ -440,9 +739,6 @@ local configurations = { flags = { no_commit = "--no-commit", }, - pull = config { - flags = {}, - }, }, cherry = config { @@ -462,12 +758,10 @@ local configurations = { very_verbose = "-vv", move = "-m", }, + options = { + sort = "--sort" + }, aliases = { - list = function(tbl) - return function(sort) - return tbl.args("--sort=" .. sort) - end - end, name = function(tbl) return function(name) return tbl.args(name) @@ -477,16 +771,12 @@ local configurations = { }, fetch = config { - options = { + flags = { recurse_submodules = "--recurse-submodules", verbose = "--verbose", }, - aliases = { - jobs = function(tbl) - return function(n) - return tbl.args("--jobs=" .. tostring(n)) - end - end, + options = { + jobs = "--jobs" }, }, @@ -576,6 +866,7 @@ local configurations = { aliases = { message = function(tbl) return function(text) + -- TODO: Is this escapement needed? local escaped_text, _ = text:gsub([["]], [[\"]]) return tbl.args("-m", string.format([["%s"]], escaped_text)) end @@ -863,6 +1154,7 @@ local function new_builder(subcommand) } end + ---@return CliCallOptions local function make_options(options) local opts = vim.tbl_extend("keep", (options or {}), { hidden = false, diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index 17d1c8746..86ee3034d 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -121,7 +121,7 @@ end function M.fetch_submodules(_) notification.info("Fetching submodules") - git.cli.fetch.recurse_submodules().verbose().jobs(4).call() + git.cli.fetch.recurse_submodules.verbose.jobs(4).call() end function M.set_variables() diff --git a/lua/neogit/runner.lua b/lua/neogit/runner.lua index 76d5b3f88..80d595bbe 100644 --- a/lua/neogit/runner.lua +++ b/lua/neogit/runner.lua @@ -100,6 +100,7 @@ end ---@param process Process ---@param opts table +---@return ProcessResult function M.call(process, opts) logger.trace(string.format("[RUNNER]: Executing %q", table.concat(process.cmd, " "))) From 1111b9de9ffdaddacf31b42e1ea09f5f92ae04f6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 11:21:05 +0100 Subject: [PATCH 052/437] Auto-close floating commit view with blur --- lua/neogit/buffers/commit_view/init.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 0a0d32367..67b54bb0d 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -153,6 +153,13 @@ function M:open(kind) kind = kind, status_column = not config.values.disable_signs and "" or nil, context_highlight = not config.values.disable_context_highlighting, + autocmds = { + ["WinLeave"] = function() + if self.buffer.kind == "floating" then + pcall(self.close, self) + end + end, + }, mappings = { n = { [""] = function() From b81f37ec3169153394084cd564d7dc5871f0729a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 11:21:39 +0100 Subject: [PATCH 053/437] lint --- lua/neogit/lib/git/branch.lua | 2 +- lua/neogit/lib/git/cli.lua | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index e2b084775..426df41af 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -68,7 +68,7 @@ function M.get_local_branches(include_current) end function M.get_remote_branches(include_current) - local branches = git.cli.branch.remotes.list(config.values.sort_branches).call({ hidden = true }).stdout + local branches = git.cli.branch.remotes.sort(config.values.sort_branches).call({ hidden = true }).stdout return parse_branches(branches, include_current) end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index ff909e96c..f0c0e3b60 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -39,7 +39,6 @@ local runner = require("neogit.runner") ---@field on_error fun(res: ProcessResult): boolean function to call if the process exits with status > 0. Used to --- determine how to handle the error, if user should be alerted or not - ---@class GitCommandShow: GitCommandBuilder ---@field stat self ---@field oneline self @@ -314,7 +313,6 @@ local runner = require("neogit.runner") ---@class GitCommandBisect: GitCommandBuilder - ---@class NeogitGitCLI ---@field absorb GitCommandAbsorb ---@field add GitCommandAdd @@ -759,7 +757,7 @@ local configurations = { move = "-m", }, options = { - sort = "--sort" + sort = "--sort", }, aliases = { name = function(tbl) @@ -776,7 +774,7 @@ local configurations = { verbose = "--verbose", }, options = { - jobs = "--jobs" + jobs = "--jobs", }, }, From c9687f84cea5856e9828228ac35d19f331c1d308 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:49:08 +0100 Subject: [PATCH 054/437] add stash to config types --- lua/neogit/config.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index be39a94dd..b0e0bee72 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -320,6 +320,7 @@ end ---@field status? NeogitConfigStatusOptions Status buffer options ---@field commit_editor? NeogitCommitEditorConfigPopup Commit editor options ---@field commit_select_view? NeogitConfigPopup Commit select view options +---@field stash? NeogitConfigPopup Commit select view options ---@field commit_view? NeogitCommitBufferConfig Commit buffer options ---@field log_view? NeogitConfigPopup Log view options ---@field rebase_editor? NeogitConfigPopup Rebase editor options From 23083a30deeb54ea25db388401039bf7a73fec3c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:49:29 +0100 Subject: [PATCH 055/437] Log errors here --- lua/neogit/lib/util.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 532c8d419..3cd8215d4 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -559,7 +559,7 @@ end --- @return F throttled function. function M.throttle_by_id(fn, schedule) local scheduled = {} --- @type table - local running = {} --- @type table + local running = {} --- @type table return function(id, ...) if scheduled[id] then @@ -600,9 +600,9 @@ end ---@param winid integer ---@param force boolean function M.safe_win_close(winid, force) - local ok, _ = pcall(vim.api.nvim_win_close, winid, force) - + local ok, err = pcall(vim.api.nvim_win_close, winid, force) if not ok then + require("neogit.logger").error(err) pcall(vim.cmd, "b#") end end From c6bd59a045c98cfe901adc03ba7e03577b6eae0e Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:49:45 +0100 Subject: [PATCH 056/437] Check for valid job ID when stopping process --- lua/neogit/process.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 3b0925118..f3c54174d 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -185,7 +185,7 @@ end function Process:stop() if self.job then - fn.jobstop(self.job) + assert(fn.jobstop(self.job) == 1, "invalid job id") end end @@ -373,6 +373,7 @@ function Process:spawn(cb) if not self.cmd[#self.cmd] == "-" then self:send("\04") end + self:close_stdin() end From efd0daa977c50da302d47306691033c557ae8c9c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:50:07 +0100 Subject: [PATCH 057/437] Fix: when closing buffer, self.buffer is nil here --- lua/neogit/buffers/commit_view/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 67b54bb0d..ef6e63859 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -155,8 +155,8 @@ function M:open(kind) context_highlight = not config.values.disable_context_highlighting, autocmds = { ["WinLeave"] = function() - if self.buffer.kind == "floating" then - pcall(self.close, self) + if self.buffer and self.buffer.kind == "floating" then + self:close() end end, }, From be200603ec9f2c8da95636dd4ec68ba92d728352 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:55:43 +0100 Subject: [PATCH 058/437] lint --- lua/neogit/lib/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 3cd8215d4..0d6809dc8 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -559,7 +559,7 @@ end --- @return F throttled function. function M.throttle_by_id(fn, schedule) local scheduled = {} --- @type table - local running = {} --- @type table + local running = {} --- @type table return function(id, ...) if scheduled[id] then From d46678d6fd155965b280842084163205e7b91d46 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 28 Oct 2024 20:56:32 +0100 Subject: [PATCH 059/437] Allow popups to be focusable. no idea why they were not. --- lua/neogit/lib/buffer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8eac48ca7..48504b037 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -310,7 +310,7 @@ function Buffer:show() col = col, row = row, style = "minimal", - focusable = false, + focusable = true, border = "rounded", }) @@ -325,7 +325,7 @@ function Buffer:show() col = 0, row = vim.o.lines - 2, style = "minimal", - focusable = false, + focusable = true, border = { "─", "─", "─", "", "", "", "", "" }, title = " Git Console ", }) From b2e226be43c1031a851378eaf9619a2f94134322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Mon, 28 Oct 2024 17:12:21 +0100 Subject: [PATCH 060/437] Fix trying to perform arithmetic on nil hunk_offset I got this locally. I'm not sure how I got there, but there I was: ``` ...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:18: The coroutine failed with this message: ....local/share/nvim/lazy/neogit/lua/neogit/lib/ui/init.lua:507: attempt to perform arithmetic on field 'hunk _offset' (a nil value) stack traceback: ^I[C]: in function 'error' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:18: in function 'callback_or_next' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:45: in function 'step' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:48: in function 'execute' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:118: in function 'callback' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:25: in function 'callback_or_next' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:45: in function 'saved_callback' ^I...are/nvim/lazy/plenary.nvim/lua/plenary/async/control.lua:126: in function 'tx' ^I.../share/nvim/lazy/plenary.nvim/lua/plenary/async/util.lua:71: in function 'callback' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:25: in function 'callback_or_next' ^I...share/nvim/lazy/plenary.nvim/lua/plenary/async/async.lua:45: in function 'cb' ^I...sto/.local/share/nvim/lazy/neogit/lua/neogit/process.lua:334: in function <...sto/.local/share/nvim/lazy/neogit/lua/neogit/process.lua:292> ``` See `attempt to perform arithmetic on field 'hunk _offset' (a nil value)`. This PR works around that by defaulting to `0` if there is no `hunk_offset`. --- lua/neogit/lib/ui/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 92f9814d2..2a461a5ff 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -504,7 +504,7 @@ function Ui:resolve_cursor_location(cursor) if cursor.hunk.index_from == hunk.index_from then logger.debug(("[UI] Using hunk.first with offset %q"):format(cursor.hunk.name)) - return hunk.first + cursor.hunk_offset - (cursor.last - hunk.last) + return hunk.first + (cursor.hunk_offset or 0) - (cursor.last - hunk.last) else logger.debug(("[UI] Using hunk.first %q"):format(cursor.hunk.name)) return hunk.first From 42ebd2ae70ce31b485e7dea99504796498cb18f8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 29 Oct 2024 10:08:01 +0100 Subject: [PATCH 061/437] Allow interrupting a process by closing the console buffer, or by sending an interrupt via c-c --- lua/neogit/buffers/process/init.lua | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 7f8b1efd6..4f46aa46d 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -43,6 +43,7 @@ end function M:close() if self.buffer then + self.buffer:close_terminal_channel() self.buffer:close() self.buffer = nil end @@ -92,8 +93,9 @@ function M:flush_content() end end -local function hide(self) +local function close(self) return function() + self.process:stop() self:close() end end @@ -111,29 +113,20 @@ function M:open() open = false, buftype = false, kind = config.values.preview_buffer.kind, - on_detach = function() - self.buffer:close_terminal_channel() - self.buffer = nil + after = function(buffer) + buffer:open_terminal_channel() end, - autocmds = { - ["WinLeave"] = function() - pcall(self.close, self) - end, - }, mappings = { - t = { - [status_maps["Close"]] = hide(self), - [""] = hide(self), - }, n = { - [status_maps["Close"]] = hide(self), - [""] = hide(self), + [""] = function() + pcall(self.process.stop, self.process) + end, + [status_maps["Close"]] = close(self), + [""] = close(self), }, }, } - self.buffer:open_terminal_channel() - return self end From daad0e1b7df8da2260455339f9615c640b19c27d Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 29 Oct 2024 22:39:24 +0100 Subject: [PATCH 062/437] Document config.highlight --- doc/neogit.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 88d347b39..62f965b73 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -387,6 +387,39 @@ The following mappings can all be customized via the setup function. ============================================================================== 5. Highlights *neogit_highlights* +To provide a custom color pallette directly to the plugin, you can use the +`config.highlight` table with the following signature: + +---@class HighlightOptions +---@field italic? boolean +---@field bold? boolean +---@field underline? boolean +---@field bg0? string Darkest background color +---@field bg1? string Second darkest background color +---@field bg2? string Second lightest background color +---@field bg3? string Lightest background color +---@field grey? string middle grey shade for foreground +---@field white? string Foreground white (main text) +---@field red? string Foreground red +---@field bg_red? string Background red +---@field line_red? string Cursor line highlight for red regions +---@field orange? string Foreground orange +---@field bg_orange? string background orange +---@field yellow? string Foreground yellow +---@field bg_yellow? string background yellow +---@field green? string Foreground green +---@field bg_green? string Background green +---@field line_green? string Cursor line highlight for green regions +---@field cyan? string Foreground cyan +---@field bg_cyan? string Background cyan +---@field blue? string Foreground blue +---@field bg_blue? string Background blue +---@field purple? string Foreground purple +---@field bg_purple? string Background purple +---@field md_purple? string Background medium purple + +The following highlight groups will all be derrived from this pallette. + The following highlight groups are defined by this plugin. If you set any of 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. From 001f43f50d9589d837cec59004fd92486ab06870 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 29 Oct 2024 22:42:33 +0100 Subject: [PATCH 063/437] spelling... --- doc/neogit.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 62f965b73..4aada53d3 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -387,7 +387,7 @@ The following mappings can all be customized via the setup function. ============================================================================== 5. Highlights *neogit_highlights* -To provide a custom color pallette directly to the plugin, you can use the +To provide a custom color palette directly to the plugin, you can use the `config.highlight` table with the following signature: ---@class HighlightOptions @@ -418,7 +418,7 @@ To provide a custom color pallette directly to the plugin, you can use the ---@field bg_purple? string Background purple ---@field md_purple? string Background medium purple -The following highlight groups will all be derrived from this pallette. +The following highlight groups will all be derived from this palette. The following highlight groups are defined by this plugin. If you set any of these yourself before the plugin loads, that will be respected. If they do not From e99e9a6af14e7c61ad88169bf9f5a97df308528d Mon Sep 17 00:00:00 2001 From: Leon Watson Date: Mon, 4 Nov 2024 01:38:09 -0600 Subject: [PATCH 064/437] Added kind to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b84ce9d6..843e6fa5d 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,7 @@ The `kind` option can be one of the following values: - `split_below` - `split_below_all` - `vsplit` +- `floating` - `auto` (`vsplit` if window would have 80 cols, otherwise `split`) ## Popups From 9f59ec46bffee4cb77e63406c3539631493bf4c4 Mon Sep 17 00:00:00 2001 From: Leon Watson Date: Mon, 4 Nov 2024 01:39:51 -0600 Subject: [PATCH 065/437] Revert 1 commits e99e9a6 'Added kind to README.md' --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 843e6fa5d..3b84ce9d6 100644 --- a/README.md +++ b/README.md @@ -417,7 +417,6 @@ The `kind` option can be one of the following values: - `split_below` - `split_below_all` - `vsplit` -- `floating` - `auto` (`vsplit` if window would have 80 cols, otherwise `split`) ## Popups From 24f1b4d2cd4fa83d37b71a73519f5a859310c42a Mon Sep 17 00:00:00 2001 From: Leon Watson Date: Mon, 4 Nov 2024 01:42:01 -0600 Subject: [PATCH 066/437] Added floating option for kind values --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b84ce9d6..843e6fa5d 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,7 @@ The `kind` option can be one of the following values: - `split_below` - `split_below_all` - `vsplit` +- `floating` - `auto` (`vsplit` if window would have 80 cols, otherwise `split`) ## Popups From 66ff88761cf5fe8852c18326ed30ca79097aa589 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 00:10:28 -0600 Subject: [PATCH 067/437] Handle `incompatible`/`dependant` for options Both of the `incompatible` and `dependant` concepts are only implemented for switches, not options. However, options can be incompatible or dependent on each other and switches as well. Handle both concepts between all options and switches. Use the existing `toggle_switch`/`set_option` to do the disabling so that any recursive dependencies are resolved as well. --- lua/neogit/lib/popup/builder.lua | 18 +++++++- lua/neogit/lib/popup/init.lua | 73 +++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 5036697e2..5ffa0da1c 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -25,9 +25,11 @@ local M = {} ---@field cli string ---@field cli_prefix string ---@field default string|integer|boolean +---@field dependant table ---@field description string ---@field fn function ---@field id string +---@field incompatible table ---@field key string ---@field key_prefix string ---@field separator string @@ -68,20 +70,22 @@ local M = {} ---@class PopupSwitchOpts ---@field enabled boolean Controls if the switch should default to 'on' state ---@field internal boolean Whether the switch is internal to neogit or should be included in the cli command. If `true` we don't include it in the cli command. ----@field incompatible table A table of strings that represent other cli flags that this one cannot be used with +---@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with ---@field key_prefix string Allows overwriting the default '-' to toggle switch ---@field cli_prefix string Allows overwriting the default '--' thats used to create the cli flag. Sometimes you may want to use '++' or '-'. ---@field cli_suffix string ---@field options table ---@field value string Allows for pre-building cli flags that can be customised by user input ---@field user_input boolean If true, allows user to customise the value of the cli flag ----@field dependant string[] other switches with a state dependency on this one +---@field dependant string[] other switches/options with a state dependency on this one ---@class PopupOptionsOpts ---@field key_prefix string Allows overwriting the default '=' to set option ---@field cli_prefix string Allows overwriting the default '--' cli prefix ---@field choices table Table of predefined choices that a user can select for option ---@field default string|integer|boolean Default value for option, if the user attempts to unset value +---@field dependant string[] other switches/options with a state dependency on this one +---@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with ---@class PopupConfigOpts ---@field options { display: string, value: string, config: function? } @@ -266,6 +270,14 @@ function M:option(key, cli, value, description, opts) opts.separator = "=" end + if opts.dependant == nil then + opts.dependant = {} + end + + if opts.incompatible == nil then + opts.incompatible = {} + end + if opts.setup then opts.setup(self) end @@ -283,6 +295,8 @@ function M:option(key, cli, value, description, opts) choices = opts.choices, default = opts.default, separator = opts.separator, + dependant = util.build_reverse_lookup(opts.dependant), + incompatible = util.build_reverse_lookup(opts.incompatible), fn = opts.fn, }) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 909fb5c43..9ffb3fc71 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -128,22 +128,28 @@ function M:toggle_switch(switch) state.set({ self.state.name, switch.cli }, switch.enabled) - -- Ensure that other switches that are incompatible with this one are disabled + -- Ensure that other switches/options that are incompatible with this one are disabled if switch.enabled and #switch.incompatible > 0 then for _, var in ipairs(self.state.args) do - if var.type == "switch" and var.enabled and switch.incompatible[var.cli] then - var.enabled = false - state.set({ self.state.name, var.cli }, var.enabled) + if switch.incompatible[var.cli] then + if var.type == "switch" then + self:disable_switch(var) + elseif var.type == "option" then + self:disable_option(var) + end end end end - -- Ensure that switches that depend on this one are also disabled + -- Ensure that switches/options that depend on this one are also disabled if not switch.enabled and #switch.dependant > 0 then for _, var in ipairs(self.state.args) do - if var.type == "switch" and var.enabled and switch.dependant[var.cli] then - var.enabled = false - state.set({ self.state.name, var.cli }, var.enabled) + if switch.dependant[var.cli] then + if var.type == "switch" then + self:disable_switch(var) + elseif var.type == "option" then + self:disable_option(var) + end end end end @@ -151,10 +157,13 @@ end -- Toggle an option on/off and set it's value ---@param option table +---@param value? string ---@return nil -function M:set_option(option) +function M:set_option(option, value) -- Prompt user to select from predetermined choices - if option.choices then + if value then + option.value = value + elseif option.choices then if not option.value or option.value == "" then local choice = FuzzyFinderBuffer.new(option.choices):open_async { prompt_prefix = option.description, @@ -186,6 +195,50 @@ function M:set_option(option) end state.set({ self.state.name, option.cli }, option.value) + + -- Ensure that other switches/options that are incompatible with this one are disabled + if option.value and option.value ~= "" and #option.incompatible > 0 then + for _, var in ipairs(self.state.args) do + if option.incompatible[var.cli] then + if var.type == "switch" then + self:disable_switch(var) + elseif var.type == "option" then + self:disable_option(var) + end + end + end + end + + -- Ensure that switches/options that depend on this one are also disabled + if option.value and option.value ~= "" and #option.dependant > 0 then + for _, var in ipairs(self.state.args) do + if option.dependant[var.cli] then + if var.type == "switch" then + self:disable_switch(var) + elseif var.type == "option" then + self:disable_option(var) + end + end + end + end +end + +---Disables a switch. +---@param switch table +function M:disable_switch(switch) + if switch.enabled then + self:toggle_switch(switch) + end +end + +---Disables an option, setting its value to "". Doesn't use the default, which +---is important to ensure that we don't use incompatible switches/options +---together. +---@param option table +function M:disable_option(option) + if option.value and option.value ~= "" then + self:set_option(option, "") + end end -- Set a config value From 2b770465b79dfe60898983ddfc897cdf08cf9d34 Mon Sep 17 00:00:00 2001 From: Steve Beaulac Date: Wed, 6 Nov 2024 22:16:34 -0500 Subject: [PATCH 068/437] fix: process.lua error when we have a bare repo in the worktree list When generating a list of worktrees using the 'git worktree list --porcelain' command, a bare worktree only has 2 lines, whereas all other worktrees have 3 lines. Since we don't need to switch to the bare repo, we can safely ignore it. The new listing function will look for a branch listing and only add that to the list of worktrees. --- lua/neogit/lib/git/worktree.lua | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index 9fb46002e..890a46fc8 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -48,20 +48,22 @@ function M.list(opts) local list = git.cli.worktree.list.args("--porcelain").call({ hidden = true }).stdout local worktrees = {} - for i = 1, #list, 3 do - local path = list[i]:match("^worktree (.-)$") - local head = list[i]:match("^HEAD (.-)$") - local type, ref = list[i + 2]:match("^([^ ]+) (.+)$") + for i = 1, #list, 1 do + if list[i]:match("^branch.*$") then + local path = list[i - 2]:match("^worktree (.-)$") + local head = list[i - 1]:match("^HEAD (.-)$") + local type, ref = list[i]:match("^([^ ]+) (.+)$") - if path then - local main = Path.new(path, ".git"):is_dir() - table.insert(worktrees, { - head = head, - type = type, - ref = ref, - main = main, - path = path, - }) + if path then + local main = Path.new(path, ".git"):is_file() + table.insert(worktrees, { + head = head, + type = type, + ref = ref, + main = main, + path = path, + }) + end end end From 4f8411786e304b370835117e37b81620a79dbf14 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:06:02 -0600 Subject: [PATCH 069/437] Remove duplicated `PopupData` definition This is a subset of the `PopupData` defined in `popup/init.lua` and is throwing LSP warnings. --- lua/neogit/lib/popup/builder.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 5ffa0da1c..f5d28ef5d 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -5,9 +5,6 @@ local notification = require("neogit.lib.notification") local M = {} ----@class PopupData ----@field state PopupState - ---@class PopupState ---@field name string ---@field args PopupOption[]|PopupSwitch[]|PopupHeading[] From 5781297eed487b1666bc4eceaa6a77591101c48a Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:08:47 -0600 Subject: [PATCH 070/437] Fix config entry type --- lua/neogit/lib/popup/builder.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index f5d28ef5d..3fb00f4a5 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -55,7 +55,7 @@ local M = {} ---@field id string ---@field key string ---@field name string ----@field entry string +---@field entry ConfigEntry ---@field value string ---@field type string From ab40701e437dac7e8e3324cb60081a79d6733ae6 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:47:18 -0600 Subject: [PATCH 071/437] Clean up some of the builder annotations --- lua/neogit/lib/popup/builder.lua | 75 +++++++++++++++++++++----------- lua/neogit/lib/popup/init.lua | 1 + 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 3fb00f4a5..150064820 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -76,7 +76,7 @@ local M = {} ---@field user_input boolean If true, allows user to customise the value of the cli flag ---@field dependant string[] other switches/options with a state dependency on this one ----@class PopupOptionsOpts +---@class PopupOptionOpts ---@field key_prefix string Allows overwriting the default '=' to set option ---@field cli_prefix string Allows overwriting the default '--' cli prefix ---@field choices table Table of predefined choices that a user can select for option @@ -87,7 +87,7 @@ local M = {} ---@class PopupConfigOpts ---@field options { display: string, value: string, config: function? } ---@field passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. +--- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. function M.new(builder_fn) local instance = { @@ -107,17 +107,23 @@ function M.new(builder_fn) return instance end -function M:name(x) - self.state.name = x +-- Set the popup's name. This must be set for all popups. +---@param name string The name +---@return self +function M:name(name) + self.state.name = name return self end -function M:env(x) - self.state.env = x or {} +-- Set initial context for the popup +---@param env table The initial context +---@return self +function M:env(env) + self.state.env = env or {} return self end ----Adds new column to actions section of popup +-- adds a new column to the actions section of the popup ---@param heading string? ---@return self function M:new_action_group(heading) @@ -125,7 +131,7 @@ function M:new_action_group(heading) return self end ----Conditionally adds new column to actions section of popup +-- Conditionally adds a new column to the actions section of the popup ---@param cond boolean ---@param heading string? ---@return self @@ -137,7 +143,7 @@ function M:new_action_group_if(cond, heading) return self end ----Adds new heading to current column within actions section of popup +-- adds a new heading to current column within the actions section of the popup ---@param heading string ---@return self function M:group_heading(heading) @@ -145,7 +151,7 @@ function M:group_heading(heading) return self end ----Conditionally adds new heading to current column within actions section of popup +-- Conditionally adds a new heading to current column within the actions section of the popup ---@param cond boolean ---@param heading string ---@return self @@ -157,10 +163,11 @@ function M:group_heading_if(cond, heading) return self end +-- Adds a switch to the popup ---@param key string Which key triggers switch ---@param cli string Git cli flag to use ---@param description string Description text to show user ----@param opts PopupSwitchOpts? +---@param opts PopupSwitchOpts? Additional options ---@return self function M:switch(key, cli, description, opts) opts = opts or {} @@ -232,13 +239,13 @@ function M:switch(key, cli, description, opts) return self end --- Conditionally adds a switch. +-- Conditionally adds a switch to the popup ---@see M:switch ----@param cond boolean +---@param cond boolean The condition under which to add the config ---@param key string Which key triggers switch ---@param cli string Git cli flag to use ---@param description string Description text to show user ----@param opts PopupSwitchOpts? +---@param opts PopupSwitchOpts? Additional options ---@return self function M:switch_if(cond, key, cli, description, opts) if cond then @@ -248,10 +255,12 @@ function M:switch_if(cond, key, cli, description, opts) return self end +-- Adds an option to the popup ---@param key string Key for the user to engage option ---@param cli string CLI value used ---@param value string Current value of option ---@param description string Description of option, presented to user +---@param opts PopupOptionOpts? Additional options function M:option(key, cli, value, description, opts) opts = opts or {} @@ -300,7 +309,7 @@ function M:option(key, cli, value, description, opts) return self end --- Adds heading text within Arguments (options/switches) section of popup +-- adds a heading text within Arguments (options/switches) section of the popup ---@param heading string Heading to show ---@return self function M:arg_heading(heading) @@ -308,8 +317,13 @@ function M:arg_heading(heading) return self end +-- Conditionally adds an option to the popup ---@see M:option ----@param cond boolean +---@param cond boolean The condition under which to add the config +---@param key string Which key triggers switch +---@param cli string Git cli flag to use +---@param description string Description text to show user +---@param opts PopupOptionOpts? Additional options ---@return self function M:option_if(cond, key, cli, value, description, opts) if cond then @@ -319,16 +333,18 @@ function M:option_if(cond, key, cli, value, description, opts) return self end ----@param heading string Heading to render within config section of popup +-- adds a heading text with the config section of the popup +---@param heading string Heading to render ---@return self function M:config_heading(heading) table.insert(self.state.config, { heading = heading }) return self end +-- Adds config to the popup ---@param key string Key for user to use that engages config ---@param name string Name of config ----@param options PopupConfigOpts? +---@param options PopupConfigOpts? Additional options ---@return self function M:config(key, name, options) local entry = git.config.get(name) @@ -352,9 +368,12 @@ function M:config(key, name, options) return self end --- Conditionally adds config to popup +-- Conditionally adds config to the popup ---@see M:config ----@param cond boolean +---@param cond boolean The condition under which to add the config +---@param key string Key for user to use that engages config +---@param name string Name of config +---@param options PopupConfigOpts? Additional options ---@return self function M:config_if(cond, key, name, options) if cond then @@ -364,9 +383,10 @@ function M:config_if(cond, key, name, options) return self end +-- Adds an action to the popup ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ----@param callback function Function that gets run in async context +---@param callback fun(popup: PopupData) Function that gets run in async context ---@return self function M:action(keys, description, callback) if type(keys) == "string" then @@ -391,18 +411,23 @@ function M:action(keys, description, callback) return self end --- Conditionally adds action to popup ----@param cond boolean +-- Conditionally adds an action to the popup ---@see M:action +---@param cond boolean The condition under which to add the action +---@param keys string|string[] Key or list of keys for the user to press that runs the action +---@param description string Description of action in UI +---@param callback fun(popup: PopupData) Function that gets run in async context ---@return self -function M:action_if(cond, key, description, callback) +function M:action_if(cond, keys, description, callback) if cond then - return self:action(key, description, callback) + return self:action(keys, description, callback) end return self end +-- Builds the popup +---@return PopupData # The popup function M:build() if self.state.name == nil then error("A popup needs to have a name!") diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 9ffb3fc71..700734a1e 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -26,6 +26,7 @@ local ui = require("neogit.lib.popup.ui") ---@field buffer Buffer local M = {} +-- Create a new popup builder function M.builder() return PopupBuilder.new(M.new) end From 563b4d6b83da3582adc5422cfe026b754b9ad2b2 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 18:40:55 -0600 Subject: [PATCH 072/437] Flesh out some missing builder annotations --- lua/neogit/lib/popup/builder.lua | 20 ++++++++++++++++---- lua/neogit/lib/popup/init.lua | 17 ++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 150064820..77066b185 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -3,6 +3,7 @@ local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") +---@class PopupBuilder local M = {} ---@class PopupState @@ -31,7 +32,7 @@ local M = {} ---@field key_prefix string ---@field separator string ---@field type string ----@field value string +---@field value string? ---@class PopupSwitch ---@field cli string @@ -56,8 +57,17 @@ local M = {} ---@field key string ---@field name string ---@field entry ConfigEntry ----@field value string +---@field value string? ---@field type string +---@field passive boolean? +---@field options PopupConfigOption[]? +---@field callback fun(popup: PopupData, config: self)? Called after the config is set +---@field fn fun(popup: PopupData, config: self)? If set, overrides the actual config setting behavior + +---@class PopupConfigOption An option that can be selected as a value for a config +---@field display string The display name for the option +---@field value string The value to set in git config +---@field condition fun()? An option predicate to determine if the option should appear ---@class PopupAction ---@field keys table @@ -85,8 +95,10 @@ local M = {} ---@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with ---@class PopupConfigOpts ----@field options { display: string, value: string, config: function? } ----@field passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI +---@field options PopupConfigOption[] +---@field fn fun(popup: PopupData, config: self) If set, overrides the actual config setting behavior +---@field callback fun(popup: PopupData, config: PopupConfig)? A callback that will be invoked after the config is set +---@field passive boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. function M.new(builder_fn) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 700734a1e..ae5c994ce 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -27,6 +27,7 @@ local ui = require("neogit.lib.popup.ui") local M = {} -- Create a new popup builder +---@return PopupBuilder function M.builder() return PopupBuilder.new(M.new) end @@ -92,7 +93,7 @@ function M:close() end -- Toggle a switch on/off ----@param switch table +---@param switch PopupSwitch ---@return nil function M:toggle_switch(switch) if switch.options then @@ -134,8 +135,10 @@ function M:toggle_switch(switch) for _, var in ipairs(self.state.args) do if switch.incompatible[var.cli] then if var.type == "switch" then + ---@cast var PopupSwitch self:disable_switch(var) elseif var.type == "option" then + ---@cast var PopupOption self:disable_option(var) end end @@ -147,8 +150,10 @@ function M:toggle_switch(switch) for _, var in ipairs(self.state.args) do if switch.dependant[var.cli] then if var.type == "switch" then + ---@cast var PopupSwitch self:disable_switch(var) elseif var.type == "option" then + ---@cast var PopupOption self:disable_option(var) end end @@ -157,7 +162,7 @@ function M:toggle_switch(switch) end -- Toggle an option on/off and set it's value ----@param option table +---@param option PopupOption ---@param value? string ---@return nil function M:set_option(option, value) @@ -225,7 +230,7 @@ function M:set_option(option, value) end ---Disables a switch. ----@param switch table +---@param switch PopupSwitch function M:disable_switch(switch) if switch.enabled then self:toggle_switch(switch) @@ -235,7 +240,7 @@ end ---Disables an option, setting its value to "". Doesn't use the default, which ---is important to ensure that we don't use incompatible switches/options ---together. ----@param option table +---@param option PopupOption function M:disable_option(option) if option.value and option.value ~= "" then self:set_option(option, "") @@ -243,7 +248,7 @@ function M:disable_option(option) end -- Set a config value ----@param config table +---@param config PopupConfig ---@return nil function M:set_config(config) if config.options then @@ -315,8 +320,10 @@ function M:mappings() arg_prefixes[arg.key_prefix] = true mappings.n[arg.id] = a.void(function() if arg.type == "switch" then + ---@cast arg PopupSwitch self:toggle_switch(arg) elseif arg.type == "option" then + ---@cast arg PopupOption self:set_option(arg) end From bee092aa9e802181771faf8108d2c11dc9bcacc3 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Wed, 6 Nov 2024 12:52:23 -0600 Subject: [PATCH 073/437] Clean up trailing whitespaces in docs --- doc/neogit.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 4aada53d3..3bec9a8a8 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1249,7 +1249,7 @@ Arguments: *neogit_pull_popup_args* 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 + See pull.rebase, branch..rebase and branch.autoSetupRebase if you want to make git pull always use --rebase instead of merging. Note: @@ -1850,4 +1850,3 @@ The following keys, in normal mode, will act on the commit under the cursor: ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: - From b917302de3f02aeb62cf769d26ad593fa3d6dfd7 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Wed, 6 Nov 2024 01:51:30 -0600 Subject: [PATCH 074/437] Mention the possibility of custom popups in the docs --- doc/neogit.txt | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 3bec9a8a8..ea763b368 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1848,5 +1848,55 @@ The following keys, in normal mode, will act on the commit under the cursor: • `b` Insert breakpoint • `` Open current commit in Commit Buffer +============================================================================== +Custom Popups *neogit_custom_popups* + +You can leverage Neogit's infrastructure to create your own popups and +actions. For example: >lua + local function my_action(popup) + -- You can use Neogit's git abstraction for many common operations + -- local git = require("neogit.lib.git") + local input = require("neogit.lib.input") + local user_input = input.get_user_input("User-specified free text for the action") + local cli_args = popup:get_arguments() + vim.notify( + "Hello from my custom action!\n" + .. "CLI args: `" .. table.concat(cli_args, " ") .. "`\n" + .. "User input: `" .. user_input .. "`") + end + + function create_custom_popup() + local popup = require("neogit.lib.popup") + local p = popup + .builder() + :name("NeogitMyCustomPopup") + :switch("s", "my-switch", "My switch") + :option("o", "my-option", "default_value", "My option", { key_prefix = "-" }) + :new_action_group("My actions") + :action("a", "Some action", my_action) + :build() + + p:show() + + return p + end + + require("neogit") +< + +Look at the builder APIs in `lua/neogit/lib/popup/builder.lua`, the built-in +popups/actions in `lua/neogit/popups/*`, and the git APIs in +`lua/neogit/lib/git` for more information (and inspiration!). + +To access your custom popup via a keymapping, you can include a mapping when +calling the setup function: >lua + require("neogit").setup({ + mappings = { + status = { + ["A"] = create_custom_popup, + }, + }, + }) +< ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: From 43fa47fb61773b0d90a78ebc2521ea8faaeebd86 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 31 Oct 2024 12:03:37 +0100 Subject: [PATCH 075/437] Breaking!: Use `gitcommit` and `gitrebase` filetypes instead of custom NeogitCommitMessage -> gitcommit NeogitBranchDescription -> gitcommit NeogitMergeMessage -> gitcommit NeogitTagMessage -> gitcommit NeogitRebaseTodo -> gitrebase --- lua/neogit/buffers/editor/init.lua | 29 ++--------------------- lua/neogit/buffers/rebase_editor/init.lua | 17 ++----------- 2 files changed, 4 insertions(+), 42 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index dbfff356e..9d87b3147 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -12,13 +12,6 @@ local pad = util.pad_right local M = {} -local filetypes = { - ["COMMIT_EDITMSG"] = "NeogitCommitMessage", - ["MERGE_MSG"] = "NeogitMergeMessage", - ["TAG_EDITMSG"] = "NeogitTagMessage", - ["EDIT_DESCRIPTION"] = "NeogitBranchDescription", -} - ---@class EditorBuffer ---@field filename string filename of buffer ---@field on_unload function callback invoked when buffer is unloaded @@ -70,12 +63,9 @@ function M:open(kind) return message end - local filetype = filetypes[self.filename:match("[%u_]+$")] or "NeogitEditor" - logger.debug("[EDITOR] Filetype " .. filetype) - self.buffer = Buffer.create { name = self.filename, - filetype = filetype, + filetype = "gitcommit", load = true, spell_check = config.values.commit_editor.spell_check, buftype = "", @@ -96,10 +86,8 @@ function M:open(kind) end end, }, - on_detach = function(buffer) + on_detach = function() logger.debug("[EDITOR] Cleaning Up") - pcall(vim.treesitter.stop, buffer.handle) - if self.on_unload then logger.debug("[EDITOR] Running on_unload callback") self.on_unload(aborted and 1 or 0) @@ -171,19 +159,6 @@ function M:open(kind) vim.cmd(":startinsert") end - -- Source runtime ftplugin - vim.cmd.source("$VIMRUNTIME/ftplugin/gitcommit.vim") - - -- Apply syntax highlighting - local ok, _ = pcall(vim.treesitter.language.inspect, "gitcommit") - if ok then - logger.debug("[EDITOR] Loading treesitter for gitcommit") - vim.treesitter.start(buffer.handle, "gitcommit") - else - logger.debug("[EDITOR] Loading syntax for gitcommit") - vim.cmd.source("$VIMRUNTIME/syntax/gitcommit.vim") - end - if git.branch.current() then vim.fn.matchadd("NeogitBranch", git.branch.current(), 100) end diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 671a4b93b..c4bc9b393 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -72,7 +72,7 @@ function M:open(kind) self.buffer = Buffer.create { name = self.filename, load = true, - filetype = "NeogitRebaseTodo", + filetype = "gitrebase", buftype = "", status_column = not config.values.disable_signs and "" or nil, kind = kind, @@ -80,9 +80,7 @@ function M:open(kind) disable_line_numbers = config.values.disable_line_numbers, disable_relative_line_numbers = config.values.disable_relative_line_numbers, readonly = false, - on_detach = function(buffer) - pcall(vim.treesitter.stop, buffer.handle) - + on_detach = function() if self.on_unload then self.on_unload(aborted and 1 or 0) end @@ -130,17 +128,6 @@ function M:open(kind) buffer:set_lines(-1, -1, false, help_lines) buffer:write() buffer:move_cursor(1) - - -- Source runtime ftplugin - vim.cmd.source("$VIMRUNTIME/ftplugin/gitrebase.vim") - - -- Apply syntax highlighting - local ok, _ = pcall(vim.treesitter.language.inspect, "git_rebase") - if ok then - vim.treesitter.start(buffer.handle, "git_rebase") - else - vim.cmd.source("$VIMRUNTIME/syntax/gitrebase.vim") - end end, mappings = { i = { From 9615b6ae3b32b33c7a7b464779982b5e18c22cea Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 31 Oct 2024 11:34:44 +0100 Subject: [PATCH 076/437] Change git.config.get() to query both local and global values, and create git.config.get_local() api to get local only. This reflects the existing .get_global() function. --- lua/neogit/buffers/editor/init.lua | 5 +---- lua/neogit/buffers/rebase_editor/init.lua | 5 +---- lua/neogit/buffers/refs_view/ui.lua | 2 +- lua/neogit/lib/git/branch.lua | 2 +- lua/neogit/lib/git/config.lua | 13 ++++++++++++- lua/neogit/lib/git/push.lua | 7 ++----- lua/neogit/lib/git/remote.lua | 2 +- lua/neogit/lib/popup/builder.lua | 2 +- lua/neogit/popups/branch_config/actions.lua | 2 +- lua/neogit/popups/branch_config/init.lua | 2 +- lua/neogit/popups/remote/actions.lua | 8 ++++++-- 11 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 9d87b3147..b33ccd113 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -110,10 +110,7 @@ function M:open(kind) return pad(mapping[name] and mapping[name][1] or "", padding) end - local comment_char = git.config.get("core.commentChar"):read() - or git.config.get_global("core.commentChar"):read() - or "#" - + local comment_char = git.config.get("core.commentChar"):read() or "#" logger.debug("[EDITOR] Using comment character '" .. comment_char .. "'") -- stylua: ignore diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index c4bc9b393..10149c304 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -61,10 +61,7 @@ function M.new(filename, on_unload) end function M:open(kind) - local comment_char = git.config.get("core.commentChar"):read() - or git.config.get_global("core.commentChar"):read() - or "#" - + local comment_char = git.config.get("core.commentChar"):read() or "#" local mapping = config.get_reversed_rebase_editor_maps() local mapping_I = config.get_reversed_rebase_editor_maps_I() local aborted = false diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index d69b1e340..462d8c84a 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -133,7 +133,7 @@ function M.Remotes(remotes, head) text.highlight("NeogitBranch")("Remote "), text.highlight("NeogitRemote")(name, { align_right = max_len }), text.highlight("NeogitBranch")( - string.format(" (%s)", git.config.get(string.format("remote.%s.url", name)):read()) + string.format(" (%s)", git.config.get_local(string.format("remote.%s.url", name)):read()) ), }, head) ) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 426df41af..68407b466 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -166,7 +166,7 @@ function M.pushRemote(branch) branch = branch or M.current() if branch then - local remote = git.config.get("branch." .. branch .. ".pushRemote") + local remote = git.config.get_local("branch." .. branch .. ".pushRemote") if remote:is_set() then return remote.value end diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index bb73b1d1b..b7f6ac866 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -109,7 +109,13 @@ end ---@return ConfigEntry function M.get(key) - return config()[key:lower()] or ConfigEntry.new(key, "", "local") + if M.get_local(key):is_set() then + return M.get_local(key) + elseif M.get_global(key):is_set() then + return M.get_global(key) + else + return ConfigEntry.new(key, "", "local") + end end ---@return ConfigEntry @@ -118,6 +124,11 @@ function M.get_global(key) return ConfigEntry.new(key, result, "global") end +---@return ConfigEntry +function M.get_local(key) + return config()[key:lower()] or ConfigEntry.new(key, "", "local") +end + function M.get_matching(pattern) local matches = {} for key, value in pairs(config()) do diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index c12359ebf..6b2137701 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -21,11 +21,8 @@ function M.auto_setup_remote(branch) end local push_autoSetupRemote = git.config.get("push.autoSetupRemote"):read() - or git.config.get_global("push.autoSetupRemote"):read() - - local push_default = git.config.get("push.default"):read() or git.config.get_global("push.default"):read() - - local branch_remote = git.config.get("branch." .. branch .. ".remote"):read() + local push_default = git.config.get("push.default"):read() + local branch_remote = git.config.get_local("branch." .. branch .. ".remote"):read() return ( push_autoSetupRemote diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index c7ed74b3e..4188fbd99 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -6,7 +6,7 @@ local M = {} -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#LL141C32-L141C32 local function cleanup_push_variables(remote, new_name) - if remote == git.config.get("remote.pushDefault").value then + if remote == git.config.get("remote.pushDefault"):read() then git.config.set("remote.pushDefault", new_name) end diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 77066b185..f61669d1e 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -57,7 +57,7 @@ local M = {} ---@field key string ---@field name string ---@field entry ConfigEntry ----@field value string? +---@field value string ---@field type string ---@field passive boolean? ---@field options PopupConfigOption[]? diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index 73444cc92..9e2dabe35 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -59,7 +59,7 @@ function M.description_config(branch) }) vim.o.eventignore = "" - return git.config.get("branch." .. branch .. ".description"):read() + return git.config.get_local("branch." .. branch .. ".description"):read() end return a.wrap(fn, 2) diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index 6c953b42c..b3920245a 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -7,7 +7,7 @@ local actions = require("neogit.popups.branch_config.actions") function M.create(branch) branch = branch or git.branch.current() local g_pull_rebase = git.config.get_global("pull.rebase") - local pull_rebase_entry = git.config.get("pull.rebase") + local pull_rebase_entry = git.config.get_local("pull.rebase") local pull_rebase = pull_rebase_entry:is_set() and pull_rebase_entry.value or "false" local p = popup diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 7e82f6cad..0f9a7c29b 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -26,9 +26,13 @@ function M.add(popup) return end - local origin = git.config.get("remote.origin.url").value - local host, _, remote = origin:match("([^:/]+)[:/]([^/]+)/(.+)") + local origin = git.config.get("remote.origin.url"):read() + if not origin then + return + end + assert(type(origin) == "string", "remote.origin.url isn't a string") + local host, _, remote = origin:match("([^:/]+)[:/]([^/]+)/(.+)") remote = remote and remote:gsub("%.git$", "") local msg From 965e45fc72314ce9d821c76b832bd0846e83bccc Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 6 Nov 2024 22:34:13 +0100 Subject: [PATCH 077/437] Fix: https://github.com/NeogitOrg/neogit/issues/1544 --- lua/neogit/lib/popup/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index ae5c994ce..2844b20fc 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -295,7 +295,7 @@ function M:mappings() [""] = function() self:close() end, - [""] = function() + [""] = a.void(function() local component = self.buffer.ui:get_interactive_component_under_cursor() if not component then return @@ -310,7 +310,7 @@ function M:mappings() end self:refresh() - end, + end), }, } From 3987b02e6eb309de87e405a555086d259a153c42 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 14:39:55 +0100 Subject: [PATCH 078/437] Add retry mechanism to spec runner --- bin/specs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/bin/specs b/bin/specs index b7c1c8d6b..d20dde53c 100755 --- a/bin/specs +++ b/bin/specs @@ -24,6 +24,7 @@ class Runner # rubocop:disable Style/Documentation @spinner = spinner @length = length @title = test.gsub("spec/", "") + @retries = 0 end def register @@ -32,24 +33,32 @@ class Runner # rubocop:disable Style/Documentation self end - def call(results, failures) + def call(results, failures) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize start! - output, wait = run - results[test] = JSON.parse(output) - time = results[test].dig("summary", "duration").round(3) - - if wait.value.success? - register_success!(time) - else - failures << test - register_failure!(time) + loop do + output, wait = run + results[test] = JSON.parse(output) + + time = results[test].dig("summary", "duration").round(3) + + if wait.value.success? + register_success!(time) + break + elsif retries < 3 + @retries += 1 + register_retry! + else + failures << test + register_failure!(time) + break + end end end private - attr_reader :title, :spinner, :test, :length + attr_reader :title, :spinner, :test, :length, :retries def start! spinner.update(test: COLOR.blue(title)) @@ -69,6 +78,10 @@ class Runner # rubocop:disable Style/Documentation spinner.success(COLOR.green(time)) end + def register_retry! + spinner.update(test: "#{COLOR.yellow(title)} (#{retries})") + end + def register_failure!(time) spinner.update(test: COLOR.red(title)) spinner.error(COLOR.red(time)) From 85a11adbea7a392143aa861d83476ef0ee66e177 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 21:53:25 +0100 Subject: [PATCH 079/437] Rename: Dependant to Dependent Improve docs --- doc/neogit.txt | 23 ++++++++++-- lua/neogit/lib/popup/builder.lua | 63 ++++++++++++++++++-------------- lua/neogit/lib/popup/init.lua | 8 ++-- lua/neogit/popups/log/init.lua | 2 +- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index ea763b368..b331cb1ba 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1852,13 +1852,21 @@ The following keys, in normal mode, will act on the commit under the cursor: Custom Popups *neogit_custom_popups* You can leverage Neogit's infrastructure to create your own popups and -actions. For example: >lua +actions. For example, you can define actions as a function which will take the +popup instance as it's argument: +>lua local function my_action(popup) + -- You can access the popup state (enabled flags) like so: + local cli_args = popup:get_arguments() + -- You can use Neogit's git abstraction for many common operations -- local git = require("neogit.lib.git") + + -- The input library provides some helpful interfaces for getting user + -- input local input = require("neogit.lib.input") local user_input = input.get_user_input("User-specified free text for the action") - local cli_args = popup:get_arguments() + vim.notify( "Hello from my custom action!\n" .. "CLI args: `" .. table.concat(cli_args, " ") .. "`\n" @@ -1870,10 +1878,18 @@ actions. For example: >lua local p = popup .builder() :name("NeogitMyCustomPopup") + -- A switch is a boolean CLI flag, like `--no-verify` :switch("s", "my-switch", "My switch") + -- An "_if" variant exists for builder methods, that takes a boolean + -- as it's first argument. + :switch_if(true, "S", "conditional-switch", "This switch is conditional") + -- Options are CLI flags that have a value, like `--strategy=octopus` :option("o", "my-option", "default_value", "My option", { key_prefix = "-" }) :new_action_group("My actions") :action("a", "Some action", my_action) + -- Data can be stored on the popup instance via the `env`, accessible + -- to the action via `popup.state.env.*` + :env({ some_data = { "like this" } }) :build() p:show() @@ -1889,7 +1905,8 @@ popups/actions in `lua/neogit/popups/*`, and the git APIs in `lua/neogit/lib/git` for more information (and inspiration!). To access your custom popup via a keymapping, you can include a mapping when -calling the setup function: >lua +calling the setup function: +>lua require("neogit").setup({ mappings = { status = { diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index f61669d1e..b1f7579b5 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -4,6 +4,8 @@ local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") ---@class PopupBuilder +---@field state PopupState +---@field builder_fn PopupData local M = {} ---@class PopupState @@ -23,7 +25,7 @@ local M = {} ---@field cli string ---@field cli_prefix string ---@field default string|integer|boolean ----@field dependant table +---@field dependent table ---@field description string ---@field fn function ---@field id string @@ -39,12 +41,12 @@ local M = {} ---@field cli_base string ---@field cli_prefix string ---@field cli_suffix string ----@field dependant table +---@field dependent table ---@field description string ---@field enabled boolean ---@field fn function ---@field id string ----@field incompatible table +---@field incompatible string[] ---@field internal boolean ---@field key string ---@field key_prefix string @@ -70,29 +72,32 @@ local M = {} ---@field condition fun()? An option predicate to determine if the option should appear ---@class PopupAction ----@field keys table +---@field keys string|string[] ---@field description string ---@field callback function ---@class PopupSwitchOpts ----@field enabled boolean Controls if the switch should default to 'on' state ----@field internal boolean Whether the switch is internal to neogit or should be included in the cli command. If `true` we don't include it in the cli command. ----@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with ----@field key_prefix string Allows overwriting the default '-' to toggle switch ----@field cli_prefix string Allows overwriting the default '--' thats used to create the cli flag. Sometimes you may want to use '++' or '-'. ----@field cli_suffix string ----@field options table ----@field value string Allows for pre-building cli flags that can be customised by user input ----@field user_input boolean If true, allows user to customise the value of the cli flag ----@field dependant string[] other switches/options with a state dependency on this one +---@field enabled? boolean Controls if the switch should default to 'on' state +---@field internal? boolean Whether the switch is internal to neogit or should be included in the cli command. If `true` we don't include it in the cli command. +---@field incompatible? string[] A table of strings that represent other cli switches/options that this one cannot be used with +---@field key_prefix? string Allows overwriting the default '-' to toggle switch +---@field cli_prefix? string Allows overwriting the default '--' that's used to create the cli flag. Sometimes you may want to use '++' or '-'. +---@field cli_suffix? string +---@field options? table +---@field value? string Allows for pre-building cli flags that can be customized by user input +---@field user_input? boolean If true, allows user to customize the value of the cli flag +---@field dependent? string[] other switches/options with a state dependency on this one ---@class PopupOptionOpts ----@field key_prefix string Allows overwriting the default '=' to set option ----@field cli_prefix string Allows overwriting the default '--' cli prefix ----@field choices table Table of predefined choices that a user can select for option ----@field default string|integer|boolean Default value for option, if the user attempts to unset value ----@field dependant string[] other switches/options with a state dependency on this one ----@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with +---@field key_prefix? string Allows overwriting the default '=' to set option +---@field cli_prefix? string Allows overwriting the default '--' cli prefix +---@field choices? table Table of predefined choices that a user can select for option +---@field default? string|integer|boolean Default value for option, if the user attempts to unset value +---@field dependent? string[] other switches/options with a state dependency on this one +---@field incompatible? string[] A table of strings that represent other cli switches/options that this one cannot be used with +---@field separator? string Defaults to `=`, separating the key from the value. Some CLI options are weird. +---@field setup? fun(PopupBuilder) function called before rendering +---@field fn? fun() function called - like an action. Used to launch a popup from a popup. ---@class PopupConfigOpts ---@field options PopupConfigOption[] @@ -101,6 +106,8 @@ local M = {} ---@field passive boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. +---@param builder_fn PopupData +---@return PopupBuilder function M.new(builder_fn) local instance = { state = { @@ -196,8 +203,8 @@ function M:switch(key, cli, description, opts) opts.incompatible = {} end - if opts.dependant == nil then - opts.dependant = {} + if opts.dependent == nil then + opts.dependent = {} end if opts.key_prefix == nil then @@ -245,7 +252,7 @@ function M:switch(key, cli, description, opts) cli_suffix = opts.cli_suffix, options = opts.options, incompatible = util.build_reverse_lookup(opts.incompatible), - dependant = util.build_reverse_lookup(opts.dependant), + dependent = util.build_reverse_lookup(opts.dependent), }) return self @@ -288,8 +295,8 @@ function M:option(key, cli, value, description, opts) opts.separator = "=" end - if opts.dependant == nil then - opts.dependant = {} + if opts.dependent == nil then + opts.dependent = {} end if opts.incompatible == nil then @@ -313,7 +320,7 @@ function M:option(key, cli, value, description, opts) choices = opts.choices, default = opts.default, separator = opts.separator, - dependant = util.build_reverse_lookup(opts.dependant), + dependent = util.build_reverse_lookup(opts.dependent), incompatible = util.build_reverse_lookup(opts.incompatible), fn = opts.fn, }) @@ -398,7 +405,7 @@ end -- Adds an action to the popup ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ----@param callback fun(popup: PopupData) Function that gets run in async context +---@param callback? fun(popup: PopupData) Function that gets run in async context ---@return self function M:action(keys, description, callback) if type(keys) == "string" then @@ -428,7 +435,7 @@ end ---@param cond boolean The condition under which to add the action ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ----@param callback fun(popup: PopupData) Function that gets run in async context +---@param callback? fun(popup: PopupData) Function that gets run in async context ---@return self function M:action_if(cond, keys, description, callback) if cond then diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 2844b20fc..0728b0083 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -146,9 +146,9 @@ function M:toggle_switch(switch) end -- Ensure that switches/options that depend on this one are also disabled - if not switch.enabled and #switch.dependant > 0 then + if not switch.enabled and #switch.dependent > 0 then for _, var in ipairs(self.state.args) do - if switch.dependant[var.cli] then + if switch.dependent[var.cli] then if var.type == "switch" then ---@cast var PopupSwitch self:disable_switch(var) @@ -216,9 +216,9 @@ function M:set_option(option, value) end -- Ensure that switches/options that depend on this one are also disabled - if option.value and option.value ~= "" and #option.dependant > 0 then + if option.value and option.value ~= "" and #option.dependent > 0 then for _, var in ipairs(self.state.args) do - if option.dependant[var.cli] then + if option.dependent[var.cli] then if var.type == "switch" then self:disable_switch(var) elseif var.type == "option" then diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index 7d896c2ea..700cb2495 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -62,7 +62,7 @@ function M.create() enabled = true, internal = true, incompatible = { "reverse" }, - dependant = { "color" }, + dependent = { "color" }, }) :switch_if( config.values.graph_style == "ascii" or config.values.graph_style == "kitty", From 176213c643012bea1fe094b30c641059e1f4217a Mon Sep 17 00:00:00 2001 From: Will Lynas <43895423+will-lynas@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:05:11 +0000 Subject: [PATCH 080/437] Add kitty graph style to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b84ce9d6..2078efe89 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ neogit.setup { }, -- "ascii" is the graph the git CLI generates -- "unicode" is the graph like https://github.com/rbong/vim-flog + -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim graph_style = "ascii", -- Used to generate URL's for branch popup action "pull request". git_services = { From 8c56fc126c22bd7a258f9af27b6a7a3b5058a70a Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 22:31:08 +0100 Subject: [PATCH 081/437] Fix: https://github.com/NeogitOrg/neogit/issues/1545 Previous patch was eaten by the git ghost. --- lua/neogit/lib/popup/init.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 0728b0083..0cb6bcc6d 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -171,9 +171,13 @@ function M:set_option(option, value) option.value = value elseif option.choices then if not option.value or option.value == "" then + local eventignore = vim.o.eventignore + vim.o.eventignore = "WinLeave" local choice = FuzzyFinderBuffer.new(option.choices):open_async { prompt_prefix = option.description, } + vim.o.eventignore = eventignore + if choice then option.value = choice else From 36c3843e6dce4a0f67bc2ba111698e6216536eaa Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 22:44:44 +0100 Subject: [PATCH 082/437] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edd1d2d21..904f4e150 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ neogit.setup { }, -- "ascii" is the graph the git CLI generates -- "unicode" is the graph like https://github.com/rbong/vim-flog - -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim + -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim - use https://github.com/rbong/flog-symbols if you don't use Kitty graph_style = "ascii", -- Used to generate URL's for branch popup action "pull request". git_services = { From cea49ba626fb1df9b2b9dc4808ca9474c797753c Mon Sep 17 00:00:00 2001 From: SheffeyG <57262511+SheffeyG@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:54:30 +0800 Subject: [PATCH 083/437] Update hl.lua --- lua/neogit/lib/hl.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index bd9620563..55ee03351 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -89,13 +89,13 @@ end local function make_palette(config) local bg = Color.from_hex(get_bg("Normal") or (vim.o.bg == "dark" and "#22252A" or "#eeeeee")) local fg = Color.from_hex((vim.o.bg == "dark" and "#fcfcfc" or "#22252A")) - local red = Color.from_hex(get_fg("Error") or "#E06C75") - local orange = Color.from_hex(get_fg("SpecialChar") or "#ffcb6b") - local yellow = Color.from_hex(get_fg("PreProc") or "#FFE082") - local green = Color.from_hex(get_fg("String") or "#C3E88D") - local cyan = Color.from_hex(get_fg("Operator") or "#89ddff") - local blue = Color.from_hex(get_fg("Macro") or "#82AAFF") - local purple = Color.from_hex(get_fg("Include") or "#C792EA") + local red = Color.from_hex(config.highlight.red or get_fg("Error") or "#E06C75") + local orange = Color.from_hex(config.highlight.orange or get_fg("SpecialChar") or "#ffcb6b") + local yellow = Color.from_hex(config.highlight.yellow or get_fg("PreProc") or "#FFE082") + local green = Color.from_hex(config.highlight.green or get_fg("String") or "#C3E88D") + local cyan = Color.from_hex(config.highlight.cyan or get_fg("Operator") or "#89ddff") + local blue = Color.from_hex(config.highlight.blue or get_fg("Macro") or "#82AAFF") + local purple = Color.from_hex(config.highlight.purple or get_fg("Include") or "#C792EA") local bg_factor = vim.o.bg == "dark" and 1 or -1 From 9470bfd4fcf45bb0ecd1da25c21a63d89dcb413a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 26 Oct 2024 19:14:08 +0200 Subject: [PATCH 084/437] Add a spinner to show a process is running. --- lua/neogit/process.lua | 43 ++++++++++++++++++++++------ lua/neogit/spinner.lua | 65 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 lua/neogit/spinner.lua diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index f3c54174d..d6b305438 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -5,6 +5,9 @@ local config = require("neogit.config") local logger = require("neogit.logger") local util = require("neogit.lib.util") +local ProcessBuffer = require("neogit.buffers.process") +local Spinner = require("neogit.spinner") + local api = vim.api local fn = vim.fn @@ -17,7 +20,6 @@ local function mask_command(cmd) end ---@class ProcessOpts ----@field buffer ProcessBuffer|nil ---@field cmd string[] ---@field cwd string|nil ---@field env table|nil @@ -44,6 +46,7 @@ end ---@field on_partial_line fun(process: Process, data: string)|nil callback on complete lines ---@field on_error (fun(res: ProcessResult): boolean) Intercept the error externally, returning false prevents the error from being logged ---@field defer_show_preview_buffers fun(): nil +---@field spinner Spinner|nil local Process = {} Process.__index = Process @@ -75,6 +78,7 @@ function ProcessResult:trim() return self end +---@return ProcessResult function ProcessResult:remove_ansi() self.stdout = vim.tbl_map(remove_ansi_escape_codes, self.stdout) self.stderr = vim.tbl_map(remove_ansi_escape_codes, self.stderr) @@ -87,8 +91,6 @@ ProcessResult.__index = ProcessResult ---@param process ProcessOpts ---@return Process function Process.new(process) - process.buffer = require("neogit.buffers.process"):new(process) - return setmetatable(process, Process) ---@class Process end @@ -103,7 +105,26 @@ function Process.hide_preview_buffers() end function Process:show_console() - self.buffer:show() + if self.buffer then + self.buffer:show() + end +end + +function Process:show_spinner() + if self.suppress_console or self.spinner then + return + end + + self.spinner = Spinner.new("Running: " .. mask_command(table.concat(self.cmd, " "))) + self.spinner:start() +end + +function Process:hide_spinner() + if not self.spinner then + return + end + + self.spinner:stop() end function Process:start_timer() @@ -116,6 +137,7 @@ function Process:start_timer() self.timer = timer local timeout = assert(self.git_hook and 800 or config.values.console_timeout, "no timeout") + timer:start( timeout, 0, @@ -267,12 +289,14 @@ function Process:spawn(cb) self:on_partial_line(line) end - self.buffer:append_partial(line) + if self.buffer then + self.buffer:append_partial(line) + end end local stdout_on_line = function(line) insert(res.stdout, line) - if not self.suppress_console then + if self.buffer and not self.suppress_console then self.buffer:append(line) end end @@ -281,7 +305,7 @@ function Process:spawn(cb) local stderr_on_line = function(line) insert(res.stderr, line) - if not self.suppress_console then + if self.buffer and not self.suppress_console then self.buffer:append(line) end end @@ -297,11 +321,12 @@ function Process:spawn(cb) processes[self.job] = nil self.result = res self:stop_timer() + self:hide_spinner() stdout_cleanup() stderr_cleanup() - if not self.suppress_console then + if self.buffer and not self.suppress_console then self.buffer:append(string.format("Process exited with code: %d", code)) if not self.buffer:is_visible() and code > 0 and self.on_error(res) then @@ -360,6 +385,8 @@ function Process:spawn(cb) self.stdin = job if not hide_console then + self.buffer = ProcessBuffer:new(self) + self:show_spinner() self:start_timer() end diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua new file mode 100644 index 000000000..fdf1bf43d --- /dev/null +++ b/lua/neogit/spinner.lua @@ -0,0 +1,65 @@ +---@class Spinner +---@field text string +---@field count number +---@field interval number +---@field pattern string[] +---@field timer uv_timer_t +local Spinner = {} +Spinner.__index = Spinner + +---@return Spinner +function Spinner.new(text) + local instance = { + text = text, + interval = 100, + count = 0, + timer = nil, + pattern = { + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏", + }, + } + + return setmetatable(instance, Spinner) +end + +function Spinner:start() + if not self.timer then + self.timer = vim.uv.new_timer() + self.timer:start( + 0, + self.interval, + vim.schedule_wrap(function() + self.count = self.count + 1 + local step = self.pattern[(self.count % #self.pattern) + 1] + vim.cmd(string.format("redraw | echomsg '%s %s'", step, self.text)) + end) + ) + end +end + +function Spinner:stop() + if self.timer then + local timer = self.timer + self.timer = nil + timer:stop() + + if not timer:is_closing() then + timer:close() + end + end + + vim.schedule(function() + vim.cmd("redraw | echomsg ''") + end) +end + +return Spinner From a682f8f10aa58e54ecba6d16eab38fd40ef0bc67 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 26 Oct 2024 19:15:40 +0200 Subject: [PATCH 085/437] Give the spinner some grace period --- lua/neogit/spinner.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index fdf1bf43d..7654ff0da 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -35,7 +35,7 @@ function Spinner:start() if not self.timer then self.timer = vim.uv.new_timer() self.timer:start( - 0, + 250, self.interval, vim.schedule_wrap(function() self.count = self.count + 1 From f4f0baacbc7ccec9919766d9a65470779b8a3d21 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 29 Oct 2024 13:41:25 +0100 Subject: [PATCH 086/437] tweak spinner text --- 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 d6b305438..842f3bd26 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -115,7 +115,7 @@ function Process:show_spinner() return end - self.spinner = Spinner.new("Running: " .. mask_command(table.concat(self.cmd, " "))) + self.spinner = Spinner.new(mask_command(table.concat(self.cmd, " "))) self.spinner:start() end From 36446c35c80d092c9e04d1b354e50388524e4cf3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 31 Oct 2024 14:59:46 +0100 Subject: [PATCH 087/437] Add config for spinner --- lua/neogit/config.lua | 2 ++ lua/neogit/process.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index b0e0bee72..60db9ecd3 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -306,6 +306,7 @@ end ---@field git_services? table Templartes to use when opening a pull request for a branch ---@field fetch_after_checkout? boolean Perform a fetch if the newly checked out branch has an upstream or pushRemote set ---@field telescope_sorter? function The sorter telescope will use +---@field process_spinner? boolean Hide/Show the process spinner ---@field disable_insert_on_commit? boolean|"auto" Disable automatically entering insert mode in commit dialogues ---@field use_per_project_settings? boolean Scope persisted settings on a per-project basis ---@field remember_settings? boolean Whether neogit should persist flags from popups, e.g. git push flags @@ -349,6 +350,7 @@ function M.get_default_values() disable_context_highlighting = false, disable_signs = false, graph_style = "ascii", + process_spinner = true, filewatcher = { enabled = true, }, diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 842f3bd26..afa6ebec7 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -111,7 +111,7 @@ function Process:show_console() end function Process:show_spinner() - if self.suppress_console or self.spinner then + if not config.values.process_spinner or self.suppress_console or self.spinner then return end From 530b4a8fa33e56e14b4f2f88d14c11b5f8d2ce7a Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 31 Oct 2024 14:59:54 +0100 Subject: [PATCH 088/437] Add annotations for spinner --- lua/neogit/spinner.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index 7654ff0da..be6f06b0c 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -4,6 +4,8 @@ ---@field interval number ---@field pattern string[] ---@field timer uv_timer_t +---@field start fun(self) +---@field stop fun(self) local Spinner = {} Spinner.__index = Spinner From df58c8cbe43874561a40b1cd5b32efe049f9d185 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 6 Nov 2024 22:40:26 +0100 Subject: [PATCH 089/437] capture and re-use existing eventignore value --- lua/neogit/popups/log/actions.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index a70bd35b1..4f054d2a6 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -148,12 +148,13 @@ function M.limit_to_files() return "" end + local eventignore = vim.o.eventignore vim.o.eventignore = "WinLeave" local files = FuzzyFinderBuffer.new(git.files.all_tree { with_dir = true }):open_async { allow_multi = true, refocus_status = false, } - vim.o.eventignore = "" + vim.o.eventignore = eventignore if not files or vim.tbl_isempty(files) then popup.state.env.files = nil From 0f1f5894c140ceba94fa4e38184962b619874d5b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 6 Nov 2024 22:40:45 +0100 Subject: [PATCH 090/437] Types --- lua/neogit/lib/buffer.lua | 2 +- lua/neogit/lib/ui/component.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 48504b037..8cc5a95c3 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -156,7 +156,7 @@ function Buffer:set_text(first_line, last_line, first_col, last_col, lines) api.nvim_buf_set_text(self.handle, first_line, first_col, last_line, last_col, lines) end ----@param line nil|number|number[] +---@param line nil|integer|integer[] function Buffer:move_cursor(line) if not line or not self:is_focused() then return diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 82a55d2cc..19e886a09 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -37,6 +37,7 @@ local default_component_options = { ---@field id string|nil local Component = {} +---@return integer, integer function Component:row_range_abs() return self.position.row_start, self.position.row_end end From 64dfcd3c7c77a2f7d1f4e37d94f818c8df56106e Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 6 Nov 2024 22:41:34 +0100 Subject: [PATCH 091/437] Allow removing untracked directories when showUntrackedFiles=normal fixes: https://github.com/NeogitOrg/neogit/issues/1533 --- lua/neogit/buffers/status/actions.lua | 40 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 2820a93c2..9136fe902 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -13,15 +13,32 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local fn = vim.fn local api = vim.api +local function cleanup_dir(dir) + if vim.in_fast_event() then + a.util.scheduler() + end + + for name, type in vim.fs.dir(dir, { depth = math.huge }) do + if type == "file" then + local bufnr = fn.bufnr(name) + if bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = false }) + end + end + end + + fn.delete(dir, "rf") +end + local function cleanup_items(...) if vim.in_fast_event() then a.util.scheduler() end for _, item in ipairs { ... } do - local bufnr = fn.bufexists(item.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) + local bufnr = fn.bufnr(item.name) + if bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = false }) end fn.delete(item.name) @@ -659,11 +676,20 @@ M.n_discard = function(self) if selection.item and selection.item.first == fn.line(".") then -- Discard File if section == "untracked" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - cleanup_items(selection.item) - end + local mode = git.config.get("status.showUntrackedFiles"):read() + refresh = { update_diffs = { "untracked:" .. selection.item.name } } + if mode == "all" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + cleanup_items(selection.item) + end + else + message = ("Recursively discard %q?"):format(selection.item.name) + action = function() + cleanup_dir(selection.item.name) + end + end elseif section == "unstaged" then if selection.item.mode:match("^[UAD][UAD]") then choices = { "&ours", "&theirs", "&conflict", "&abort" } From 5673f82aa6e73d8d6c979a2650164b47954ab59d Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 10:51:55 +0100 Subject: [PATCH 092/437] Add "try" util --- lua/neogit/lib/util.lua | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 0d6809dc8..5f6b4e8c6 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -600,9 +600,8 @@ end ---@param winid integer ---@param force boolean function M.safe_win_close(winid, force) - local ok, err = pcall(vim.api.nvim_win_close, winid, force) - if not ok then - require("neogit.logger").error(err) + local success = M.try(vim.api.nvim_win_close, winid, force) + if not success then pcall(vim.cmd, "b#") end end @@ -611,4 +610,17 @@ function M.weak_table(mode) return setmetatable({}, { __mode = mode or "k" }) end +---@param fn fun(...): any +---@param ...any +---@return boolean|any +function M.try(fn, ...) + local ok, result = pcall(fn, ...) + if not ok then + require("neogit.logger").error(result) + return false + else + return result or true + end +end + return M From 5f690ba0c9ce3455fc44ef56aa8ec6782f656205 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Nov 2024 10:52:09 +0100 Subject: [PATCH 093/437] Add note --- lua/neogit/buffers/stash_list_view/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/stash_list_view/init.lua b/lua/neogit/buffers/stash_list_view/init.lua index adeb08d05..9abe7dad9 100644 --- a/lua/neogit/buffers/stash_list_view/init.lua +++ b/lua/neogit/buffers/stash_list_view/init.lua @@ -42,6 +42,7 @@ function M:open() mappings = { v = { [popups.mapping_for("CherryPickPopup")] = function() + -- TODO: implement -- local stash = self.buffer.ui:get_commit_under_cursor()[1] -- if stash then -- local stash_item = util.find(self.stashes, function(s) From c26bea435c55f5bf9d2a1947b790e484955b31df Mon Sep 17 00:00:00 2001 From: Steve Beaulac Date: Tue, 12 Nov 2024 10:51:07 -0500 Subject: [PATCH 094/437] fix: prevent deletion of the main worktree Ensure the main worktree, which contains a .git directory, is not deleted by filtering it out from the list of worktrees. Mark any worktree with a .git directory as the main worktree to protect it from deletion. --- lua/neogit/lib/git/worktree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index 890a46fc8..64700c2c1 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -55,7 +55,7 @@ function M.list(opts) local type, ref = list[i]:match("^([^ ]+) (.+)$") if path then - local main = Path.new(path, ".git"):is_file() + local main = Path.new(path, ".git"):is_dir() table.insert(worktrees, { head = head, type = type, From 74199cfd10aaf27b2a835c165d953ba27dbe8e35 Mon Sep 17 00:00:00 2001 From: David Mejorado Date: Wed, 13 Nov 2024 09:16:15 -0800 Subject: [PATCH 095/437] Make spinner step messages transient Using `echom` pushes the messages into the message history, which means that if the git operation is long enough, we'll pollute the history with redundant messages. For some context, those messages are useful to explore the output of different processes. You can explore the history using the built-in `:messages` command, or you can use something like `:Message` from vim-scriptease which sends the message history into the quickfix window. Since it would be great to keep at least one entry in the history, in this commit I'm adding one entry to the messages history, prefixed with `[neogit]` and then replace the spinner step messages with `echo`, which also prints the message but it doesn't save it in the history. --- lua/neogit/spinner.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index be6f06b0c..502987900 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -36,13 +36,15 @@ end function Spinner:start() if not self.timer then self.timer = vim.uv.new_timer() + vim.cmd(string.format("redraw | echomsg '[neogit] %s'", self.text)) + self.timer:start( 250, self.interval, vim.schedule_wrap(function() self.count = self.count + 1 local step = self.pattern[(self.count % #self.pattern) + 1] - vim.cmd(string.format("redraw | echomsg '%s %s'", step, self.text)) + vim.cmd(string.format("redraw | echo '%s %s'", step, self.text)) end) ) end From 426fb998d1556030a20123d1367b54343c33731e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Nov 2024 15:09:26 +0100 Subject: [PATCH 096/437] Remove "echomsg" to prevent hit-enter-prompt with log buffer. --- lua/neogit/spinner.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index 502987900..5f3f83120 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -36,8 +36,6 @@ end function Spinner:start() if not self.timer then self.timer = vim.uv.new_timer() - vim.cmd(string.format("redraw | echomsg '[neogit] %s'", self.text)) - self.timer:start( 250, self.interval, From 419f08bbdfedcd7a85c7f96b3367eb2a63366ea0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Nov 2024 15:10:00 +0100 Subject: [PATCH 097/437] Move status maps to inside the open function so it is evaluated at the right time. --- lua/neogit/buffers/process/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 4f46aa46d..aceff178e 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -1,6 +1,5 @@ local Buffer = require("neogit.lib.buffer") local config = require("neogit.config") -local status_maps = require("neogit.config").get_reversed_status_maps() ---@class ProcessBuffer ---@field content string[] @@ -106,6 +105,8 @@ function M:open() return self end + local status_maps = config.get_reversed_status_maps() + self.buffer = Buffer.create { name = "NeogitConsole", filetype = "NeogitConsole", From c59bddafd83c19eb024367fec1a099b3ea7e6b64 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Nov 2024 22:11:37 +0100 Subject: [PATCH 098/437] fix: when non-tab buffer has a header, ensure it's closed with the buffer Fixes: https://github.com/NeogitOrg/neogit/issues/1540 --- lua/neogit/lib/buffer.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8cc5a95c3..8bcea5d0e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -11,6 +11,7 @@ local Path = require("plenary.path") ---@class Buffer ---@field handle number ---@field win_handle number +---@field header_win_handle number? ---@field namespaces table ---@field autocmd_group number ---@field ui Ui @@ -201,6 +202,10 @@ function Buffer:close(force) force = false end + if self.header_win_handle ~= nil then + api.nvim_win_close(self.header_win_handle, true) + end + if self.kind == "replace" then api.nvim_buf_delete(self.handle, { force = force }) return @@ -592,6 +597,7 @@ function Buffer:set_header(text, scroll) vim.wo[winid].winhl = "NormalFloat:NeogitFloatHeader" fn.matchadd("NeogitFloatHeaderHighlight", [[\v\|\]], 100, -1, { window = winid }) + self.header_win_handle = winid if scroll then -- Log view doesn't need scroll because the top line is blank... Because it can't be a fold or the view doesn't work. From 3d7c47b1b1239f35ace65756b879fb635458d7ac Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 17 Nov 2024 20:46:28 +0100 Subject: [PATCH 099/437] Add Ci workflow that checks lua types with lua-lsp --- .github/workflows/lint.yml | 15 +++++++++++++++ .luarc.json | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 711bddf38..07477766c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,3 +39,18 @@ jobs: with: bundler-cache: true - run: bundle exec rubocop + + lua_types: + name: lua-typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Homebrew/actions/setup-homebrew@master + - uses: luarocks/gh-actions-lua@v10 + with: + luaVersion: luajit + - uses: luarocks/gh-actions-luarocks@v5 + - run: | + HOMEBREW_NO_INSTALL_CLEANUP=1 brew install lua-language-server + luarocks install llscheck + llscheck lua/ diff --git a/.luarc.json b/.luarc.json index b03227d4b..f24169681 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", - "Lua.diagnostics.disable": [ + "diagnostics.disable": [ "redefined-local" ], "diagnostics.globals": [ @@ -9,5 +9,6 @@ "describe", "before_each" ], - "workspace.checkThirdParty": "Disable", + "workspace.checkThirdParty": false, + "runtime.version": "LuaJIT" } From 66fa7e2d01391a853965520df51965663f1eef54 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Nov 2024 22:40:26 +0100 Subject: [PATCH 100/437] Fix type most diagnostic warnings --- lua/neogit/buffers/commit_select_view/init.lua | 2 +- lua/neogit/buffers/diff/init.lua | 2 +- lua/neogit/lib/git/branch.lua | 7 +++++-- lua/neogit/lib/git/cli.lua | 9 ++++++++- lua/neogit/lib/git/files.lua | 2 +- lua/neogit/lib/git/index.lua | 3 +++ lua/neogit/lib/git/init.lua | 2 +- lua/neogit/lib/git/log.lua | 11 ++++++++--- lua/neogit/lib/git/rev_parse.lua | 3 +-- lua/neogit/lib/git/status.lua | 9 ++++++++- lua/neogit/lib/graph/unicode.lua | 1 + lua/neogit/lib/popup/builder.lua | 13 +++++++------ lua/neogit/lib/popup/init.lua | 11 ++++++----- lua/neogit/lib/state.lua | 9 +++++++-- lua/neogit/lib/ui/component.lua | 7 +++++++ lua/neogit/lib/ui/init.lua | 18 +++++------------- 16 files changed, 70 insertions(+), 39 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 006207319..241b0d894 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -53,7 +53,7 @@ function M:open(action) M.instance = self - ---@type fun(commit: CommitLogEntry[])|nil + ---@type fun(commit: string[])|nil local action = action self.buffer = Buffer.create { diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index 987378d7f..53f42f86e 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -7,7 +7,7 @@ local api = vim.api ---@class DiffBuffer ---@field buffer Buffer ----@field open fun(self, kind: string) +---@field open fun(self): DiffBuffer ---@field close fun() ---@field stats table ---@field diffs table diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 68407b466..bd1b91f27 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -162,6 +162,8 @@ function M.current_full_name() end end +---@param branch? string +---@return string|nil function M.pushRemote(branch) branch = branch or M.current() @@ -208,6 +210,8 @@ function M.set_pushRemote() pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote" } end + assert(type(pushRemote) == "nil" or type(pushRemote) == "string") + if pushRemote then git.config.set(string.format("branch.%s.pushRemote", M.current()), pushRemote) end @@ -222,8 +226,7 @@ end function M.upstream(name) if name then local result = git.cli["rev-parse"].symbolic_full_name - .abbrev_ref() - .args(name .. "@{upstream}") + .abbrev_ref(name .. "@{upstream}") .call { ignore_error = true } if result.code == 0 then diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index f0c0e3b60..1fe1bbe88 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -107,7 +107,7 @@ local runner = require("neogit.runner") ---@field shortstat self ---@field patch self ---@field name_only self ----@field ext_diff self +---@field no_ext_diff self ---@field index self ---@field check self @@ -173,6 +173,12 @@ local runner = require("neogit.runner") ---@field prune self ---@field get_url fun(remote: string): self +---@class GitCommandRevert: GitCommandBuilder +---@field no_commit self +---@field continue self +---@field skip self +---@field abort self + ---@class GitCommandApply: GitCommandBuilder ---@field ignore_space_change self ---@field cached self @@ -344,6 +350,7 @@ local runner = require("neogit.runner") ---@field rebase GitCommandRebase ---@field reflog GitCommandReflog ---@field remote GitCommandRemote +---@field revert GitCommandRevert ---@field reset GitCommandReset ---@field rev-list GitCommandRevList ---@field rev-parse GitCommandRevParse diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index fb4cf7d78..1dadb8e13 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -17,7 +17,7 @@ function M.untracked() return git.cli["ls-files"].others.exclude_standard.call({ hidden = true }).stdout end ----@param opts { with_dir: boolean } +---@param opts? { with_dir: boolean } ---@return string[] function M.all_tree(opts) opts = opts or {} diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index cc2c9e3c5..d03619d9e 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -153,6 +153,9 @@ function M.update() on_error = function(_) return false end, + suppress_console = true, + git_hook = false, + user_command = false, }) :spawn_async() end diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 1b5365c47..229f849a6 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -28,7 +28,7 @@ M.init_repo = function() status.instance():chdir(directory) end - if git.cli.is_inside_worktree() then + if git.cli.is_inside_worktree(directory) then if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then return end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index c1b82fccc..24d112fad 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -24,6 +24,11 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)" ---@field subject string ---@field parent string ---@field diffs any[] +---@field ref_name string +---@field abbreviated_commit string +---@field body string +---@field verification_flag string? +---@field rel_date string ---Parses the provided list of lines into a CommitLogEntry ---@param raw string[] @@ -136,7 +141,7 @@ function M.parse(raw) if not line or vim.startswith(line, "diff") then -- There was a previous diff, parse it if in_diff then - table.insert(commit.diffs, git.diff.parse(current_diff)) + table.insert(commit.diffs, git.diff.parse(current_diff, {})) current_diff = {} end @@ -144,7 +149,7 @@ function M.parse(raw) elseif line == "" then -- A blank line signifies end of diffs -- Parse the last diff, consume the blankline, and exit if in_diff then - table.insert(commit.diffs, git.diff.parse(current_diff)) + table.insert(commit.diffs, git.diff.parse(current_diff, {})) current_diff = {} end @@ -443,7 +448,7 @@ end --- Runs `git verify-commit` ---@param commit string Hash of commit ----@return string The stderr output of the command +---@return string[] The stderr output of the command function M.verify_commit(commit) return git.cli["verify-commit"].args(commit).call({ ignore_error = true }).stderr end diff --git a/lua/neogit/lib/git/rev_parse.lua b/lua/neogit/lib/git/rev_parse.lua index 18c05a3e3..1f97b24e6 100644 --- a/lua/neogit/lib/git/rev_parse.lua +++ b/lua/neogit/lib/git/rev_parse.lua @@ -29,8 +29,7 @@ end ---@async function M.verify(rev) return git.cli["rev-parse"].verify - .abbrev_ref() - .args(rev) + .abbrev_ref(rev) .call({ hidden = true, ignore_error = true }).stdout[1] end diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index bf0b1d40a..d0c598009 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -6,12 +6,19 @@ local logger = require("neogit.logger") ---@class StatusItem ---@field mode string ----@field diff string[] +---@field diff Diff ---@field absolute_path string ---@field escaped_path string ---@field original_name string|nil ---@field file_mode {head: number, index: number, worktree: number}|nil ---@field submodule SubmoduleStatus|nil +---@field name string +---@field first number +---@field last number +---@field oid string|nil optional object id +---@field commit CommitLogEntry|nil optional object id +---@field folded boolean|nil +---@field hunks Hunk[]|nil ---@class SubmoduleStatus ---@field commit_changed boolean C diff --git a/lua/neogit/lib/graph/unicode.lua b/lua/neogit/lib/graph/unicode.lua index b2309b72a..0904fe482 100644 --- a/lua/neogit/lib/graph/unicode.lua +++ b/lua/neogit/lib/graph/unicode.lua @@ -477,6 +477,7 @@ function M.build(commits) if is_missing_parent and branch_index ~= moved_parent_branch_index then -- Remove branch branch_hashes[branch_index] = nil + assert(branch_hash) branch_indexes[branch_hash] = nil -- Trim trailing empty branches diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index b1f7579b5..cf8efdf6c 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -53,13 +53,14 @@ local M = {} ---@field options table ---@field type string ---@field user_input boolean +---@field value string? ---@class PopupConfig ---@field id string ---@field key string ---@field name string ---@field entry ConfigEntry ----@field value string +---@field value string? ---@field type string ---@field passive boolean? ---@field options PopupConfigOption[]? @@ -69,7 +70,7 @@ local M = {} ---@class PopupConfigOption An option that can be selected as a value for a config ---@field display string The display name for the option ---@field value string The value to set in git config ----@field condition fun()? An option predicate to determine if the option should appear +---@field condition? fun(): boolean An option predicate to determine if the option should appear ---@class PopupAction ---@field keys string|string[] @@ -100,10 +101,10 @@ local M = {} ---@field fn? fun() function called - like an action. Used to launch a popup from a popup. ---@class PopupConfigOpts ----@field options PopupConfigOption[] ----@field fn fun(popup: PopupData, config: self) If set, overrides the actual config setting behavior ----@field callback fun(popup: PopupData, config: PopupConfig)? A callback that will be invoked after the config is set ----@field passive boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI +---@field options? PopupConfigOption[] +---@field fn? fun(popup: PopupData, config: self) If set, overrides the actual config setting behavior +---@field callback? fun(popup: PopupData, config: PopupConfig)? A callback that will be invoked after the config is set +---@field passive? boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. ---@param builder_fn PopupData diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 0cb6bcc6d..b51417488 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -198,7 +198,7 @@ function M:set_option(option, value) -- If the option specifies a default value, and the user set the value to be empty, defer to default value. -- This is handy to prevent the user from accidentally loading thousands of log entries by accident. if option.default and input == "" then - option.value = option.default + option.value = tostring(option.default) else option.value = input end @@ -211,9 +211,9 @@ function M:set_option(option, value) for _, var in ipairs(self.state.args) do if option.incompatible[var.cli] then if var.type == "switch" then - self:disable_switch(var) + self:disable_switch(var --[[@as PopupSwitch]]) elseif var.type == "option" then - self:disable_option(var) + self:disable_option(var --[[@as PopupOption]]) end end end @@ -224,9 +224,9 @@ function M:set_option(option, value) for _, var in ipairs(self.state.args) do if option.dependent[var.cli] then if var.type == "switch" then - self:disable_switch(var) + self:disable_switch(var --[[@as PopupSwitch]]) elseif var.type == "option" then - self:disable_option(var) + self:disable_option(var --[[@as PopupOption]]) end end end @@ -272,6 +272,7 @@ function M:set_config(config) else local result = input.get_user_input(config.name, { default = config.value, cancel = config.value }) + assert(result) config.value = result git.config.set(config.name, config.value) end diff --git a/lua/neogit/lib/state.lua b/lua/neogit/lib/state.lua index 246701809..5b6335492 100644 --- a/lua/neogit/lib/state.lua +++ b/lua/neogit/lib/state.lua @@ -17,7 +17,7 @@ end ---@return Path function M.filepath(config) - local state_path = Path.new(vim.fn.stdpath("state")):joinpath("neogit") + local state_path = Path:new(vim.fn.stdpath("state")):joinpath("neogit") local filename = "state" if config.use_per_project_settings then @@ -60,7 +60,12 @@ function M.read() end log("Reading file") - return vim.mpack.decode(M.path:read()) + local content = M.path:read() + if content then + return vim.mpack.decode(content) + else + return {} + end end ---Writes state to disk diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 19e886a09..e115f7f74 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -25,6 +25,13 @@ local default_component_options = { ---@field section string|nil ---@field item table|nil ---@field id string|nil +---@field oid string|nil +---@field ref ParsedRef +---@field yankable string? +---@field on_open fun(fold, Ui) +---@field hunk Hunk +---@field filename string? +---@field value any ---@class Component ---@field position ComponentPosition diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 2a461a5ff..a1289ccee 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -7,6 +7,7 @@ local logger = require("neogit.logger") -- TODO: Add logging ---@class Section ---@field items StatusItem[] ---@field name string +---@field first number ---@class Selection ---@field sections Section[] @@ -148,15 +149,6 @@ function Ui:get_fold_under_cursor() end) end ----@class StatusItem ----@field name string ----@field first number ----@field last number ----@field oid string|nil optional object id ----@field commit CommitLogEntry|nil optional object id ----@field folded boolean|nil ----@field hunks Hunk[]|nil - ---@class SelectedHunk: Hunk ---@field from number start offset from the first line of the hunk ---@field to number end offset from the first line of the hunk @@ -301,7 +293,7 @@ function Ui:get_commits_in_selection() local commits = {} for i = start, stop do local component = self:_find_component_by_index(i, function(node) - return node.options.oid + return node.options.oid ~= nil end) if component then @@ -321,7 +313,7 @@ function Ui:get_filepaths_in_selection() local paths = {} for i = start, stop do local component = self:_find_component_by_index(i, function(node) - return node.options.item and node.options.item.escaped_path + return node.options.item ~= nil and node.options.item.escaped_path end) if component then @@ -515,7 +507,7 @@ end function Ui:get_hunk_or_filename_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) local component = self:_find_component_by_index(cursor[1], function(node) - return node.options.hunk or node.options.filename + return node.options.hunk ~= nil or node.options.filename ~= nil end) return component and { @@ -528,7 +520,7 @@ end function Ui:get_item_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) local component = self:_find_component_by_index(cursor[1], function(node) - return node.options.item + return node.options.item ~= nil end) return component and component.options.item From 56cacbbd40d1b00d5262f78f1620bb42e4b45962 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Nov 2024 22:41:15 +0100 Subject: [PATCH 101/437] Rename: vim.loop -> vim.uv --- lua/neogit/client.lua | 2 +- lua/neogit/lib/git/config.lua | 2 +- lua/neogit/lib/git/index.lua | 3 +-- lua/neogit/lib/util.lua | 4 ++-- lua/neogit/popups/ignore/init.lua | 2 +- lua/neogit/process.lua | 6 +++--- lua/neogit/watcher.lua | 2 +- tests/specs/neogit/docs_spec.lua | 4 ++-- tests/util/util.lua | 4 ++-- 9 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index ee94e956a..c8b5e10d2 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -67,7 +67,7 @@ function M.client(opts) local lua_cmd = fmt('lua require("neogit.client").editor("%s", "%s", %s)', file_target, client, opts.show_diff) - if vim.loop.os_uname().sysname == "Windows_NT" then + if vim.uv.os_uname().sysname == "Windows_NT" then lua_cmd = lua_cmd:gsub("\\", "/") end diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index b7f6ac866..e5e7cfcc6 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -73,7 +73,7 @@ local config_cache = {} local cache_key = nil local function make_cache_key() - local stat = vim.loop.fs_stat(git.repo:git_path("config"):absolute()) + local stat = vim.uv.fs_stat(git.repo:git_path("config"):absolute()) 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 d03619d9e..99308fc5d 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -1,7 +1,6 @@ local git = require("neogit.lib.git") local Path = require("plenary.path") local util = require("neogit.lib.util") -local uv = vim.uv or vim.loop ---@class NeogitGitIndex local M = {} @@ -134,7 +133,7 @@ function M.with_temp_index(revision, fn) assert(revision, "temp index requires a revision") assert(fn, "Pass a function to call with temp index") - local tmp_index = Path:new(uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) + local tmp_index = Path:new(vim.uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) git.cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call { hidden = true } assert(tmp_index:exists(), "Failed to create temp index") diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 5f6b4e8c6..f9b55125e 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -455,7 +455,7 @@ end ---@param callback function ---@return uv_timer_t local function set_timeout(timeout, callback) - local timer = vim.loop.new_timer() + local timer = vim.uv.new_timer() timer:start(timeout, 0, function() timer:stop() @@ -522,7 +522,7 @@ function M.debounce_trailing(ms, fn, hash) return function(...) local id = hash and hash(...) or true if running[id] == nil then - running[id] = assert(vim.loop.new_timer()) + running[id] = assert(vim.uv.new_timer()) end local timer = running[id] diff --git a/lua/neogit/popups/ignore/init.lua b/lua/neogit/popups/ignore/init.lua index 7b624a461..d359334f8 100644 --- a/lua/neogit/popups/ignore/init.lua +++ b/lua/neogit/popups/ignore/init.lua @@ -22,7 +22,7 @@ function M.create(env) "g", string.format( "privately for all repositories (%s)", - "~/" .. Path:new(excludesFile:read()):make_relative(vim.loop.os_homedir()) + "~/" .. Path:new(excludesFile:read()):make_relative(vim.uv.os_homedir()) ), actions.private_global ) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index afa6ebec7..a1e6f20a4 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -133,7 +133,7 @@ function Process:start_timer() end if self.timer == nil then - local timer = vim.loop.new_timer() + local timer = vim.uv.new_timer() self.timer = timer local timeout = assert(self.git_hook and 800 or config.values.console_timeout, "no timeout") @@ -158,7 +158,7 @@ function Process:start_timer() local message = string.format( "Command %q running for more than: %.1f seconds", mask_command(table.concat(self.cmd, " ")), - math.ceil((vim.loop.now() - self.start) / 100) / 10 + math.ceil((vim.uv.now() - self.start) / 100) / 10 ) notification.warn(message .. "\n\nOpen the command history for details") @@ -315,7 +315,7 @@ function Process:spawn(cb) local function on_exit(_, code) res.code = code - res.time = (vim.loop.now() - start) + res.time = (vim.uv.now() - start) -- Remove self processes[self.job] = nil diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 773ca6ba1..1314c2afc 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -22,7 +22,7 @@ function Watcher.new(root) buffers = {}, git_root = Path:new(root):joinpath(".git"):absolute(), running = false, - fs_event_handler = assert(vim.loop.new_fs_event()), + fs_event_handler = assert(vim.uv.new_fs_event()), } setmetatable(instance, Watcher) diff --git a/tests/specs/neogit/docs_spec.lua b/tests/specs/neogit/docs_spec.lua index 644ff1492..8e8e72098 100644 --- a/tests/specs/neogit/docs_spec.lua +++ b/tests/specs/neogit/docs_spec.lua @@ -2,7 +2,7 @@ local Path = require("plenary.path") describe("docs", function() it("doesn't repeat any tags", function() - local docs = Path.new(vim.loop.cwd(), "doc", "neogit.txt") + local docs = Path.new(vim.uv.cwd(), "doc", "neogit.txt") local tags = {} for line in docs:iter() do @@ -14,7 +14,7 @@ describe("docs", function() end) it("doesn't reference any undefined tags", function() - local docs = Path.new(vim.loop.cwd(), "doc", "neogit.txt") + local docs = Path.new(vim.uv.cwd(), "doc", "neogit.txt") local tags = {} local refs = {} diff --git a/tests/util/util.lua b/tests/util/util.lua index a898df127..f9dabdcc5 100644 --- a/tests/util/util.lua +++ b/tests/util/util.lua @@ -39,7 +39,7 @@ end M.neogit_test_base_dir = "/tmp/neogit-testing/" local function is_macos() - return vim.loop.os_uname().sysname == "Darwin" + return vim.uv.os_uname().sysname == "Darwin" end local function is_gnu_mktemp() @@ -72,7 +72,7 @@ function M.ensure_installed(repo, path) vim.opt.runtimepath:prepend(install_path) - if not vim.loop.fs_stat(install_path) then + if not vim.uv.fs_stat(install_path) then print("* Downloading " .. name .. " to '" .. install_path .. "/'") vim.fn.system { "git", "clone", "--depth=1", "git@github.com:" .. repo .. ".git", install_path } From 83fbefac09f0041d1549a91e7021a59cc80036ee Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Nov 2024 22:41:25 +0100 Subject: [PATCH 102/437] Add a redraw here to flush the prompt --- lua/neogit/lib/git/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 229f849a6..8b6f7fed0 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -29,6 +29,7 @@ M.init_repo = function() end if git.cli.is_inside_worktree(directory) then + vim.cmd.redraw() if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then return end From 9c793606d4482d23f15785a80c166bda461a2821 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 19 Nov 2024 14:57:17 +0100 Subject: [PATCH 103/437] Fix remaining type definitions. All thats left error-wise are 3rd party --- lua/neogit/buffers/editor/init.lua | 5 ++--- lua/neogit/buffers/process/init.lua | 17 ++++------------- lua/neogit/buffers/status/actions.lua | 5 +++-- lua/neogit/lib/color.lua | 2 ++ lua/neogit/lib/git/branch.lua | 3 ++- lua/neogit/lib/git/cli.lua | 4 +++- lua/neogit/lib/git/index.lua | 2 +- lua/neogit/lib/git/rebase.lua | 15 +++++++++------ lua/neogit/lib/git/sequencer.lua | 3 ++- lua/neogit/lib/popup/builder.lua | 6 ++++-- lua/neogit/lib/ui/component.lua | 6 ++++++ lua/neogit/lib/ui/debug.lua | 14 +++++++------- lua/neogit/lib/util.lua | 2 +- lua/neogit/popups/branch/actions.lua | 1 + lua/neogit/popups/branch/init.lua | 2 +- lua/neogit/popups/commit/actions.lua | 4 ++-- lua/neogit/popups/ignore/actions.lua | 6 +++--- lua/neogit/popups/pull/init.lua | 6 +++--- lua/neogit/popups/rebase/init.lua | 2 +- 19 files changed, 57 insertions(+), 48 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index b33ccd113..d01548520 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -10,14 +10,13 @@ local DiffViewBuffer = require("neogit.buffers.diff") local pad = util.pad_right -local M = {} - ---@class EditorBuffer ---@field filename string filename of buffer ---@field on_unload function callback invoked when buffer is unloaded ---@field show_diff boolean show the diff view or not ---@field buffer Buffer ----@see Buffer +local M = {} + --- Creates a new EditorBuffer ---@param filename string the filename of buffer diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index aceff178e..2ea6b7b2c 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -5,23 +5,12 @@ local config = require("neogit.config") ---@field content string[] ---@field truncated boolean ---@field buffer Buffer ----@field open fun(self) ----@field hide fun(self) ----@field close fun(self) ----@field focus fun(self) ----@field flush_content fun(self) ----@field show fun(self) ----@field is_visible fun(self): boolean ----@field append fun(self, data: string) Appends a complete line to the buffer ----@field append_partial fun(self, data: string) Appends a partial line - for things like spinners. ----@field new fun(self, table): ProcessBuffer ----@see Buffer ----@see Ui +---@field process Process local M = {} M.__index = M ----@return ProcessBuffer ---@param process Process +---@return ProcessBuffer function M:new(process) local instance = { content = { string.format("> %s\r\n", table.concat(process.cmd, " ")) }, @@ -66,6 +55,7 @@ function M:is_visible() return self.buffer and self.buffer:is_valid() and self.buffer:is_visible() end +---@param data string function M:append(data) assert(data, "no data to append") @@ -77,6 +67,7 @@ function M:append(data) end end +---@param data string function M:append_partial(data) assert(data, "no data to append") diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 9136fe902..2ee8460b2 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1280,8 +1280,9 @@ M.n_help_popup = function(self) -- 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 + local section_name if section then - section = section.name + section_name = section.name end local item = self.buffer.ui:get_yankable_under_cursor() @@ -1303,7 +1304,7 @@ M.n_help_popup = function(self) tag = { commit = commit }, stash = { name = stash and stash:match("^stash@{%d+}") }, diff = { - section = { name = section }, + section = { name = section_name }, item = { name = item }, }, ignore = { diff --git a/lua/neogit/lib/color.lua b/lua/neogit/lib/color.lua index 6b7790541..d0790f3d3 100644 --- a/lua/neogit/lib/color.lua +++ b/lua/neogit/lib/color.lua @@ -97,6 +97,8 @@ function Color.from_hex(c) end end + assert(type(n) == "number") + return Color( bit.rshift(n, 24) / 0xff, bit.band(bit.rshift(n, 16), 0xff) / 0xff, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index bd1b91f27..3b0eafee2 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -80,10 +80,11 @@ function M.is_unmerged(branch, base) return git.cli.cherry.arg_list({ base or M.base_branch(), branch }).call({ hidden = true }).stdout[1] ~= nil end +---@return string|nil function M.base_branch() local value = git.config.get("neogit.baseBranch") if value:is_set() then - return value:read() + return value:read() ---@type string else if M.exists("master") then return "master" diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 1fe1bbe88..88c142589 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -108,6 +108,7 @@ local runner = require("neogit.runner") ---@field patch self ---@field name_only self ---@field no_ext_diff self +---@field no_index self ---@field index self ---@field check self @@ -129,7 +130,7 @@ local runner = require("neogit.runner") ---@class GitCommandRebase: GitCommandBuilder ---@field interactive self ---@field onto self ----@field todo self +---@field edit_todo self ---@field continue self ---@field abort self ---@field skip self @@ -368,6 +369,7 @@ local runner = require("neogit.runner") ---@field write-tree GitCommandWriteTree ---@field git_root fun(dir: string):string ---@field is_inside_worktree fun(dir: string):boolean +---@field history ProcessResult[] ---@param setup GitCommandSetup|nil ---@return GitCommand diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 99308fc5d..a40210e6b 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -134,7 +134,7 @@ function M.with_temp_index(revision, fn) assert(fn, "Pass a function to call with temp index") local tmp_index = Path:new(vim.uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) - git.cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call { hidden = true } + git.cli["read-tree"].index_output(tmp_index:absolute()).args(revision).call { hidden = true } assert(tmp_index:exists(), "Failed to create temp index") fn(tmp_index:absolute()) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index f17b48144..ae521ab19 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -20,9 +20,10 @@ end ---@return ProcessResult function M.instantly(commit, args) local result = git.cli.rebase - .env({ GIT_SEQUENCE_EDITOR = ":" }).interactive.autostash.autosquash - .arg_list(args or {}) + .interactive.autostash.autosquash .commit(commit) + .env({ GIT_SEQUENCE_EDITOR = ":" }) + .arg_list(args or {}) .call { long = true, pty = true } if result.code ~= 0 then @@ -99,9 +100,10 @@ function M.modify(commit) local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/edit \\1/' -c 'wq'" local result = git.cli.rebase - .env({ GIT_SEQUENCE_EDITOR = editor }).interactive.autosquash.autostash - .in_pty(true) + .interactive.autosquash.autostash .commit(commit) + .in_pty(true) + .env({ GIT_SEQUENCE_EDITOR = editor }) .call() if result.code ~= 0 then return @@ -113,9 +115,10 @@ function M.drop(commit) local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/drop \\1/' -c 'wq'" local result = git.cli.rebase - .env({ GIT_SEQUENCE_EDITOR = editor }).interactive.autosquash.autostash - .in_pty(true) + .interactive.autosquash.autostash .commit(commit) + .in_pty(true) + .env({ GIT_SEQUENCE_EDITOR = editor }) .call() if result.code ~= 0 then return diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index c5fc385e8..63abdbb93 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -8,6 +8,7 @@ local M = {} -- And CHERRY_PICK_HEAD does not exist when a conflict happens while picking a series of commits with --no-commit. -- And REVERT_HEAD does not exist when a conflict happens while reverting a series of commits with --no-commit. -- +---@return boolean function M.pick_or_revert_in_progress() local pick_or_revert_todo = false @@ -18,7 +19,7 @@ function M.pick_or_revert_in_progress() end end - return git.repo.state.sequencer.head or pick_or_revert_todo + return git.repo.state.sequencer.head ~= nil or pick_or_revert_todo end ---@class SequencerItem diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index cf8efdf6c..b1e0ec56e 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -63,6 +63,7 @@ local M = {} ---@field value string? ---@field type string ---@field passive boolean? +---@field heading string? ---@field options PopupConfigOption[]? ---@field callback fun(popup: PopupData, config: self)? Called after the config is set ---@field fn fun(popup: PopupData, config: self)? If set, overrides the actual config setting behavior @@ -73,9 +74,10 @@ local M = {} ---@field condition? fun(): boolean An option predicate to determine if the option should appear ---@class PopupAction ----@field keys string|string[] +---@field keys string[] ---@field description string ---@field callback function +---@field heading string? ---@class PopupSwitchOpts ---@field enabled? boolean Controls if the switch should default to 'on' state @@ -107,7 +109,7 @@ local M = {} ---@field passive? boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. ----@param builder_fn PopupData +---@param builder_fn fun(): PopupData ---@return PopupBuilder function M.new(builder_fn) local instance = { diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index e115f7f74..b393e1e10 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -42,6 +42,10 @@ local default_component_options = { ---@field index number|nil ---@field value string|nil ---@field id string|nil +---@field highlight fun(hl_group:string): self +---@field line_hl fun(hl_group:string): self +---@field padding_left fun(string): self +---@operator call: Component local Component = {} ---@return integer, integer @@ -148,6 +152,8 @@ function Component:close_all_folds(ui) end end +---@param f fun(...): table +---@return Component function Component.new(f) local instance = {} diff --git a/lua/neogit/lib/ui/debug.lua b/lua/neogit/lib/ui/debug.lua index 4e5df63b9..c09bda914 100644 --- a/lua/neogit/lib/ui/debug.lua +++ b/lua/neogit/lib/ui/debug.lua @@ -33,13 +33,13 @@ function Ui._visualize_tree(indent, components, tree) end end -function Ui.visualize_component(c, options) - Ui._print_component(0, c, options or {}) - - if c.tag == "col" or c.tag == "row" then - Ui._visualize_tree(1, c.children, options or {}) - end -end +-- function Ui.visualize_component(c, options) +-- Ui._print_component(0, c, options or {}) +-- +-- if c.tag == "col" or c.tag == "row" then +-- Ui._visualize_tree(1, c.children, options or {}) +-- end +-- end function Ui._draw_component(indent, c, _) local output = string.rep(" ", indent) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index f9b55125e..46c9942f6 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -3,7 +3,7 @@ local M = {} ---@generic T: any ---@generic U: any ---@param tbl T[] ----@param f fun(v: T): U +---@param f Component|fun(v: T): U ---@return U[] function M.map(tbl, f) local t = {} diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index ed3d34f76..ea9c69afb 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -309,6 +309,7 @@ function M.delete_branch(popup) if choice == "d" then git.cli.checkout.detach.call() elseif choice == "c" then + assert(upstream) git.cli.checkout.branch(upstream).call() else return diff --git a/lua/neogit/popups/branch/init.lua b/lua/neogit/popups/branch/init.lua index 34a8ad011..ca3a8b157 100644 --- a/lua/neogit/popups/branch/init.lua +++ b/lua/neogit/popups/branch/init.lua @@ -50,7 +50,7 @@ function M.create(env) :action("m", "rename", actions.rename_branch) :action("X", "reset", actions.reset_branch) :action("D", "delete", actions.delete_branch) - :action_if(git.branch.upstream(), "o", "pull request", actions.open_pull_request) + :action_if(git.branch.upstream() ~= nil, "o", "pull request", actions.open_pull_request) :env(env) :build() diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index d2bb9b0f9..9772617d7 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -76,7 +76,7 @@ local function commit_special(popup, method, opts) end end - local cmd = git.cli.commit.args(string.format("--%s=%s", method, commit)) + local cmd = git.cli.commit if opts.edit then cmd = cmd.edit else @@ -88,7 +88,7 @@ local function commit_special(popup, method, opts) end a.util.scheduler() - do_commit(popup, cmd) + do_commit(popup, cmd.args(string.format("--%s=%s", method, commit))) if opts.rebase then a.util.scheduler() diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index bdcbca280..4153f0d40 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -40,9 +40,9 @@ function M.shared_toplevel(popup) end function M.shared_subdirectory(popup) - local subdirectory = input.get_user_input("Ignore sub-directory", { completion = "dir" }) - if subdirectory then - subdirectory = Path:new(vim.uv.cwd(), subdirectory) + local choice = input.get_user_input("Ignore sub-directory", { completion = "dir" }) + if choice then + local subdirectory = Path:new(vim.uv.cwd(), choice) local ignore_file = subdirectory:joinpath(".gitignore") local rules = make_rules(popup, tostring(subdirectory)) diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index dcba8a281..1eb8e03fc 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -24,10 +24,10 @@ function M.create() :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(current ~= nil, "Pull into " .. current .. " from") :group_heading_if(not current, "Pull from") - :action_if(current, "p", git.branch.pushRemote_label(), actions.from_pushremote) - :action_if(current, "u", git.branch.upstream_label(), actions.from_upstream) + :action_if(current ~= nil, "p", git.branch.pushRemote_label(), actions.from_pushremote) + :action_if(current ~= nil, "u", git.branch.upstream_label(), actions.from_upstream) :action("e", "elsewhere", actions.from_elsewhere) :new_action_group("Configure") :action("C", "Set variables...", actions.configure) diff --git a/lua/neogit/popups/rebase/init.lua b/lua/neogit/popups/rebase/init.lua index 63125f9ce..e704f52bf 100644 --- a/lua/neogit/popups/rebase/init.lua +++ b/lua/neogit/popups/rebase/init.lua @@ -35,7 +35,7 @@ function M.create(env) :action_if(not in_rebase, "p", git.branch.pushRemote_label(), actions.onto_pushRemote) :action_if(not in_rebase, "u", git.branch.upstream_label(), actions.onto_upstream) :action_if(not in_rebase, "e", "elsewhere", actions.onto_elsewhere) - :action_if(not in_rebase and show_base_branch, "b", base_branch, actions.onto_base) + :action_if(not in_rebase and show_base_branch, "b", base_branch or "", actions.onto_base) :new_action_group_if(not in_rebase, "Rebase") :action_if(not in_rebase, "i", "interactively", actions.interactively) :action_if(not in_rebase, "s", "a subset", actions.subset) From b081786a559ddb9a68a5cab9569f59f47b9360bc Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Nov 2024 15:06:20 +0100 Subject: [PATCH 104/437] Fix remaining issues --- lua/neogit/lib/git/hooks.lua | 2 +- lua/neogit/lib/git/repository.lua | 4 ++-- lua/neogit/lib/git/sequencer.lua | 2 +- lua/neogit/spinner.lua | 2 -- lua/neogit/vendor/types.lua | 18 ++++++++++++++++++ 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 lua/neogit/vendor/types.lua diff --git a/lua/neogit/lib/git/hooks.lua b/lua/neogit/lib/git/hooks.lua index 0ee07e6b5..b6a2efec1 100644 --- a/lua/neogit/lib/git/hooks.lua +++ b/lua/neogit/lib/git/hooks.lua @@ -1,4 +1,4 @@ -local Path = require("plenary.path") ---@class Path +local Path = require("plenary.path") local git = require("neogit.lib.git") local M = {} ---@class NeogitGitHooks diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index b1e3afb85..fd3af277a 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -1,6 +1,6 @@ local a = require("plenary.async") local logger = require("neogit.logger") -local Path = require("plenary.path") ---@class Path +local Path = require("plenary.path") local git = require("neogit.lib.git") local ItemFilter = require("neogit.lib.item_filter") local util = require("neogit.lib.util") @@ -21,7 +21,7 @@ local modules = { } ---@class NeogitRepoState ----@field git_path fun(self, ...):Path +---@field git_path fun(self, ...): Path ---@field refresh fun(self, table) ---@field git_root string ---@field head NeogitRepoHead diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 63abdbb93..e951b0a3b 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -69,7 +69,7 @@ function M.update_sequencer_status(state) table.insert(state.sequencer.items, { action = "join", oid = state.sequencer.head_oid, - abbreviated_commit = state.sequencer.head_oid:sub(1, git.log.abbreviated_size()), + abbreviated_commit = string.sub(state.sequencer.head_oid, 1, git.log.abbreviated_size()), subject = git.log.message(state.sequencer.head_oid), }) end diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index 5f3f83120..0490e72a2 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -4,8 +4,6 @@ ---@field interval number ---@field pattern string[] ---@field timer uv_timer_t ----@field start fun(self) ----@field stop fun(self) local Spinner = {} Spinner.__index = Spinner diff --git a/lua/neogit/vendor/types.lua b/lua/neogit/vendor/types.lua new file mode 100644 index 000000000..54fef393a --- /dev/null +++ b/lua/neogit/vendor/types.lua @@ -0,0 +1,18 @@ +--This file exists to facilitate llscheck CI types + +---@class Path +---@field absolute fun(self): boolean +---@field exists fun(self): boolean +---@field touch fun(self, opts:table) +---@field write fun(self, txt:string, flag:string) +---@field read fun(self): string|nil + +---@class uv_timer_t +---@field start fun(self, time:number, repeat: number, fn: function) +---@field stop fun(self) +---@field is_closing fun(self): boolean +---@field close fun(self) +--- +---@class uv_fs_event_t +---@field start fun(self, path: string, opts: table, callback: function) +---@field stop fun(self) From 9262ef1b946d71a81a9634f061a125fef50268b0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Nov 2024 21:53:09 +0100 Subject: [PATCH 105/437] lint --- lua/neogit/buffers/editor/init.lua | 1 - lua/neogit/lib/color.lua | 2 +- lua/neogit/lib/git/branch.lua | 7 +++---- lua/neogit/lib/git/rebase.lua | 9 +++------ lua/neogit/lib/git/rev_parse.lua | 4 +--- lua/neogit/lib/graph/unicode.lua | 2 +- lua/neogit/lib/popup/init.lua | 2 +- lua/neogit/popups/branch/actions.lua | 2 +- 8 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index d01548520..e2475ad25 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -17,7 +17,6 @@ local pad = util.pad_right ---@field buffer Buffer local M = {} - --- Creates a new EditorBuffer ---@param filename string the filename of buffer ---@param on_unload function the event dispatched on buffer unload diff --git a/lua/neogit/lib/color.lua b/lua/neogit/lib/color.lua index d0790f3d3..c80fed2c4 100644 --- a/lua/neogit/lib/color.lua +++ b/lua/neogit/lib/color.lua @@ -97,7 +97,7 @@ function Color.from_hex(c) end end - assert(type(n) == "number") + assert(type(n) == "number", "must be a number") return Color( bit.rshift(n, 24) / 0xff, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 3b0eafee2..054e0c305 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -211,7 +211,7 @@ function M.set_pushRemote() pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote" } end - assert(type(pushRemote) == "nil" or type(pushRemote) == "string") + assert(type(pushRemote) == "nil" or type(pushRemote) == "string", "pushRemote is not a string or nil?") if pushRemote then git.config.set(string.format("branch.%s.pushRemote", M.current()), pushRemote) @@ -226,9 +226,8 @@ end ---@return string|nil function M.upstream(name) if name then - local result = git.cli["rev-parse"].symbolic_full_name - .abbrev_ref(name .. "@{upstream}") - .call { ignore_error = true } + local result = + git.cli["rev-parse"].symbolic_full_name.abbrev_ref(name .. "@{upstream}").call { ignore_error = true } if result.code == 0 then return result.stdout[1] diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index ae521ab19..330e12a9c 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -19,8 +19,7 @@ end ---@param args? string[] list of arguments to pass to git rebase ---@return ProcessResult function M.instantly(commit, args) - local result = git.cli.rebase - .interactive.autostash.autosquash + local result = git.cli.rebase.interactive.autostash.autosquash .commit(commit) .env({ GIT_SEQUENCE_EDITOR = ":" }) .arg_list(args or {}) @@ -99,8 +98,7 @@ end function M.modify(commit) local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/edit \\1/' -c 'wq'" - local result = git.cli.rebase - .interactive.autosquash.autostash + local result = git.cli.rebase.interactive.autosquash.autostash .commit(commit) .in_pty(true) .env({ GIT_SEQUENCE_EDITOR = editor }) @@ -114,8 +112,7 @@ end function M.drop(commit) local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/drop \\1/' -c 'wq'" - local result = git.cli.rebase - .interactive.autosquash.autostash + local result = git.cli.rebase.interactive.autosquash.autostash .commit(commit) .in_pty(true) .env({ GIT_SEQUENCE_EDITOR = editor }) diff --git a/lua/neogit/lib/git/rev_parse.lua b/lua/neogit/lib/git/rev_parse.lua index 1f97b24e6..e0d4a29e6 100644 --- a/lua/neogit/lib/git/rev_parse.lua +++ b/lua/neogit/lib/git/rev_parse.lua @@ -28,9 +28,7 @@ end ---@return string ---@async function M.verify(rev) - return git.cli["rev-parse"].verify - .abbrev_ref(rev) - .call({ hidden = true, ignore_error = true }).stdout[1] + return git.cli["rev-parse"].verify.abbrev_ref(rev).call({ hidden = true, ignore_error = true }).stdout[1] end return M diff --git a/lua/neogit/lib/graph/unicode.lua b/lua/neogit/lib/graph/unicode.lua index 0904fe482..65fdda2eb 100644 --- a/lua/neogit/lib/graph/unicode.lua +++ b/lua/neogit/lib/graph/unicode.lua @@ -477,7 +477,7 @@ function M.build(commits) if is_missing_parent and branch_index ~= moved_parent_branch_index then -- Remove branch branch_hashes[branch_index] = nil - assert(branch_hash) + assert(branch_hash, "no branch hash") branch_indexes[branch_hash] = nil -- Trim trailing empty branches diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index b51417488..e8ac7f21d 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -272,7 +272,7 @@ function M:set_config(config) else local result = input.get_user_input(config.name, { default = config.value, cancel = config.value }) - assert(result) + assert(result, "no input from user - what happened to the default?") config.value = result git.config.set(config.name, config.value) end diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index ea9c69afb..a4bf94b80 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -309,7 +309,7 @@ function M.delete_branch(popup) if choice == "d" then git.cli.checkout.detach.call() elseif choice == "c" then - assert(upstream) + assert(upstream, "there should be an upstream by this point") git.cli.checkout.branch(upstream).call() else return From 67a3766bf6e930fd483a21dd927ada7a4f53caf3 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Wed, 20 Nov 2024 17:01:49 +0100 Subject: [PATCH 106/437] Only set buffer/window options in "local" scope --- lua/neogit/lib/buffer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8cc5a95c3..9b3ca9111 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -404,13 +404,13 @@ end function Buffer:set_buffer_option(name, value) if self.handle ~= nil then - api.nvim_set_option_value(name, value, { buf = self.handle }) + api.nvim_set_option_value(name, value, { scope = "local" }) end end function Buffer:set_window_option(name, value) if self.win_handle ~= nil then - api.nvim_set_option_value(name, value, { win = self.win_handle }) + api.nvim_set_option_value(name, value, { scope = "local", win = self.win_handle }) end end From 26f12ca5a31fbcca3876ccf6dbe5e9c5e1c37d83 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Nov 2024 11:04:07 +0100 Subject: [PATCH 107/437] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..644cd0954 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [ckolkey] From 776e37a6684df71d33acadd73b2254f7968aec2e Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Nov 2024 11:26:05 +0100 Subject: [PATCH 108/437] Add "--force" to pull popup --- lua/neogit/config.lua | 1 + lua/neogit/popups/pull/init.lua | 1 + spec/popups/pull_popup_spec.rb | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 60db9ecd3..a9e94bcd9 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -511,6 +511,7 @@ function M.get_default_values() "NeogitPushPopup--force-with-lease", "NeogitPushPopup--force", "NeogitPullPopup--rebase", + "NeogitPullPopup--force", "NeogitCommitPopup--allow-empty", }, mappings = { diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index 1eb8e03fc..17635cc0a 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -24,6 +24,7 @@ function M.create() :switch("r", "rebase", "Rebase local commits") :switch("a", "autostash", "Autostash") :switch("t", "tags", "Fetch tags") + :switch("F", "force", "Force") :group_heading_if(current ~= nil, "Pull into " .. current .. " from") :group_heading_if(not current, "Pull from") :action_if(current ~= nil, "p", git.branch.pushRemote_label(), actions.from_pushremote) diff --git a/spec/popups/pull_popup_spec.rb b/spec/popups/pull_popup_spec.rb index 6e7f489bd..b40e4d79f 100644 --- a/spec/popups/pull_popup_spec.rb +++ b/spec/popups/pull_popup_spec.rb @@ -15,6 +15,7 @@ " -r Rebase local commits (--rebase) ", " -a Autostash (--autostash) ", " -t Fetch tags (--tags) ", + " -F Force (--force) ", " ", " Pull into master from Configure ", " p pushRemote, setting that C Set variables... ", @@ -23,5 +24,5 @@ ] end - %w[r -f -r -a -t p u e C].each { include_examples "interaction", _1 } + %w[r -f -r -a -t -F p u e C].each { include_examples "interaction", _1 } end From f1998f63c2e3d328fd1ab77508737ea6a33f5bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fausto=20N=C3=BA=C3=B1ez=20Alberro?= Date: Fri, 8 Nov 2024 08:16:55 +0100 Subject: [PATCH 109/437] feat: Add auto_show_console_on = "output" | "error" setting This pairs very well with the new spinner feature: https://github.com/NeogitOrg/neogit/pull/1551 Allows users to set: ```lua { auto_show_console_on = "error" } ``` Note that this is only considered if `auto_show_console` is `true` (this is the default). This results in Neogit showing a spinner while, e.g. pre-commit hooks are running, and only shows the console if the pre-commit hooks fail. The default is `auto_show_console_on = "output"`, which matches the previous behavior. This PR also stops Neogit from calling `notification.warn` on a bad exit code, because this overlaps with showing the console, and requires the user to press enter unnecessarily in order to get to the console output. The `notification.warn` call is preserved if `auto_show_console` is `false`. --- doc/neogit.txt | 3 +++ lua/neogit/config.lua | 5 +++++ lua/neogit/process.lua | 18 ++++++++++-------- tests/specs/neogit/config_spec.lua | 7 ++++++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index b331cb1ba..7196ff76e 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -120,6 +120,9 @@ TODO: Detail what these do console_timeout = 2000, -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, + -- If `auto_show_console` is enabled, specify "output" (default) to show + -- the console always, or "error" to auto-show the console only on error + auto_show_console_on = "output", notification_icon = "󰊢", status = { recent_commit_count = 10, diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index a9e94bcd9..6e3b53cdd 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -317,6 +317,7 @@ end ---@field disable_relative_line_numbers? boolean Whether to disable line numbers ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout +---@field auto_show_console_on? string Specify "output" (show always; default) or "error" if `auto_show_console` enabled ---@field auto_close_console? boolean Automatically hide the console if the process exits with a 0 status ---@field status? NeogitConfigStatusOptions Status buffer options ---@field commit_editor? NeogitCommitEditorConfigPopup Commit editor options @@ -377,6 +378,9 @@ function M.get_default_values() console_timeout = 2000, -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, + -- If `auto_show_console` is enabled, specify "output" (default) to show + -- the console always, or "error" to auto-show the console only on error + auto_show_console_on = "output", auto_close_console = true, notification_icon = "󰊢", status = { @@ -1107,6 +1111,7 @@ function M.validate_config() validate_type(config.disable_line_numbers, "disable_line_numbers", "boolean") validate_type(config.disable_relative_line_numbers, "disable_relative_line_numbers", "boolean") validate_type(config.auto_show_console, "auto_show_console", "boolean") + validate_type(config.auto_show_console_on, "auto_show_console_on", "string") validate_type(config.auto_close_console, "auto_close_console", "boolean") if validate_type(config.status, "status", "table") then validate_type(config.status.show_head_commit_hash, "status.show_head_commit_hash", "boolean") diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index a1e6f20a4..d9de2b385 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -152,16 +152,15 @@ function Process:start_timer() return end - if config.values.auto_show_console then - self:show_console() - else + if not config.values.auto_show_console then local message = string.format( "Command %q running for more than: %.1f seconds", mask_command(table.concat(self.cmd, " ")), math.ceil((vim.uv.now() - self.start) / 100) / 10 ) - notification.warn(message .. "\n\nOpen the command history for details") + elseif config.values.auto_show_console_on == "output" then + self:show_console() end end) ) @@ -336,10 +335,13 @@ function Process:spawn(cb) insert(output, "> " .. util.remove_ansi_escape_codes(res.stderr[i])) end - local message = - string.format("%s:\n\n%s", mask_command(table.concat(self.cmd, " ")), table.concat(output, "\n")) - - notification.warn(message) + if not config.values.auto_close_console then + local message = + string.format("%s:\n\n%s", mask_command(table.concat(self.cmd, " ")), table.concat(output, "\n")) + notification.warn(message) + elseif config.values.auto_show_console_on == "error" then + self.buffer:show() + end end if diff --git a/tests/specs/neogit/config_spec.lua b/tests/specs/neogit/config_spec.lua index 942401e06..0d169c33c 100644 --- a/tests/specs/neogit/config_spec.lua +++ b/tests/specs/neogit/config_spec.lua @@ -82,7 +82,12 @@ describe("Neogit config", function() end) it("should return invalid when auto_show_console isn't a boolean", function() - config.values.console_timeout = "not a boolean" + config.values.auto_show_console = "not a boolean" + assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) + end) + + it("should return invalid when auto_show_console_on isn't a string", function() + config.values.auto_show_console_on = true assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) end) From fe687b4d63d96a52931b503a0906e0e44b74fde3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Nov 2024 20:31:48 +0100 Subject: [PATCH 110/437] Add "squash" to Apply popup --- lua/neogit/popups/cherry_pick/actions.lua | 13 ++++++++++++- lua/neogit/popups/cherry_pick/init.lua | 2 +- spec/popups/cherry_pick_popup_spec.rb | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index f44d77f74..a4244b8ee 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -1,8 +1,9 @@ local M = {} - +local util = require("neogit.lib.util") local git = require("neogit.lib.git") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") +local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") ---@param popup any ---@return table @@ -39,6 +40,16 @@ function M.apply(popup) git.cherry_pick.apply(commits, popup:get_arguments()) end +function M.squash(popup) + local refs = util.merge(popup.state.env.commits, git.refs.list_branches(), git.refs.list_tags()) + local ref = FuzzyFinderBuffer.new(refs):open_async({ prompt_prefix = "Squash" }) + if ref then + local args = popup:get_arguments() + table.insert(args, "--squash") + git.merge.merge(ref, args) + end +end + function M.continue() git.cherry_pick.continue() end diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index 180d476a8..eeea4be9e 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -30,7 +30,7 @@ function M.create(env) :action_if(not in_progress, "A", "Pick", actions.pick) :action_if(not in_progress, "a", "Apply", actions.apply) :action_if(not in_progress, "h", "Harvest") - :action_if(not in_progress, "m", "Squash") + :action_if(not in_progress, "m", "Squash", actions.squash) :new_action_group_if(not in_progress, "Apply elsewhere") :action_if(not in_progress, "d", "Donate") :action_if(not in_progress, "n", "Spinout") diff --git a/spec/popups/cherry_pick_popup_spec.rb b/spec/popups/cherry_pick_popup_spec.rb index 5aed593e7..df7585b25 100644 --- a/spec/popups/cherry_pick_popup_spec.rb +++ b/spec/popups/cherry_pick_popup_spec.rb @@ -25,5 +25,5 @@ end %w[-m =s -F -x -e -s -S].each { include_examples "argument", _1 } - %w[A a].each { include_examples "interaction", _1 } + %w[A a m].each { include_examples "interaction", _1 } end From faa4701c47dad15f9882c0b63728ef684ffbf40e Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Nov 2024 23:41:30 +0100 Subject: [PATCH 111/437] Add Cherry Pick -> Donate action --- lua/neogit/lib/git/branch.lua | 6 +++ lua/neogit/lib/git/cherry_pick.lua | 62 +++++++++++++++++++++++ lua/neogit/lib/git/cli.lua | 2 + lua/neogit/lib/git/log.lua | 2 + lua/neogit/lib/git/rev_parse.lua | 8 +++ lua/neogit/popups/cherry_pick/actions.lua | 35 ++++++++++++- lua/neogit/popups/cherry_pick/init.lua | 2 +- lua/neogit/popups/merge/actions.lua | 9 ++-- spec/popups/cherry_pick_popup_spec.rb | 2 +- 9 files changed, 121 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 054e0c305..6c369603f 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -237,6 +237,12 @@ function M.upstream(name) end end +---@param name string +---@param destination string? +function M.set_upstream(name, destination) + git.cli.branch.set_upstream_to(name).args(destination or M.current()) +end + function M.upstream_label() return M.upstream() or "@{upstream}, creating it" end diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index f02c4405d..8029f0e8c 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -10,6 +10,9 @@ local function fire_cherrypick_event(data) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitCherryPick", modeline = false, data = data }) end +---@param commits string[] +---@param args string[] +---@return boolean function M.pick(commits, args) local cmd = git.cli["cherry-pick"].arg_list(util.merge(args, commits)) @@ -22,8 +25,10 @@ function M.pick(commits, args) if result.code ~= 0 then notification.error("Cherry Pick failed. Resolve conflicts before continuing") + return false else fire_cherrypick_event { commits = commits } + return true end end @@ -42,6 +47,63 @@ function M.apply(commits, args) end end +---@param commits string[] +---@param src string +---@param dst string +---@param start string +---@param checkout_dst? boolean +function M.move(commits, src, dst, args, start, checkout_dst) + local current = git.branch.current() + + if not git.branch.exists(dst) then + git.cli.branch.args(start or "", dst).call { hidden = true } + local upstream = git.branch.upstream(start) + if upstream then + git.branch.set_upstream_to(upstream, dst) + end + end + + if dst ~= current then + git.branch.checkout(dst) + end + + if not src then + return git.cherry_pick.pick(commits, args) + end + + local tip = commits[#commits] + local keep = commits[1] .. "^" + + if not git.cherry_pick.pick(commits, args) then + return + end + + if git.log.is_ancestor(src, tip) then + git.cli["update-ref"] + .message(string.format("reset: moving to %s", keep)) + .args(git.rev_parse.full_name(src), keep, tip) + .call() + + if not checkout_dst then + git.branch.checkout(src) + end + else + git.branch.checkout(src) + + local editor = "nvim -c '%g/^pick \\(" .. table.concat(commits, ".*|") .. ".*\\)/norm! dd/' -c 'wq'" + local result = + git.cli.rebase.interactive.args(keep).in_pty(true).env({ GIT_SEQUENCE_EDITOR = editor }).call() + + if result.code ~= 0 then + return notification.error("Picking failed - Fix things manually before continuing.") + end + + if checkout_dst then + git.branch.checkout(dst) + end + end +end + function M.continue() git.cli["cherry-pick"].continue.call { await = true } end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 88c142589..e4077f3a7 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -229,6 +229,7 @@ local runner = require("neogit.runner") ---@field very_verbose self ---@field move self ---@field sort fun(sort: string): self +---@field set_upstream_to fun(name: string): self ---@field name fun(name: string): self ---@class GitCommandFetch: GitCommandBuilder @@ -767,6 +768,7 @@ local configurations = { }, options = { sort = "--sort", + set_upstream_to = "--set-upstream-to", }, aliases = { name = function(tbl) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 24d112fad..5a6416516 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -414,6 +414,8 @@ function M.register(meta) end end +---@param from string +---@param to string function M.update_ref(from, to) git.cli["update-ref"].message(string.format("reset: moving to %s", to)).args(from, to).call() end diff --git a/lua/neogit/lib/git/rev_parse.lua b/lua/neogit/lib/git/rev_parse.lua index e0d4a29e6..b44d98196 100644 --- a/lua/neogit/lib/git/rev_parse.lua +++ b/lua/neogit/lib/git/rev_parse.lua @@ -31,4 +31,12 @@ function M.verify(rev) return git.cli["rev-parse"].verify.abbrev_ref(rev).call({ hidden = true, ignore_error = true }).stdout[1] end +---@param rev string +---@return string +function M.full_name(rev) + return git.cli["rev-parse"].verify.symbolic_full_name + .args(rev) + .call({ hidden = true, ignore_error = true }).stdout[1] +end + return M diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index a4244b8ee..250a6ce08 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -1,6 +1,7 @@ local M = {} local util = require("neogit.lib.util") local git = require("neogit.lib.git") +local notification = require("neogit.lib.notification") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -42,7 +43,7 @@ end function M.squash(popup) local refs = util.merge(popup.state.env.commits, git.refs.list_branches(), git.refs.list_tags()) - local ref = FuzzyFinderBuffer.new(refs):open_async({ prompt_prefix = "Squash" }) + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Squash" } if ref then local args = popup:get_arguments() table.insert(args, "--squash") @@ -50,6 +51,38 @@ function M.squash(popup) end end +function M.donate(popup) + local head = git.rev_parse.oid("HEAD") + + local commits + if #popup.state.env.commits > 1 then + commits = popup.state.env.commits + else + local ref = FuzzyFinderBuffer.new(util.merge(popup.state.env.commits, git.refs.list_branches())) + :open_async { prompt_prefix = "Donate" } + if not git.log.is_ancestor(head, git.rev_parse.oid(ref)) then + return notification.error("Cannot donate cherries that are not reachable from HEAD") + end + + if ref == popup.state.env.commits[1] then + commits = popup.state.env.commits + else + commits = util.map(git.cherry.list(head, ref), function(cherry) + return cherry.oid or cherry + end) + end + end + + local src = git.branch.is_detached() and head or git.branch.current() + + local prefix = string.format("Move %d cherr%s to branch", #commits, #commits > 1 and "ies" or "y") + local dst = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = prefix } + + if dst then + git.cherry_pick.move(commits, src, dst, popup:get_arguments()) + end +end + function M.continue() git.cherry_pick.continue() end diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index eeea4be9e..eeb4cff35 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -32,7 +32,7 @@ function M.create(env) :action_if(not in_progress, "h", "Harvest") :action_if(not in_progress, "m", "Squash", actions.squash) :new_action_group_if(not in_progress, "Apply elsewhere") - :action_if(not in_progress, "d", "Donate") + :action_if(not in_progress, "d", "Donate", actions.donate) :action_if(not in_progress, "n", "Spinout") :action_if(not in_progress, "s", "Spinoff") :group_heading_if(in_progress, "Cherry Pick") diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index b9b94b208..3507cc45a 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -19,7 +19,7 @@ end function M.merge(popup) local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - local ref = FuzzyFinderBuffer.new(refs):open_async() + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--no-edit") @@ -30,7 +30,7 @@ end function M.squash(popup) local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - local ref = FuzzyFinderBuffer.new(refs):open_async() + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Squash" } if ref then local args = popup:get_arguments() table.insert(args, "--squash") @@ -41,7 +41,7 @@ end function M.merge_edit(popup) local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - local ref = FuzzyFinderBuffer.new(refs):open_async() + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--edit") @@ -57,7 +57,7 @@ end function M.merge_nocommit(popup) local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - local ref = FuzzyFinderBuffer.new(refs):open_async() + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--no-commit") @@ -69,4 +69,5 @@ function M.merge_nocommit(popup) git.merge.merge(ref, args) end end + return M diff --git a/spec/popups/cherry_pick_popup_spec.rb b/spec/popups/cherry_pick_popup_spec.rb index df7585b25..b8f9dac11 100644 --- a/spec/popups/cherry_pick_popup_spec.rb +++ b/spec/popups/cherry_pick_popup_spec.rb @@ -25,5 +25,5 @@ end %w[-m =s -F -x -e -s -S].each { include_examples "argument", _1 } - %w[A a m].each { include_examples "interaction", _1 } + %w[A a m d].each { include_examples "interaction", _1 } end From 13723ab26457143f6c9fc5bcead42ac67a14b5a5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 24 Nov 2024 23:10:01 +0100 Subject: [PATCH 112/437] Fix types --- lua/neogit/lib/git/cherry_pick.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index 8029f0e8c..0cfb0999a 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -48,7 +48,7 @@ function M.apply(commits, args) end ---@param commits string[] ----@param src string +---@param src? string ---@param dst string ---@param start string ---@param checkout_dst? boolean @@ -59,7 +59,7 @@ function M.move(commits, src, dst, args, start, checkout_dst) git.cli.branch.args(start or "", dst).call { hidden = true } local upstream = git.branch.upstream(start) if upstream then - git.branch.set_upstream_to(upstream, dst) + git.branch.set_upstream(upstream, dst) end end From fb89d5b0f23499f63e1d4d5f5e61f09ffcc337b8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 24 Nov 2024 23:04:51 +0100 Subject: [PATCH 113/437] Allow unsetting branch remote/merge from popup --- lua/neogit/lib/git/config.lua | 11 +++++++++++ lua/neogit/popups/branch/init.lua | 7 +++++++ lua/neogit/popups/branch_config/actions.lua | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index e5e7cfcc6..f6645305f 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -68,6 +68,17 @@ function ConfigEntry:update(value) end end +---@return self +function ConfigEntry:refresh() + if self.scope == "local" then + self.value = M.get_local(self.name).value + elseif self.scope == "global" then + self.value = M.get_global(self.name).value + end + + return self +end + ---@type table local config_cache = {} local cache_key = nil diff --git a/lua/neogit/popups/branch/init.lua b/lua/neogit/popups/branch/init.lua index ca3a8b157..900c5477e 100644 --- a/lua/neogit/popups/branch/init.lua +++ b/lua/neogit/popups/branch/init.lua @@ -21,6 +21,13 @@ function M.create(env) }) :config_if(show_config, "u", "branch." .. current_branch .. ".merge", { fn = config_actions.merge_config(current_branch), + callback = function(popup) + for _, config in ipairs(popup.state.config) do + if config.name == "branch." .. current_branch .. ".remote" then + config.value = tostring(config.entry:refresh():read() or "") + end + end + end }) :config_if(show_config, "m", "branch." .. current_branch .. ".remote", { passive = true }) :config_if(show_config, "R", "branch." .. current_branch .. ".rebase", { diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index 9e2dabe35..ecb9b8cc9 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -24,7 +24,22 @@ end function M.merge_config(branch) local fn = function() - local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "upstream" } + -- When the values are set, clear them and return + if git.config.get_local("branch." .. branch .. ".merge"):is_set() then + git.config.set("branch." .. branch .. ".merge", nil) + git.config.set("branch." .. branch .. ".remote", nil) + + return + end + + local eventignore = vim.o.eventignore + vim.o.eventignore = "WinLeave" + local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { + prompt_prefix = "upstream", + refocus_status = false, + } + vim.o.eventignore = eventignore + if not target then return end From a94eb770b475041a711b49bb75b936e7350e6d7b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 24 Nov 2024 23:06:11 +0100 Subject: [PATCH 114/437] Popup kind value is now hardcoded, so remove conditional checks --- lua/neogit/lib/popup/init.lua | 34 ++++++++++++------------------- lua/neogit/popups/branch/init.lua | 2 +- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index e8ac7f21d..3d95c5820 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -2,7 +2,6 @@ local PopupBuilder = require("neogit.lib.popup.builder") local Buffer = require("neogit.lib.buffer") local logger = require("neogit.logger") local util = require("neogit.lib.util") -local config = require("neogit.config") local state = require("neogit.lib.state") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") @@ -416,7 +415,7 @@ function M:show() pcall(self.close, self) end, }, - after = function(buf, _win) + after = function(buf) buf:set_window_option("cursorline", false) buf:set_window_option("list", false) @@ -434,25 +433,18 @@ function M:show() end end - if - config.values.popup.kind == "split" - or config.values.popup.kind == "split_above" - or config.values.popup.kind == "split_above_all" - or config.values.popup.kind == "split_below" - or config.values.popup.kind == "split_below_all" - then - vim.cmd.resize(vim.fn.line("$") + 1) - - -- We do it again because things like the BranchConfigPopup come from an async context, - -- but if we only do it schedule wrapped, then you can see it load at one size, and - -- resize a few ms later - vim.schedule(function() - if buf:is_focused() then - vim.cmd.resize(vim.fn.line("$") + 1) - buf:set_window_option("winfixheight", true) - end - end) - end + local height = vim.fn.line("$") + 1 + vim.cmd.resize(height) + + -- We do it again because things like the BranchConfigPopup come from an async context, + -- but if we only do it schedule wrapped, then you can see it load at one size, and + -- resize a few ms later + vim.schedule(function() + if buf:is_focused() then + vim.cmd.resize(height) + buf:set_window_option("winfixheight", true) + end + end) end, render = function() return ui.Popup(self.state) diff --git a/lua/neogit/popups/branch/init.lua b/lua/neogit/popups/branch/init.lua index 900c5477e..e71157e6d 100644 --- a/lua/neogit/popups/branch/init.lua +++ b/lua/neogit/popups/branch/init.lua @@ -27,7 +27,7 @@ function M.create(env) config.value = tostring(config.entry:refresh():read() or "") end end - end + end, }) :config_if(show_config, "m", "branch." .. current_branch .. ".remote", { passive = true }) :config_if(show_config, "R", "branch." .. current_branch .. ".rebase", { From 0b4d5693c85fe3aefce4d11ff9ee6bde4579752a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Nov 2024 00:12:52 +0100 Subject: [PATCH 115/437] Add correct unset behaviour to branch config popup --- lua/neogit/popups/branch_config/init.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index b3920245a..5aedee7c7 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -15,7 +15,17 @@ function M.create(branch) :name("NeogitBranchConfigPopup") :config_heading("Configure branch") :config("d", "branch." .. branch .. ".description", { fn = actions.description_config(branch) }) - :config("u", "branch." .. branch .. ".merge", { fn = actions.merge_config(branch) }) + :config("u", "branch." .. branch .. ".merge", { + fn = actions.merge_config(branch), + callback = function(popup) + for _, config in ipairs(popup.state.config) do + if config.name == "branch." .. branch .. ".remote" then + config.value = tostring(config.entry:refresh():read() or "") + end + end + end, + + }) :config("m", "branch." .. branch .. ".remote", { passive = true }) :config("r", "branch." .. branch .. ".rebase", { options = { From 7735474364c971f50e94ac8cbfc41e5fcc18689a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Nov 2024 00:15:34 +0100 Subject: [PATCH 116/437] Update init.lua --- lua/neogit/popups/branch_config/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index 5aedee7c7..fbac9d724 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -24,7 +24,6 @@ function M.create(branch) end end end, - }) :config("m", "branch." .. branch .. ".remote", { passive = true }) :config("r", "branch." .. branch .. ".rebase", { From d5223f40ca77e82721ab37585cfa2ffee9895694 Mon Sep 17 00:00:00 2001 From: Sergey Alexandrov Date: Mon, 25 Nov 2024 17:11:14 +0100 Subject: [PATCH 117/437] Update default mappings in README.md helpfile --- README.md | 29 +++++++++---- doc/neogit.txt | 108 +++++++++++++++++++++++++++---------------------- 2 files changed, 82 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 904f4e150..57c6f725d 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,9 @@ neogit.setup { ["q"] = "Close", [""] = "Submit", [""] = "Abort", + [""] = "PrevMessage", + [""] = "NextMessage", + [""] = "ResetMessage", }, commit_editor_I = { [""] = "Submit", @@ -317,21 +320,31 @@ neogit.setup { [""] = "Previous", [""] = "Next", [""] = "Previous", - [""] = "MultiselectToggleNext", - [""] = "MultiselectTogglePrevious", + [""] = "InsertCompletion", + [""] = "MultiselectToggleNext", + [""] = "MultiselectTogglePrevious", [""] = "NOP", + [""] = "ScrollWheelDown", + [""] = "ScrollWheelUp", + [""] = "NOP", + [""] = "NOP", + [""] = "MouseClick", + ["<2-LeftMouse>"] = "NOP", }, -- Setting any of these to `false` will disable the mapping. popup = { ["?"] = "HelpPopup", ["A"] = "CherryPickPopup", - ["D"] = "DiffPopup", + ["d"] = "DiffPopup", ["M"] = "RemotePopup", ["P"] = "PushPopup", ["X"] = "ResetPopup", ["Z"] = "StashPopup", + ["i"] = "IgnorePopup", + ["t"] = "TagPopup", ["b"] = "BranchPopup", ["B"] = "BisectPopup", + ["w"] = "WorktreePopup", ["c"] = "CommitPopup", ["f"] = "FetchPopup", ["l"] = "LogPopup", @@ -339,26 +352,28 @@ neogit.setup { ["p"] = "PullPopup", ["r"] = "RebasePopup", ["v"] = "RevertPopup", - ["w"] = "WorktreePopup", }, status = { - ["k"] = "MoveUp", ["j"] = "MoveDown", - ["q"] = "Close", + ["k"] = "MoveUp", ["o"] = "OpenTree", + ["q"] = "Close", ["I"] = "InitRepo", ["1"] = "Depth1", ["2"] = "Depth2", ["3"] = "Depth3", ["4"] = "Depth4", + ["Q"] = "Command", [""] = "Toggle", ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", + ["L"] = "StageLine", [""] = "StageAll", - ["K"] = "Untrack", ["u"] = "Unstage", + ["K"] = "Untrack", ["U"] = "UnstageStaged", + ["y"] = "ShowRefs", ["$"] = "CommandHistory", ["Y"] = "YankSelected", [""] = "RefreshBuffer", diff --git a/doc/neogit.txt b/doc/neogit.txt index 7196ff76e..4e27d6a36 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -299,70 +299,82 @@ The following mappings can all be customized via the setup function. } finder = { - [""] = "Select", - [""] = "Close", - [""] = "Close", - [""] = "Next", - [""] = "Previous", - [""] = "Next", - [""] = "Previous", - [""] = "MultiselectToggleNext", - [""] = "MultiselectTogglePrevious", + [""] = "Select", + [""] = "Close", + [""] = "Close", + [""] = "Next", + [""] = "Previous", + [""] = "Next", + [""] = "Previous", + [""] = "InsertCompletion", + [""] = "MultiselectToggleNext", + [""] = "MultiselectTogglePrevious", + [""] = "NOP", + [""] = "ScrollWheelDown", + [""] = "ScrollWheelUp", + [""] = "NOP", + [""] = "NOP", + [""] = "MouseClick", + ["<2-LeftMouse>"] = "NOP", } popup = { ["?"] = "HelpPopup", ["A"] = "CherryPickPopup", - ["B"] = "BisectPopup", + ["d"] = "DiffPopup", + ["M"] = "RemotePopup", + ["P"] = "PushPopup", + ["X"] = "ResetPopup", + ["Z"] = "StashPopup", + ["i"] = "IgnorePopup", + ["t"] = "TagPopup", ["b"] = "BranchPopup", + ["B"] = "BisectPopup", + ["w"] = "WorktreePopup", ["c"] = "CommitPopup", - ["d"] = "DiffPopup", ["f"] = "FetchPopup", - ["i"] = "IgnorePopup", ["l"] = "LogPopup", ["m"] = "MergePopup", - ["M"] = "RemotePopup", ["p"] = "PullPopup", - ["P"] = "PushPopup", ["r"] = "RebasePopup", - ["t"] = "TagPopup", ["v"] = "RevertPopup", - ["w"] = "WorktreePopup", - ["X"] = "ResetPopup", - ["Z"] = "StashPopup", } status = { - ["q"] = "Close", - ["Q"] = "Command", - ["I"] = "InitRepo", - ["1"] = "Depth1", - ["2"] = "Depth2", - ["3"] = "Depth3", - ["4"] = "Depth4", - [""] = "Toggle", - ["x"] = "Discard", - ["s"] = "Stage", - ["S"] = "StageUnstaged", - [""] = "StageAll", - ["u"] = "Unstage", - ["U"] = "UnstageStaged", - ["y"] = "ShowRefs", - ["$"] = "CommandHistory", - ["#"] = "Console", - ["Y"] = "YankSelected", - [""] = "RefreshBuffer", - [""] = "GoToFile", - [""] = "PeekFile", - [""] = "VSplitOpen", - [""] = "SplitOpen", - [""] = "TabOpen", - ["{"] = "GoToPreviousHunkHeader", - ["}"] = "GoToNextHunkHeader", - ["[c"] = "OpenOrScrollUp", - ["]c"] = "OpenOrScrollDown", - [""] = "PeekUp", - [""] = "PeekDown", + ["j"] = "MoveDown", + ["k"] = "MoveUp", + ["o"] = "OpenTree", + ["q"] = "Close", + ["I"] = "InitRepo", + ["1"] = "Depth1", + ["2"] = "Depth2", + ["3"] = "Depth3", + ["4"] = "Depth4", + ["Q"] = "Command", + [""] = "Toggle", + ["x"] = "Discard", + ["s"] = "Stage", + ["S"] = "StageUnstaged", + ["L"] = "StageLine", + [""] = "StageAll", + ["u"] = "Unstage", + ["K"] = "Untrack", + ["U"] = "UnstageStaged", + ["y"] = "ShowRefs", + ["$"] = "CommandHistory", + ["Y"] = "YankSelected", + [""] = "RefreshBuffer", + [""] = "GoToFile", + [""] = "PeekFile", + [""] = "VSplitOpen", + [""] = "SplitOpen", + [""] = "TabOpen", + ["{"] = "GoToPreviousHunkHeader", + ["}"] = "GoToNextHunkHeader", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", + [""] = "PeekUp", + [""] = "PeekDown", } < ============================================================================== From 1bf132ac805559792013f332f80a5ad9a64a76d1 Mon Sep 17 00:00:00 2001 From: Sergey Alexandrov Date: Mon, 25 Nov 2024 17:14:09 +0100 Subject: [PATCH 118/437] Add missing fields to "Default Config" section in README.md --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 57c6f725d..493cff8ad 100644 --- a/README.md +++ b/README.md @@ -131,14 +131,17 @@ neogit.setup { initial_branch_name = "", -- Change the default way of opening neogit kind = "tab", - -- Disable line numbers and relative line numbers + -- Disable line numbers disable_line_numbers = true, + -- Disable relative line numbers + disable_relative_line_numbers = true, -- The time after which an output console is shown for slow running commands console_timeout = 2000, -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, -- Automatically close the console if the process exits with a 0 (success) status auto_close_console = true, + notification_icon = "󰊢", status = { show_head_commit_hash = true, recent_commit_count = 10, @@ -194,6 +197,9 @@ neogit.setup { merge_editor = { kind = "auto", }, + description_editor = { + kind = "auto", + }, tag_editor = { kind = "auto", }, @@ -203,6 +209,12 @@ neogit.setup { popup = { kind = "split", }, + stash = { + kind = "tab", + }, + refs_view = { + kind = "tab", + }, signs = { -- { CLOSED, OPENED } hunk = { "", "" }, From 9008290c81118da7d2855ff44cee22dcf8f9852c Mon Sep 17 00:00:00 2001 From: Sergey Alexandrov Date: Mon, 25 Nov 2024 17:14:51 +0100 Subject: [PATCH 119/437] Fix default value of preview_buffer["kind"] field in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 493cff8ad..a5cdbd0af 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ neogit.setup { kind = "auto", }, preview_buffer = { - kind = "floating", + kind = "floating_console", }, popup = { kind = "split", From d2472ae25d3b99227019047af72bc4ec541d328f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Nov 2024 21:17:24 +0100 Subject: [PATCH 120/437] Formatting --- lua/neogit/popups/branch_config/init.lua | 17 +++++++++++---- lua/neogit/popups/cherry_pick/init.lua | 27 ++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index fbac9d724..5076d5d56 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -6,6 +6,7 @@ local actions = require("neogit.popups.branch_config.actions") function M.create(branch) branch = branch or git.branch.current() + local g_pull_rebase = git.config.get_global("pull.rebase") local pull_rebase_entry = git.config.get_local("pull.rebase") local pull_rebase = pull_rebase_entry:is_set() and pull_rebase_entry.value or "false" @@ -14,7 +15,9 @@ function M.create(branch) .builder() :name("NeogitBranchConfigPopup") :config_heading("Configure branch") - :config("d", "branch." .. branch .. ".description", { fn = actions.description_config(branch) }) + :config("d", "branch." .. branch .. ".description", { + fn = actions.description_config(branch), + }) :config("u", "branch." .. branch .. ".merge", { fn = actions.merge_config(branch), callback = function(popup) @@ -25,7 +28,9 @@ function M.create(branch) end end, }) - :config("m", "branch." .. branch .. ".remote", { passive = true }) + :config("m", "branch." .. branch .. ".remote", { + passive = true, + }) :config("r", "branch." .. branch .. ".rebase", { options = { { display = "true", value = "true" }, @@ -33,7 +38,9 @@ function M.create(branch) { display = "pull.rebase:" .. pull_rebase, value = "" }, }, }) - :config("p", "branch." .. branch .. ".pushRemote", { options = actions.remotes_for_config() }) + :config("p", "branch." .. branch .. ".pushRemote", { + options = actions.remotes_for_config(), + }) :config_heading("") :config_heading("Configure repository defaults") :config("R", "pull.rebase", { @@ -49,7 +56,9 @@ function M.create(branch) }, }, }) - :config("P", "remote.pushDefault", { options = actions.remotes_for_config() }) + :config("P", "remote.pushDefault", { + options = actions.remotes_for_config(), + }) :config("b", "neogit.baseBranch") :config("A", "neogit.askSetPushDefault", { options = { diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index eeb4cff35..e31a84095 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -10,22 +10,27 @@ function M.create(env) local p = popup .builder() :name("NeogitCherryPickPopup") - :option_if(not in_progress, "m", "mainline", "", "Replay merge relative to parent", { key_prefix = "-" }) + :option_if(not in_progress, "m", "mainline", "", "Replay merge relative to parent", { + key_prefix = "-", + }) :option_if(not in_progress, "s", "strategy", "", "Strategy", { key_prefix = "=", choices = { "octopus", "ours", "resolve", "subtree", "recursive" }, }) - :switch_if( - not in_progress, - "F", - "ff", - "Attempt fast-forward", - { enabled = true, incompatible = { "edit" } } - ) - :switch_if(not in_progress, "x", "x", "Reference cherry in commit message", { cli_prefix = "-" }) - :switch_if(not in_progress, "e", "edit", "Edit commit messages", { incompatible = { "ff" } }) + :switch_if(not in_progress, "F", "ff", "Attempt fast-forward", { + enabled = true, + incompatible = { "edit" }, + }) + :switch_if(not in_progress, "x", "x", "Reference cherry in commit message", { + cli_prefix = " - ", + }) + :switch_if(not in_progress, "e", "edit", "Edit commit messages", { + incompatible = { "ff" }, + }) :switch_if(not in_progress, "s", "signoff", "Add Signed-off-by lines") - :option_if(not in_progress, "S", "gpg-sign", "", "Sign using gpg", { key_prefix = "-" }) + :option_if(not in_progress, "S", "gpg-sign", "", "Sign using gpg", { + key_prefix = "-", + }) :group_heading_if(not in_progress, "Apply here") :action_if(not in_progress, "A", "Pick", actions.pick) :action_if(not in_progress, "a", "Apply", actions.apply) From 03e8b7afde23f982742ad3aab6fd749ce89a19e6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Nov 2024 21:28:01 +0100 Subject: [PATCH 121/437] Fix Cherry pick donate and implement harvest --- lua/neogit/lib/git/branch.lua | 25 ++++++++- lua/neogit/lib/git/cherry_pick.lua | 2 +- lua/neogit/popups/cherry_pick/actions.lua | 65 +++++++++++++++++++---- lua/neogit/popups/cherry_pick/init.lua | 2 +- spec/popups/cherry_pick_popup_spec.rb | 2 +- 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 6c369603f..97a5853cc 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -53,6 +53,30 @@ function M.get_recent_local_branches() return util.deduplicate(branches) end +---@param relation? string +---@param commit? string +function M.list_related_branches(relation, commit, ...) + local result = git.cli.branch.args(relation or "", commit or "", ...).call({ hidden = true }) + + local branches = {} + for _, branch in ipairs(result.stdout) do + branch = branch:match("^%s*(.-)%s*$") + if branch and + not branch:match("^%(HEAD") and + not branch:match("^HEAD ->") and + branch ~= "" then + table.insert(branches, branch) + end + end + + return branches +end + +---@param commit string +function M.list_containing_branches(commit, ...) + return M.list_related_branches("--contains", commit, ...) +end + ---@return ProcessResult function M.checkout(name, args) return git.cli.checkout.branch(name).arg_list(args or {}).call { await = true } @@ -391,5 +415,4 @@ end M.register = function(meta) meta.update_branch_information = update_branch_information end - return M diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index 0cfb0999a..0e64449b7 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -50,7 +50,7 @@ end ---@param commits string[] ---@param src? string ---@param dst string ----@param start string +---@param start? string ---@param checkout_dst? boolean function M.move(commits, src, dst, args, start, checkout_dst) local current = git.branch.current() diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index 250a6ce08..e6830cf14 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -51,38 +51,81 @@ function M.squash(popup) end end -function M.donate(popup) - local head = git.rev_parse.oid("HEAD") - +---@param popup PopupData +---@param verb string +---@return string[] +local function get_cherries(popup, verb) local commits if #popup.state.env.commits > 1 then commits = popup.state.env.commits else - local ref = FuzzyFinderBuffer.new(util.merge(popup.state.env.commits, git.refs.list_branches())) - :open_async { prompt_prefix = "Donate" } - if not git.log.is_ancestor(head, git.rev_parse.oid(ref)) then - return notification.error("Cannot donate cherries that are not reachable from HEAD") - end + local refs = util.merge(popup.state.env.commits, git.refs.list_branches()) + local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = verb .. " cherry" } if ref == popup.state.env.commits[1] then commits = popup.state.env.commits else - commits = util.map(git.cherry.list(head, ref), function(cherry) + commits = util.map(git.cherry.list(git.rev_parse.oid("HEAD"), ref), function(cherry) return cherry.oid or cherry end) end + + if not commits[1] then + commits = { git.rev_parse.oid(ref) } + end end - local src = git.branch.is_detached() and head or git.branch.current() + return commits +end + +---@param popup PopupData +function M.donate(popup) + local commits = get_cherries(popup, "Donate") + local src = git.branch.current() or git.rev_parse.oid("HEAD") + + if not git.log.is_ancestor(commits[1], git.rev_parse.oid(src)) then + return notification.error("Cannot donate cherries that are not reachable from HEAD") + end local prefix = string.format("Move %d cherr%s to branch", #commits, #commits > 1 and "ies" or "y") - local dst = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = prefix } + local options = git.refs.list_branches() + util.remove_item_from_table(options, src) + local dst = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = prefix } if dst then + notification.info(("Moved %d cherr%s from %q to %q"):format(#commits, #commits > 1 and "ies" or "y", src, dst)) git.cherry_pick.move(commits, src, dst, popup:get_arguments()) end end +---@param popup PopupData +function M.harvest(popup) + local current = git.branch.current() + if not current then + return + end + + local commits = get_cherries(popup, "Harvest") + + if git.log.is_ancestor(commits[1], git.rev_parse.oid("HEAD")) then + return notification.error("Cannot harvest cherries that are reachable from HEAD") + end + + local branch + local containing_branches = git.branch.list_containing_branches(commits[1]) + if #containing_branches > 1 then + local prefix = string.format("Remove %d cherr%s from branch", #commits, #commits > 1 and "ies" or "y") + branch = FuzzyFinderBuffer.new(containing_branches):open_async { prompt_prefix = prefix } + else + branch = containing_branches[1] + end + + if branch then + notification.info(("Harvested %d cherr%s"):format(#commits, #commits > 1 and "ies" or "y")) + git.cherry_pick.move(commits, branch, current, popup:get_arguments(), nil, true) + end +end + function M.continue() git.cherry_pick.continue() end diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index e31a84095..26d55db99 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -34,7 +34,7 @@ function M.create(env) :group_heading_if(not in_progress, "Apply here") :action_if(not in_progress, "A", "Pick", actions.pick) :action_if(not in_progress, "a", "Apply", actions.apply) - :action_if(not in_progress, "h", "Harvest") + :action_if(not in_progress, "h", "Harvest", actions.harvest) :action_if(not in_progress, "m", "Squash", actions.squash) :new_action_group_if(not in_progress, "Apply elsewhere") :action_if(not in_progress, "d", "Donate", actions.donate) diff --git a/spec/popups/cherry_pick_popup_spec.rb b/spec/popups/cherry_pick_popup_spec.rb index b8f9dac11..a98bd109b 100644 --- a/spec/popups/cherry_pick_popup_spec.rb +++ b/spec/popups/cherry_pick_popup_spec.rb @@ -25,5 +25,5 @@ end %w[-m =s -F -x -e -s -S].each { include_examples "argument", _1 } - %w[A a m d].each { include_examples "interaction", _1 } + %w[A a m d h].each { include_examples "interaction", _1 } end From 36f2aedf3125b7b77810bee24a5b6127e00c48aa Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Nov 2024 21:28:16 +0100 Subject: [PATCH 122/437] Fix formatting --- lua/neogit/lib/git/branch.lua | 7 ++----- lua/neogit/popups/cherry_pick/actions.lua | 4 +++- lua/neogit/popups/cherry_pick/init.lua | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 97a5853cc..6fe144aa8 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -56,15 +56,12 @@ end ---@param relation? string ---@param commit? string function M.list_related_branches(relation, commit, ...) - local result = git.cli.branch.args(relation or "", commit or "", ...).call({ hidden = true }) + local result = git.cli.branch.args(relation or "", commit or "", ...).call { hidden = true } local branches = {} for _, branch in ipairs(result.stdout) do branch = branch:match("^%s*(.-)%s*$") - if branch and - not branch:match("^%(HEAD") and - not branch:match("^HEAD ->") and - branch ~= "" then + if branch and not branch:match("^%(HEAD") and not branch:match("^HEAD ->") and branch ~= "" then table.insert(branches, branch) end end diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index e6830cf14..764e81934 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -93,7 +93,9 @@ function M.donate(popup) local dst = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = prefix } if dst then - notification.info(("Moved %d cherr%s from %q to %q"):format(#commits, #commits > 1 and "ies" or "y", src, dst)) + notification.info( + ("Moved %d cherr%s from %q to %q"):format(#commits, #commits > 1 and "ies" or "y", src, dst) + ) git.cherry_pick.move(commits, src, dst, popup:get_arguments()) end end diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index 26d55db99..2f48714b0 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -22,7 +22,7 @@ function M.create(env) incompatible = { "edit" }, }) :switch_if(not in_progress, "x", "x", "Reference cherry in commit message", { - cli_prefix = " - ", + cli_prefix = "-", }) :switch_if(not in_progress, "e", "edit", "Edit commit messages", { incompatible = { "ff" }, From c50dc569c0c7a97f5c06c10ee33cae72115dfc77 Mon Sep 17 00:00:00 2001 From: Sergey Alexandrov Date: Wed, 27 Nov 2024 09:31:28 +0100 Subject: [PATCH 123/437] Remove non-existent StageLine keymap from documentation The `["L"] = "StageLine"` keymap does not exist in upstream HEAD. It is a feature that I'm working on locally. The keymap was copied over to documentation by accident. --- README.md | 1 - doc/neogit.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index a5cdbd0af..83cc2c0bb 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,6 @@ neogit.setup { ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", - ["L"] = "StageLine", [""] = "StageAll", ["u"] = "Unstage", ["K"] = "Untrack", diff --git a/doc/neogit.txt b/doc/neogit.txt index 4e27d6a36..c51dac6c7 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -355,7 +355,6 @@ The following mappings can all be customized via the setup function. ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", - ["L"] = "StageLine", [""] = "StageAll", ["u"] = "Unstage", ["K"] = "Untrack", From a394568d88d3aab9289c96308796e882b7646645 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 13:00:51 +0100 Subject: [PATCH 124/437] Add spec for unsetting branch remote/merge heads --- spec/popups/branch_popup_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/popups/branch_popup_spec.rb b/spec/popups/branch_popup_spec.rb index 8089c1d29..09d47899a 100644 --- a/spec/popups/branch_popup_spec.rb +++ b/spec/popups/branch_popup_spec.rb @@ -48,6 +48,25 @@ expect(git.config("branch.#{git.branch.name}.remote")).to eq(".") expect(git.config("branch.#{git.branch.name}.merge")).to eq("refs/heads/master") end + + it "unsets both values if already set" do + nvim.keys("umaster") + + expect(nvim.screen[8..9]).to eq( + [" u branch.master.merge refs/heads/master ", + " branch.master.remote . "] + ) + + nvim.keys("u") + + expect_git_failure { git.config("branch.#{git.branch.name}.remote") } + expect_git_failure { git.config("branch.#{git.branch.name}.merge") } + + expect(nvim.screen[8..9]).to eq( + [" u branch.master.merge unset ", + " branch.master.remote unset "] + ) + end end describe "branch..rebase" do From 51f4f87feb6820cc48d778c42d32bee654449805 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 13:21:26 +0100 Subject: [PATCH 125/437] Update docs for merge and cherry pick popups --- doc/neogit.txt | 52 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 4e27d6a36..76d49980c 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -704,13 +704,27 @@ Actions: *neogit_cherry_pick_popup_actions* Otherwise the user is prompted to select one or more commits. • Harvest *neogit_cherry_pick_harvest* - (Not yet implemented) + This command moves the selected COMMITS that must be located on another + BRANCH onto the current branch instead, removing them from the former. + When this command succeeds, then the same branch is current as before. + + Applying the commits on the current branch or removing them from the other + branch can lead to conflicts. When that happens, then this command stops + and you have to resolve the conflicts and then finish the process manually. • Squash *neogit_cherry_pick_squash* - (Not yet implemented) + See: |neogit_merge_squash| • Donate *neogit_cherry_pick_donate* - (Not yet implemented) + This command moves the selected COMMITS from the current branch onto + another existing BRANCH, removing them from the former. When this command + succeeds, then the same branch is current as before. + + HEAD is allowed to be detached initially. + + Applying the commits on the other branch or removing them from the current + branch can lead to conflicts. When that happens, then this command stops + and you have to resolve the conflicts and then finish the process manually. • Spinout *neogit_cherry_pick_spinout* (Not yet implemented) @@ -1209,7 +1223,37 @@ Log Popup *neogit_log_popup* ============================================================================== Merge Popup *neogit_merge_popup* -(TODO) +Arguments: *neogit_merge_popup_args* + (TODO) + +Actions: *neogit_merge_popup_actions* + • Merge *neogit_merge_merge* + This command merges another branch or revision into the current branch. + + • Merge and edit message *neogit_merge_editmsg* + Like `Merge` above, but opens editor to modify commit message. + + • Merge but don't commit *neogit_merge_nocommit* + This command merges another branch or revision into the current branch, + but does not actually create the merge commit, allowing the user to make + modifications before committing themselves. + + • Absorb *neogit_merge_absorb* + (Not yet implemented) + + • Preview Merge *neogit_merge_preview* + (Not yet implemented) + + • Squash Merge *neogit_merge_squash* + This command squashes the changes introduced by another branch or revision + into the current branch. This only applies the changes made by the + squashed commits. No information is preserved that would allow creating an + actual merge commit. + + Instead of this command you should probably use a cherry-pick command. + + • Dissolve *neogit_merge_dissolve* + (Not yet implemented) ============================================================================== Remote Popup *neogit_remote_popup* From d6aff990039f46d60f78c7a8451eb8c638368720 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 21:50:21 +0100 Subject: [PATCH 126/437] Implement Push -> a tag --- doc/neogit.txt | 2 +- lua/neogit/popups/push/actions.lua | 25 +++++++++++++++++++++++-- lua/neogit/popups/push/init.lua | 4 ++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 1325e1816..dd6833c47 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1422,7 +1422,7 @@ Actions: *neogit_push_popup_actions* (Not yet implemented) • Push a tag *neogit_push_tag* - (Not yet implemented) + Pushes a single tag to a remote. • Push all tags *neogit_push_all_tags* Pushes all tags to selected remote. If only one remote exists, that will diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 8bd30f6e8..ed0c572a0 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -133,14 +133,35 @@ function M.push_other(popup) push_to(popup:get_arguments(), remote, source .. ":" .. destination) end -function M.push_tags(popup) +function M.push_a_tag(popup) + local tags = git.tag.list() + + local tag = FuzzyFinderBuffer.new(tags):open_async { prompt_prefix = "Push tag" } + if not tag then + return + end + + local remotes = git.remote.list() + local remote + if #remotes == 1 then + remote = remotes[1] + else + remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = ("Push %s to remote"):format(tag) } + end + + if tag and remote then + push_to({ tag, unpack(popup:get_arguments()) }, remote) + end +end + +function M.push_all_tags(popup) local remotes = git.remote.list() local remote 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 remote" } end if remote then diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index b5644a4ee..499ce1ab1 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -23,8 +23,8 @@ function M.create(env) :action("o", "another branch", actions.push_other) :action("r", "explicit refspecs") :action("m", "matching branches") - :action("T", "a tag") - :action("t", "all tags", actions.push_tags) + :action("T", "a tag", actions.push_a_tag) + :action("t", "all tags", actions.push_all_tags) :new_action_group("Configure") :action("C", "Set variables...", actions.configure) :env({ From 1fab0b1367ac4c2230c17192d2a5d8ea0ede5be8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 22:12:13 +0100 Subject: [PATCH 127/437] Implement Push -> explicit refspec --- doc/neogit.txt | 2 +- lua/neogit/popups/push/actions.lua | 50 ++++++++++++++++++++---------- lua/neogit/popups/push/init.lua | 6 ++-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index dd6833c47..85380a319 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1416,7 +1416,7 @@ Actions: *neogit_push_popup_actions* the user. • Push explicit refspecs *neogit_push_explicit_refspecs* - (Not yet implemented) + Push a refspec to a remote. • Push matching branches *neogit_push_matching_branches* (Not yet implemented) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index ed0c572a0..f0ead6ed6 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -3,6 +3,7 @@ local git = require("neogit.lib.git") local logger = require("neogit.logger") local notification = require("neogit.lib.notification") local input = require("neogit.lib.input") +local util = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -133,6 +134,21 @@ function M.push_other(popup) push_to(popup:get_arguments(), remote, source .. ":" .. destination) end +---@param prompt string +---@return string|nil +local function choose_remote(prompt) + local remotes = git.remote.list() + local remote + if #remotes == 1 then + remote = remotes[1] + else + remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = prompt } + end + + return remote +end + +---@param popup PopupData function M.push_a_tag(popup) local tags = git.tag.list() @@ -141,31 +157,31 @@ function M.push_a_tag(popup) return end - local remotes = git.remote.list() - local remote - if #remotes == 1 then - remote = remotes[1] - else - remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = ("Push %s to remote"):format(tag) } - end - - if tag and remote then + local remote = choose_remote(("Push %s to remote"):format(tag)) + if remote then push_to({ tag, unpack(popup:get_arguments()) }, remote) end end +---@param popup PopupData function M.push_all_tags(popup) - local remotes = git.remote.list() + local remote = choose_remote("Push tags to remote") + if remote then + push_to({ "--tags", unpack(popup:get_arguments()) }, remote) + end +end - local remote - if #remotes == 1 then - remote = remotes[1] - else - remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "Push tags to remote" } +---@param popup PopupData +function M.explicit_refspec(popup) + local remote = choose_remote("Push to remote") + if not remote then + return end - if remote then - push_to({ "--tags", unpack(popup:get_arguments()) }, remote) + local options = util.merge({ "HEAD" }, git.refs.list_local_branches()) + local refspec = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "Push refspec" } + if refspec then + push_to({ "-v", unpack(popup:get_arguments()) }, remote, refspec) end end diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index 499ce1ab1..72a03fb7c 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -12,17 +12,17 @@ function M.create(env) :name("NeogitPushPopup") :switch("f", "force-with-lease", "Force with lease") :switch("F", "force", "Force") - :switch("u", "set-upstream", "Set the upstream before pushing") :switch("h", "no-verify", "Disable hooks") :switch("d", "dry-run", "Dry run") + :switch("u", "set-upstream", "Set the upstream before pushing") :group_heading("Push " .. ((current and (current .. " ")) or "") .. "to") :action("p", git.branch.pushRemote_label(), actions.to_pushremote) :action("u", git.branch.upstream_label(), actions.to_upstream) :action("e", "elsewhere", actions.to_elsewhere) :new_action_group("Push") :action("o", "another branch", actions.push_other) - :action("r", "explicit refspecs") - :action("m", "matching branches") + :action("r", "explicit refspec", actions.explicit_refspec) + :action("m", "matching branches", actions.matching_branches) :action("T", "a tag", actions.push_a_tag) :action("t", "all tags", actions.push_all_tags) :new_action_group("Configure") From 9e654c1a335086b26bc8168962855cf2b56f0b62 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 22:14:56 +0100 Subject: [PATCH 128/437] Implement push -> matching branches --- doc/neogit.txt | 2 +- lua/neogit/popups/push/actions.lua | 8 ++++++++ spec/popups/push_popup_spec.rb | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 85380a319..9fe8baeb7 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1419,7 +1419,7 @@ Actions: *neogit_push_popup_actions* Push a refspec to a remote. • Push matching branches *neogit_push_matching_branches* - (Not yet implemented) + Push all matching branches to another repository. • Push a tag *neogit_push_tag* Pushes a single tag to a remote. diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index f0ead6ed6..2304c302e 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -171,6 +171,14 @@ function M.push_all_tags(popup) end end +---@param popup PopupData +function M.matching_branches(popup) + local remote = choose_remote("Push matching branches to") + if remote then + push_to({ "-v", unpack(popup:get_arguments()) }, remote, ":") + end +end + ---@param popup PopupData function M.explicit_refspec(popup) local remote = choose_remote("Push to remote") diff --git a/spec/popups/push_popup_spec.rb b/spec/popups/push_popup_spec.rb index 817ec4946..8dfd4833a 100644 --- a/spec/popups/push_popup_spec.rb +++ b/spec/popups/push_popup_spec.rb @@ -10,9 +10,9 @@ " Arguments ", " -f Force with lease (--force-with-lease) ", " -F Force (--force) ", - " -u Set the upstream before pushing (--set-upstream) ", " -h Disable hooks (--no-verify) ", " -d Dry run (--dry-run) ", + " -u Set the upstream before pushing (--set-upstream) ", " ", " Push master to Push Configure ", " p pushRemote, setting that o another branch C Set variables... ", From 30b53e3c8562111474c446ce7d176ddc8dabe130 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Nov 2024 23:04:52 +0100 Subject: [PATCH 129/437] fix --- spec/popups/push_popup_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/popups/push_popup_spec.rb b/spec/popups/push_popup_spec.rb index 8dfd4833a..fc6e32bf8 100644 --- a/spec/popups/push_popup_spec.rb +++ b/spec/popups/push_popup_spec.rb @@ -16,7 +16,7 @@ " ", " Push master to Push Configure ", " p pushRemote, setting that o another branch C Set variables... ", - " u @{upstream}, creating it r explicit refspecs ", + " u @{upstream}, creating it r explicit refspec ", " e elsewhere m matching branches ", " T a tag ", " t all tags " From 5f384093f64b3c10027f358735d07efb7e53fa97 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 28 Nov 2024 08:17:13 +0100 Subject: [PATCH 130/437] Handle detached head in log popup --- lua/neogit/popups/log/actions.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 4f054d2a6..af3aabebc 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -32,14 +32,13 @@ local function fetch_more_commits(popup, flags) end end --- TODO: Handle when head is detached function M.log_current(popup) LogViewBuffer.new( commits(popup, {}), popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, {}), - "Commits in " .. git.branch.current(), + "Commits in " .. git.branch.current() or ("(detached) " .. git.log.message("HEAD")), git.remote.list() ):open() end From 74e8922bc4888435091d0ffff44b67fe9d023929 Mon Sep 17 00:00:00 2001 From: Sebastian Witte Date: Fri, 29 Nov 2024 18:59:03 +0100 Subject: [PATCH 131/437] Open commit view immediately when switching commits Before this addition of the M:close() call, it was necessary to press the key for OpenOrScrollUp/OpenOrScrollDown twice to view a different commit. The first key press closed the old commit view and the second opens the current. --- lua/neogit/buffers/commit_view/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index ef6e63859..0ec999abb 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -89,6 +89,7 @@ function M.open_or_run_in_window(commit_id, filter, cmd) if M.is_open() and M.instance.commit_info.commit_arg == commit_id then M.instance.buffer:win_exec(cmd) else + M:close() local cw = api.nvim_get_current_win() M.new(commit_id, filter):open() api.nvim_set_current_win(cw) From 2eae717f6b6b95650f243293033037f9a46ec4a7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 21:54:51 +0100 Subject: [PATCH 132/437] Overhaul worktrees: - Changing to a new worktree will spawn a new window - Replace file-path picker with vim input - Remove current worktree from list of worktrees to visit --- lua/neogit/buffers/status/init.lua | 17 +++++-- lua/neogit/lib/git/worktree.lua | 15 +++--- lua/neogit/lib/input.lua | 1 + lua/neogit/popups/worktree/actions.lua | 64 +++++++++++++------------- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2839850c2..20621c75c 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -244,15 +244,22 @@ function M:close() end function M:chdir(dir) - local destination = require("plenary.path").new(dir) + local Path = require("plenary.path") + + local destination = Path:new(dir) vim.wait(5000, function() return destination:exists() end) - logger.debug("[STATUS] Changing Dir: " .. dir) - vim.api.nvim_set_current_dir(dir) - self.cwd = dir - self:dispatch_reset() + local kind = self.buffer.kind + self:close() + + vim.schedule(function() + logger.debug("[STATUS] Changing Dir: " .. dir) + vim.api.nvim_set_current_dir(dir) + local repo = require("neogit.lib.git.repository").instance(dir) + self.new(config.values, git.repo.git_root, dir):open(kind):dispatch_refresh() + end) end function M:focus() diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index 64700c2c1..b4e96adf1 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -8,10 +8,14 @@ local M = {} ---Creates new worktree at path for ref ---@param ref string branch name, tag name, HEAD, etc. ---@param path string absolute path ----@return boolean +---@return boolean, string function M.add(ref, path, params) - local result = git.cli.worktree.add.arg_list(params or {}).args(path, ref).call { await = true } - return result.code == 0 + local result = git.cli.worktree.add.arg_list(params or {}).args(path, ref).call() + if result.code == 0 then + return true, "" + else + return false, result.stderr[#result.stderr] + end end ---Moves an existing worktree @@ -19,7 +23,7 @@ end ---@param destination string absolute path for where to move worktree ---@return boolean function M.move(worktree, destination) - local result = git.cli.worktree.move.args(worktree, destination).call { await = true } + local result = git.cli.worktree.move.args(worktree, destination).call() return result.code == 0 end @@ -28,8 +32,7 @@ end ---@param args? table ---@return boolean function M.remove(worktree, args) - local result = - git.cli.worktree.remove.args(worktree).arg_list(args or {}).call { ignore_error = true, await = true } + local result = git.cli.worktree.remove.args(worktree).arg_list(args or {}).call { ignore_error = true } return result.code == 0 end diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index b0308478c..26a336180 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -49,6 +49,7 @@ end ---@field completion string? ---@field separator string? ---@field cancel string? +---@field prepend string? ---@param prompt string Prompt to use for user input ---@param opts GetUserInputOpts? Options table diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 60e58fac8..7a5afa514 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -7,51 +7,42 @@ local status = require("neogit.buffers.status") local notification = require("neogit.lib.notification") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") -local Path = require("plenary.path") -local scan_dir = require("plenary.scandir").scan_dir ----Poor man's dired +---@param prompt string ---@return string|nil local function get_path(prompt) - local dir = Path.new(".") - repeat - local dirs = scan_dir(dir:absolute(), { depth = 1, only_dirs = true }) - local selected = FuzzyFinderBuffer.new(util.merge({ ".." }, dirs)):open_async { - prompt_prefix = prompt, - } - - if not selected then - return - end - - if vim.startswith(selected, "/") then - dir = Path.new(selected) - else - dir = dir:joinpath(selected) - end - until not dir:exists() + return input.get_user_input(prompt, { + completion = "dir", + prepend = vim.fs.normalize(vim.uv.cwd() .. "/..") .. "/", + }) +end - local path, _ = dir:absolute():gsub("%s", "_") - return path +---@param prompt string +---@return string|nil +local function get_ref(prompt) + local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) + return FuzzyFinderBuffer.new(options):open_async { prompt_prefix = prompt } end function M.checkout_worktree() - local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - local selected = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "checkout" } + local selected = get_ref("checkout") if not selected then return end - local path = get_path(("Checkout %s in new worktree"):format(selected)) + local path = get_path(("Checkout '%s' in new worktree"):format(selected)) if not path then return end - if git.worktree.add(selected, path) then + local success, err = git.worktree.add(selected, path) + if success then notification.info("Added worktree") if status.is_open() then status.instance():chdir(path) end + else + notification.error(err) end end @@ -61,9 +52,7 @@ function M.create_worktree() return end - local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - local selected = FuzzyFinderBuffer.new(options) - :open_async { prompt_prefix = "Create and checkout branch starting at" } + local selected = get_ref("Create and checkout branch starting at") if not selected then return end @@ -73,11 +62,14 @@ function M.create_worktree() return end - if git.worktree.add(selected, path, { "-b", name }) then + local success, err = git.worktree.add(selected, path) + if success then notification.info("Added worktree") if status.is_open() then status.instance():chdir(path) end + else + notification.error(err) end end @@ -157,9 +149,15 @@ function M.delete() end function M.visit() - local options = vim.tbl_map(function(w) - return w.path - end, git.worktree.list()) + local options = vim + .iter(git.worktree.list()) + :map(function(w) + return w.path + end) + :filter(function(path) + return path ~= vim.uv.cwd() + end) + :totable() if #options == 0 then notification.info("No worktrees present") From 125edf6fca5bed2ab8ecebfc3bc9b0b5968baeb4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 21:57:24 +0100 Subject: [PATCH 133/437] Fix: when in a worktree, ignore -> private should find the main worktree to edit the .git/info/exclude file --- lua/neogit/popups/ignore/actions.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 4153f0d40..0a3bfef7b 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -43,7 +43,6 @@ function M.shared_subdirectory(popup) local choice = input.get_user_input("Ignore sub-directory", { completion = "dir" }) if choice then local subdirectory = Path:new(vim.uv.cwd(), choice) - local ignore_file = subdirectory:joinpath(".gitignore") local rules = make_rules(popup, tostring(subdirectory)) @@ -52,7 +51,7 @@ function M.shared_subdirectory(popup) end function M.private_local(popup) - local ignore_file = git.repo:git_path("info", "exclude") + local ignore_file = Path:new(git.worktree.main().path, ".git", "info", "exclude") local rules = make_rules(popup, git.repo.git_root) add_rules(ignore_file, rules) From 1b6994c4298d269d93a79f792f8c2d28f12943c2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 22:13:22 +0100 Subject: [PATCH 134/437] Fix types --- lua/neogit/buffers/status/init.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 20621c75c..5dd96ac67 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -79,7 +79,12 @@ function M:_action(name) return action(self) end ----@param kind string<"floating" | "split" | "tab" | "split" | "vsplit">|nil +---@param kind nil|string +---| "'floating'" +---| "'split'" +---| "'tab'" +---| "'split'" +---| "'vsplit'" ---@return StatusBuffer function M:open(kind) if self.buffer and self.buffer:is_visible() then @@ -257,7 +262,6 @@ function M:chdir(dir) vim.schedule(function() logger.debug("[STATUS] Changing Dir: " .. dir) vim.api.nvim_set_current_dir(dir) - local repo = require("neogit.lib.git.repository").instance(dir) self.new(config.values, git.repo.git_root, dir):open(kind):dispatch_refresh() end) end From 25dda3895e6dbbd982b32f6213b6de61f07e9252 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 23:12:40 +0100 Subject: [PATCH 135/437] Fix worktree specs --- lua/neogit/lib/git/branch.lua | 3 ++- lua/neogit/popups/worktree/actions.lua | 16 +++++++++------- spec/popups/worktree_popup_spec.rb | 15 +++++++-------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 6fe144aa8..da94146be 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -141,8 +141,9 @@ end ---@param name string ---@param base_branch? string +---@return boolean function M.create(name, base_branch) - git.cli.branch.args(name, base_branch).call { await = true } + return git.cli.branch.args(name, base_branch).call({ await = true }).code == 0 end function M.delete(name) diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 7a5afa514..a5f13132f 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -62,14 +62,16 @@ function M.create_worktree() return end - local success, err = git.worktree.add(selected, path) - if success then - notification.info("Added worktree") - if status.is_open() then - status.instance():chdir(path) + if git.branch.create(name, selected) then + local success, err = git.worktree.add(name, path) + if success then + notification.info("Added worktree") + if status.is_open() then + status.instance():chdir(path) + end + else + notification.error(err) end - else - notification.error(err) end end diff --git a/spec/popups/worktree_popup_spec.rb b/spec/popups/worktree_popup_spec.rb index b8c60fd58..d6027b14b 100644 --- a/spec/popups/worktree_popup_spec.rb +++ b/spec/popups/worktree_popup_spec.rb @@ -32,9 +32,9 @@ end it "creates a worktree for an existing branch and checks it out", :aggregate_failures do - nvim.keys("w") # Action - nvim.keys("wor") # Select "worktree-test" branch - nvim.keys("#{dir}") # go up level, new folder name + nvim.keys("w") # Action + nvim.keys("wor") # Select "worktree-test" branch + nvim.keys("#{dir}/") # go up level, new folder name expect(git.worktrees.map(&:dir).last).to match(%r{/#{dir}$}) expect(nvim.cmd("pwd").first).to match(%r{/#{dir}$}) @@ -48,11 +48,10 @@ end it "creates a worktree for a new branch and checks it out", :aggregate_failures do - nvim.input("create-worktree-test") # Branch name - - nvim.keys("W") # Action - nvim.keys("#{dir}") # go up level, new folder name - nvim.keys("mas") # Set base branch to 'master' + nvim.keys("W") # Action + nvim.keys("#{dir}/") # new folder name + nvim.keys("mas") # Set base branch to 'master' + nvim.keys("create-worktree-test") # branch name expect(git.worktrees.map(&:dir).last).to match(%r{/#{dir}$}) expect(nvim.cmd("pwd").first).to match(%r{/#{dir}$}) From 173f6879ec5d25f1bd0145cfb76e387f35d74db6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 23:34:29 +0100 Subject: [PATCH 136/437] Use "replace" to prevent jank --- lua/neogit/buffers/status/init.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 5dd96ac67..a45e53260 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -256,13 +256,11 @@ function M:chdir(dir) return destination:exists() end) - local kind = self.buffer.kind - self:close() - vim.schedule(function() logger.debug("[STATUS] Changing Dir: " .. dir) vim.api.nvim_set_current_dir(dir) - self.new(config.values, git.repo.git_root, dir):open(kind):dispatch_refresh() + require("neogit.lib.git.repository").instance(dir) + self.new(config.values, git.repo.git_root, dir):open("replace"):dispatch_refresh() end) end From d7772bca4ac00c02282b0d02623f2f8316c21f32 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Nov 2024 23:38:49 +0100 Subject: [PATCH 137/437] Pass cmd mask to process buffer --- lua/neogit/buffers/process/init.lua | 5 +++-- lua/neogit/process.lua | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index 2ea6b7b2c..aa38db1ab 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -10,10 +10,11 @@ local M = {} M.__index = M ---@param process Process +---@param mask_fn fun(string):string ---@return ProcessBuffer -function M:new(process) +function M:new(process, mask_fn) local instance = { - content = { string.format("> %s\r\n", table.concat(process.cmd, " ")) }, + content = { string.format("> %s\r\n", mask_fn(table.concat(process.cmd, " "))) }, process = process, buffer = nil, truncated = false, diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index d9de2b385..fb0dd2c8a 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -387,7 +387,7 @@ function Process:spawn(cb) self.stdin = job if not hide_console then - self.buffer = ProcessBuffer:new(self) + self.buffer = ProcessBuffer:new(self, mask_command) self:show_spinner() self:start_timer() end From 3a2379fe27adb392c251fcfe378fefbd4879e82b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 12:19:44 +0100 Subject: [PATCH 138/437] Use rev-parse to find the '.git' dir, instead of assuming it's the cwd root --- lua/neogit/lib/git/cli.lua | 19 ++++++++++++++++--- lua/neogit/lib/git/hooks.lua | 6 +++--- lua/neogit/lib/git/repository.lua | 19 ++++++++++++------- lua/neogit/popups/ignore/actions.lua | 2 +- lua/neogit/watcher.lua | 11 +++++------ 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index e4077f3a7..976838cab 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -369,6 +369,7 @@ local runner = require("neogit.runner") ---@field worktree GitCommandWorktree ---@field write-tree GitCommandWriteTree ---@field git_root fun(dir: string):string +---@field git_dir fun(dir: string):string ---@field is_inside_worktree fun(dir: string):boolean ---@field history ProcessResult[] @@ -970,11 +971,21 @@ local configurations = { --- git_root_of_cwd() returns the git repo of the cwd, which can change anytime --- after git_root_of_cwd() has been called. ---@param dir string ----@return string +---@return string Absolute path of current worktree local function git_root(dir) - local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel" } + local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel", "--path-format=absolute" } local result = vim.system(cmd, { text = true }):wait() - return Path:new(vim.trim(result.stdout)):absolute() + + return vim.trim(result.stdout) +end + +---@param dir string +---@return string Absolute path of `.git/` directory +local function git_dir(dir) + local cmd = { "git", "-C", dir, "rev-parse", "--git-common-dir", "--path-format=absolute" } + local result = vim.system(cmd, { text = true }):wait() + + return vim.trim(result.stdout) end ---@param dir string @@ -982,6 +993,7 @@ end local function is_inside_worktree(dir) local cmd = { "git", "-C", dir, "rev-parse", "--is-inside-work-tree" } local result = vim.system(cmd):wait() + return result.code == 0 end @@ -1230,6 +1242,7 @@ local meta = { local cli = setmetatable({ history = runner.history, git_root = git_root, + git_dir = git_dir, is_inside_worktree = is_inside_worktree, }, meta) diff --git a/lua/neogit/lib/git/hooks.lua b/lua/neogit/lib/git/hooks.lua index b6a2efec1..991a2ab07 100644 --- a/lua/neogit/lib/git/hooks.lua +++ b/lua/neogit/lib/git/hooks.lua @@ -47,13 +47,13 @@ function M.register(meta) meta.update_hooks = function(state) state.hooks = {} - if not Path:new(state.git_root):joinpath(".git", "hooks"):is_dir() then + if not Path:new(state.git_dir):joinpath("hooks"):is_dir() then return end - for file in vim.fs.dir(vim.fs.joinpath(state.git_root, ".git", "hooks")) do + for file in vim.fs.dir(vim.fs.joinpath(state.git_dir, "hooks")) do if not file:match("%.sample$") then - local path = vim.fs.joinpath(state.git_root, ".git", "hooks", file) + local path = vim.fs.joinpath(state.git_dir, "hooks", file) local stat = vim.uv.fs_stat(path) if stat and stat.mode and is_executable(stat.mode) then diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index fd3af277a..9dbcc0aee 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -24,6 +24,7 @@ local modules = { ---@field git_path fun(self, ...): Path ---@field refresh fun(self, table) ---@field git_root string +---@field git_dir string ---@field head NeogitRepoHead ---@field upstream NeogitRepoRemote ---@field pushRemote NeogitRepoRemote @@ -99,6 +100,7 @@ local modules = { local function empty_state() return { git_root = "", + git_dir = "", head = { branch = nil, detached = false, @@ -165,12 +167,13 @@ local function empty_state() end ---@class NeogitRepo ----@field lib table ----@field state NeogitRepoState ----@field git_root string ----@field running table ----@field interrupt table ----@field tmp_state table +---@field lib table +---@field state NeogitRepoState +---@field git_root string Project root, or worktree +---@field git_dir string '.git/' directory for repo +---@field running table +---@field interrupt table +---@field tmp_state table ---@field refresh_callbacks function[] local Repo = {} Repo.__index = Repo @@ -206,6 +209,7 @@ function Repo.new(dir) lib = {}, state = empty_state(), git_root = git.cli.git_root(dir), + git_dir = git.cli.git_dir(dir), refresh_callbacks = {}, running = util.weak_table(), interrupt = util.weak_table(), @@ -213,6 +217,7 @@ function Repo.new(dir) } instance.state.git_root = instance.git_root + instance.state.git_dir = instance.git_dir setmetatable(instance, Repo) @@ -228,7 +233,7 @@ function Repo:reset() end function Repo:git_path(...) - return Path:new(self.git_root):joinpath(".git", ...) + return Path:new(self.git_dir):joinpath(...) end function Repo:tasks(filter, state) diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 0a3bfef7b..ac6cab358 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -51,7 +51,7 @@ function M.shared_subdirectory(popup) end function M.private_local(popup) - local ignore_file = Path:new(git.worktree.main().path, ".git", "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/watcher.lua b/lua/neogit/watcher.lua index 1314c2afc..a158351ab 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -1,14 +1,13 @@ -- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103 local logger = require("neogit.logger") -local Path = require("plenary.path") local util = require("neogit.lib.util") local git = require("neogit.lib.git") local config = require("neogit.config") local a = require("plenary.async") ---@class Watcher ----@field git_root string +---@field git_dir string ---@field buffers table ---@field running boolean ---@field fs_event_handler uv_fs_event_t @@ -20,7 +19,7 @@ Watcher.__index = Watcher function Watcher.new(root) local instance = { buffers = {}, - git_root = Path:new(root):joinpath(".git"):absolute(), + git_dir = git.cli.git_dir(root), running = false, fs_event_handler = assert(vim.uv.new_fs_event()), } @@ -83,9 +82,9 @@ function Watcher:start() return self end - logger.debug("[WATCHER] Watching git dir: " .. self.git_root) + logger.debug("[WATCHER] Watching git dir: " .. self.git_dir) self.running = true - self.fs_event_handler:start(self.git_root, {}, self:fs_event_callback()) + self.fs_event_handler:start(self.git_dir, {}, self:fs_event_callback()) return self end @@ -99,7 +98,7 @@ function Watcher:stop() return self end - logger.debug("[WATCHER] Stopped watching git dir: " .. self.git_root) + logger.debug("[WATCHER] Stopped watching git dir: " .. self.git_dir) self.running = false self.fs_event_handler:stop() return self From 8e7ea8cd14a068a1f532bf9478edec3c8e3931af Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 12:25:52 +0100 Subject: [PATCH 139/437] Rename "repo.git_root" -> "repo.worktree_root" to make it more accurate. This value is the root of the current -worktree- after all. To get the `.git` dir, use "repo.git_dir" --- lua/neogit.lua | 4 ++-- lua/neogit/buffers/status/actions.lua | 11 ++++++----- lua/neogit/buffers/status/init.lua | 2 +- lua/neogit/integrations/diffview.lua | 2 +- lua/neogit/lib/git/cli.lua | 17 ++++++++--------- lua/neogit/lib/git/index.lua | 4 ++-- lua/neogit/lib/git/repository.lua | 12 ++++++------ lua/neogit/lib/git/status.lua | 12 ++++++------ lua/neogit/popups/ignore/actions.lua | 8 ++++---- tests/specs/neogit/lib/git/cli_spec.lua | 4 ++-- tests/specs/neogit/lib/git/repository_spec.lua | 4 ++-- 11 files changed, 40 insertions(+), 40 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index ecaac9171..4f052e882 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -84,7 +84,7 @@ local function construct_opts(opts) if not opts.cwd then local git = require("neogit.lib.git") - opts.cwd = git.cli.git_root(".") + opts.cwd = git.cli.worktree_root(".") if opts.cwd == "" then opts.cwd = vim.uv.cwd() @@ -111,7 +111,7 @@ local function open_status_buffer(opts) -- going to open into. We will use vim.fn.lcd() in the status buffer constructor, so this will eventually be -- correct. local repo = require("neogit.lib.git.repository").instance(opts.cwd) - status.new(config.values, repo.git_root, opts.cwd):open(opts.kind):dispatch_refresh() + status.new(config.values, repo.worktree_root, opts.cwd):open(opts.kind):dispatch_refresh() end ---@alias Popup diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 2ee8460b2..4bb3eb65e 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -406,7 +406,7 @@ end ---@param self StatusBuffer M.v_ignore_popup = function(self) return popups.open("ignore", function(p) - p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } + p { paths = self.buffer.ui:get_filepaths_in_selection(), worktree_root = git.repo.worktree_root } end) end @@ -638,7 +638,7 @@ end ---@param _self StatusBuffer M.n_show_refs = function(_self) return a.void(function() - require("neogit.buffers.refs_view").new(git.refs.list_parsed(), git.repo.git_root):open() + require("neogit.buffers.refs_view").new(git.refs.list_parsed(), git.repo.worktree_root):open() end) end @@ -1269,7 +1269,7 @@ M.n_ignore_popup = function(self) local path = self.buffer.ui:get_hunk_or_filename_under_cursor() p { paths = { path and path.escaped_path }, - git_root = git.repo.git_root, + worktree_root = git.repo.worktree_root, } end) end @@ -1309,7 +1309,7 @@ M.n_help_popup = function(self) }, ignore = { paths = { path and path.escaped_path }, - git_root = git.repo.git_root, + worktree_root = git.repo.worktree_root, }, remote = {}, fetch = {}, @@ -1364,7 +1364,8 @@ M.n_command = function(self) local runner = require("neogit.runner") return a.void(function() - local cmd = input.get_user_input(("Run command in %s"):format(git.repo.git_root), { prepend = "git " }) + local cmd = + input.get_user_input(("Run command in %s"):format(git.repo.worktree_root), { prepend = "git " }) if not cmd then return end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index a45e53260..378a94230 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -260,7 +260,7 @@ function M:chdir(dir) 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.git_root, dir):open("replace"):dispatch_refresh() + self.new(config.values, git.repo.worktree_root, dir):open("replace"):dispatch_refresh() end) end diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 1fc7dfe58..dd9cb70c2 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -73,7 +73,7 @@ local function get_local_diff_view(section_name, item_name, opts) local files = update_files() local view = CDiffView { - git_root = git.repo.git_root, + git_root = git.repo.worktree_root, left = left, right = right, files = files, diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 976838cab..efd0bbec4 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1,7 +1,6 @@ local git = require("neogit.lib.git") local process = require("neogit.process") local util = require("neogit.lib.util") -local Path = require("plenary.path") local runner = require("neogit.runner") ---@class GitCommandSetup @@ -368,7 +367,7 @@ local runner = require("neogit.runner") ---@field verify-commit GitCommandVerifyCommit ---@field worktree GitCommandWorktree ---@field write-tree GitCommandWriteTree ----@field git_root fun(dir: string):string +---@field worktree_root fun(dir: string):string ---@field git_dir fun(dir: string):string ---@field is_inside_worktree fun(dir: string):boolean ---@field history ProcessResult[] @@ -966,13 +965,13 @@ local configurations = { ["bisect"] = config {}, } ---- NOTE: Use require("neogit.lib.git").repo.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. +--- NOTE: Use require("neogit.lib.git").repo.worktree_root instead of calling this function. +--- repository.worktree_root is used by all other library functions, so it's most likely the one you want to use. +--- worktree_root_of_cwd() returns the git repo of the cwd, which can change anytime +--- after worktree_root_of_cwd() has been called. ---@param dir string ---@return string Absolute path of current worktree -local function git_root(dir) +local function worktree_root(dir) local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel", "--path-format=absolute" } local result = vim.system(cmd, { text = true }):wait() @@ -1164,7 +1163,7 @@ local function new_builder(subcommand) return process.new { cmd = cmd, - cwd = git.repo.git_root, + cwd = git.repo.worktree_root, env = state.env, input = state.input, on_error = opts.on_error, @@ -1241,7 +1240,7 @@ local meta = { local cli = setmetatable({ history = runner.history, - git_root = git_root, + worktree_root = worktree_root, git_dir = git_dir, is_inside_worktree = is_inside_worktree, }, meta) diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index a40210e6b..35b9c8cfe 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -67,10 +67,10 @@ function M.generate_patch(item, hunk, from, to, reverse) string.format("@@ -%d,%d +%d,%d @@", hunk.index_from, len_start, hunk.index_from, len_start + len_offset) ) - local git_root = git.repo.git_root + local worktree_root = git.repo.worktree_root assert(item.absolute_path, "Item is not a path") - local path = Path:new(item.absolute_path):make_relative(git_root) + local path = Path:new(item.absolute_path):make_relative(worktree_root) table.insert(diff_content, 1, string.format("+++ b/%s", path)) table.insert(diff_content, 1, string.format("--- a/%s", path)) diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 9dbcc0aee..a2cf95095 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -23,7 +23,7 @@ local modules = { ---@class NeogitRepoState ---@field git_path fun(self, ...): Path ---@field refresh fun(self, table) ----@field git_root string +---@field worktree_root string ---@field git_dir string ---@field head NeogitRepoHead ---@field upstream NeogitRepoRemote @@ -99,7 +99,7 @@ local modules = { ---@return NeogitRepoState local function empty_state() return { - git_root = "", + worktree_root = "", git_dir = "", head = { branch = nil, @@ -169,7 +169,7 @@ end ---@class NeogitRepo ---@field lib table ---@field state NeogitRepoState ----@field git_root string Project root, or worktree +---@field worktree_root string Project root, or worktree ---@field git_dir string '.git/' directory for repo ---@field running table ---@field interrupt table @@ -208,7 +208,7 @@ function Repo.new(dir) local instance = { lib = {}, state = empty_state(), - git_root = git.cli.git_root(dir), + worktree_root = git.cli.worktree_root(dir), git_dir = git.cli.git_dir(dir), refresh_callbacks = {}, running = util.weak_table(), @@ -216,7 +216,7 @@ function Repo.new(dir) tmp_state = util.weak_table("v"), } - instance.state.git_root = instance.git_root + instance.state.worktree_root = instance.worktree_root instance.state.git_dir = instance.git_dir setmetatable(instance, Repo) @@ -282,7 +282,7 @@ function Repo:set_state(id) end function Repo:refresh(opts) - if self.git_root == "" then + if self.worktree_root == "" then logger.debug("[REPO] No git root found - skipping refresh") return end diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index d0c598009..2877789de 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -124,12 +124,12 @@ local function update_status(state, filter) local mode, _, _, _, _, _, _, _, _, name = rest:match(match_u) table.insert( state.unstaged.items, - update_file("unstaged", state.git_root, old_files.unstaged_files[name], mode, name) + update_file("unstaged", state.worktree_root, old_files.unstaged_files[name], mode, name) ) elseif kind == "?" then table.insert( state.untracked.items, - update_file("untracked", state.git_root, old_files.untracked_files[rest], "?", rest) + update_file("untracked", state.worktree_root, old_files.untracked_files[rest], "?", rest) ) elseif kind == "1" then local mode_staged, mode_unstaged, submodule, mH, mI, mW, hH, _, name = rest:match(match_1) @@ -145,7 +145,7 @@ local function update_status(state, filter) state.staged.items, update_file( "staged", - state.git_root, + state.worktree_root, old_files.staged_files[name], mode_staged, name, @@ -161,7 +161,7 @@ local function update_status(state, filter) state.unstaged.items, update_file( "unstaged", - state.git_root, + state.worktree_root, old_files.unstaged_files[name], mode_unstaged, name, @@ -181,7 +181,7 @@ local function update_status(state, filter) state.staged.items, update_file( "staged", - state.git_root, + state.worktree_root, old_files.staged_files[name], mode_staged, name, @@ -197,7 +197,7 @@ local function update_status(state, filter) state.unstaged.items, update_file( "unstaged", - state.git_root, + state.worktree_root, old_files.unstaged_files[name], mode_unstaged, name, diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index ac6cab358..ebbd3b3f3 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -33,8 +33,8 @@ local function add_rules(path, rules) end function M.shared_toplevel(popup) - local ignore_file = Path:new(git.repo.git_root, ".gitignore") - local rules = make_rules(popup, git.repo.git_root) + local ignore_file = Path:new(git.repo.worktree_root, ".gitignore") + local rules = make_rules(popup, git.repo.worktree_root) add_rules(ignore_file, rules) end @@ -52,14 +52,14 @@ end function M.private_local(popup) local ignore_file = git.repo:git_path("info", "exclude") - local rules = make_rules(popup, git.repo.git_root) + local rules = make_rules(popup, git.repo.worktree_root) add_rules(ignore_file, rules) end function M.private_global(popup) local ignore_file = Path:new(git.config.get_global("core.excludesfile"):read()) - local rules = make_rules(popup, git.repo.git_root) + local rules = make_rules(popup, git.repo.worktree_root) add_rules(ignore_file, rules) end diff --git a/tests/specs/neogit/lib/git/cli_spec.lua b/tests/specs/neogit/lib/git/cli_spec.lua index d80c89e8b..a913e9e7b 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.worktree_root(".") 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.worktree_root(".") eq(detected_root_dir, git_dir) end) ) diff --git a/tests/specs/neogit/lib/git/repository_spec.lua b/tests/specs/neogit/lib/git/repository_spec.lua index c5757e820..5b8e9b8c9 100644 --- a/tests/specs/neogit/lib/git/repository_spec.lua +++ b/tests/specs/neogit/lib/git/repository_spec.lua @@ -8,8 +8,8 @@ describe("lib.git.instance", function() it( "creates cached git instance and returns it", in_prepared_repo(function(root_dir) - local dir1 = git_repo.instance(root_dir).git_root - local dir2 = git_repo.instance().git_root + local dir1 = git_repo.instance(root_dir).worktree_root + local dir2 = git_repo.instance().worktree_root eq(dir1, dir2) end) ) From b84d4c111ddfb6904fbf6781306f62399b2ba7b3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 12:53:39 +0100 Subject: [PATCH 140/437] Use Path() to ensure absolute paths. when in main worktree the path is relative from the cli despite specifying otherwise. --- lua/neogit/lib/git/cli.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index efd0bbec4..f9427c432 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local process = require("neogit.process") local util = require("neogit.lib.util") +local Path = require("plenary.path") local runner = require("neogit.runner") ---@class GitCommandSetup @@ -975,7 +976,7 @@ local function worktree_root(dir) local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel", "--path-format=absolute" } local result = vim.system(cmd, { text = true }):wait() - return vim.trim(result.stdout) + return Path:new(vim.trim(result.stdout)):absolute() end ---@param dir string @@ -984,7 +985,7 @@ local function git_dir(dir) local cmd = { "git", "-C", dir, "rev-parse", "--git-common-dir", "--path-format=absolute" } local result = vim.system(cmd, { text = true }):wait() - return vim.trim(result.stdout) + return Path:new(vim.trim(result.stdout)):absolute() end ---@param dir string From ed221ed692e016e5024f02946ddb8902669e281a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 13:18:25 +0100 Subject: [PATCH 141/437] Use git worktree directory for various HEAD files and watcher --- lua/neogit/lib/git/bisect.lua | 6 ++-- lua/neogit/lib/git/cli.lua | 11 ++++++++ lua/neogit/lib/git/merge.lua | 4 +-- lua/neogit/lib/git/rebase.lua | 4 +-- lua/neogit/lib/git/refs.lua | 2 +- lua/neogit/lib/git/repository.lua | 46 +++++++++++++++++++------------ lua/neogit/lib/git/sequencer.lua | 10 +++---- lua/neogit/watcher.lua | 2 +- 8 files changed, 54 insertions(+), 31 deletions(-) diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 3542079d9..c552241ef 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -17,7 +17,7 @@ local function bisect(cmd) end function M.in_progress() - return git.repo:git_path("BISECT_LOG"):exists() + return git.repo:worktree_git_path("BISECT_LOG"):exists() end function M.is_finished() @@ -74,7 +74,7 @@ M.register = function(meta) local finished - for line in git.repo:git_path("BISECT_LOG"):iter() do + for line in git.repo:worktree_git_path("BISECT_LOG"):iter() do if line:match("^#") and line ~= "" then local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") @@ -96,7 +96,7 @@ M.register = function(meta) end end - local expected = vim.trim(git.repo:git_path("BISECT_EXPECTED_REV"):read()) + local expected = vim.trim(git.repo:worktree_git_path("BISECT_EXPECTED_REV"):read()) state.bisect.current = git.log.parse(git.cli.show.format("fuller").args(expected).call({ trim = false }).stdout)[1] diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index f9427c432..df89df123 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -370,6 +370,7 @@ local runner = require("neogit.runner") ---@field write-tree GitCommandWriteTree ---@field worktree_root fun(dir: string):string ---@field git_dir fun(dir: string):string +---@field worktree_git_dir fun(dir: string):string ---@field is_inside_worktree fun(dir: string):boolean ---@field history ProcessResult[] @@ -988,6 +989,15 @@ local function git_dir(dir) return Path:new(vim.trim(result.stdout)):absolute() end +---@param dir string +---@return string Absolute path of `.git/` directory +local function worktree_git_dir(dir) + local cmd = { "git", "-C", dir, "rev-parse", "--git-dir", "--path-format=absolute" } + local result = vim.system(cmd, { text = true }):wait() + + return Path:new(vim.trim(result.stdout)):absolute() +end + ---@param dir string ---@return boolean local function is_inside_worktree(dir) @@ -1242,6 +1252,7 @@ local meta = { local cli = setmetatable({ history = runner.history, worktree_root = worktree_root, + worktree_git_dir = worktree_git_dir, git_dir = git_dir, is_inside_worktree = is_inside_worktree, }, meta) diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 77d431484..3bcaa2aa1 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -44,7 +44,7 @@ M.register = function(meta) meta.update_merge_status = function(state) state.merge = { head = nil, branch = nil, msg = "", items = {} } - local merge_head = git.repo:git_path("MERGE_HEAD") + local merge_head = git.repo:worktree_git_path("MERGE_HEAD") if not merge_head:exists() then return end @@ -52,7 +52,7 @@ M.register = function(meta) state.merge.head = merge_head:read():match("([^\r\n]+)") state.merge.subject = git.log.message(state.merge.head) - local message = git.repo:git_path("MERGE_MSG") + local message = git.repo:worktree_git_path("MERGE_MSG") if message:exists() then state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows state.merge.branch = state.merge.msg:match("Merge branch '(.*)'$") diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 330e12a9c..942bd5680 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -192,8 +192,8 @@ function M.update_rebase_status(state) state.rebase = { items = {}, onto = {}, head_oid = nil, head = nil, current = nil } local rebase_file - local rebase_merge = git.repo:git_path("rebase-merge") - local rebase_apply = git.repo:git_path("rebase-apply") + local rebase_merge = git.repo:worktree_git_path("rebase-merge") + local rebase_apply = git.repo:worktree_git_path("rebase-apply") if rebase_merge:exists() then rebase_file = rebase_merge diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 0c25a2289..1125ab46e 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -132,7 +132,7 @@ M.heads = util.memoize(function() local heads = { "HEAD", "ORIG_HEAD", "FETCH_HEAD", "MERGE_HEAD", "CHERRY_PICK_HEAD" } local present = {} for _, head in ipairs(heads) do - if git.repo:git_path(head):exists() then + if git.repo:worktree_git_path(head):exists() then table.insert(present, head) end end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index a2cf95095..a8e55fcc4 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -21,23 +21,25 @@ local modules = { } ---@class NeogitRepoState ----@field git_path fun(self, ...): Path ----@field refresh fun(self, table) ----@field worktree_root string ----@field git_dir string ----@field head NeogitRepoHead ----@field upstream NeogitRepoRemote ----@field pushRemote NeogitRepoRemote ----@field untracked NeogitRepoIndex ----@field unstaged NeogitRepoIndex ----@field staged NeogitRepoIndex ----@field stashes NeogitRepoStash ----@field recent NeogitRepoRecent ----@field sequencer NeogitRepoSequencer ----@field rebase NeogitRepoRebase ----@field merge NeogitRepoMerge ----@field bisect NeogitRepoBisect ----@field hooks string[] +---@field git_path fun(self, ...): Path +---@field worktree_git_path fun(self, ...): Path +---@field refresh fun(self, table) +---@field worktree_root string Absolute path to the root of the current worktree +---@field worktree_git_dir string Absolute path to the .git/ dir of the current worktree +---@field git_dir string Absolute path of the .git/ dir for the repository +---@field head NeogitRepoHead +---@field upstream NeogitRepoRemote +---@field pushRemote NeogitRepoRemote +---@field untracked NeogitRepoIndex +---@field unstaged NeogitRepoIndex +---@field staged NeogitRepoIndex +---@field stashes NeogitRepoStash +---@field recent NeogitRepoRecent +---@field sequencer NeogitRepoSequencer +---@field rebase NeogitRepoRebase +---@field merge NeogitRepoMerge +---@field bisect NeogitRepoBisect +---@field hooks string[] --- ---@class NeogitRepoHead ---@field branch string|nil @@ -100,6 +102,7 @@ local modules = { local function empty_state() return { worktree_root = "", + worktree_git_dir = "", git_dir = "", head = { branch = nil, @@ -170,6 +173,7 @@ end ---@field lib table ---@field state NeogitRepoState ---@field worktree_root string Project root, or worktree +---@field worktree_git_dir string Dir to watch for changes in worktree ---@field git_dir string '.git/' directory for repo ---@field running table ---@field interrupt table @@ -209,6 +213,7 @@ function Repo.new(dir) lib = {}, state = empty_state(), worktree_root = git.cli.worktree_root(dir), + worktree_git_dir = git.cli.worktree_git_dir(dir), git_dir = git.cli.git_dir(dir), refresh_callbacks = {}, running = util.weak_table(), @@ -217,6 +222,7 @@ function Repo.new(dir) } instance.state.worktree_root = instance.worktree_root + instance.state.worktree_git_dir = instance.worktree_git_dir instance.state.git_dir = instance.git_dir setmetatable(instance, Repo) @@ -232,6 +238,12 @@ function Repo:reset() self.state = empty_state() end +---@return Path +function Repo:worktree_git_path(...) + return Path:new(self.worktree_git_dir):joinpath(...) +end + +---@return Path function Repo:git_path(...) return Path:new(self.git_dir):joinpath(...) end diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index e951b0a3b..5c10b6bb2 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -31,16 +31,16 @@ end function M.update_sequencer_status(state) state.sequencer = { items = {}, head = nil, head_oid = nil, revert = false, cherry_pick = false } - local revert_head = git.repo:git_path("REVERT_HEAD") - local cherry_head = git.repo:git_path("CHERRY_PICK_HEAD") + local revert_head = git.repo:worktree_git_path("REVERT_HEAD") + local cherry_head = git.repo:worktree_git_path("CHERRY_PICK_HEAD") if cherry_head:exists() then state.sequencer.head = "CHERRY_PICK_HEAD" - state.sequencer.head_oid = vim.trim(git.repo:git_path("CHERRY_PICK_HEAD"):read()) + state.sequencer.head_oid = vim.trim(git.repo:worktree_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 = vim.trim(git.repo:git_path("REVERT_HEAD"):read()) + state.sequencer.head_oid = vim.trim(git.repo:worktree_git_path("REVERT_HEAD"):read()) state.sequencer.revert = true end @@ -52,7 +52,7 @@ function M.update_sequencer_status(state) subject = git.log.message(HEAD_oid), }) - local todo = git.repo:git_path("sequencer/todo") + local todo = git.repo:worktree_git_path("sequencer/todo") if todo:exists() then for line in todo:iter() do if line:match("^[^#]") and line ~= "" then diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index a158351ab..ec233a56b 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -19,7 +19,7 @@ Watcher.__index = Watcher function Watcher.new(root) local instance = { buffers = {}, - git_dir = git.cli.git_dir(root), + git_dir = git.cli.worktree_git_dir(root), running = false, fs_event_handler = assert(vim.uv.new_fs_event()), } From dab4eacef8ee520e5ba998862d8c2d36b2b2f00a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 13:31:19 +0100 Subject: [PATCH 142/437] When in a bare repo, there is no OID, so don't assume there is. --- lua/neogit/lib/git/branch.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index da94146be..e1114d908 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -375,7 +375,7 @@ local function update_branch_information(state) state.head.oid = status.oid state.head.detached = status.detached - if status.oid ~= INITIAL_COMMIT then + if status.oid and status.oid ~= INITIAL_COMMIT then state.head.abbrev = git.rev_parse.abbreviate_commit(status.oid) state.head.commit_message = git.log.message(status.oid) From 7f0341c844293bef2a5005307c20043305ce4f0a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 20:46:56 +0100 Subject: [PATCH 143/437] fix types --- lua/neogit/buffers/status/actions.lua | 2 +- lua/neogit/vendor/types.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 4bb3eb65e..c378e718e 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1376,7 +1376,7 @@ M.n_command = function(self) local proc = process.new { cmd = cmd, - cwd = git.repo.git_root, + cwd = git.repo.worktree_root, env = {}, on_error = function() return false diff --git a/lua/neogit/vendor/types.lua b/lua/neogit/vendor/types.lua index 54fef393a..68b8fb3fa 100644 --- a/lua/neogit/vendor/types.lua +++ b/lua/neogit/vendor/types.lua @@ -6,6 +6,7 @@ ---@field touch fun(self, opts:table) ---@field write fun(self, txt:string, flag:string) ---@field read fun(self): string|nil +---@field iter fun(self): self ---@class uv_timer_t ---@field start fun(self, time:number, repeat: number, fn: function) From 45c0cbbe1dfb05a10ad29ebd7911eeda10fa5cf8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 20:47:10 +0100 Subject: [PATCH 144/437] Allow removing a worktree in a bare repo --- lua/neogit/popups/worktree/actions.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index a5f13132f..ba4979d7a 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -129,8 +129,9 @@ function M.delete() local success = false if input.get_permission(("Remove worktree at %q?"):format(selected)) then - if change_dir and status.is_open() then - status.instance():chdir(git.worktree.main().path) + local main = git.worktree.main() -- A bare repo has no main, so check + if change_dir and status.is_open() and main then + status.instance():chdir(main.path) end -- This might produce some error messages that need to get suppressed From 8bfbb0133d88d706ad8844e7e66b3a0fae466709 Mon Sep 17 00:00:00 2001 From: Steven Xu Date: Mon, 2 Dec 2024 21:40:01 +1100 Subject: [PATCH 145/437] feat: show log dates using a new `log_date_format` config option --- README.md | 2 ++ doc/neogit.txt | 1 + lua/neogit/buffers/common.lua | 7 +++++-- lua/neogit/config.lua | 2 ++ lua/neogit/lib/git/log.lua | 14 ++++++++++++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 83cc2c0bb..a2ef62385 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,8 @@ neogit.setup { -- "unicode" is the graph like https://github.com/rbong/vim-flog -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim - use https://github.com/rbong/flog-symbols if you don't use Kitty graph_style = "ascii", + -- Show relative date by default. When set, use `strftime` to display dates + log_date_format = nil, -- Used to generate URL's for branch popup action "pull request". git_services = { ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", diff --git a/doc/neogit.txt b/doc/neogit.txt index 9fe8baeb7..b3b9fa522 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -89,6 +89,7 @@ TODO: Detail what these do disable_context_highlighting = false, disable_signs = false, graph_style = "ascii", + log_date_format = nil, filewatcher = { enabled = true, }, diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index cb13937e6..701ff3c35 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,6 +1,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local config = require("neogit.config") local git = require("neogit.lib.git") local text = Ui.text @@ -247,6 +248,8 @@ M.CommitEntry = Component.new(function(commit, remotes, args) } end + local date = (config.values.log_date_format == nil and commit.rel_date or commit.log_date) + return col.tag("commit")({ row( util.merge({ @@ -260,10 +263,10 @@ M.CommitEntry = Component.new(function(commit, remotes, args) virtual_text = { { " ", "Constant" }, { - util.str_clamp(commit.author_name, 30 - (#commit.rel_date > 10 and #commit.rel_date or 10)), + util.str_clamp(commit.author_name, 30 - (#date > 10 and #date or 10)), "NeogitGraphAuthor", }, - { util.str_min_width(commit.rel_date, 10), "Special" }, + { util.str_min_width(date, 10), "Special" }, }, } ), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 6e3b53cdd..d2d04d043 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -300,6 +300,7 @@ end ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher ---@field graph_style? NeogitGraphStyle Style for graph +---@field log_date_format? string Log date format ---@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 @@ -351,6 +352,7 @@ function M.get_default_values() disable_context_highlighting = false, disable_signs = false, graph_style = "ascii", + log_date_format = nil, process_spinner = true, filewatcher = { enabled = true, diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 5a6416516..c5e5952fb 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -29,6 +29,7 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)" ---@field body string ---@field verification_flag string? ---@field rel_date string +---@field log_date string ---Parses the provided list of lines into a CommitLogEntry ---@param raw string[] @@ -287,6 +288,17 @@ local function determine_order(options, graph) return options end +--- Specifies date format when not using relative dates +--- @param options table +--- @return table, string|nil +local function set_date_format(options) + if config.values.log_date_format ~= nil then + table.insert(options, "--date=format:" .. config.values.log_date_format) + end + + return options +end + ---@param options table|nil ---@param files? table ---@param color? boolean @@ -327,6 +339,7 @@ local function format(show_signature) committer_email = "%cE", committer_date = "%cD", rel_date = "%cr", + log_date = "%cd", } if show_signature then @@ -352,6 +365,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) options = ensure_max(options or {}) options = determine_order(options, graph) options, signature = show_signature(options) + options = set_date_format(options) local output = git.cli.log .format(format(signature)) From 58e829ccc8b7903e8b8eab77fc536f9c6d2d9cbb Mon Sep 17 00:00:00 2001 From: Steven Xu Date: Mon, 2 Dec 2024 22:07:13 +1100 Subject: [PATCH 146/437] feat: show reflog dates using the `log_date_format` config option --- lua/neogit/buffers/reflog_view/ui.lua | 11 +++++++++-- lua/neogit/lib/git/reflog.lua | 16 +++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/reflog_view/ui.lua b/lua/neogit/buffers/reflog_view/ui.lua index 8e4c587de..6a054ad2a 100644 --- a/lua/neogit/buffers/reflog_view/ui.lua +++ b/lua/neogit/buffers/reflog_view/ui.lua @@ -1,6 +1,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local config = require("neogit.config") local col = Ui.col local row = Ui.row @@ -25,7 +26,13 @@ local function highlight_for_type(type) end M.Entry = Component.new(function(entry, total) - local date_number, date_quantifier = unpack(vim.split(entry.rel_date, " ")) + local date + if config.values.log_date_format == nil then + local date_number, date_quantifier = unpack(vim.split(entry.rel_date, " ")) + date = date_number .. date_quantifier:sub(1, 1) + else + date = entry.commit_date + end return col({ row({ @@ -38,7 +45,7 @@ M.Entry = Component.new(function(entry, total) virtual_text = { { " ", "Constant" }, -- { util.str_clamp(entry.author_name, 20 - #tostring(date_number)), "Constant" }, - { date_number .. date_quantifier:sub(1, 1), "Special" }, + { date, "Special" }, }, }), }, { oid = entry.oid }) diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index d859cc199..82e82c305 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -1,5 +1,6 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +local config = require("neogit.config") ---@class NeogitGitReflog local M = {} @@ -15,7 +16,7 @@ local function parse(entries) return util.filter_map(entries, function(entry) index = index + 1 - local hash, author, name, subject, date = unpack(vim.split(entry, "\30")) + local hash, author, name, subject, rel_date, commit_date = unpack(vim.split(entry, "\30")) local command, message = subject:match([[^(.-): (.*)]]) if not command then command = subject:match([[^(.-):]]) @@ -42,7 +43,8 @@ local function parse(entries) author_name = author, ref_name = name, ref_subject = message, - rel_date = date, + rel_date = rel_date, + commit_date = commit_date, type = command, } end) @@ -55,15 +57,23 @@ function M.list(refname, options) "%gd", -- Reflog Name "%gs", -- Reflog Subject "%cr", -- Commit Date (Relative) + "%cd", -- Commit Date }, "%x1E") util.remove_item_from_table(options, "--simplify-by-decoration") util.remove_item_from_table(options, "--follow") + local date_format + if config.values.log_date_format ~= nil then + date_format = "format:" .. config.values.log_date_format + else + date_format = "raw" + end + return parse( git.cli.reflog.show .format(format) - .date("raw") + .date(date_format) .arg_list(options or {}) .args(refname, "--") .call({ hidden = true }).stdout From 76758c1d44387a885cb5324f49ac3b8a1a551825 Mon Sep 17 00:00:00 2001 From: Steven Xu Date: Mon, 2 Dec 2024 22:24:07 +1100 Subject: [PATCH 147/437] feat: show stash dates using the `log_date_format` config option --- lua/neogit/buffers/stash_list_view/ui.lua | 3 ++- lua/neogit/lib/git/stash.lua | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/stash_list_view/ui.lua b/lua/neogit/buffers/stash_list_view/ui.lua index eb18ae1cd..5ba839a24 100644 --- a/lua/neogit/buffers/stash_list_view/ui.lua +++ b/lua/neogit/buffers/stash_list_view/ui.lua @@ -1,6 +1,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local config = require("neogit.config") local text = Ui.text local col = Ui.col @@ -19,7 +20,7 @@ M.Stash = Component.new(function(stash) }, { virtual_text = { { " ", "Constant" }, - { stash.rel_date, "Special" }, + { config.values.log_date_format ~= nil and stash.date or stash.rel_date, "Special" }, }, }), }, { oid = label }) diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index e07972729..ea54b1b2f 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local util = require("neogit.lib.util") +local config = require("neogit.config") ---@class NeogitGitStash local M = {} @@ -89,6 +90,7 @@ end ---@class StashItem ---@field idx number string the id of the stash i.e. stash@{7} ---@field name string +---@field date string timestamp ---@field rel_date string relative timestamp ---@field message string the message associated with each stash. @@ -118,6 +120,15 @@ function M.register(meta) .call({ hidden = true }).stdout[1] return self.rel_date + elseif key == "date" then + self.date = git.cli.log + .max_count(1) + .format("%cd") + .args("--date=format:" .. config.values.log_date_format) + .args(("stash@{%s}"):format(idx)) + .call({ hidden = true }).stdout[1] + + return self.date end end, }) From bc2564749e0aaa0791d08998ff127fea09189a78 Mon Sep 17 00:00:00 2001 From: Steven Xu Date: Mon, 2 Dec 2024 22:40:07 +1100 Subject: [PATCH 148/437] feat: show commit dates using a new `commit_date_format` config option --- README.md | 1 + doc/neogit.txt | 1 + lua/neogit/buffers/commit_view/init.lua | 7 +++++-- lua/neogit/config.lua | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2ef62385..929cce584 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ neogit.setup { -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim - use https://github.com/rbong/flog-symbols if you don't use Kitty graph_style = "ascii", -- Show relative date by default. When set, use `strftime` to display dates + commit_date_format = nil, log_date_format = nil, -- Used to generate URL's for branch popup action "pull request". git_services = { diff --git a/doc/neogit.txt b/doc/neogit.txt index b3b9fa522..fc44a6e05 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -89,6 +89,7 @@ TODO: Detail what these do disable_context_highlighting = false, disable_signs = false, graph_style = "ascii", + commit_date_format = nil, log_date_format = nil, filewatcher = { enabled = true, diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 0ec999abb..6678cd855 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -47,8 +47,11 @@ local M = { ---@param filter? string[] Filter diffs to filepaths in table ---@return CommitViewBuffer function M.new(commit_id, filter) - local commit_info = - git.log.parse(git.cli.show.format("fuller").args(commit_id).call({ trim = false }).stdout)[1] + local cmd = git.cli.show.format("fuller").args(commit_id) + if config.values.commit_date_format ~= nil then + cmd = cmd.args("--date=format:" .. config.values.commit_date_format) + end + local commit_info = git.log.parse(cmd.call({ trim = false }).stdout)[1] commit_info.commit_arg = commit_id diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index d2d04d043..405e61757 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -300,6 +300,7 @@ end ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher ---@field graph_style? NeogitGraphStyle Style for graph +---@field commit_date_format? string Commit date format ---@field log_date_format? string Log date format ---@field disable_hint? boolean Remove the top hint in the Status buffer ---@field disable_context_highlighting? boolean Disable context highlights based on cursor position @@ -352,6 +353,7 @@ function M.get_default_values() disable_context_highlighting = false, disable_signs = false, graph_style = "ascii", + commit_date_format = nil, log_date_format = nil, process_spinner = true, filewatcher = { From ca822e79fa4c096e0d48105aaeb37db0b8916f31 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Wed, 4 Dec 2024 15:02:28 +0000 Subject: [PATCH 149/437] fix: git-absorb getting stuck This fixes neogit hanging forever when using git absorb as reported in the issue #1564 --- lua/neogit/popups/commit/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 9772617d7..47fd5f26a 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -182,7 +182,7 @@ function M.absorb(popup) return end - git.cli.absorb.verbose.base(commit).and_rebase.call() + git.cli.absorb.verbose.base(commit).env({ GIT_SEQUENCE_EDITOR = ":" }).and_rebase.call() end return M From 9e3ae89a7101468f7d69434685232d49440b3916 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 21:05:21 +0100 Subject: [PATCH 150/437] Notify user if nothing is staged when trying to commit. Fixes: https://github.com/NeogitOrg/neogit/issues/580 --- lua/neogit/popups/commit/actions.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 47fd5f26a..2c351260a 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -97,10 +97,20 @@ local function commit_special(popup, method, opts) end function M.commit(popup) + if not git.status.anything_staged() then + notification.warn("No changes to commit.") + return + end + do_commit(popup, git.cli.commit) end function M.extend(popup) + if not git.status.anything_staged() then + notification.warn("No changes to commit.") + return + end + if not confirm_modifications() then return end @@ -109,6 +119,11 @@ function M.extend(popup) end function M.reword(popup) + if not git.status.anything_staged() then + notification.warn("No changes to commit.") + return + end + if not confirm_modifications() then return end @@ -117,6 +132,11 @@ function M.reword(popup) end function M.amend(popup) + if not git.status.anything_staged() then + notification.warn("No changes to commit.") + return + end + if not confirm_modifications() then return end From 6fda2949a5048f3f95cfbf543c2a511426f796f6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 30 Nov 2024 21:20:58 +0100 Subject: [PATCH 151/437] Support file mode "T". Fixes: https://github.com/NeogitOrg/neogit/issues/1408 --- lua/neogit/config.lua | 1 + lua/neogit/lib/hl.lua | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 405e61757..e9a5c5dcd 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -401,6 +401,7 @@ function M.get_default_values() C = "copied", U = "updated", R = "renamed", + T = "changed", DD = "unmerged", AU = "unmerged", UD = "unmerged", diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 55ee03351..9d946934a 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -252,6 +252,7 @@ function M.setup(config) NeogitChangeCunstaged = { link = "NeogitChangeCopied" }, NeogitChangeUunstaged = { link = "NeogitChangeUpdated" }, NeogitChangeRunstaged = { link = "NeogitChangeRenamed" }, + NeogitChangeTunstaged = { link = "NeogitChangeUpdated" }, NeogitChangeDDunstaged = { link = "NeogitChangeUnmerged" }, NeogitChangeUUunstaged = { link = "NeogitChangeUnmerged" }, NeogitChangeAAunstaged = { link = "NeogitChangeUnmerged" }, @@ -267,6 +268,7 @@ function M.setup(config) NeogitChangeCstaged = { link = "NeogitChangeCopied" }, NeogitChangeUstaged = { link = "NeogitChangeUpdated" }, NeogitChangeRstaged = { link = "NeogitChangeRenamed" }, + NeogitChangeTstaged = { link = "NeogitChangeUpdated" }, NeogitChangeDDstaged = { link = "NeogitChangeUnmerged" }, NeogitChangeUUstaged = { link = "NeogitChangeUnmerged" }, NeogitChangeAAstaged = { link = "NeogitChangeUnmerged" }, From 3b2ea180468f0ba8c3643a5075fa278996a72e2b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 11:45:11 +0100 Subject: [PATCH 152/437] When initializing a new repo, HEAD_oid is nil --- lua/neogit/lib/git/sequencer.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 5c10b6bb2..a961a7e0e 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -45,12 +45,14 @@ function M.update_sequencer_status(state) end local HEAD_oid = git.rev_parse.oid("HEAD") - table.insert(state.sequencer.items, { - action = "onto", - oid = HEAD_oid, - abbreviated_commit = HEAD_oid:sub(1, git.log.abbreviated_size()), - subject = git.log.message(HEAD_oid), - }) + if HEAD_oid then + table.insert(state.sequencer.items, { + action = "onto", + oid = HEAD_oid, + abbreviated_commit = HEAD_oid:sub(1, git.log.abbreviated_size()), + subject = git.log.message(HEAD_oid), + }) + end local todo = git.repo:worktree_git_path("sequencer/todo") if todo:exists() then From fbb5d80ceb25a3d8dacee4059f0a9888bc62aec5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:18:15 +0100 Subject: [PATCH 153/437] Add type info to git.prune.* functions --- lua/neogit/lib/git/remote.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index 4188fbd99..be2a25db5 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -5,6 +5,8 @@ local util = require("neogit.lib.util") local M = {} -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#LL141C32-L141C32 +---@param remote string +---@param new_name string|nil local function cleanup_push_variables(remote, new_name) if remote == git.config.get("remote.pushDefault"):read() then git.config.set("remote.pushDefault", new_name) @@ -21,10 +23,17 @@ local function cleanup_push_variables(remote, new_name) end end +---@param name string +---@param url string +---@param args string[] +---@return boolean function M.add(name, url, args) return git.cli.remote.add.arg_list(args).args(name, url).call({ await = true }).code == 0 end +---@param from string +---@param to string +---@return boolean function M.rename(from, to) local result = git.cli.remote.rename.arg_list({ from, to }).call { await = true } if result.code == 0 then @@ -34,6 +43,8 @@ function M.rename(from, to) return result.code == 0 end +---@param name string +---@return boolean function M.remove(name) local result = git.cli.remote.rm.args(name).call { await = true } if result.code == 0 then @@ -43,14 +54,19 @@ function M.remove(name) return result.code == 0 end +---@param name string +---@return boolean function M.prune(name) return git.cli.remote.prune.args(name).call().code == 0 end +---@return string[] M.list = util.memoize(function() return git.cli.remote.call({ hidden = true }).stdout end) +---@param name string +---@return string[] function M.get_url(https://github.com/name) return git.cli.remote.get_url(https://github.com/name).call({ hidden = true }).stdout end @@ -105,7 +121,7 @@ function M.parse(url) repository = url:match([[/([^/]+)%.git]]) or url:match([[/([^/]+)$]]) end - return { + return { ---@type RemoteInfo url = url, protocol = protocol, user = user, From 819b49abb9b5a9bfd6f818469f18e52acf11eb8e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:18:44 +0100 Subject: [PATCH 154/437] Run git.remote.* functions async --- lua/neogit/lib/git/remote.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index be2a25db5..0627b4d73 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -28,14 +28,14 @@ end ---@param args string[] ---@return boolean function M.add(name, url, args) - return git.cli.remote.add.arg_list(args).args(name, url).call({ await = true }).code == 0 + return git.cli.remote.add.arg_list(args).args(name, url).call().code == 0 end ---@param from string ---@param to string ---@return boolean function M.rename(from, to) - local result = git.cli.remote.rename.arg_list({ from, to }).call { await = true } + local result = git.cli.remote.rename.arg_list({ from, to }).call() if result.code == 0 then cleanup_push_variables(from, to) end @@ -46,7 +46,7 @@ end ---@param name string ---@return boolean function M.remove(name) - local result = git.cli.remote.rm.args(name).call { await = true } + local result = git.cli.remote.rm.args(name).call() if result.code == 0 then cleanup_push_variables(name) end From b303bdbed884eea186de8c79c915780bd55489f3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:19:03 +0100 Subject: [PATCH 155/437] Fix: when no origin is set, allow adding a remote --- lua/neogit/popups/remote/actions.lua | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 0f9a7c29b..272ddc4fe 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -26,18 +26,16 @@ function M.add(popup) return end + local msg local origin = git.config.get("remote.origin.url"):read() - if not origin then - return - end + if origin then + assert(type(origin) == "string", "remote.origin.url isn't a string") + local host, _, remote = origin:match("([^:/]+)[:/]([^/]+)/(.+)") + remote = remote and remote:gsub("%.git$", "") - assert(type(origin) == "string", "remote.origin.url isn't a string") - local host, _, remote = origin:match("([^:/]+)[:/]([^/]+)/(.+)") - remote = remote and remote:gsub("%.git$", "") - - local msg - if host and remote then - msg = string.format("%s:%s/%s.git", host, name, remote) + if host and remote then + msg = string.format("%s:%s/%s.git", host, name, remote) + end end local remote_url = input.get_user_input("URL for " .. name, { default = msg }) From 4aa91cc3fe85299f64140977873ba75aac279875 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:19:27 +0100 Subject: [PATCH 156/437] If no remote exists for repo, notify user and return early --- lua/neogit/popups/remote/actions.lua | 35 +++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 272ddc4fe..38952f6aa 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -58,8 +58,13 @@ function M.add(popup) end function M.rename(_) - local selected_remote = FuzzyFinderBuffer.new(git.remote.list()) - :open_async { prompt_prefix = "Rename remote" } + local options = git.remote.list() + if #options == 0 then + notification.info("No remotes found") + return + end + + local selected_remote = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "Rename remote" } if not selected_remote then return end @@ -76,7 +81,13 @@ function M.rename(_) end function M.remove(_) - local selected_remote = FuzzyFinderBuffer.new(git.remote.list()):open_async() + local options = git.remote.list() + if #options == 0 then + notification.info("No remotes found") + return + end + + local selected_remote = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "Remove remote" } if not selected_remote then return end @@ -88,7 +99,13 @@ function M.remove(_) end function M.configure(_) - local remote_name = FuzzyFinderBuffer.new(git.remote.list()):open_async() + local options = git.remote.list() + if #options == 0 then + notification.info("No remotes found") + return + end + + local remote_name = FuzzyFinderBuffer.new(options):open_async() if not remote_name then return end @@ -97,13 +114,19 @@ function M.configure(_) end function M.prune_branches(_) - local selected_remote = FuzzyFinderBuffer.new(git.remote.list()):open_async() + local options = git.remote.list() + if #options == 0 then + notification.info("No remotes found") + return + end + + local selected_remote = FuzzyFinderBuffer.new(options):open_async() if not selected_remote then return end - notification.info("Pruning remote " .. selected_remote) git.remote.prune(selected_remote) + notification.info("Pruned remote " .. selected_remote) end -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#L159 From 046c062c754fd3ad6870e990d283c7564f233a86 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:19:51 +0100 Subject: [PATCH 157/437] Improve remote popup spec --- spec/popups/remote_popup_spec.rb | 122 ++++++++++++++++++++++++++++++- spec/support/helpers.rb | 32 ++++---- 2 files changed, 136 insertions(+), 18 deletions(-) diff --git a/spec/popups/remote_popup_spec.rb b/spec/popups/remote_popup_spec.rb index 17f61e8b3..f3bbdd35d 100644 --- a/spec/popups/remote_popup_spec.rb +++ b/spec/popups/remote_popup_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe "Remote Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup +RSpec.describe "Remote Popup", :git, :nvim, :popup do before { nvim.keys("M") } let(:view) do @@ -28,4 +28,124 @@ %w[u U s S O a d x C p P b z].each { include_examples "interaction", _1 } %w[-f].each { include_examples "argument", _1 } + + describe "add" do + context "with 'origin 'unset" do + it "allow user to add remote" do + nvim.keys("a") + nvim.keys("origin") + nvim.keys("git@github.com:NeogitOrg/neogit.git") + expect(git.remote.name).to eq("origin") + expect(git.remote.url).to eq("git@github.com:NeogitOrg/neogit.git") + end + end + + context "with 'origin' set" do + before do + git.config("remote.origin.url", "git@github.com:NeogitOrg/neogit.git") + end + + it "auto-populates host/remote" do + nvim.keys("a") + nvim.keys("fork") + expect(nvim.screen.last).to start_with("URL for fork: git@github.com:fork/neogit.git") + end + end + end + + describe "remove" do + context "with no remotes configured" do + it "notifies user" do + nvim.keys("x") + expect(nvim.screen.last).to start_with("No remotes found") + end + end + + context "with a remote configured" do + before do + git.config("remote.origin.url", "git@github.com:NeogitOrg/neogit.git") + end + + it "can remove a remote" do + nvim.keys("x") + nvim.keys("origin") + expect(nvim.screen.last).to start_with("Removed remote 'origin'") + expect(git.remotes).to be_empty + end + end + end + + describe "rename" do + context "with no remotes configured" do + it "notifies user" do + nvim.keys("r") + expect(nvim.screen.last).to start_with("No remotes found") + end + end + + context "with a remote configured" do + before do + git.config("remote.origin.url", "git@github.com:NeogitOrg/neogit.git") + end + + it "can rename a remote" do + nvim.keys("r") + nvim.keys("origin") + nvim.keys("fork") + expect(nvim.screen.last).to start_with("Renamed 'origin' -> 'fork'") + expect(git.remotes.first.name).to eq("fork") + end + end + end + + describe "configure" do + context "with no remotes configured" do + it "notifies user" do + nvim.keys("C") + expect(nvim.screen.last).to start_with("No remotes found") + end + end + + context "with a remote configured" do + before do + git.config("remote.origin.url", "git@github.com:NeogitOrg/neogit.git") + end + + it "can launch remote config popup" do + nvim.keys("C") + nvim.keys("origin") + expect(nvim.screen[14..19]).to eq( + [" Configure remote ", + " u remote.origin.url git@github.com:NeogitOrg/neogit.git ", + " U remote.origin.fetch unset ", + " s remote.origin.pushurl unset ", + " S remote.origin.push unset ", + " O remote.origin.tagOpt [--no-tags|--tags] "] + ) + end + end + end + + describe "prune_branches" do + context "with no remotes configured" do + it "notifies user" do + nvim.keys("p") + expect(nvim.screen.last).to start_with("No remotes found") + end + end + + context "with a remote configured" do + before do + git.config("remote.origin.url", "git@github.com:NeogitOrg/neogit.git") + end + + it "can launch remote config popup" do + nvim.keys("p") + nvim.keys("origin") + await do + expect(nvim.screen.last).to start_with("Pruned remote origin") + end + end + end + end end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 06dea7fc5..9d5ad584c 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -9,21 +9,19 @@ def expect_git_failure(&) expect(&).to raise_error(Git::FailedError) end - # def wait_for_expect - # last_error = nil - # success = false - # - # 5.times do - # begin - # yield - # success = true - # break - # rescue RSpec::Expectations::ExpectationNotMetError => e - # last_error = e - # sleep 0.5 - # end - # end - # - # raise last_error if !success && last_error - # end + def await # rubocop:disable Metrics/MethodLength + last_error = nil + success = false + + 10.times do + yield + success = true + break + rescue RSpec::Expectations::ExpectationNotMetError => e + last_error = e + sleep 0.1 + end + + raise last_error if !success && last_error + end end From b863dc94bbed776a4ba2b26af90d6609b7dff881 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:31:28 +0100 Subject: [PATCH 158/437] Add timeout to rspec tests --- spec/spec_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3b5bffec2..f582c80a1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ require "neovim" require "debug" require "active_support/all" +require "timeout" ENV["GIT_CONFIG_GLOBAL"] = "" @@ -49,4 +50,8 @@ end end end + + config.around do |example| + Timeout.timeout(10) { example.call } + end end From ba51bf78ef13673ad537cac136b41d9910e971b1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:51:01 +0100 Subject: [PATCH 159/437] Add git.branch.* types --- lua/neogit/buffers/status/actions.lua | 7 +++- lua/neogit/lib/git/branch.lua | 50 +++++++++++++++++++++------ lua/neogit/popups/branch/actions.lua | 13 +++++-- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index c378e718e..b456d834d 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1350,7 +1350,12 @@ M.n_open_tree = function(_self) return a.void(function() local template = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D" - local url = git.remote.get_url(https://github.com/git.branch.upstream_remote())[1] + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local url = git.remote.get_url(https://github.com/upstream)[1] local format_values = git.remote.parse(url) format_values["branch_name"] = git.branch.current() diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index e1114d908..a5e472377 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -7,6 +7,9 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") ---@class NeogitGitBranch local M = {} +---@param branches string[] +---@param include_current? boolean +---@return string[] local function parse_branches(branches, include_current) include_current = include_current or false local other_branches = {} @@ -37,6 +40,7 @@ local function parse_branches(branches, include_current) return other_branches end +---@return string[] function M.get_recent_local_branches() local valid_branches = M.get_local_branches() @@ -55,6 +59,8 @@ end ---@param relation? string ---@param commit? string +---@param ... any +---@return string[] function M.list_related_branches(relation, commit, ...) local result = git.cli.branch.args(relation or "", commit or "", ...).call { hidden = true } @@ -70,33 +76,47 @@ function M.list_related_branches(relation, commit, ...) end ---@param commit string +---@return string[] function M.list_containing_branches(commit, ...) return M.list_related_branches("--contains", commit, ...) end +---@param name string +---@param args? string[] ---@return ProcessResult function M.checkout(name, args) return git.cli.checkout.branch(name).arg_list(args or {}).call { await = true } end +---@param name string +---@param args? string[] function M.track(name, args) git.cli.checkout.track(name).arg_list(args or {}).call { await = true } end +---@param include_current? boolean +---@return string[] function M.get_local_branches(include_current) local branches = git.cli.branch.sort(config.values.sort_branches).call({ hidden = true }).stdout return parse_branches(branches, include_current) end +---@param include_current? boolean +---@return string[] function M.get_remote_branches(include_current) local branches = git.cli.branch.remotes.sort(config.values.sort_branches).call({ hidden = true }).stdout return parse_branches(branches, include_current) end +---@param include_current? boolean +---@return string[] function M.get_all_branches(include_current) return util.merge(M.get_local_branches(include_current), M.get_remote_branches(include_current)) end +---@param branch string +---@param base? string +---@return boolean function M.is_unmerged(branch, base) return git.cli.cherry.arg_list({ base or M.base_branch(), branch }).call({ hidden = true }).stdout[1] ~= nil end @@ -146,6 +166,8 @@ function M.create(name, base_branch) return git.cli.branch.args(name, base_branch).call({ await = true }).code == 0 end +---@param name string +---@return boolean function M.delete(name) local input = require("neogit.lib.input") @@ -178,6 +200,7 @@ function M.current() end end +---@return string|nil function M.current_full_name() local current = M.current() if current then @@ -198,6 +221,8 @@ function M.pushRemote(branch) end end +---@param branch? string +---@return string|nil function M.pushRemote_ref(branch) branch = branch or M.current() local pushRemote = M.pushRemote(branch) @@ -207,14 +232,17 @@ function M.pushRemote_ref(branch) end end +---@return string function M.pushRemote_label() return M.pushRemote_ref() or "pushRemote, setting that" end +---@return string function M.pushRemote_remote_label() return M.pushRemote() or "pushRemote, setting that" end +---@return boolean function M.is_detached() return git.repo.state.head.branch == "(detached)" end @@ -265,27 +293,28 @@ function M.set_upstream(name, destination) git.cli.branch.set_upstream_to(name).args(destination or M.current()) end +---@return string function M.upstream_label() return M.upstream() or "@{upstream}, creating it" end +---@return string function M.upstream_remote_label() return M.upstream_remote() or "@{upstream}, setting it" end +---@return string|nil function M.upstream_remote() - local remote = git.repo.state.upstream.remote - - if not remote then - local remotes = git.remote.list() - if #remotes == 1 then - remote = remotes[1] - elseif vim.tbl_contains(remotes, "origin") then - remote = "origin" - end + if git.repo.state.upstream.remote then + return git.repo.state.upstream.remote end - return remote + local remotes = git.remote.list() + if #remotes == 1 then + return remotes[1] + elseif vim.tbl_contains(remotes, "origin") then + return "origin" + end end ---@return string[] @@ -413,4 +442,5 @@ end M.register = function(meta) meta.update_branch_information = update_branch_information end + return M diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index a4bf94b80..4f4b10557 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -48,6 +48,7 @@ local function get_branch_name_user_input(prompt, default) return input.get_user_input(prompt, { strip_spaces = true, default = default }) end +---@param checkout boolean local function spin_off_branch(checkout) if git.status.is_dirty() and not checkout then notification.info("Staying on HEAD due to uncommitted changes") @@ -72,6 +73,7 @@ local function spin_off_branch(checkout) local upstream = git.branch.upstream() if upstream then if checkout then + assert(current_branch_name, "No current branch") git.log.update_ref(current_branch_name, upstream) else git.cli.reset.hard.args(upstream).call() @@ -268,7 +270,9 @@ function M.reset_branch(popup) -- Reset the current branch to the desired state & update reflog git.cli.reset.hard.args(to).call() - git.log.update_ref(git.branch.current_full_name(), to) + local current = git.branch.current_full_name() + assert(current, "no current branch") + git.log.update_ref(current, to) notification.info(string.format("Reset '%s' to '%s'", current, to)) fire_branch_event("NeogitBranchReset", { branch_name = current, resetting_to = to }) @@ -336,7 +340,12 @@ end function M.open_pull_request() local template local service - local url = git.remote.get_url(https://github.com/git.branch.upstream_remote())[1] + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local url = git.remote.get_url(https://github.com/upstream)[1] for s, v in pairs(config.values.git_services) do if url:match(util.pattern_escape(s)) then From 19020a7a7b2a3677f84ce0f01c9568049bdfdf83 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 21:51:09 +0100 Subject: [PATCH 160/437] Add llscheck to lefthook --- lefthook.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lefthook.yml b/lefthook.yml index 939d00410..bef6a3a9a 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -17,6 +17,9 @@ pre-push: run: stylua --check {files} typos: run: typos {files} + lua-types: + glob: "*.lua" + run: llscheck lua/ lua-test: glob: "tests/specs/**/*_spec.lua" run: nvim --headless -S "./tests/init.lua" || echo {files} From d153da404d16a928cdad28ca129f4bb16a783fef Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Dec 2024 22:42:13 +0100 Subject: [PATCH 161/437] Rewrite git.status.* module to conform to the module style used elsewhere. Rewrite functions that used repo state to instead make their own cli calls --- lua/neogit/lib/git/status.lua | 91 +++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 2877789de..7f4086b7c 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -212,42 +212,61 @@ local function update_status(state, filter) end ---@class NeogitGitStatus -local status = { - stage = function(files) - git.cli.add.files(unpack(files)).call { await = true } - end, - stage_modified = function() - git.cli.add.update.call { await = true } - end, - stage_untracked = function() - local paths = util.map(git.repo.state.untracked.items, function(item) - return item.escaped_path - end) - - git.cli.add.files(unpack(paths)).call { await = true } - end, - stage_all = function() - git.cli.add.all.call { await = true } - end, - unstage = function(files) - git.cli.reset.files(unpack(files)).call { await = true } - end, - unstage_all = function() - git.cli.reset.call { await = true } - end, - is_dirty = function() - return #git.repo.state.staged.items > 0 or #git.repo.state.unstaged.items > 0 - end, - anything_staged = function() - return #git.repo.state.staged.items > 0 - end, - anything_unstaged = function() - return #git.repo.state.unstaged.items > 0 - end, -} - -status.register = function(meta) +local M = {} + +---@param files string[] +function M.stage(files) + git.cli.add.files(unpack(files)).call { await = true } +end + +function M.stage_modified() + git.cli.add.update.call { await = true } +end + +function M.stage_untracked() + local paths = util.map(git.repo.state.untracked.items, function(item) + return item.escaped_path + end) + + git.cli.add.files(unpack(paths)).call { await = true } +end + +function M.stage_all() + git.cli.add.all.call { await = true } +end + +---@param files string[] +function M.unstage(files) + git.cli.reset.files(unpack(files)).call { await = true } +end + +function M.unstage_all() + git.cli.reset.call { await = true } +end + +---@return boolean +function M.is_dirty() + return M.anything_unstaged() or M.anything_staged() +end + +---@return boolean +function M.anything_staged() + local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + return vim.iter(output):any(function(line) + return line:match("^%d [^%.]") + end) +end + +---@return boolean +function M.anything_unstaged() + local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + return vim.iter(output):any(function(line) + return line:match("^%d %..") + end) +end + +M.register = function(meta) meta.update_status = update_status end -return status +return M From ee783626be85bd16050a41462ec62a55758d6392 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 09:37:09 +0100 Subject: [PATCH 162/437] Remove requirement for staged changes from amend and reword --- lua/neogit/popups/commit/actions.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 2c351260a..0bbdb007a 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -119,11 +119,6 @@ function M.extend(popup) end function M.reword(popup) - if not git.status.anything_staged() then - notification.warn("No changes to commit.") - return - end - if not confirm_modifications() then return end @@ -132,11 +127,6 @@ function M.reword(popup) end function M.amend(popup) - if not git.status.anything_staged() then - notification.warn("No changes to commit.") - return - end - if not confirm_modifications() then return end From a1231ce0d2abcb78d935f4546936f69e47195ee2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 09:37:30 +0100 Subject: [PATCH 163/437] Type: changed -> changes --- lua/neogit/popups/commit/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 0bbdb007a..26790ef2c 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -170,7 +170,7 @@ function M.absorb(popup) if not git.status.anything_staged() then if git.status.anything_unstaged() then - if input.get_permission("Nothing is staged. Absorb all unstaged changed?") then + if input.get_permission("Nothing is staged. Absorb all unstaged changes?") then git.status.stage_modified() else return From 31f531fece795c85e38c27c23f8b2979d9ae0044 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 09:37:41 +0100 Subject: [PATCH 164/437] Commit->extend, with nothing staged, will offer to stage all tracked files in worktree --- lua/neogit/popups/commit/actions.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 26790ef2c..e5e9ab5b5 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -107,8 +107,15 @@ end function M.extend(popup) if not git.status.anything_staged() then - notification.warn("No changes to commit.") - return + if git.status.anything_unstaged() then + if input.get_permission("Nothing is staged. Commit all uncommitted changes?") then + git.status.stage_modified() + else + return + end + else + return notification.warn("No changes to commit.") + end end if not confirm_modifications() then From 6664955919f7cbb710799ee828365056f310bab9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 09:38:21 +0100 Subject: [PATCH 165/437] Remove lint-short and add typecheck to Makefile --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5005f526e..6c1a42139 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,10 @@ lint: selene --config selene/config.toml lua typos -lint-short: - selene --config selene/config.toml --display-style Quiet lua - format: stylua . -.PHONY: format lint test +typecheck: + llscheck lua/ + +.PHONY: format lint typecheck From a1f1f742809bff9e3201b92a1421b3b65c8a00dc Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 12:26:59 +0100 Subject: [PATCH 166/437] remove comment --- lua/neogit/popups/stash/actions.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/popups/stash/actions.lua b/lua/neogit/popups/stash/actions.lua index 7db0d3c21..6c8b65a9f 100644 --- a/lua/neogit/popups/stash/actions.lua +++ b/lua/neogit/popups/stash/actions.lua @@ -72,7 +72,6 @@ function M.rename(popup) use("rename", popup.state.env.stash) end ---- git stash list function M.list() StashListBuffer.new(git.repo.state.stashes.items):open() end From 36512ea8ad3c94af9388420f0732cd4ae1a41e1e Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 12:35:05 +0100 Subject: [PATCH 167/437] Improve stashing. Adds arguments for untracked files and all files. adds documentation for events. --- README.md | 1 + lua/neogit/lib/git/stash.lua | 64 ++++++++++++++++---------------- lua/neogit/popups/stash/init.lua | 10 +++-- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 929cce584..591d8168a 100644 --- a/README.md +++ b/README.md @@ -503,6 +503,7 @@ Neogit emits the following events: | `NeogitTagDelete` | A tag was removed | `{ name: string }` | | `NeogitCherryPick` | One or more commits were cherry-picked | `{ commits: string[] }` | | `NeogitMerge` | A merge finished | `{ branch: string, args = string[], status: "ok"\|"conflict" }` | +| `NeogitStash` | A stash finished | `{ success: boolean }` | ## Versioning diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index ea54b1b2f..b5b212fd6 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -6,9 +6,13 @@ local config = require("neogit.config") ---@class NeogitGitStash local M = {} ----@param pattern string -local function fire_stash_event(pattern) - vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false }) +---@param success boolean +local function fire_stash_event(success) + vim.api.nvim_exec_autocmds("User", { + pattern = "NeogitStash", + modeline = false, + data = { success = success } + }) end function M.list_refs() @@ -20,58 +24,56 @@ function M.list_refs() end end +---@param args string[] function M.stash_all(args) - git.cli.stash.arg_list(args).call { await = true } - fire_stash_event("NeogitStash") - -- this should work, but for some reason doesn't. - --return perform_stash({ worktree = true, index = true }) + local result = git.cli.stash.push.files(".").arg_list(args).call() + fire_stash_event(result.code == 0) end -function M.stash_index() - git.cli.stash.staged.call { await = true } - fire_stash_event("NeogitStash") +---@param args string[] +function M.stash_index(args) + local result = git.cli.stash.staged.arg_list(args).call() + fire_stash_event(result.code == 0) end -function M.stash_keep_index() - local files = git.cli["ls-files"].call({ hidden = true }).stdout - -- for some reason complains if not passed files, - -- but this seems to be a git cli error; running: - -- git --literal-pathspecs stash --keep-index - -- fails with a bizarre error: - -- error: pathspec ':/' did not match any file(s) known to git - git.cli.stash.keep_index.files(unpack(files)).call { await = true } - fire_stash_event("NeogitStash") +---@param args string[] +function M.stash_keep_index(args) + local result = git.cli.stash.keep_index.files(".").arg_list(args).call() + fire_stash_event(result.code == 0) end +---@param args string[] +---@param files string[] function M.push(args, files) - git.cli.stash.push.arg_list(args).files(unpack(files)).call { await = true } + local result = git.cli.stash.push.arg_list(args).files(unpack(files)).call() + fire_stash_event(result.code == 0) end function M.pop(stash) - local result = git.cli.stash.apply.index.args(stash).call { await = true } + local result = git.cli.stash.apply.index.args(stash).call() if result.code == 0 then - git.cli.stash.drop.args(stash).call { await = true } + git.cli.stash.drop.args(stash).call() else - git.cli.stash.apply.args(stash).call { await = true } + git.cli.stash.apply.args(stash).call() end - fire_stash_event("NeogitStash") + fire_stash_event(result.code == 0) end function M.apply(stash) - local result = git.cli.stash.apply.index.args(stash).call { await = true } + local result = git.cli.stash.apply.index.args(stash).call() if result.code ~= 0 then - git.cli.stash.apply.args(stash).call { await = true } + git.cli.stash.apply.args(stash).call() end - fire_stash_event("NeogitStash") + fire_stash_event(result.code == 0) end function M.drop(stash) - git.cli.stash.drop.args(stash).call { await = true } - fire_stash_event("NeogitStash") + local result = git.cli.stash.drop.args(stash).call() + fire_stash_event(result.code == 0) end function M.list() @@ -82,8 +84,8 @@ function M.rename(stash) local message = input.get_user_input("New name") if message then local oid = git.rev_parse.abbreviate_commit(stash) - git.cli.stash.drop.args(stash).call { await = true } - git.cli.stash.store.message(message).args(oid).call { await = true } + git.cli.stash.drop.args(stash).call() + git.cli.stash.store.message(message).args(oid).call() end end diff --git a/lua/neogit/popups/stash/init.lua b/lua/neogit/popups/stash/init.lua index 1dea0d466..c8945d8c2 100644 --- a/lua/neogit/popups/stash/init.lua +++ b/lua/neogit/popups/stash/init.lua @@ -4,13 +4,15 @@ local popup = require("neogit.lib.popup") local M = {} function M.create(stash) - -- TODO: - -- :switch("u", "include-untracked", "Also save untracked files") - -- :switch("a", "all", "Also save untracked and ignored files") - local p = popup .builder() :name("NeogitStashPopup") + :switch("u", "include-untracked", "Also save untracked files", { + incompatible = { "all" } + }) + :switch("a", "all", "Also save untracked and ignored files", { + incompatible = { "include-untracked" } + }) :group_heading("Stash") :action("z", "both", actions.both) :action("i", "index", actions.index) From a53a3374d87ab9fb210775a51769a92de05561ab Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 15:28:53 +0100 Subject: [PATCH 168/437] Stash index/keeping index shouldn't take args --- lua/neogit/lib/git/stash.lua | 12 +++++------- lua/neogit/popups/stash/actions.lua | 8 ++++---- spec/spec_helper.rb | 6 +++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index b5b212fd6..1614dd971 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -11,7 +11,7 @@ local function fire_stash_event(success) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitStash", modeline = false, - data = { success = success } + data = { success = success }, }) end @@ -30,15 +30,13 @@ function M.stash_all(args) fire_stash_event(result.code == 0) end ----@param args string[] -function M.stash_index(args) - local result = git.cli.stash.staged.arg_list(args).call() +function M.stash_index() + local result = git.cli.stash.staged.call() fire_stash_event(result.code == 0) end ----@param args string[] -function M.stash_keep_index(args) - local result = git.cli.stash.keep_index.files(".").arg_list(args).call() +function M.stash_keep_index() + local result = git.cli.stash.keep_index.files(".").call() fire_stash_event(result.code == 0) end diff --git a/lua/neogit/popups/stash/actions.lua b/lua/neogit/popups/stash/actions.lua index 6c8b65a9f..c47adbfe3 100644 --- a/lua/neogit/popups/stash/actions.lua +++ b/lua/neogit/popups/stash/actions.lua @@ -10,12 +10,12 @@ function M.both(popup) git.stash.stash_all(popup:get_arguments()) end -function M.index(popup) - git.stash.stash_index(popup:get_arguments()) +function M.index() + git.stash.stash_index() end -function M.keep_index(popup) - git.stash.stash_keep_index(popup:get_arguments()) +function M.keep_index() + git.stash.stash_keep_index() end function M.push(popup) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f582c80a1..c2c8f628a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,7 +51,7 @@ end end - config.around do |example| - Timeout.timeout(10) { example.call } - end + # config.around do |example| + # Timeout.timeout(10) { example.call } + # end end From f47b1727983f850c61988c44409e481579f31b52 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 15:29:19 +0100 Subject: [PATCH 169/437] Improve stash popup spec --- lua/neogit/popups/stash/init.lua | 4 +- spec/popups/stash_popup_spec.rb | 118 ++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/stash/init.lua b/lua/neogit/popups/stash/init.lua index c8945d8c2..9b461c9a0 100644 --- a/lua/neogit/popups/stash/init.lua +++ b/lua/neogit/popups/stash/init.lua @@ -8,10 +8,10 @@ function M.create(stash) .builder() :name("NeogitStashPopup") :switch("u", "include-untracked", "Also save untracked files", { - incompatible = { "all" } + incompatible = { "all" }, }) :switch("a", "all", "Also save untracked and ignored files", { - incompatible = { "include-untracked" } + incompatible = { "include-untracked" }, }) :group_heading("Stash") :action("z", "both", actions.both) diff --git a/spec/popups/stash_popup_spec.rb b/spec/popups/stash_popup_spec.rb index fdaf15024..25810ab16 100644 --- a/spec/popups/stash_popup_spec.rb +++ b/spec/popups/stash_popup_spec.rb @@ -2,11 +2,15 @@ require "spec_helper" -RSpec.describe "Stash Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup +RSpec.describe "Stash Popup", :git, :nvim, :popup do before { nvim.keys("Z") } let(:view) do [ + " Arguments ", + " -u Also save untracked files (--include-untracked) ", + " -a Also save untracked and ignored files (--all) ", + " ", " Stash Snapshot Use Inspect Transform ", " z both Z both p pop l List b Branch ", " i index I index a apply v Show B Branch here ", @@ -17,4 +21,116 @@ end %w[z i w x P Z I W r p a d l b B m f].each { include_examples "interaction", _1 } + %w[-u -a].each { include_examples "argument", _1 } + + describe "Stash both" do + before do + File.write("foo", "hello foo") + File.write("bar", "hello bar") + File.write("baz", "hello baz") + git.add("foo") + git.add("bar") + git.commit("initial commit") + File.write("foo", "hello world") + File.write("bar", "hello world") + git.add("foo") + end + + context "with --include-untracked" do + it "stashes staged, unstaged, and untracked changed" do + nvim.keys("-u") + nvim.keys("z") + expect(git.status.changed).to be_empty + expect(git.status.untracked).to be_empty + end + end + + context "with --all" do + it "stashes staged, unstaged, untracked, and ignored changes" do + nvim.keys("-a") + nvim.keys("z") + expect(git.status.changed).to be_empty + expect(git.status.untracked).to be_empty + end + end + + it "stashes both staged and unstaged changes" do + nvim.keys("z") + expect(git.status.changed).to be_empty + expect(git.status.untracked).not_to be_empty + end + end + + describe "Stash index" do + before do + File.write("foo", "hello foo") # Staged + File.write("bar", "hello bar") # Unstaged + File.write("baz", "hello baz") # Untracked + + git.add("foo") + git.add("bar") + git.commit("initial commit") + + File.write("foo", "hello world") + File.write("bar", "hello world") + + git.add("foo") + end + + it "stashes only staged changes" do + nvim.keys("i") + expect(git.status.changed.keys).to contain_exactly("bar") + expect(git.status.untracked).not_to be_empty + end + end + + describe "Stash Keeping index" do + before do + File.write("foo", "hello foo") # Staged + File.write("bar", "hello bar") # Unstaged + File.write("baz", "hello baz") # Untracked + + git.add("foo") + git.add("bar") + git.commit("initial commit") + + File.write("foo", "hello world") + File.write("bar", "hello world") + + git.add("foo") + end + + it "stashes only unstaged changes" do + nvim.keys("x") + expect(git.status.changed.keys).to contain_exactly("foo") + expect(git.status.untracked).not_to be_empty + end + end + + describe "Stash push" do + before do + File.write("foo", "hello foo") # Staged + File.write("bar", "hello bar") # Unstaged + File.write("baz", "hello baz") # Untracked + + git.add("foo") + git.add("bar") + git.commit("initial commit") + + File.write("foo", "hello world") + File.write("bar", "hello world") + + git.add("foo") + end + + it "stashes only specified file" do + expect(git.status.changed.keys).to contain_exactly("foo", "bar") + + nvim.keys("Pfoo") + expect(git.status.changed.keys).to contain_exactly("bar") + + nvim.keys("ZPbar") + expect(git.status.changed.keys).to be_empty + end + end end From 62084b6e4e6bfcf8d1997e0dadd2b9465bb1bb6c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 19:35:56 +0100 Subject: [PATCH 170/437] Capture and retry only specific failing specs instead of the entire file --- bin/specs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bin/specs b/bin/specs index d20dde53c..77b43d1ad 100755 --- a/bin/specs +++ b/bin/specs @@ -25,6 +25,7 @@ class Runner # rubocop:disable Style/Documentation @length = length @title = test.gsub("spec/", "") @retries = 0 + @failed_lines = [] end def register @@ -45,7 +46,12 @@ class Runner # rubocop:disable Style/Documentation if wait.value.success? register_success!(time) break - elsif retries < 3 + elsif retries < 5 + @failed_lines = JSON.parse(output)["examples"] + .select { _1["status"] == "failed" } + .map { _1["line_number"] } + .uniq + @retries += 1 register_retry! else @@ -65,7 +71,12 @@ class Runner # rubocop:disable Style/Documentation end def run - stdin, stdout, wait = Open3.popen2({ "CI" => "1" }, "bundle exec rspec #{test} --format json --order random") + failed = @failed_lines.empty? ? "" : "[#{@failed_lines.join(',')}]" + stdin, stdout, wait = Open3.popen2( + { "CI" => "1" }, + "bundle exec rspec #{test}#{failed} --format json --order random" + ) + stdin.close output = stdout.read.lines.last stdout.close From 8b620178e0c0e80143b3eb431214df18056db3a1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 19:36:23 +0100 Subject: [PATCH 171/437] Print line number when spec fails --- bin/specs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/specs b/bin/specs index 77b43d1ad..aebb9e8ac 100755 --- a/bin/specs +++ b/bin/specs @@ -138,7 +138,7 @@ if failures.any? output = results[test] puts "\nFail: #{output.dig('examples', 0, 'full_description')}" - puts " #{test}" + puts " #{test}:#{output.dig('examples', 0, 'line_number')}" puts " #{output.dig('examples', 0, 'exception', 'class')}" puts " #{output.dig('examples', 0, 'exception', 'message')}" end From 8c25734b35079abfbd9b5aefea44a22223fc666d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 19:47:43 +0100 Subject: [PATCH 172/437] Revert partially - this breaks the lua specs --- lua/neogit/lib/git/status.lua | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 7f4086b7c..0c43d6c4a 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -251,18 +251,22 @@ end ---@return boolean function M.anything_staged() - local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout - return vim.iter(output):any(function(line) - return line:match("^%d [^%.]") - end) + -- TODO: + -- local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + -- return vim.iter(output):any(function(line) + -- return line:match("^%d [^%.]") + -- end) + return #git.repo.state.staged.items > 0 end ---@return boolean function M.anything_unstaged() - local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout - return vim.iter(output):any(function(line) - return line:match("^%d %..") - end) + -- TODO: + -- local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + -- return vim.iter(output):any(function(line) + -- return line:match("^%d %..") + -- end) + return #git.repo.state.unstaged.items > 0 end M.register = function(meta) From 0081f412deeee3ab01987c84b1b8aba04825d612 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 19:47:56 +0100 Subject: [PATCH 173/437] Remove process spec. Doesn't really test anything meaningful and we have e2e tests for this --- tests/specs/neogit/process_spec.lua | 65 ----------------------------- 1 file changed, 65 deletions(-) delete mode 100644 tests/specs/neogit/process_spec.lua diff --git a/tests/specs/neogit/process_spec.lua b/tests/specs/neogit/process_spec.lua deleted file mode 100644 index 6af9ae870..000000000 --- a/tests/specs/neogit/process_spec.lua +++ /dev/null @@ -1,65 +0,0 @@ -require("plenary.async").tests.add_to_env() -local util = require("tests.util.util") - -local process = require("neogit.process") - -describe("process execution", function() - it("basic command", function() - local result = - process.new({ cmd = { "cat", "process_test" }, cwd = util.get_fixtures_dir() }):spawn_blocking(1) - assert(result) - assert.are.same({ - "This is a test file", - "", - "", - "It is intended to be read by cat and returned to neovim using the process api", - "", - "", - }, result.stdout) - end) - - it("can cat a file", function() - local result = process.new({ cmd = { "cat", "a.txt" }, cwd = util.get_fixtures_dir() }):spawn_blocking(1) - - assert(result) - assert.are.same({ - "Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet.", - "Nisi anim cupidatat excepteur officia.", - "Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident.", - "Nostrud officia pariatur ut officia.", - "Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate.", - "", - "Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod.", - "Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim.", - "Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.", - "", - }, result.stdout) - end) - - it("process input", function() - local tmp_dir = util.create_temp_dir() - local input = { "This is a line", "This is another line", "", "" } - local p = process.new { cmd = { "tee", tmp_dir .. "/output" } } - - p:spawn() - p:send(table.concat(input, "\n")) - p:send("\04") - p:close_stdin() - p:wait() - - local result = process.new({ cmd = { "cat", tmp_dir .. "/output" } }):spawn_blocking(1) - assert(result) - assert.are.same({ "This is a line", "This is another line", "", "\04" }, result.stdout) - end) - - it("basic command trim", function() - local result = - process.new({ cmd = { "cat", "process_test" }, cwd = util.get_fixtures_dir() }):spawn_blocking(1) - - assert(result) - assert.are.same({ - "This is a test file", - "It is intended to be read by cat and returned to neovim using the process api", - }, result:trim().stdout) - end) -end) From 3f098789c6baaf24c81eb6428444d9fa6364353a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 3 Dec 2024 22:06:25 +0100 Subject: [PATCH 174/437] Improve diffview integration: when attempting to stage a change that is unmerged, open it in the diffview merge resolution view. --- lua/neogit/buffers/status/actions.lua | 59 +++++++++++++++++++++------ lua/neogit/integrations/diffview.lua | 59 ++++++++++----------------- lua/neogit/lib/git/merge.lua | 11 +++++ lua/neogit/lib/git/status.lua | 7 ++++ lua/neogit/popups/diff/actions.lua | 2 +- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index b456d834d..d176f2efd 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -7,6 +7,7 @@ local logger = require("neogit.logger") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") +local config = require("neogit.config") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -773,7 +774,6 @@ M.n_discard = function(self) end elseif selection.item then -- Discard Hunk if selection.item.mode == "UU" then - -- TODO: https://github.com/emacs-mirror/emacs/blob/master/lisp/vc/smerge-mode.el notification.warn("Resolve conflicts in file before discarding hunks.") return end @@ -825,7 +825,7 @@ M.n_discard = function(self) end if conflict then - -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#Lair + -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#L515 notification.warn("Resolve conflicts before discarding section.") return else @@ -1025,23 +1025,39 @@ M.n_stage = function(self) return a.void(function() 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() if stagable and section then if section.options.section == "staged" then return end - if stagable.hunk then - local item = self.buffer.ui:get_item_under_cursor() - assert(item, "Item cannot be nil") - - if item.mode == "UU" then - notification.info("Conflicts must be resolved before staging hunks") + if selection.item and selection.item.mode == "UU" then + if config.check_integration("diffview") then + require("neogit.integrations.diffview").open("conflict", selection.item.name, { + on_close = { + handle = self.buffer.handle, + fn = function() + if not git.merge.is_conflicted(selection.item.name) then + git.status.stage { selection.item.name } + self:dispatch_refresh({ update_diffs = { "*:" .. selection.item.name } }, "n_stage") + + if not git.merge.any_conflicted() then + popups.open("merge")() + end + end + end, + }, + }) + else + notification.info("Conflicts must be resolved before staging") return end + elseif stagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + assert(item, "Item cannot be nil") local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) - git.index.apply(patch, { cached = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_stage") elseif stagable.filename then @@ -1058,8 +1074,28 @@ M.n_stage = function(self) git.status.stage_untracked() self:dispatch_refresh({ update_diffs = { "untracked:*" } }, "n_stage") elseif section.options.section == "unstaged" then - git.status.stage_modified() - self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_stage") + if git.status.any_unmerged() then + if config.check_integration("diffview") then + require("neogit.integrations.diffview").open("conflict", nil, { + on_close = { + handle = self.buffer.handle, + fn = function() + if not git.merge.any_conflicted() then + git.status.stage_modified() + self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_stage") + popups.open("merge")() + end + end, + }, + }) + else + notification.info("Conflicts must be resolved before staging") + return + end + else + git.status.stage_modified() + self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_stage") + end end end end) @@ -1403,5 +1439,4 @@ M.n_command = function(self) }) end) end - return M diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index dd9cb70c2..871dd4ba9 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -1,7 +1,5 @@ local M = {} -local dv = require("diffview") -local dv_config = require("diffview.config") local Rev = require("diffview.vcs.adapters.git.rev").GitRev local RevType = require("diffview.vcs.rev").RevType local CDiffView = require("diffview.api.views.diff.diff_view").CDiffView @@ -12,14 +10,6 @@ local Watcher = require("neogit.watcher") local git = require("neogit.lib.git") local a = require("plenary.async") -local old_config - -local function close() - vim.cmd("tabclose") - Watcher.instance():dispatch_refresh() - dv.setup(old_config) -end - local function get_local_diff_view(section_name, item_name, opts) local left = Rev(RevType.STAGE) local right = Rev(RevType.LOCAL) @@ -33,8 +23,8 @@ local function get_local_diff_view(section_name, item_name, opts) local sections = { conflicting = { - items = vim.tbl_filter(function(o) - return o.mode and o.mode:sub(2, 2) == "U" + items = vim.tbl_filter(function(item) + return item.mode and item.mode:sub(2, 2) == "U" end, git.repo.state.untracked.items), }, working = git.repo.state.unstaged, @@ -103,31 +93,23 @@ local function get_local_diff_view(section_name, item_name, opts) return view end +---@param section_name string +---@param item_name string|nil +---@param opts table|nil function M.open(section_name, item_name, opts) opts = opts or {} - old_config = vim.deepcopy(dv_config.get_config()) - - local config = dv_config.get_config() - - local keymaps = { - view = { - ["q"] = close, - [""] = close, - }, - file_panel = { - ["q"] = close, - [""] = close, - }, - } - for key, keymap in pairs(keymaps) do - config.keymaps[key] = dv_config.extend_keymaps(keymap, config.keymaps[key] or {}) + -- Hack way to do an on-close callback + if opts.on_close then + vim.api.nvim_create_autocmd({ "BufEnter" }, { + buffer = opts.on_close.handle, + once = true, + callback = opts.on_close.fn, + }) end - dv.setup(config) - local view - + -- selene: allow(if_same_then_else) if section_name == "recent" or section_name == "unmerged" or section_name == "log" then local range if type(item_name) == "table" then @@ -143,20 +125,21 @@ function M.open(section_name, item_name, opts) local range = item_name view = dv_lib.diffview_open(dv_utils.tbl_pack(range)) elseif section_name == "stashes" then - -- TODO: Fix when no item name + assert(item_name, "No item name for stash!") local stash_id = item_name:match("stash@{%d+}") view = dv_lib.diffview_open(dv_utils.tbl_pack(stash_id .. "^!")) elseif section_name == "commit" then view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) + elseif section_name == "conflict" and item_name then + view = dv_lib.diffview_open(dv_utils.tbl_pack("--selected-file=" .. item_name)) + elseif section_name == "conflict" and not item_name then + view = dv_lib.diffview_open() elseif section_name ~= nil then view = get_local_diff_view(section_name, item_name, opts) + elseif section_name == nil and item_name ~= nil then + view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) else - -- selene: allow(if_same_then_else) - if item_name ~= nil then - view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) - else - view = dv_lib.diffview_open() - end + view = dv_lib.diffview_open() end if view then diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 3bcaa2aa1..1fdbcab00 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -37,6 +37,17 @@ function M.in_progress() return git.repo.state.merge.head ~= nil end +---@param path string filepath to check for conflict markers +---@return boolean +function M.is_conflicted(path) + return git.cli.diff.check.files(path).call().code ~= 0 +end + +---@return boolean +function M.any_conflicted() + return git.cli.diff.check.call().code ~= 0 +end + ---@class MergeItem ---Not used, just for a consistent interface diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 0c43d6c4a..c6489a30d 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -269,6 +269,13 @@ function M.anything_unstaged() return #git.repo.state.unstaged.items > 0 end +---@return boolean +function M.any_unmerged() + return vim.iter(git.repo.state.unstaged.items):any(function(item) + return item.mode == "UU" + end) +end + M.register = function(meta) meta.update_status = update_status end diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index 39d3df266..f652bdce6 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -56,7 +56,7 @@ end function M.worktree(popup) popup:close() - diffview.open() + diffview.open("worktree") end function M.staged(popup) From ac074281acb9c8e171b1f45cfb13bdedf5a9ba1c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Dec 2024 22:01:30 +0100 Subject: [PATCH 175/437] Fix: Truncate spinner text to prevent "hit-enter" prompt --- lua/neogit/spinner.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/spinner.lua b/lua/neogit/spinner.lua index 0490e72a2..9b6203fef 100644 --- a/lua/neogit/spinner.lua +++ b/lua/neogit/spinner.lua @@ -1,3 +1,4 @@ +local util = require("neogit.lib.util") ---@class Spinner ---@field text string ---@field count number @@ -10,7 +11,7 @@ Spinner.__index = Spinner ---@return Spinner function Spinner.new(text) local instance = { - text = text, + text = util.str_truncate(text, vim.v.echospace - 2, "..."), interval = 100, count = 0, timer = nil, @@ -40,7 +41,7 @@ function Spinner:start() vim.schedule_wrap(function() self.count = self.count + 1 local step = self.pattern[(self.count % #self.pattern) + 1] - vim.cmd(string.format("redraw | echo '%s %s'", step, self.text)) + vim.cmd(string.format("echo '%s %s' | redraw", step, self.text)) end) ) end From 3a7b7294a594465ef23bbd4dc638144212c37d0f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Dec 2024 22:05:40 +0100 Subject: [PATCH 176/437] fix typecheck --- lua/neogit/popups/commit/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index e5e9ab5b5..d3817a85c 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -199,7 +199,7 @@ function M.absorb(popup) return end - git.cli.absorb.verbose.base(commit).env({ GIT_SEQUENCE_EDITOR = ":" }).and_rebase.call() + git.cli.absorb.verbose.base(commit).and_rebase.env({ GIT_SEQUENCE_EDITOR = ":" }).call() end return M From 49271da0b29255173268925136a1d3767781e27f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Dec 2024 23:39:01 +0100 Subject: [PATCH 177/437] Use lib to find HEAD's available --- lua/neogit/popups/diff/actions.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index f652bdce6..af184cbd4 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -81,8 +81,7 @@ end function M.commit(popup) popup:close() - local options = - util.merge(git.branch.get_all_branches(), git.tag.list(), { "HEAD", "ORIG_HEAD", "FETCH_HEAD" }) + local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) local selected = FuzzyFinderBuffer.new(options):open_async() if selected then From 0e59552c9c9b98ca3c0b76e1fbc73d4b21be18a9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Dec 2024 23:41:49 +0100 Subject: [PATCH 178/437] Set up diffview in test suite --- spec/support/neovim_client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/support/neovim_client.rb b/spec/support/neovim_client.rb index a114cce83..d573e8dd7 100644 --- a/spec/support/neovim_client.rb +++ b/spec/support/neovim_client.rb @@ -25,6 +25,7 @@ def setup(neogit_config) # rubocop:disable Metrics/MethodLength lua <<~LUA require("plenary") + require("diffview").setup() require('neogit').setup(#{neogit_config}) require('neogit').open() LUA From c498f308fc5bcf99e3ca83c4231e0a901b28d10d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Dec 2024 23:53:49 +0100 Subject: [PATCH 179/437] Revert "Revert partially - this breaks the lua specs" This reverts commit 8c25734b35079abfbd9b5aefea44a22223fc666d. --- lua/neogit/lib/git/status.lua | 20 ++++------ tests/specs/neogit/lib/git/status_spec.lua | 46 ---------------------- 2 files changed, 8 insertions(+), 58 deletions(-) delete mode 100644 tests/specs/neogit/lib/git/status_spec.lua diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index c6489a30d..cc3bb6862 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -251,22 +251,18 @@ end ---@return boolean function M.anything_staged() - -- TODO: - -- local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout - -- return vim.iter(output):any(function(line) - -- return line:match("^%d [^%.]") - -- end) - return #git.repo.state.staged.items > 0 + local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + return vim.iter(output):any(function(line) + return line:match("^%d [^%.]") + end) end ---@return boolean function M.anything_unstaged() - -- TODO: - -- local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout - -- return vim.iter(output):any(function(line) - -- return line:match("^%d %..") - -- end) - return #git.repo.state.unstaged.items > 0 + local output = git.cli.status.porcelain(2).call({ hidden = true }).stdout + return vim.iter(output):any(function(line) + return line:match("^%d %..") + end) end ---@return boolean diff --git a/tests/specs/neogit/lib/git/status_spec.lua b/tests/specs/neogit/lib/git/status_spec.lua deleted file mode 100644 index d5894a62c..000000000 --- a/tests/specs/neogit/lib/git/status_spec.lua +++ /dev/null @@ -1,46 +0,0 @@ -local neogit = require("neogit") -local git_harness = require("tests.util.git_harness") -local util = require("tests.util.util") - -local subject = require("neogit.lib.git.status") - -neogit.setup {} - -describe("lib.git.status", function() - before_each(function() - git_harness.prepare_repository() - -- plenary_async.util.block_on(neogit.reset) - end) - - describe("#anything_staged", function() - -- it("returns true when there are staged items", function() - -- util.system("git add --all") - -- plenary_async.util.block_on(neogit.refresh) - -- - -- assert.True(subject.anything_staged()) - -- end) - - it("returns false when there are no staged items", function() - util.system { "git", "reset" } - neogit.refresh() - - assert.False(subject.anything_staged()) - end) - end) - - describe("#anything_unstaged", function() - -- it("returns true when there are unstaged items", function() - -- util.system("git reset") - -- plenary_async.util.block_on(neogit.refresh) - -- - -- assert.True(subject.anything_unstaged()) - -- end) - - it("returns false when there are no unstaged items", function() - util.system { "git", "add", "--all" } - neogit.refresh() - - assert.False(subject.anything_unstaged()) - end) - end) -end) From 8c1b8840c46a21c8dc5dfade3ef9b670f78ada27 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 00:06:35 +0100 Subject: [PATCH 180/437] Check for all unmerged types --- lua/neogit/lib/git/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index cc3bb6862..6383393cb 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -268,7 +268,7 @@ end ---@return boolean function M.any_unmerged() return vim.iter(git.repo.state.unstaged.items):any(function(item) - return item.mode == "UU" + return vim.tbl_contains({ "UU", "AA", "DU", "UD", "AU", "UA", "DD" }, item.mode) end) end From f8d7d3757d376081c99f5444538be57459c9b99a Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 00:09:51 +0100 Subject: [PATCH 181/437] Have "commit->absorb" make more sense when you select a commit to absorb into. --- lua/neogit/popups/commit/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index d3817a85c..a08531e9d 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -199,7 +199,7 @@ function M.absorb(popup) return end - git.cli.absorb.verbose.base(commit).and_rebase.env({ GIT_SEQUENCE_EDITOR = ":" }).call() + git.cli.absorb.verbose.base(commit .. "^").and_rebase.env({ GIT_SEQUENCE_EDITOR = ":" }).call() end return M From 9a81ced56b43bda1747b8ae4cf19ebb624db68f3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 21:34:05 +0100 Subject: [PATCH 182/437] Use buffer option setter --- lua/neogit/lib/buffer.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index cc3160f65..ead0b61cf 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -827,12 +827,12 @@ function Buffer.create(config) end if config.status_column then - vim.opt_local.statuscolumn = config.status_column - vim.opt_local.signcolumn = "no" + buffer:set_buffer_option("statuscolumn", config.status_column) + buffer:set_buffer_option("signcolumn", "no") end if config.foldmarkers then - vim.opt_local.signcolumn = "auto" + buffer:set_buffer_option("signcolumn", "auto") logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up foldmarkers") buffer:create_namespace("FoldSigns") From e144a2261cbdc0d68503309440835020a253677e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 21:34:17 +0100 Subject: [PATCH 183/437] Modify behaviour of trying to open commit view - if it's already open and you go to open a new one, just open the new one instead of closing the old one first --- lua/neogit/buffers/commit_view/init.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 6678cd855..6bf70e84a 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -145,10 +145,6 @@ end function M:open(kind) kind = kind or config.values.commit_view.kind - if M.is_open() then - M.instance:close() - end - M.instance = self self.buffer = Buffer.create { From f1f4e02143db5060d09513cc99224fbe974e7751 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 23:30:47 +0100 Subject: [PATCH 184/437] Add a highlight on the status/log/reflog/stash-log buffer when the commit view is open that indicates the current active item --- lua/neogit/buffers/commit_view/init.lua | 9 +++++ lua/neogit/buffers/common.lua | 8 ++++- lua/neogit/buffers/log_view/init.lua | 1 + lua/neogit/buffers/reflog_view/init.lua | 1 + lua/neogit/buffers/reflog_view/ui.lua | 5 ++- lua/neogit/buffers/stash_list_view/init.lua | 1 + lua/neogit/buffers/stash_list_view/ui.lua | 2 +- lua/neogit/buffers/status/init.lua | 1 + lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/lib/buffer.lua | 40 +++++++++++++++++++-- lua/neogit/lib/git/reflog.lua | 2 +- lua/neogit/lib/git/stash.lua | 1 + lua/neogit/lib/hl.lua | 1 + lua/neogit/lib/ui/init.lua | 6 ++++ lua/neogit/lib/ui/renderer.lua | 12 +++++++ 15 files changed, 84 insertions(+), 8 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 6bf70e84a..cebc9b45e 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -81,6 +81,15 @@ function M:close() M.instance = nil end +---@return string +function M.current_oid() + if M.is_open() then + return M.instance.commit_info.oid + else + return "null-oid" + end +end + ---Opens the CommitViewBuffer if it isn't open or performs the given action ---which is passed the window id of the commit view buffer ---@param commit_id string commit diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 701ff3c35..31118009b 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -271,7 +271,13 @@ M.CommitEntry = Component.new(function(commit, remotes, args) } ), details, - }, { oid = commit.oid, foldable = args.details == true, folded = true, remote = info.remotes[1] }) + }, { + item = commit, + oid = commit.oid, + foldable = args.details == true, + folded = true, + remote = info.remotes[1] + }) end) M.CommitGraph = Component.new(function(commit, padding) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index c73c033e6..2dbb52890 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -81,6 +81,7 @@ function M:open() context_highlight = false, header = self.header, scroll_header = false, + active_item_highlight = true, status_column = not config.values.disable_signs and "" or nil, mappings = { v = { diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index d740c63b4..8f62d78c1 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -56,6 +56,7 @@ function M:open(_) scroll_header = true, status_column = not config.values.disable_signs and "" or nil, context_highlight = true, + active_item_highlight = true, mappings = { v = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) diff --git a/lua/neogit/buffers/reflog_view/ui.lua b/lua/neogit/buffers/reflog_view/ui.lua index 6a054ad2a..ac777ed06 100644 --- a/lua/neogit/buffers/reflog_view/ui.lua +++ b/lua/neogit/buffers/reflog_view/ui.lua @@ -48,7 +48,10 @@ M.Entry = Component.new(function(entry, total) { date, "Special" }, }, }), - }, { oid = entry.oid }) + }, { + oid = entry.oid, + item = entry, + }) end) ---@param entries ReflogEntry[] diff --git a/lua/neogit/buffers/stash_list_view/init.lua b/lua/neogit/buffers/stash_list_view/init.lua index 9abe7dad9..fed4d67e7 100644 --- a/lua/neogit/buffers/stash_list_view/init.lua +++ b/lua/neogit/buffers/stash_list_view/init.lua @@ -39,6 +39,7 @@ function M:open() scroll_header = true, kind = config.values.stash.kind, context_highlight = true, + active_item_highlight = true, mappings = { v = { [popups.mapping_for("CherryPickPopup")] = function() diff --git a/lua/neogit/buffers/stash_list_view/ui.lua b/lua/neogit/buffers/stash_list_view/ui.lua index 5ba839a24..76c2b2f34 100644 --- a/lua/neogit/buffers/stash_list_view/ui.lua +++ b/lua/neogit/buffers/stash_list_view/ui.lua @@ -23,7 +23,7 @@ M.Stash = Component.new(function(stash) { config.values.log_date_format ~= nil and stash.date or stash.rel_date, "Special" }, }, }), - }, { oid = label }) + }, { oid = label, item = stash }) end) ---@param stashes StashItem[] diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 378a94230..948ce5e39 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -104,6 +104,7 @@ function M:open(kind) disable_line_numbers = config.values.disable_line_numbers, disable_relative_line_numbers = config.values.disable_relative_line_numbers, foldmarkers = not config.values.disable_signs, + active_item_highlight = true, on_detach = function() Watcher.instance(self.root):unregister(self) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index bfc26046e..572b223b3 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -314,7 +314,7 @@ local SectionItemStash = Component.new(function(item) text.highlight("NeogitSubtleText")(name), text.highlight("NeogitSubtleText")(": "), text(item.message), - }, { yankable = name, item = item }) + }, { yankable = item.oid, item = item }) end) local SectionItemCommit = Component.new(function(item) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index ead0b61cf..336283888 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -620,6 +620,7 @@ end ---@field status_column string|nil ---@field load boolean|nil ---@field context_highlight boolean|nil +---@field active_item_highlight boolean|nil ---@field open boolean|nil ---@field disable_line_numbers boolean|nil ---@field disable_relative_line_numbers boolean|nil @@ -675,6 +676,11 @@ function Buffer.create(config) buffer:set_filetype(config.filetype) end + if config.status_column then + buffer:set_buffer_option("statuscolumn", config.status_column) + buffer:set_buffer_option("signcolumn", "no") + end + if config.user_mappings then logger.debug("[BUFFER:" .. buffer.handle .. "] Building user key-mappings") @@ -826,9 +832,37 @@ function Buffer.create(config) }) end - if config.status_column then - buffer:set_buffer_option("statuscolumn", config.status_column) - buffer:set_buffer_option("signcolumn", "no") + if config.active_item_highlight then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up active item decorations") + buffer:create_namespace("ActiveItem") + buffer:set_decorations("ActiveItem", { + on_start = function() + return buffer:exists() and buffer:is_valid() + end, + on_win = function() + buffer:clear_namespace("ActiveItem") + + local active_oid = require("neogit.buffers.commit_view").current_oid() + local item = buffer.ui:find_component_by_oid(active_oid) + if item then + for line = item.first, item.last do + buffer:add_line_highlight(line - 1, "NeogitActiveItem", { + priority = 200, + namespace = "ActiveItem", + }) + end + end + end, + }) + + -- The decoration provider will not quite update in time, leaving two lines highlighted unless we use an autocmd too + api.nvim_create_autocmd("WinLeave", { + buffer = buffer.handle, + group = buffer.autocmd_group, + callback = function() + buffer:clear_namespace("ActiveItem") + end, + }) end if config.foldmarkers then diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index 82e82c305..d84e36890 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -52,7 +52,7 @@ end function M.list(refname, options) local format = table.concat({ - "%h", -- Full Hash + "%H", -- Full Hash "%aN", -- Author Name "%gd", -- Reflog Name "%gs", -- Reflog Subject diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 1614dd971..b0325a38d 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -107,6 +107,7 @@ function M.register(meta) idx = idx, name = line, message = message, + oid = git.rev_parse.oid("stash@{" .. idx .. "}"), } -- These calls can be somewhat expensive, so lazy load them diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 9d946934a..b788b763a 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -304,6 +304,7 @@ function M.setup(config) NeogitTagDistance = { fg = palette.cyan }, NeogitFloatHeader = { bg = palette.bg0, bold = palette.bold }, NeogitFloatHeaderHighlight = { bg = palette.bg2, fg = palette.cyan, bold = palette.bold }, + NeogitActiveItem = { bg = palette.bg_orange, fg = palette.bg0, bold = palette.bold }, } for group, hl in pairs(hl_store) do diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index a1289ccee..95b2d5e0e 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -114,6 +114,12 @@ function Ui:_find_component_by_index(line, f) end end +---@param oid string +---@return Component|nil +function Ui:find_component_by_oid(oid) + return self.node_index:find_by_oid(oid) +end + ---@return Component|nil function Ui:get_cursor_context(line) local cursor = line or vim.api.nvim_win_get_cursor(0)[1] diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 2c187bcdb..00fce647c 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -3,6 +3,7 @@ ---@class RendererIndex ---@field index table ---@field items table +---@field oid_index table local RendererIndex = {} RendererIndex.__index = RendererIndex @@ -12,6 +13,12 @@ function RendererIndex:find_by_line(line) return self.index[line] or {} end +---@param oid string +---@return Component|nil +function RendererIndex:find_by_oid(oid) + return self.oid_index[oid] +end + ---@param node Component function RendererIndex:add(node) if not self.index[node.position.row_start] then @@ -38,11 +45,16 @@ function RendererIndex:add_item(item, first, last) item.first = first item.last = last table.insert(self.items[#self.items].items, item) + + if item.oid then + self.oid_index[item.oid] = item + end end function RendererIndex.new() return setmetatable({ index = {}, + oid_index = {}, items = { { items = {} }, -- First section }, From 4607cb4d84d60a1aeb16039421640f3411c48940 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Dec 2024 23:34:08 +0100 Subject: [PATCH 185/437] lint --- lua/neogit/buffers/common.lua | 2 +- lua/neogit/lib/git/reflog.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 31118009b..2fd54566c 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -276,7 +276,7 @@ M.CommitEntry = Component.new(function(commit, remotes, args) oid = commit.oid, foldable = args.details == true, folded = true, - remote = info.remotes[1] + remote = info.remotes[1], }) end) diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index d84e36890..ca3c1650c 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -52,7 +52,7 @@ end function M.list(refname, options) local format = table.concat({ - "%H", -- Full Hash + "%H", -- Full Hash "%aN", -- Author Name "%gd", -- Reflog Name "%gs", -- Reflog Subject From ed12d235ed47252e392f92b3b4016449092897e4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 6 Dec 2024 13:00:04 +0100 Subject: [PATCH 186/437] Fix types --- lua/neogit/lib/buffer.lua | 2 +- lua/neogit/lib/ui/component.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 336283888..2c5f31aa5 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -844,7 +844,7 @@ function Buffer.create(config) local active_oid = require("neogit.buffers.commit_view").current_oid() local item = buffer.ui:find_component_by_oid(active_oid) - if item then + if item and item.first and item.last then for line = item.first, item.last do buffer:add_line_highlight(line - 1, "NeogitActiveItem", { priority = 200, diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index b393e1e10..ae737977c 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -45,6 +45,8 @@ local default_component_options = { ---@field highlight fun(hl_group:string): self ---@field line_hl fun(hl_group:string): self ---@field padding_left fun(string): self +---@field first integer|nil first line component appears rendered in buffer +---@field last integer|nil last line component appears rendered in buffer ---@operator call: Component local Component = {} From ed01faa2724f4d11fe4c229bc40dc95d824ff0a0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 6 Dec 2024 13:03:15 +0100 Subject: [PATCH 187/437] Add documentation for new highlight group NeogitActiveItem --- doc/neogit.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index fc44a6e05..c6d4ad1d5 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -522,6 +522,7 @@ NeogitDiffContext NeogitDiffAdd NeogitDiffDelete NeogitDiffHeader +NeogitActiveItem Highlight of current commit-ish open SIGNS FOR LINE HIGHLIGHTING CURRENT CONTEXT These are essentially an accented version of the above highlight groups. Only From 568415f236c6b0cef1f55f957112cdf0689c150a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 8 Dec 2024 20:38:25 +0100 Subject: [PATCH 188/437] Fix: Commit --allow-empty shouldn't return early because you have no changes staged. Thats the whole point... --- lua/neogit/popups/commit/actions.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index a08531e9d..4c547da53 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -8,6 +8,12 @@ local notification = require("neogit.lib.notification") local config = require("neogit.config") local a = require("plenary.async") +---@param popup PopupData +---@return boolean +local function allow_empty(popup) + return vim.tbl_contains(popup:get_arguments(), "--allow-empty") +end + local function confirm_modifications() if git.branch.upstream() @@ -37,7 +43,7 @@ local function do_commit(popup, cmd) end local function commit_special(popup, method, opts) - if not git.status.anything_staged() then + if not git.status.anything_staged() and not allow_empty(popup) then if git.status.anything_unstaged() then if input.get_permission("Nothing is staged. Commit all uncommitted changed?") then opts.all = true @@ -97,7 +103,7 @@ local function commit_special(popup, method, opts) end function M.commit(popup) - if not git.status.anything_staged() then + if not git.status.anything_staged() and not allow_empty(popup) then notification.warn("No changes to commit.") return end @@ -106,7 +112,7 @@ function M.commit(popup) end function M.extend(popup) - if not git.status.anything_staged() then + if not git.status.anything_staged() and not allow_empty(popup) then if git.status.anything_unstaged() then if input.get_permission("Nothing is staged. Commit all uncommitted changes?") then git.status.stage_modified() @@ -175,7 +181,7 @@ function M.absorb(popup) return end - if not git.status.anything_staged() then + if not git.status.anything_staged() and not allow_empty(popup) then if git.status.anything_unstaged() then if input.get_permission("Nothing is staged. Absorb all unstaged changes?") then git.status.stage_modified() From 24d1375dc8fcadeb37207fb82ac14060b8eb6cce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 8 Dec 2024 21:06:32 +0100 Subject: [PATCH 189/437] Ask to fetch from a remote after adding it --- lua/neogit/popups/remote/actions.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 38952f6aa..5582e2549 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -54,6 +54,10 @@ function M.add(popup) else notification.info("Added remote " .. name) end + + if input.get_permission("Fetch refs from " .. name .. "?") then + git.fetch.fetch_interactive(name, "", { "--tags" }) + end end end From c224db3997f6936c3a71f786d61147f66f8fb431 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Dec 2024 11:57:31 +0100 Subject: [PATCH 190/437] Ensure that viewport doesn't shift when expanding hunks --- lua/neogit/buffers/status/ui.lua | 6 ++++-- lua/neogit/lib/buffer.lua | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 572b223b3..1efc135f6 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -235,8 +235,10 @@ local SectionItemFile = function(section, config) end end - this:append(DiffHunks(diff)) - ui:update() + ui.buf:with_locked_viewport(function() + this:append(DiffHunks(diff)) + ui:update() + end) end) end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 2c5f31aa5..e3f02241e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -76,6 +76,13 @@ function Buffer:clear() api.nvim_buf_set_lines(self.handle, 0, -1, false, {}) end +---@param fn fun() +function Buffer:with_locked_viewport(fn) + local view = self:save_view() + self:call(fn) + self:restore_view(view) +end + ---@return table function Buffer:save_view() local view = fn.winsaveview() From 40038473707c54a846bd11ecaf5933dd45858972 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Dec 2024 12:11:23 +0100 Subject: [PATCH 191/437] Ensure item name is properly escaped before passing to delete() --- lua/neogit/buffers/status/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index d176f2efd..f48f1c5c0 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -42,7 +42,7 @@ local function cleanup_items(...) api.nvim_buf_delete(bufnr, { force = false }) end - fn.delete(item.name) + fn.delete(fn.fnameescape(item.name)) end end From bd0c2d3429eac3e70dae42cfe913b44242da7132 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Dec 2024 22:12:35 +0100 Subject: [PATCH 192/437] fix: https://github.com/NeogitOrg/neogit/issues/1596 Don't lose the cwd when using `kind=replace` if it's nested within the repo --- lua/neogit/lib/buffer.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e3f02241e..ec4bb619b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -214,6 +214,11 @@ function Buffer:close(force) end if self.kind == "replace" then + if self.old_cwd then + api.nvim_set_current_dir(self.old_cwd) + self.old_cwd = nil + end + api.nvim_buf_delete(self.handle, { force = force }) return end @@ -249,6 +254,11 @@ function Buffer:hide() vim.cmd("silent! 1only") vim.cmd("try | tabn # | catch /.*/ | tabp | endtry") elseif self.kind == "replace" then + if self.old_cwd then + api.nvim_set_current_dir(self.old_cwd) + self.old_cwd = nil + end + 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 @@ -288,6 +298,7 @@ function Buffer:show() local win if self.kind == "replace" then self.old_buf = api.nvim_get_current_buf() + self.old_cwd = vim.uv.cwd() api.nvim_set_current_buf(self.handle) win = api.nvim_get_current_win() elseif self.kind == "tab" then From 991108906faf8fab4029ae421fea6cc5a6c25441 Mon Sep 17 00:00:00 2001 From: Steven Xu Date: Thu, 12 Dec 2024 20:36:04 +1100 Subject: [PATCH 193/437] fix(commit-view): fix jump to file, when filenames contain `-` --- lua/neogit/buffers/commit_view/init.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index cebc9b45e..90942a931 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -208,9 +208,18 @@ function M:open(kind) -- Search for a match and jump if we find it for path, line_nr in pairs(diff_headers) do + local path_norm = path + for _, kind in ipairs { "modified", "renamed", "new file", "deleted file" } do + if vim.startswith(path_norm, kind .. " ") then + path_norm = string.sub(path_norm, string.len(kind) + 2) + break + end + end -- The gsub is to work around the fact that the OverviewFiles use -- => in renames but the diff header uses -> - if path:gsub(" %-> ", " => "):match(selected_path) then + path_norm = path_norm:gsub(" %-> ", " => ") + + if path_norm == selected_path then -- Save position in jumplist vim.cmd("normal! m'") From 02ee70f39a2dd882c8900255a4df778ecc7f943f Mon Sep 17 00:00:00 2001 From: gldtn Date: Fri, 13 Dec 2024 18:33:37 -0500 Subject: [PATCH 194/437] Allow multi-selection for fzf --- lua/neogit/lib/finder.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 03f3fedd1..adc42d9d9 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -117,6 +117,7 @@ end --- Utility function to map actions ---@param on_select fun(item: any|nil) ---@param allow_multi boolean +---@param refocus_status boolean local function fzf_actions(on_select, allow_multi, refocus_status) local function refresh() if refocus_status then @@ -150,7 +151,10 @@ end local function fzf_opts(opts) local fzf_opts = {} - if not opts.allow_multi then + -- Allow multi by default + if opts.allow_multi then + fzf_opts["--multi"] = "" + else fzf_opts["--no-multi"] = "" end From d7b7db9495692897d8915b1c6a9c841c5db71623 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sat, 14 Dec 2024 19:20:49 +0100 Subject: [PATCH 195/437] Fix "Handle detached head in log popup" --- lua/neogit/popups/log/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index af3aabebc..ebc5c836d 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -38,7 +38,7 @@ function M.log_current(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, {}), - "Commits in " .. git.branch.current() or ("(detached) " .. git.log.message("HEAD")), + "Commits in " .. (git.branch.current() or ("(detached) " .. git.log.message("HEAD"))), git.remote.list() ):open() end From ab6966d9fef9692dd0fbbfd94146f4c828f8fea1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 14 Dec 2024 19:48:10 +0100 Subject: [PATCH 196/437] Define 'NeogitWinSeparator' highlight group and link it to 'WinSeparator' fixes: https://github.com/NeogitOrg/neogit/issues/1371 --- lua/neogit/lib/hl.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index b788b763a..483913a64 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -230,6 +230,7 @@ function M.setup(config) NeogitStash = { link = "NeogitSubtleText" }, NeogitRebaseDone = { link = "NeogitSubtleText" }, NeogitFold = { fg = "None", bg = "None" }, + NeogitWinSeparator = { link = "WinSeparator" }, NeogitChangeMuntracked = { link = "NeogitChangeModified" }, NeogitChangeAuntracked = { link = "NeogitChangeAdded" }, NeogitChangeNuntracked = { link = "NeogitChangeNewFile" }, From cfea08ebb2fda22f1357e413d4ea45dcda6fcab5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 14 Dec 2024 20:07:14 +0100 Subject: [PATCH 197/437] Ensure current branch is not in list of options for merging --- lua/neogit/popups/merge/actions.lua | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index 3507cc45a..41dd9e58a 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -16,10 +16,17 @@ function M.abort() end end -function M.merge(popup) +---@param popup PopupData +---@return string[] +local function get_refs(popup) local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) + util.remove_item_from_table(refs, git.branch.current()) + + return refs +end - local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } +function M.merge(popup) + local ref = FuzzyFinderBuffer.new(get_refs(popup)):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--no-edit") @@ -28,9 +35,7 @@ function M.merge(popup) end function M.squash(popup) - local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - - local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Squash" } + local ref = FuzzyFinderBuffer.new(get_refs(popup)):open_async { prompt_prefix = "Squash" } if ref then local args = popup:get_arguments() table.insert(args, "--squash") @@ -39,9 +44,7 @@ function M.squash(popup) end function M.merge_edit(popup) - local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - - local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } + local ref = FuzzyFinderBuffer.new(get_refs(popup)):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--edit") @@ -55,9 +58,7 @@ function M.merge_edit(popup) end function M.merge_nocommit(popup) - local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) - - local ref = FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Merge" } + local ref = FuzzyFinderBuffer.new(get_refs(popup)):open_async { prompt_prefix = "Merge" } if ref then local args = popup:get_arguments() table.insert(args, "--no-commit") From 070924eaa25cfd9556e566ca6e6dabeb9408ca6c Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:33:14 +0100 Subject: [PATCH 198/437] Add more information in a Hunk --- lua/neogit/lib/git/diff.lua | 7 +++++++ tests/specs/neogit/lib/git/log_spec.lua | 3 +++ 2 files changed, 10 insertions(+) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 15bea3182..d8c514975 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -24,12 +24,14 @@ local sha256 = vim.fn.sha256 ---@field deletions number --- ---@class Hunk +---@field file string ---@field index_from number ---@field index_len number ---@field diff_from number ---@field diff_to number ---@field first number First line number in buffer ---@field last number Last line number in buffer +---@field lines string[] --- ---@class DiffStagedStats ---@field summary string @@ -224,6 +226,11 @@ local function parse_diff(raw_diff, raw_stats) local file = build_file(header, kind) local stats = parse_diff_stats(raw_stats or {}) + util.map(hunks, function(hunk) + hunk.file = file + return hunk + end) + return { ---@type Diff kind = kind, lines = lines, diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 446eaa07e..f3618c353 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -96,6 +96,7 @@ describe("lib.git.log.parse", function() index_from = 692, index_len = 33, length = 40, + file = "lua/neogit/status.lua", line = "@@ -692,33 +692,28 @@ end", lines = { " ---@param first_line number", @@ -149,6 +150,7 @@ describe("lib.git.log.parse", function() index_from = 734, index_len = 14, length = 15, + file = "lua/neogit/status.lua", line = "@@ -734,14 +729,10 @@ function M.get_item_hunks(item, first_line, last_line, partial)", lines = { " setmetatable(o, o)", @@ -290,6 +292,7 @@ describe("lib.git.log.parse", function() index_len = 7, length = 9, line = "@@ -1,7 +1,9 @@", + file = "LICENSE", lines = { " MIT License", " ", From ad51676617b8bbe7070e2c7d52136bf860ac08b3 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:33:58 +0100 Subject: [PATCH 199/437] Use only Hunk in `generate_patch` --- lua/neogit/buffers/status/actions.lua | 25 +++++++++------- lua/neogit/lib/git/index.lua | 36 ++++++++--------------- lua/neogit/lib/ui/init.lua | 11 ++----- tests/specs/neogit/lib/git/index_spec.lua | 8 ++--- 4 files changed, 33 insertions(+), 47 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index f48f1c5c0..ee15ed14e 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -120,7 +120,8 @@ M.v_discard = function(self) for _, hunk in ipairs(hunks) do table.insert(invalidated_diffs, "*:" .. item.name) table.insert(patches, function() - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + local patch = + git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) logger.debug(("Discarding Patch: %s"):format(patch)) @@ -231,7 +232,7 @@ M.v_stage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + table.insert(patches, git.index.generate_patch(hunk.hunk, { from = hunk.from, to = hunk.to })) end else if section.name == "unstaged" then @@ -281,7 +282,10 @@ M.v_unstage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to, true)) + table.insert( + patches, + git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) + ) end else table.insert(files, item.escaped_path) @@ -781,7 +785,7 @@ M.n_discard = function(self) local hunk = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] - local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) + local patch = git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) if section == "untracked" then message = "Discard hunk?" @@ -789,9 +793,8 @@ M.n_discard = function(self) local hunks = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) - local patch = git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) - - git.index.apply(patch, { reverse = true }) + local patch = + git.index.generate_patch(hunks[1], { from = hunks[1].from, to = hunks[1].to, reverse = true }) git.index.apply(patch, { reverse = true }) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } @@ -1057,7 +1060,7 @@ M.n_stage = function(self) local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + local patch = git.index.generate_patch(stagable.hunk) git.index.apply(patch, { cached = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_stage") elseif stagable.filename then @@ -1131,8 +1134,10 @@ M.n_unstage = function(self) if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = - git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) + local patch = git.index.generate_patch( + unstagable.hunk, + { from = unstagable.hunk.from, to = unstagable.hunk.to, reverse = true } + ) git.index.apply(patch, { cached = true, reverse = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_unstage") diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 35b9c8cfe..54837376e 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -6,19 +6,15 @@ local util = require("neogit.lib.util") local M = {} ---Generates a patch that can be applied to index ----@param item any ---@param hunk Hunk ----@param from number ----@param to number ----@param reverse boolean|nil +---@param opts table|nil ---@return string -function M.generate_patch(item, hunk, from, to, reverse) - reverse = reverse or false +function M.generate_patch(hunk, opts) + opts = opts or { reverse = false, cached = false, index = false } + local reverse = opts.reverse - if not from and not to then - from = hunk.diff_from + 1 - to = hunk.diff_to - end + local from = opts.from or 1 + local to = opts.to or (hunk.diff_to - hunk.diff_from) assert(from <= to, string.format("from must be less than or equal to to %d %d", from, to)) if from > to then @@ -29,35 +25,31 @@ function M.generate_patch(item, hunk, from, to, reverse) local len_start = hunk.index_len local len_offset = 0 - -- + 1 skips the hunk header, since we construct that manually afterwards - -- TODO: could use `hunk.lines` instead if this is only called with the `SelectedHunk` type - for k = hunk.diff_from + 1, hunk.diff_to do - local v = item.diff.lines[k] - local operand, line = v:match("^([+ -])(.*)") - + for k, line in pairs(hunk.lines) do + local operand, l = line:match("^([+ -])(.*)") if operand == "+" or operand == "-" then if from <= k and k <= to then len_offset = len_offset + (operand == "+" and 1 or -1) - table.insert(diff_content, v) + table.insert(diff_content, line) else -- If we want to apply the patch normally, we need to include every `-` line we skip as a normal line, -- since we want to keep that line. if not reverse then if operand == "-" then - table.insert(diff_content, " " .. line) + table.insert(diff_content, " " .. l) end -- If we want to apply the patch in reverse, we need to include every `+` line we skip as a normal line, since -- it's unchanged as far as the diff is concerned and should not be reversed. -- We also need to adapt the original line offset based on if we skip or not elseif reverse then if operand == "+" then - table.insert(diff_content, " " .. line) + table.insert(diff_content, " " .. l) end len_start = len_start + (operand == "-" and -1 or 1) end end else - table.insert(diff_content, v) + table.insert(diff_content, line) end end @@ -68,9 +60,7 @@ function M.generate_patch(item, hunk, from, to, reverse) ) local worktree_root = git.repo.worktree_root - - assert(item.absolute_path, "Item is not a path") - local path = Path:new(item.absolute_path):make_relative(worktree_root) + local path = Path:new(hunk.file):make_relative(worktree_root) table.insert(diff_content, 1, string.format("+++ b/%s", path)) table.insert(diff_content, 1, string.format("--- a/%s", path)) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 95b2d5e0e..88cd772fe 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -182,25 +182,19 @@ function Ui:item_hunks(item, first_line, last_line, partial) if not item.folded and item.diff.hunks then for _, h in ipairs(item.diff.hunks) do - if h.first <= last_line and h.last >= first_line then + if h.first <= first_line and h.last >= last_line then local from, to if partial then - local cursor_offset = first_line - h.first local length = last_line - first_line - from = h.diff_from + cursor_offset + from = first_line - h.first to = from + length else from = h.diff_from + 1 to = h.diff_to end - local hunk_lines = {} - for i = from, to do - table.insert(hunk_lines, item.diff.lines[i]) - end - -- local conflict = false -- for _, n in ipairs(conflict_markers) do -- if from <= n and n <= to then @@ -214,7 +208,6 @@ function Ui:item_hunks(item, first_line, last_line, partial) to = to, __index = h, hunk = h, - lines = hunk_lines, -- conflict = conflict, } diff --git a/tests/specs/neogit/lib/git/index_spec.lua b/tests/specs/neogit/lib/git/index_spec.lua index 3d1be1cc6..cc0358087 100644 --- a/tests/specs/neogit/lib/git/index_spec.lua +++ b/tests/specs/neogit/lib/git/index_spec.lua @@ -10,17 +10,15 @@ local function run_with_hunk(hunk, from, to, reverse) local header_matches = vim.fn.matchlist(lines[1], "@@ -\\(\\d\\+\\),\\(\\d\\+\\) +\\(\\d\\+\\),\\(\\d\\+\\) @@") return generate_patch_from_selection({ - name = "test.txt", - absolute_path = "test.txt", - diff = { lines = lines }, - }, { first = 1, last = #lines, index_from = header_matches[2], index_len = header_matches[3], diff_from = diff_from, diff_to = #lines, - }, diff_from + from, diff_from + to, reverse) + lines = vim.list_slice(lines, 2), + file = "test.txt", + }, { from = from, to = to, reverse = reverse }) end describe("patch creation", function() From cbf82b13a88324767a69a12145878518ea177981 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:34:49 +0100 Subject: [PATCH 200/437] Add ability to revert hunk --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/lib/git/revert.lua | 5 +++++ lua/neogit/popups/revert/actions.lua | 8 ++++++++ lua/neogit/popups/revert/init.lua | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index cebc9b45e..ec9b4313e 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -281,7 +281,7 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid } } + p { commits = { self.commit_info.oid }, hunk = self.buffer.ui:get_hunk_or_filename_under_cursor() } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 54064b3b7..ac67b52ee 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -8,6 +8,11 @@ function M.commits(commits, args) return git.cli.revert.no_commit.arg_list(util.merge(args, commits)).call({ pty = true }).code == 0 end +function M.hunk(hunk, _) + local patch = git.index.generate_patch(hunk, { reverse = true }) + git.index.apply(patch, { reverse = true }) +end + function M.continue() git.cli.revert.continue.call() end diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 93057766a..002f54365 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -72,6 +72,14 @@ function M.changes(popup) git.revert.commits(commits, popup:get_arguments()) end +function M.hunk(popup) + local hunk = popup.state.env.hunk + if hunk == nil then + return + end + git.revert.hunk(hunk.hunk, popup:get_arguments()) +end + function M.continue() git.revert.continue() end diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index 092f16596..abc87d9c2 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,6 +23,7 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) + :action_if(not in_progress, "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) From c5b8264f48548b94822f25096891a9615bf89855 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 14 Dec 2024 20:50:43 +0100 Subject: [PATCH 201/437] Improve "revert" workflow: - Use finder buffer instead of commit select view. To select a specific commit, or a range of commits, use the log/reflog view. - Require confirmation when aborting a revert in progress - If multiple commits are selected in the log view, don't pompt the user, otherwise, prompt the user. --- lua/neogit/lib/git/revert.lua | 10 +++++- lua/neogit/popups/revert/actions.lua | 48 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 54064b3b7..557627163 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -4,8 +4,16 @@ local util = require("neogit.lib.util") ---@class NeogitGitRevert local M = {} +---@param commits string[] +---@param args string[] +---@return boolean, string|nil function M.commits(commits, args) - return git.cli.revert.no_commit.arg_list(util.merge(args, commits)).call({ pty = true }).code == 0 + local result = git.cli.revert.no_commit.arg_list(util.merge(args, commits)).call { pty = true } + if result.code == 0 then + return true, "" + else + return false, result.stdout[1] + end end function M.continue() diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 93057766a..9589287ea 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -3,23 +3,22 @@ local M = {} local git = require("neogit.lib.git") local client = require("neogit.client") local notification = require("neogit.lib.notification") -local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") +local input = require("neogit.lib.input") +local util = require("neogit.lib.util") +local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") ---@param popup any ----@return CommitLogEntry[] -local function get_commits(popup) - local commits - if #popup.state.env.commits > 0 then - commits = popup.state.env.commits +---@param thing string +---@return string[] +local function get_commits(popup, thing) + if #popup.state.env.commits > 1 then + return popup.state.env.commits else - commits = CommitSelectViewBuffer.new( - git.log.list { "--max-count=256" }, - git.remote.list(), - "Select one or more commits to revert with , or to abort" - ):open_async() - end + local refs = + util.merge(popup.state.env.commits, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - return commits or {} + return { FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = "Revert " .. thing } } + end end local function build_commit_message(commits) @@ -34,17 +33,15 @@ local function build_commit_message(commits) end function M.commits(popup) - local commits = get_commits(popup) + local commits = get_commits(popup, "commits") if #commits == 0 then return end local args = popup:get_arguments() - - local success = git.revert.commits(commits, args) - + local success, msg = git.revert.commits(commits, args) if not success then - notification.error("Revert failed. Resolve conflicts before continuing") + notification.error("Revert failed with " .. msg) return end @@ -64,12 +61,13 @@ function M.commits(popup) end function M.changes(popup) - local commits = get_commits(popup) - if not commits[1] then - return + local commits = get_commits(popup, "changes") + if #commits > 0 then + local success, msg = git.revert.commits(commits, popup:get_arguments()) + if not success then + notification.error("Revert failed with " .. msg) + end end - - git.revert.commits(commits, popup:get_arguments()) end function M.continue() @@ -81,7 +79,9 @@ function M.skip() end function M.abort() - git.revert.abort() + if input.get_permission("Abort revert?") then + git.revert.abort() + end end return M From 5df381ea2d668e0dc3e7ceae7b096efad73d6aec Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 14 Dec 2024 22:30:11 +0100 Subject: [PATCH 202/437] Fix: Ensure the buffer's header is only the width of the window. Fixes the log view header overlapping the commit view --- lua/neogit/lib/buffer.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index ec4bb619b..00ed58b35 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -583,6 +583,14 @@ function Buffer:line_count() return api.nvim_buf_line_count(self.handle) end +function Buffer:resize_header() + if not self.header_win_handle then + return + end + + api.nvim_win_set_width(self.header_win_handle, fn.winwidth(self.win_handle)) +end + ---@param text string ---@param scroll boolean function Buffer:set_header(text, scroll) @@ -603,7 +611,8 @@ function Buffer:set_header(text, scroll) -- Display the buffer in a floating window local winid = api.nvim_open_win(buf, false, { relative = "win", - width = vim.o.columns, + win = self.win_handle, + width = fn.winwidth(self.win_handle), height = 1, row = 0, col = 0, @@ -624,6 +633,15 @@ function Buffer:set_header(text, scroll) vim.api.nvim_feedkeys(keys, "n", false) end) end + + -- Ensure the header only covers the intended window. + api.nvim_create_autocmd("WinResized", { + callback = function() + self:resize_header() + end, + buffer = self.handle, + group = self.autocmd_group, + }) end ---@class BufferConfig From 7f3145af0e461dffcd12630266ca43021f4851eb Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 14 Dec 2024 22:30:49 +0100 Subject: [PATCH 203/437] Use fn instead of vim.fn here --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 00ed58b35..6991cf465 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -186,7 +186,7 @@ function Buffer:move_top_line(line) return end - if vim.o.lines < vim.fn.line("$") then + if vim.o.lines < fn.line("$") then return end From 22de67b9b9e7ed50da14b937e3464dce0c4fc95f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 16 Dec 2024 15:06:34 +0100 Subject: [PATCH 204/437] Don't set "foldminlines" for internal buffers. It doesn't seem to be needed. Fixes: https://github.com/NeogitOrg/neogit/issues/1521 --- lua/neogit/lib/buffer.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 6991cf465..31452aab7 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -757,7 +757,6 @@ function Buffer.create(config) buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) - buffer:set_window_option("foldminlines", 0) buffer:set_window_option("foldtext", "") buffer:set_window_option("foldcolumn", "0") buffer:set_window_option("listchars", "") From a060ba07ec31c34930173ae27bc781fcbbc1748d Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Mon, 16 Dec 2024 15:48:02 +0100 Subject: [PATCH 205/437] Change name from hunk to item Reads a bit better Change-Id: I2c5aaa5b68a140de49bd38c10b2dca4b10e15efe --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/popups/revert/actions.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 136004f0e..9561771d4 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -290,7 +290,7 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid }, hunk = self.buffer.ui:get_hunk_or_filename_under_cursor() } + p { commits = { self.commit_info.oid }, item = self.buffer.ui:get_hunk_or_filename_under_cursor() } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 31a680964..1222488da 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -71,11 +71,11 @@ function M.changes(popup) end function M.hunk(popup) - local hunk = popup.state.env.hunk - if hunk == nil then + local item = popup.state.env.item + if item == nil then return end - git.revert.hunk(hunk.hunk, popup:get_arguments()) + git.revert.hunk(item.hunk, popup:get_arguments()) end function M.continue() From c7696869acd2cad6c9db316a5519af4bccee6c36 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Mon, 16 Dec 2024 15:48:24 +0100 Subject: [PATCH 206/437] Only show hunk actions if on a hunk Change-Id: I266a58026636cee04e1d825545df0f047324c068 --- lua/neogit/popups/revert/actions.lua | 6 +----- lua/neogit/popups/revert/init.lua | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 1222488da..f1e0ecff1 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -71,11 +71,7 @@ function M.changes(popup) end function M.hunk(popup) - local item = popup.state.env.item - if item == nil then - return - end - git.revert.hunk(item.hunk, popup:get_arguments()) + git.revert.hunk(popup.state.env.item.hunk, popup:get_arguments()) end function M.continue() diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index abc87d9c2..d5863165b 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,7 +23,7 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) - :action_if(not in_progress, "h", "Hunk", actions.hunk) + :action_if(((not in_progress) and env.item ~= nil), "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) From e9949c0ea8ea594ad746557f66ab05748c18917d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 16 Dec 2024 15:21:36 +0100 Subject: [PATCH 207/437] modify naming in help popup to be a little more clear --- lua/neogit/popups/help/actions.lua | 4 ++-- spec/popups/help_popup_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 0249d03df..cede6c0c6 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -115,10 +115,10 @@ end M.actions = function() return present { { "Stage", "Stage", NONE }, - { "StageUnstaged", "Stage-Unstaged", NONE }, + { "StageUnstaged", "Stage unstaged", NONE }, { "StageAll", "Stage all", NONE }, { "Unstage", "Unstage", NONE }, - { "UnstageStaged", "Unstage-Staged", NONE }, + { "UnstageStaged", "Unstage all", NONE }, { "Discard", "Discard", NONE }, { "Untrack", "Untrack", NONE }, } diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index 0e673f10a..8b3846a4d 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -11,9 +11,9 @@ " $ History M Remote Stage all Refresh ", " A Cherry Pick m Merge K Untrack Go to file ", " b Branch P Push s Stage Toggle ", - " B Bisect p Pull S Stage-Unstaged ", + " B Bisect p Pull S Stage unstaged ", " c Commit Q Command u Unstage ", - " d Diff r Rebase U Unstage-Staged ", + " d Diff r Rebase U Unstage all ", " f Fetch t Tag x Discard ", " I Init v Revert ", " i Ignore w Worktree ", From 913d14bed1b002e6e60479b01f9c4259e163bd77 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 20:20:33 +0100 Subject: [PATCH 208/437] fix: reverting a commit, when using GPG that has not yet received a password, will now ask for a password. --- lua/neogit/lib/git/cli.lua | 2 ++ lua/neogit/lib/git/revert.lua | 2 +- lua/neogit/popups/revert/actions.lua | 16 ++++------------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index df89df123..7396975ac 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -176,6 +176,7 @@ local runner = require("neogit.runner") ---@class GitCommandRevert: GitCommandBuilder ---@field no_commit self +---@field no_edit self ---@field continue self ---@field skip self ---@field abort self @@ -600,6 +601,7 @@ local configurations = { flags = { no_commit = "--no-commit", continue = "--continue", + no_edit = "--no-edit", skip = "--skip", abort = "--abort", }, diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 96e48c053..b84ee8921 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -22,7 +22,7 @@ function M.hunk(hunk, _) end function M.continue() - git.cli.revert.continue.call() + git.cli.revert.continue.no_edit.call { pty = true } end function M.skip() diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index f1e0ecff1..cc0004890 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -1,5 +1,6 @@ local M = {} +local config = require("neogit.config") local git = require("neogit.lib.git") local client = require("neogit.client") local notification = require("neogit.lib.notification") @@ -21,17 +22,6 @@ local function get_commits(popup, thing) end end -local function build_commit_message(commits) - local message = {} - table.insert(message, string.format("Revert %d commits\n", #commits)) - - for _, commit in ipairs(commits) do - table.insert(message, string.format("%s '%s'", commit:sub(1, 7), git.log.message(commit))) - end - - return table.concat(message, "\n") -end - function M.commits(popup) local commits = get_commits(popup, "commits") if #commits == 0 then @@ -45,7 +35,7 @@ function M.commits(popup) return end - local commit_cmd = git.cli.commit.no_verify.with_message(build_commit_message(commits)) + local commit_cmd = git.cli.commit.no_verify if vim.tbl_contains(args, "--edit") then commit_cmd = commit_cmd.edit else @@ -54,9 +44,11 @@ function M.commits(popup) client.wrap(commit_cmd, { autocmd = "NeogitRevertComplete", + interactive = true, msg = { success = "Reverted", }, + show_diff = config.values.commit_editor.show_staged_diff, }) end From 4fa9f8cc3345e8e581f8cfc7358491e6d39dd5e3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 20:21:11 +0100 Subject: [PATCH 209/437] Revert "Don't set "foldminlines" for internal buffers. It doesn't seem to be" This reverts commit 22de67b9b9e7ed50da14b937e3464dce0c4fc95f. --- lua/neogit/lib/buffer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 31452aab7..6991cf465 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -757,6 +757,7 @@ function Buffer.create(config) buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) + buffer:set_window_option("foldminlines", 0) buffer:set_window_option("foldtext", "") buffer:set_window_option("foldcolumn", "0") buffer:set_window_option("listchars", "") From 774db62a74cdd4d09f8a8ea4a2b69d6efee3f8ae Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 21:14:15 +0100 Subject: [PATCH 210/437] Clean up tags annotations --- lua/neogit/popups/tag/actions.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index 363a8c374..f50394269 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -11,6 +11,7 @@ local function fire_tag_event(pattern, data) vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false, data = data }) end +---@param popup PopupData function M.create_tag(popup) local tag_input = input.get_user_input("Create tag", { strip_spaces = true }) if not tag_input then @@ -48,7 +49,7 @@ function M.create_release(_) end --- If there are multiple tags then offer to delete those. --- Otherwise prompt for a single tag to be deleted. --- git tag -d TAGS ----@param _ table +---@param _ PopupData function M.delete(_) local tags = FuzzyFinderBuffer.new(git.tag.list()):open_async { allow_multi = true } if #(tags or {}) == 0 then @@ -64,22 +65,21 @@ function M.delete(_) end --- Prunes differing tags from local and remote ----@param _ table +---@param _ PopupData function M.prune(_) + local tags = git.tag.list() + if #tags == 0 then + notification.info("No tags found") + return + end + local selected_remote = FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "Prune tags using remote", } - if (selected_remote or "") == "" then return end - local tags = git.tag.list() - if #tags == 0 then - notification.info("No tags found") - return - end - notification.info("Fetching remote tags...") local r_out = git.tag.list_remote(selected_remote) local remote_tags = {} @@ -96,7 +96,7 @@ function M.prune(_) notification.delete_all() if #l_tags == 0 and #r_tags == 0 then - notification.info("Same tags exist locally and remotely") + notification.info("Tags are in sync - nothing to do.") return end From c5d2231dd5229506e117648386bf8268a5acf930 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 21:14:43 +0100 Subject: [PATCH 211/437] Decorate commit OID's when passed into fuzzy finder, if possible --- lua/neogit/buffers/fuzzy_finder.lua | 12 ++++++++++++ lua/neogit/lib/git/log.lua | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lua/neogit/buffers/fuzzy_finder.lua b/lua/neogit/buffers/fuzzy_finder.lua index ae90a5df3..6950e7d7f 100644 --- a/lua/neogit/buffers/fuzzy_finder.lua +++ b/lua/neogit/buffers/fuzzy_finder.lua @@ -1,4 +1,5 @@ local Finder = require("neogit.lib.finder") +local git = require("neogit.lib.git") local function buffer_height(count) if count < (vim.o.lines / 2) then @@ -24,6 +25,17 @@ function M.new(list) list = list, } + -- If the first item in the list is an git OID, decorate it + if type(list[1]) == "string" and list[1]:match("^%x%x%x%x%x%x%x") then + local oid = table.remove(list, 1) + local ok, result = pcall(git.log.decorate, oid) + if ok then + table.insert(list, 1, result) + else + table.insert(list, 1, oid) + end + end + setmetatable(instance, { __index = M }) return instance diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index c5e5952fb..b9fef523e 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -553,4 +553,19 @@ M.abbreviated_size = util.memoize(function() end end, { timeout = math.huge }) +function M.decorate(oid) + local result = git.cli.log.format("%D").max_count(1).args(oid).call().stdout + + if result[1] == nil then + return oid + else + local decorated_ref = vim.split(result[1], ",")[1] + if decorated_ref:match("%->") then + return oid + else + return decorated_ref + end + end +end + return M From 375b2e532f9e715e4a6e55a5f5c0d60fa0c91bb7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 21:21:26 +0100 Subject: [PATCH 212/437] Add more events to auto-refresh status buffer --- lua/neogit/buffers/status/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 948ce5e39..cd5bfb183 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -231,6 +231,12 @@ function M:open(kind) ["NeogitStash"] = function() self:dispatch_refresh(nil, "stash") end, + ["NeogitRevertComplete"] = function() + self:dispatch_refresh(nil, "revert") + end, + ["NeogitCherryPick"] = function() + self:dispatch_refresh(nil, "cherry_pick") + end, }, } From eaec943be1461b10136d24f5a3f94a9b96609059 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 21:38:36 +0100 Subject: [PATCH 213/437] Add e2e specs to makefile --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 6c1a42139..19efec42a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ test: TEMP_DIR=$$TEMP_DIR TEST_FILES=$$TEST_FILES GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null NVIM_APPNAME=neogit-test nvim --headless -S "./tests/init.lua" +specs: + bundle install && CI=1 bundle exec rspec --format Fuubar + lint: selene --config selene/config.toml lua typos From 66ea9e259aa8ebd52b82720c67df660bc66d8342 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 22:05:29 +0100 Subject: [PATCH 214/437] Add "rename" action (`R` from status buffer) --- lua/neogit/buffers/status/actions.lua | 35 +++++++++++++++++++++++++++ lua/neogit/buffers/status/init.lua | 1 + lua/neogit/config.lua | 1 + lua/neogit/lib/git/cli.lua | 5 ++++ lua/neogit/lib/git/files.lua | 7 ++++++ 5 files changed, 49 insertions(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index ee15ed14e..98e1d6347 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -966,6 +966,41 @@ M.n_init_repo = function(_self) end end +---@param self StatusBuffer +M.n_rename = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + local paths = git.files.all_tree() + + if + selection.item + and selection.item.escaped_path + and git.files.is_tracked(selection.item.escaped_path) + then + paths = util.deduplicate(util.merge({ selection.item.escaped_path }, paths)) + end + + local selected = FuzzyFinderBuffer.new(paths):open_async { prompt_prefix = "Rename file" } + if (selected or "") == "" then + return + end + + local destination = input.get_user_input("Move to", { completion = "dir", prepend = selected }) + if (destination or "") == "" then + return + end + + assert(destination, "must have a destination") + local success = git.files.move(selected, destination) + + if not success then + notification.warn("Renaming failed") + end + + self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_rename") + end) +end + ---@param self StatusBuffer M.n_untrack = function(self) return a.void(function() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index cd5bfb183..5d33ee980 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -145,6 +145,7 @@ function M:open(kind) [mappings["MoveDown"]] = self:_action("n_down"), [mappings["MoveUp"]] = self:_action("n_up"), [mappings["Untrack"]] = self:_action("n_untrack"), + [mappings["Rename"]] = self:_action("n_rename"), [mappings["Toggle"]] = self:_action("n_toggle"), [mappings["Close"]] = self:_action("n_close"), [mappings["OpenOrScrollDown"]] = self:_action("n_open_or_scroll_down"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index e9a5c5dcd..b0af37216 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -616,6 +616,7 @@ function M.get_default_values() [""] = "StageAll", ["u"] = "Unstage", ["K"] = "Untrack", + ["R"] = "Rename", ["U"] = "UnstageStaged", ["y"] = "ShowRefs", ["$"] = "CommandHistory", diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 7396975ac..7342098db 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -67,6 +67,8 @@ local runner = require("neogit.runner") ---@class GitCommandRm: GitCommandBuilder ---@field cached self +---@class GitCommandMove: GitCommandBuilder + ---@class GitCommandStatus: GitCommandBuilder ---@field short self ---@field branch self @@ -369,6 +371,7 @@ local runner = require("neogit.runner") ---@field verify-commit GitCommandVerifyCommit ---@field worktree GitCommandWorktree ---@field write-tree GitCommandWriteTree +---@field mv GitCommandMove ---@field worktree_root fun(dir: string):string ---@field git_dir fun(dir: string):string ---@field worktree_git_dir fun(dir: string):string @@ -607,6 +610,8 @@ local configurations = { }, }, + mv = config {}, + checkout = config { short_opts = { b = "-b", diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index 1dadb8e13..e053573ab 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -64,4 +64,11 @@ function M.untrack(paths) return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).code == 0 end +---@param from string +---@param to string +---@return boolean +function M.move(from, to) + return git.cli.mv.args(from, to).call().code == 0 +end + return M From a919e6382ee604f01d85fb9ecc023b82473af798 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 17 Dec 2024 22:14:24 +0100 Subject: [PATCH 215/437] Add prompt prefix when deleting branch. If a branch already exists when spinning off/out change, abort --- lua/neogit/popups/branch/actions.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 4f4b10557..352379e7a 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -60,8 +60,12 @@ local function spin_off_branch(checkout) return end + if not git.branch.create(name) then + notification.warn("Branch " .. name .. " already exists.") + return + end + fire_branch_event("NeogitBranchCreate", { branch_name = name }) - git.branch.create(name) local current_branch_name = git.branch.current_full_name() @@ -280,7 +284,8 @@ end function M.delete_branch(popup) local options = util.deduplicate(util.merge({ popup.state.env.ref_name }, git.refs.list_branches())) - local selected_branch = FuzzyFinderBuffer.new(options):open_async { refocus_status = false } + local selected_branch = FuzzyFinderBuffer.new(options) + :open_async { prompt_prefix = "Delete branch", refocus_status = false } if not selected_branch then return end From da309da42fadb1d6a655f272cacf9dbb6a74558b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 18 Dec 2024 20:47:15 +0100 Subject: [PATCH 216/437] Added: Mappings to navigate between sections in the status buffer --- README.md | 2 ++ lua/neogit/buffers/status/actions.lua | 25 +++++++++++++++++++++++++ lua/neogit/buffers/status/init.lua | 2 ++ lua/neogit/config.lua | 2 ++ 4 files changed, 31 insertions(+) diff --git a/README.md b/README.md index 591d8168a..31d6adc93 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,8 @@ neogit.setup { ["]c"] = "OpenOrScrollDown", [""] = "PeekUp", [""] = "PeekDown", + [""] = "NextSection", + [""] = "PreviousSection", }, }, } diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 98e1d6347..13cb3b87c 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1479,4 +1479,29 @@ M.n_command = function(self) }) end) end + +---@param self StatusBuffer +M.n_next_section = function(self) + return function() + local section = self.buffer.ui:get_current_section() + if section then + local position = section.position.row_end + 2 + self.buffer:move_cursor(position) + end + end +end + +---@param self StatusBuffer +M.n_prev_section = function(self) + return function() + local section = self.buffer.ui:get_current_section() + if section then + local prev_section = self.buffer.ui:get_current_section(section.position.row_start - 1) + if prev_section then + self.buffer:move_cursor(prev_section.position.row_start + 1) + end + end + end +end + return M diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 5d33ee980..fbfb97ab7 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -171,6 +171,8 @@ function M:open(kind) [mappings["TabOpen"]] = self:_action("n_tab_open"), [mappings["SplitOpen"]] = self:_action("n_split_open"), [mappings["VSplitOpen"]] = self:_action("n_vertical_split_open"), + [mappings["NextSection"]] = self:_action("n_next_section"), + [mappings["PreviousSection"]] = self:_action("n_prev_section"), [popups.mapping_for("BisectPopup")] = self:_action("n_bisect_popup"), [popups.mapping_for("BranchPopup")] = self:_action("n_branch_popup"), [popups.mapping_for("CherryPickPopup")] = self:_action("n_cherry_pick_popup"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index b0af37216..1b1bd841c 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -633,6 +633,8 @@ function M.get_default_values() ["]c"] = "OpenOrScrollDown", [""] = "PeekUp", [""] = "PeekDown", + [""] = "NextSection", + [""] = "PreviousSection", }, }, } From af51f59a649e7a6394a3ebe8a6161003e81a21f6 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Wed, 18 Dec 2024 22:39:10 +0000 Subject: [PATCH 217/437] vimdoc: setup section to match README This updates neogit's vim documentation with setup() section with contents sourced from the README file. --- doc/neogit.txt | 227 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 153 insertions(+), 74 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index c6d4ad1d5..578ce42e4 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -82,158 +82,237 @@ to Neovim users. ============================================================================== 2. Plugin Setup *neogit_setup_plugin* -TODO: Detail what these do + >lua + local neogit = require("neogit") - use_default_keymaps = true, + neogit.setup { + -- Hides the hints at the top of the status buffer disable_hint = false, + -- Disables changing the buffer highlights based on where the cursor is. disable_context_highlighting = false, + -- Disables signs for sections/items/hunks disable_signs = false, + -- Changes what mode the Commit Editor starts in. `true` will leave nvim in normal mode, `false` will change nvim to + -- insert mode, and `"auto"` will change nvim to insert mode IF the commit message is empty, otherwise leaving it in + -- normal mode. + disable_insert_on_commit = "auto", + -- When enabled, will watch the `.git/` directory for changes and refresh the status buffer in response to filesystem + -- events. + filewatcher = { + interval = 1000, + enabled = true, + }, + -- "ascii" is the graph the git CLI generates + -- "unicode" is the graph like https://github.com/rbong/vim-flog + -- "kitty" is the graph like https://github.com/isakbm/gitgraph.nvim - use https://github.com/rbong/flog-symbols if you don't use Kitty graph_style = "ascii", + -- Show relative date by default. When set, use `strftime` to display dates commit_date_format = nil, log_date_format = nil, - filewatcher = { - enabled = true, + -- Used to generate URL's for branch popup action "pull request". + git_services = { + ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", + ["bitbucket.org"] = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", + ["gitlab.com"] = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", + ["azure.com"] = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", }, + -- Allows a different telescope sorter. Defaults to 'fuzzy_with_index_bias'. The example below will use the native fzf + -- sorter instead. By default, this function returns `nil`. telescope_sorter = function() - return nil + return require("telescope").extensions.fzf.native_fzf_sorter() end, - git_services = { - ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", - ["bitbucket.org"] = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", - ["gitlab.com"] = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", - ["azure.com"] = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + -- Persist the values of switches/options within and across sessions + remember_settings = true, + -- Scope persisted settings on a per-project basis + use_per_project_settings = true, + -- Table of settings to never persist. Uses format "Filetype--cli-value" + ignored_settings = { + "NeogitPushPopup--force-with-lease", + "NeogitPushPopup--force", + "NeogitPullPopup--rebase", + "NeogitCommitPopup--allow-empty", + "NeogitRevertPopup--no-edit", }, + -- Configure highlight group features highlight = { - italic = true, - bold = true, - underline = true, + italic = true, + bold = true, + underline = true }, - disable_insert_on_commit = "auto", - use_per_project_settings = true, - show_head_commit_hash = true, - remember_settings = true, - fetch_after_checkout = false, + -- Set to false if you want to be responsible for creating _ALL_ keymappings + use_default_keymaps = true, + -- Neogit refreshes its internal state after specific events, which can be expensive depending on the repository size. + -- Disabling `auto_refresh` will make it so you have to manually refresh the status after you open it. auto_refresh = true, + -- Value used for `--sort` option for `git branch` command + -- By default, branches will be sorted by commit date descending + -- Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt + -- Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options sort_branches = "-committerdate", + -- Default for new branch name prompts initial_branch_name = "", + -- Change the default way of opening neogit kind = "tab", + -- Disable line numbers disable_line_numbers = true, + -- Disable relative line numbers + disable_relative_line_numbers = true, -- The time after which an output console is shown for slow running commands console_timeout = 2000, -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, - -- If `auto_show_console` is enabled, specify "output" (default) to show - -- the console always, or "error" to auto-show the console only on error - auto_show_console_on = "output", + -- Automatically close the console if the process exits with a 0 (success) status + auto_close_console = true, notification_icon = "󰊢", status = { - recent_commit_count = 10, - HEAD_folded = false, + show_head_commit_hash = true, + recent_commit_count = 10, + HEAD_padding = 10, + HEAD_folded = false, + mode_padding = 3, + mode_text = { + M = "modified", + N = "new file", + A = "added", + D = "deleted", + C = "copied", + U = "updated", + R = "renamed", + DD = "unmerged", + AU = "unmerged", + UD = "unmerged", + UA = "unmerged", + DU = "unmerged", + AA = "unmerged", + UU = "unmerged", + ["?"] = "", + }, }, commit_editor = { - kind = "tab", + kind = "tab", + show_staged_diff = true, + -- Accepted values: + -- "split" to show the staged diff below the commit editor + -- "vsplit" to show it to the right + -- "split_above" Like :top split + -- "vsplit_left" like :vsplit, but open to the left + -- "auto" "vsplit" if window would have 80 cols, otherwise "split" + staged_diff_split_kind = "split", + spell_check = true, }, commit_select_view = { - kind = "tab", + kind = "tab", }, commit_view = { - kind = "vsplit", - verify_commit = vim.fn.executable("gpg") == 1, + kind = "vsplit", + verify_commit = vim.fn.executable("gpg") == 1, -- Can be set to true or false, otherwise we try to find the binary }, log_view = { - kind = "tab", + kind = "tab", }, rebase_editor = { - kind = "auto", + kind = "auto", }, reflog_view = { - kind = "tab", + kind = "tab", }, merge_editor = { - kind = "auto", + kind = "auto", }, description_editor = { - kind = "auto", + kind = "auto", }, tag_editor = { - kind = "auto", + kind = "auto", }, preview_buffer = { - kind = "split", + kind = "floating_console", }, popup = { - kind = "split", + kind = "split", + }, + stash = { + kind = "tab", }, refs_view = { - kind = "tab", + kind = "tab", }, signs = { - hunk = { "", "" }, - item = { ">", "v" }, - section = { ">", "v" }, + -- { CLOSED, OPENED } + hunk = { "", "" }, + item = { ">", "v" }, + section = { ">", "v" }, }, + -- Each Integration is auto-detected through plugin presence, however, it can be disabled by setting to `false` integrations = { - telescope = nil, - diffview = nil, - fzf_lua = nil, - mini_pick = nil, + -- If enabled, use telescope for menu selection rather than vim.ui.select. + -- Allows multi-select and some things that vim.ui.select doesn't. + telescope = nil, + -- Neogit only provides inline diffs. If you want a more traditional way to look at diffs, you can use `diffview`. + -- The diffview integration enables the diff popup. + -- + -- Requires you to have `sindrets/diffview.nvim` installed. + diffview = nil, + + -- If enabled, uses fzf-lua for menu selection. If the telescope integration + -- is also selected then telescope is used instead + -- Requires you to have `ibhagwan/fzf-lua` installed. + fzf_lua = nil, + + -- If enabled, uses mini.pick for menu selection. If the telescope integration + -- is also selected then telescope is used instead + -- Requires you to have `echasnovski/mini.pick` installed. + mini_pick = nil, }, sections = { - sequencer = { + -- Reverting/Cherry Picking + sequencer = { folded = false, hidden = false, - }, - bisect = { - folded = false, - hidden = false, - }, - untracked = { + }, + untracked = { folded = false, hidden = false, - }, - unstaged = { + }, + unstaged = { folded = false, hidden = false, - }, - staged = { + }, + staged = { folded = false, hidden = false, - }, - stashes = { + }, + stashes = { folded = true, hidden = false, - }, - unpulled_upstream = { + }, + unpulled_upstream = { folded = true, hidden = false, - }, - unmerged_upstream = { + }, + unmerged_upstream = { folded = false, hidden = false, - }, - unpulled_pushRemote = { + }, + unpulled_pushRemote = { folded = true, hidden = false, - }, - unmerged_pushRemote = { + }, + unmerged_pushRemote = { folded = false, hidden = false, - }, - recent = { + }, + recent = { folded = true, hidden = false, - }, - rebase = { + }, + rebase = { folded = true, hidden = false, - }, + }, }, - ignored_settings = { - "NeogitPushPopup--force-with-lease", - "NeogitPushPopup--force", - "NeogitPullPopup--rebase", - "NeogitCommitPopup--allow-empty", } + > ============================================================================== Commit Signing / GPG Integration *neogit_setup_gpg* From 5903dbac699bfe68c7f00ac1e5e457242c8fcb99 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Wed, 18 Dec 2024 22:57:00 +0000 Subject: [PATCH 218/437] vimdoc: add Events section --- doc/neogit.txt | 80 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 578ce42e4..6b77e034f 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -477,8 +477,86 @@ The following mappings can all be customized via the setup function. ============================================================================== 4. Events *neogit_events* -(TODO) +The following events are emitted by Neogit: + + Event Description ~ + NeogitStatusRefreshed Status has been reloaded ~ + + Event Data: {} ~ + + NeogitCommitComplete Commit has been created ~ + + Event Data: {} ~ + + NeogitPushComplete Push has completed ~ + + Event Data: {} ~ + + NeogitPullComplete Pull has completed ~ + + Event Data: {} ~ + + NeogitFetchComplete Fetch has completed ~ + + Event Data: {} ~ + + NeogitBranchCreate Branch was created, starting from ~ + `base` ~ + + Event Data: ~ + { branch_name: string, base: string? } ~ + + NeogitBranchDelete Branch was deleted ~ + + Event Data: { branch_name: string } ~ + + NeogitBranchCheckout Branch was checked out ~ + + Event Data: { branch_name: string } ~ + + NeogitBranchReset Branch was reset to a commit/branch ~ + + Event Data: ~ + { branch_name: string, resetting_to: string } ~ + + NeogitBranchRename Branch was renamed ~ + + Event Data: ~ + { branch_name: string, new_name: string } ~ + + NeogitRebase A rebase finished ~ + + Event Data: ~ + { commit: string, status: "ok" | "conflict" } ~ + + NeogitReset A branch was reset to a certain commit ~ + + Event Data: ~ + { commit: string, mode: "soft" | ~ + "mixed" | "hard" | "keep" | "index" } ~ + + NeogitTagCreate A tag was placed on a certain commit ~ + + Event Data: { name: string, ref: string } ~ + + NeogitTagDelete A tag was removed ~ + + Event Data: { name: string } ~ + + NeogitCherryPick One or more commits were cherry-picked ~ + + Event Data: { commits: string[] } ~ + + NeogitMerge A merge finished ~ + + Event Data: ~ + { branch: string, args: string[], ~ + status: "ok" | "conflict" } ~ + + NeogitStash A stash finished ~ + + Event Data: { success: boolean } ~ ============================================================================== 5. Highlights *neogit_highlights* From af5e785de9846394201dea5979e371956ebd38c0 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Wed, 18 Dec 2024 23:00:59 +0000 Subject: [PATCH 219/437] vimdoc: add highlighting for highlighting section... --- doc/neogit.txt | 59 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 6b77e034f..90bf911d8 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -561,35 +561,36 @@ The following events are emitted by Neogit: 5. Highlights *neogit_highlights* To provide a custom color palette directly to the plugin, you can use the -`config.highlight` table with the following signature: - ----@class HighlightOptions ----@field italic? boolean ----@field bold? boolean ----@field underline? boolean ----@field bg0? string Darkest background color ----@field bg1? string Second darkest background color ----@field bg2? string Second lightest background color ----@field bg3? string Lightest background color ----@field grey? string middle grey shade for foreground ----@field white? string Foreground white (main text) ----@field red? string Foreground red ----@field bg_red? string Background red ----@field line_red? string Cursor line highlight for red regions ----@field orange? string Foreground orange ----@field bg_orange? string background orange ----@field yellow? string Foreground yellow ----@field bg_yellow? string background yellow ----@field green? string Foreground green ----@field bg_green? string Background green ----@field line_green? string Cursor line highlight for green regions ----@field cyan? string Foreground cyan ----@field bg_cyan? string Background cyan ----@field blue? string Foreground blue ----@field bg_blue? string Background blue ----@field purple? string Foreground purple ----@field bg_purple? string Background purple ----@field md_purple? string Background medium purple +`config.highlight` table with the following signature: >lua + + ---@class HighlightOptions + ---@field italic? boolean + ---@field bold? boolean + ---@field underline? boolean + ---@field bg0? string Darkest background color + ---@field bg1? string Second darkest background color + ---@field bg2? string Second lightest background color + ---@field bg3? string Lightest background color + ---@field grey? string middle grey shade for foreground + ---@field white? string Foreground white (main text) + ---@field red? string Foreground red + ---@field bg_red? string Background red + ---@field line_red? string Cursor line highlight for red regions + ---@field orange? string Foreground orange + ---@field bg_orange? string background orange + ---@field yellow? string Foreground yellow + ---@field bg_yellow? string background yellow + ---@field green? string Foreground green + ---@field bg_green? string Background green + ---@field line_green? string Cursor line highlight for green regions + ---@field cyan? string Foreground cyan + ---@field bg_cyan? string Background cyan + ---@field blue? string Foreground blue + ---@field bg_blue? string Background blue + ---@field purple? string Foreground purple + ---@field bg_purple? string Background purple + ---@field md_purple? string Background medium purple +< The following highlight groups will all be derived from this palette. From b7d6bbea7f22a7ccfb5a8a9efdf0ad70a8ccfbfe Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 20 Dec 2024 21:40:00 +0100 Subject: [PATCH 220/437] fix: and on status buffer will navigate you to/from header --- lua/neogit/buffers/status/actions.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 13cb3b87c..d6993ef00 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1487,6 +1487,8 @@ M.n_next_section = function(self) if section then local position = section.position.row_end + 2 self.buffer:move_cursor(position) + else + self.buffer:move_cursor(self.buffer.ui:first_section().first + 1) end end end @@ -1499,8 +1501,11 @@ M.n_prev_section = function(self) local prev_section = self.buffer.ui:get_current_section(section.position.row_start - 1) if prev_section then self.buffer:move_cursor(prev_section.position.row_start + 1) + return end end + + self.buffer:win_exec("norm! gg") end end From 518dc43957d9656fe52cef624f9ca66766096d27 Mon Sep 17 00:00:00 2001 From: tdoan Date: Fri, 20 Dec 2024 20:46:34 -0800 Subject: [PATCH 221/437] upd enum --- lua/neogit/config.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 1b1bd841c..2d5fb9481 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -208,6 +208,10 @@ end ---| "YankSelected" ---| "OpenOrScrollUp" ---| "OpenOrScrollDown" +---| "PeekUp" +---| "PeekDown" +---| "NextSection" +---| "PreviousSection" ---| false ---| fun() From 40c6bd7f8e85d09ef45e6a4cdc21e64c8eab05ea Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 21 Dec 2024 00:21:36 +0100 Subject: [PATCH 222/437] Only show spinner when cmdheight is > 0 --- lua/neogit/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 1b1bd841c..641bced86 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -355,7 +355,7 @@ function M.get_default_values() graph_style = "ascii", commit_date_format = nil, log_date_format = nil, - process_spinner = true, + process_spinner = vim.opt.cmdheight:get() > 0, filewatcher = { enabled = true, }, From 9489f841330931bf544365d48cf4355ba340f3ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 23 Dec 2024 23:49:46 +0100 Subject: [PATCH 223/437] Revert "Add ability to revert hunk" --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/buffers/status/actions.lua | 25 +++++++--------- lua/neogit/lib/git/diff.lua | 7 ----- lua/neogit/lib/git/index.lua | 36 +++++++++++++++-------- lua/neogit/lib/git/revert.lua | 5 ---- lua/neogit/lib/ui/init.lua | 11 +++++-- lua/neogit/popups/revert/actions.lua | 4 --- lua/neogit/popups/revert/init.lua | 1 - tests/specs/neogit/lib/git/index_spec.lua | 8 +++-- tests/specs/neogit/lib/git/log_spec.lua | 3 -- 10 files changed, 48 insertions(+), 54 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 9561771d4..90942a931 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -290,7 +290,7 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid }, item = self.buffer.ui:get_hunk_or_filename_under_cursor() } + p { commits = { self.commit_info.oid } } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index d6993ef00..0a52fbdf9 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -120,8 +120,7 @@ M.v_discard = function(self) for _, hunk in ipairs(hunks) do table.insert(invalidated_diffs, "*:" .. item.name) table.insert(patches, function() - local patch = - git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) + local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) logger.debug(("Discarding Patch: %s"):format(patch)) @@ -232,7 +231,7 @@ M.v_stage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(hunk.hunk, { from = hunk.from, to = hunk.to })) + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) end else if section.name == "unstaged" then @@ -282,10 +281,7 @@ M.v_unstage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert( - patches, - git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) - ) + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to, true)) end else table.insert(files, item.escaped_path) @@ -785,7 +781,7 @@ M.n_discard = function(self) local hunk = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] - local patch = git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) + local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) if section == "untracked" then message = "Discard hunk?" @@ -793,8 +789,9 @@ M.n_discard = function(self) local hunks = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) - local patch = - git.index.generate_patch(hunks[1], { from = hunks[1].from, to = hunks[1].to, reverse = true }) + local patch = git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) + + git.index.apply(patch, { reverse = true }) git.index.apply(patch, { reverse = true }) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } @@ -1095,7 +1092,7 @@ M.n_stage = function(self) local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = git.index.generate_patch(stagable.hunk) + local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_stage") elseif stagable.filename then @@ -1169,10 +1166,8 @@ M.n_unstage = function(self) if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = git.index.generate_patch( - unstagable.hunk, - { from = unstagable.hunk.from, to = unstagable.hunk.to, reverse = true } - ) + local patch = + git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) git.index.apply(patch, { cached = true, reverse = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_unstage") diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index d8c514975..15bea3182 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -24,14 +24,12 @@ local sha256 = vim.fn.sha256 ---@field deletions number --- ---@class Hunk ----@field file string ---@field index_from number ---@field index_len number ---@field diff_from number ---@field diff_to number ---@field first number First line number in buffer ---@field last number Last line number in buffer ----@field lines string[] --- ---@class DiffStagedStats ---@field summary string @@ -226,11 +224,6 @@ local function parse_diff(raw_diff, raw_stats) local file = build_file(header, kind) local stats = parse_diff_stats(raw_stats or {}) - util.map(hunks, function(hunk) - hunk.file = file - return hunk - end) - return { ---@type Diff kind = kind, lines = lines, diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 54837376e..35b9c8cfe 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -6,15 +6,19 @@ local util = require("neogit.lib.util") local M = {} ---Generates a patch that can be applied to index +---@param item any ---@param hunk Hunk ----@param opts table|nil +---@param from number +---@param to number +---@param reverse boolean|nil ---@return string -function M.generate_patch(hunk, opts) - opts = opts or { reverse = false, cached = false, index = false } - local reverse = opts.reverse +function M.generate_patch(item, hunk, from, to, reverse) + reverse = reverse or false - local from = opts.from or 1 - local to = opts.to or (hunk.diff_to - hunk.diff_from) + if not from and not to then + from = hunk.diff_from + 1 + to = hunk.diff_to + end assert(from <= to, string.format("from must be less than or equal to to %d %d", from, to)) if from > to then @@ -25,31 +29,35 @@ function M.generate_patch(hunk, opts) local len_start = hunk.index_len local len_offset = 0 - for k, line in pairs(hunk.lines) do - local operand, l = line:match("^([+ -])(.*)") + -- + 1 skips the hunk header, since we construct that manually afterwards + -- TODO: could use `hunk.lines` instead if this is only called with the `SelectedHunk` type + for k = hunk.diff_from + 1, hunk.diff_to do + local v = item.diff.lines[k] + local operand, line = v:match("^([+ -])(.*)") + if operand == "+" or operand == "-" then if from <= k and k <= to then len_offset = len_offset + (operand == "+" and 1 or -1) - table.insert(diff_content, line) + table.insert(diff_content, v) else -- If we want to apply the patch normally, we need to include every `-` line we skip as a normal line, -- since we want to keep that line. if not reverse then if operand == "-" then - table.insert(diff_content, " " .. l) + table.insert(diff_content, " " .. line) end -- If we want to apply the patch in reverse, we need to include every `+` line we skip as a normal line, since -- it's unchanged as far as the diff is concerned and should not be reversed. -- We also need to adapt the original line offset based on if we skip or not elseif reverse then if operand == "+" then - table.insert(diff_content, " " .. l) + table.insert(diff_content, " " .. line) end len_start = len_start + (operand == "-" and -1 or 1) end end else - table.insert(diff_content, line) + table.insert(diff_content, v) end end @@ -60,7 +68,9 @@ function M.generate_patch(hunk, opts) ) local worktree_root = git.repo.worktree_root - local path = Path:new(hunk.file):make_relative(worktree_root) + + assert(item.absolute_path, "Item is not a path") + local path = Path:new(item.absolute_path):make_relative(worktree_root) table.insert(diff_content, 1, string.format("+++ b/%s", path)) table.insert(diff_content, 1, string.format("--- a/%s", path)) diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index b84ee8921..797ca36be 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -16,11 +16,6 @@ function M.commits(commits, args) end end -function M.hunk(hunk, _) - local patch = git.index.generate_patch(hunk, { reverse = true }) - git.index.apply(patch, { reverse = true }) -end - function M.continue() git.cli.revert.continue.no_edit.call { pty = true } end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 88cd772fe..95b2d5e0e 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -182,19 +182,25 @@ function Ui:item_hunks(item, first_line, last_line, partial) if not item.folded and item.diff.hunks then for _, h in ipairs(item.diff.hunks) do - if h.first <= first_line and h.last >= last_line then + if h.first <= last_line and h.last >= first_line then local from, to if partial then + local cursor_offset = first_line - h.first local length = last_line - first_line - from = first_line - h.first + from = h.diff_from + cursor_offset to = from + length else from = h.diff_from + 1 to = h.diff_to end + local hunk_lines = {} + for i = from, to do + table.insert(hunk_lines, item.diff.lines[i]) + end + -- local conflict = false -- for _, n in ipairs(conflict_markers) do -- if from <= n and n <= to then @@ -208,6 +214,7 @@ function Ui:item_hunks(item, first_line, last_line, partial) to = to, __index = h, hunk = h, + lines = hunk_lines, -- conflict = conflict, } diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index cc0004890..7d49c28ae 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -62,10 +62,6 @@ function M.changes(popup) end end -function M.hunk(popup) - git.revert.hunk(popup.state.env.item.hunk, popup:get_arguments()) -end - function M.continue() git.revert.continue() end diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index d5863165b..092f16596 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,7 +23,6 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) - :action_if(((not in_progress) and env.item ~= nil), "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) diff --git a/tests/specs/neogit/lib/git/index_spec.lua b/tests/specs/neogit/lib/git/index_spec.lua index cc0358087..3d1be1cc6 100644 --- a/tests/specs/neogit/lib/git/index_spec.lua +++ b/tests/specs/neogit/lib/git/index_spec.lua @@ -10,15 +10,17 @@ local function run_with_hunk(hunk, from, to, reverse) local header_matches = vim.fn.matchlist(lines[1], "@@ -\\(\\d\\+\\),\\(\\d\\+\\) +\\(\\d\\+\\),\\(\\d\\+\\) @@") return generate_patch_from_selection({ + name = "test.txt", + absolute_path = "test.txt", + diff = { lines = lines }, + }, { first = 1, last = #lines, index_from = header_matches[2], index_len = header_matches[3], diff_from = diff_from, diff_to = #lines, - lines = vim.list_slice(lines, 2), - file = "test.txt", - }, { from = from, to = to, reverse = reverse }) + }, diff_from + from, diff_from + to, reverse) end describe("patch creation", function() diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index f3618c353..446eaa07e 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -96,7 +96,6 @@ describe("lib.git.log.parse", function() index_from = 692, index_len = 33, length = 40, - file = "lua/neogit/status.lua", line = "@@ -692,33 +692,28 @@ end", lines = { " ---@param first_line number", @@ -150,7 +149,6 @@ describe("lib.git.log.parse", function() index_from = 734, index_len = 14, length = 15, - file = "lua/neogit/status.lua", line = "@@ -734,14 +729,10 @@ function M.get_item_hunks(item, first_line, last_line, partial)", lines = { " setmetatable(o, o)", @@ -292,7 +290,6 @@ describe("lib.git.log.parse", function() index_len = 7, length = 9, line = "@@ -1,7 +1,9 @@", - file = "LICENSE", lines = { " MIT License", " ", From ea99ae0b835f9098ee01aa2fe8a0f47fb20cde22 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 23 Dec 2024 20:59:04 +0100 Subject: [PATCH 224/437] If checking out a branch fails, inform the user --- lua/neogit/popups/branch/actions.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 352379e7a..31ce1a528 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -24,7 +24,12 @@ local function fetch_remote_branch(target) end local function checkout_branch(target, args) - git.branch.checkout(target, args) + local result = git.branch.checkout(target, args) + if result.code > 0 then + notification.error(table.concat(result.stderr, "\n")) + return + end + fire_branch_event("NeogitBranchCheckout", { branch_name = target }) if config.values.fetch_after_checkout then From 5ec2bf32bc3622f882cd0fcd70ef1b729db32fbd Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 24 Dec 2024 00:06:47 +0100 Subject: [PATCH 225/437] remove redundant hunks/patch - it's the same as above --- lua/neogit/buffers/status/actions.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 0a52fbdf9..6ec18644c 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -786,12 +786,6 @@ M.n_discard = function(self) if section == "untracked" then message = "Discard hunk?" action = function() - local hunks = - self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) - - local patch = git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) - - git.index.apply(patch, { reverse = true }) git.index.apply(patch, { reverse = true }) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } From 028e9eee94863ed31044eb67bc5eb4d1f695d99f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 24 Dec 2024 23:32:27 +0100 Subject: [PATCH 226/437] Bump ruby version --- .ruby-version | 2 +- Gemfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.ruby-version b/.ruby-version index bea438e9a..9c25013db 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.1 +3.3.6 diff --git a/Gemfile.lock b/Gemfile.lock index 290088715..212087ae9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -125,6 +125,7 @@ GEM PLATFORMS arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 x64-mingw-ucrt x86_64-darwin-20 x86_64-linux @@ -147,7 +148,7 @@ DEPENDENCIES tmpdir RUBY VERSION - ruby 3.3.1p55 + ruby 3.3.6p108 BUNDLED WITH - 2.4.21 + 2.5.23 From 8c4316618d9f01f489a96ba0883aa9deb0b2933a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 7 Jan 2025 21:26:15 +0100 Subject: [PATCH 227/437] Change: config.process_spinner is now `false` by default. Too many issues with blinking cursors for users. --- README.md | 2 ++ lua/neogit/config.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31d6adc93..f808a1f87 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ neogit.setup { -- Show relative date by default. When set, use `strftime` to display dates commit_date_format = nil, log_date_format = nil, + -- Show message with spinning animation when a git command is running. + process_spinner = false, -- Used to generate URL's for branch popup action "pull request". git_services = { ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 46d7c2ae6..86a1bd094 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -359,7 +359,7 @@ function M.get_default_values() graph_style = "ascii", commit_date_format = nil, log_date_format = nil, - process_spinner = vim.opt.cmdheight:get() > 0, + process_spinner = false, filewatcher = { enabled = true, }, From 060a625660b2816649d83dbd6c7ab1b4345ea8bc Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 14 Jan 2025 21:53:30 +0100 Subject: [PATCH 228/437] fix: Do not pass "--path-format=absolute" to rev-parse calls. Older git versions do not support this flag and it breaks functionality for them. Also it appears not to do anything in this context. --- lua/neogit/lib/git/cli.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 7342098db..315246e41 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -981,7 +981,7 @@ local configurations = { ---@param dir string ---@return string Absolute path of current worktree local function worktree_root(dir) - local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel", "--path-format=absolute" } + local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel" } local result = vim.system(cmd, { text = true }):wait() return Path:new(vim.trim(result.stdout)):absolute() @@ -990,7 +990,7 @@ end ---@param dir string ---@return string Absolute path of `.git/` directory local function git_dir(dir) - local cmd = { "git", "-C", dir, "rev-parse", "--git-common-dir", "--path-format=absolute" } + local cmd = { "git", "-C", dir, "rev-parse", "--git-common-dir" } local result = vim.system(cmd, { text = true }):wait() return Path:new(vim.trim(result.stdout)):absolute() @@ -999,7 +999,7 @@ end ---@param dir string ---@return string Absolute path of `.git/` directory local function worktree_git_dir(dir) - local cmd = { "git", "-C", dir, "rev-parse", "--git-dir", "--path-format=absolute" } + local cmd = { "git", "-C", dir, "rev-parse", "--git-dir" } local result = vim.system(cmd, { text = true }):wait() return Path:new(vim.trim(result.stdout)):absolute() From 4a9bae2749016030b70cafad8188e9b664464eb8 Mon Sep 17 00:00:00 2001 From: sheffey <57262511+SheffeyG@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:51:49 +0800 Subject: [PATCH 229/437] Fix console window height --- lua/neogit/lib/buffer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 6991cf465..58ed92254 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -346,7 +346,7 @@ function Buffer:show() width = vim.o.columns, height = math.floor(vim.o.lines * 0.3), col = 0, - row = vim.o.lines - 2, + row = vim.o.lines - vim.o.cmdheight - 1, style = "minimal", focusable = true, border = { "─", "─", "─", "", "", "", "", "" }, @@ -364,7 +364,7 @@ function Buffer:show() width = vim.o.columns, height = math.floor(vim.o.lines * 0.3), col = 0, - row = vim.o.lines - 2, + row = vim.o.lines - vim.o.cmdheight - 1, style = "minimal", border = { "─", "─", "─", "", "", "", "", "" }, -- title = (" %s Actions "):format(title), From ac2744c00ed1dd58b231f6ce724f1b2baa2b93d5 Mon Sep 17 00:00:00 2001 From: sheffey <57262511+SheffeyG@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:04:17 +0800 Subject: [PATCH 230/437] Determine statusline height too --- lua/neogit/lib/buffer.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 58ed92254..423033988 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -346,7 +346,8 @@ function Buffer:show() width = vim.o.columns, height = math.floor(vim.o.lines * 0.3), col = 0, - row = vim.o.lines - vim.o.cmdheight - 1, + -- buffer_height - cmdline - statusline + row = vim.o.lines - vim.o.cmdheight - (vim.o.laststatus > 0 and 1 or 0), style = "minimal", focusable = true, border = { "─", "─", "─", "", "", "", "", "" }, @@ -364,7 +365,8 @@ function Buffer:show() width = vim.o.columns, height = math.floor(vim.o.lines * 0.3), col = 0, - row = vim.o.lines - vim.o.cmdheight - 1, + -- buffer_height - cmdline - statusline + row = vim.o.lines - vim.o.cmdheight - (vim.o.laststatus > 0 and 1 or 0), style = "minimal", border = { "─", "─", "─", "", "", "", "", "" }, -- title = (" %s Actions "):format(title), From e801734576592f0042f9712968a3070385456ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20B=C3=ACnh=20An?= <111893501+brianhuster@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:42:01 +0700 Subject: [PATCH 231/437] Update README.md --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index f808a1f87..90c6e2a52 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,7 @@ Here's an example spec for [Lazy](https://github.com/folke/lazy.nvim), but you'r "ibhagwan/fzf-lua", -- optional "echasnovski/mini.pick", -- optional }, - config = true } - -``` - -If you're not using lazy, you'll need to require and setup the plugin like so: - -```lua --- init.lua -local neogit = require('neogit') -neogit.setup {} ``` ## Compatibility @@ -58,7 +48,7 @@ The `master` branch will always be compatible with the latest **stable** release ## Configuration -You can configure neogit by running the `neogit.setup()` function, passing a table as the argument. +You can configure neogit by running the `require('neogit').setup {}` function, passing a table as the argument.
Default Config From cd793fb9d37ad605b8699d8482aeba8a6b17749f Mon Sep 17 00:00:00 2001 From: gilice <104317939+gilice@users.noreply.github.com> Date: Sat, 22 Feb 2025 15:34:52 +0100 Subject: [PATCH 232/437] Only try to detach UFO if it is loaded Without this check, it could be that ufo.setup() is not called before ufo.detach(), which breaks UFO's internal assumptions, and causes an error. --- lua/neogit/lib/buffer.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 423033988..4495cf6fd 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -388,10 +388,12 @@ function Buffer:show() end -- Workaround UFO getting folds wrong. - local ok, ufo = pcall(require, "ufo") - if ok and type(ufo.detach) == "function" then - logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") - ufo.detach(self.handle) + if package.loaded["nvim-ufo"] then + local ok, ufo = pcall(require, "ufo") + if ok and type(ufo.detach) == "function" then + logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") + ufo.detach(self.handle) + end end self.win_handle = win From 9097acc571df5528585afc77af3d1c1c4b0bea17 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 19 Feb 2025 21:23:12 +0100 Subject: [PATCH 233/437] Move line to be in visual order --- lua/neogit/popups/branch/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/branch/init.lua b/lua/neogit/popups/branch/init.lua index e71157e6d..b93554fdd 100644 --- a/lua/neogit/popups/branch/init.lua +++ b/lua/neogit/popups/branch/init.lua @@ -15,7 +15,6 @@ function M.create(env) local p = popup .builder() :name("NeogitBranchPopup") - :switch("r", "recurse-submodules", "Recurse submodules when checking out an existing branch") :config_if(show_config, "d", "branch." .. current_branch .. ".description", { fn = config_actions.description_config(current_branch), }) @@ -40,6 +39,7 @@ function M.create(env) :config_if(show_config, "p", "branch." .. current_branch .. ".pushRemote", { options = config_actions.remotes_for_config(), }) + :switch("r", "recurse-submodules", "Recurse submodules when checking out an existing branch") :group_heading("Checkout") :action("b", "branch/revision", actions.checkout_branch_revision) :action("l", "local branch", actions.checkout_local_branch) From da37d9f807d70ff9b86a92606a13f7ca2d1673e8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 19 Feb 2025 21:24:02 +0100 Subject: [PATCH 234/437] Add a conditional config heading for the branch popup --- lua/neogit/lib/popup/builder.lua | 12 ++++++++++++ lua/neogit/popups/branch/init.lua | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index b1e0ec56e..2a10b9da4 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -363,6 +363,18 @@ function M:config_heading(heading) return self end +-- adds a heading text with the config section of the popup +---@param cond boolean +---@param heading string Heading to render +---@return self +function M:config_heading_if(cond, heading) + if cond then + table.insert(self.state.config, { heading = heading }) + end + + return self +end + -- Adds config to the popup ---@param key string Key for user to use that engages config ---@param name string Name of config diff --git a/lua/neogit/popups/branch/init.lua b/lua/neogit/popups/branch/init.lua index b93554fdd..3a4177f4f 100644 --- a/lua/neogit/popups/branch/init.lua +++ b/lua/neogit/popups/branch/init.lua @@ -11,10 +11,12 @@ function M.create(env) local show_config = current_branch ~= "" local pull_rebase_entry = git.config.get("pull.rebase") local pull_rebase = pull_rebase_entry:is_set() and pull_rebase_entry.value or "false" + local has_upstream = git.branch.upstream() ~= nil local p = popup .builder() :name("NeogitBranchPopup") + :config_heading_if(show_config, "Configure branch") :config_if(show_config, "d", "branch." .. current_branch .. ".description", { fn = config_actions.description_config(current_branch), }) @@ -57,7 +59,7 @@ function M.create(env) :action("m", "rename", actions.rename_branch) :action("X", "reset", actions.reset_branch) :action("D", "delete", actions.delete_branch) - :action_if(git.branch.upstream() ~= nil, "o", "pull request", actions.open_pull_request) + :action_if(has_upstream, "o", "pull request", actions.open_pull_request) :env(env) :build() From 8d128afff2ebe3884a13a71797280309b5cc7ee6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 19 Feb 2025 21:38:06 +0100 Subject: [PATCH 235/437] Add completion candidates for tag creation input. Closes #1661 --- lua/neogit/popups/tag/actions.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index f50394269..301d4f30c 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -13,7 +13,10 @@ end ---@param popup PopupData function M.create_tag(popup) - local tag_input = input.get_user_input("Create tag", { strip_spaces = true }) + local tag_input = input.get_user_input("Create tag", { + strip_spaces = true, + completion = "customlist,v:lua.require'neogit.lib.git'.refs.list_tags" + }) if not tag_input then return end From 4aafdf3507864c06247c43af9e14f66957dd25da Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 25 Feb 2025 08:45:58 +0100 Subject: [PATCH 236/437] Revert "Only try to detach UFO if it is loaded" This reverts commit cd793fb9d37ad605b8699d8482aeba8a6b17749f. --- lua/neogit/lib/buffer.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 4495cf6fd..423033988 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -388,12 +388,10 @@ function Buffer:show() end -- Workaround UFO getting folds wrong. - if package.loaded["nvim-ufo"] then - local ok, ufo = pcall(require, "ufo") - if ok and type(ufo.detach) == "function" then - logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") - ufo.detach(self.handle) - end + local ok, ufo = pcall(require, "ufo") + if ok and type(ufo.detach) == "function" then + logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") + ufo.detach(self.handle) end self.win_handle = win From e9b2530afe1b94bdac3440597b1c5930b6bc8fc1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 25 Feb 2025 08:55:03 +0100 Subject: [PATCH 237/437] spelling --- lua/neogit/lib/graph/kitty.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index fa5ab6ca6..4936abcc2 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -670,10 +670,10 @@ function M.build(commits, color) local connector_row = { cells = connector_cells } ---@type I.Row -- handle bi-connector rows - local is_bi_crossing, bi_crossing_safely_resolveable = + local is_bi_crossing, bi_crossing_safely_resolvable = get_is_bi_crossing(commit_row, connector_row, next_commit) - if is_bi_crossing and bi_crossing_safely_resolveable and next_commit then + if is_bi_crossing and bi_crossing_safely_resolvable and next_commit then resolve_bi_crossing(prev_commit_row, prev_connector_row, commit_row, connector_row, next_commit) end From 12f78aaabb37b4946254dd5e47cf7b552904937a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 25 Feb 2025 08:55:37 +0100 Subject: [PATCH 238/437] lint --- lua/neogit/popups/tag/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index 301d4f30c..b775f9457 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -15,7 +15,7 @@ end function M.create_tag(popup) local tag_input = input.get_user_input("Create tag", { strip_spaces = true, - completion = "customlist,v:lua.require'neogit.lib.git'.refs.list_tags" + completion = "customlist,v:lua.require'neogit.lib.git'.refs.list_tags", }) if not tag_input then return From 07b2fc7ef79c39ac5b6ec3dbf42bd46a9e9c181d Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Mar 2025 21:45:21 +0100 Subject: [PATCH 239/437] fix: rename to match new section title --- spec/popups/branch_popup_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/popups/branch_popup_spec.rb b/spec/popups/branch_popup_spec.rb index 09d47899a..f699c754f 100644 --- a/spec/popups/branch_popup_spec.rb +++ b/spec/popups/branch_popup_spec.rb @@ -7,7 +7,7 @@ let(:view) do [ - " Variables ", + " Configure branch ", " d branch.master.description unset ", " u branch.master.merge unset ", " branch.master.remote unset ", From a9e3240a9366e1c7225740d4df4db32e4ffeaa40 Mon Sep 17 00:00:00 2001 From: Casper Szymiczek-Graley Date: Fri, 14 Mar 2025 11:34:36 +1100 Subject: [PATCH 240/437] fix: fix diff worktree when there are conflicts --- lua/neogit/integrations/diffview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 871dd4ba9..ef66bae39 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -132,7 +132,7 @@ function M.open(section_name, item_name, opts) view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) elseif section_name == "conflict" and item_name then view = dv_lib.diffview_open(dv_utils.tbl_pack("--selected-file=" .. item_name)) - elseif section_name == "conflict" and not item_name then + elseif (section_name == "conflict" or section_name == "worktree") and not item_name then view = dv_lib.diffview_open() elseif section_name ~= nil then view = get_local_diff_view(section_name, item_name, opts) From b4ec88921d8d43ef84cf72dc7efe3fac1d8b475f Mon Sep 17 00:00:00 2001 From: Soham Shanbhag Date: Fri, 14 Mar 2025 01:45:33 +0900 Subject: [PATCH 241/437] Error notifications when pulling Presently, there are no notifications when there are errors when pulling. This commit adds them. I'm not sure how you want those to look like, so I've added what feel like sensible defaults. Please feel free to change those. --- lua/neogit/popups/pull/actions.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index b2060e13a..b7a1b631e 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -32,6 +32,11 @@ local function pull_from(args, remote, branch, opts) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitPullComplete", modeline = false }) else logger.error("Failed to pull from " .. name) + notification.error("Failed to pull from " .. name, { dismiss = true }) + if res.code == 128 then + notification.info(table.concat(res.stdout, "\n")) + return + end end end From 05074954ad2c83e8d91479ce391379be95b13c8c Mon Sep 17 00:00:00 2001 From: gilice <104317939+gilice@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:26:20 +0100 Subject: [PATCH 242/437] Only detach UFO if it is loaded, 2nd try Fixes cd793fb9d37ad605b8699d8482aeba8a6b17749f. --- lua/neogit/lib/buffer.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 423033988..fbe2d9311 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -388,10 +388,12 @@ function Buffer:show() end -- Workaround UFO getting folds wrong. - local ok, ufo = pcall(require, "ufo") - if ok and type(ufo.detach) == "function" then - logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") - ufo.detach(self.handle) + if package.loaded["ufo"] then + local ok, ufo = pcall(require, "ufo") + if ok and type(ufo.detach) == "function" then + logger.debug("[BUFFER:" .. self.handle .. "] Disabling UFO for buffer") + ufo.detach(self.handle) + end end self.win_handle = win From 0e58b1467db5c31157b1772ebeb3b199c2186547 Mon Sep 17 00:00:00 2001 From: Felipe Mozer Barina Date: Mon, 10 Mar 2025 06:12:35 -0300 Subject: [PATCH 243/437] feat: add prompt_force_push option Allows users to disable the prompt to force push when branches diverge. --- README.md | 2 ++ doc/neogit.txt | 2 ++ lua/neogit/config.lua | 2 ++ lua/neogit/popups/push/actions.lua | 5 +++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f808a1f87..3f09469cb 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ neogit.setup { disable_context_highlighting = false, -- Disables signs for sections/items/hunks disable_signs = false, + -- Offer to force push when branches diverge + prompt_force_push = true, -- Changes what mode the Commit Editor starts in. `true` will leave nvim in normal mode, `false` will change nvim to -- insert mode, and `"auto"` will change nvim to insert mode IF the commit message is empty, otherwise leaving it in -- normal mode. diff --git a/doc/neogit.txt b/doc/neogit.txt index 90bf911d8..35665bfae 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -92,6 +92,8 @@ to Neovim users. disable_context_highlighting = false, -- Disables signs for sections/items/hunks disable_signs = false, + -- Offer to force push when branches diverge + prompt_force_push = true, -- Changes what mode the Commit Editor starts in. `true` will leave nvim in normal mode, `false` will change nvim to -- insert mode, and `"auto"` will change nvim to insert mode IF the commit message is empty, otherwise leaving it in -- normal mode. diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 86a1bd094..11c3e68a9 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -309,6 +309,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 prompt_force_push? boolean Offer to force push when branches diverge ---@field git_services? table Templartes to use when opening a pull request for a branch ---@field fetch_after_checkout? boolean Perform a fetch if the newly checked out branch has an upstream or pushRemote set ---@field telescope_sorter? function The sorter telescope will use @@ -356,6 +357,7 @@ function M.get_default_values() disable_hint = false, disable_context_highlighting = false, disable_signs = false, + prompt_force_push = true, graph_style = "ascii", commit_date_format = nil, log_date_format = nil, diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 2304c302e..87c52a486 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -4,6 +4,7 @@ local logger = require("neogit.logger") local notification = require("neogit.lib.notification") local input = require("neogit.lib.input") local util = require("neogit.lib.util") +local config = require("neogit.config") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -41,8 +42,8 @@ local function push_to(args, remote, branch, opts) local using_force = vim.tbl_contains(args, "--force") or vim.tbl_contains(args, "--force-with-lease") local updates_rejected = string.find(table.concat(res.stdout), "Updates were rejected") ~= nil - -- Only ask the user whether to force push if not already specified - if res and res.code ~= 0 and not using_force and updates_rejected then + -- Only ask the user whether to force push if not already specified and feature enabled + if res and res.code ~= 0 and not using_force and updates_rejected and config.values.prompt_force_push then logger.error("Attempting force push to " .. name) local message = "Your branch has diverged from the remote branch. Do you want to force push?" From 0a058e290223415d73130b73ee24d6b72aa3ced9 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 11 Mar 2025 10:03:56 +0100 Subject: [PATCH 244/437] Fix: Allow `s` to stage resolved files --- lua/neogit/buffers/status/actions.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 6ec18644c..db3849b03 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1079,7 +1079,12 @@ M.n_stage = function(self) }, }) else - notification.info("Conflicts must be resolved before staging") + if not git.merge.is_conflicted(selection.item.name) then + git.status.stage { selection.item.name } + self:dispatch_refresh({ update_diffs = { "*:" .. selection.item.name } }, "n_stage") + else + notification.info("Conflicts must be resolved before staging") + end return end elseif stagable.hunk then From fc313adbc4450e858276ca80f20cfc77eaedaf4d Mon Sep 17 00:00:00 2001 From: msladecek Date: Fri, 28 Mar 2025 20:46:11 +0100 Subject: [PATCH 245/437] fix typo --- doc/neogit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 35665bfae..92c099e05 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -650,7 +650,7 @@ whats recommended. However, if you want to control the style on a per-section basis, the _actual_ highlight groups on the labels follow this pattern: `NeogitChange
` -Where `` is one of: (corrospinding to the git mode) +Where `` is one of: (corresponding to the git mode) M A N From 18394c9cfd1870a4973d63c24e8a28b76fb47682 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 5 Apr 2025 21:36:57 +0200 Subject: [PATCH 246/437] Fix #1702 - when nothing is staged, "--all" shouldn't produce a warning that nothing is staged, as the flag will take care of that. --- lua/neogit/popups/commit/actions.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 4c547da53..eb91366d0 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -12,6 +12,7 @@ local a = require("plenary.async") ---@return boolean local function allow_empty(popup) return vim.tbl_contains(popup:get_arguments(), "--allow-empty") + or vim.tbl_contains(popup:get_arguments(), "--all") end local function confirm_modifications() From 0542ca3aa9bf0b67b7b7713e4d230f9c1fc2c631 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 5 Apr 2025 21:50:56 +0200 Subject: [PATCH 247/437] Add handler for fatal git errors - cancel process and provide notification to user. --- lua/neogit/runner.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lua/neogit/runner.lua b/lua/neogit/runner.lua index 80d595bbe..f6cb4366b 100644 --- a/lua/neogit/runner.lua +++ b/lua/neogit/runner.lua @@ -62,6 +62,15 @@ local function handle_interactive_password(line) return input.get_secret_user_input(prompt, { cancel = "__CANCEL__" }) or "__CANCEL__" end +---@param line string +---@return string +local function handle_fatal_error(line) + logger.debug("[RUNNER]: Fatal error encountered") + local notification = require("neogit.lib.notification") + + notification.error(line) + return "__CANCEL__" +end ---@param process Process ---@param line string ---@return boolean @@ -76,6 +85,8 @@ local function handle_line_interactive(process, line) handler = handle_interactive_username elseif line:match("^Enter passphrase") or line:match("^Password for") or line:match("^Enter PIN for") then handler = handle_interactive_password + elseif line:match("^fatal") then + handler = handle_fatal_error end if handler then From 81bbefa40a49c1c00a8c31292889b1e078321ecc Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 6 Apr 2025 21:48:49 +0200 Subject: [PATCH 248/437] Fix #1700: The diff cache uses the item.name field as the key for the cache, not the escaped path. Any escaped character will result in a modified key, which is a cache miss - the diff is never removed. --- lua/neogit/buffers/status/actions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index db3849b03..2d91d0d53 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1093,7 +1093,7 @@ M.n_stage = function(self) local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) - self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_stage") + self:dispatch_refresh({ update_diffs = { "*:" .. item.name } }, "n_stage") elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } @@ -1169,7 +1169,7 @@ M.n_unstage = function(self) git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) git.index.apply(patch, { cached = true, reverse = true }) - self:dispatch_refresh({ update_diffs = { "*:" .. item.escaped_path } }, "n_unstage") + self:dispatch_refresh({ update_diffs = { "*:" .. item.name } }, "n_unstage") elseif unstagable.filename then git.status.unstage { unstagable.filename } self:dispatch_refresh({ update_diffs = { "*:" .. unstagable.filename } }, "n_unstage") From 7cece14856b501ee3b1074b585ad4c59b9e907f0 Mon Sep 17 00:00:00 2001 From: Filippo Sestini Date: Mon, 17 Feb 2025 22:51:49 +0000 Subject: [PATCH 249/437] fix: pushRemote.branch string extraction --- lua/neogit/lib/git/branch.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index a5e472377..8eb80efa3 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -424,7 +424,7 @@ local function update_branch_information(state) local pushRemote = git.branch.pushRemote_ref() if pushRemote and not status.detached then - local remote, branch = unpack(vim.split(pushRemote, "/")) + local remote, branch = pushRemote:match("([^/]+)/(.+)") state.pushRemote.ref = pushRemote state.pushRemote.remote = remote state.pushRemote.branch = branch From 9cb97a31bd5d7d0b40cfa90106768fb6077fb0ad Mon Sep 17 00:00:00 2001 From: Filippo Sestini Date: Tue, 18 Feb 2025 19:34:35 +0000 Subject: [PATCH 250/437] fix: markdown code block formatting --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac606bc0e..456bd7e97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,9 @@ See [the test documentation for more details](./tests/README.md). Additionally, linting is enforced using `selene` to catch common errors, most of which are also caught by `lua-language-server`. -```sh make lint ``` +```sh +make lint +``` ### Formatting From 9cf858e3577d309d066ad66ccd824bc0e975e689 Mon Sep 17 00:00:00 2001 From: Filippo Sestini Date: Tue, 18 Feb 2025 19:35:19 +0000 Subject: [PATCH 251/437] chore: add mention of `typos` to CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 456bd7e97..1f3010b30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ See [the test documentation for more details](./tests/README.md). ### Linting Additionally, linting is enforced using `selene` to catch common errors, most of which are also caught by -`lua-language-server`. +`lua-language-server`. Source code spell checking is done via `typos`. ```sh make lint From bc4538bbc1c1958fb6de040cfd76ab8818a78d81 Mon Sep 17 00:00:00 2001 From: Dave Aitken Date: Sat, 15 Feb 2025 16:17:55 +0000 Subject: [PATCH 252/437] feat: direct branch delete bind to refs view --- lua/neogit/buffers/refs_view/init.lua | 15 +++++++++++++++ lua/neogit/config.lua | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 750d143df..484f4cabf 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -3,6 +3,7 @@ local config = require("neogit.config") local ui = require("neogit.buffers.refs_view.ui") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() +local mapping = config.get_reversed_refs_view_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") local Watcher = require("neogit.watcher") local logger = require("neogit.logger") @@ -49,6 +50,17 @@ function M.is_open() return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end +function M.delete_branch(ref) + local name = ref and ref.unambiguous_name + if name then + local input = require("neogit.lib.input") + local message = ("Delete branch: '%s'?"):format(name) + if input.get_permission(message) then + git.branch.delete(name) + end + end +end + --- Opens the RefsViewBuffer function M:open() if M.is_open() then @@ -121,6 +133,9 @@ function M:open() suggested_branch_name = ref and ref.name, } end), + [mapping["DeleteBranch"]] = function() + M.delete_branch(self.buffer.ui:get_ref_under_cursor()) + end, [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 11c3e68a9..8833ea01e 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -61,6 +61,11 @@ end function M.get_reversed_commit_editor_maps_I() return get_reversed_maps("commit_editor_I") end +--- +---@return table +function M.get_reversed_refs_view_maps() + return get_reversed_maps("refs_view") +end ---@param set string ---@return table @@ -278,6 +283,11 @@ end ---| "Abort" ---| false ---| fun() +--- +---@alias NeogitConfigMappingsRefsView +---| "DeleteBranch" +---| false +---| fun() ---@alias NeogitGraphStyle ---| "ascii" @@ -300,6 +310,7 @@ end ---@field rebase_editor_I? { [string]: NeogitConfigMappingsRebaseEditor_I } A dictionary that uses Rebase editor commands to set a single keybind ---@field commit_editor? { [string]: NeogitConfigMappingsCommitEditor } A dictionary that uses Commit editor commands to set a single keybind ---@field commit_editor_I? { [string]: NeogitConfigMappingsCommitEditor_I } A dictionary that uses Commit editor commands to set a single keybind +---@field refs_view? { [string]: NeogitConfigMappingsRefsView } A dictionary that uses Refs view editor commands to set a single keybind ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher @@ -583,6 +594,9 @@ function M.get_default_values() [""] = "MouseClick", ["<2-LeftMouse>"] = "NOP", }, + refs_view = { + ["x"] = "DeleteBranch", + }, popup = { ["?"] = "HelpPopup", ["A"] = "CherryPickPopup", @@ -1211,7 +1225,8 @@ function M.setup(opts) end if opts.use_default_keymaps == false then - M.values.mappings = { status = {}, popup = {}, finder = {}, commit_editor = {}, rebase_editor = {} } + M.values.mappings = + { status = {}, popup = {}, finder = {}, commit_editor = {}, rebase_editor = {}, refs_view = {} } else -- Clear our any "false" user mappings from defaults for section, maps in pairs(opts.mappings or {}) do From a1ef45033e905db4f81f909f1a95a73b2488f4b9 Mon Sep 17 00:00:00 2001 From: Dave Aitken Date: Sat, 15 Feb 2025 16:41:52 +0000 Subject: [PATCH 253/437] feat: delete multiple branches with visual selection --- lua/neogit/buffers/refs_view/init.lua | 19 ++++++++++++++++ lua/neogit/lib/git/branch.lua | 32 +++++++++++++++++++++++++++ lua/neogit/lib/ui/init.lua | 20 +++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 484f4cabf..d7e986cef 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -61,6 +61,22 @@ function M.delete_branch(ref) end end +function M.delete_branches(refs) + if #refs > 0 then + local input = require("neogit.lib.input") + local names = {} + for _, ref in ipairs(refs) do + if ref.unambiguous_name then + table.insert(names, ref.unambiguous_name) + end + end + local message = ("Delete %s branch(es)?"):format(#names) + if input.get_permission(message) then + git.branch.delete_many(names) + end + end +end + --- Opens the RefsViewBuffer function M:open() if M.is_open() then @@ -120,6 +136,9 @@ function M:open() item = { name = items }, } end), + [mapping["DeleteBranch"]] = function() + M.delete_branches(self.buffer.ui:get_refs_under_cursor()) + end, }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 8eb80efa3..d5c2e8139 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -183,6 +183,38 @@ function M.delete(name) return result and result.code == 0 or false end +--- +---@param names string[] +---@return boolean +function M.delete_many(names) + local input = require("neogit.lib.input") + + local unmerged = 0 + for _, name in ipairs(names) do + if M.is_unmerged(name) then + unmerged = unmerged + 1 + end + end + + if unmerged > 0 then + local message = ("%d branch(es) contain unmerged commits! Are you sure you want to delete them?"):format( + unmerged + ) + if not input.get_permission(message) then + return false + end + end + + local result + for _, name in ipairs(names) do + local result_single = git.cli.branch.delete.force.name(name).call { await = true } + if result_single.code ~= 0 then + return result and result.code == 0 or false + end + end + + return true +end ---Returns current branch name, or nil if detached HEAD ---@return string|nil diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 95b2d5e0e..38fb32239 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -349,6 +349,26 @@ function Ui:get_ref_under_cursor() return component and component.options.ref end +--- +---@return ParsedRef[] +function Ui:get_refs_under_cursor() + local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } + table.sort(range) + local start, stop = unpack(range) + + local refs = {} + for i = start, stop do + local component = self:_find_component_by_index(i, function(node) + return node.options.ref ~= nil + end) + + if component then + table.insert(refs, 1, component.options.ref) + end + end + + return util.deduplicate(refs) +end ---@return string|nil function Ui:get_yankable_under_cursor() From 83ca3883fa64bf7bf5dbb8fc4ffaa6fc1ce8ead5 Mon Sep 17 00:00:00 2001 From: Dave Aitken Date: Sat, 15 Feb 2025 16:56:48 +0000 Subject: [PATCH 254/437] feat: refactor to support deleting remote branches --- lua/neogit/buffers/refs_view/init.lua | 27 ++++++++++++---------- lua/neogit/lib/git/branch.lua | 32 --------------------------- 2 files changed, 15 insertions(+), 44 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index d7e986cef..f97a88f00 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -50,13 +50,20 @@ function M.is_open() return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end +function M._do_delete(ref) + if not ref.remote then + git.branch.delete(ref.unambiguous_name) + else + git.cli.push.remote(ref.remote).delete.to(ref.name).call() + end +end + function M.delete_branch(ref) - local name = ref and ref.unambiguous_name - if name then + if ref then local input = require("neogit.lib.input") - local message = ("Delete branch: '%s'?"):format(name) + local message = ("Delete branch: '%s'?"):format(ref.unambiguous_name) if input.get_permission(message) then - git.branch.delete(name) + M._do_delete(ref) end end end @@ -64,15 +71,11 @@ end function M.delete_branches(refs) if #refs > 0 then local input = require("neogit.lib.input") - local names = {} - for _, ref in ipairs(refs) do - if ref.unambiguous_name then - table.insert(names, ref.unambiguous_name) - end - end - local message = ("Delete %s branch(es)?"):format(#names) + local message = ("Delete %s branch(es)?"):format(#refs) if input.get_permission(message) then - git.branch.delete_many(names) + for _, ref in ipairs(refs) do + M._do_delete(ref) + end end end end diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index d5c2e8139..8eb80efa3 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -183,38 +183,6 @@ function M.delete(name) return result and result.code == 0 or false end ---- ----@param names string[] ----@return boolean -function M.delete_many(names) - local input = require("neogit.lib.input") - - local unmerged = 0 - for _, name in ipairs(names) do - if M.is_unmerged(name) then - unmerged = unmerged + 1 - end - end - - if unmerged > 0 then - local message = ("%d branch(es) contain unmerged commits! Are you sure you want to delete them?"):format( - unmerged - ) - if not input.get_permission(message) then - return false - end - end - - local result - for _, name in ipairs(names) do - local result_single = git.cli.branch.delete.force.name(name).call { await = true } - if result_single.code ~= 0 then - return result and result.code == 0 or false - end - end - - return true -end ---Returns current branch name, or nil if detached HEAD ---@return string|nil From 3df76695046e721de4cba7a7756b5ad7c3e864b8 Mon Sep 17 00:00:00 2001 From: Dave Aitken Date: Sat, 15 Feb 2025 17:05:58 +0000 Subject: [PATCH 255/437] fix: redraw after delete --- lua/neogit/buffers/refs_view/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index f97a88f00..78615806e 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -141,6 +141,7 @@ function M:open() end), [mapping["DeleteBranch"]] = function() M.delete_branches(self.buffer.ui:get_refs_under_cursor()) + self:redraw() end, }, n = { @@ -157,6 +158,7 @@ function M:open() end), [mapping["DeleteBranch"]] = function() M.delete_branch(self.buffer.ui:get_ref_under_cursor()) + self:redraw() end, [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } From 0a0dbe1a5eda40699accb52f23ae59f6a902d7e0 Mon Sep 17 00:00:00 2001 From: Danthewaann Date: Thu, 6 Feb 2025 10:49:23 +0000 Subject: [PATCH 256/437] fix: empty buffer error on open buffer action --- lua/neogit/buffers/status/actions.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 2d91d0d53..d7af08088 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -72,7 +72,7 @@ local function translate_cursor_location(self, item) end local function open(type, path, cursor) - local command = ("silent! %s %s | %s | redraw! | norm! zz"):format( + local command = ("silent! %s %s | %s"):format( type, fn.fnameescape(path), cursor and cursor[1] or "1" @@ -81,6 +81,12 @@ local function open(type, path, cursor) logger.debug("[Status - Open] '" .. command .. "'") vim.cmd(command) + + command = "redraw! | norm! zz" + + logger.debug("[Status - Open] '" .. command .. "'") + + vim.cmd(command) end local M = {} From b6ae9c7dc6f7769f0247af2ed9fd357ab948dee7 Mon Sep 17 00:00:00 2001 From: Danthewaann Date: Fri, 14 Mar 2025 14:11:38 +0000 Subject: [PATCH 257/437] fix: formatting --- lua/neogit/buffers/status/actions.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index d7af08088..db7b6d560 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -72,11 +72,7 @@ local function translate_cursor_location(self, item) end local function open(type, path, cursor) - local command = ("silent! %s %s | %s"):format( - type, - fn.fnameescape(path), - cursor and cursor[1] or "1" - ) + local command = ("silent! %s %s | %s"):format(type, fn.fnameescape(path), cursor and cursor[1] or "1") logger.debug("[Status - Open] '" .. command .. "'") From bc53c8bfc46d2dc9ca055389986fae0db3c3ae04 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Wed, 16 Apr 2025 22:53:25 +0700 Subject: [PATCH 258/437] fix: set border=none for header text explicitly --- lua/neogit/lib/buffer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index fbe2d9311..be7af5d0b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -623,6 +623,7 @@ function Buffer:set_header(text, scroll) focusable = false, style = "minimal", noautocmd = true, + border = "none", }) vim.wo[winid].wrap = false vim.wo[winid].winhl = "NormalFloat:NeogitFloatHeader" From f140b4df2d416b27123a5a4423e9c7d8a29c5cd4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 May 2025 18:45:58 +0200 Subject: [PATCH 259/437] Explicitly pass buffer handle when setting buffer options to prevent race conditions where, if the user closes the neogit buffer too quickly, their code buffer could be made readonly by mistake, as this function wouldn't care _which_ buffer to apply the option to. --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index be7af5d0b..5e4ccb0f6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -431,7 +431,7 @@ end function Buffer:set_buffer_option(name, value) if self.handle ~= nil then - api.nvim_set_option_value(name, value, { scope = "local" }) + api.nvim_set_option_value(name, value, { scope = "local", buf = self.handle }) end end From fa0ef3e5ae8d812a2b26c80bb7d717c14b15a440 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 May 2025 18:47:02 +0200 Subject: [PATCH 260/437] correction: these options are _window_ options, not _buffer_ options. --- lua/neogit/lib/buffer.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 5e4ccb0f6..9771247fe 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -718,8 +718,8 @@ function Buffer.create(config) end if config.status_column then - buffer:set_buffer_option("statuscolumn", config.status_column) - buffer:set_buffer_option("signcolumn", "no") + buffer:set_window_option("statuscolumn", config.status_column) + buffer:set_window_option("signcolumn", "no") end if config.user_mappings then @@ -907,7 +907,7 @@ function Buffer.create(config) end if config.foldmarkers then - buffer:set_buffer_option("signcolumn", "auto") + buffer:set_window_option("signcolumn", "auto") logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up foldmarkers") buffer:create_namespace("FoldSigns") From 2328b9b65c79382c774bd98c5918f58932421a4f Mon Sep 17 00:00:00 2001 From: Sewbacca Date: Mon, 14 Apr 2025 01:03:28 +0200 Subject: [PATCH 261/437] fix: `\` is not correctly escaped on MinGW --- lua/neogit/client.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index c8b5e10d2..052bf3424 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -65,11 +65,7 @@ function M.client(opts) logger.debug(("[CLIENT] Client address: %s"):format(client)) local lua_cmd = - fmt('lua require("neogit.client").editor("%s", "%s", %s)', file_target, client, opts.show_diff) - - if vim.uv.os_uname().sysname == "Windows_NT" then - lua_cmd = lua_cmd:gsub("\\", "/") - end + fmt('lua require("neogit.client").editor(%q, %q, %s)', file_target, client, opts.show_diff) local rpc_server = RPC.create_connection(nvim_server) rpc_server:send_cmd(lua_cmd) From 3ace46b4432945b4f99a63428817b6a284bcdba2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 May 2025 18:59:16 +0200 Subject: [PATCH 262/437] lint --- lua/neogit/client.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 052bf3424..37ea05d8c 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -64,9 +64,7 @@ function M.client(opts) local client = fn.serverstart() logger.debug(("[CLIENT] Client address: %s"):format(client)) - local lua_cmd = - fmt('lua require("neogit.client").editor(%q, %q, %s)', file_target, client, opts.show_diff) - + local lua_cmd = fmt('lua require("neogit.client").editor(%q, %q, %s)', file_target, client, opts.show_diff) local rpc_server = RPC.create_connection(nvim_server) rpc_server:send_cmd(lua_cmd) end From afbf66f157c3c0e2aa08ae5b8312b9199ae9c242 Mon Sep 17 00:00:00 2001 From: Frank Friberg Date: Wed, 12 Feb 2025 19:16:19 +0100 Subject: [PATCH 263/437] reafactor: use `vim.ui.input` instead of `vim.fn.input` --- lua/neogit/lib/input.lua | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index 26a336180..5df738c6f 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -1,5 +1,14 @@ local M = {} +local async = require("plenary.async") +local input = async.wrap(function(prompt, default, completion, callback) + vim.ui.input({ + prompt = prompt, + default = default, + completion = completion, + }, callback) +end, 4) + --- Provides the user with a confirmation ---@param msg string Prompt to use for confirmation ---@param options table|nil @@ -57,23 +66,15 @@ end function M.get_user_input(prompt, opts) opts = vim.tbl_extend("keep", opts or {}, { strip_spaces = false, separator = ": " }) - vim.fn.inputsave() - if opts.prepend then vim.defer_fn(function() vim.api.nvim_input(opts.prepend) end, 10) end - local status, result = pcall(vim.fn.input, { - prompt = ("%s%s"):format(prompt, opts.separator), - default = opts.default, - completion = opts.completion, - cancelreturn = opts.cancel, - }) + local result = input(("%s%s"):format(prompt, opts.separator), opts.default, opts.completion) - vim.fn.inputrestore() - if not status then + if result == "" or result == nil then return nil end @@ -81,10 +82,6 @@ function M.get_user_input(prompt, opts) result, _ = result:gsub("%s", "-") end - if result == "" then - return nil - end - return result end From 866813d67bb1b7ee0b8b65937901b21c8c386197 Mon Sep 17 00:00:00 2001 From: sheffey <57262511+SheffeyG@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:28:35 +0800 Subject: [PATCH 264/437] Allow custom floating window --- lua/neogit/config.lua | 18 ++++++++++++++++++ lua/neogit/lib/buffer.lua | 23 +++++++++-------------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 8833ea01e..6eddc994b 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -102,6 +102,16 @@ end ---@class NeogitConfigPopup Popup window options ---@field kind WindowKind The type of window that should be opened +---@class NeogitConfigFloating +---@field relative? string +---@field width? number +---@field height? number +---@field col? number +---@field row? number +---@field style? string +---@field focusable? boolean +---@field border? string + ---@alias StagedDiffSplitKind ---| "split" Open in a split ---| "vsplit" Open in a vertical split @@ -331,6 +341,7 @@ end ---@field sort_branches? string Value used for `--sort` for the `git branch` command ---@field initial_branch_name? string Default for new branch name prompts ---@field kind? WindowKind The default type of window neogit should open in +---@field floating? NeogitConfigFloating The floating window style ---@field disable_line_numbers? boolean Whether to disable line numbers ---@field disable_relative_line_numbers? boolean Whether to disable line numbers ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands @@ -392,6 +403,13 @@ function M.get_default_values() fetch_after_checkout = false, sort_branches = "-committerdate", kind = "tab", + floating = { + relative = "editor", + width = 0.5, + height = 0.5, + style = "minimal", + border = "rounded", + }, initial_branch_name = "", disable_line_numbers = true, disable_relative_line_numbers = true, diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 9771247fe..369fb8dee 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -317,25 +317,20 @@ function Buffer:show() elseif self.kind == "vsplit_left" then win = api.nvim_open_win(self.handle, true, { split = "left", vertical = true }) elseif self.kind == "floating" then + local floating = require("neogit.config").values.floating + -- Creates the border window local vim_height = vim.o.lines local vim_width = vim.o.columns - local width = math.floor(vim_width * 0.8) + 3 - local height = math.floor(vim_height * 0.7) - local col = vim_width * 0.1 - 1 - local row = vim_height * 0.15 + floating.width = floating.width > 1 and floating.width or math.floor(vim_width * floating.width) + floating.height = floating.height > 1 and floating.height or math.floor(vim_height * floating.height) + floating.col = (vim_width - floating.width) / 2 + -- floating.row = (vim_height - floating.height) / 2 + floating.row = vim_height * 0.15 + floating.focusable = true - local content_window = api.nvim_open_win(self.handle, true, { - relative = "editor", - width = width, - height = height, - col = col, - row = row, - style = "minimal", - focusable = true, - border = "rounded", - }) + local content_window = api.nvim_open_win(self.handle, true, floating) api.nvim_win_set_cursor(content_window, { 1, 0 }) win = content_window From b0a2641f17890edac1cc07fe22c46a17b0d3187e Mon Sep 17 00:00:00 2001 From: sheffey <57262511+SheffeyG@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:18:14 +0800 Subject: [PATCH 265/437] Fix type checks make lua_ls happy --- lua/neogit/config.lua | 8 +++++++- lua/neogit/lib/buffer.lua | 26 +++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 6eddc994b..61b4f6607 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -109,7 +109,6 @@ end ---@field col? number ---@field row? number ---@field style? string ----@field focusable? boolean ---@field border? string ---@alias StagedDiffSplitKind @@ -1154,6 +1153,13 @@ function M.validate_config() validate_type(config.notification_icon, "notification_icon", "string") validate_type(config.console_timeout, "console_timeout", "number") validate_kind(config.kind, "kind") + if validate_type(config.floating, "floating", "table") then + validate_type(config.floating.relative, "relative", "string") + validate_type(config.floating.width, "width", "number") + validate_type(config.floating.height, "height", "number") + validate_type(config.floating.style, "style", "string") + validate_type(config.floating.border, "border", "string") + end validate_type(config.disable_line_numbers, "disable_line_numbers", "boolean") validate_type(config.disable_relative_line_numbers, "disable_relative_line_numbers", "boolean") validate_type(config.auto_show_console, "auto_show_console", "boolean") diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 369fb8dee..b0db4f880 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -5,6 +5,7 @@ local util = require("neogit.lib.util") local signs = require("neogit.lib.signs") local Ui = require("neogit.lib.ui") +local config = require("neogit.config") local Path = require("plenary.path") @@ -317,20 +318,23 @@ function Buffer:show() elseif self.kind == "vsplit_left" then win = api.nvim_open_win(self.handle, true, { split = "left", vertical = true }) elseif self.kind == "floating" then - local floating = require("neogit.config").values.floating - - -- Creates the border window + local width = config.values.floating.width + local height = config.values.floating.height local vim_height = vim.o.lines local vim_width = vim.o.columns + width = width > 1 and width or math.floor(vim_width * width) + height = height > 1 and height or math.floor(vim_height * height) - floating.width = floating.width > 1 and floating.width or math.floor(vim_width * floating.width) - floating.height = floating.height > 1 and floating.height or math.floor(vim_height * floating.height) - floating.col = (vim_width - floating.width) / 2 - -- floating.row = (vim_height - floating.height) / 2 - floating.row = vim_height * 0.15 - floating.focusable = true - - local content_window = api.nvim_open_win(self.handle, true, floating) + local content_window = api.nvim_open_win(self.handle, true, { + width = width, + height = height, + relative = config.values.floating.relative, + border = config.values.floating.border, + style = config.values.floating.style, + col = config.values.floating.col or (vim_width - width) / 2, + row = config.values.floating.row or (vim_height - height) / 2, + focusable = true, + }) api.nvim_win_set_cursor(content_window, { 1, 0 }) win = content_window From 6a098d14f9b6df0d56261b2b77a9db577c86a4be Mon Sep 17 00:00:00 2001 From: sheffey <57262511+SheffeyG@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:45:29 +0800 Subject: [PATCH 266/437] Add floating config to documents --- README.md | 8 ++++++++ doc/neogit.txt | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 4582ee7cc..aaae2d988 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,14 @@ neogit.setup { initial_branch_name = "", -- Change the default way of opening neogit kind = "tab", + -- Floating window style + floating = { + relative = "editor", + width = 0.5, + height = 0.5, + style = "minimal", + border = "rounded", + }, -- Disable line numbers disable_line_numbers = true, -- Disable relative line numbers diff --git a/doc/neogit.txt b/doc/neogit.txt index 92c099e05..1f89145b1 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -155,6 +155,14 @@ to Neovim users. initial_branch_name = "", -- Change the default way of opening neogit kind = "tab", + -- Floating window style + floating = { + relative = "editor", + width = 0.5, + height = 0.5, + style = "minimal", + border = "rounded", + }, -- Disable line numbers disable_line_numbers = true, -- Disable relative line numbers From 3ceaa8c8dbb4117163b65a426d887b984fc15262 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 May 2025 19:39:25 +0200 Subject: [PATCH 267/437] update default floating values to match current values in release --- README.md | 4 ++-- doc/neogit.txt | 4 ++-- lua/neogit/config.lua | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aaae2d988..4e3bcdbe3 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,8 @@ neogit.setup { -- Floating window style floating = { relative = "editor", - width = 0.5, - height = 0.5, + width = 0.8, + height = 0.7, style = "minimal", border = "rounded", }, diff --git a/doc/neogit.txt b/doc/neogit.txt index 1f89145b1..3c0827b3c 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -158,8 +158,8 @@ to Neovim users. -- Floating window style floating = { relative = "editor", - width = 0.5, - height = 0.5, + width = 0.8, + height = 0.7, style = "minimal", border = "rounded", }, diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 61b4f6607..5c36209e8 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -404,8 +404,8 @@ function M.get_default_values() kind = "tab", floating = { relative = "editor", - width = 0.5, - height = 0.5, + width = 0.8, + height = 0.7, style = "minimal", border = "rounded", }, From 3ff369c6210eb16620e0096a66a322ec623c9875 Mon Sep 17 00:00:00 2001 From: Alexander Arvidsson <2972103+AlexanderArvidsson@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:28:15 +0200 Subject: [PATCH 268/437] feat: #1684 Ability to add custom popup switches --- lua/neogit/config.lua | 9 ++------- lua/neogit/lib/popup/builder.lua | 12 ++++++++++++ lua/neogit/lib/popup/init.lua | 9 +++++++-- lua/neogit/popups/commit/init.lua | 2 +- lua/neogit/popups/fetch/init.lua | 2 +- lua/neogit/popups/pull/init.lua | 4 ++-- lua/neogit/popups/push/init.lua | 4 ++-- lua/neogit/popups/tag/init.lua | 2 +- 8 files changed, 28 insertions(+), 16 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 5c36209e8..1eb4593bf 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -369,6 +369,7 @@ end ---@field notification_icon? string ---@field use_default_keymaps? boolean ---@field highlight? HighlightOptions +---@field builders? { [string]: fun(builder: PopupBuilder) } ---Returns the default Neogit configuration ---@return NeogitConfig @@ -550,13 +551,7 @@ function M.get_default_values() hidden = false, }, }, - ignored_settings = { - "NeogitPushPopup--force-with-lease", - "NeogitPushPopup--force", - "NeogitPullPopup--rebase", - "NeogitPullPopup--force", - "NeogitCommitPopup--allow-empty", - }, + ignored_settings = {}, mappings = { commit_editor = { ["q"] = "Close", diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 2a10b9da4..cd2920425 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") +local config = require("neogit.config") ---@class PopupBuilder ---@field state PopupState @@ -54,6 +55,7 @@ local M = {} ---@field type string ---@field user_input boolean ---@field value string? +---@field persisted? boolean ---@class PopupConfig ---@field id string @@ -90,6 +92,7 @@ local M = {} ---@field value? string Allows for pre-building cli flags that can be customized by user input ---@field user_input? boolean If true, allows user to customize the value of the cli flag ---@field dependent? string[] other switches/options with a state dependency on this one +---@field persisted? boolean Allows overwriting the default 'true' to decide if this switch should be persisted ---@class PopupOptionOpts ---@field key_prefix? string Allows overwriting the default '=' to set option @@ -222,6 +225,10 @@ function M:switch(key, cli, description, opts) opts.cli_suffix = "" end + if opts.persisted == nil then + opts.persisted = true + end + local value if opts.enabled and opts.value then value = cli .. opts.value @@ -253,6 +260,7 @@ function M:switch(key, cli, description, opts) cli_prefix = opts.cli_prefix, user_input = opts.user_input, cli_suffix = opts.cli_suffix, + persisted = opts.persisted, options = opts.options, incompatible = util.build_reverse_lookup(opts.incompatible), dependent = util.build_reverse_lookup(opts.dependent), @@ -467,6 +475,10 @@ function M:build() error("A popup needs to have a name!") end + if config.values.builders ~= nil and type(config.values.builders[self.state.name]) == "function" then + config.values.builders[self.state.name](self) + end + return self.builder_fn(self.state) end diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 3d95c5820..e88bb6dd3 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -108,7 +108,10 @@ function M:toggle_switch(switch) switch.cli = options[(index + 1)] or options[1] switch.value = switch.cli switch.enabled = switch.cli ~= "" - state.set({ self.state.name, switch.cli_suffix }, switch.cli) + + if switch.persisted ~= false then + state.set({ self.state.name, switch.cli_suffix }, switch.cli) + end return end @@ -127,7 +130,9 @@ function M:toggle_switch(switch) end end - state.set({ self.state.name, switch.cli }, switch.enabled) + if switch.persisted ~= false then + state.set({ self.state.name, switch.cli }, switch.enabled) + end -- Ensure that other switches/options that are incompatible with this one are disabled if switch.enabled and #switch.incompatible > 0 then diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index 6b9223519..7885281ce 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -8,7 +8,7 @@ function M.create(env) .builder() :name("NeogitCommitPopup") :switch("a", "all", "Stage all modified and deleted files") - :switch("e", "allow-empty", "Allow empty commit") + :switch("e", "allow-empty", "Allow empty commit", { persisted = false }) :switch("v", "verbose", "Show diff of changes to be committed") :switch("h", "no-verify", "Disable hooks") :switch("R", "reset-author", "Claim authorship and reset author date") diff --git a/lua/neogit/popups/fetch/init.lua b/lua/neogit/popups/fetch/init.lua index 2a3371eb6..8fa17f70e 100644 --- a/lua/neogit/popups/fetch/init.lua +++ b/lua/neogit/popups/fetch/init.lua @@ -10,7 +10,7 @@ function M.create() :name("NeogitFetchPopup") :switch("p", "prune", "Prune deleted branches") :switch("t", "tags", "Fetch all tags") - :switch("F", "force", "force") + :switch("F", "force", "force", { persisted = false }) :group_heading("Fetch from") :action("p", git.branch.pushRemote_remote_label(), actions.fetch_pushremote) :action("u", git.branch.upstream_remote_label(), actions.fetch_upstream) diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index 17635cc0a..a1da91f69 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -21,10 +21,10 @@ function M.create() }, }) :switch("f", "ff-only", "Fast-forward only") - :switch("r", "rebase", "Rebase local commits") + :switch("r", "rebase", "Rebase local commits", { persisted = false }) :switch("a", "autostash", "Autostash") :switch("t", "tags", "Fetch tags") - :switch("F", "force", "Force") + :switch("F", "force", "Force", { persisted = false }) :group_heading_if(current ~= nil, "Pull into " .. current .. " from") :group_heading_if(not current, "Pull from") :action_if(current ~= nil, "p", git.branch.pushRemote_label(), actions.from_pushremote) diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index 72a03fb7c..6b6124154 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -10,8 +10,8 @@ function M.create(env) local p = popup .builder() :name("NeogitPushPopup") - :switch("f", "force-with-lease", "Force with lease") - :switch("F", "force", "Force") + :switch("f", "force-with-lease", "Force with lease", { persisted = false }) + :switch("F", "force", "Force", { persisted = false }) :switch("h", "no-verify", "Disable hooks") :switch("d", "dry-run", "Dry run") :switch("u", "set-upstream", "Set the upstream before pushing") diff --git a/lua/neogit/popups/tag/init.lua b/lua/neogit/popups/tag/init.lua index 994a32a4b..c7bbef5e7 100644 --- a/lua/neogit/popups/tag/init.lua +++ b/lua/neogit/popups/tag/init.lua @@ -8,7 +8,7 @@ function M.create(env) .builder() :name("NeogitTagPopup") :arg_heading("Arguments") - :switch("f", "force", "Force") + :switch("f", "force", "Force", { persisted = false }) :switch("a", "annotate", "Annotate") :switch("s", "sign", "Sign") :option("u", "local-user", "", "Sign as", { key_prefix = "-" }) From 0d63794072f0e0b81157f6c466705dc3c74342ab Mon Sep 17 00:00:00 2001 From: Alexander Arvidsson <2972103+AlexanderArvidsson@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:39:53 +0200 Subject: [PATCH 269/437] chore(docs): #1684 Explain builder hooks in docs --- README.md | 8 +------- doc/neogit.txt | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4e3bcdbe3..c92e462b6 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,7 @@ neogit.setup { -- Scope persisted settings on a per-project basis use_per_project_settings = true, -- Table of settings to never persist. Uses format "Filetype--cli-value" - ignored_settings = { - "NeogitPushPopup--force-with-lease", - "NeogitPushPopup--force", - "NeogitPullPopup--rebase", - "NeogitCommitPopup--allow-empty", - "NeogitRevertPopup--no-edit", - }, + ignored_settings = {}, -- Configure highlight group features highlight = { italic = true, diff --git a/doc/neogit.txt b/doc/neogit.txt index 3c0827b3c..024b59b16 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -128,13 +128,7 @@ to Neovim users. -- Scope persisted settings on a per-project basis use_per_project_settings = true, -- Table of settings to never persist. Uses format "Filetype--cli-value" - ignored_settings = { - "NeogitPushPopup--force-with-lease", - "NeogitPushPopup--force", - "NeogitPullPopup--rebase", - "NeogitCommitPopup--allow-empty", - "NeogitRevertPopup--no-edit", - }, + ignored_settings = {}, -- Configure highlight group features highlight = { italic = true, @@ -2144,5 +2138,21 @@ calling the setup function: }, }) < + +You can also customize existing popups via the Neogit config. +Below is an example of adding a custom switch, but you can use any function +from the builder API. +>lua + require("neogit").setup({ + builders = { + NeogitPushPopup = function(builder) + builder:switch('m', 'merge_request.create', 'Create merge request', { cli_prefix = '-o ', persisted = false }) + end, + }, + }) + +Keep in mind that builder hooks are executed at the end of the popup +builder, so any switches or options added will be placed at the end. + ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: From 5e32b03a1cca824d16a1ce576c5583a09eae52e3 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Sun, 9 Feb 2025 22:39:55 +0900 Subject: [PATCH 270/437] feat: add snacks.picker integration --- lua/neogit/config.lua | 5 +-- lua/neogit/lib/finder.lua | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 1eb4593bf..311274be2 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -362,7 +362,7 @@ end ---@field preview_buffer? NeogitConfigPopup Preview options ---@field popup? NeogitConfigPopup Set the default way of opening popups ---@field signs? NeogitConfigSigns Signs used for toggled regions ----@field integrations? { diffview: boolean, telescope: boolean, fzf_lua: boolean, mini_pick: boolean } Which integrations to enable +---@field integrations? { diffview: boolean, telescope: boolean, fzf_lua: boolean, mini_pick: boolean, snacks: boolean } Which integrations to enable ---@field sections? NeogitConfigSections ---@field ignored_settings? string[] Settings to never persist, format: "Filetype--cli-value", i.e. "NeogitCommitPopup--author" ---@field mappings? NeogitConfigMappings @@ -500,6 +500,7 @@ function M.get_default_values() diffview = nil, fzf_lua = nil, mini_pick = nil, + snacks = nil, }, sections = { sequencer = { @@ -803,7 +804,7 @@ function M.validate_config() end local function validate_integrations() - local valid_integrations = { "diffview", "telescope", "fzf_lua", "mini_pick" } + local valid_integrations = { "diffview", "telescope", "fzf_lua", "mini_pick", "snacks" } if not validate_type(config.integrations, "integrations", "table") or #config.integrations == 0 then return end diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index adc42d9d9..9b21aa6cd 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -145,6 +145,59 @@ local function fzf_actions(on_select, allow_multi, refocus_status) } end +---Convert entries to snack picker items +---@param entries any[] +---@return any[] +local function entries_to_snack_items(entries) + local items = {} + for idx, entry in ipairs(entries) do + table.insert(items, { idx = idx, score = 0, text = entry }) + end + return items +end + +---Convert snack picker items to entries +---@param items any[] +---@return any[] +local function snack_items_to_entries(items) + local entries = {} + for _, item in ipairs(items) do + table.insert(entries, item.text) + end + return entries +end + +--- Utility function to map actions +---@param on_select fun(item: any|nil) +---@param allow_multi boolean +---@param refocus_status boolean +local function snacks_actions(on_select, allow_multi, refocus_status) + local function refresh(picker) + picker:close() + if refocus_status then + refocus_status_buffer() + end + end + + local function confirm(picker, item) + if allow_multi then + -- Try fetch all selected items + on_select(snack_items_to_entries(picker:selected { fallback = true })) + else + -- Use current item + on_select(snack_items_to_entries({ item })[1]) + end + refresh(picker) + end + + local function close(picker) + on_select(nil) + refresh(picker) + end + + return { confirm = confirm, close = close } +end + --- Utility function to map finder opts to fzf ---@param opts FinderOpts ---@return table @@ -274,6 +327,20 @@ function Finder:find(on_select) elseif config.check_integration("mini_pick") then local mini_pick = require("mini.pick") mini_pick.start { source = { items = self.entries, choose = on_select } } + elseif config.check_integration("snacks") then + Snacks.picker.pick(nil, { + title = "Neogit", + prompt = string.format("%s > ", self.opts.prompt_prefix), + items = entries_to_snack_items(self.entries), + format = "text", + layout = { + preset = self.opts.theme, + preview = self.opts.previewer, + height = self.opts.layout_config.height, + border = self.opts.border and "rounded" or "none", + }, + actions = snacks_actions(on_select, self.opts.allow_multi, self.opts.refocus_status), + }) else vim.ui.select(self.entries, { prompt = string.format("%s: ", self.opts.prompt_prefix), From da5ef4fac334b7155562e760879f7f6488d86690 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Sun, 9 Feb 2025 22:40:12 +0900 Subject: [PATCH 271/437] docs: update readme for snacks.picker integration --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c92e462b6..9de0281f0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Here's an example spec for [Lazy](https://github.com/folke/lazy.nvim), but you'r "nvim-telescope/telescope.nvim", -- optional "ibhagwan/fzf-lua", -- optional "echasnovski/mini.pick", -- optional + "folke/snacks.nvim", -- optional }, } ``` @@ -240,6 +241,11 @@ neogit.setup { -- is also selected then telescope is used instead -- Requires you to have `echasnovski/mini.pick` installed. mini_pick = nil, + + -- If enabled, uses snacks.picker for menu selection. If the telescope integration + -- is also selected then telescope is used instead + -- Requires you to have `folke/snacks.nvim` installed. + snacks = nil, }, sections = { -- Reverting/Cherry Picking From 486637dd1e329fb37d0fe314a8f1b2d04c9c704a Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Sun, 9 Feb 2025 22:40:38 +0900 Subject: [PATCH 272/437] docs: update help for docs: update readme for snacks.picker integration --- doc/neogit.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 024b59b16..893631c6c 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -267,6 +267,11 @@ to Neovim users. -- is also selected then telescope is used instead -- Requires you to have `echasnovski/mini.pick` installed. mini_pick = nil, + + -- If enabled, uses snacks.picker for menu selection. If the telescope integration + -- is also selected then telescope is used instead + -- Requires you to have `folke/snacks.nvim` installed. + snacks = nil, }, sections = { -- Reverting/Cherry Picking From 3abb318876aecff28a41cb177f265de67b46575b Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Mon, 10 Feb 2025 11:04:18 +0900 Subject: [PATCH 273/437] fix: use require to avoid global variable accessing --- lua/neogit/lib/finder.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 9b21aa6cd..2c3133417 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -328,7 +328,8 @@ function Finder:find(on_select) local mini_pick = require("mini.pick") mini_pick.start { source = { items = self.entries, choose = on_select } } elseif config.check_integration("snacks") then - Snacks.picker.pick(nil, { + local snacks_picker = require("snacks.picker") + snacks_picker.pick(nil, { title = "Neogit", prompt = string.format("%s > ", self.opts.prompt_prefix), items = entries_to_snack_items(self.entries), From ca44970bc17c3c6c5e87dd8ba6e2707cea895ae2 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Sun, 16 Mar 2025 00:17:53 +0900 Subject: [PATCH 274/437] feat: use same logic as telescope integration --- lua/neogit/lib/finder.lua | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 2c3133417..e83791ec6 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -180,13 +180,28 @@ local function snacks_actions(on_select, allow_multi, refocus_status) end local function confirm(picker, item) - if allow_multi then - -- Try fetch all selected items - on_select(snack_items_to_entries(picker:selected { fallback = true })) + local selection + local picker_selected = picker:selected { fallback = true } + + if #picker_selected > 1 then + selection = snack_items_to_entries(picker_selected) else - -- Use current item - on_select(snack_items_to_entries({ item })[1]) + local entry = item.text + local prompt = picker:filter().pattern + + local navigate_up_level = entry == ".." and #prompt > 0 + local input_git_refspec = prompt:match("%^") + or prompt:match("~") + or prompt:match("@") + or prompt:match(":") + + selection = { (navigate_up_level or input_git_refspec) and prompt or entry } end + + if selection and selection[1] and selection[1] ~= "" then + on_select(allow_multi and selection or selection[1]) + end + refresh(picker) end From cae3af47ff84cf11e845c600df09e8f2870b3b51 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Sun, 16 Mar 2025 00:23:31 +0900 Subject: [PATCH 275/437] refactor: inline the helper function --- lua/neogit/lib/finder.lua | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index e83791ec6..b24f9d47d 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -156,17 +156,6 @@ local function entries_to_snack_items(entries) return items end ----Convert snack picker items to entries ----@param items any[] ----@return any[] -local function snack_items_to_entries(items) - local entries = {} - for _, item in ipairs(items) do - table.insert(entries, item.text) - end - return entries -end - --- Utility function to map actions ---@param on_select fun(item: any|nil) ---@param allow_multi boolean @@ -180,11 +169,13 @@ local function snacks_actions(on_select, allow_multi, refocus_status) end local function confirm(picker, item) - local selection + local selection = {} local picker_selected = picker:selected { fallback = true } if #picker_selected > 1 then - selection = snack_items_to_entries(picker_selected) + for _, item in ipairs(picker_selected) do + table.insert(selection, item.text) + end else local entry = item.text local prompt = picker:filter().pattern @@ -195,7 +186,7 @@ local function snacks_actions(on_select, allow_multi, refocus_status) or prompt:match("@") or prompt:match(":") - selection = { (navigate_up_level or input_git_refspec) and prompt or entry } + table.insert(selection, (navigate_up_level or input_git_refspec) and prompt or entry) end if selection and selection[1] and selection[1] ~= "" then From e7b188250697c3c21995321e60a0a23fdf1ab2d9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 6 May 2025 11:52:04 +0200 Subject: [PATCH 276/437] fixes #1714 Ensure that repo state is populated prior to running an action. Some actions depend on state to not raise an error. --- lua/neogit.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 4f052e882..6efaf407b 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -186,6 +186,7 @@ end ---@return function function M.action(popup, action, args) local util = require("neogit.lib.util") + local git = require("neogit.lib.git") local a = require("plenary.async") args = args or {} @@ -202,15 +203,19 @@ function M.action(popup, action, args) if ok then local fn = actions[action] if fn then - fn { - state = { env = {} }, - get_arguments = function() - return args - end, - get_internal_arguments = function() - return internal_args - end, - } + local action = function() + fn { + state = { env = {} }, + get_arguments = function() + return args + end, + get_internal_arguments = function() + return internal_args + end, + } + end + + git.repo:dispatch_refresh { source = "action", callback = action } else M.notification.error( string.format( From 0571c0fa15b3246057fdbe071775b781065aa485 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 6 May 2025 21:14:26 +0200 Subject: [PATCH 277/437] Speed up fetching stashes by lazily evaluating OID --- lua/neogit/lib/git/stash.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index b0325a38d..9a9bdf1e5 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -107,7 +107,6 @@ function M.register(meta) idx = idx, name = line, message = message, - oid = git.rev_parse.oid("stash@{" .. idx .. "}"), } -- These calls can be somewhat expensive, so lazy load them @@ -130,6 +129,9 @@ function M.register(meta) .call({ hidden = true }).stdout[1] return self.date + elseif key == "oid" then + self.oid = git.rev_parse.oid("stash@{" .. idx .. "}") + return self.oid end end, }) From 0cac75a5aa818d03d80fc51f6be3328238e9d350 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 6 May 2025 21:15:34 +0200 Subject: [PATCH 278/437] Do not open the merge popup when done handling conflict --- lua/neogit/buffers/status/actions.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index db7b6d560..52bfdd76b 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1072,10 +1072,6 @@ M.n_stage = function(self) if not git.merge.is_conflicted(selection.item.name) then git.status.stage { selection.item.name } self:dispatch_refresh({ update_diffs = { "*:" .. selection.item.name } }, "n_stage") - - if not git.merge.any_conflicted() then - popups.open("merge")() - end end end, }, From 7a3daecbfdd4b6338a94dea49ad7802b3ca5fd9b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 7 May 2025 09:13:46 +0200 Subject: [PATCH 279/437] Add a workaround for people not using the latest neovim verson (0.11). An error is thrown when using both scope and buf options prior to this version. --- lua/neogit/lib/buffer.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b0db4f880..73bfc1b5e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -430,7 +430,12 @@ end function Buffer:set_buffer_option(name, value) if self.handle ~= nil then - api.nvim_set_option_value(name, value, { scope = "local", buf = self.handle }) + -- TODO: Remove this at some point. Nvim 0.10 throws an error if using both buf and scope + if vim.fn.has("nvim-0.11") == 1 then + api.nvim_set_option_value(name, value, { scope = "local", buf = self.handle }) + else + api.nvim_set_option_value(name, value, { buf = self.handle }) + end end end From 7bf4e53f9fb1fa02bfbc933313dcbb968e22af7e Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Sun, 27 Apr 2025 15:47:43 -0400 Subject: [PATCH 280/437] feat(margin): add margin popup --- lua/neogit/buffers/status/actions.lua | 10 ++++ lua/neogit/buffers/status/init.lua | 2 + lua/neogit/buffers/status/ui.lua | 80 ++++++++++++++++++++++++++- lua/neogit/config.lua | 2 + lua/neogit/lib/git/log.lua | 2 + lua/neogit/popups/help/actions.lua | 3 + lua/neogit/popups/margin/actions.lua | 25 +++++++++ lua/neogit/popups/margin/init.lua | 49 ++++++++++++++++ 8 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 lua/neogit/popups/margin/actions.lua create mode 100644 lua/neogit/popups/margin/init.lua diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 52bfdd76b..ef21db8cb 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -445,6 +445,11 @@ M.v_log_popup = function(_self) return popups.open("log") end +---@param _self StatusBuffer +M.v_margin_popup = function(_self) + return popups.open("margin") +end + ---@param _self StatusBuffer M.v_worktree_popup = function(_self) return popups.open("worktree") @@ -1408,6 +1413,11 @@ M.n_log_popup = function(_self) return popups.open("log") end +---@param _self StatusBuffer +M.n_margin_popup = function(_self) + return popups.open("margin") +end + ---@param _self StatusBuffer M.n_worktree_popup = function(_self) return popups.open("worktree") diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fbfb97ab7..918fb356b 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -128,6 +128,7 @@ function M:open(kind) [popups.mapping_for("HelpPopup")] = self:_action("v_help_popup"), [popups.mapping_for("IgnorePopup")] = self:_action("v_ignore_popup"), [popups.mapping_for("LogPopup")] = self:_action("v_log_popup"), + [popups.mapping_for("MarginPopup")] = self:_action("v_margin_popup"), [popups.mapping_for("MergePopup")] = self:_action("v_merge_popup"), [popups.mapping_for("PullPopup")] = self:_action("v_pull_popup"), [popups.mapping_for("PushPopup")] = self:_action("v_push_popup"), @@ -182,6 +183,7 @@ function M:open(kind) [popups.mapping_for("HelpPopup")] = self:_action("n_help_popup"), [popups.mapping_for("IgnorePopup")] = self:_action("n_ignore_popup"), [popups.mapping_for("LogPopup")] = self:_action("n_log_popup"), + [popups.mapping_for("MarginPopup")] = self:_action("n_margin_popup"), [popups.mapping_for("MergePopup")] = self:_action("n_merge_popup"), [popups.mapping_for("PullPopup")] = self:_action("n_pull_popup"), [popups.mapping_for("PushPopup")] = self:_action("n_push_popup"), diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 1efc135f6..724cc11aa 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -2,6 +2,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local common = require("neogit.buffers.common") +local config = require("neogit.config") local a = require("plenary.async") local col = Ui.col @@ -359,6 +360,78 @@ local SectionItemCommit = Component.new(function(item) end end + -- Render date + if item.commit.rel_date:match(" years?,") then + item.commit.rel_date, _ = item.commit.rel_date:gsub(" years?,", "y") + item.commit.rel_date = item.commit.rel_date .. " " + elseif item.commit.rel_date:match("^%d ") then + item.commit.rel_date = " " .. item.commit.rel_date + end + + -- Render author and date in margin + local state = require("neogit.lib.state") + local visibility = state.get({ "margin", "visibility" }, false) + local margin_date_style = state.get({ "margin", "date_style" }, 1) + local details = state.get({ "margin", "details" }, false) + local date + local author_table = { "" } + local date_table + local date_width = 10 + local clamp_width = 30 -- to avoid having too much space when relative date is short + + if margin_date_style == 1 then -- relative date (short) + local unpacked = vim.split(item.commit.rel_date, " ") + -- above, we added a space if the rel_date started with a single number + -- we get the last two elements to deal with that + local date_number = unpacked[#unpacked - 1] + local date_quantifier = unpacked[#unpacked] + if date_quantifier:match("months?") then + date_quantifier = date_quantifier:gsub("m", "M") -- to distinguish from minutes + end + -- add back the space if we have a single number + local left_pad + if #unpacked > 2 then + left_pad = " " + else + left_pad = "" + end + date = left_pad .. date_number .. date_quantifier:sub(1, 1) + date_width = 3 + clamp_width = 23 + elseif margin_date_style == 2 then -- relative date (long) + date = item.commit.rel_date + date_width = 10 + else -- local iso date + if config.values.log_date_format == nil then + -- we get the unix date to be able to convert the date to + -- the local timezone + date = os.date("%Y-%m-%d %H:%M", item.commit.unix_date) + date_width = 16 -- TODO: what should the width be here? + else + date = item.commit.log_date + date_width = 16 + end + end + + date_table = { util.str_min_width(date, date_width), "Special" } + + if details then + author_table = { + util.str_clamp(item.commit.author_name, clamp_width - (#date > date_width and #date or date_width)), + "NeogitGraphAuthor", + } + end + + if visibility then + Virt = { + { " ", "Constant" }, + author_table, + date_table, + } + else + Virt = {} + end + return row( util.merge( { text.highlight("NeogitObjectId")(item.commit.abbreviated_commit) }, @@ -367,7 +440,12 @@ local SectionItemCommit = Component.new(function(item) ref_last, { text(item.commit.subject) } ), - { oid = item.commit.oid, yankable = item.commit.oid, item = item } + { + virtual_text = Virt, + oid = item.commit.oid, + yankable = item.commit.oid, + item = item, + } ) end) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 311274be2..295f9c6b3 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -238,6 +238,7 @@ end ---| "PushPopup" ---| "CommitPopup" ---| "LogPopup" +---| "MarginPopup" ---| "RevertPopup" ---| "StashPopup" ---| "IgnorePopup" @@ -626,6 +627,7 @@ function M.get_default_values() ["c"] = "CommitPopup", ["f"] = "FetchPopup", ["l"] = "LogPopup", + ["L"] = "MarginPopup", ["m"] = "MergePopup", ["p"] = "PullPopup", ["r"] = "RebasePopup", diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b9fef523e..236b4c00c 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -30,6 +30,7 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)" ---@field verification_flag string? ---@field rel_date string ---@field log_date string +---@field unix_date string ---Parses the provided list of lines into a CommitLogEntry ---@param raw string[] @@ -340,6 +341,7 @@ local function format(show_signature) committer_date = "%cD", rel_date = "%cr", log_date = "%cd", + unix_date = "%ct", } if show_signature then diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index cede6c0c6..28736adfb 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -75,6 +75,9 @@ M.popups = function(env) { "LogPopup", "Log", popups.open("log", function(p) p(env.log) end) }, + { "MarginPopup", "Margin", popups.open("margin", function(p) + p(env.log) + end) }, { "CherryPickPopup", "Cherry Pick", diff --git a/lua/neogit/popups/margin/actions.lua b/lua/neogit/popups/margin/actions.lua new file mode 100644 index 000000000..5c4cba642 --- /dev/null +++ b/lua/neogit/popups/margin/actions.lua @@ -0,0 +1,25 @@ +local M = {} + +local state = require("neogit.lib.state") + +function M.toggle_visibility() + local visibility = state.get({ "margin", "visibility" }, false) + local new_visibility = not visibility + state.set({ "margin", "visibility" }, new_visibility) +end + +function M.cycle_date_style() + local styles = { "relative_short", "relative_long", "local_datetime" } + local current_index = state.get({ "margin", "date_style" }, #styles) + local next_index = (current_index % #styles) + 1 -- wrap around to the first style + + state.set({ "margin", "date_style" }, next_index) +end + +function M.toggle_details() + local details = state.get({ "margin", "details" }, false) + local new_details = not details + state.set({ "margin", "details" }, new_details) +end + +return M diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua new file mode 100644 index 000000000..ef129c564 --- /dev/null +++ b/lua/neogit/popups/margin/init.lua @@ -0,0 +1,49 @@ +local popup = require("neogit.lib.popup") +local config = require("neogit.config") +local actions = require("neogit.popups.margin.actions") + +local M = {} + +function M.create(env) + local p = popup + .builder() + :name("NeogitMarginPopup") + :option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" }) + :switch("o", "topo", "Order commits by", { + cli_suffix = "-order", + options = { + { display = "", value = "" }, + { display = "topo", value = "topo" }, + { display = "author-date", value = "author-date" }, + { display = "date", value = "date" }, + }, + }) + :switch("g", "graph", "Show graph", { + enabled = true, + internal = true, + incompatible = { "reverse" }, + dependent = { "color" }, + }) + :switch_if( + config.values.graph_style == "ascii" or config.values.graph_style == "kitty", + "c", + "color", + "Show graph in color", + { internal = true, incompatible = { "reverse" } } + ) + :switch("d", "decorate", "Show refnames", { enabled = true, internal = true }) + :group_heading("Refresh") + :action("g", "buffer", actions.log_current) + :new_action_group("Margin") + :action("L", "toggle visibility", actions.toggle_visibility) + :action("l", "cycle style", actions.cycle_date_style) + :action("d", "toggle details", actions.toggle_details) + :action("x", "toggle shortstat", actions.log_current) + :build() + + p:show() + + return p +end + +return M From 43d30618640bf45b2e4a738b7115055489afed49 Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Wed, 7 May 2025 18:48:28 -0400 Subject: [PATCH 281/437] refactor(margin): fix linting violations --- lua/neogit/buffers/status/ui.lua | 7 ++++--- lua/neogit/popups/margin/init.lua | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 724cc11aa..f693ec844 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -422,14 +422,15 @@ local SectionItemCommit = Component.new(function(item) } end + local virt if visibility then - Virt = { + virt = { { " ", "Constant" }, author_table, date_table, } else - Virt = {} + virt = {} end return row( @@ -441,7 +442,7 @@ local SectionItemCommit = Component.new(function(item) { text(item.commit.subject) } ), { - virtual_text = Virt, + virtual_text = virt, oid = item.commit.oid, yankable = item.commit.oid, item = item, diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index ef129c564..ae1e86645 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -4,7 +4,7 @@ local actions = require("neogit.popups.margin.actions") local M = {} -function M.create(env) +function M.create() local p = popup .builder() :name("NeogitMarginPopup") From 073995d83daaf8fb1c7f7e95dac55fd48a30d927 Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Thu, 8 May 2025 11:48:27 -0400 Subject: [PATCH 282/437] fix(margin): env name in help popup action --- lua/neogit/popups/help/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 28736adfb..7b03c6f1f 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -76,7 +76,7 @@ M.popups = function(env) p(env.log) end) }, { "MarginPopup", "Margin", popups.open("margin", function(p) - p(env.log) + p(env.margin) end) }, { "CherryPickPopup", From 5276f99ccaee940b837b7912dbca1a92f8067dae Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Thu, 8 May 2025 11:51:46 -0400 Subject: [PATCH 283/437] test(margin): add margin popup spec Also had to change the help popup slightly: because columns are calculated dynamically, adding an entry for the margin popup changed the order of some of the existing entries. --- spec/popups/help_popup_spec.rb | 14 +++++++------- spec/popups/margin_popup_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 spec/popups/margin_popup_spec.rb diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index 8b3846a4d..1199d28fc 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -10,17 +10,17 @@ " Commands Applying changes Essential commands ", " $ History M Remote Stage all Refresh ", " A Cherry Pick m Merge K Untrack Go to file ", - " b Branch P Push s Stage Toggle ", - " B Bisect p Pull S Stage unstaged ", + " b Branch p Pull s Stage Toggle ", + " B Bisect P Push S Stage unstaged ", " c Commit Q Command u Unstage ", " d Diff r Rebase U Unstage all ", " f Fetch t Tag x Discard ", - " I Init v Revert ", - " i Ignore w Worktree ", - " l Log X Reset ", - " Z Stash " + " i Ignore v Revert ", + " I Init w Worktree ", + " L Margin X Reset ", + " l Log Z Stash " ] end - %w[$ A b B c d f i I l M m P p r t v w X Z].each { include_examples "interaction", _1 } + %w[$ A b B c d f i I l L M m P p r t v w X Z].each { include_examples "interaction", _1 } end diff --git a/spec/popups/margin_popup_spec.rb b/spec/popups/margin_popup_spec.rb new file mode 100644 index 000000000..66897e0e3 --- /dev/null +++ b/spec/popups/margin_popup_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Margin Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup + before { nvim.keys("L") } + + let(:view) do + [ + " Arguments ", + " -n Limit number of commits (--max-count=256) ", + " -o Order commits by (--[topo|author-date|date]-order) ", + " -g Show graph (--graph) ", + " -c Show graph in color (--color) ", + " -d Show refnames (--decorate) ", + " ", + " Refresh Margin ", + " g buffer L toggle visibility ", + " l cycle style ", + " d toggle details ", + " x toggle shortstat " + ] + end + + %w[L l d].each { include_examples "interaction", _1 } +end From 53f58aee0cedb9c4a7872ebcac22a5ddf9b6d6a6 Mon Sep 17 00:00:00 2001 From: Garrett Hopper Date: Sat, 10 May 2025 13:20:17 -0500 Subject: [PATCH 284/437] Remove unused references --- README.md | 6 ------ doc/neogit.txt | 6 ------ lua/neogit/config.lua | 8 -------- 3 files changed, 20 deletions(-) diff --git a/README.md b/README.md index 9de0281f0..cef71dde9 100644 --- a/README.md +++ b/README.md @@ -197,12 +197,6 @@ neogit.setup { merge_editor = { kind = "auto", }, - description_editor = { - kind = "auto", - }, - tag_editor = { - kind = "auto", - }, preview_buffer = { kind = "floating_console", }, diff --git a/doc/neogit.txt b/doc/neogit.txt index 893631c6c..36c60a440 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -223,12 +223,6 @@ to Neovim users. merge_editor = { kind = "auto", }, - description_editor = { - kind = "auto", - }, - tag_editor = { - kind = "auto", - }, preview_buffer = { kind = "floating_console", }, diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 311274be2..573f85e9d 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -357,8 +357,6 @@ end ---@field reflog_view? NeogitConfigPopup Reflog view options ---@field refs_view? NeogitConfigPopup Refs view options ---@field merge_editor? NeogitConfigPopup Merge editor options ----@field description_editor? NeogitConfigPopup Merge editor options ----@field tag_editor? NeogitConfigPopup Tag editor options ---@field preview_buffer? NeogitConfigPopup Preview options ---@field popup? NeogitConfigPopup Set the default way of opening popups ---@field signs? NeogitConfigSigns Signs used for toggled regions @@ -472,12 +470,6 @@ function M.get_default_values() merge_editor = { kind = "auto", }, - description_editor = { - kind = "auto", - }, - tag_editor = { - kind = "auto", - }, preview_buffer = { kind = "floating_console", }, From dc79c4b0da23e0ab36793009e0bbde109c84759e Mon Sep 17 00:00:00 2001 From: Garrett Hopper Date: Mon, 12 May 2025 13:03:25 -0500 Subject: [PATCH 285/437] Fix snacks.picker confirm action and on_close callback --- lua/neogit/lib/finder.lua | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index b24f9d47d..2b01866ba 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -160,19 +160,26 @@ end ---@param on_select fun(item: any|nil) ---@param allow_multi boolean ---@param refocus_status boolean -local function snacks_actions(on_select, allow_multi, refocus_status) - local function refresh(picker) - picker:close() +local function snacks_confirm(on_select, allow_multi, refocus_status) + local completed = false + local function complete(selection) + if completed then + return + end + on_select(selection) + completed = true if refocus_status then refocus_status_buffer() end end - local function confirm(picker, item) local selection = {} local picker_selected = picker:selected { fallback = true } - if #picker_selected > 1 then + if #picker_selected == 0 then + complete(nil) + picker:close() + elseif #picker_selected > 1 then for _, item in ipairs(picker_selected) do table.insert(selection, item.text) end @@ -190,18 +197,16 @@ local function snacks_actions(on_select, allow_multi, refocus_status) end if selection and selection[1] and selection[1] ~= "" then - on_select(allow_multi and selection or selection[1]) + complete(allow_multi and selection or selection[1]) + picker:close() end - - refresh(picker) end - local function close(picker) - on_select(nil) - refresh(picker) + local function on_close() + complete(nil) end - return { confirm = confirm, close = close } + return confirm, on_close end --- Utility function to map finder opts to fzf @@ -335,6 +340,7 @@ function Finder:find(on_select) mini_pick.start { source = { items = self.entries, choose = on_select } } elseif config.check_integration("snacks") then local snacks_picker = require("snacks.picker") + local confirm, on_close = snacks_confirm(on_select, self.opts.allow_multi, self.opts.refocus_status) snacks_picker.pick(nil, { title = "Neogit", prompt = string.format("%s > ", self.opts.prompt_prefix), @@ -346,7 +352,8 @@ function Finder:find(on_select) height = self.opts.layout_config.height, border = self.opts.border and "rounded" or "none", }, - actions = snacks_actions(on_select, self.opts.allow_multi, self.opts.refocus_status), + confirm = confirm, + on_close = on_close, }) else vim.ui.select(self.entries, { From d90cba10ac2713120d8ec038a3a67b22a540ac1d Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:33:14 +0100 Subject: [PATCH 286/437] Add more information in a Hunk --- lua/neogit/lib/git/diff.lua | 7 +++++++ tests/specs/neogit/lib/git/log_spec.lua | 3 +++ 2 files changed, 10 insertions(+) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 15bea3182..d8c514975 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -24,12 +24,14 @@ local sha256 = vim.fn.sha256 ---@field deletions number --- ---@class Hunk +---@field file string ---@field index_from number ---@field index_len number ---@field diff_from number ---@field diff_to number ---@field first number First line number in buffer ---@field last number Last line number in buffer +---@field lines string[] --- ---@class DiffStagedStats ---@field summary string @@ -224,6 +226,11 @@ local function parse_diff(raw_diff, raw_stats) local file = build_file(header, kind) local stats = parse_diff_stats(raw_stats or {}) + util.map(hunks, function(hunk) + hunk.file = file + return hunk + end) + return { ---@type Diff kind = kind, lines = lines, diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 446eaa07e..f3618c353 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -96,6 +96,7 @@ describe("lib.git.log.parse", function() index_from = 692, index_len = 33, length = 40, + file = "lua/neogit/status.lua", line = "@@ -692,33 +692,28 @@ end", lines = { " ---@param first_line number", @@ -149,6 +150,7 @@ describe("lib.git.log.parse", function() index_from = 734, index_len = 14, length = 15, + file = "lua/neogit/status.lua", line = "@@ -734,14 +729,10 @@ function M.get_item_hunks(item, first_line, last_line, partial)", lines = { " setmetatable(o, o)", @@ -290,6 +292,7 @@ describe("lib.git.log.parse", function() index_len = 7, length = 9, line = "@@ -1,7 +1,9 @@", + file = "LICENSE", lines = { " MIT License", " ", From bef89cccd525f8159fa1e23b02ce8fa8ea0c86b0 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:33:58 +0100 Subject: [PATCH 287/437] Use only Hunk in `generate_patch` --- lua/neogit/buffers/status/actions.lua | 25 +++++++++++----- lua/neogit/lib/git/index.lua | 36 ++++++++--------------- lua/neogit/lib/ui/init.lua | 11 ++----- tests/specs/neogit/lib/git/index_spec.lua | 8 ++--- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 52bfdd76b..d2c11dd1f 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -122,7 +122,8 @@ M.v_discard = function(self) for _, hunk in ipairs(hunks) do table.insert(invalidated_diffs, "*:" .. item.name) table.insert(patches, function() - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + local patch = + git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) logger.debug(("Discarding Patch: %s"):format(patch)) @@ -233,7 +234,7 @@ M.v_stage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + table.insert(patches, git.index.generate_patch(hunk.hunk, { from = hunk.from, to = hunk.to })) end else if section.name == "unstaged" then @@ -283,7 +284,10 @@ M.v_unstage = function(self) if #hunks > 0 then for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to, true)) + table.insert( + patches, + git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) + ) end else table.insert(files, item.escaped_path) @@ -783,11 +787,16 @@ M.n_discard = function(self) local hunk = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] - local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) + local patch = git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) if section == "untracked" then message = "Discard hunk?" action = function() + local hunks = + self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) + + local patch = + git.index.generate_patch(hunks[1], { from = hunks[1].from, to = hunks[1].to, reverse = true }) git.index.apply(patch, { reverse = true }) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } @@ -1089,7 +1098,7 @@ M.n_stage = function(self) local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + local patch = git.index.generate_patch(stagable.hunk) git.index.apply(patch, { cached = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.name } }, "n_stage") elseif stagable.filename then @@ -1163,8 +1172,10 @@ M.n_unstage = function(self) if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") - local patch = - git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) + local patch = git.index.generate_patch( + unstagable.hunk, + { from = unstagable.hunk.from, to = unstagable.hunk.to, reverse = true } + ) git.index.apply(patch, { cached = true, reverse = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.name } }, "n_unstage") diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 35b9c8cfe..54837376e 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -6,19 +6,15 @@ local util = require("neogit.lib.util") local M = {} ---Generates a patch that can be applied to index ----@param item any ---@param hunk Hunk ----@param from number ----@param to number ----@param reverse boolean|nil +---@param opts table|nil ---@return string -function M.generate_patch(item, hunk, from, to, reverse) - reverse = reverse or false +function M.generate_patch(hunk, opts) + opts = opts or { reverse = false, cached = false, index = false } + local reverse = opts.reverse - if not from and not to then - from = hunk.diff_from + 1 - to = hunk.diff_to - end + local from = opts.from or 1 + local to = opts.to or (hunk.diff_to - hunk.diff_from) assert(from <= to, string.format("from must be less than or equal to to %d %d", from, to)) if from > to then @@ -29,35 +25,31 @@ function M.generate_patch(item, hunk, from, to, reverse) local len_start = hunk.index_len local len_offset = 0 - -- + 1 skips the hunk header, since we construct that manually afterwards - -- TODO: could use `hunk.lines` instead if this is only called with the `SelectedHunk` type - for k = hunk.diff_from + 1, hunk.diff_to do - local v = item.diff.lines[k] - local operand, line = v:match("^([+ -])(.*)") - + for k, line in pairs(hunk.lines) do + local operand, l = line:match("^([+ -])(.*)") if operand == "+" or operand == "-" then if from <= k and k <= to then len_offset = len_offset + (operand == "+" and 1 or -1) - table.insert(diff_content, v) + table.insert(diff_content, line) else -- If we want to apply the patch normally, we need to include every `-` line we skip as a normal line, -- since we want to keep that line. if not reverse then if operand == "-" then - table.insert(diff_content, " " .. line) + table.insert(diff_content, " " .. l) end -- If we want to apply the patch in reverse, we need to include every `+` line we skip as a normal line, since -- it's unchanged as far as the diff is concerned and should not be reversed. -- We also need to adapt the original line offset based on if we skip or not elseif reverse then if operand == "+" then - table.insert(diff_content, " " .. line) + table.insert(diff_content, " " .. l) end len_start = len_start + (operand == "-" and -1 or 1) end end else - table.insert(diff_content, v) + table.insert(diff_content, line) end end @@ -68,9 +60,7 @@ function M.generate_patch(item, hunk, from, to, reverse) ) local worktree_root = git.repo.worktree_root - - assert(item.absolute_path, "Item is not a path") - local path = Path:new(item.absolute_path):make_relative(worktree_root) + local path = Path:new(hunk.file):make_relative(worktree_root) table.insert(diff_content, 1, string.format("+++ b/%s", path)) table.insert(diff_content, 1, string.format("--- a/%s", path)) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 38fb32239..c4e8ce879 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -182,25 +182,19 @@ function Ui:item_hunks(item, first_line, last_line, partial) if not item.folded and item.diff.hunks then for _, h in ipairs(item.diff.hunks) do - if h.first <= last_line and h.last >= first_line then + if h.first <= first_line and h.last >= last_line then local from, to if partial then - local cursor_offset = first_line - h.first local length = last_line - first_line - from = h.diff_from + cursor_offset + from = first_line - h.first to = from + length else from = h.diff_from + 1 to = h.diff_to end - local hunk_lines = {} - for i = from, to do - table.insert(hunk_lines, item.diff.lines[i]) - end - -- local conflict = false -- for _, n in ipairs(conflict_markers) do -- if from <= n and n <= to then @@ -214,7 +208,6 @@ function Ui:item_hunks(item, first_line, last_line, partial) to = to, __index = h, hunk = h, - lines = hunk_lines, -- conflict = conflict, } diff --git a/tests/specs/neogit/lib/git/index_spec.lua b/tests/specs/neogit/lib/git/index_spec.lua index 3d1be1cc6..cc0358087 100644 --- a/tests/specs/neogit/lib/git/index_spec.lua +++ b/tests/specs/neogit/lib/git/index_spec.lua @@ -10,17 +10,15 @@ local function run_with_hunk(hunk, from, to, reverse) local header_matches = vim.fn.matchlist(lines[1], "@@ -\\(\\d\\+\\),\\(\\d\\+\\) +\\(\\d\\+\\),\\(\\d\\+\\) @@") return generate_patch_from_selection({ - name = "test.txt", - absolute_path = "test.txt", - diff = { lines = lines }, - }, { first = 1, last = #lines, index_from = header_matches[2], index_len = header_matches[3], diff_from = diff_from, diff_to = #lines, - }, diff_from + from, diff_from + to, reverse) + lines = vim.list_slice(lines, 2), + file = "test.txt", + }, { from = from, to = to, reverse = reverse }) end describe("patch creation", function() From f633d67f91c1a0ed87a3daaebc372a014c7c04f3 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Sun, 15 Dec 2024 17:34:49 +0100 Subject: [PATCH 288/437] Add ability to revert hunk --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/lib/git/revert.lua | 5 +++++ lua/neogit/popups/revert/actions.lua | 8 ++++++++ lua/neogit/popups/revert/init.lua | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 90942a931..136004f0e 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -290,7 +290,7 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid } } + p { commits = { self.commit_info.oid }, hunk = self.buffer.ui:get_hunk_or_filename_under_cursor() } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 797ca36be..b84ee8921 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -16,6 +16,11 @@ function M.commits(commits, args) end end +function M.hunk(hunk, _) + local patch = git.index.generate_patch(hunk, { reverse = true }) + git.index.apply(patch, { reverse = true }) +end + function M.continue() git.cli.revert.continue.no_edit.call { pty = true } end diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 7d49c28ae..9f4740005 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -62,6 +62,14 @@ function M.changes(popup) end end +function M.hunk(popup) + local hunk = popup.state.env.hunk + if hunk == nil then + return + end + git.revert.hunk(hunk.hunk, popup:get_arguments()) +end + function M.continue() git.revert.continue() end diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index 092f16596..abc87d9c2 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,6 +23,7 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) + :action_if(not in_progress, "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) From 6a2f1aa28186b11d9ddfd9364002df2b2c11da04 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Mon, 16 Dec 2024 15:48:02 +0100 Subject: [PATCH 289/437] Change name from hunk to item Reads a bit better Change-Id: I2c5aaa5b68a140de49bd38c10b2dca4b10e15efe --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/popups/revert/actions.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 136004f0e..9561771d4 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -290,7 +290,7 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid }, hunk = self.buffer.ui:get_hunk_or_filename_under_cursor() } + p { commits = { self.commit_info.oid }, item = self.buffer.ui:get_hunk_or_filename_under_cursor() } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 9f4740005..d0a248474 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -63,11 +63,11 @@ function M.changes(popup) end function M.hunk(popup) - local hunk = popup.state.env.hunk - if hunk == nil then + local item = popup.state.env.item + if item == nil then return end - git.revert.hunk(hunk.hunk, popup:get_arguments()) + git.revert.hunk(item.hunk, popup:get_arguments()) end function M.continue() From 26996b904421305402a48c63b76187dde811eb6d Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Mon, 16 Dec 2024 15:48:24 +0100 Subject: [PATCH 290/437] Only show hunk actions if on a hunk Change-Id: I266a58026636cee04e1d825545df0f047324c068 --- lua/neogit/popups/revert/actions.lua | 6 +----- lua/neogit/popups/revert/init.lua | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index d0a248474..cc0004890 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -63,11 +63,7 @@ function M.changes(popup) end function M.hunk(popup) - local item = popup.state.env.item - if item == nil then - return - end - git.revert.hunk(item.hunk, popup:get_arguments()) + git.revert.hunk(popup.state.env.item.hunk, popup:get_arguments()) end function M.continue() diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index abc87d9c2..d5863165b 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,7 +23,7 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) - :action_if(not in_progress, "h", "Hunk", actions.hunk) + :action_if(((not in_progress) and env.item ~= nil), "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) From 05106927c3a75588d42cf9abde438143c97b5ec0 Mon Sep 17 00:00:00 2001 From: "Philip J." Date: Tue, 24 Dec 2024 16:33:43 +0100 Subject: [PATCH 291/437] Fix discarding hunk among many --- lua/neogit/buffers/status/actions.lua | 7 +------ lua/neogit/lib/git/index.lua | 6 ++---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index d2c11dd1f..2d227e377 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -787,16 +787,11 @@ M.n_discard = function(self) local hunk = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] - local patch = git.index.generate_patch(hunk, { from = hunk.from, to = hunk.to, reverse = true }) + local patch = git.index.generate_patch(hunk, { reverse = true }) if section == "untracked" then message = "Discard hunk?" action = function() - local hunks = - self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) - - local patch = - git.index.generate_patch(hunks[1], { from = hunks[1].from, to = hunks[1].to, reverse = true }) git.index.apply(patch, { reverse = true }) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 54837376e..535eab9d0 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -10,16 +10,14 @@ local M = {} ---@param opts table|nil ---@return string function M.generate_patch(hunk, opts) - opts = opts or { reverse = false, cached = false, index = false } + opts = opts or { reverse = false } + local reverse = opts.reverse local from = opts.from or 1 local to = opts.to or (hunk.diff_to - hunk.diff_from) assert(from <= to, string.format("from must be less than or equal to to %d %d", from, to)) - if from > to then - from, to = to, from - end local diff_content = {} local len_start = hunk.index_len From 8ed8b494817039c40c6a9a15508b2ee02f541a16 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 6 May 2025 22:07:27 +0200 Subject: [PATCH 292/437] Assert hunk should always have a path --- lua/neogit/lib/git/index.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 535eab9d0..cad417ec9 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -58,6 +58,8 @@ function M.generate_patch(hunk, opts) ) local worktree_root = git.repo.worktree_root + assert(hunk.file, "hunk has no filepath") + local path = Path:new(hunk.file):make_relative(worktree_root) table.insert(diff_content, 1, string.format("+++ b/%s", path)) From 29d1e30289c83e163eec3bb641a0041cedb43132 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 6 May 2025 22:07:41 +0200 Subject: [PATCH 293/437] Generalize pattern: letter here is not always 'a' --- lua/neogit/lib/git/diff.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index d8c514975..64d3be1fa 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -96,7 +96,7 @@ end ---@return string local function build_file(header, kind) if kind == "modified" then - return header[3]:match("%-%-%- a/(.*)") + return header[3]:match("%-%-%- ./(.*)") elseif kind == "renamed" then return ("%s -> %s"):format(header[3]:match("rename from (.*)"), header[4]:match("rename to (.*)")) elseif kind == "new file" then From 7aa80b47869babb0fa02c0b6e6e494a140aea005 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 10:36:05 +0200 Subject: [PATCH 294/437] Add an env getter method to popup to make accessing values nicer. Simplify revert popup env by passing in hunk, if it is present --- lua/neogit/buffers/commit_view/init.lua | 3 ++- lua/neogit/lib/popup/init.lua | 10 ++++++++++ lua/neogit/popups/revert/actions.lua | 2 +- lua/neogit/popups/revert/init.lua | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 9561771d4..2fbfb1ee0 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -290,7 +290,8 @@ function M:open(kind) end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.commit_info.oid }, item = self.buffer.ui:get_hunk_or_filename_under_cursor() } + local item = self.buffer.ui:get_hunk_or_filename_under_cursor() or {} + p { commits = { self.commit_info.oid }, hunk = item.hunk } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.commit_info.oid } diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index e88bb6dd3..9d48a7d83 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -64,6 +64,16 @@ function M:get_arguments() return flags end +---@param key string +---@return any|nil +function M:get_env(key) + if not self.state.env then + return nil + end + + return self.state.env[key] +end + -- Returns a table of key/value pairs, where the key is the name of the switch, and value is `true`, for all -- enabled arguments that are NOT for cli consumption (internal use only). ---@return table diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index cc0004890..2ecd010c5 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -63,7 +63,7 @@ function M.changes(popup) end function M.hunk(popup) - git.revert.hunk(popup.state.env.item.hunk, popup:get_arguments()) + git.revert.hunk(popup:get_env("hunk"), popup:get_arguments()) end function M.continue() diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index d5863165b..e697521e0 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -23,7 +23,7 @@ function M.create(env) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) - :action_if(((not in_progress) and env.item ~= nil), "h", "Hunk", actions.hunk) + :action_if(((not in_progress) and env.hunk ~= nil), "h", "Hunk", actions.hunk) :action_if(in_progress, "v", "continue", actions.continue) :action_if(in_progress, "s", "skip", actions.skip) :action_if(in_progress, "a", "abort", actions.abort) From c7188839f6995367d0341e5f06836e090e42a00e Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 10:42:44 +0200 Subject: [PATCH 295/437] Assert that a line has an OID when parsing the rebase-todo file. --- lua/neogit/lib/git/rebase.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 942bd5680..2941448ee 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -248,13 +248,15 @@ function M.update_rebase_status(state) for line in todo:iter() do if line:match("^[^#]") and line ~= "" then local oid = line:match("^%w+ (%x+)") - table.insert(state.rebase.items, { - done = false, - action = line:match("^(%w+) "), - oid = oid, - abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), - subject = line:match("^%w+ %x+ (.+)$"), - }) + if oid then + table.insert(state.rebase.items, { + done = false, + action = line:match("^(%w+) "), + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), + subject = line:match("^%w+ %x+ (.+)$"), + }) + end end end end From a7ba80135c8327bd6db15059eaa770097b895095 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 10:43:27 +0200 Subject: [PATCH 296/437] Add sign-off, strategy, and gpg-sign to revert popup --- lua/neogit/popups/revert/init.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index e697521e0..dbefc20ac 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -8,18 +8,21 @@ function M.create(env) local in_progress = git.sequencer.pick_or_revert_in_progress() -- TODO: enabled = true needs to check if incompatible switch is toggled in internal state, and not apply. -- if you enable 'no edit', and revert, next time you load the popup both will be enabled - -- - -- :option("s", "strategy", "", "Strategy") - -- :switch("s", "signoff", "Add Signed-off-by lines") - -- :option("S", "gpg-sign", "", "Sign using gpg") - -- stylua: ignore local p = popup .builder() :name("NeogitRevertPopup") :option_if(not in_progress, "m", "mainline", "", "Replay merge relative to parent") :switch_if(not in_progress, "e", "edit", "Edit commit messages", { enabled = true, incompatible = { "no-edit" } }) :switch_if(not in_progress, "E", "no-edit", "Don't edit commit messages", { incompatible = { "edit" } }) + :switch_if(not in_progress, "s", "signoff", "Add Signed-off-by lines") + :option_if(not in_progress, "s", "strategy", "", "Strategy", { + key_prefix = "=", + choices = { "octopus", "ours", "resolve", "subtree", "recursive" }, + }) + :option_if(not in_progress, "S", "gpg-sign", "", "Sign using gpg", { + key_prefix = "-", + }) :group_heading("Revert") :action_if(not in_progress, "v", "Commit(s)", actions.commits) :action_if(not in_progress, "V", "Changes", actions.changes) From db930ddb1536670b04ffcb0f5a9e47f4235c7937 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 10:44:50 +0200 Subject: [PATCH 297/437] Add skeleton for new actions to commit popup --- lua/neogit/lib/popup/builder.lua | 11 +++++++++++ lua/neogit/lib/popup/ui.lua | 2 ++ lua/neogit/popups/commit/init.lua | 10 +++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index cd2920425..524270735 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -425,6 +425,17 @@ function M:config_if(cond, key, name, options) return self end +---Inserts a blank slot +---@return self +function M:spacer() + table.insert(self.state.actions[#self.state.actions], { + keys = "", + description = "", + heading = "" + }) + return self +end + -- Adds an action to the popup ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI diff --git a/lua/neogit/lib/popup/ui.lua b/lua/neogit/lib/popup/ui.lua index 29f758bf6..b8f78e39a 100644 --- a/lua/neogit/lib/popup/ui.lua +++ b/lua/neogit/lib/popup/ui.lua @@ -196,6 +196,8 @@ local function render_action(action) -- selene: allow(empty_if) if action.keys == nil then -- Action group heading + elseif action.keys == "" then + table.insert(items, text("")) -- spacer elseif #action.keys == 0 then table.insert(items, text.highlight("NeogitPopupActionDisabled")("_")) else diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index 7885281ce..f654d588e 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -21,13 +21,17 @@ function M.create(env) :action("x", "Absorb", actions.absorb) :new_action_group("Edit HEAD") :action("e", "Extend", actions.extend) - :action("w", "Reword", actions.reword) + :spacer() :action("a", "Amend", actions.amend) + :spacer() + :action("w", "Reword", actions.reword) :new_action_group("Edit") :action("f", "Fixup", actions.fixup) :action("s", "Squash", actions.squash) - :action("A", "Augment", actions.augment) - :new_action_group() + :action("A", "Alter") + :action("n", "Augment", actions.augment) + :action("W", "Revise") + :new_action_group("Edit and rebase") :action("F", "Instant Fixup", actions.instant_fixup) :action("S", "Instant Squash", actions.instant_squash) :env({ highlight = { "HEAD" }, commit = env.commit }) From ae0eeca0d925ff8e8dde0701086b38f9f28d2462 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 11:47:36 +0200 Subject: [PATCH 298/437] Update docs for Popup Builder and Customizing popup APIs --- doc/neogit.txt | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 36c60a440..4f7a80e61 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -18,6 +18,8 @@ CONTENTS *neogit_contents* 4. Events |neogit_events| 5. Highlights |neogit_highlights| 6. API |neogit_api| + • Popup Builder |neogit_popup_builder| + • Customizing Popups |neogit_custom_popups| 7. Usage |neogit_usage| 8. Popups *neogit_popups* • Bisect |neogit_bisect_popup| @@ -1001,7 +1003,7 @@ Actions: *neogit_branch_popup_actions* the old branch. • Checkout new worktree *neogit_branch_checkout_worktree* - (Not yet implemented) + see: *neogit_worktree_checkout* • Create new branch *neogit_branch_create_branch* Functionally the same as |neogit_branch_checkout_new|, but does not update @@ -1012,7 +1014,7 @@ Actions: *neogit_branch_popup_actions* index has uncommitted changes, will behave exactly the same as spin_off. • Create new worktree *neogit_branch_create_worktree* - (Not yet implemented) + see: *neogit_worktree_create_branch* • Configure *neogit_branch_configure* Opens selector to choose a branch, then offering some configuration @@ -1842,7 +1844,7 @@ Actions: *neogit_reset_popup_actions* changes. • Worktree *neogit_reset_worktree* - (Not yet implemented) + Resets current worktree to specified commit. ============================================================================== Stash Popup *neogit_stash_popup* @@ -2071,7 +2073,7 @@ The following keys, in normal mode, will act on the commit under the cursor: • `` Open current commit in Commit Buffer ============================================================================== -Custom Popups *neogit_custom_popups* +Popup Builder *neogit_popup_builder* You can leverage Neogit's infrastructure to create your own popups and actions. For example, you can define actions as a function which will take the @@ -2138,9 +2140,13 @@ calling the setup function: }) < -You can also customize existing popups via the Neogit config. +============================================================================== +Customizing Popups *neogit_custom_popups* + +You can customize existing popups via the Neogit config. + Below is an example of adding a custom switch, but you can use any function -from the builder API. +from the builder API. >lua require("neogit").setup({ builders = { @@ -2150,7 +2156,7 @@ from the builder API. }, }) -Keep in mind that builder hooks are executed at the end of the popup +Keep in mind that builder hooks are executed at the end of the popup builder, so any switches or options added will be placed at the end. ------------------------------------------------------------------------------ From e38021df11d05980471d80919d0dfd482fdd43ac Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 12:47:58 +0200 Subject: [PATCH 299/437] Reorganize documentation on autocmd events, and add docs for WorktreeCreate event --- doc/neogit.txt | 206 ++++++++++++++++--------- lua/neogit/popups/worktree/actions.lua | 38 +++++ 2 files changed, 167 insertions(+), 77 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 4f7a80e61..8d8eef21f 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -485,83 +485,135 @@ The following mappings can all be customized via the setup function. The following events are emitted by Neogit: - Event Description ~ - NeogitStatusRefreshed Status has been reloaded ~ - - Event Data: {} ~ - - NeogitCommitComplete Commit has been created ~ - - Event Data: {} ~ - - NeogitPushComplete Push has completed ~ - - Event Data: {} ~ - - NeogitPullComplete Pull has completed ~ - - Event Data: {} ~ - - NeogitFetchComplete Fetch has completed ~ - - Event Data: {} ~ - - NeogitBranchCreate Branch was created, starting from ~ - `base` ~ - - Event Data: ~ - { branch_name: string, base: string? } ~ - - NeogitBranchDelete Branch was deleted ~ - - Event Data: { branch_name: string } ~ - - NeogitBranchCheckout Branch was checked out ~ - - Event Data: { branch_name: string } ~ - - NeogitBranchReset Branch was reset to a commit/branch ~ - - Event Data: ~ - { branch_name: string, resetting_to: string } ~ - - NeogitBranchRename Branch was renamed ~ - - Event Data: ~ - { branch_name: string, new_name: string } ~ - - NeogitRebase A rebase finished ~ - - Event Data: ~ - { commit: string, status: "ok" | "conflict" } ~ - - NeogitReset A branch was reset to a certain commit ~ - - Event Data: ~ - { commit: string, mode: "soft" | ~ - "mixed" | "hard" | "keep" | "index" } ~ - - NeogitTagCreate A tag was placed on a certain commit ~ - - Event Data: { name: string, ref: string } ~ - - NeogitTagDelete A tag was removed ~ - - Event Data: { name: string } ~ - - NeogitCherryPick One or more commits were cherry-picked ~ - - Event Data: { commits: string[] } ~ - - NeogitMerge A merge finished ~ - - Event Data: ~ - { branch: string, args: string[], ~ - status: "ok" | "conflict" } ~ - - NeogitStash A stash finished ~ - - Event Data: { success: boolean } ~ +• `NeogitStatusRefreshed` + When: Status has been reloaded + Data: `{}` + +• `NeogitCommitComplete` + When: Commit has been created + Data: `{}` + +• `NeogitPushComplete` + When: Push has finished + Data: `{}` + +• `NeogitPullComplete` + When: Push has finished + Data: `{}` + +• `NeogitFetchComplete` + When: Fetch has finished + Data: `{}` + +• `NeogitBranchCreate` + When: Branch was created, starting from `` + Data: > + { + branch_name: string, + base: string? + } +< +• `NeogitBranchDelete` + When: Branch was deleted + Data: > + { + branch_name: string, + } +< +• `NeogitBranchCheckout` + When: Branch was checked out + Data: > + { + branch_name: string, + } +< +• `NeogitBranchReset` + When: Branch was reset to commit/branch + Data: > + { + branch_name: string, + resetting_to: string + } +< +• `NeogitBranchRename` + When: Branch was renamed + Data: > + { + branch_name: string, + new_name: string + } +< +• `NeogitRebase` + When: A rebase has finished + Data: > + { + commit: string, + status: "ok" | "conflict" + } +< +• `NeogitReset` + When: A reset has been performed + Data: > + { + commit: string, + mode: "soft" | "mixed" | "hard" | "keep" | "index" + } +< +• `NeogitTagCreate` + When: A tag is placed on a commit + Data: > + { + ref: string, + name: string + } +< +• `NeogitTagCreate` + When: A tag is placed on a commit + Data: > + { + ref: string, + name: string + } +< +• `NeogitTagDelete` + When: A tag is removed + Data: > + { + name: string + } +< +• `NeogitCherryPick` + When: One or more commits were cherry picked + Data: > + { + commits: string[] + } +< +• `NeogitMerge` + When: A merge has finished + Data: > + { + branch: string, + args: string[], + status: "ok" | "conflict" + } +< +• `NeogitStash` + When: A stash was performed + Data: > + { + success: boolean + } +< +• `NeogitWorktreeCreate` + When: A worktree was created + Data: > + { + old_cwd: string, + new_cwd: string, + copy_if_present: function(filename: string, callback: function|nil) + } +< ============================================================================== 5. Highlights *neogit_highlights* diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index ba4979d7a..9f42c921b 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -15,6 +15,36 @@ local function get_path(prompt) completion = "dir", prepend = vim.fs.normalize(vim.uv.cwd() .. "/..") .. "/", }) + +---@param old_cwd string? +---@param new_cwd string +---@return table +local function autocmd_helpers(old_cwd, new_cwd) + return { + old_cwd = old_cwd, + new_cwd = new_cwd, + ---@param filename string the file you want to copy + ---@param callback function? callback to run if copy was successful + copy_if_present = function(filename, callback) + assert(old_cwd, "couldn't resolve old cwd") + + local source = vim.fs.joinpath(old_cwd, filename) + local destination = vim.fs.joinpath(new_cwd, filename) + + if vim.uv.fs_stat(source) and not vim.uv.fs_stat(destination) then + local ok = vim.uv.fs_copyfile(source, destination) + if ok and type(callback) == "function" then + callback() + end + end + end + } +end + +---@param pattern string +---@param data table +local function fire_worktree_event(pattern, data) + vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false, data = data }) end ---@param prompt string @@ -37,10 +67,14 @@ function M.checkout_worktree() local success, err = git.worktree.add(selected, path) if success then + local cwd = vim.uv.cwd() notification.info("Added worktree") + if status.is_open() then status.instance():chdir(path) end + + fire_worktree_event("NeogitWorktreeCreate", autocmd_helpers(cwd, path)) else notification.error(err) end @@ -65,10 +99,14 @@ function M.create_worktree() if git.branch.create(name, selected) then local success, err = git.worktree.add(name, path) if success then + local cwd = vim.uv.cwd() notification.info("Added worktree") + if status.is_open() then status.instance():chdir(path) end + + fire_worktree_event("NeogitWorktreeCreate", autocmd_helpers(cwd, path)) else notification.error(err) end From 5e72294d2e4ed35ee36b1f1c205925edf32943eb Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 12:48:21 +0200 Subject: [PATCH 300/437] Improve worktree destination prompt: when the target dir isn't empty, make a new der with the branch name. --- lua/neogit/popups/worktree/actions.lua | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 9f42c921b..419fd126c 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -9,13 +9,25 @@ local notification = require("neogit.lib.notification") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") ---@param prompt string +---@param branch string? ---@return string|nil -local function get_path(prompt) - return input.get_user_input(prompt, { +local function get_path(prompt, branch) + local path = input.get_user_input(prompt, { completion = "dir", prepend = vim.fs.normalize(vim.uv.cwd() .. "/..") .. "/", }) + if path then + if branch and vim.uv.fs_stat(path) then + return vim.fs.joinpath(path, branch) + else + return path + end + else + return nil + end +end + ---@param old_cwd string? ---@param new_cwd string ---@return table @@ -60,7 +72,7 @@ function M.checkout_worktree() return end - local path = get_path(("Checkout '%s' in new worktree"):format(selected)) + local path = get_path(("Checkout '%s' in new worktree"):format(selected), selected) if not path then return end From 220a118e84026498bd2b9fa33afd14fd2c2a1539 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 12:48:55 +0200 Subject: [PATCH 301/437] Remove manual refresh calls: the watcher handles this --- lua/neogit/buffers/status/init.lua | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fbfb97ab7..8e3fa1565 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -212,35 +212,6 @@ function M:open(kind) Watcher.instance(self.root):register(self) buffer:move_cursor(buffer.ui:first_section().first) end, - user_autocmds = { - ["NeogitPushComplete"] = function() - self:dispatch_refresh(nil, "push_complete") - end, - ["NeogitPullComplete"] = function() - self:dispatch_refresh(nil, "pull_complete") - end, - ["NeogitFetchComplete"] = function() - self:dispatch_refresh(nil, "fetch_complete") - end, - ["NeogitRebase"] = function() - self:dispatch_refresh(nil, "rebase") - end, - ["NeogitMerge"] = function() - self:dispatch_refresh(nil, "merge") - end, - ["NeogitReset"] = function() - self:dispatch_refresh(nil, "reset_complete") - end, - ["NeogitStash"] = function() - self:dispatch_refresh(nil, "stash") - end, - ["NeogitRevertComplete"] = function() - self:dispatch_refresh(nil, "revert") - end, - ["NeogitCherryPick"] = function() - self:dispatch_refresh(nil, "cherry_pick") - end, - }, } return self From 2910752b3912505de84abe10559499f63a8d2a7c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 12:49:50 +0200 Subject: [PATCH 302/437] Dispatch a refresh when updating config values to ensure status buffer is in sync --- lua/neogit/lib/popup/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 9d48a7d83..394e0ebce 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -368,6 +368,7 @@ function M:mappings() mappings.n[config.id] = a.void(function() self:set_config(config) self:refresh() + Watcher.instance():dispatch_refresh() end) end end From 704297f2c266799ff0342dad22d9873aecdc4245 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 12:50:51 +0200 Subject: [PATCH 303/437] linting --- lua/neogit/lib/popup/builder.lua | 2 +- lua/neogit/popups/revert/init.lua | 8 +++++++- lua/neogit/popups/worktree/actions.lua | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 524270735..aedbfbc03 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -431,7 +431,7 @@ function M:spacer() table.insert(self.state.actions[#self.state.actions], { keys = "", description = "", - heading = "" + heading = "", }) return self end diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index dbefc20ac..c926e1e50 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -13,7 +13,13 @@ function M.create(env) .builder() :name("NeogitRevertPopup") :option_if(not in_progress, "m", "mainline", "", "Replay merge relative to parent") - :switch_if(not in_progress, "e", "edit", "Edit commit messages", { enabled = true, incompatible = { "no-edit" } }) + :switch_if( + not in_progress, + "e", + "edit", + "Edit commit messages", + { enabled = true, incompatible = { "no-edit" } } + ) :switch_if(not in_progress, "E", "no-edit", "Don't edit commit messages", { incompatible = { "edit" } }) :switch_if(not in_progress, "s", "signoff", "Add Signed-off-by lines") :option_if(not in_progress, "s", "strategy", "", "Strategy", { diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 419fd126c..1d560737d 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -49,7 +49,7 @@ local function autocmd_helpers(old_cwd, new_cwd) callback() end end - end + end, } end From eb28dfbc54f6065c29fae41aa110d382fee8390d Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 May 2025 16:06:04 +0200 Subject: [PATCH 304/437] Fix specs for new views --- spec/popups/commit_popup_spec.rb | 10 ++++++---- spec/popups/revert_popup_spec.rb | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/popups/commit_popup_spec.rb b/spec/popups/commit_popup_spec.rb index 9bc3b1749..ec42d9df3 100644 --- a/spec/popups/commit_popup_spec.rb +++ b/spec/popups/commit_popup_spec.rb @@ -18,15 +18,17 @@ " -S Sign using gpg (--gpg-sign=) ", " -C Reuse commit message (--reuse-message=) ", " ", - " Create Edit HEAD Edit ", + " Create Edit HEAD Edit Edit and rebase ", " c Commit e Extend f Fixup F Instant Fixup ", - " x Absorb w Reword s Squash S Instant Squash ", - " a Amend A Augment " + " x Absorb s Squash S Instant Squash ", + " a Amend A Alter ", + " n Augment ", + " w Reword W Revise " ] end %w[-a -e -v -h -R -A -s -S -C].each { include_examples "argument", _1 } - %w[c x e w a f s A F S].each { include_examples "interaction", _1 } + %w[c x e w a f s A F S n W].each { include_examples "interaction", _1 } describe "Actions" do describe "Create Commit" do diff --git a/spec/popups/revert_popup_spec.rb b/spec/popups/revert_popup_spec.rb index eb1736a03..996d6822f 100644 --- a/spec/popups/revert_popup_spec.rb +++ b/spec/popups/revert_popup_spec.rb @@ -11,6 +11,9 @@ " =m Replay merge relative to parent (--mainline=) ", " -e Edit commit messages (--edit) ", " -E Don't edit commit messages (--no-edit) ", + " -s Add Signed-off-by lines (--signoff) ", + " =s Strategy (--strategy=) ", + " -S Sign using gpg (--gpg-sign=) ", " ", " Revert ", " v Commit(s) ", @@ -19,5 +22,5 @@ end %w[v V].each { include_examples "interaction", _1 } - %w[=m -e -E].each { include_examples "argument", _1 } + %w[=m -e -E -s =s -S].each { include_examples "argument", _1 } end From fb6b117fd70bd8983d2d2d6032015f28ec4e27f2 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Wed, 14 May 2025 00:28:31 +0800 Subject: [PATCH 305/437] Fix duplicated helptags error --- doc/neogit.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8d8eef21f..c51133310 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1055,7 +1055,7 @@ Actions: *neogit_branch_popup_actions* the old branch. • Checkout new worktree *neogit_branch_checkout_worktree* - see: *neogit_worktree_checkout* + see: |neogit_worktree_checkout| • Create new branch *neogit_branch_create_branch* Functionally the same as |neogit_branch_checkout_new|, but does not update @@ -1066,7 +1066,7 @@ Actions: *neogit_branch_popup_actions* index has uncommitted changes, will behave exactly the same as spin_off. • Create new worktree *neogit_branch_create_worktree* - see: *neogit_worktree_create_branch* + see: |neogit_worktree_create_branch| • Configure *neogit_branch_configure* Opens selector to choose a branch, then offering some configuration From 92b5c75108845eb5d8c12554ec2dd86c98e565b2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 May 2025 10:45:22 +0200 Subject: [PATCH 306/437] Expand documentation for commit-alter and commit-revise actions --- doc/neogit.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index c51133310..984cc8dfd 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1314,14 +1314,38 @@ Actions: *neogit_commit_popup_actions* Creates a fixup commit. If a commit is selected it will be used, otherwise the user is prompted to pick a commit. + `git commit --fixup=COMMIT --no-edit` + • Squash *neogit_commit_squash* Creates a squash commit. If a commit is selected it will be used, otherwise the user is prompted to pick a commit. + `git commit --squash=COMMIT --no-edit` + • Augment *neogit_commit_augment* Creates a squash commit, editing the squash message. If a commit is selected it will be used, otherwise the user is prompted to pick a commit. + `git commit --squash=COMMIT --edit` + + • Alter *neogit_commit_alter* + Create a squash commit, authoring the final message now. + + During a later rebase, when this commit gets squashed into it's targeted + commit, the original message of the targeted commit is replaced with the + message of this commit, without the user automatically being given a + chance to edit it again. + + `git commit --fixup=amend:COMMIT --edit` + + • Revise *neogit_commit_revise* + Reword the message of an existing commit, without editing it's tree. + Later, when the commit is squashed into it's targeted commit, a combined + commit is created which uses the message of the fixup commit and the tree + of the targeted commit. + + `git commit --fixup=reword:COMMIT --edit` + • Instant Fixup *neogit_commit_instant_fixup* Similar to |neogit_commit_fixup|, but instantly rebases after. From f452f15342ca0bca638497975f126cb004e40d56 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 May 2025 10:59:43 +0200 Subject: [PATCH 307/437] Add Revise and Alter actions to commit popup --- lua/neogit/popups/commit/actions.lua | 22 +++++++++++++++------- lua/neogit/popups/commit/init.lua | 7 ++++--- spec/popups/commit_popup_spec.rb | 6 +++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index eb91366d0..d8a1ffec6 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -95,7 +95,7 @@ local function commit_special(popup, method, opts) end a.util.scheduler() - do_commit(popup, cmd.args(string.format("--%s=%s", method, commit))) + do_commit(popup, cmd.args(method:format(commit))) if opts.rebase then a.util.scheduler() @@ -149,15 +149,23 @@ function M.amend(popup) end function M.fixup(popup) - commit_special(popup, "fixup", { edit = false }) + commit_special(popup, "--fixup=%s", { edit = false }) end function M.squash(popup) - commit_special(popup, "squash", { edit = false }) + commit_special(popup, "--squash=%s", { edit = false }) end function M.augment(popup) - commit_special(popup, "squash", { edit = true }) + commit_special(popup, "--squash=%s", { edit = true }) +end + +function M.alter(popup) + commit_special(popup, "--fixup=amend:%s", { edit = true }) +end + +function M.revise(popup) + commit_special(popup, "--fixup=reword:%s", { edit = true }) end function M.instant_fixup(popup) @@ -165,7 +173,7 @@ function M.instant_fixup(popup) return end - commit_special(popup, "fixup", { rebase = true, edit = false }) + commit_special(popup, "--fixup=%s", { rebase = true, edit = false }) end function M.instant_squash(popup) @@ -173,7 +181,7 @@ function M.instant_squash(popup) return end - commit_special(popup, "squash", { rebase = true, edit = false }) + commit_special(popup, "--squash=%s", { rebase = true, edit = false }) end function M.absorb(popup) @@ -201,7 +209,7 @@ function M.absorb(popup) git.remote.list(), "Select a base commit for the absorb stack with , or to abort" ) - :open_async()[1] + :open_async()[1] if not commit then return end diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index f654d588e..fa5fd8a9f 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -18,7 +18,6 @@ function M.create(env) :option("C", "reuse-message", "", "Reuse commit message", { key_prefix = "-" }) :group_heading("Create") :action("c", "Commit", actions.commit) - :action("x", "Absorb", actions.absorb) :new_action_group("Edit HEAD") :action("e", "Extend", actions.extend) :spacer() @@ -28,12 +27,14 @@ function M.create(env) :new_action_group("Edit") :action("f", "Fixup", actions.fixup) :action("s", "Squash", actions.squash) - :action("A", "Alter") + :action("A", "Alter", actions.alter) :action("n", "Augment", actions.augment) - :action("W", "Revise") + :action("W", "Revise", actions.revise) :new_action_group("Edit and rebase") :action("F", "Instant Fixup", actions.instant_fixup) :action("S", "Instant Squash", actions.instant_squash) + :new_action_group("Spread across commits") + :action("x", "Absorb", actions.absorb) :env({ highlight = { "HEAD" }, commit = env.commit }) :build() diff --git a/spec/popups/commit_popup_spec.rb b/spec/popups/commit_popup_spec.rb index ec42d9df3..81c2e3d83 100644 --- a/spec/popups/commit_popup_spec.rb +++ b/spec/popups/commit_popup_spec.rb @@ -18,9 +18,9 @@ " -S Sign using gpg (--gpg-sign=) ", " -C Reuse commit message (--reuse-message=) ", " ", - " Create Edit HEAD Edit Edit and rebase ", - " c Commit e Extend f Fixup F Instant Fixup ", - " x Absorb s Squash S Instant Squash ", + " Create Edit HEAD Edit Edit and rebase Spread across commits ", + " c Commit e Extend f Fixup F Instant Fixup x Absorb ", + " s Squash S Instant Squash ", " a Amend A Alter ", " n Augment ", " w Reword W Revise " From f133b16449a7cbd348e9646d13eeb173e23dce0d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 May 2025 11:00:20 +0200 Subject: [PATCH 308/437] Refactor lib.git.reset to no longer touch notifications/events, as that's the realm of the actions (user io) --- lua/neogit/lib/event.lua | 20 ++++++ lua/neogit/lib/git/reset.lua | 108 +++++++++++----------------- lua/neogit/popups/reset/actions.lua | 45 +++++++----- 3 files changed, 91 insertions(+), 82 deletions(-) create mode 100644 lua/neogit/lib/event.lua diff --git a/lua/neogit/lib/event.lua b/lua/neogit/lib/event.lua new file mode 100644 index 000000000..55f23ce0f --- /dev/null +++ b/lua/neogit/lib/event.lua @@ -0,0 +1,20 @@ +local M = {} + +-- TODO: All callers of nvim_exec_autocmd should route through here + +---@param name string +---@param data table? +function M.send(name, data) + assert(name, "event must have name") + + vim.api.nvim_exec_autocmds( + "User", + { + pattern = "Neogit" .. name, + modeline = false, + data = data + } + ) +end + +return M diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index 2834c82bb..ddadf1539 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -1,87 +1,63 @@ -local notification = require("neogit.lib.notification") local git = require("neogit.lib.git") ---@class NeogitGitReset local M = {} -local function fire_reset_event(data) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitReset", modeline = false, data = data }) +---@param target string +---@return boolean +function M.mixed(target) + local result = git.cli.reset.mixed.args(target).call() + return result.code == 0 end -function M.mixed(commit) - local result = git.cli.reset.mixed.args(commit).call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - notification.info("Reset to " .. commit) - fire_reset_event { commit = commit, mode = "mixed" } - end +---@param target string +---@return boolean +function M.soft(target) + local result = git.cli.reset.soft.args(target).call() + return result.code == 0 end -function M.soft(commit) - local result = git.cli.reset.soft.args(commit).call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - notification.info("Reset to " .. commit) - fire_reset_event { commit = commit, mode = "soft" } - end -end - -function M.hard(commit) +---@param target string +---@return boolean +function M.hard(target) git.index.create_backup() - local result = git.cli.reset.hard.args(commit).call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - notification.info("Reset to " .. commit) - fire_reset_event { commit = commit, mode = "hard" } - end + local result = git.cli.reset.hard.args(target).call() + return result.code == 0 end -function M.keep(commit) - local result = git.cli.reset.keep.args(commit).call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - notification.info("Reset to " .. commit) - fire_reset_event { commit = commit, mode = "keep" } - end +---@param target string +---@return boolean +function M.keep(target) + local result = git.cli.reset.keep.args(target).call() + return result.code == 0 end -function M.index(commit) - local result = git.cli.reset.args(commit).files(".").call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - notification.info("Reset to " .. commit) - fire_reset_event { commit = commit, mode = "index" } - end +---@param target string +---@return boolean +function M.index(target) + local result = git.cli.reset.args(target).files(".").call() + return result.code == 0 end --- TODO: Worktree support --- "Reset the worktree to COMMIT. Keep the `HEAD' and index as-is." --- --- (magit-wip-commit-before-change nil " before reset") --- (magit-with-temp-index commit nil (magit-call-git "checkout-index" "--all" "--force")) --- (magit-wip-commit-after-apply nil " after reset") --- --- function M.worktree(commit) --- end +---@param target string revision to reset to +---@return boolean +function M.worktree(target) + local success = false + git.index.with_temp_index(target, function(index) + local result = git.cli["checkout-index"].all.force.env({ GIT_INDEX_FILE = index }).call() + success = result.code == 0 + end) + + return success +end -function M.file(commit, files) - local result = git.cli.checkout.rev(commit).files(unpack(files)).call { await = true } - if result.code ~= 0 then - notification.error("Reset Failed") - else - fire_reset_event { commit = commit, mode = "files" } - if #files > 1 then - notification.info("Reset " .. #files .. " files") - else - notification.info("Reset " .. files[1]) - end - end +---@param target string +---@param files string[] +---@return boolean +function M.file(target, files) + local result = git.cli.checkout.rev(target).files(unpack(files)).call() + return result.code == 0 end return M diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 0b7d79885..302d74d38 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") +local event = require("neogit.lib.event") local M = {} @@ -18,50 +19,51 @@ local function target(popup, prompt) return FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = prompt } end ----@param type string +---@param fn fun(target: string): boolean ---@param popup PopupData ---@param prompt string -local function reset(type, popup, prompt) +---@param mode string +local function reset(fn, popup, prompt, mode) local target = target(popup, prompt) if target then - git.reset[type](target) + local success = fn(target) + if success then + notification.info("Reset to " .. target) + event.send("Reset", { commit = target, mode = mode }) + else + notification.error("Reset Failed") + end end end ---@param popup PopupData function M.mixed(popup) - reset("mixed", popup, ("Reset %s to"):format(git.branch.current())) + reset(git.reset.mixed, popup, ("Reset %s to"):format(git.branch.current()), "mixed") end ---@param popup PopupData function M.soft(popup) - reset("soft", popup, ("Soft reset %s to"):format(git.branch.current())) + reset(git.reset.soft, popup, ("Soft reset %s to"):format(git.branch.current()), "soft") end ---@param popup PopupData function M.hard(popup) - reset("hard", popup, ("Hard reset %s to"):format(git.branch.current())) + reset(git.reset.hard, popup, ("Hard reset %s to"):format(git.branch.current()), "hard") end ---@param popup PopupData function M.keep(popup) - reset("keep", popup, ("Reset %s to"):format(git.branch.current())) + reset(git.reset.keep, popup, ("Reset %s to"):format(git.branch.current()), "keep") end ---@param popup PopupData function M.index(popup) - reset("index", popup, "Reset index to") + reset(git.reset.index, popup, "Reset index to", "index") end ---@param popup PopupData function M.worktree(popup) - local target = target(popup, "Reset worktree to") - if target then - git.index.with_temp_index(target, function(index) - git.cli["checkout-index"].all.force.env({ GIT_INDEX_FILE = index }).call() - notification.info(("Reset worktree to %s"):format(target)) - end) - end + reset(git.reset.worktree, popup, "Reset worktree to", "worktree") end ---@param popup PopupData @@ -82,7 +84,18 @@ function M.a_file(popup) return end - git.reset.file(target, files) + local success = git.reset.file(target, files) + if success then + notification.error("Reset Failed") + else + if #files > 1 then + notification.info("Reset " .. #files .. " files") + else + notification.info("Reset " .. files[1]) + end + + event.send("Reset", { commit = target, mode = "files", files = files }) + end end return M From e15657e83431eb2d70313a860c62ce373dc73329 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 May 2025 11:28:22 +0200 Subject: [PATCH 309/437] minor hack: after resetting the repo, queue a deferred refresh of the repo state because the instantaneous refresh often does not have the correct information. --- lua/neogit/buffers/status/init.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8e3fa1565..2558e56bd 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -212,6 +212,12 @@ function M:open(kind) Watcher.instance(self.root):register(self) buffer:move_cursor(buffer.ui:first_section().first) end, + user_autocmds = { + -- Resetting doesn't yield the correct repo state instantly, so we need to re-refresh after a few seconds + -- in order to show the user the correct state. + ["NeogitReset"] = self:deferred_refresh("reset"), + ["NeogitBranchReset"] = self:deferred_refresh("reset_branch"), + }, } return self @@ -304,6 +310,17 @@ M.dispatch_refresh = a.void(function(self, partial, reason) self:refresh(partial, reason) end) +---@param reason string +---@param wait number? timeout in ms, or 2 seconds +---@return fun() +function M:deferred_refresh(reason, wait) + return function() + vim.defer_fn(function() + self:dispatch_refresh(nil, reason) + end, wait or 2000) + end +end + function M:reset() logger.debug("[STATUS] Resetting repo and refreshing - CWD: " .. vim.uv.cwd()) git.repo:reset() From cb12298f3b1339f3fbc02fb3a9aabff8b8aa082a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 May 2025 11:48:54 +0200 Subject: [PATCH 310/437] lint --- lua/neogit/lib/event.lua | 13 +++++-------- lua/neogit/popups/commit/actions.lua | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/event.lua b/lua/neogit/lib/event.lua index 55f23ce0f..930dfa27b 100644 --- a/lua/neogit/lib/event.lua +++ b/lua/neogit/lib/event.lua @@ -7,14 +7,11 @@ local M = {} function M.send(name, data) assert(name, "event must have name") - vim.api.nvim_exec_autocmds( - "User", - { - pattern = "Neogit" .. name, - modeline = false, - data = data - } - ) + vim.api.nvim_exec_autocmds("User", { + pattern = "Neogit" .. name, + modeline = false, + data = data, + }) end return M diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index d8a1ffec6..9d28dc13c 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -209,7 +209,7 @@ function M.absorb(popup) git.remote.list(), "Select a base commit for the absorb stack with , or to abort" ) - :open_async()[1] + :open_async()[1] if not commit then return end From 2efbceb860aec16c706c37d641cbf88413bc6dfb Mon Sep 17 00:00:00 2001 From: Benjamin Wolff Date: Fri, 9 May 2025 18:00:01 +0200 Subject: [PATCH 311/437] add option to copy selection --- README.md | 1 + doc/neogit.txt | 1 + lua/neogit/config.lua | 2 ++ lua/neogit/lib/finder.lua | 8 ++++++++ 4 files changed, 12 insertions(+) diff --git a/README.md b/README.md index cef71dde9..c6db30014 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,7 @@ neogit.setup { [""] = "Next", [""] = "Previous", [""] = "InsertCompletion", + [""] = "CopySelection", [""] = "MultiselectToggleNext", [""] = "MultiselectTogglePrevious", [""] = "NOP", diff --git a/doc/neogit.txt b/doc/neogit.txt index 984cc8dfd..b8abcd514 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -393,6 +393,7 @@ The following mappings can all be customized via the setup function. [""] = "Next", [""] = "Previous", [""] = "InsertCompletion", + [""] = "CopySelection", [""] = "MultiselectToggleNext", [""] = "MultiselectTogglePrevious", [""] = "NOP", diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 573f85e9d..fd979e712 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -185,6 +185,7 @@ end ---| "Close" ---| "Next" ---| "Previous" +---| "CopySelection" ---| "MultiselectToggleNext" ---| "MultiselectTogglePrevious" ---| "InsertCompletion" @@ -589,6 +590,7 @@ function M.get_default_values() [""] = "Next", [""] = "Previous", [""] = "InsertCompletion", + [""] = "CopySelection", [""] = "MultiselectToggleNext", [""] = "MultiselectTogglePrevious", [""] = "NOP", diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 2b01866ba..659c662fa 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -9,6 +9,13 @@ local function refocus_status_buffer() end end +local copy_selection = function() + local selection = require("telescope.actions.state").get_selected_entry() + if selection ~= nil then + vim.cmd.let(("@+='%s'"):format(selection[1])) + end +end + local function telescope_mappings(on_select, allow_multi, refocus_status) local action_state = require("telescope.actions.state") local actions = require("telescope.actions") @@ -85,6 +92,7 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) ["InsertCompletion"] = completion_action, ["Next"] = actions.move_selection_next, ["Previous"] = actions.move_selection_previous, + ["CopySelection"] = copy_selection, ["NOP"] = actions.nop, ["MultiselectToggleNext"] = actions.toggle_selection + actions.move_selection_worse, ["MultiselectTogglePrevious"] = actions.toggle_selection + actions.move_selection_better, From 3e81572c8fc367596336e220f836b602ee67b310 Mon Sep 17 00:00:00 2001 From: Benjamin Wolff Date: Fri, 9 May 2025 20:50:07 +0200 Subject: [PATCH 312/437] properly escape entry --- lua/neogit/lib/finder.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 659c662fa..95c34c1e3 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -12,7 +12,7 @@ end local copy_selection = function() local selection = require("telescope.actions.state").get_selected_entry() if selection ~= nil then - vim.cmd.let(("@+='%s'"):format(selection[1])) + vim.cmd.let(("@+=%q"):format(selection[1])) end end From 6de4b9f9a92917f9aea3a0dbdc3dbbedc11d26be Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 15 May 2025 15:52:52 +0200 Subject: [PATCH 313/437] Prevent git commit instantly commands from stalling due to editor being opened in an unreachable state --- lua/neogit/lib/git/rebase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 2941448ee..e48a861c1 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -21,7 +21,7 @@ end function M.instantly(commit, args) local result = git.cli.rebase.interactive.autostash.autosquash .commit(commit) - .env({ GIT_SEQUENCE_EDITOR = ":" }) + .env({ GIT_SEQUENCE_EDITOR = ":", GIT_EDITOR = ":" }) .arg_list(args or {}) .call { long = true, pty = true } From 337447c4b0467a0904795b50ce44f715de760a5d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 19 May 2025 19:19:06 +0200 Subject: [PATCH 314/437] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c6db30014..d6060a1b8 100644 --- a/README.md +++ b/README.md @@ -512,6 +512,12 @@ Neogit follows semantic versioning. See [CONTRIBUTING.md](https://github.com/NeogitOrg/neogit/blob/master/CONTRIBUTING.md) for more details. +## Contributors + + + + + ## Special Thanks - [kolja](https://github.com/kolja) for the Neogit Logo From 1a7f1f3428a38a56b3f5bcaf0e6bda49d350f307 Mon Sep 17 00:00:00 2001 From: Dave Aitken Date: Wed, 21 May 2025 11:44:31 +0100 Subject: [PATCH 315/437] allow access to logger config via lua api at runtime --- CONTRIBUTING.md | 18 ++++++++++++++++-- lua/neogit/logger.lua | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f3010b30..10fd3df64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,23 @@ Simply clone *Neogit* to your project directory of choice to be able to use your Logging is a useful tool for inspecting what happens in the code and in what order. Neogit uses [`Plenary`](https://github.com/nvim-lua/plenary.nvim) for logging. -Export the environment variables `NEOGIT_LOG_CONSOLE="sync"` to enable logging, and `NEOGIT_LOG_LEVEL="debug"` for more -verbose logging. +#### Enabling logging via environment variables +- To enable logging to console, export `NEOGIT_LOG_CONSOLE="sync"` +- To enable logging to a file, export `NEOGIT_LOG_FILE="true"` +- For more verbose logging, set the log level to `debug` via `NEOGIT_LOG_LEVEL="debug"` + +#### Enabling logging via lua api + +To turn on logging while neovim is already running, you can use: + +```lua +:lua require("neogit.logger").config.use_file = true -- for logs to ~/.cache/nvim/neogit.log. +:lua require("neogit.logger").config.use_console = true -- for logs to console. +:lua require("neogit.logger").config.level = 'debug' -- to set the log level +``` + +#### Using the logger from the neogit codebase ```lua local logger = require("neogit.logger") diff --git a/lua/neogit/logger.lua b/lua/neogit/logger.lua index a07a21916..b8d725a56 100644 --- a/lua/neogit/logger.lua +++ b/lua/neogit/logger.lua @@ -41,6 +41,8 @@ log.new = function(config, standalone) obj = {} end + obj.config = config + local levels = {} for i, v in ipairs(config.modes) do levels[v.name] = i From a48820b80c24360dac88eb5a9b12e155f0051c0f Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 16 May 2025 14:05:54 +0200 Subject: [PATCH 316/437] Fix: when decorating a ref, do not return a string like "tag: xxx", just return the OID. The issue is that the fuzzy finder uses this to try to show a nice ref name, but "tag: xxx" is not a valid input to the git cli --- lua/neogit/lib/git/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b9fef523e..e1b53b4ea 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -560,7 +560,7 @@ function M.decorate(oid) return oid else local decorated_ref = vim.split(result[1], ",")[1] - if decorated_ref:match("%->") then + if decorated_ref:match("%->") or decorated_ref:match("tag: ") then return oid else return decorated_ref From 5acf15718af7506e656df09e7334f6a6622c29c9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 19 May 2025 13:49:42 +0200 Subject: [PATCH 317/437] Revert "reafactor: use `vim.ui.input` instead of `vim.fn.input`" This reverts commit afbf66f157c3c0e2aa08ae5b8312b9199ae9c242. --- lua/neogit/lib/input.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index 5df738c6f..26a336180 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -1,14 +1,5 @@ local M = {} -local async = require("plenary.async") -local input = async.wrap(function(prompt, default, completion, callback) - vim.ui.input({ - prompt = prompt, - default = default, - completion = completion, - }, callback) -end, 4) - --- Provides the user with a confirmation ---@param msg string Prompt to use for confirmation ---@param options table|nil @@ -66,15 +57,23 @@ end function M.get_user_input(prompt, opts) opts = vim.tbl_extend("keep", opts or {}, { strip_spaces = false, separator = ": " }) + vim.fn.inputsave() + if opts.prepend then vim.defer_fn(function() vim.api.nvim_input(opts.prepend) end, 10) end - local result = input(("%s%s"):format(prompt, opts.separator), opts.default, opts.completion) + local status, result = pcall(vim.fn.input, { + prompt = ("%s%s"):format(prompt, opts.separator), + default = opts.default, + completion = opts.completion, + cancelreturn = opts.cancel, + }) - if result == "" or result == nil then + vim.fn.inputrestore() + if not status then return nil end @@ -82,6 +81,10 @@ function M.get_user_input(prompt, opts) result, _ = result:gsub("%s", "-") end + if result == "" then + return nil + end + return result end From 6ea64daf7fd4bc6c6a7d95eb9b543ee61f0fe8c4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 May 2025 22:16:11 +0200 Subject: [PATCH 318/437] fix: forgot a not --- lua/neogit/popups/reset/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 302d74d38..d2b594d7a 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -85,7 +85,7 @@ function M.a_file(popup) end local success = git.reset.file(target, files) - if success then + if not success then notification.error("Reset Failed") else if #files > 1 then From 087489e7f90b0b3f7ab6de9ff5147cd6898020ef Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 May 2025 22:20:11 +0200 Subject: [PATCH 319/437] Add documentation on reset: file and extend it to work in more situations --- doc/neogit.txt | 7 +++++++ lua/neogit/lib/git/reset.lua | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index b8abcd514..aa2a51574 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1923,6 +1923,13 @@ Actions: *neogit_reset_popup_actions* • Worktree *neogit_reset_worktree* Resets current worktree to specified commit. + • Branch *neogit_reset_branch* + see: |neogit_branch_reset| + + • File *neogit_reset_file* + Attempts to perform a `git checkout` from the specified revision, and if + that fails, tries `git reset` instead. + ============================================================================== Stash Popup *neogit_stash_popup* diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index ddadf1539..22dd6a8e8 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -57,6 +57,10 @@ end ---@return boolean function M.file(target, files) local result = git.cli.checkout.rev(target).files(unpack(files)).call() + if result.code > 0 then + result = git.cli.reset.args(target).files(unpack(files)).call() + end + return result.code == 0 end From 8fbef04563c5fb91c137f37662b9b686406f9df7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 May 2025 22:22:08 +0200 Subject: [PATCH 320/437] Perform a refresh when refocusing the status buffer --- lua/neogit/buffers/status/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2558e56bd..858f58668 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -218,6 +218,9 @@ function M:open(kind) ["NeogitReset"] = self:deferred_refresh("reset"), ["NeogitBranchReset"] = self:deferred_refresh("reset_branch"), }, + autocmds = { + ["FocusGained"] = self:deferred_refresh("focused", 10), + }, } return self From b83aa474ed7777ddd65ba390dafe402e8961b74a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 May 2025 22:23:35 +0200 Subject: [PATCH 321/437] Ensure window handle is valid before calling window function --- lua/neogit/lib/buffer.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 73bfc1b5e..911a670ac 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -538,10 +538,12 @@ function Buffer:call(f, ...) end function Buffer:win_call(f, ...) - local args = { ... } - api.nvim_win_call(self.win_handle, function() - f(unpack(args)) - end) + if self.win_handle and api.nvim_win_is_valid(self.win_handle) then + local args = { ... } + api.nvim_win_call(self.win_handle, function() + f(unpack(args)) + end) + end end function Buffer:chan_send(data) From 9b624f2caed2300489796ec16ad01fca1dc36963 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 May 2025 22:23:54 +0200 Subject: [PATCH 322/437] update luarc schema --- .luarc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.luarc.json b/.luarc.json index f24169681..3a2495a62 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "diagnostics.disable": [ "redefined-local" ], From e7f94183445e52260464b4abc43de9623f6fa891 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 22 May 2025 11:23:57 +0200 Subject: [PATCH 323/437] fix: when discarding a visual selection of staged files, take the file names from the correct section. --- lua/neogit/buffers/status/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 2d227e377..f53560771 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -185,7 +185,7 @@ M.v_discard = function(self) end if #new_files > 0 then - git.index.reset(util.map(unstaged_files, function(item) + git.index.reset(util.map(new_files, function(item) return item.escaped_path end)) cleanup_items(unpack(new_files)) From 7b3cd7c90dffad61b99570c17cd84d141f9b9ef7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 22 May 2025 11:25:08 +0200 Subject: [PATCH 324/437] Fix: discarding all staged files by section should work again --- lua/neogit/buffers/status/actions.lua | 31 ++++++++++++++++----------- lua/neogit/lib/ui/init.lua | 7 +++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index f53560771..afb760e25 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -31,12 +31,16 @@ local function cleanup_dir(dir) fn.delete(dir, "rf") end -local function cleanup_items(...) +---@param items StatusItem[] +local function cleanup_items(items) if vim.in_fast_event() then a.util.scheduler() end - for _, item in ipairs { ... } do + for _, item in ipairs(items) do + logger.trace("[cleanup_items()] Cleaning " .. vim.inspect(item.name)) + assert(item.name, "cleanup_items() - item must have a name") + local bufnr = fn.bufnr(item.name) if bufnr > 0 then api.nvim_buf_delete(bufnr, { force = false }) @@ -175,7 +179,7 @@ M.v_discard = function(self) end if #untracked_files > 0 then - cleanup_items(unpack(untracked_files)) + cleanup_items(untracked_files) end if #unstaged_files > 0 then @@ -188,7 +192,7 @@ M.v_discard = function(self) git.index.reset(util.map(new_files, function(item) return item.escaped_path end)) - cleanup_items(unpack(new_files)) + cleanup_items(new_files) end if #staged_files_modified > 0 then @@ -689,7 +693,7 @@ M.n_discard = function(self) if mode == "all" then message = ("Discard %q?"):format(selection.item.name) action = function() - cleanup_items(selection.item) + cleanup_items { selection.item } end else message = ("Recursively discard %q?"):format(selection.item.name) @@ -721,7 +725,7 @@ M.n_discard = function(self) action = function() if selection.item.mode == "A" then git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) + cleanup_items { selection.item } else git.index.checkout { selection.item.name } end @@ -752,14 +756,14 @@ M.n_discard = function(self) action = function() if selection.item.mode == "N" then git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) + cleanup_items { selection.item } elseif selection.item.mode == "M" then git.index.reset { selection.item.escaped_path } git.index.checkout { selection.item.escaped_path } elseif selection.item.mode == "R" then git.index.reset_HEAD(selection.item.name, selection.item.original_name) git.index.checkout { selection.item.original_name } - cleanup_items(selection.item) + cleanup_items { selection.item } elseif selection.item.mode == "D" then git.index.reset_HEAD(selection.item.escaped_path) git.index.checkout { selection.item.escaped_path } @@ -812,7 +816,7 @@ M.n_discard = function(self) if section == "untracked" then message = ("Discard %s files?"):format(#selection.section.items) action = function() - cleanup_items(unpack(selection.section.items)) + cleanup_items(selection.section.items) end refresh = { update_diffs = { "untracked:*" } } elseif section == "unstaged" then @@ -845,7 +849,7 @@ M.n_discard = function(self) for _, item in ipairs(selection.section.items) do if item.mode == "N" or item.mode == "A" then - table.insert(new_files, item.escaped_path) + table.insert(new_files, item) elseif item.mode == "M" then table.insert(staged_files_modified, item.escaped_path) elseif item.mode == "R" then @@ -858,9 +862,10 @@ M.n_discard = function(self) end if #new_files > 0 then - -- ensure the file is deleted - git.index.reset(new_files) - cleanup_items(unpack(new_files)) + git.index.reset(util.map(new_files, function(item) + return item.escaped_path + end)) + cleanup_items(new_files) end if #staged_files_modified > 0 then diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index c4e8ce879..6e4ef243a 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -5,7 +5,7 @@ local Collection = require("neogit.lib.collection") local logger = require("neogit.logger") -- TODO: Add logging ---@class Section ----@field items StatusItem[] +---@field items StatusItem[] ---@field name string ---@field first number @@ -16,8 +16,8 @@ local logger = require("neogit.logger") -- TODO: Add logging ---@field section Section|nil ---@field item StatusItem|nil ---@field commit CommitLogEntry|nil ----@field commits CommitLogEntry[] ----@field items StatusItem[] +---@field commits CommitLogEntry[] +---@field items StatusItem[] local Selection = {} Selection.__index = Selection @@ -221,6 +221,7 @@ function Ui:item_hunks(item, first_line, last_line, partial) return hunks end +---@return Selection function Ui:get_selection() local visual_pos = vim.fn.line("v") local cursor_pos = vim.fn.line(".") From 7cec58c9272d61da5d77fe5869e3956b8a91f1eb Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 22 May 2025 11:29:26 +0200 Subject: [PATCH 325/437] Aggressively check for buffer presence, as the use may have closed it while this function was running. --- lua/neogit/buffers/status/init.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 858f58668..c06f64e98 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -293,18 +293,18 @@ function M:redraw(cursor, view) logger.debug("[STATUS] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo.state, self.config))) - if self.fold_state then + if self.fold_state and self.buffer then logger.debug("[STATUS] Restoring fold state") self.buffer.ui:set_fold_state(self.fold_state) self.fold_state = nil end - if self.cursor_state and self.view_state then + if self.cursor_state and self.view_state and self.buffer then logger.debug("[STATUS] Restoring cursor and view state") self.buffer:restore_view(self.view_state, self.cursor_state) self.view_state = nil self.cursor_state = nil - elseif cursor and view then + elseif cursor and view and self.buffer then self.buffer:restore_view(view, self.buffer.ui:resolve_cursor_location(cursor)) end end From a2a5ff6527d9998a8e0c2da2bef8585dd3fd97fe Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 22 May 2025 11:43:48 +0200 Subject: [PATCH 326/437] Replace call to nvim_buf_add_highlight() (deprecated) with vim.hl.range() --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 911a670ac..e63ebb707 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -457,7 +457,7 @@ end function Buffer:add_highlight(line, col_start, col_end, name, namespace) local ns_id = self:get_namespace_id(namespace) if ns_id then - api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) + vim.hl.range(self.handle, ns_id, name, { line, col_start }, { line, col_end }) end end From 5fc3cb15537d1bd2b018cfe78e4fcefc60723728 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 23 May 2025 10:15:56 +0200 Subject: [PATCH 327/437] Add more notifications to branch popup when action succeeds or fails, migrate to use internal event lib --- lua/neogit/popups/branch/actions.lua | 68 +++++++++++++++++----------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 31ce1a528..751b6b2b3 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -5,21 +5,18 @@ local config = require("neogit.config") local input = require("neogit.lib.input") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") +local event = require("neogit.lib.event") local a = require("plenary.async") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local BranchConfigPopup = require("neogit.popups.branch_config") -local function fire_branch_event(pattern, data) - vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false, data = data }) -end - local function fetch_remote_branch(target) local remote, branch = git.branch.parse_remote_branch(target) if remote then notification.info("Fetching from " .. remote .. "/" .. branch) git.fetch.fetch(remote, branch) - fire_branch_event("NeogitFetchComplete", { branch = branch, remote = remote }) + event.send("FetchComplete", { branch = branch, remote = remote }) end end @@ -30,7 +27,8 @@ local function checkout_branch(target, args) return end - fire_branch_event("NeogitBranchCheckout", { branch_name = target }) + event.send("BranchCheckout", { branch_name = target }) + notification.info("Checked out branch " .. target) if config.values.fetch_after_checkout then a.void(function() @@ -70,13 +68,13 @@ local function spin_off_branch(checkout) return end - fire_branch_event("NeogitBranchCreate", { branch_name = name }) + event.send("BranchCreate", { branch_name = name }) local current_branch_name = git.branch.current_full_name() if checkout then git.cli.checkout.branch(name).call() - fire_branch_event("NeogitBranchCheckout", { branch_name = name }) + event.send("BranchCheckout", { branch_name = name }) end local upstream = git.branch.upstream() @@ -86,7 +84,7 @@ local function spin_off_branch(checkout) git.log.update_ref(current_branch_name, upstream) else git.cli.reset.hard.args(upstream).call() - fire_branch_event("NeogitReset", { commit = name, mode = "hard" }) + event.send("Reset", { commit = name, mode = "hard" }) end end end @@ -133,11 +131,17 @@ local function create_branch(popup, prompt, checkout, name) return end - git.branch.create(name, base_branch) - fire_branch_event("NeogitBranchCreate", { branch_name = name, base = base_branch }) + local success = git.branch.create(name, base_branch) + if success then + event.send("BranchCreate", { branch_name = name, base = base_branch }) - if checkout then - checkout_branch(name, popup:get_arguments()) + if checkout then + checkout_branch(name, popup:get_arguments()) + else + notification.info("Created branch " .. name) + end + else + notification.warn("Branch " .. name .. " already exists.") end end @@ -186,7 +190,8 @@ function M.checkout_local_branch(popup) if target then if vim.tbl_contains(remote_branches, target) then git.branch.track(target, popup:get_arguments()) - fire_branch_event("NeogitBranchCheckout", { branch_name = target }) + notification.info("Created local branch " .. target .. " tracking remote") + event.send("BranchCheckout", { branch_name = target }) elseif not vim.tbl_contains(options, target) then target, _ = target:gsub("%s", "-") create_branch(popup, "Create " .. target .. " starting at", true, target) @@ -235,10 +240,13 @@ function M.rename_branch() return end - git.cli.branch.move.args(selected_branch, new_name).call { await = true } - - notification.info(string.format("Renamed '%s' to '%s'", selected_branch, new_name)) - fire_branch_event("NeogitBranchRename", { branch_name = selected_branch, new_name = new_name }) + local result = git.cli.branch.move.args(selected_branch, new_name).call { await = true } + if result.code == 0 then + notification.info(string.format("Renamed '%s' to '%s'", selected_branch, new_name)) + event.send("BranchRename", { branch_name = selected_branch, new_name = new_name }) + else + notification.warn(string.format("Couldn't rename '%s' to '%s'", selected_branch, new_name)) + end end function M.reset_branch(popup) @@ -278,13 +286,17 @@ function M.reset_branch(popup) end -- Reset the current branch to the desired state & update reflog - git.cli.reset.hard.args(to).call() - local current = git.branch.current_full_name() - assert(current, "no current branch") - git.log.update_ref(current, to) - - notification.info(string.format("Reset '%s' to '%s'", current, to)) - fire_branch_event("NeogitBranchReset", { branch_name = current, resetting_to = to }) + local result = git.cli.reset.hard.args(to).call() + if result.code == 0 then + local current = git.branch.current_full_name() + assert(current, "no current branch") + git.log.update_ref(current, to) + + notification.info(string.format("Reset '%s' to '%s'", current, to)) + event.send("BranchReset", { branch_name = current, resetting_to = to }) + else + notification.error("Couldn't reset branch.") + end end function M.delete_branch(popup) @@ -343,7 +355,7 @@ function M.delete_branch(popup) else notification.info(string.format("Deleted branch '%s'", branch_name)) end - fire_branch_event("NeogitBranchDelete", { branch_name = branch_name }) + event.send("BranchDelete", { branch_name = branch_name }) end end @@ -387,7 +399,9 @@ function M.open_pull_request() format_values["target"] = target end - vim.ui.open(util.format(template, format_values)) + local uri = util.format(template, format_values) + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) else notification.warn("Requires Neovim 0.10") end From f004327af87e2df8a2ee4abf5ce3daea5d6ee91c Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 23 May 2025 10:49:18 +0200 Subject: [PATCH 328/437] Migrate all events to use internal event lib --- lua/neogit/lib/event.lua | 2 -- lua/neogit/lib/git/bisect.lua | 11 +++----- lua/neogit/lib/git/cherry_pick.lua | 9 +++---- lua/neogit/lib/git/merge.lua | 9 +++---- lua/neogit/lib/git/rebase.lua | 35 ++++++++++++-------------- lua/neogit/lib/git/stash.lua | 24 ++++++------------ lua/neogit/popups/tag/actions.lua | 10 +++----- lua/neogit/popups/worktree/actions.lua | 11 +++----- 8 files changed, 41 insertions(+), 70 deletions(-) diff --git a/lua/neogit/lib/event.lua b/lua/neogit/lib/event.lua index 930dfa27b..95ae65481 100644 --- a/lua/neogit/lib/event.lua +++ b/lua/neogit/lib/event.lua @@ -1,7 +1,5 @@ local M = {} --- TODO: All callers of nvim_exec_autocmd should route through here - ---@param name string ---@param data table? function M.send(name, data) diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index c552241ef..cd0c3e130 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -1,18 +1,15 @@ local git = require("neogit.lib.git") +local event = require("neogit.lib.event") ---@class NeogitGitBisect local M = {} -local function fire_bisect_event(data) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitBisect", modeline = false, data = data }) -end - ---@param cmd string local function bisect(cmd) local result = git.cli.bisect.args(cmd).call { long = true } if result.code == 0 then - fire_bisect_event { type = cmd } + event.send("Bisect", { type = cmd }) end end @@ -32,7 +29,7 @@ function M.start(bad_revision, good_revision, args) git.cli.bisect.args("start").arg_list(args).args(bad_revision, good_revision).call { long = true } if result.code == 0 then - fire_bisect_event { type = "start" } + event.send("Bisect", { type = "start" }) end end @@ -80,7 +77,7 @@ M.register = function(meta) finished = action == "first bad commit" if finished then - fire_bisect_event { type = "finished", oid = oid } + event.send("Bisect", { type = "finished", oid = oid }) end ---@type BisectItem diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index 0e64449b7..82a25ddd3 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -2,14 +2,11 @@ local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") local client = require("neogit.client") +local event = require("neogit.lib.event") ---@class NeogitGitCherryPick local M = {} -local function fire_cherrypick_event(data) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitCherryPick", modeline = false, data = data }) -end - ---@param commits string[] ---@param args string[] ---@return boolean @@ -27,7 +24,7 @@ function M.pick(commits, args) notification.error("Cherry Pick failed. Resolve conflicts before continuing") return false else - fire_cherrypick_event { commits = commits } + event.send("CherryPick", { commits = commits }) return true end end @@ -43,7 +40,7 @@ function M.apply(commits, args) if result.code ~= 0 then notification.error("Cherry Pick failed. Resolve conflicts before continuing") else - fire_cherrypick_event { commits = commits } + event.send("CherryPick", { commits = commits }) end end diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 1fdbcab00..97cfcb0af 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -1,6 +1,7 @@ local client = require("neogit.client") local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") +local event = require("neogit.lib.event") ---@class NeogitGitMerge local M = {} @@ -9,18 +10,14 @@ local function merge_command(cmd) return cmd.env(client.get_envs_git_editor()).call { pty = true } end -local function fire_merge_event(data) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitMerge", modeline = false, data = data }) -end - function M.merge(branch, args) local result = merge_command(git.cli.merge.args(branch).arg_list(args)) if result.code ~= 0 then notification.error("Merging failed. Resolve conflicts before continuing") - fire_merge_event { branch = branch, args = args, status = "conflict" } + event.send("Merge", { branch = branch, args = args, status = "conflict" }) else notification.info("Merged '" .. branch .. "' into '" .. git.branch.current() .. "'") - fire_merge_event { branch = branch, args = args, status = "ok" } + event.send("Merge", { branch = branch, args = args, status = "ok" }) end end diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index e48a861c1..34844b0d1 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -2,14 +2,11 @@ local logger = require("neogit.logger") local git = require("neogit.lib.git") local client = require("neogit.client") local notification = require("neogit.lib.notification") +local event = require("neogit.lib.event") ---@class NeogitGitRebase local M = {} -local function fire_rebase_event(data) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitRebase", modeline = false, data = data }) -end - local function rebase_command(cmd) return cmd.env(client.get_envs_git_editor()).call { long = true, pty = true } end @@ -26,9 +23,9 @@ function M.instantly(commit, args) .call { long = true, pty = true } if result.code ~= 0 then - fire_rebase_event { commit = commit, status = "failed" } + event.send("Rebase", { commit = commit, status = "failed" }) else - fire_rebase_event { commit = commit, status = "ok" } + event.send("Rebase", { commit = commit, status = "ok" }) end return result @@ -43,14 +40,14 @@ function M.rebase_interactive(commit, args) if result.code ~= 0 then if result.stdout[1]:match("^hint: Waiting for your editor to close the file%.%.%. error") then notification.info("Rebase aborted") - fire_rebase_event { commit = commit, status = "aborted" } + event.send("Rebase", { commit = commit, status = "aborted" }) else notification.error("Rebasing failed. Resolve conflicts before continuing") - fire_rebase_event { commit = commit, status = "conflict" } + event.send("Rebase", { commit = commit, status = "conflict" }) end else notification.info("Rebased successfully") - fire_rebase_event { commit = commit, status = "ok" } + event.send("Rebase", { commit = commit, status = "ok" }) end end @@ -58,10 +55,10 @@ function M.onto_branch(branch, args) local result = rebase_command(git.cli.rebase.args(branch).arg_list(args)) if result.code ~= 0 then notification.error("Rebasing failed. Resolve conflicts before continuing") - fire_rebase_event("conflict") + event.send("Rebase", { commit = branch, status = "conflict" }) else notification.info("Rebased onto '" .. branch .. "'") - fire_rebase_event("ok") + event.send("Rebase", { commit = branch, status = "ok" }) end end @@ -69,10 +66,10 @@ function M.onto(start, newbase, args) local result = rebase_command(git.cli.rebase.onto.args(newbase, start).arg_list(args)) if result.code ~= 0 then notification.error("Rebasing failed. Resolve conflicts before continuing") - fire_rebase_event("conflict") + event.send("Rebase", { status = "conflict" }) else notification.info("Rebased onto '" .. newbase .. "'") - fire_rebase_event("ok") + event.send("Rebase", { commit = newbase, status = "ok" }) end end @@ -103,10 +100,10 @@ function M.modify(commit) .in_pty(true) .env({ GIT_SEQUENCE_EDITOR = editor }) .call() - if result.code ~= 0 then - return + + if result.code == 0 then + event.send("Rebase", { commit = commit, status = "ok" }) end - fire_rebase_event { commit = commit, status = "ok" } end function M.drop(commit) @@ -117,10 +114,10 @@ function M.drop(commit) .in_pty(true) .env({ GIT_SEQUENCE_EDITOR = editor }) .call() - if result.code ~= 0 then - return + + if result.code == 0 then + event.send("Rebase", { commit = commit, status = "ok" }) end - fire_rebase_event { commit = commit, status = "ok" } end function M.continue() diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 9a9bdf1e5..2335092cf 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -2,19 +2,11 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local util = require("neogit.lib.util") local config = require("neogit.config") +local event = require("neogit.lib.event") ---@class NeogitGitStash local M = {} ----@param success boolean -local function fire_stash_event(success) - vim.api.nvim_exec_autocmds("User", { - pattern = "NeogitStash", - modeline = false, - data = { success = success }, - }) -end - function M.list_refs() local result = git.cli.reflog.show.format("%h").args("stash").call { ignore_error = true } if result.code > 0 then @@ -27,24 +19,24 @@ end ---@param args string[] function M.stash_all(args) local result = git.cli.stash.push.files(".").arg_list(args).call() - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.stash_index() local result = git.cli.stash.staged.call() - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.stash_keep_index() local result = git.cli.stash.keep_index.files(".").call() - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end ---@param args string[] ---@param files string[] function M.push(args, files) local result = git.cli.stash.push.arg_list(args).files(unpack(files)).call() - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.pop(stash) @@ -56,7 +48,7 @@ function M.pop(stash) git.cli.stash.apply.args(stash).call() end - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.apply(stash) @@ -66,12 +58,12 @@ function M.apply(stash) git.cli.stash.apply.args(stash).call() end - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.drop(stash) local result = git.cli.stash.drop.args(stash).call() - fire_stash_event(result.code == 0) + event.send("Stash", { success = result.code == 0 }) end function M.list() diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index b775f9457..c93b35307 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -6,10 +6,7 @@ local utils = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") - -local function fire_tag_event(pattern, data) - vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false, data = data }) -end +local event = require("neogit.lib.event") ---@param popup PopupData function M.create_tag(popup) @@ -40,10 +37,11 @@ function M.create_tag(popup) }, }) if code == 0 then - fire_tag_event("NeogitTagCreate", { name = tag_input, ref = selected }) + event.send("TagCreate", { name = tag_input, ref = selected }) end end +--TODO: --- Create a release tag for `HEAD'. ---@param _ table function M.create_release(_) end @@ -62,7 +60,7 @@ function M.delete(_) if git.tag.delete(tags) then notification.info("Deleted tags: " .. table.concat(tags, ",")) for _, tag in pairs(tags) do - fire_tag_event("NeogitTagDelete", { name = tag }) + event.send("TagDelete", { name = tag }) end end end diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 1d560737d..d0ff7b6fb 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -5,6 +5,7 @@ local input = require("neogit.lib.input") local util = require("neogit.lib.util") local status = require("neogit.buffers.status") local notification = require("neogit.lib.notification") +local event = require("neogit.lib.event") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -53,12 +54,6 @@ local function autocmd_helpers(old_cwd, new_cwd) } end ----@param pattern string ----@param data table -local function fire_worktree_event(pattern, data) - vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false, data = data }) -end - ---@param prompt string ---@return string|nil local function get_ref(prompt) @@ -86,7 +81,7 @@ function M.checkout_worktree() status.instance():chdir(path) end - fire_worktree_event("NeogitWorktreeCreate", autocmd_helpers(cwd, path)) + event.send("WorktreeCreate", autocmd_helpers(cwd, path)) else notification.error(err) end @@ -118,7 +113,7 @@ function M.create_worktree() status.instance():chdir(path) end - fire_worktree_event("NeogitWorktreeCreate", autocmd_helpers(cwd, path)) + event.send("WorktreeCreate", autocmd_helpers(cwd, path)) else notification.error(err) end From 3a1c280c40d1b9084afebff6d3489cd4493e0bcb Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 23 May 2025 10:49:34 +0200 Subject: [PATCH 329/437] Some rebase-todo lines don't have an OID --- lua/neogit/lib/git/rebase.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 34844b0d1..4b0757823 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -222,13 +222,17 @@ function M.update_rebase_status(state) for line in done:iter() do if line:match("^[^#]") and line ~= "" then local oid = line:match("^%w+ (%x+)") or line:match("^fixup %-C (%x+)") - table.insert(state.rebase.items, { - action = line:match("^(%w+) "), - oid = oid, - abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), - subject = line:match("^%w+ %x+ (.+)$"), - done = true, - }) + if oid then + table.insert(state.rebase.items, { + action = line:match("^(%w+) "), + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), + subject = line:match("^%w+ %x+ (.+)$"), + done = true, + }) + else + logger.debug("[rebase status] No OID found on line '" .. line .. "'") + end end end end From 1d9c3e1797ac867b1576e1bcdda76b7f6f819d64 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 24 May 2025 21:04:00 +0200 Subject: [PATCH 330/437] When the index contains no changes, "git commit" will fail here, meaning a hard reset will move to ref~1 instead of ref. For extra safety, only perform the hidden hard-reset if the commit succeeded. Fixes issue #1741 --- lua/neogit/lib/git/index.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index cad417ec9..158e52500 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -158,9 +158,12 @@ end -- Capture state of index as reflog entry function M.create_backup() git.cli.add.update.call { hidden = true, await = true } - git.cli.commit.message("Hard reset backup").call { hidden = true, await = true, pty = true } - git.cli["update-ref"].args("refs/backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } - git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } + local result = + git.cli.commit.allow_empty.message("Hard reset backup").call { hidden = true, await = true, pty = true } + if result.code == 0 then + git.cli["update-ref"].args("refs/backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } + git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } + end end return M From f48912295e86065e84808bbc85619fb6e7fcbc0e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 24 May 2025 21:12:48 +0200 Subject: [PATCH 331/437] Fix: The section name here is "upstream_unmerged" and "pushRemote_unmerged" Fixes issue #1740 --- lua/neogit/integrations/diffview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index ef66bae39..42cf8f92b 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -110,7 +110,7 @@ function M.open(section_name, item_name, opts) local view -- selene: allow(if_same_then_else) - if section_name == "recent" or section_name == "unmerged" or section_name == "log" then + if section_name == "recent" or section_name:match("unmerged$") or section_name == "log" then local range if type(item_name) == "table" then range = string.format("%s..%s", item_name[1], item_name[#item_name]) From 18fd1997c62556b3a43678df33199201e234e142 Mon Sep 17 00:00:00 2001 From: Eric Chen <54382303+ericdachen@users.noreply.github.com> Date: Mon, 26 May 2025 21:37:45 -0400 Subject: [PATCH 332/437] Warp open source support I picked a background that hopefully fits with your repo's colour theme :) --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d6060a1b8..cd95cdec2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ +
+
+
+ + Warp sponsorship + + +### [Warp, the intelligent terminal for developers](https://www.warp.dev/neogit) +#### [Try running neogit in Warp](https://www.warp.dev/neogit)
+ +
+
From fbea356df0b86d24fee91ddd7002cbfa45214466 Mon Sep 17 00:00:00 2001 From: Eric Chen <54382303+ericdachen@users.noreply.github.com> Date: Mon, 26 May 2025 21:38:11 -0400 Subject: [PATCH 333/437] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cd95cdec2..511694383 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@
+
+
From 29a8f4e7119565b413cdef72f219cf6129e9d3f6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 27 May 2025 15:17:20 +0200 Subject: [PATCH 334/437] Add annotations to renderer class --- lua/neogit/lib/ui/renderer.lua | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 00fce647c..721278565 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -1,5 +1,7 @@ ---@source component.lua +local strdisplaywidth = vim.fn.strdisplaywidth + ---@class RendererIndex ---@field index table ---@field items table @@ -39,6 +41,9 @@ function RendererIndex:add_section(name, first, last) table.insert(self.items, { items = {} }) end +---@param item table +---@param first number +---@param last number function RendererIndex:add_item(item, first, last) self.items[#self.items].last = last @@ -72,6 +77,11 @@ end ---@field in_row boolean ---@field in_nested_row boolean +---@class RendererHighlight +---@field from integer +---@field to integer +---@field name string + ---@class Renderer ---@field buffer RendererBuffer ---@field flags RendererFlags @@ -125,6 +135,9 @@ function Renderer:item_index() return self.index.items end +---@param child Component +---@param parent Component +---@param index integer function Renderer:_build_child(child, parent, index) child.parent = parent child.index = index @@ -252,6 +265,10 @@ end ---@param child Component ---@param i integer index of child in parent.children +---@param col_start integer +---@param col_end integer|nil +---@param highlights RendererHighlight[] +---@param text string[] function Renderer:_render_child_in_row(child, i, col_start, col_end, highlights, text) if child.tag == "text" then return self:_render_in_row_text(child, i, col_start, highlights, text) @@ -264,6 +281,9 @@ end ---@param child Component ---@param index integer index of child in parent.children +---@param col_start integer +---@param highlights RendererHighlight[] +---@param text string[] function Renderer:_render_in_row_text(child, index, col_start, highlights, text) local padding_left = self.flags.in_nested_row and "" or child:get_padding_left(index == 1) table.insert(text, 1, padding_left) @@ -291,6 +311,10 @@ function Renderer:_render_in_row_text(child, index, col_start, highlights, text) end ---@param child Component +---@param highlights RendererHighlight[] +---@param text string[] +---@param col_start integer +---@param col_end integer|nil function Renderer:_render_in_row_row(child, highlights, text, col_start, col_end) self.flags.in_nested_row = true local res = self:_render(child, child.children, col_start) @@ -302,7 +326,7 @@ function Renderer:_render_in_row_row(child, highlights, text, col_start, col_end table.insert(highlights, h) end - col_end = col_start + vim.fn.strdisplaywidth(res.text) + col_end = col_start + strdisplaywidth(res.text) child.position.col_start = col_start child.position.col_end = col_end From a66c4c2f11896757edf208f9b2363bca560bca01 Mon Sep 17 00:00:00 2001 From: Chris Roscher Date: Wed, 28 May 2025 23:09:05 +0200 Subject: [PATCH 335/437] Fix: focus popup diff "range to", keep diff tab focused --- lua/neogit/popups/diff/actions.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index af184cbd4..2d7523641 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -28,13 +28,15 @@ function M.range(popup) ) ) - local range_from = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "Diff for range from" } + local range_from = FuzzyFinderBuffer.new(options):open_async { + prompt_prefix = "Diff for range from", refocus_status = false + } if not range_from then return end local range_to = FuzzyFinderBuffer.new(options) - :open_async { prompt_prefix = "Diff from " .. range_from .. " to" } + :open_async { prompt_prefix = "Diff from " .. range_from .. " to", refocus_status = false } if not range_to then return end From ce2a5effbe1457ae5b69f8026d48fd55c38667f2 Mon Sep 17 00:00:00 2001 From: Chris Roscher Date: Thu, 29 May 2025 01:26:49 +0200 Subject: [PATCH 336/437] Fix: keep stash and commit diff tab focused --- lua/neogit/popups/diff/actions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index 2d7523641..11e009ced 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -74,7 +74,7 @@ end function M.stash(popup) popup:close() - local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async() + local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async{ refocus_status = false } if selected then diffview.open("stashes", selected) end @@ -85,7 +85,7 @@ function M.commit(popup) local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - local selected = FuzzyFinderBuffer.new(options):open_async() + local selected = FuzzyFinderBuffer.new(options):open_async{ refocus_status = false } if selected then diffview.open("commit", selected) end From ab9137d5c99530c2da000001b0058e32412ff431 Mon Sep 17 00:00:00 2001 From: aland Date: Thu, 29 May 2025 10:30:30 +0700 Subject: [PATCH 337/437] provide popup.close for actions --- lua/neogit.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit.lua b/lua/neogit.lua index 6efaf407b..7f66dcce0 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -205,6 +205,7 @@ function M.action(popup, action, args) if fn then local action = function() fn { + close = function() end, state = { env = {} }, get_arguments = function() return args From beda8099114a1b2d592e60d0c1cc04cda6affb6a Mon Sep 17 00:00:00 2001 From: Chris Roscher Date: Thu, 29 May 2025 11:32:51 +0200 Subject: [PATCH 338/437] formatting --- lua/neogit/popups/diff/actions.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index 11e009ced..bd750668f 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -29,7 +29,8 @@ function M.range(popup) ) local range_from = FuzzyFinderBuffer.new(options):open_async { - prompt_prefix = "Diff for range from", refocus_status = false + prompt_prefix = "Diff for range from", + refocus_status = false, } if not range_from then return @@ -74,7 +75,7 @@ end function M.stash(popup) popup:close() - local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async{ refocus_status = false } + local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async { refocus_status = false } if selected then diffview.open("stashes", selected) end @@ -85,7 +86,7 @@ function M.commit(popup) local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - local selected = FuzzyFinderBuffer.new(options):open_async{ refocus_status = false } + local selected = FuzzyFinderBuffer.new(options):open_async { refocus_status = false } if selected then diffview.open("commit", selected) end From 025df0f5703ba5d1622839d41af89a9c4b8bbe08 Mon Sep 17 00:00:00 2001 From: aland Date: Fri, 30 May 2025 06:45:12 +0700 Subject: [PATCH 339/437] Update doc for graph bold highlights --- doc/neogit.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index aa2a51574..ed33e8c11 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -764,21 +764,21 @@ NeogitCommitViewHeader Applied to header of Commit View LOG VIEW BUFFER NeogitGraphAuthor Applied to the commit's author in graph view NeogitGraphBlack Used when --colors is enabled for graph -NeogitGraphBlackBold +NeogitGraphBoldBlack NeogitGraphRed -NeogitGraphRedBold +NeogitGraphBoldRed NeogitGraphGreen -NeogitGraphGreenBold +NeogitGraphBoldGreen NeogitGraphYellow -NeogitGraphYellowBold +NeogitGraphBoldYellow NeogitGraphBlue -NeogitGraphBlueBold +NeogitGraphBoldBlue NeogitGraphPurple -NeogitGraphPurpleBold +NeogitGraphBoldPurple NeogitGraphCyan -NeogitGraphCyanBold +NeogitGraphBoldCyan NeogitGraphWhite -NeogitGraphWhiteBold +NeogitGraphBoldWhite NeogitGraphGray NeogitGraphBoldGray NeogitGraphOrange From 12a4b0286d13fc17f6cec1c7bbb6f393665fe8fa Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 27 May 2025 15:17:48 +0200 Subject: [PATCH 340/437] Fix: Do not apply single highlight to rows in refs view that have no upstream. --- lua/neogit/buffers/refs_view/ui.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 462d8c84a..084660717 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -41,13 +41,18 @@ local function Cherries(ref, head) end local function Ref(ref) - return row { + local ref_content = { text.highlight("NeogitGraphBoldPurple")(ref.head and "@ " or " "), text.highlight(highlights[ref.type])(util.str_truncate(ref.name, 34), { align_right = 35 }), - text.highlight(highlights[ref.upstream_status])(ref.upstream_name), - text(ref.upstream_name ~= "" and " " or ""), text(ref.subject), } + + if ref.upstream_name ~= "" then + table.insert(ref_content, 3, text.highlight(highlights[ref.upstream_status])(ref.upstream_name)) + table.insert(ref_content, 4, text(" ")) + end + + return row(ref_content) end local function section(refs, heading, head) From 51ed84c019acad331b8dcd1fe9c6c84baf8c4e36 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 29 May 2025 19:43:42 +0200 Subject: [PATCH 341/437] Handle when pushDefault is set with a more informative label. --- lua/neogit/lib/git/branch.lua | 34 +++++++++++++++++++++++++++++++++ lua/neogit/popups/push/init.lua | 9 +++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 8eb80efa3..5542cea55 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -232,6 +232,40 @@ function M.pushRemote_ref(branch) end end +---@return string|nil +function M.pushDefault() + local pushDefault = git.config.get("remote.pushDefault") + if pushDefault:is_set() then + return pushDefault:read() ---@type string + end +end + +---@param branch? string +---@return string|nil +function M.pushDefault_ref(branch) + branch = branch or M.current() + local pushDefault = M.pushDefault() + + if branch and pushDefault then + return string.format("%s/%s", pushDefault, branch) + end +end + +---@return string +function M.pushRemote_or_pushDefault_label() + local ref = M.pushRemote_ref() + if ref then + return ref + end + + local pushDefault = M.pushDefault() + if pushDefault then + return ("%s, creating it"):format(M.pushDefault_ref()) + end + + return "pushRemote, setting that" +end + ---@return string function M.pushRemote_label() return M.pushRemote_ref() or "pushRemote, setting that" diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index 6b6124154..f954e2831 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -16,7 +16,7 @@ function M.create(env) :switch("d", "dry-run", "Dry run") :switch("u", "set-upstream", "Set the upstream before pushing") :group_heading("Push " .. ((current and (current .. " ")) or "") .. "to") - :action("p", git.branch.pushRemote_label(), actions.to_pushremote) + :action("p", git.branch.pushRemote_or_pushDefault_label(), actions.to_pushremote) :action("u", git.branch.upstream_label(), actions.to_upstream) :action("e", "elsewhere", actions.to_elsewhere) :new_action_group("Push") @@ -28,7 +28,12 @@ function M.create(env) :new_action_group("Configure") :action("C", "Set variables...", actions.configure) :env({ - highlight = { current, git.branch.upstream(), git.branch.pushRemote_ref() }, + highlight = { + current, + git.branch.upstream(), + git.branch.pushRemote_ref(), + git.branch.pushDefault_ref(), + }, bold = { "pushRemote", "@{upstream}" }, commit = env.commit, }) From 968c0b8da3b0076da5ab9e1398d7eb481a983e1e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 29 May 2025 19:48:21 +0200 Subject: [PATCH 342/437] Fix rebasing onto upstream when the upstream is not on a remote. --- lua/neogit/popups/rebase/actions.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 466d35359..a05fbf0a6 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -31,19 +31,14 @@ function M.onto_pushRemote(popup) end function M.onto_upstream(popup) - local upstream - if git.repo.state.upstream.ref then - upstream = string.format("refs/remotes/%s", git.repo.state.upstream.ref) - else - local target = FuzzyFinderBuffer.new(git.refs.list_remote_branches()):open_async() - if not target then - return - end - - upstream = string.format("refs/remotes/%s", target) + local upstream = git.branch.upstream(git.branch.current()) + if not upstream then + upstream = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() end - git.rebase.onto_branch(upstream, popup:get_arguments()) + if upstream then + git.rebase.onto_branch(upstream, popup:get_arguments()) + end end function M.onto_elsewhere(popup) From 6fd54f7f6957b49a39674d319e9252bb87efe05b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 14:31:18 +0200 Subject: [PATCH 343/437] pass in ordered commits to diff->this when a visual selection is made in the log/reflog buffer. This allows diffing a specific range of commits. --- lua/neogit/buffers/log_view/init.lua | 4 ++-- lua/neogit/buffers/reflog_view/init.lua | 4 ++-- lua/neogit/buffers/refs_view/init.lua | 4 ++-- lua/neogit/integrations/diffview.lua | 1 + lua/neogit/lib/ui/init.lua | 29 +++++++++++++++++++++++++ lua/neogit/popups/diff/actions.lua | 17 ++++++++++----- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 2dbb52890..118fd7517 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -119,10 +119,10 @@ function M:open() p { commits = self.buffer.ui:get_commits_in_selection() } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local items = self.buffer.ui:get_commits_in_selection() + local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - item = { name = items }, + items = items, } end), }, diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 8f62d78c1..ab27c7e07 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -89,10 +89,10 @@ function M:open(_) end), [popups.mapping_for("PullPopup")] = popups.open("pull"), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local items = self.buffer.ui:get_commits_in_selection() + local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - item = { name = items }, + items = items, } end), [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 78615806e..36f504d51 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -133,10 +133,10 @@ function M:open() p { commits = self.buffer.ui:get_commits_in_selection() } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local items = self.buffer.ui:get_commits_in_selection() + local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - item = { name = items }, + items = items, } end), [mapping["DeleteBranch"]] = function() diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 42cf8f92b..6f467d406 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -47,6 +47,7 @@ local function get_local_diff_view(section_name, item_name, opts) selected = (item_name and item.name == item_name) or (not item_name and idx == 1), } + -- restrict diff to only a particular section if opts.only then if (item_name and file.selected) or (not item_name and section_name == kind) then table.insert(files[kind], file) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 6e4ef243a..c2dcba05e 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -284,6 +284,7 @@ function Ui:get_selection() return setmetatable(res, Selection) end +--- returns commits in selection in a constant order ---@return string[] function Ui:get_commits_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -304,6 +305,33 @@ function Ui:get_commits_in_selection() return util.deduplicate(commits) end +--- returns commits in selection ordered according to the direction of the selection the user has made +---@return string[] +function Ui:get_ordered_commits_in_selection() + local start = vim.fn.getpos("v")[2] + local stop = vim.fn.getpos(".")[2] + + local increment + if start <= stop then + increment = 1 + else + increment = -1 + end + + local commits = {} + for i = start, stop, increment do + local component = self:_find_component_by_index(i, function(node) + return node.options.oid ~= nil + end) + + if component then + table.insert(commits, component.options.oid) + end + end + + return util.deduplicate(commits) +end + ---@return string[] function Ui:get_filepaths_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -343,6 +371,7 @@ function Ui:get_ref_under_cursor() return component and component.options.ref end + --- ---@return ParsedRef[] function Ui:get_refs_under_cursor() diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index af184cbd4..b2ac8d17e 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -9,12 +9,17 @@ local input = require("neogit.lib.input") function M.this(popup) popup:close() - if popup.state.env.section and popup.state.env.item then - diffview.open(popup.state.env.section.name, popup.state.env.item.name, { - only = true, - }) - elseif popup.state.env.section then - diffview.open(popup.state.env.section.name, nil, { only = true }) + local item = popup:get_env("item") + local items = popup:get_env("items") + local section = popup:get_env("section") + + if items[1] then + local range = items[1] .. ".." .. items[#items] + diffview.open("range", range) + elseif section and item then + diffview.open(section, item, { only = true }) + elseif section then + diffview.open(section, nil, { only = true }) end end From 804af48f93c563cfb80d0d0488346e9101edf814 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 14:45:53 +0200 Subject: [PATCH 344/437] Allow diff->range action to list the current commit under cursor when prompting for options. --- lua/neogit/popups/diff/actions.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index b2ac8d17e..9ad61cfbc 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -24,9 +24,16 @@ function M.this(popup) end function M.range(popup) + local commit + local item = popup:get_env("item") + local section = popup:get_env("section") + if section and section.name == "log" then + commit = item and item.name + end + local options = util.deduplicate( util.merge( - { git.branch.current() or "HEAD" }, + { commit, git.branch.current() or "HEAD" }, git.branch.get_all_branches(false), git.tag.list(), git.refs.heads() From f1332c7bc482295db9efa2019629eed5ff4d900c Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 15:51:39 +0200 Subject: [PATCH 345/437] Allow quick diffing against head when invoking the diff popup without a selection from the log, refs, reflog, and commit view. --- lua/neogit/buffers/log_view/init.lua | 2 +- lua/neogit/buffers/reflog_view/init.lua | 2 +- lua/neogit/buffers/refs_view/init.lua | 2 +- lua/neogit/integrations/diffview.lua | 8 +++-- lua/neogit/popups/diff/actions.lua | 45 ++++++++++++++++--------- lua/neogit/popups/diff/init.lua | 2 ++ 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 118fd7517..45aa1ed18 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -122,7 +122,7 @@ function M:open() local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - items = items, + item = { name = items }, } end), }, diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index ab27c7e07..0187a187d 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -92,7 +92,7 @@ function M:open(_) local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - items = items, + item = { name = items }, } end), [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 36f504d51..bb8f70062 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -136,7 +136,7 @@ function M:open() local items = self.buffer.ui:get_ordered_commits_in_selection() p { section = { name = "log" }, - items = items, + item = { name = items }, } end), [mapping["DeleteBranch"]] = function() diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 6f467d406..54cdd9915 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -95,7 +95,7 @@ local function get_local_diff_view(section_name, item_name, opts) end ---@param section_name string ----@param item_name string|nil +---@param item_name string|string[]|nil ---@param opts table|nil function M.open(section_name, item_name, opts) opts = opts or {} @@ -111,7 +111,11 @@ function M.open(section_name, item_name, opts) local view -- selene: allow(if_same_then_else) - if section_name == "recent" or section_name:match("unmerged$") or section_name == "log" then + if + section_name == "recent" + or section_name == "log" + or (section_name and section_name:match("unmerged$")) + then local range if type(item_name) == "table" then range = string.format("%s..%s", item_name[1], item_name[#item_name]) diff --git a/lua/neogit/popups/diff/actions.lua b/lua/neogit/popups/diff/actions.lua index 9ad61cfbc..015b84214 100644 --- a/lua/neogit/popups/diff/actions.lua +++ b/lua/neogit/popups/diff/actions.lua @@ -10,16 +10,25 @@ function M.this(popup) popup:close() local item = popup:get_env("item") - local items = popup:get_env("items") local section = popup:get_env("section") - if items[1] then - local range = items[1] .. ".." .. items[#items] - diffview.open("range", range) - elseif section and item then - diffview.open(section, item, { only = true }) - elseif section then - diffview.open(section, nil, { only = true }) + if section and section.name and item and item.name then + diffview.open(section.name, item.name, { only = true }) + elseif section.name then + diffview.open(section.name, nil, { only = true }) + elseif item.name then + diffview.open("range", item.name .. "..HEAD") + end +end + +function M.this_to_HEAD(popup) + popup:close() + + local item = popup:get_env("item") + if item then + if item.name then + diffview.open("range", item.name .. "..HEAD") + end end end @@ -27,7 +36,7 @@ function M.range(popup) local commit local item = popup:get_env("item") local section = popup:get_env("section") - if section and section.name == "log" then + if section and (section.name == "log" or section.name == "recent") then commit = item and item.name end @@ -40,23 +49,27 @@ function M.range(popup) ) ) - local range_from = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "Diff for range from" } + local range_from = FuzzyFinderBuffer.new(options):open_async { + prompt_prefix = "Diff for range from", + refocus_status = false, + } + if not range_from then return end local range_to = FuzzyFinderBuffer.new(options) - :open_async { prompt_prefix = "Diff from " .. range_from .. " to" } + :open_async { prompt_prefix = "Diff from " .. range_from .. " to", refocus_status = false } if not range_to then return end local choices = { - "&1. " .. range_from .. ".." .. range_to, - "&2. " .. range_from .. "..." .. range_to, + "&1. Range (a..b)", + "&2. Symmetric Difference (a...b)", "&3. Cancel", } - local choice = input.get_choice("Select range", { values = choices, default = #choices }) + local choice = input.get_choice("Select type", { values = choices, default = #choices }) popup:close() if choice == "1" then @@ -84,7 +97,7 @@ end function M.stash(popup) popup:close() - local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async() + local selected = FuzzyFinderBuffer.new(git.stash.list()):open_async { refocus_status = false } if selected then diffview.open("stashes", selected) end @@ -95,7 +108,7 @@ function M.commit(popup) local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - local selected = FuzzyFinderBuffer.new(options):open_async() + local selected = FuzzyFinderBuffer.new(options):open_async { refocus_status = false } if selected then diffview.open("commit", selected) end diff --git a/lua/neogit/popups/diff/init.lua b/lua/neogit/popups/diff/init.lua index ed8b849c0..7530f3d4c 100644 --- a/lua/neogit/popups/diff/init.lua +++ b/lua/neogit/popups/diff/init.lua @@ -6,12 +6,14 @@ local actions = require("neogit.popups.diff.actions") function M.create(env) local diffview = config.check_integration("diffview") + local commit_selected = env.section.name == "log" and type(env.item.name) == "string" local p = popup .builder() :name("NeogitDiffPopup") :group_heading("Diff") :action_if(diffview, "d", "this", actions.this) + :action_if(diffview and commit_selected, "h", "this..HEAD", actions.this_to_HEAD) :action_if(diffview, "r", "range", actions.range) :action("p", "paths") :new_action_group() From db902cfe8180929d88a1cda569b2519f2aa82e2a Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 15:57:05 +0200 Subject: [PATCH 346/437] Add tags and follow-tags switches to push popup --- lua/neogit/popups/push/init.lua | 2 ++ spec/popups/push_popup_spec.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index f954e2831..935931a26 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -15,6 +15,8 @@ function M.create(env) :switch("h", "no-verify", "Disable hooks") :switch("d", "dry-run", "Dry run") :switch("u", "set-upstream", "Set the upstream before pushing") + :switch("T", "tags", "Include all tags") + :switch("t", "follow-tags", "Include related annotated tags") :group_heading("Push " .. ((current and (current .. " ")) or "") .. "to") :action("p", git.branch.pushRemote_or_pushDefault_label(), actions.to_pushremote) :action("u", git.branch.upstream_label(), actions.to_upstream) diff --git a/spec/popups/push_popup_spec.rb b/spec/popups/push_popup_spec.rb index fc6e32bf8..5e447ceb2 100644 --- a/spec/popups/push_popup_spec.rb +++ b/spec/popups/push_popup_spec.rb @@ -13,6 +13,8 @@ " -h Disable hooks (--no-verify) ", " -d Dry run (--dry-run) ", " -u Set the upstream before pushing (--set-upstream) ", + " -T Include all tags (--tags) ", + " -t Include related annotated tags (--follow-tags) ", " ", " Push master to Push Configure ", " p pushRemote, setting that o another branch C Set variables... ", From 855007c67853e45530d1a28f1ddc62ef8b6907ae Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 19:14:30 +0200 Subject: [PATCH 347/437] Fix types --- lua/neogit/integrations/diffview.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 54cdd9915..c7c3cfe67 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -130,7 +130,8 @@ function M.open(section_name, item_name, opts) local range = item_name view = dv_lib.diffview_open(dv_utils.tbl_pack(range)) elseif section_name == "stashes" then - assert(item_name, "No item name for stash!") + assert(item_name and type(item_name) == "string", "No item name for stash!") + local stash_id = item_name:match("stash@{%d+}") view = dv_lib.diffview_open(dv_utils.tbl_pack(stash_id .. "^!")) elseif section_name == "commit" then From 0f901ca1ce528eb9903aff45c17fd4c7c9f9d2e0 Mon Sep 17 00:00:00 2001 From: aland Date: Thu, 29 May 2025 10:30:30 +0700 Subject: [PATCH 348/437] provide popup.close for actions --- lua/neogit.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit.lua b/lua/neogit.lua index 6efaf407b..7f66dcce0 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -205,6 +205,7 @@ function M.action(popup, action, args) if fn then local action = function() fn { + close = function() end, state = { env = {} }, get_arguments = function() return args From db082683b0c93fa7f31d6e2e324d92b9f3f0375d Mon Sep 17 00:00:00 2001 From: aland Date: Fri, 30 May 2025 06:45:12 +0700 Subject: [PATCH 349/437] Update doc for graph bold highlights --- doc/neogit.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index aa2a51574..ed33e8c11 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -764,21 +764,21 @@ NeogitCommitViewHeader Applied to header of Commit View LOG VIEW BUFFER NeogitGraphAuthor Applied to the commit's author in graph view NeogitGraphBlack Used when --colors is enabled for graph -NeogitGraphBlackBold +NeogitGraphBoldBlack NeogitGraphRed -NeogitGraphRedBold +NeogitGraphBoldRed NeogitGraphGreen -NeogitGraphGreenBold +NeogitGraphBoldGreen NeogitGraphYellow -NeogitGraphYellowBold +NeogitGraphBoldYellow NeogitGraphBlue -NeogitGraphBlueBold +NeogitGraphBoldBlue NeogitGraphPurple -NeogitGraphPurpleBold +NeogitGraphBoldPurple NeogitGraphCyan -NeogitGraphCyanBold +NeogitGraphBoldCyan NeogitGraphWhite -NeogitGraphWhiteBold +NeogitGraphBoldWhite NeogitGraphGray NeogitGraphBoldGray NeogitGraphOrange From 8d31b4459a628672065d67fee658e72b73dabf65 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 21:51:36 +0200 Subject: [PATCH 350/437] fix: push and pull popup work again with detached head --- lua/neogit/popups/pull/init.lua | 14 +++++++------- lua/neogit/popups/push/init.lua | 14 ++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index a1da91f69..070dd91ce 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -5,15 +5,15 @@ local popup = require("neogit.lib.popup") local M = {} function M.create() - local current = git.branch.current() - local show_config = current ~= "" and current ~= "(detached)" + local current = git.branch.current() or "" local pull_rebase_entry = git.config.get("pull.rebase") local pull_rebase = pull_rebase_entry:is_set() and pull_rebase_entry.value or "false" + local is_detached = git.branch.is_detached() local p = popup .builder() :name("NeogitPullPopup") - :config_if(show_config, "r", "branch." .. (current or "") .. ".rebase", { + :config_if(not is_detached, "r", "branch." .. current .. ".rebase", { options = { { display = "true", value = "true" }, { display = "false", value = "false" }, @@ -25,10 +25,10 @@ function M.create() :switch("a", "autostash", "Autostash") :switch("t", "tags", "Fetch tags") :switch("F", "force", "Force", { persisted = false }) - :group_heading_if(current ~= nil, "Pull into " .. current .. " from") - :group_heading_if(not current, "Pull from") - :action_if(current ~= nil, "p", git.branch.pushRemote_label(), actions.from_pushremote) - :action_if(current ~= nil, "u", git.branch.upstream_label(), actions.from_upstream) + :group_heading_if(not is_detached, "Pull into " .. current .. " from") + :group_heading_if(is_detached, "Pull from") + :action_if(not is_detached, "p", git.branch.pushRemote_label(), actions.from_pushremote) + :action_if(not is_detached, "u", git.branch.upstream_label(), actions.from_upstream) :action("e", "elsewhere", actions.from_elsewhere) :new_action_group("Configure") :action("C", "Set variables...", actions.configure) diff --git a/lua/neogit/popups/push/init.lua b/lua/neogit/popups/push/init.lua index 935931a26..b4357a2f8 100644 --- a/lua/neogit/popups/push/init.lua +++ b/lua/neogit/popups/push/init.lua @@ -5,7 +5,8 @@ local git = require("neogit.lib.git") local M = {} function M.create(env) - local current = git.branch.current() + local current = git.branch.current() or "" + local is_detached = git.branch.is_detached() local p = popup .builder() @@ -17,11 +18,12 @@ function M.create(env) :switch("u", "set-upstream", "Set the upstream before pushing") :switch("T", "tags", "Include all tags") :switch("t", "follow-tags", "Include related annotated tags") - :group_heading("Push " .. ((current and (current .. " ")) or "") .. "to") - :action("p", git.branch.pushRemote_or_pushDefault_label(), actions.to_pushremote) - :action("u", git.branch.upstream_label(), actions.to_upstream) - :action("e", "elsewhere", actions.to_elsewhere) - :new_action_group("Push") + :group_heading_if(not is_detached, "Push " .. current .. " to") + :action_if(not is_detached, "p", git.branch.pushRemote_or_pushDefault_label(), actions.to_pushremote) + :action_if(not is_detached, "u", git.branch.upstream_label(), actions.to_upstream) + :action_if(not is_detached, "e", "elsewhere", actions.to_elsewhere) + :group_heading_if(is_detached, "Push") + :new_action_group_if(not is_detached, "Push") :action("o", "another branch", actions.push_other) :action("r", "explicit refspec", actions.explicit_refspec) :action("m", "matching branches", actions.matching_branches) From 65ee561a80300e062ab5861cc561dfe7fc68436b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 30 May 2025 21:52:00 +0200 Subject: [PATCH 351/437] Add specs for all popups to assert they do not error out with detached HEAD state in repo. --- spec/popups/bisect_popup_spec.rb | 3 +-- spec/popups/branch_config_popup_spec.rb | 3 +-- spec/popups/branch_popup_spec.rb | 3 +-- spec/popups/cherry_pick_popup_spec.rb | 3 +-- spec/popups/commit_popup_spec.rb | 3 +-- spec/popups/diff_popup_spec.rb | 3 +-- spec/popups/fetch_popup_spec.rb | 3 +-- spec/popups/help_popup_spec.rb | 3 +-- spec/popups/ignore_popup_spec.rb | 3 +-- spec/popups/log_popup_spec.rb | 2 +- spec/popups/merge_popup_spec.rb | 3 +-- spec/popups/pull_popup_spec.rb | 3 +-- spec/popups/push_popup_spec.rb | 2 +- spec/popups/rebase_popup_spec.rb | 2 +- spec/popups/remote_popup_spec.rb | 3 +-- spec/popups/reset_popup_spec.rb | 2 +- spec/popups/revert_popup_spec.rb | 2 +- spec/popups/stash_popup_spec.rb | 2 +- spec/popups/tag_popup_spec.rb | 2 +- spec/popups/worktree_popup_spec.rb | 2 +- spec/support/shared.rb | 16 ++++++++++++++++ 21 files changed, 36 insertions(+), 32 deletions(-) diff --git a/spec/popups/bisect_popup_spec.rb b/spec/popups/bisect_popup_spec.rb index 87714c9ab..3654228ef 100644 --- a/spec/popups/bisect_popup_spec.rb +++ b/spec/popups/bisect_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Bisect Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("B") } - + let(:keymap) { "B" } let(:view) do [ " Arguments ", diff --git a/spec/popups/branch_config_popup_spec.rb b/spec/popups/branch_config_popup_spec.rb index 57dee8d30..bc0875581 100644 --- a/spec/popups/branch_config_popup_spec.rb +++ b/spec/popups/branch_config_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Branch Config Popup", :git, :nvim, :popup do - before { nvim.keys("bC") } - + let(:keymap) { "bC" } let(:view) do [ " Configure branch ", diff --git a/spec/popups/branch_popup_spec.rb b/spec/popups/branch_popup_spec.rb index f699c754f..fac371074 100644 --- a/spec/popups/branch_popup_spec.rb +++ b/spec/popups/branch_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Branch Popup", :git, :nvim, :popup do - before { nvim.keys("b") } - + let(:keymap) { "b" } let(:view) do [ " Configure branch ", diff --git a/spec/popups/cherry_pick_popup_spec.rb b/spec/popups/cherry_pick_popup_spec.rb index a98bd109b..2b7cb78eb 100644 --- a/spec/popups/cherry_pick_popup_spec.rb +++ b/spec/popups/cherry_pick_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Cherry Pick Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("A") } - + let(:keymap) { "A" } let(:view) do [ " Arguments ", diff --git a/spec/popups/commit_popup_spec.rb b/spec/popups/commit_popup_spec.rb index 81c2e3d83..2fa2294ce 100644 --- a/spec/popups/commit_popup_spec.rb +++ b/spec/popups/commit_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Commit Popup", :git, :nvim, :popup do - before { nvim.keys("c") } - + let(:keymap) { "c" } let(:view) do [ " Arguments ", diff --git a/spec/popups/diff_popup_spec.rb b/spec/popups/diff_popup_spec.rb index cff609ffa..b78b695ce 100644 --- a/spec/popups/diff_popup_spec.rb +++ b/spec/popups/diff_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Diff Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("d") } - + let(:keymap) { "d" } let(:view) do [ " Diff Show ", diff --git a/spec/popups/fetch_popup_spec.rb b/spec/popups/fetch_popup_spec.rb index 390abf356..755d83f3d 100644 --- a/spec/popups/fetch_popup_spec.rb +++ b/spec/popups/fetch_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Fetch Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("f") } - + let(:keymap) { "f" } let(:view) do [ " Arguments ", diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index 8b3846a4d..f7aca60ab 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Help Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("?") } - + let(:keymap) { "?" } let(:view) do [ " Commands Applying changes Essential commands ", diff --git a/spec/popups/ignore_popup_spec.rb b/spec/popups/ignore_popup_spec.rb index ce833f852..3e8deebc2 100644 --- a/spec/popups/ignore_popup_spec.rb +++ b/spec/popups/ignore_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Ignore Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("i") } - + let(:keymap) { "i" } let(:view) do [ " Gitignore ", diff --git a/spec/popups/log_popup_spec.rb b/spec/popups/log_popup_spec.rb index 88bfebfb1..06e434b89 100644 --- a/spec/popups/log_popup_spec.rb +++ b/spec/popups/log_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Log Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("l") } + let(:keymap) { "l" } # TODO: PTY needs to be bigger to show the entire popup let(:view) do diff --git a/spec/popups/merge_popup_spec.rb b/spec/popups/merge_popup_spec.rb index 7a4f207a3..e5c9130f5 100644 --- a/spec/popups/merge_popup_spec.rb +++ b/spec/popups/merge_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Merge Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("m") } - + let(:keymap) { "m" } let(:view) do [ " Arguments ", diff --git a/spec/popups/pull_popup_spec.rb b/spec/popups/pull_popup_spec.rb index b40e4d79f..b535a1573 100644 --- a/spec/popups/pull_popup_spec.rb +++ b/spec/popups/pull_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Pull Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("p") } - + let(:keymap) { "p" } let(:view) do [ " Variables ", diff --git a/spec/popups/push_popup_spec.rb b/spec/popups/push_popup_spec.rb index 5e447ceb2..fc9af1360 100644 --- a/spec/popups/push_popup_spec.rb +++ b/spec/popups/push_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Push Popup", :git, :nvim, :popup, :with_remote_origin do - before { nvim.keys("P") } + let(:keymap) { "P" } let(:view) do [ diff --git a/spec/popups/rebase_popup_spec.rb b/spec/popups/rebase_popup_spec.rb index 1faad3661..ad43d5101 100644 --- a/spec/popups/rebase_popup_spec.rb +++ b/spec/popups/rebase_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Rebase Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("r") } + let(:keymap) { "r" } let(:view) do [ diff --git a/spec/popups/remote_popup_spec.rb b/spec/popups/remote_popup_spec.rb index f3bbdd35d..cff948627 100644 --- a/spec/popups/remote_popup_spec.rb +++ b/spec/popups/remote_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Remote Popup", :git, :nvim, :popup do - before { nvim.keys("M") } - + let(:keymap) { "M" } let(:view) do [ " Variables ", diff --git a/spec/popups/reset_popup_spec.rb b/spec/popups/reset_popup_spec.rb index d7543366c..81b170145 100644 --- a/spec/popups/reset_popup_spec.rb +++ b/spec/popups/reset_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Reset Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("X") } + let(:keymap) { "X" } let(:view) do [ diff --git a/spec/popups/revert_popup_spec.rb b/spec/popups/revert_popup_spec.rb index 996d6822f..07d97b487 100644 --- a/spec/popups/revert_popup_spec.rb +++ b/spec/popups/revert_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Revert Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("v") } + let(:keymap) { "v" } let(:view) do [ diff --git a/spec/popups/stash_popup_spec.rb b/spec/popups/stash_popup_spec.rb index 25810ab16..c4c339397 100644 --- a/spec/popups/stash_popup_spec.rb +++ b/spec/popups/stash_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Stash Popup", :git, :nvim, :popup do - before { nvim.keys("Z") } + let(:keymap) { "Z" } let(:view) do [ diff --git a/spec/popups/tag_popup_spec.rb b/spec/popups/tag_popup_spec.rb index 31fdf6227..142aaee96 100644 --- a/spec/popups/tag_popup_spec.rb +++ b/spec/popups/tag_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Tag Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("t") } + let(:keymap) { "t" } let(:view) do [ diff --git a/spec/popups/worktree_popup_spec.rb b/spec/popups/worktree_popup_spec.rb index d6027b14b..e4a495336 100644 --- a/spec/popups/worktree_popup_spec.rb +++ b/spec/popups/worktree_popup_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe "Worktree Popup", :git, :nvim, :popup do - before { nvim.keys("w") } + let(:keymap) { "w" } let(:view) do [ diff --git a/spec/support/shared.rb b/spec/support/shared.rb index 187775c0d..c68584ac5 100644 --- a/spec/support/shared.rb +++ b/spec/support/shared.rb @@ -15,10 +15,26 @@ end RSpec.shared_examples "popup", :popup do + before do + nvim.keys(keymap) + end + it "raises no errors" do expect(nvim.errors).to be_empty end + it "raises no errors with detached HEAD" do + nvim.keys("") # close popup + + # Detach HEAD + git.commit("dummy commit", allow_empty: true) + git.checkout("HEAD^") + + sleep(1) # Allow state to propagate + nvim.keys(keymap) # open popup + expect(nvim.errors).to be_empty + end + it "has correct filetype" do expect(nvim.filetype).to eq("NeogitPopup") end From c73c5d83301fd3eb2d6789683016e707b3176908 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Jun 2025 19:11:27 +0200 Subject: [PATCH 352/437] Add timeout and specs for launching popup without status buffer --- spec/general_spec.rb | 20 ++++++++++++++++++++ spec/spec_helper.rb | 8 +++++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 spec/general_spec.rb diff --git a/spec/general_spec.rb b/spec/general_spec.rb new file mode 100644 index 000000000..e452d1efe --- /dev/null +++ b/spec/general_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.describe "general things", :git, :nvim do + popups = %w[ + bisect branch branch_config cherry_pick commit + diff fetch help ignore log merge pull push rebase + remote remote_config reset revert stash tag worktree + ] + + popups.each do |popup| + it "can invoke #{popup} popup without status buffer" do + nvim.keys("q") + nvim.lua("require('neogit').open({ '#{popup}' })") + sleep(0.1) # Allow popup to open + + expect(nvim.filetype).to eq("NeogitPopup") + expect(nvim.errors).to be_empty + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c2c8f628a..9c4222d88 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,7 +51,9 @@ end end - # config.around do |example| - # Timeout.timeout(10) { example.call } - # end + config.around do |example| + Timeout.timeout(10) do + example.run + end + end end From 91841e6b1febe0b5894bd285a297e82d77525a5e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Jun 2025 19:11:48 +0200 Subject: [PATCH 353/437] Add warning when launching branch/remote config popups without a branch/remote specified. --- lua/neogit/popups/branch_config/init.lua | 6 ++++++ lua/neogit/popups/remote_config/init.lua | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index 5076d5d56..f3f62df81 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -3,10 +3,16 @@ local M = {} local popup = require("neogit.lib.popup") local git = require("neogit.lib.git") local actions = require("neogit.popups.branch_config.actions") +local notification = require("neogit.lib.notification") function M.create(branch) branch = branch or git.branch.current() + if not branch then + notification.error("No branch selected.") + return + end + local g_pull_rebase = git.config.get_global("pull.rebase") local pull_rebase_entry = git.config.get_local("pull.rebase") local pull_rebase = pull_rebase_entry:is_set() and pull_rebase_entry.value or "false" diff --git a/lua/neogit/popups/remote_config/init.lua b/lua/neogit/popups/remote_config/init.lua index 0a2d9538a..01a87e2db 100644 --- a/lua/neogit/popups/remote_config/init.lua +++ b/lua/neogit/popups/remote_config/init.lua @@ -1,7 +1,13 @@ local M = {} local popup = require("neogit.lib.popup") +local notification = require("neogit.lib.notification") function M.create(remote) + if not remote then + notification.error("No remote selected.") + return + end + local p = popup .builder() :name("NeogitRemoteConfigPopup") From 6686f3818e178eeee4614cb47c8ea320ee1daa01 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Jun 2025 19:55:59 +0200 Subject: [PATCH 354/437] Ensure there's a timeout when running on CI server --- spec/spec_helper.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9c4222d88..51205f17a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,9 +51,11 @@ end end - config.around do |example| - Timeout.timeout(10) do - example.run + if ENV["CI"].present? + config.around do |example| + Timeout.timeout(10) do + example.run + end end end end From 6f2237798fb51659be8dd4bbff2267d33c9ec006 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Jun 2025 19:56:32 +0200 Subject: [PATCH 355/437] Ensure remote/branch config popups get a table (env) as argument like all other popups. fix diff popup when launched directly. env has no section. --- lua/neogit/popups/branch/actions.lua | 2 +- lua/neogit/popups/branch_config/init.lua | 7 ++++--- lua/neogit/popups/diff/init.lua | 4 ++-- lua/neogit/popups/fetch/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 2 +- lua/neogit/popups/push/actions.lua | 2 +- lua/neogit/popups/remote/actions.lua | 2 +- lua/neogit/popups/remote_config/init.lua | 22 +++++++++++++++++++--- spec/general_spec.rb | 2 +- 9 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 751b6b2b3..125be20d8 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -225,7 +225,7 @@ function M.configure_branch() return end - BranchConfigPopup.create(branch_name) + BranchConfigPopup.create { branch = branch_name } end function M.rename_branch() diff --git a/lua/neogit/popups/branch_config/init.lua b/lua/neogit/popups/branch_config/init.lua index f3f62df81..2cd1e8171 100644 --- a/lua/neogit/popups/branch_config/init.lua +++ b/lua/neogit/popups/branch_config/init.lua @@ -5,11 +5,12 @@ local git = require("neogit.lib.git") local actions = require("neogit.popups.branch_config.actions") local notification = require("neogit.lib.notification") -function M.create(branch) - branch = branch or git.branch.current() +---@param env table +function M.create(env) + local branch = env.branch or git.branch.current() if not branch then - notification.error("No branch selected.") + notification.error("Cannot infer branch.") return end diff --git a/lua/neogit/popups/diff/init.lua b/lua/neogit/popups/diff/init.lua index 7530f3d4c..34b32ef83 100644 --- a/lua/neogit/popups/diff/init.lua +++ b/lua/neogit/popups/diff/init.lua @@ -6,13 +6,13 @@ local actions = require("neogit.popups.diff.actions") function M.create(env) local diffview = config.check_integration("diffview") - local commit_selected = env.section.name == "log" and type(env.item.name) == "string" + local commit_selected = (env.section and env.section.name == "log") and type(env.item.name) == "string" local p = popup .builder() :name("NeogitDiffPopup") :group_heading("Diff") - :action_if(diffview, "d", "this", actions.this) + :action_if(diffview and env.item, "d", "this", actions.this) :action_if(diffview and commit_selected, "h", "this..HEAD", actions.this_to_HEAD) :action_if(diffview, "r", "range", actions.range) :action("p", "paths") diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index 86ee3034d..8c3e25805 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -125,7 +125,7 @@ function M.fetch_submodules(_) end function M.set_variables() - require("neogit.popups.branch_config").create() + require("neogit.popups.branch_config").create {} end return M diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index b7a1b631e..95e7aa51d 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -86,7 +86,7 @@ function M.from_elsewhere(popup) end function M.configure() - require("neogit.popups.branch_config").create() + require("neogit.popups.branch_config").create {} end return M diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 87c52a486..4af6216e0 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -195,7 +195,7 @@ function M.explicit_refspec(popup) end function M.configure() - require("neogit.popups.branch_config").create() + require("neogit.popups.branch_config").create {} end return M diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 5582e2549..06609bec7 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -114,7 +114,7 @@ function M.configure(_) return end - RemoteConfigPopup.create(remote_name) + RemoteConfigPopup.create { remote = remote_name } end function M.prune_branches(_) diff --git a/lua/neogit/popups/remote_config/init.lua b/lua/neogit/popups/remote_config/init.lua index 01a87e2db..728aca14b 100644 --- a/lua/neogit/popups/remote_config/init.lua +++ b/lua/neogit/popups/remote_config/init.lua @@ -1,13 +1,29 @@ local M = {} local popup = require("neogit.lib.popup") local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") -function M.create(remote) - if not remote then - notification.error("No remote selected.") +---@param env table +function M.create(env) + local remotes = git.remote.list() + if vim.tbl_isempty(remotes) then + notification.warn("Repo has no configured remotes.") return end + local remote = env.remote + + if not remote then + if vim.tbl_contains(remotes, "origin") then + remote = "origin" + elseif #remotes == 1 then + remote = remotes[1] + else + notification.error("Cannot infer remote.") + return + end + end + local p = popup .builder() :name("NeogitRemoteConfigPopup") diff --git a/spec/general_spec.rb b/spec/general_spec.rb index e452d1efe..30fb8f4d2 100644 --- a/spec/general_spec.rb +++ b/spec/general_spec.rb @@ -8,7 +8,7 @@ ] popups.each do |popup| - it "can invoke #{popup} popup without status buffer" do + it "can invoke #{popup} popup without status buffer", :with_remote_origin do nvim.keys("q") nvim.lua("require('neogit').open({ '#{popup}' })") sleep(0.1) # Allow popup to open From d86fbd7db78f4d5dfb82447def1d01d3176ace39 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 1 Jun 2025 20:17:11 +0200 Subject: [PATCH 356/437] Update hooks, ensure llscheck runs --- lefthook.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index bef6a3a9a..0920c0252 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,8 +1,6 @@ skip_output: - meta pre-push: - only: - - ref: master files: "rg --files" parallel: true commands: @@ -19,7 +17,7 @@ pre-push: run: typos {files} lua-types: glob: "*.lua" - run: llscheck lua/ + run: llscheck lua/ || echo {files} lua-test: glob: "tests/specs/**/*_spec.lua" run: nvim --headless -S "./tests/init.lua" || echo {files} @@ -29,4 +27,23 @@ pre-push: - GIT_CONFIG_SYSTEM: /dev/null - NVIM_APPNAME: neogit-test rspec: + only: + - ref: master run: bin/specs {files} +pre-commit: + parallel: true + commands: + rubocop: + glob: "*.rb" + run: bundle exec rubocop {staged_files} + selene: + glob: "{lua,plugin}/**/*.lua" + run: selene --config selene/config.toml {staged_files} + stylua: + glob: "*.lua" + run: stylua --check {staged_files} + typos: + run: typos {staged_files} + lua-types: + glob: "*.lua" + run: llscheck lua/ From 0583348154d3e1a6aef147b6092d32b287036497 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Jun 2025 09:22:26 +0200 Subject: [PATCH 357/437] tweak behaviour of options - when they are set, the trigger key will first unset them, before letting you set them again. --- lua/neogit/lib/popup/init.lua | 38 ++++++++++------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 394e0ebce..cc4b34a4e 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -180,42 +180,26 @@ end ---@param value? string ---@return nil function M:set_option(option, value) - -- Prompt user to select from predetermined choices - if value then + if option.value and option.value ~= "" then -- Toggle option off when it's currently set + option.value = "" + elseif value then option.value = value elseif option.choices then - if not option.value or option.value == "" then - local eventignore = vim.o.eventignore - vim.o.eventignore = "WinLeave" - local choice = FuzzyFinderBuffer.new(option.choices):open_async { - prompt_prefix = option.description, - } - vim.o.eventignore = eventignore - - if choice then - option.value = choice - else - option.value = "" - end - else - option.value = "" - end + local eventignore = vim.o.eventignore + vim.o.eventignore = "WinLeave" + option.value = FuzzyFinderBuffer.new(option.choices):open_async { + prompt_prefix = option.description, + refocus_status = false, + } + vim.o.eventignore = eventignore elseif option.fn then option.value = option.fn(self, option) else - local input = input.get_user_input(option.cli, { + option.value = input.get_user_input(option.cli, { separator = "=", default = option.value, cancel = option.value, }) - - -- If the option specifies a default value, and the user set the value to be empty, defer to default value. - -- This is handy to prevent the user from accidentally loading thousands of log entries by accident. - if option.default and input == "" then - option.value = tostring(option.default) - else - option.value = input - end end state.set({ self.state.name, option.cli }, option.value) From 00038cca54436b6fecc064bba00bf42b77189041 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Jun 2025 09:23:01 +0200 Subject: [PATCH 358/437] prevent error when canceling out of options choice fuzzy finder --- lua/neogit/lib/popup/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index cc4b34a4e..b8f927a61 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -386,7 +386,6 @@ end function M:refresh() if self.buffer then - self.buffer:focus() self.buffer.ui:render(unpack(ui.Popup(self.state))) end end From 89a2df9a882868ddfa2a757e3224f57971e38329 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Jun 2025 10:50:03 +0200 Subject: [PATCH 359/437] Add autocmd to refresh open buffers after making changes in neogit. --- lua/neogit/autocmds.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/neogit/autocmds.lua b/lua/neogit/autocmds.lua index bfb4bdb19..edcd7926c 100644 --- a/lua/neogit/autocmds.lua +++ b/lua/neogit/autocmds.lua @@ -46,6 +46,14 @@ function M.setup() autocmd_disabled = args.event == "QuickFixCmdPre" end, }) + + -- Ensure vim buffers are updated + api.nvim_create_autocmd("User", { + pattern = "NeogitStatusRefreshed", + callback = function() + vim.cmd("set autoread | checktime") + end, + }) end return M From cc0dd574c6fe3a24c1a8d4595892930e4f2ac405 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 4 Jun 2025 11:53:38 +0200 Subject: [PATCH 360/437] add annotation --- lua/neogit/buffers/process/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua index aa38db1ab..1e7308ce0 100644 --- a/lua/neogit/buffers/process/init.lua +++ b/lua/neogit/buffers/process/init.lua @@ -52,6 +52,7 @@ function M:show() self:flush_content() end +---@return boolean function M:is_visible() return self.buffer and self.buffer:is_valid() and self.buffer:is_visible() end From 1df653c393602e043faa2b7774938983993e8bdf Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 11:31:17 +0200 Subject: [PATCH 361/437] update docs: editor buffer filetypes --- doc/neogit.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index ed33e8c11..5d1cb0276 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -2086,6 +2086,8 @@ Untracked Files *neogit_status_buffer_untracked* ============================================================================== Editor Buffer *neogit_editor_buffer* +User customizations can be made via `gitcommit` ftplugin. + Commands: *neogit_editor_commands* • Submit *neogit_editor_submit* Default key: `` @@ -2143,6 +2145,8 @@ Refs Buffer *neogit_refs_buffer* ============================================================================== Rebase Todo Buffer *neogit_rebase_todo_buffer* +User customizations can be made via `gitrebase` ftplugin. + The Rebase editor has some extra commands, beyond being a normal vim buffer. The following keys, in normal mode, will act on the commit under the cursor: From dbbbfe68b354473c44dc94b4b20a4af51428816e Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 12:19:26 +0200 Subject: [PATCH 362/437] Notify user when checking out a local branch that tracks an upstream branch fails. --- lua/neogit/lib/git/branch.lua | 3 ++- lua/neogit/popups/branch/actions.lua | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 5542cea55..7f1470abb 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -90,8 +90,9 @@ end ---@param name string ---@param args? string[] +---@return ProcessResult function M.track(name, args) - git.cli.checkout.track(name).arg_list(args or {}).call { await = true } + return git.cli.checkout.track(name).arg_list(args or {}).call { await = true } end ---@param include_current? boolean diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 125be20d8..05c177017 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -189,7 +189,12 @@ function M.checkout_local_branch(popup) if target then if vim.tbl_contains(remote_branches, target) then - git.branch.track(target, popup:get_arguments()) + local result = git.branch.track(target, popup:get_arguments()) + if result.code > 0 then + notification.error(table.concat(result.stderr, "\n")) + return + end + notification.info("Created local branch " .. target .. " tracking remote") event.send("BranchCheckout", { branch_name = target }) elseif not vim.tbl_contains(options, target) then From 63f95f204fafcab0252d789902fc360b17e15ab0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 12:43:34 +0200 Subject: [PATCH 363/437] Fix when trying to use "diff this" from status buffer. Stash was being passed as a SHA instead of stash{n} format, leading to a nil concat error. Simplify by just merging the stash case with the commit case, since both the stash format and sha format are valid. --- lua/neogit/buffers/stash_list_view/init.lua | 4 ++-- lua/neogit/integrations/diffview.lua | 21 ++++++--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/lua/neogit/buffers/stash_list_view/init.lua b/lua/neogit/buffers/stash_list_view/init.lua index fed4d67e7..95eb6b484 100644 --- a/lua/neogit/buffers/stash_list_view/init.lua +++ b/lua/neogit/buffers/stash_list_view/init.lua @@ -97,7 +97,7 @@ function M:open() [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) local items = self.buffer.ui:get_commits_in_selection() p { - section = { name = "log" }, + section = { name = "stashes" }, item = { name = items }, } end), @@ -166,7 +166,7 @@ function M:open() [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) local item = self.buffer.ui:get_commit_under_cursor() p { - section = { name = "log" }, + section = { name = "stashes" }, item = { name = item }, } end), diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index c7c3cfe67..13fbe2db3 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -112,29 +112,20 @@ function M.open(section_name, item_name, opts) local view -- selene: allow(if_same_then_else) if - section_name == "recent" - or section_name == "log" - or (section_name and section_name:match("unmerged$")) + (section_name == "recent" or section_name == "log" or (section_name and section_name:match("unmerged$"))) + and item_name then local range if type(item_name) == "table" then range = string.format("%s..%s", item_name[1], item_name[#item_name]) - elseif item_name ~= nil then - range = string.format("%s^!", item_name:match("[a-f0-9]+")) else - return + range = string.format("%s^!", item_name:match("[a-f0-9]+")) end view = dv_lib.diffview_open(dv_utils.tbl_pack(range)) - elseif section_name == "range" then - local range = item_name - view = dv_lib.diffview_open(dv_utils.tbl_pack(range)) - elseif section_name == "stashes" then - assert(item_name and type(item_name) == "string", "No item name for stash!") - - local stash_id = item_name:match("stash@{%d+}") - view = dv_lib.diffview_open(dv_utils.tbl_pack(stash_id .. "^!")) - elseif section_name == "commit" then + elseif section_name == "range" and item_name then + view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name)) + elseif (section_name == "stashes" or section_name == "commit") and item_name then view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) elseif section_name == "conflict" and item_name then view = dv_lib.diffview_open(dv_utils.tbl_pack("--selected-file=" .. item_name)) From d0c773dcd08b9f7ed87bf9330e773e5eb2d3b42a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 13:52:54 +0200 Subject: [PATCH 364/437] Migrate remaining events to use event lib --- lua/neogit/buffers/refs_view/init.lua | 3 ++- lua/neogit/buffers/status/init.lua | 5 ++--- lua/neogit/popups/fetch/actions.lua | 7 ++----- lua/neogit/popups/pull/actions.lua | 3 ++- lua/neogit/popups/push/actions.lua | 3 ++- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index bb8f70062..cf812c0a1 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -9,6 +9,7 @@ local Watcher = require("neogit.watcher") local logger = require("neogit.logger") local a = require("plenary.async") local git = require("neogit.lib.git") +local event = require("neogit.lib.event") ---@class RefsViewBuffer ---@field buffer Buffer @@ -329,7 +330,7 @@ function M:redraw() logger.debug("[REFS] Beginning redraw") self.buffer.ui:render(unpack(ui.RefsView(git.refs.list_parsed(), self.head))) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitRefsRefreshed", modeline = false }) + event.send("RefsRefreshed") logger.info("[REFS] Redraw complete") end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index c06f64e98..2b0b384bf 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -6,8 +6,7 @@ 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 api = vim.api +local event = require("neogit.lib.event") ---@class Semaphore ---@field permits number @@ -276,7 +275,7 @@ function M:refresh(partial, reason) partial = partial, callback = function() self:redraw(cursor, view) - api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) + event.send("StatusRefreshed") logger.info("[STATUS] Refresh complete") end, } diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index 8c3e25805..d9707895c 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -5,6 +5,7 @@ local git = require("neogit.lib.git") local logger = require("neogit.logger") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") +local event = require("neogit.lib.event") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -20,11 +21,7 @@ local function fetch_from(name, remote, branch, args) a.util.scheduler() notification.info("Fetched from " .. name, { dismiss = true }) logger.debug("Fetched from " .. name) - vim.api.nvim_exec_autocmds("User", { - pattern = "NeogitFetchComplete", - modeline = false, - data = { remote = remote, branch = branch }, - }) + event.send("FetchComplete", { remote = remote, branch = branch }) else logger.error("Failed to fetch from " .. name) end diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index 95e7aa51d..5fe6a81a9 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -2,6 +2,7 @@ local a = require("plenary.async") local git = require("neogit.lib.git") local logger = require("neogit.logger") local notification = require("neogit.lib.notification") +local event = require("neogit.lib.event") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -29,7 +30,7 @@ local function pull_from(args, remote, branch, opts) a.util.scheduler() notification.info("Pulled from " .. name, { dismiss = true }) logger.debug("Pulled from " .. name) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitPullComplete", modeline = false }) + event.send("PullComplete") else logger.error("Failed to pull from " .. name) notification.error("Failed to pull from " .. name, { dismiss = true }) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 4af6216e0..e0d5e8860 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -5,6 +5,7 @@ local notification = require("neogit.lib.notification") local input = require("neogit.lib.input") local util = require("neogit.lib.util") local config = require("neogit.config") +local event = require("neogit.lib.event") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -57,7 +58,7 @@ local function push_to(args, remote, branch, opts) a.util.scheduler() logger.debug("Pushed to " .. name) notification.info("Pushed to " .. name, { dismiss = true }) - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitPushComplete", modeline = false }) + event.send("PushComplete") else logger.debug("Failed to push to " .. name) notification.error("Failed to push to " .. name, { dismiss = true }) From 07ab294c1379ab71f8dd7bec966ff1a352746554 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 14:00:31 +0200 Subject: [PATCH 365/437] Fix types --- lua/neogit/lib/git/branch.lua | 4 ++-- lua/neogit/lib/git/push.lua | 4 ++-- lua/neogit/popups/push/actions.lua | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 7f1470abb..a8d65a235 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -150,11 +150,11 @@ end ---Determine if a branch name ("origin/master", "fix/bug-1000", etc) ---is a remote branch or a local branch ---@param ref string ----@return nil|string remote +---@return string remote ---@return string branch function M.parse_remote_branch(ref) if M.exists(ref) then - return nil, ref + return ".", ref end return ref:match("^([^/]*)/(.*)$") diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index 6b2137701..2041add09 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -5,8 +5,8 @@ local util = require("neogit.lib.util") local M = {} ---Pushes to the remote and handles password questions ----@param remote string ----@param branch string +---@param remote string? +---@param branch string? ---@param args string[] ---@return ProcessResult function M.push_interactive(remote, branch, args) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index e0d5e8860..53fb28dd8 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -11,6 +11,10 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} +---@param args string[] +---@param remote string +---@param branch string|nil +---@param opts table|nil local function push_to(args, remote, branch, opts) opts = opts or {} From 5c98ee16c1f9841d6d963576ddb9838f1886a7e2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 14:00:49 +0200 Subject: [PATCH 366/437] Simplify how we get branches/heads in push_other action --- lua/neogit/popups/push/actions.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 53fb28dd8..77723731f 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -112,14 +112,7 @@ function M.to_elsewhere(popup) end function M.push_other(popup) - local sources = git.branch.get_local_branches() - table.insert(sources, "HEAD") - table.insert(sources, "ORIG_HEAD") - table.insert(sources, "FETCH_HEAD") - if popup.state.env.commit then - table.insert(sources, 1, popup.state.env.commit) - end - + local sources = util.merge({ popup.state.env.commit }, git.refs.list_local_branches(), git.refs.heads()) local source = FuzzyFinderBuffer.new(sources):open_async { prompt_prefix = "push" } if not source then return From 8345f8c4fee01e518b307b34b9fc18325b6e37d5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 14:03:36 +0200 Subject: [PATCH 367/437] Deduplicate destinations --- lua/neogit/popups/push/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 77723731f..d43b228b2 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -123,7 +123,7 @@ function M.push_other(popup) table.insert(destinations, 1, remote .. "/" .. source) end - local destination = FuzzyFinderBuffer.new(destinations) + local destination = FuzzyFinderBuffer.new(util.deduplicate(destinations)) :open_async { prompt_prefix = "push " .. source .. " to" } if not destination then return From 7fb9188d87c43e33717df5514d43754375fb9f00 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 14:19:51 +0200 Subject: [PATCH 368/437] Prepopulate existing stash message in input when a user renames a stash. --- lua/neogit/lib/git/stash.lua | 3 ++- lua/neogit/popups/stash/actions.lua | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 2335092cf..eb10a94fa 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -71,7 +71,8 @@ function M.list() end function M.rename(stash) - local message = input.get_user_input("New name") + local current = git.log.message(stash) + local message = input.get_user_input("rename", { prepend = current }) if message then local oid = git.rev_parse.abbreviate_commit(stash) git.cli.stash.drop.args(stash).call() diff --git a/lua/neogit/popups/stash/actions.lua b/lua/neogit/popups/stash/actions.lua index c47adbfe3..288d82e6e 100644 --- a/lua/neogit/popups/stash/actions.lua +++ b/lua/neogit/popups/stash/actions.lua @@ -27,6 +27,9 @@ function M.push(popup) git.stash.push(popup:get_arguments(), files) end +---@param action string +---@param stash { name: string } +---@param opts { confirm: boolean }|nil local function use(action, stash, opts) opts = opts or {} local name, get_permission From a6683afec56e323734f0785d0c2510cddf81ddc8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 15:43:40 +0200 Subject: [PATCH 369/437] formatting --- lua/neogit/buffers/status/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 918fb356b..d902de1f0 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -128,7 +128,7 @@ function M:open(kind) [popups.mapping_for("HelpPopup")] = self:_action("v_help_popup"), [popups.mapping_for("IgnorePopup")] = self:_action("v_ignore_popup"), [popups.mapping_for("LogPopup")] = self:_action("v_log_popup"), - [popups.mapping_for("MarginPopup")] = self:_action("v_margin_popup"), + [popups.mapping_for("MarginPopup")] = self:_action("v_margin_popup"), [popups.mapping_for("MergePopup")] = self:_action("v_merge_popup"), [popups.mapping_for("PullPopup")] = self:_action("v_pull_popup"), [popups.mapping_for("PushPopup")] = self:_action("v_push_popup"), @@ -183,7 +183,7 @@ function M:open(kind) [popups.mapping_for("HelpPopup")] = self:_action("n_help_popup"), [popups.mapping_for("IgnorePopup")] = self:_action("n_ignore_popup"), [popups.mapping_for("LogPopup")] = self:_action("n_log_popup"), - [popups.mapping_for("MarginPopup")] = self:_action("n_margin_popup"), + [popups.mapping_for("MarginPopup")] = self:_action("n_margin_popup"), [popups.mapping_for("MergePopup")] = self:_action("n_merge_popup"), [popups.mapping_for("PullPopup")] = self:_action("n_pull_popup"), [popups.mapping_for("PushPopup")] = self:_action("n_push_popup"), From 7e7738898aba5d942784edab12c7ed476c4412be Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 15:43:49 +0200 Subject: [PATCH 370/437] Allow actions to suppress closing their popup. --- lua/neogit/lib/popup/init.lua | 7 +++++-- lua/neogit/popups/margin/actions.lua | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index e88bb6dd3..ac4952742 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -371,8 +371,11 @@ function M:mappings() for _, key in ipairs(action.keys) do mappings.n[key] = a.void(function() logger.debug(string.format("[POPUP]: Invoking action %q of %s", key, self.state.name)) - self:close() - action.callback(self) + local persist = action.callback(self) + if not persist then + self:close() + end + Watcher.instance():dispatch_refresh() end) end diff --git a/lua/neogit/popups/margin/actions.lua b/lua/neogit/popups/margin/actions.lua index 5c4cba642..0acd7eb64 100644 --- a/lua/neogit/popups/margin/actions.lua +++ b/lua/neogit/popups/margin/actions.lua @@ -6,6 +6,8 @@ function M.toggle_visibility() local visibility = state.get({ "margin", "visibility" }, false) local new_visibility = not visibility state.set({ "margin", "visibility" }, new_visibility) + + return true end function M.cycle_date_style() @@ -14,12 +16,16 @@ function M.cycle_date_style() local next_index = (current_index % #styles) + 1 -- wrap around to the first style state.set({ "margin", "date_style" }, next_index) + + return true end function M.toggle_details() local details = state.get({ "margin", "details" }, false) local new_details = not details state.set({ "margin", "details" }, new_details) + + return true end return M From f38a8dd9eb6f7ceaefe5be2d579ed446bb541cdb Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 11 Jun 2025 15:57:53 +0200 Subject: [PATCH 371/437] Formatting --- lua/neogit/buffers/status/ui.lua | 116 +++++++++++++++---------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index f693ec844..d942b2cc2 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -4,6 +4,7 @@ local util = require("neogit.lib.util") local common = require("neogit.buffers.common") local config = require("neogit.config") local a = require("plenary.async") +local state = require("neogit.lib.state") local col = Ui.col local row = Ui.row @@ -368,69 +369,66 @@ local SectionItemCommit = Component.new(function(item) item.commit.rel_date = " " .. item.commit.rel_date end - -- Render author and date in margin - local state = require("neogit.lib.state") - local visibility = state.get({ "margin", "visibility" }, false) - local margin_date_style = state.get({ "margin", "date_style" }, 1) - local details = state.get({ "margin", "details" }, false) - local date - local author_table = { "" } - local date_table - local date_width = 10 - local clamp_width = 30 -- to avoid having too much space when relative date is short - - if margin_date_style == 1 then -- relative date (short) - local unpacked = vim.split(item.commit.rel_date, " ") - -- above, we added a space if the rel_date started with a single number - -- we get the last two elements to deal with that - local date_number = unpacked[#unpacked - 1] - local date_quantifier = unpacked[#unpacked] - if date_quantifier:match("months?") then - date_quantifier = date_quantifier:gsub("m", "M") -- to distinguish from minutes - end - -- add back the space if we have a single number - local left_pad - if #unpacked > 2 then - left_pad = " " - else - left_pad = "" - end - date = left_pad .. date_number .. date_quantifier:sub(1, 1) - date_width = 3 - clamp_width = 23 - elseif margin_date_style == 2 then -- relative date (long) - date = item.commit.rel_date - date_width = 10 - else -- local iso date - if config.values.log_date_format == nil then - -- we get the unix date to be able to convert the date to - -- the local timezone - date = os.date("%Y-%m-%d %H:%M", item.commit.unix_date) - date_width = 16 -- TODO: what should the width be here? - else - date = item.commit.log_date - date_width = 16 - end - end + local virtual_text - date_table = { util.str_min_width(date, date_width), "Special" } + -- Render author and date in margin, if visible + if state.get({ "margin", "visibility" }, false) then + local margin_date_style = state.get({ "margin", "date_style" }, 1) + local details = state.get({ "margin", "details" }, false) - if details then - author_table = { - util.str_clamp(item.commit.author_name, clamp_width - (#date > date_width and #date or date_width)), - "NeogitGraphAuthor", - } - end + local date + local date_width = 10 + local clamp_width = 30 -- to avoid having too much space when relative date is short - local virt - if visibility then - virt = { - { " ", "Constant" }, + if margin_date_style == 1 then -- relative date (short) + local unpacked = vim.split(item.commit.rel_date, " ") + + -- above, we added a space if the rel_date started with a single number + -- we get the last two elements to deal with that + local date_number = unpacked[#unpacked - 1] + local date_quantifier = unpacked[#unpacked] + if date_quantifier:match("months?") then + date_quantifier = date_quantifier:gsub("m", "M") -- to distinguish from minutes + end + + -- add back the space if we have a single number + local left_pad + if #unpacked > 2 then + left_pad = " " + else + left_pad = "" + end + + date = left_pad .. date_number .. date_quantifier:sub(1, 1) + date_width = 3 + clamp_width = 23 + elseif margin_date_style == 2 then -- relative date (long) + date = item.commit.rel_date + date_width = 10 + else -- local iso date + if config.values.log_date_format == nil then + -- we get the unix date to be able to convert the date to the local timezone + date = os.date("%Y-%m-%d %H:%M", item.commit.unix_date) + date_width = 16 -- TODO: what should the width be here? + else + date = item.commit.log_date + date_width = 16 + end + end + + local author_table = { "" } + if details then + author_table = { + util.str_clamp(item.commit.author_name, clamp_width - (#date > date_width and #date or date_width)), + "NeogitGraphAuthor", + } + end + + virtual_text = { + { " ", "Constant" }, author_table, - date_table, + { util.str_min_width(date, date_width), "Special" } } - else - virt = {} end return row( @@ -442,7 +440,7 @@ local SectionItemCommit = Component.new(function(item) { text(item.commit.subject) } ), { - virtual_text = virt, + virtual_text = virtual_text, oid = item.commit.oid, yankable = item.commit.oid, item = item, From b81fbe553d47dc530d2bf9c2e143cf0ab877e93b Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Jun 2025 09:37:30 +0200 Subject: [PATCH 372/437] lint --- lua/neogit/buffers/status/ui.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index d942b2cc2..5821f126d 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -378,7 +378,7 @@ local SectionItemCommit = Component.new(function(item) local date local date_width = 10 - local clamp_width = 30 -- to avoid having too much space when relative date is short + local clamp_width = 30 -- to avoid having too much space when relative date is short if margin_date_style == 1 then -- relative date (short) local unpacked = vim.split(item.commit.rel_date, " ") @@ -425,9 +425,9 @@ local SectionItemCommit = Component.new(function(item) end virtual_text = { - { " ", "Constant" }, + { " ", "Constant" }, author_table, - { util.str_min_width(date, date_width), "Special" } + { util.str_min_width(date, date_width), "Special" }, } end From 065301bd111b5a813b5692a5be78e8234af026de Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Jun 2025 10:16:57 +0200 Subject: [PATCH 373/437] Change how we persist popup to not break existing popups. Long running actions need to have the popup closed prior to being invoked, like pushing/fetching. --- lua/neogit/lib/popup/builder.lua | 15 ++++++++++++--- lua/neogit/lib/popup/init.lua | 5 +++-- lua/neogit/popups/margin/actions.lua | 6 ------ lua/neogit/popups/margin/init.lua | 8 ++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index cd2920425..910cd8370 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -80,6 +80,10 @@ local M = {} ---@field description string ---@field callback function ---@field heading string? +---@field persist_popup boolean? set to true to prevent closing the popup when invoking + +---@class PopupActionOptions +---@field persist_popup boolean Controls if the action should close the popup (false/nil) or keep it open (true) ---@class PopupSwitchOpts ---@field enabled? boolean Controls if the switch should default to 'on' state @@ -429,8 +433,11 @@ end ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ---@param callback? fun(popup: PopupData) Function that gets run in async context +---@param opts? PopupActionOptions ---@return self -function M:action(keys, description, callback) +function M:action(keys, description, callback, opts) + opts = opts or {} + if type(keys) == "string" then keys = { keys } end @@ -448,6 +455,7 @@ function M:action(keys, description, callback) keys = keys, description = description, callback = callback, + persist_popup = opts.persist_popup or false }) return self @@ -459,10 +467,11 @@ end ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ---@param callback? fun(popup: PopupData) Function that gets run in async context +---@param opts? PopupActionOptions ---@return self -function M:action_if(cond, keys, description, callback) +function M:action_if(cond, keys, description, callback, opts) if cond then - return self:action(keys, description, callback) + return self:action(keys, description, callback, opts) end return self diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index ac4952742..f4a39e0c2 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -371,11 +371,12 @@ function M:mappings() for _, key in ipairs(action.keys) do mappings.n[key] = a.void(function() logger.debug(string.format("[POPUP]: Invoking action %q of %s", key, self.state.name)) - local persist = action.callback(self) - if not persist then + if not action.persist_popup then + logger.debug("[POPUP]: Closing popup") self:close() end + action.callback(self) Watcher.instance():dispatch_refresh() end) end diff --git a/lua/neogit/popups/margin/actions.lua b/lua/neogit/popups/margin/actions.lua index 0acd7eb64..5c4cba642 100644 --- a/lua/neogit/popups/margin/actions.lua +++ b/lua/neogit/popups/margin/actions.lua @@ -6,8 +6,6 @@ function M.toggle_visibility() local visibility = state.get({ "margin", "visibility" }, false) local new_visibility = not visibility state.set({ "margin", "visibility" }, new_visibility) - - return true end function M.cycle_date_style() @@ -16,16 +14,12 @@ function M.cycle_date_style() local next_index = (current_index % #styles) + 1 -- wrap around to the first style state.set({ "margin", "date_style" }, next_index) - - return true end function M.toggle_details() local details = state.get({ "margin", "details" }, false) local new_details = not details state.set({ "margin", "details" }, new_details) - - return true end return M diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index ae1e86645..c4e92948d 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -35,10 +35,10 @@ function M.create() :group_heading("Refresh") :action("g", "buffer", actions.log_current) :new_action_group("Margin") - :action("L", "toggle visibility", actions.toggle_visibility) - :action("l", "cycle style", actions.cycle_date_style) - :action("d", "toggle details", actions.toggle_details) - :action("x", "toggle shortstat", actions.log_current) + :action("L", "toggle visibility", actions.toggle_visibility, { persist_popup = true }) + :action("l", "cycle style", actions.cycle_date_style, { persist_popup = true }) + :action("d", "toggle details", actions.toggle_details, { persist_popup = true }) + :action("x", "toggle shortstat", actions.log_current, { persist_popup = true }) :build() p:show() From 442b1fe43a148a8398a35109ca5f89b0f20f2fa6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Jun 2025 10:17:39 +0200 Subject: [PATCH 374/437] Implement refreshing of buffer --- lua/neogit/buffers/status/actions.lua | 16 ++++++++++------ lua/neogit/popups/margin/actions.lua | 7 +++++++ lua/neogit/popups/margin/init.lua | 10 ++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index ef21db8cb..a6c8871d5 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -445,9 +445,11 @@ M.v_log_popup = function(_self) return popups.open("log") end ----@param _self StatusBuffer -M.v_margin_popup = function(_self) - return popups.open("margin") +---@param self StatusBuffer +M.v_margin_popup = function(self) + return popups.open("margin", function(p) + p { buffer = self } + end) end ---@param _self StatusBuffer @@ -1413,9 +1415,11 @@ M.n_log_popup = function(_self) return popups.open("log") end ----@param _self StatusBuffer -M.n_margin_popup = function(_self) - return popups.open("margin") +---@param self StatusBuffer +M.n_margin_popup = function(self) + return popups.open("margin", function(p) + p { buffer = self } + end) end ---@param _self StatusBuffer diff --git a/lua/neogit/popups/margin/actions.lua b/lua/neogit/popups/margin/actions.lua index 5c4cba642..8ab733736 100644 --- a/lua/neogit/popups/margin/actions.lua +++ b/lua/neogit/popups/margin/actions.lua @@ -1,6 +1,13 @@ local M = {} local state = require("neogit.lib.state") +local a = require("plenary.async") + +function M.refresh_buffer(buffer) + return a.void(function() + buffer:dispatch_refresh({ update_diffs = { "*:*" } }, "margin_refresh_buffer") + end) +end function M.toggle_visibility() local visibility = state.get({ "margin", "visibility" }, false) diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index c4e92948d..e7d5429dc 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -4,7 +4,7 @@ local actions = require("neogit.popups.margin.actions") local M = {} -function M.create() +function M.create(env) local p = popup .builder() :name("NeogitMarginPopup") @@ -33,7 +33,13 @@ function M.create() ) :switch("d", "decorate", "Show refnames", { enabled = true, internal = true }) :group_heading("Refresh") - :action("g", "buffer", actions.log_current) + :action_if( + env.buffer, + "g", + "buffer", + actions.refresh_buffer(env.buffer), + { persist_popup = true } + ) :new_action_group("Margin") :action("L", "toggle visibility", actions.toggle_visibility, { persist_popup = true }) :action("l", "cycle style", actions.cycle_date_style, { persist_popup = true }) From 41cbd258a724f9ea95705d9aab6c7ef39c8a1e6f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Jun 2025 10:23:50 +0200 Subject: [PATCH 375/437] Modify rel_date so we don't mutate original object --- lua/neogit/buffers/status/ui.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 5821f126d..0d07d2b2c 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -361,14 +361,6 @@ local SectionItemCommit = Component.new(function(item) end end - -- Render date - if item.commit.rel_date:match(" years?,") then - item.commit.rel_date, _ = item.commit.rel_date:gsub(" years?,", "y") - item.commit.rel_date = item.commit.rel_date .. " " - elseif item.commit.rel_date:match("^%d ") then - item.commit.rel_date = " " .. item.commit.rel_date - end - local virtual_text -- Render author and date in margin, if visible @@ -377,11 +369,22 @@ local SectionItemCommit = Component.new(function(item) local details = state.get({ "margin", "details" }, false) local date + local rel_date local date_width = 10 local clamp_width = 30 -- to avoid having too much space when relative date is short + -- Render date + if item.commit.rel_date:match(" years?,") then + rel_date, _ = item.commit.rel_date:gsub(" years?,", "y") + rel_date = rel_date .. " " + elseif item.commit.rel_date:match("^%d ") then + rel_date = " " .. item.commit.rel_date + else + rel_date = item.commit.rel_date + end + if margin_date_style == 1 then -- relative date (short) - local unpacked = vim.split(item.commit.rel_date, " ") + local unpacked = vim.split(rel_date, " ") -- above, we added a space if the rel_date started with a single number -- we get the last two elements to deal with that @@ -403,7 +406,7 @@ local SectionItemCommit = Component.new(function(item) date_width = 3 clamp_width = 23 elseif margin_date_style == 2 then -- relative date (long) - date = item.commit.rel_date + date = rel_date date_width = 10 else -- local iso date if config.values.log_date_format == nil then From b3d69c2f5b729cc3574691864c503da6b66e3d79 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 12 Jun 2025 20:45:38 +0200 Subject: [PATCH 376/437] Allow keys that would act on folds (za, zc, zo) to work with lazy loaded folds. --- README.md | 2 ++ lua/neogit/buffers/status/actions.lua | 19 +++++++++++++++++++ lua/neogit/buffers/status/init.lua | 1 + lua/neogit/config.lua | 4 +++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 511694383..ecf42db47 100644 --- a/README.md +++ b/README.md @@ -391,6 +391,8 @@ neogit.setup { ["4"] = "Depth4", ["Q"] = "Command", [""] = "Toggle", + ["za"] = "Toggle", + ["zo"] = "OpenFold", ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index afb760e25..8f211337a 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -507,6 +507,25 @@ M.n_toggle = function(self) end end +---@param self StatusBuffer +M.n_open_fold = function(self) + return function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + if fold.options.on_open then + fold.options.on_open(fold, self.buffer.ui) + else + local start, _ = fold:row_range_abs() + local ok, _ = pcall(vim.cmd, "normal! zo") + if ok then + self.buffer:move_cursor(start) + fold.options.folded = false + end + end + end + end +end + ---@param self StatusBuffer M.n_close = function(self) return require("neogit.lib.ui.helpers").close_topmost(self) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2b0b384bf..b81411142 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -146,6 +146,7 @@ function M:open(kind) [mappings["Untrack"]] = self:_action("n_untrack"), [mappings["Rename"]] = self:_action("n_rename"), [mappings["Toggle"]] = self:_action("n_toggle"), + [mappings["OpenFold"]] = self:_action("n_open_fold"), [mappings["Close"]] = self:_action("n_close"), [mappings["OpenOrScrollDown"]] = self:_action("n_open_or_scroll_down"), [mappings["OpenOrScrollUp"]] = self:_action("n_open_or_scroll_up"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index fd979e712..7c44e25fd 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -61,7 +61,7 @@ end function M.get_reversed_commit_editor_maps_I() return get_reversed_maps("commit_editor_I") end ---- + ---@return table function M.get_reversed_refs_view_maps() return get_reversed_maps("refs_view") @@ -637,6 +637,8 @@ function M.get_default_values() ["4"] = "Depth4", ["Q"] = "Command", [""] = "Toggle", + ["za"] = "Toggle", + ["zo"] = "OpenFold", ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", From d63c8148c6bcf10979b05d79648510c30c37224f Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 13 Jun 2025 09:25:35 +0200 Subject: [PATCH 377/437] setup super diff properly --- spec/spec_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 51205f17a..81928d5d7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,6 +6,8 @@ require "debug" require "active_support/all" require "timeout" +require "super_diff/rspec" +require "super_diff/active_support" ENV["GIT_CONFIG_GLOBAL"] = "" From 4681357d65897f7de2711ab86a1670ecafd22756 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 13 Jun 2025 09:35:59 +0200 Subject: [PATCH 378/437] Add zO, zc, zC mappings. --- lua/neogit/buffers/status/actions.lua | 15 +++++++++++++++ lua/neogit/buffers/status/init.lua | 1 + lua/neogit/config.lua | 2 ++ 3 files changed, 18 insertions(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 8f211337a..119fb2fee 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -526,6 +526,21 @@ M.n_open_fold = function(self) end end +---@param self StatusBuffer +M.n_close_fold = function(self) + return function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + local start, _ = fold:row_range_abs() + local ok, _ = pcall(vim.cmd, "normal! zc") + if ok then + self.buffer:move_cursor(start) + fold.options.folded = true + end + end + end +end + ---@param self StatusBuffer M.n_close = function(self) return require("neogit.lib.ui.helpers").close_topmost(self) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index b81411142..b56db5d73 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -147,6 +147,7 @@ function M:open(kind) [mappings["Rename"]] = self:_action("n_rename"), [mappings["Toggle"]] = self:_action("n_toggle"), [mappings["OpenFold"]] = self:_action("n_open_fold"), + [mappings["CloseFold"]] = self:_action("n_close_fold"), [mappings["Close"]] = self:_action("n_close"), [mappings["OpenOrScrollDown"]] = self:_action("n_open_or_scroll_down"), [mappings["OpenOrScrollUp"]] = self:_action("n_open_or_scroll_up"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 7c44e25fd..8942c4639 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -639,6 +639,8 @@ function M.get_default_values() [""] = "Toggle", ["za"] = "Toggle", ["zo"] = "OpenFold", + ["zC"] = "Depth1", + ["zO"] = "Depth4", ["x"] = "Discard", ["s"] = "Stage", ["S"] = "StageUnstaged", From 1b4f44374f97d9262caa680e41ecbebcaf1553bd Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 13 Jun 2025 11:37:05 +0200 Subject: [PATCH 379/437] Abstract process result success/failures with method --- lua/neogit/client.lua | 2 +- lua/neogit/lib/git/bisect.lua | 4 ++-- lua/neogit/lib/git/branch.lua | 8 ++++---- lua/neogit/lib/git/cherry_pick.lua | 6 +++--- lua/neogit/lib/git/files.lua | 6 +++--- lua/neogit/lib/git/index.lua | 2 +- lua/neogit/lib/git/log.lua | 3 ++- lua/neogit/lib/git/merge.lua | 6 +++--- lua/neogit/lib/git/rebase.lua | 16 ++++++++-------- lua/neogit/lib/git/remote.lua | 12 ++++++------ lua/neogit/lib/git/reset.lua | 16 ++++++++-------- lua/neogit/lib/git/revert.lua | 2 +- lua/neogit/lib/git/stash.lua | 20 ++++++++++---------- lua/neogit/lib/git/tag.lua | 2 +- lua/neogit/lib/git/worktree.lua | 6 +++--- lua/neogit/popups/branch/actions.lua | 10 +++++----- lua/neogit/popups/fetch/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 2 +- lua/neogit/popups/push/actions.lua | 4 ++-- lua/neogit/process.lua | 10 ++++++++++ 20 files changed, 75 insertions(+), 64 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 37ea05d8c..177bbba0e 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -144,7 +144,7 @@ function M.wrap(cmd, opts) a.util.scheduler() logger.debug("[CLIENT] DONE editor command") - if result.code == 0 then + if result:success() then if opts.msg.success then notification.info(opts.msg.success, { dismiss = true }) end diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index cd0c3e130..9aa9f8f50 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -8,7 +8,7 @@ local M = {} local function bisect(cmd) local result = git.cli.bisect.args(cmd).call { long = true } - if result.code == 0 then + if result:success() then event.send("Bisect", { type = cmd }) end end @@ -28,7 +28,7 @@ function M.start(bad_revision, good_revision, args) local result = git.cli.bisect.args("start").arg_list(args).args(bad_revision, good_revision).call { long = true } - if result.code == 0 then + if result:success() then event.send("Bisect", { type = "start" }) end end diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index a8d65a235..007d17ec7 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -144,7 +144,7 @@ function M.exists(branch) .args(string.format("refs/heads/%s", branch)) .call { hidden = true, ignore_error = true } - return result.code == 0 + return result:success() end ---Determine if a branch name ("origin/master", "fix/bug-1000", etc) @@ -164,7 +164,7 @@ end ---@param base_branch? string ---@return boolean function M.create(name, base_branch) - return git.cli.branch.args(name, base_branch).call({ await = true }).code == 0 + return git.cli.branch.args(name, base_branch).call({ await = true }):success() end ---@param name string @@ -182,7 +182,7 @@ function M.delete(name) result = git.cli.branch.delete.name(name).call { await = true } end - return result and result.code == 0 or false + return result and result:success() or false end ---Returns current branch name, or nil if detached HEAD @@ -314,7 +314,7 @@ function M.upstream(name) local result = git.cli["rev-parse"].symbolic_full_name.abbrev_ref(name .. "@{upstream}").call { ignore_error = true } - if result.code == 0 then + if result:success() then return result.stdout[1] end else diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index 82a25ddd3..3d48e307e 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -20,7 +20,7 @@ function M.pick(commits, args) result = cmd.call { await = true } end - if result.code ~= 0 then + if result:failure() then notification.error("Cherry Pick failed. Resolve conflicts before continuing") return false else @@ -37,7 +37,7 @@ function M.apply(commits, args) end) local result = git.cli["cherry-pick"].no_commit.arg_list(util.merge(args, commits)).call { await = true } - if result.code ~= 0 then + if result:failure() then notification.error("Cherry Pick failed. Resolve conflicts before continuing") else event.send("CherryPick", { commits = commits }) @@ -91,7 +91,7 @@ function M.move(commits, src, dst, args, start, checkout_dst) local result = git.cli.rebase.interactive.args(keep).in_pty(true).env({ GIT_SEQUENCE_EDITOR = editor }).call() - if result.code ~= 0 then + if result:failure() then return notification.error("Picking failed - Fix things manually before continuing.") end diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index e053573ab..af652f7d9 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -55,20 +55,20 @@ end ---@param path string ---@return boolean function M.is_tracked(path) - return git.cli["ls-files"].error_unmatch.files(path).call({ hidden = true, ignore_error = true }).code == 0 + return git.cli["ls-files"].error_unmatch.files(path).call({ hidden = true, ignore_error = true }):success() end ---@param paths string[] ---@return boolean function M.untrack(paths) - return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).code == 0 + return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }):success() end ---@param from string ---@param to string ---@return boolean function M.move(from, to) - return git.cli.mv.args(from, to).call().code == 0 + return git.cli.mv.args(from, to).call():success() end return M diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 158e52500..354eceee1 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -160,7 +160,7 @@ function M.create_backup() git.cli.add.update.call { hidden = true, await = true } local result = git.cli.commit.allow_empty.message("Hard reset backup").call { hidden = true, await = true, pty = true } - if result.code == 0 then + if result:success() then git.cli["update-ref"].args("refs/backups/" .. timestamp(), "HEAD").call { hidden = true, await = true } git.cli.reset.hard.args("HEAD~1").call { hidden = true, await = true } end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index e1b53b4ea..9e15a486e 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -403,7 +403,8 @@ end) function M.is_ancestor(ancestor, descendant) return git.cli["merge-base"].is_ancestor .args(ancestor, descendant) - .call({ ignore_error = true, hidden = true }).code == 0 + .call({ ignore_error = true, hidden = true }) + :success() end ---Finds parent commit of a commit. If no parent exists, will return nil diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 97cfcb0af..b018bfde1 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -12,7 +12,7 @@ end function M.merge(branch, args) local result = merge_command(git.cli.merge.args(branch).arg_list(args)) - if result.code ~= 0 then + if result:failure() then notification.error("Merging failed. Resolve conflicts before continuing") event.send("Merge", { branch = branch, args = args, status = "conflict" }) else @@ -37,12 +37,12 @@ end ---@param path string filepath to check for conflict markers ---@return boolean function M.is_conflicted(path) - return git.cli.diff.check.files(path).call().code ~= 0 + return git.cli.diff.check.files(path).call():failure() end ---@return boolean function M.any_conflicted() - return git.cli.diff.check.call().code ~= 0 + return git.cli.diff.check.call():failure() end ---@class MergeItem diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 4b0757823..b5890e7b6 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -22,7 +22,7 @@ function M.instantly(commit, args) .arg_list(args or {}) .call { long = true, pty = true } - if result.code ~= 0 then + if result:failure() then event.send("Rebase", { commit = commit, status = "failed" }) else event.send("Rebase", { commit = commit, status = "ok" }) @@ -37,7 +37,7 @@ function M.rebase_interactive(commit, args) end local result = rebase_command(git.cli.rebase.interactive.arg_list(args).args(commit)) - if result.code ~= 0 then + if result:failure() then if result.stdout[1]:match("^hint: Waiting for your editor to close the file%.%.%. error") then notification.info("Rebase aborted") event.send("Rebase", { commit = commit, status = "aborted" }) @@ -53,7 +53,7 @@ end function M.onto_branch(branch, args) local result = rebase_command(git.cli.rebase.args(branch).arg_list(args)) - if result.code ~= 0 then + if result:failure() then notification.error("Rebasing failed. Resolve conflicts before continuing") event.send("Rebase", { commit = branch, status = "conflict" }) else @@ -64,7 +64,7 @@ end function M.onto(start, newbase, args) local result = rebase_command(git.cli.rebase.onto.args(newbase, start).arg_list(args)) - if result.code ~= 0 then + if result:failure() then notification.error("Rebasing failed. Resolve conflicts before continuing") event.send("Rebase", { status = "conflict" }) else @@ -101,7 +101,7 @@ function M.modify(commit) .env({ GIT_SEQUENCE_EDITOR = editor }) .call() - if result.code == 0 then + if result:success() then event.send("Rebase", { commit = commit, status = "ok" }) end end @@ -115,7 +115,7 @@ function M.drop(commit) .env({ GIT_SEQUENCE_EDITOR = editor }) .call() - if result.code == 0 then + if result:success() then event.send("Rebase", { commit = commit, status = "ok" }) end end @@ -141,7 +141,7 @@ end function M.merge_base_HEAD() local result = git.cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } - if result.code == 0 then + if result:success() then return result.stdout[1] end end @@ -168,7 +168,7 @@ local function rev_name(oid) .args(oid) .call { hidden = true, ignore_error = true } - if result.code == 0 then + if result:success() then return result.stdout[1] else return oid diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index 0627b4d73..31bc633d1 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -28,7 +28,7 @@ end ---@param args string[] ---@return boolean function M.add(name, url, args) - return git.cli.remote.add.arg_list(args).args(name, url).call().code == 0 + return git.cli.remote.add.arg_list(args).args(name, url).call():success() end ---@param from string @@ -36,28 +36,28 @@ end ---@return boolean function M.rename(from, to) local result = git.cli.remote.rename.arg_list({ from, to }).call() - if result.code == 0 then + if result:success() then cleanup_push_variables(from, to) end - return result.code == 0 + return result:success() end ---@param name string ---@return boolean function M.remove(name) local result = git.cli.remote.rm.args(name).call() - if result.code == 0 then + if result:success() then cleanup_push_variables(name) end - return result.code == 0 + return result:success() end ---@param name string ---@return boolean function M.prune(name) - return git.cli.remote.prune.args(name).call().code == 0 + return git.cli.remote.prune.args(name).call():success() end ---@return string[] diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index 22dd6a8e8..c5dbd9017 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -7,14 +7,14 @@ local M = {} ---@return boolean function M.mixed(target) local result = git.cli.reset.mixed.args(target).call() - return result.code == 0 + return result:success() end ---@param target string ---@return boolean function M.soft(target) local result = git.cli.reset.soft.args(target).call() - return result.code == 0 + return result:success() end ---@param target string @@ -23,21 +23,21 @@ function M.hard(target) git.index.create_backup() local result = git.cli.reset.hard.args(target).call() - return result.code == 0 + return result:success() end ---@param target string ---@return boolean function M.keep(target) local result = git.cli.reset.keep.args(target).call() - return result.code == 0 + return result:success() end ---@param target string ---@return boolean function M.index(target) local result = git.cli.reset.args(target).files(".").call() - return result.code == 0 + return result:success() end ---@param target string revision to reset to @@ -46,7 +46,7 @@ function M.worktree(target) local success = false git.index.with_temp_index(target, function(index) local result = git.cli["checkout-index"].all.force.env({ GIT_INDEX_FILE = index }).call() - success = result.code == 0 + success = result:success() end) return success @@ -57,11 +57,11 @@ end ---@return boolean function M.file(target, files) local result = git.cli.checkout.rev(target).files(unpack(files)).call() - if result.code > 0 then + if result:failure() then result = git.cli.reset.args(target).files(unpack(files)).call() end - return result.code == 0 + return result:success() end return M diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index b84ee8921..a6a3e608f 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -9,7 +9,7 @@ local M = {} ---@return boolean, string|nil function M.commits(commits, args) local result = git.cli.revert.no_commit.arg_list(util.merge(args, commits)).call { pty = true } - if result.code == 0 then + if result:success() then return true, "" else return false, result.stdout[1] diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index eb10a94fa..83a622825 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -9,7 +9,7 @@ local M = {} function M.list_refs() local result = git.cli.reflog.show.format("%h").args("stash").call { ignore_error = true } - if result.code > 0 then + if result:failure() then return {} else return result.stdout @@ -19,51 +19,51 @@ end ---@param args string[] function M.stash_all(args) local result = git.cli.stash.push.files(".").arg_list(args).call() - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.stash_index() local result = git.cli.stash.staged.call() - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.stash_keep_index() local result = git.cli.stash.keep_index.files(".").call() - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end ---@param args string[] ---@param files string[] function M.push(args, files) local result = git.cli.stash.push.arg_list(args).files(unpack(files)).call() - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.pop(stash) local result = git.cli.stash.apply.index.args(stash).call() - if result.code == 0 then + if result:success() then git.cli.stash.drop.args(stash).call() else git.cli.stash.apply.args(stash).call() end - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.apply(stash) local result = git.cli.stash.apply.index.args(stash).call() - if result.code ~= 0 then + if result:failure() then git.cli.stash.apply.args(stash).call() end - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.drop(stash) local result = git.cli.stash.drop.args(stash).call() - event.send("Stash", { success = result.code == 0 }) + event.send("Stash", { success = result:success() }) end function M.list() diff --git a/lua/neogit/lib/git/tag.lua b/lua/neogit/lib/git/tag.lua index 2b025a919..0bc1983e9 100644 --- a/lua/neogit/lib/git/tag.lua +++ b/lua/neogit/lib/git/tag.lua @@ -14,7 +14,7 @@ end ---@return boolean Successfully deleted function M.delete(tags) local result = git.cli.tag.delete.arg_list(tags).call { await = true } - return result.code == 0 + return result:success() end --- Show a list of tags under a selected ref diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index b4e96adf1..1095eb3b5 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -11,7 +11,7 @@ local M = {} ---@return boolean, string function M.add(ref, path, params) local result = git.cli.worktree.add.arg_list(params or {}).args(path, ref).call() - if result.code == 0 then + if result:success() then return true, "" else return false, result.stderr[#result.stderr] @@ -24,7 +24,7 @@ end ---@return boolean function M.move(worktree, destination) local result = git.cli.worktree.move.args(worktree, destination).call() - return result.code == 0 + return result:success() end ---Removes a worktree @@ -33,7 +33,7 @@ end ---@return boolean function M.remove(worktree, args) local result = git.cli.worktree.remove.args(worktree).arg_list(args or {}).call { ignore_error = true } - return result.code == 0 + return result:success() end ---@class Worktree diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 05c177017..87bf45721 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -22,7 +22,7 @@ end local function checkout_branch(target, args) local result = git.branch.checkout(target, args) - if result.code > 0 then + if result:failure() then notification.error(table.concat(result.stderr, "\n")) return end @@ -190,7 +190,7 @@ function M.checkout_local_branch(popup) if target then if vim.tbl_contains(remote_branches, target) then local result = git.branch.track(target, popup:get_arguments()) - if result.code > 0 then + if result:failure() then notification.error(table.concat(result.stderr, "\n")) return end @@ -246,7 +246,7 @@ function M.rename_branch() end local result = git.cli.branch.move.args(selected_branch, new_name).call { await = true } - if result.code == 0 then + if result:success() then notification.info(string.format("Renamed '%s' to '%s'", selected_branch, new_name)) event.send("BranchRename", { branch_name = selected_branch, new_name = new_name }) else @@ -292,7 +292,7 @@ function M.reset_branch(popup) -- Reset the current branch to the desired state & update reflog local result = git.cli.reset.hard.args(to).call() - if result.code == 0 then + if result:success() then local current = git.branch.current_full_name() assert(current, "no current branch") git.log.update_ref(current, to) @@ -320,7 +320,7 @@ function M.delete_branch(popup) and branch_name and input.get_permission(("Delete remote branch '%s/%s'?"):format(remote, branch_name)) then - success = git.cli.push.remote(remote).delete.to(branch_name).call().code == 0 + success = git.cli.push.remote(remote).delete.to(branch_name).call():success() elseif not remote and branch_name == git.branch.current() then local choices = { "&detach HEAD and delete", diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index d9707895c..67991cdf6 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -17,7 +17,7 @@ local function fetch_from(name, remote, branch, args) notification.info("Fetching from " .. name) local res = git.fetch.fetch_interactive(remote, branch, args) - if res and res.code == 0 then + if res and res:success() then a.util.scheduler() notification.info("Fetched from " .. name, { dismiss = true }) logger.debug("Fetched from " .. name) diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index 5fe6a81a9..d311d6f0d 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -26,7 +26,7 @@ local function pull_from(args, remote, branch, opts) local res = git.pull.pull_interactive(remote, branch, args) - if res and res.code == 0 then + if res and res:success() then a.util.scheduler() notification.info("Pulled from " .. name, { dismiss = true }) logger.debug("Pulled from " .. name) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index d43b228b2..d7fe25948 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -48,7 +48,7 @@ local function push_to(args, remote, branch, opts) local updates_rejected = string.find(table.concat(res.stdout), "Updates were rejected") ~= nil -- Only ask the user whether to force push if not already specified and feature enabled - if res and res.code ~= 0 and not using_force and updates_rejected and config.values.prompt_force_push then + if res and res:failure() and not using_force and updates_rejected and config.values.prompt_force_push then logger.error("Attempting force push to " .. name) local message = "Your branch has diverged from the remote branch. Do you want to force push?" @@ -58,7 +58,7 @@ local function push_to(args, remote, branch, opts) end end - if res and res.code == 0 then + if res and res:success() then a.util.scheduler() logger.debug("Pushed to " .. name) notification.info("Pushed to " .. name, { dismiss = true }) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index fb0dd2c8a..60e95f56e 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -86,6 +86,16 @@ function ProcessResult:remove_ansi() return self end +---@return boolean +function ProcessResult:success() + return self.code == 0 +end + +---@return boolean +function ProcessResult:failure() + return self.code ~= 0 +end + ProcessResult.__index = ProcessResult ---@param process ProcessOpts From 9be3820c1bdc4aa1ae82bae1336358da433d9bd2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 15 Jun 2025 18:32:51 +0200 Subject: [PATCH 380/437] add basic specs for commit select buffer and stash list buffer --- spec/buffers/commit_select_buffer_spec.rb | 11 ++++++++ spec/buffers/stash_list_buffer_spec.rb | 33 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 spec/buffers/commit_select_buffer_spec.rb create mode 100644 spec/buffers/stash_list_buffer_spec.rb diff --git a/spec/buffers/commit_select_buffer_spec.rb b/spec/buffers/commit_select_buffer_spec.rb new file mode 100644 index 000000000..e613ca868 --- /dev/null +++ b/spec/buffers/commit_select_buffer_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Commit Select Buffer", :git, :nvim do + it "renders, raising no errors" do + nvim.keys("AA") + expect(nvim.errors).to be_empty + expect(nvim.filetype).to eq("NeogitCommitSelectView") + end +end diff --git a/spec/buffers/stash_list_buffer_spec.rb b/spec/buffers/stash_list_buffer_spec.rb new file mode 100644 index 000000000..32c119b6e --- /dev/null +++ b/spec/buffers/stash_list_buffer_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Stash list Buffer", :git, :nvim do + before do + create_file("1") + git.add("1") + git.commit("test") + create_file("1", content: "hello world") + git.lib.stash_save("test") + nvim.refresh + end + + it "renders, raising no errors" do + nvim.keys("Zl") + expect(nvim.screen[1..2]).to eq( + [ + " Stashes (1) ", + "stash@{0} On master: test 0 seconds ago" + ] + ) + + expect(nvim.errors).to be_empty + expect(nvim.filetype).to eq("NeogitStashView") + end + + it "can open CommitView" do + nvim.keys("Zl") + expect(nvim.errors).to be_empty + expect(nvim.filetype).to eq("NeogitCommitView") + end +end From bc511397f9c9574b050ce69f86706f99b2b4c643 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 15 Jun 2025 18:52:13 +0200 Subject: [PATCH 381/437] something --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 07477766c..0fefca5b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -46,11 +46,11 @@ jobs: steps: - uses: actions/checkout@v4 - uses: Homebrew/actions/setup-homebrew@master + - run: brew install lua-language-server - uses: luarocks/gh-actions-lua@v10 with: luaVersion: luajit - uses: luarocks/gh-actions-luarocks@v5 - run: | - HOMEBREW_NO_INSTALL_CLEANUP=1 brew install lua-language-server luarocks install llscheck llscheck lua/ From 43853b2ab6137b2a8e29ec2a902d4a1e2a1b0032 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 18 Jun 2025 15:48:53 +0200 Subject: [PATCH 382/437] Ensure commit view buffer is always at top when using peek up/down/file commands. --- lua/neogit/buffers/commit_view/init.lua | 5 +++++ lua/neogit/buffers/log_view/init.lua | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 2fbfb1ee0..60dbed42f 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -146,11 +146,14 @@ function M:update(commit_id, filter) self.buffer.ui:render( unpack(ui.CommitView(self.commit_info, self.commit_overview, self.commit_signature, self.item_filter)) ) + + self.buffer:win_call(vim.cmd, "normal! gg") end ---Opens the CommitViewBuffer ---If already open will close the buffer ---@param kind? string +---@return CommitViewBuffer function M:open(kind) kind = kind or config.values.commit_view.kind @@ -332,6 +335,8 @@ function M:open(kind) vim.cmd("normal! zR") end, } + + return self end return M diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 45aa1ed18..92978614c 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -188,7 +188,9 @@ function M:open() [status_maps["PeekFile"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then - CommitViewBuffer.new(commit, self.files):open() + local buffer = CommitViewBuffer.new(commit, self.files):open() + buffer.buffer:win_call(vim.cmd, "normal! gg") + self.buffer:focus() end end, From 4b28f0a629d3bd53f4bcdbd8734d9a46cfd5e5da Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 18 Jun 2025 15:50:31 +0200 Subject: [PATCH 383/437] lint --- lua/neogit/runner.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/runner.lua b/lua/neogit/runner.lua index f6cb4366b..99dbe649d 100644 --- a/lua/neogit/runner.lua +++ b/lua/neogit/runner.lua @@ -71,6 +71,7 @@ local function handle_fatal_error(line) notification.error(line) return "__CANCEL__" end + ---@param process Process ---@param line string ---@return boolean From f60c58ff63bfdc54cd291d611d52f7dee608ccb5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 18 Jun 2025 15:51:43 +0200 Subject: [PATCH 384/437] Fix help popup spec --- spec/popups/help_popup_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index f7aca60ab..3b55beb47 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -9,7 +9,7 @@ " Commands Applying changes Essential commands ", " $ History M Remote Stage all Refresh ", " A Cherry Pick m Merge K Untrack Go to file ", - " b Branch P Push s Stage Toggle ", + " b Branch P Push s Stage za, Toggle ", " B Bisect p Pull S Stage unstaged ", " c Commit Q Command u Unstage ", " d Diff r Rebase U Unstage all ", From 60a27f663a6fed91cc76a371b5548e64027768b4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 18 Jun 2025 15:57:30 +0200 Subject: [PATCH 385/437] update luarocks version --- .github/workflows/lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0fefca5b7..05eab19a8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -51,6 +51,8 @@ jobs: with: luaVersion: luajit - uses: luarocks/gh-actions-luarocks@v5 + with: + luaRocksVersion: "3.12.1" - run: | luarocks install llscheck llscheck lua/ From cb20bd24ab42bfb058da2b3312681e9cf96f2ed5 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Mon, 23 Jun 2025 11:36:39 +0200 Subject: [PATCH 386/437] Show a warning notification when commit fails If a commit isn't successful due to `pre-commit` hook failure, nothing gets reported and the user has to guess what happened. Ideally, process output should be shown so the user can see what happened. But a notification will at least show that something went wrong. --- lua/neogit/popups/commit/actions.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 9d28dc13c..252922a3e 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -37,6 +37,7 @@ local function do_commit(popup, cmd) autocmd = "NeogitCommitComplete", msg = { success = "Committed", + fail = "Commit failed", }, interactive = true, show_diff = config.values.commit_editor.show_staged_diff, From fa9ac1703b218074c56aa0bc61fd80c7099a3520 Mon Sep 17 00:00:00 2001 From: rodvicj Date: Sat, 28 Jun 2025 16:16:42 +0800 Subject: [PATCH 387/437] use nvim_buf_add_highlight() (deprecated) for neovim 0.10 and below, use vim.hl.range() for newer versions --- lua/neogit/lib/buffer.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e63ebb707..3aeaa5720 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -457,7 +457,12 @@ end function Buffer:add_highlight(line, col_start, col_end, name, namespace) local ns_id = self:get_namespace_id(namespace) if ns_id then - vim.hl.range(self.handle, ns_id, name, { line, col_start }, { line, col_end }) + local version = vim.version() + if version.major == 0 and version.minor <= 10 then + api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) + else + vim.hl.range(self.handle, ns_id, name, { line, col_start }, { line, col_end }) + end end end From c84b02cacfe004d6515907a3622d49fd9843bbfa Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 24 Jun 2025 14:09:20 +0200 Subject: [PATCH 388/437] make deleteing files more robust - use abs path if available --- lua/neogit/buffers/status/actions.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 119fb2fee..2de180962 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -38,15 +38,16 @@ local function cleanup_items(items) end for _, item in ipairs(items) do - logger.trace("[cleanup_items()] Cleaning " .. vim.inspect(item.name)) - assert(item.name, "cleanup_items() - item must have a name") + local path = item.absolute_path or item.name + logger.debug("[cleanup_items()] Cleaning " .. vim.inspect(path)) + assert(path, "cleanup_items() - item must have a name") - local bufnr = fn.bufnr(item.name) + local bufnr = fn.bufnr(path) if bufnr > 0 then api.nvim_buf_delete(bufnr, { force = false }) end - fn.delete(fn.fnameescape(item.name)) + fn.delete(fn.fnameescape(path)) end end From 0d353cb9181e91e9356c0b4deefd969663f9ee45 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 17 Jul 2025 15:26:41 +0200 Subject: [PATCH 389/437] Fix performance regression: api.nvim_buf_add_highlight() is much faster than vim.hl.range() --- lua/neogit/lib/buffer.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 3aeaa5720..911a670ac 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -457,12 +457,7 @@ end function Buffer:add_highlight(line, col_start, col_end, name, namespace) local ns_id = self:get_namespace_id(namespace) if ns_id then - local version = vim.version() - if version.major == 0 and version.minor <= 10 then - api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) - else - vim.hl.range(self.handle, ns_id, name, { line, col_start }, { line, col_end }) - end + api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) end end From 1b46ec416dcb9efb96a021748c7a195b9f497ff9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 17 Jul 2025 15:27:12 +0200 Subject: [PATCH 390/437] Remove vim.validate() call that uses deprecated arguments --- lua/neogit/lib/ui/init.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index c2dcba05e..f2d6bfb1f 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -781,10 +781,6 @@ Ui.text = Component.new(function(value, options, ...) error("Too many arguments") end - vim.validate { - options = { options, "table", true }, - } - return { tag = "text", value = value or "", From 62136c657e9b0723031c56595394f792fd7be0ab Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 18 Jul 2025 15:51:07 +0200 Subject: [PATCH 391/437] Bugfix: When staging/unstaging a new file with the cursor over the _hunk_, an error was raised. This now stages/unstages the entire file. --- lua/neogit/buffers/status/actions.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 2de180962..6ca26b3ca 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1129,6 +1129,9 @@ M.n_stage = function(self) end return end + elseif selection.item and section.options.section == "untracked" then + git.index.add { selection.item.name } + self:dispatch_refresh({ update_diffs = { "*:" .. selection.item.name } }, "n_stage") elseif stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") @@ -1136,14 +1139,9 @@ M.n_stage = function(self) local patch = git.index.generate_patch(stagable.hunk) git.index.apply(patch, { cached = true }) self:dispatch_refresh({ update_diffs = { "*:" .. item.name } }, "n_stage") - elseif stagable.filename then - if section.options.section == "unstaged" then - git.status.stage { stagable.filename } - self:dispatch_refresh({ update_diffs = { "*:" .. stagable.filename } }, "n_stage") - elseif section.options.section == "untracked" then - git.index.add { stagable.filename } - self:dispatch_refresh({ update_diffs = { "*:" .. stagable.filename } }, "n_stage") - end + elseif stagable.filename and section.options.section == "unstaged" then + git.status.stage { stagable.filename } + self:dispatch_refresh({ update_diffs = { "*:" .. stagable.filename } }, "n_stage") end elseif section then if section.options.section == "untracked" then @@ -1197,6 +1195,7 @@ end M.n_unstage = function(self) return a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local selection = self.buffer.ui:get_selection() local section = self.buffer.ui:get_current_section() if section and section.options.section ~= "staged" then @@ -1204,7 +1203,10 @@ M.n_unstage = function(self) end if unstagable then - if unstagable.hunk then + if selection.item and selection.item.mode == "N" then + git.status.unstage { selection.item.name } + self:dispatch_refresh({ update_diffs = { "*:" .. selection.item.name } }, "n_unstage") + elseif unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") local patch = git.index.generate_patch( From 6f12b8ebaefd032da3231260d0429ce28db399a5 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:34:08 +0800 Subject: [PATCH 392/437] fix: handle when there's only one tabpage --- lua/neogit/lib/buffer.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 911a670ac..562e7e5af 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -226,6 +226,9 @@ function Buffer:close(force) if self.kind == "tab" then local ok, _ = pcall(vim.cmd, "tabclose") + if not ok and #api.nvim_list_tabpages() == 1 then + ok, _ = pcall(vim.cmd, "bd! " .. self.handle) + end if not ok then vim.cmd("tab sb " .. self.handle) vim.cmd("tabclose #") From e96c6a8793b13010b95b42d93435320a866fa9b1 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Thu, 24 Jul 2025 16:02:24 +0200 Subject: [PATCH 393/437] fix: enable `CloseFold` command in status mappings --- lua/neogit/config.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 8942c4639..74d08e4ac 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -639,6 +639,7 @@ function M.get_default_values() [""] = "Toggle", ["za"] = "Toggle", ["zo"] = "OpenFold", + ["zc"] = "CloseFold", ["zC"] = "Depth1", ["zO"] = "Depth4", ["x"] = "Discard", From 0a97695d772797d9d33db4925c00c87ee5c854c6 Mon Sep 17 00:00:00 2001 From: Andrew Chmutov Date: Sat, 26 Jul 2025 16:08:06 +0200 Subject: [PATCH 394/437] fix: disable commit message trimming on `reword` --- lua/neogit/lib/git/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 9e15a486e..88ffd0b0a 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -440,7 +440,7 @@ function M.message(commit) end function M.full_message(commit) - return git.cli.log.max_count(1).format("%B").args(commit).call({ hidden = true }).stdout + return git.cli.log.max_count(1).format("%B").args(commit).call({ hidden = true, trim = false }).stdout end ---@class CommitItem From b8d840ed988ec3751ea2d9c9a66f635c3439564a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 6 Aug 2025 10:45:44 +0200 Subject: [PATCH 395/437] Add missing enum types in config --- lua/neogit/config.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 74d08e4ac..c06a5eac2 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -93,6 +93,7 @@ end ---| "split_below_all" Like :below split ---| "vsplit" Open in a vertical split ---| "floating" Open in a floating window +---| "floating_console" Open in a floating window across the bottom of the screen ---| "auto" vsplit if window would have 80 cols, otherwise split ---@class NeogitCommitBufferConfig Commit buffer options @@ -190,6 +191,9 @@ end ---| "MultiselectTogglePrevious" ---| "InsertCompletion" ---| "NOP" +---| "ScrollWheelDown" +---| "ScrollWheelUp" +---| "MouseClick" ---| false ---@alias NeogitConfigMappingsStatus @@ -197,6 +201,7 @@ end ---| "MoveDown" ---| "MoveUp" ---| "OpenTree" +---| "OpenFold" ---| "Command" ---| "Depth1" ---| "Depth2" @@ -212,6 +217,7 @@ end ---| "Untrack" ---| "RefreshBuffer" ---| "GoToFile" +---| "PeekFile" ---| "VSplitOpen" ---| "SplitOpen" ---| "TabOpen" From 7db83c0c82c659e0db10857ff1290d6879db86f7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 10 Aug 2025 16:35:56 +0200 Subject: [PATCH 396/437] Bugfix: When deleting a branch, don't interprate "." as a remote. --- lua/neogit/popups/branch/actions.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 87bf45721..cc7159122 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -313,15 +313,16 @@ function M.delete_branch(popup) end local remote, branch_name = git.branch.parse_remote_branch(selected_branch) + local is_remote = remote and remote ~= "." local success = false if - remote + is_remote and branch_name and input.get_permission(("Delete remote branch '%s/%s'?"):format(remote, branch_name)) then success = git.cli.push.remote(remote).delete.to(branch_name).call():success() - elseif not remote and branch_name == git.branch.current() then + elseif not is_remote and branch_name == git.branch.current() then local choices = { "&detach HEAD and delete", "&abort", @@ -350,12 +351,12 @@ function M.delete_branch(popup) if not success then -- Reset HEAD if unsuccessful git.cli.checkout.branch(branch_name).call() end - elseif not remote and branch_name then + elseif not is_remote and branch_name then success = git.branch.delete(branch_name) end if success then - if remote then + if is_remote then notification.info(string.format("Deleted remote branch '%s/%s'", remote, branch_name)) else notification.info(string.format("Deleted branch '%s'", branch_name)) From 269a813161b009de19db416ad159891502cf0c85 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 10 Aug 2025 16:46:52 +0200 Subject: [PATCH 397/437] Bugfix: use full strings to prevent accidental namespace re-use. --- lua/neogit/lib/git/refs.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 1125ab46e..b7ae41cba 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -75,7 +75,7 @@ local RECORD_TEMPLATE = record.encode({ local insert = table.insert local format = string.format local match = string.match -local substring = string.sub +local split = vim.split local LOCAL_BRANCH = "local_branch" local REMOTE_BRANCH = "remote_branch" @@ -84,9 +84,9 @@ local TAG_TEMPLATE = "tags/%s" local BRANCH_TEMPLATE = "%s/%s" local REMOTE_BRANCH_PATTERN = "^refs/remotes/([^/]*)/(.*)$" local HEAD = "*" -local head = "h" -local remote = "r" -local tag = "t" +local head = "heads" +local remote = "remotes" +local tag = "tags" function M.list_parsed() local result = record.decode(refs(RECORD_TEMPLATE)) @@ -100,7 +100,7 @@ function M.list_parsed() for _, ref in ipairs(result) do ref.head = ref.head == HEAD - local ref_type = substring(ref.ref, 6, 6) + local ref_type = split(ref.ref, "/")[2] if ref_type == head then ref.type = LOCAL_BRANCH ref.unambiguous_name = ref.name From 61a0a21ca0a984c9992077a0c9c45aa6db0d7307 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 21:30:31 +0200 Subject: [PATCH 398/437] Fix: opening margin popup from help popup --- lua/neogit/buffers/status/actions.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 26574e27d..f363f8599 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1425,6 +1425,7 @@ M.n_help_popup = function(self) bisect = { commits = commits }, reset = { commit = commit }, tag = { commit = commit }, + margin = { buffer = self }, stash = { name = stash and stash:match("^stash@{%d+}") }, diff = { section = { name = section_name }, From 8f79cc8f1455c8a58cc7669694353b9a566f6d65 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 21:35:05 +0200 Subject: [PATCH 399/437] Fix: bad merge --- spec/popups/help_popup_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index ef33c7949..82608f7bf 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -9,13 +9,8 @@ " Commands Applying changes Essential commands ", " $ History M Remote Stage all Refresh ", " A Cherry Pick m Merge K Untrack Go to file ", -<<<<<<< HEAD - " b Branch p Pull s Stage Toggle ", - " B Bisect P Push S Stage unstaged ", -======= " b Branch P Push s Stage za, Toggle ", " B Bisect p Pull S Stage unstaged ", ->>>>>>> upstream " c Commit Q Command u Unstage ", " d Diff r Rebase U Unstage all ", " f Fetch t Tag x Discard ", From 130f1a431ea11f5fa30457a72d6599dd2878e1fd Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 21:35:36 +0200 Subject: [PATCH 400/437] Lint --- lua/neogit/lib/popup/builder.lua | 2 +- lua/neogit/popups/margin/init.lua | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index c444fca3e..bfc1c7fa5 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -466,7 +466,7 @@ function M:action(keys, description, callback, opts) keys = keys, description = description, callback = callback, - persist_popup = opts.persist_popup or false + persist_popup = opts.persist_popup or false, }) return self diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index e7d5429dc..7a9741df9 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -33,13 +33,7 @@ function M.create(env) ) :switch("d", "decorate", "Show refnames", { enabled = true, internal = true }) :group_heading("Refresh") - :action_if( - env.buffer, - "g", - "buffer", - actions.refresh_buffer(env.buffer), - { persist_popup = true } - ) + :action_if(env.buffer, "g", "buffer", actions.refresh_buffer(env.buffer), { persist_popup = true }) :new_action_group("Margin") :action("L", "toggle visibility", actions.toggle_visibility, { persist_popup = true }) :action("l", "cycle style", actions.cycle_date_style, { persist_popup = true }) From 7bc072d72feae5e1ff54cfe981b23b4b8f4006a5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 21:52:06 +0200 Subject: [PATCH 401/437] Hide flags for margin popup that are not currently wired up to UI. --- lua/neogit/popups/margin/init.lua | 50 ++++++++++++++++--------------- spec/popups/margin_popup_spec.rb | 8 ++--- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index 7a9741df9..adc0a6374 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -1,36 +1,38 @@ local popup = require("neogit.lib.popup") -local config = require("neogit.config") +-- local config = require("neogit.config") local actions = require("neogit.popups.margin.actions") local M = {} +-- TODO: Implement various flags/switches + function M.create(env) local p = popup .builder() :name("NeogitMarginPopup") - :option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" }) - :switch("o", "topo", "Order commits by", { - cli_suffix = "-order", - options = { - { display = "", value = "" }, - { display = "topo", value = "topo" }, - { display = "author-date", value = "author-date" }, - { display = "date", value = "date" }, - }, - }) - :switch("g", "graph", "Show graph", { - enabled = true, - internal = true, - incompatible = { "reverse" }, - dependent = { "color" }, - }) - :switch_if( - config.values.graph_style == "ascii" or config.values.graph_style == "kitty", - "c", - "color", - "Show graph in color", - { internal = true, incompatible = { "reverse" } } - ) + -- :option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" }) + -- :switch("o", "topo", "Order commits by", { + -- cli_suffix = "-order", + -- options = { + -- { display = "", value = "" }, + -- { display = "topo", value = "topo" }, + -- { display = "author-date", value = "author-date" }, + -- { display = "date", value = "date" }, + -- }, + -- }) + -- :switch("g", "graph", "Show graph", { + -- enabled = true, + -- internal = true, + -- incompatible = { "reverse" }, + -- dependent = { "color" }, + -- }) + -- :switch_if( + -- config.values.graph_style == "ascii" or config.values.graph_style == "kitty", + -- "c", + -- "color", + -- "Show graph in color", + -- { internal = true, incompatible = { "reverse" } } + -- ) :switch("d", "decorate", "Show refnames", { enabled = true, internal = true }) :group_heading("Refresh") :action_if(env.buffer, "g", "buffer", actions.refresh_buffer(env.buffer), { persist_popup = true }) diff --git a/spec/popups/margin_popup_spec.rb b/spec/popups/margin_popup_spec.rb index 66897e0e3..cb697787e 100644 --- a/spec/popups/margin_popup_spec.rb +++ b/spec/popups/margin_popup_spec.rb @@ -8,10 +8,10 @@ let(:view) do [ " Arguments ", - " -n Limit number of commits (--max-count=256) ", - " -o Order commits by (--[topo|author-date|date]-order) ", - " -g Show graph (--graph) ", - " -c Show graph in color (--color) ", + # " -n Limit number of commits (--max-count=256) ", + # " -o Order commits by (--[topo|author-date|date]-order) ", + # " -g Show graph (--graph) ", + # " -c Show graph in color (--color) ", " -d Show refnames (--decorate) ", " ", " Refresh Margin ", From c77329f40f52c2e7d90631239296dc888de6f1e4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 21:52:30 +0200 Subject: [PATCH 402/437] Connect --decorate flag from margin popup to UI. --- lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/popups/margin/init.lua | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 0d07d2b2c..0452bdeea 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -325,7 +325,7 @@ local SectionItemCommit = Component.new(function(item) local ref = {} local ref_last = {} - if item.commit.ref_name ~= "" then + if item.commit.ref_name ~= "" and state.get({ "NeogitMarginPopup", "decorate" }, true) then -- Render local only branches first for name, _ in pairs(item.decoration.locals) do if name:match("^refs/") then diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index adc0a6374..5079c0802 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -33,7 +33,12 @@ function M.create(env) -- "Show graph in color", -- { internal = true, incompatible = { "reverse" } } -- ) - :switch("d", "decorate", "Show refnames", { enabled = true, internal = true }) + :switch( + "d", + "decorate", + "Show refnames", + { enabled = true, internal = true } + ) :group_heading("Refresh") :action_if(env.buffer, "g", "buffer", actions.refresh_buffer(env.buffer), { persist_popup = true }) :new_action_group("Margin") From 840dbde2fe01040a103f914fedc5817891f1e66a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 22:09:38 +0200 Subject: [PATCH 403/437] Fix: e2e spec for margin popup --- spec/popups/margin_popup_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/popups/margin_popup_spec.rb b/spec/popups/margin_popup_spec.rb index cb697787e..fe26666fb 100644 --- a/spec/popups/margin_popup_spec.rb +++ b/spec/popups/margin_popup_spec.rb @@ -3,8 +3,7 @@ require "spec_helper" RSpec.describe "Margin Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup - before { nvim.keys("L") } - + let(:keymap) { "L" } let(:view) do [ " Arguments ", From 0c67bb84504795282c72434fbb8a5d84525f3d3f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Aug 2025 22:10:37 +0200 Subject: [PATCH 404/437] Fix: Help popup spec. Hide "za" as a valid toggle keymap because I don't like how it looks in the UI. It still works the same, though. --- lua/neogit/popups/help/actions.lua | 6 ++++++ spec/popups/help_popup_spec.rb | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 7b03c6f1f..b46ffe0c5 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -21,6 +21,12 @@ local function present(commands) end if type(keymap) == "table" and next(keymap) then + -- HACK: Remove "za" as listed keymap for toggle action. + table.sort(keymap) + if name == "Toggle" and keymap[2] == "za" then + table.remove(keymap, 2) + end + return { { name = name, keys = keymap, cmp = table.concat(keymap):lower(), fn = fn } } else return { { name = name, keys = {}, cmp = "", fn = fn } } diff --git a/spec/popups/help_popup_spec.rb b/spec/popups/help_popup_spec.rb index 82608f7bf..1208765d6 100644 --- a/spec/popups/help_popup_spec.rb +++ b/spec/popups/help_popup_spec.rb @@ -9,8 +9,8 @@ " Commands Applying changes Essential commands ", " $ History M Remote Stage all Refresh ", " A Cherry Pick m Merge K Untrack Go to file ", - " b Branch P Push s Stage za, Toggle ", - " B Bisect p Pull S Stage unstaged ", + " b Branch p Pull s Stage Toggle ", + " B Bisect P Push S Stage unstaged ", " c Commit Q Command u Unstage ", " d Diff r Rebase U Unstage all ", " f Fetch t Tag x Discard ", From 19ee324f4f1a5ef4a7251792aba2855343aa4697 Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Thu, 14 Aug 2025 19:45:34 -0400 Subject: [PATCH 405/437] feat(margin): add order option --- lua/neogit/lib/git/log.lua | 13 +++++++++---- lua/neogit/popups/margin/init.lua | 23 ++++++++++++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 196d4ce22..54ac725b7 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") local config = require("neogit.config") local record = require("neogit.lib.record") +local state = require("neogit.lib.state") ---@class NeogitGitLog local M = {} @@ -420,13 +421,17 @@ function M.parent(commit) end function M.register(meta) - meta.update_recent = function(state) - state.recent = { items = {} } + meta.update_recent = function(repo_state) + repo_state.recent = { items = {} } local count = config.values.status.recent_commit_count + local order = state.get({ "NeogitMarginPopup", "-order" }, "topo") + if count > 0 then - state.recent.items = - util.filter_map(M.list({ "--max-count=" .. tostring(count) }, nil, {}, true), M.present_commit) + repo_state.recent.items = util.filter_map( + M.list({ "--max-count=" .. tostring(count), "--" .. order .. "-order" }, {}, {}, true), + M.present_commit + ) end end end diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index 5079c0802..154d4f6b4 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -11,15 +11,20 @@ function M.create(env) .builder() :name("NeogitMarginPopup") -- :option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" }) - -- :switch("o", "topo", "Order commits by", { - -- cli_suffix = "-order", - -- options = { - -- { display = "", value = "" }, - -- { display = "topo", value = "topo" }, - -- { display = "author-date", value = "author-date" }, - -- { display = "date", value = "date" }, - -- }, - -- }) + :switch( + "o", + "topo", + "Order commits by", + { + cli_suffix = "-order", + options = { + { display = "", value = "" }, + { display = "topo", value = "topo" }, + { display = "author-date", value = "author-date" }, + { display = "date", value = "date" }, + }, + } + ) -- :switch("g", "graph", "Show graph", { -- enabled = true, -- internal = true, From f805959c59aa771bc6465d5824abc746bea95c95 Mon Sep 17 00:00:00 2001 From: Lucas Adelino Date: Fri, 15 Aug 2025 18:46:20 -0400 Subject: [PATCH 406/437] test(margin): uncomment order option --- spec/popups/margin_popup_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/popups/margin_popup_spec.rb b/spec/popups/margin_popup_spec.rb index fe26666fb..f834c940f 100644 --- a/spec/popups/margin_popup_spec.rb +++ b/spec/popups/margin_popup_spec.rb @@ -8,7 +8,7 @@ [ " Arguments ", # " -n Limit number of commits (--max-count=256) ", - # " -o Order commits by (--[topo|author-date|date]-order) ", + " -o Order commits by (--[topo|author-date|date]-order) ", # " -g Show graph (--graph) ", # " -c Show graph in color (--color) ", " -d Show refnames (--decorate) ", From 2eb017569354ccd06e9069141437964a04abd90b Mon Sep 17 00:00:00 2001 From: Cameron Ring Date: Sat, 23 Aug 2025 14:29:44 -0700 Subject: [PATCH 407/437] fix(diffview): in status, conflicts use merge tool Code should now correctly put conflicts in the `conflicting` table (and not in the `working` table). Handles doing a diff on "staged", "unstaged", as well as "diff this" on both of those sections or individual files. Handles all conflict modes (though I don't know how to ever get UA/AU) Also now handles opening a unstaged/staged view from the "merge" section. Fixes #1774 --- lua/neogit/integrations/diffview.lua | 37 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 13fbe2db3..d586f6a95 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -14,22 +14,31 @@ local function get_local_diff_view(section_name, item_name, opts) local left = Rev(RevType.STAGE) local right = Rev(RevType.LOCAL) - if section_name == "unstaged" then - section_name = "working" - end - local function update_files() local files = {} - local sections = { - conflicting = { + local sections = {} + + -- all conflict modes (but I don't know how to generate UA/AU) + local conflict_modes = { "UU", "UD", "DU", "AA", "UA", "AU" } + + -- merge section gets both + if section_name == "unstaged" or section_name == "merge" then + sections.conflicting = { items = vim.tbl_filter(function(item) - return item.mode and item.mode:sub(2, 2) == "U" - end, git.repo.state.untracked.items), - }, - working = git.repo.state.unstaged, - staged = git.repo.state.staged, - } + return vim.tbl_contains(conflict_modes, item.mode) and item + end, git.repo.state.unstaged.items), + } + sections.working = { + items = vim.tbl_filter(function(item) + return not vim.tbl_contains(conflict_modes, item.mode) and item + end, git.repo.state.unstaged.items), + } + end + + if section_name == "staged" or section_name == "merge" then + sections.staged = git.repo.state.staged + end for kind, section in pairs(sections) do files[kind] = {} @@ -47,9 +56,8 @@ local function get_local_diff_view(section_name, item_name, opts) selected = (item_name and item.name == item_name) or (not item_name and idx == 1), } - -- restrict diff to only a particular section if opts.only then - if (item_name and file.selected) or (not item_name and section_name == kind) then + if not item_name or (item_name and file.selected) then table.insert(files[kind], file) end else @@ -132,6 +140,7 @@ function M.open(section_name, item_name, opts) elseif (section_name == "conflict" or section_name == "worktree") and not item_name then view = dv_lib.diffview_open() elseif section_name ~= nil then + -- for staged, unstaged, merge view = get_local_diff_view(section_name, item_name, opts) elseif section_name == nil and item_name ~= nil then view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) From a823b889fde90acffca5f24a10f80c6a8a87fbdf Mon Sep 17 00:00:00 2001 From: Cameron Ring Date: Sat, 23 Aug 2025 16:11:13 -0700 Subject: [PATCH 408/437] fix(popups/reset): branch expects commits --- lua/neogit/popups/reset/actions.lua | 12 ++++++++++++ lua/neogit/popups/reset/init.lua | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index d2b594d7a..bcf4e45c3 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -98,4 +98,16 @@ function M.a_file(popup) end end +---@param popup PopupData +function M.a_branch(popup) + -- branch reset expects commits to be set, not commit + if popup.state.env.commit then + popup.state.env.commits = { popup.state.env.commit } + popup.state.env.commit = nil + end + + local branch_actions = require("neogit.popups.branch.actions") + branch_actions.reset_branch(popup) +end + return M diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index d723c3a3f..82535122f 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -1,6 +1,5 @@ local popup = require("neogit.lib.popup") local actions = require("neogit.popups.reset.actions") -local branch_actions = require("neogit.popups.branch.actions") local M = {} @@ -10,7 +9,7 @@ function M.create(env) :name("NeogitResetPopup") :group_heading("Reset") :action("f", "file", actions.a_file) - :action("b", "branch", branch_actions.reset_branch) + :action("b", "branch", actions.a_branch) :new_action_group("Reset this") :action("m", "mixed (HEAD and index)", actions.mixed) :action("s", "soft (HEAD only)", actions.soft) From 378a6ae39818bc48829f217a03a2a2c314bd5340 Mon Sep 17 00:00:00 2001 From: Austin Liu Date: Sun, 24 Aug 2025 23:12:30 -0700 Subject: [PATCH 409/437] fix: add NeogitNormal and NeogitCursorLineNr highlights --- lua/neogit/lib/hl.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 483913a64..658e32773 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -178,7 +178,9 @@ function M.setup(config) NeogitSignatureGoodExpired = { link = "NeogitGraphOrange" }, NeogitSignatureGoodExpiredKey = { link = "NeogitGraphYellow" }, NeogitSignatureGoodRevokedKey = { link = "NeogitGraphRed" }, + NeogitNormal = { link = "Normal" }, NeogitCursorLine = { link = "CursorLine" }, + NeogitCursorLineNr = { link = "CursorLineNr" }, NeogitHunkMergeHeader = { fg = palette.bg2, bg = palette.grey, bold = palette.bold }, NeogitHunkMergeHeaderHighlight = { fg = palette.bg0, bg = palette.bg_cyan, bold = palette.bold }, NeogitHunkMergeHeaderCursor = { fg = palette.bg0, bg = palette.bg_cyan, bold = palette.bold }, From d3fe6df2e2d895ce53294489549860621d5a4b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Kothe?= Date: Tue, 26 Aug 2025 08:55:32 -0300 Subject: [PATCH 410/437] fix(push-popup): use force-with-lease in push popup --- lua/neogit/popups/push/actions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index d7fe25948..26fd6df56 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -51,9 +51,9 @@ local function push_to(args, remote, branch, opts) if res and res:failure() and not using_force and updates_rejected and config.values.prompt_force_push then logger.error("Attempting force push to " .. name) - local message = "Your branch has diverged from the remote branch. Do you want to force push?" + local message = "Your branch has diverged from the remote branch. Do you want to force push with lease?" if input.get_permission(message) then - table.insert(args, "--force") + table.insert(args, "--force-with-lease") res = git.push.push_interactive(remote, branch, args) end end From 274e86a8d4f9d96fd186f9e28054ddae70ecd7c8 Mon Sep 17 00:00:00 2001 From: Hendrik Hamerlinck Date: Thu, 4 Sep 2025 17:07:13 +0200 Subject: [PATCH 411/437] add 'commit_order' option for margin popup --- README.md | 7 +++++++ doc/neogit.txt | 7 +++++++ lua/neogit/config.lua | 8 ++++++++ lua/neogit/lib/git/log.lua | 11 +++++++++-- lua/neogit/popups/margin/init.lua | 4 ++-- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ecf42db47..ff5be1813 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,13 @@ neogit.setup { -- Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt -- Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options sort_branches = "-committerdate", + -- Value passed to the `---order` flag of the `git log` command + -- Determines how commits are traversed and displayed in the log / graph: + -- "topo" topological order (parents always before children, good for graphs, slower on large repos) + -- "date" chronological order by commit date + -- "author-date" chronological order by author date + -- "" disable explicit ordering (fastest, recommended for very large repos) + commit_order = "topo" -- Default for new branch name prompts initial_branch_name = "", -- Change the default way of opening neogit diff --git a/doc/neogit.txt b/doc/neogit.txt index 5d1cb0276..73f776473 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -147,6 +147,13 @@ to Neovim users. -- Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt -- Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options sort_branches = "-committerdate", + -- Value passed to the `---order` flag of the `git log` command + -- Determines how commits are traversed and displayed in the log / graph: + -- "topo" topological order (parents always before children, good for graphs, slower on large repos) + -- "date" chronological order by commit date + -- "author-date" chronological order by author date + -- "" disable explicit ordering (fastest, recommended for very large repos) + commit_order = "topo" -- Default for new branch name prompts initial_branch_name = "", -- Change the default way of opening neogit diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 67d79e6cd..6f65ac032 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -310,6 +310,12 @@ end ---| "ascii" ---| "unicode" ---| "kitty" +--- +---@alias NeogitCommitOrder +---| "" +---| "topo" +---| "author-date" +---| "date" ---@class NeogitConfigStatusOptions ---@field recent_commit_count? integer The number of recent commits to display @@ -346,6 +352,7 @@ end ---@field use_per_project_settings? boolean Scope persisted settings on a per-project basis ---@field remember_settings? boolean Whether neogit should persist flags from popups, e.g. git push flags ---@field sort_branches? string Value used for `--sort` for the `git branch` command +---@field commit_order? NeogitCommitOrder Value used for `---order` for the `git log` command ---@field initial_branch_name? string Default for new branch name prompts ---@field kind? WindowKind The default type of window neogit should open in ---@field floating? NeogitConfigFloating The floating window style @@ -408,6 +415,7 @@ function M.get_default_values() remember_settings = true, fetch_after_checkout = false, sort_branches = "-committerdate", + commit_order = "topo", kind = "tab", floating = { relative = "editor", diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 54ac725b7..b120260ba 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -425,11 +425,18 @@ function M.register(meta) repo_state.recent = { items = {} } local count = config.values.status.recent_commit_count - local order = state.get({ "NeogitMarginPopup", "-order" }, "topo") + local order = state.get({ "NeogitMarginPopup", "-order" }, config.values.commit_order) if count > 0 then + local args = { "--max-count=" .. tostring(count) } + local graph = nil; + if order and order ~= "" then + table.insert(args, "--" .. order .. "-order") + graph = {} + end + repo_state.recent.items = util.filter_map( - M.list({ "--max-count=" .. tostring(count), "--" .. order .. "-order" }, {}, {}, true), + M.list(args, graph, {}, false), M.present_commit ) end diff --git a/lua/neogit/popups/margin/init.lua b/lua/neogit/popups/margin/init.lua index 154d4f6b4..272838737 100644 --- a/lua/neogit/popups/margin/init.lua +++ b/lua/neogit/popups/margin/init.lua @@ -1,5 +1,5 @@ local popup = require("neogit.lib.popup") --- local config = require("neogit.config") +local config = require("neogit.config") local actions = require("neogit.popups.margin.actions") local M = {} @@ -13,7 +13,7 @@ function M.create(env) -- :option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" }) :switch( "o", - "topo", + config.values.commit_order, "Order commits by", { cli_suffix = "-order", From e742bf163b9557dfb6b066067050d01b40e2ad90 Mon Sep 17 00:00:00 2001 From: Hendrik Hamerlinck Date: Fri, 5 Sep 2025 08:26:20 +0200 Subject: [PATCH 412/437] format with stylua --- lua/neogit/lib/git/log.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b120260ba..4958f433b 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -429,16 +429,13 @@ function M.register(meta) if count > 0 then local args = { "--max-count=" .. tostring(count) } - local graph = nil; + local graph = nil if order and order ~= "" then table.insert(args, "--" .. order .. "-order") graph = {} end - repo_state.recent.items = util.filter_map( - M.list(args, graph, {}, false), - M.present_commit - ) + repo_state.recent.items = util.filter_map(M.list(args, graph, {}, false), M.present_commit) end end end From 5e33e042e41268dd0809eb19f05762fc8e050586 Mon Sep 17 00:00:00 2001 From: SheffeyG <57262511+SheffeyG@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:35:34 +0800 Subject: [PATCH 413/437] Set git diff.noprefix to false --- lua/neogit/lib/git/cli.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 315246e41..958f42c98 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1174,6 +1174,7 @@ local function new_builder(subcommand) "--no-optional-locks", "-c", "core.preloadindex=true", "-c", "color.ui=always", + "-c", "diff.noprefix=false", subcommand }, cmd From e696453736ec945034499ddd55fc02c77bc24484 Mon Sep 17 00:00:00 2001 From: SheffeyG <57262511+SheffeyG@users.noreply.github.com> Date: Fri, 12 Sep 2025 17:25:06 +0800 Subject: [PATCH 414/437] Mask git command args `diff.noprefix` --- lua/neogit/process.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 60e95f56e..050b98b13 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -11,8 +11,9 @@ local Spinner = require("neogit.spinner") local api = vim.api local fn = vim.fn -local command_mask = - vim.pesc(" --no-pager --literal-pathspecs --no-optional-locks -c core.preloadindex=true -c color.ui=always") +local command_mask = vim.pesc( + " --no-pager --literal-pathspecs --no-optional-locks -c core.preloadindex=true -c color.ui=always -c diff.noprefix=false" +) local function mask_command(cmd) local command, _ = cmd:gsub(command_mask, "") From 3c519bfb59f3be135cdf81f62d48df8609a4c170 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 13:00:25 +0200 Subject: [PATCH 415/437] breaking! Add "o" mapping to commit view, which opens the commit's view in web browser. This changes the `config.git_services.x` table format from: ```lua { ["github.com"] = "", ... } ``` to: ```lua { ["github.com"] = { pull_request = "", commit = "" }, ... } ``` Github, gitlab, and bitbucket are supported out-of-the-box. --- lua/neogit/buffers/commit_view/init.lua | 34 +++++++++++++++++++++++++ lua/neogit/config.lua | 34 +++++++++++++++++++++---- lua/neogit/popups/branch/actions.lua | 4 +-- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 60dbed42f..77c287ddf 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -5,6 +5,8 @@ local git = require("neogit.lib.git") local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() +local util = require("neogit.lib.util") +local notification = require("neogit.lib.notification") local api = vim.api @@ -174,6 +176,38 @@ function M:open(kind) }, mappings = { n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local template + local url = git.remote.get_url(https://github.com/upstream)[1] + + for s, v in pairs(config.values.git_services) do + if url:match(util.pattern_escape(s)) then + template = v.commit + break + end + end + + if template and template ~= "" then + local format_values = git.remote.parse(url) + format_values["oid"] = self.commit_info.oid + local uri = util.format(template, format_values) + + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Commit URL template not found for this branch's upstream") + end + end, [""] = function() local c = self.buffer.ui:get_component_under_cursor(function(c) return c.options.highlight == "NeogitFilePath" diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 67d79e6cd..82591352b 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -329,6 +329,10 @@ end ---@field commit_editor_I? { [string]: NeogitConfigMappingsCommitEditor_I } A dictionary that uses Commit editor commands to set a single keybind ---@field refs_view? { [string]: NeogitConfigMappingsRefsView } A dictionary that uses Refs view editor commands to set a single keybind +---@class NeogitConfigGitService +---@field pull_request string +---@field commit string + ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher ---@field graph_style? NeogitGraphStyle Style for graph @@ -338,7 +342,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 prompt_force_push? boolean Offer to force push when branches diverge ----@field git_services? table Templartes to use when opening a pull request for a branch +---@field git_services? NeogitConfigGitService[] Templartes to use when opening a pull request for a branch, or commit ---@field fetch_after_checkout? boolean Perform a fetch if the newly checked out branch has an upstream or pushRemote set ---@field telescope_sorter? function The sorter telescope will use ---@field process_spinner? boolean Hide/Show the process spinner @@ -397,10 +401,22 @@ function M.get_default_values() return nil end, git_services = { - ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", - ["bitbucket.org"] = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", - ["gitlab.com"] = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", - ["azure.com"] = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + ["github.com"] = { + pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", + commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", + }, + ["bitbucket.org"] = { + pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", + commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", + }, + ["gitlab.com"] = { + pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", + commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", + }, + ["azure.com"] = { + pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + commit = "", + }, }, highlight = {}, disable_insert_on_commit = "auto", @@ -1220,6 +1236,14 @@ function M.validate_config() validate_kind(config.popup.kind, "popup.kind") end + if validate_type(config.git_services, "git_services", "table") then + for k, v in pairs(config.git_services) do + validate_type(v, "git_services." .. k, "table") + validate_type(v.pull_request, "git_services." .. k .. ".pull_request", "string") + validate_type(v.commit, "git_services." .. k .. ".commit", "string") + end + end + validate_integrations() validate_sections() validate_ignored_settings() diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index cc7159122..d733e396b 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -378,7 +378,7 @@ function M.open_pull_request() for s, v in pairs(config.values.git_services) do if url:match(util.pattern_escape(s)) then service = s - template = v + template = v.pull_request break end end @@ -409,7 +409,7 @@ function M.open_pull_request() notification.info(("Opening %q in your browser."):format(uri)) vim.ui.open(uri) else - notification.warn("Requires Neovim 0.10") + notification.warn("Requires Neovim >= 0.10") end else notification.warn("Pull request URL template not found for this branch's upstream") From 4944a435ac1f34b8636a06cbf6bf751dce0685d1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:10:34 +0200 Subject: [PATCH 416/437] extract commit_url to git.remote lib for reuse --- lua/neogit/buffers/commit_view/init.lua | 22 ++----------------- lua/neogit/lib/git/remote.lua | 29 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 77c287ddf..943ac450f 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -182,26 +182,8 @@ function M:open(kind) return end - local upstream = git.branch.upstream_remote() - if not upstream then - return - end - - local template - local url = git.remote.get_url(https://github.com/upstream)[1] - - for s, v in pairs(config.values.git_services) do - if url:match(util.pattern_escape(s)) then - template = v.commit - break - end - end - - if template and template ~= "" then - local format_values = git.remote.parse(url) - format_values["oid"] = self.commit_info.oid - local uri = util.format(template, format_values) - + local uri = git.remote.commit_url(https://github.com/self.commit_info.oid) + if uri then notification.info(("Opening %q in your browser."):format(uri)) vim.ui.open(uri) else diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index 31bc633d1..a6df6754b 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -134,4 +134,33 @@ function M.parse(url) } end +---@param oid string object-id for commit +---@return string|nil +function M.commit_url(https://github.com/oid) + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local template + local url = M.get_url(https://github.com/upstream)[1] + + for s, v in pairs(require("neogit.config").values.git_services) do + if url:match(util.pattern_escape(s)) then + template = v.commit + break + end + end + + if template and template ~= "" then + local format_values = M.parse(url) + format_values["oid"] = oid + local uri = util.format(template, format_values) + + return uri + else + return nil + end +end + return M From e7364302d8363b1e280695aff33bfbfcbd08e991 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:11:05 +0200 Subject: [PATCH 417/437] Add "open commit" functionality to log and reflog views. --- lua/neogit/buffers/commit_view/init.lua | 3 +-- lua/neogit/buffers/log_view/init.lua | 21 +++++++++++++++++++++ lua/neogit/buffers/reflog_view/init.lua | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 943ac450f..7e93fb37b 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -5,7 +5,6 @@ local git = require("neogit.lib.git") local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() -local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local api = vim.api @@ -187,7 +186,7 @@ function M:open(kind) notification.info(("Opening %q in your browser."):format(uri)) vim.ui.open(uri) else - notification.warn("Commit URL template not found for this branch's upstream") + notification.warn("Couldn't determine commit URL to open") end end, [""] = function() diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 92978614c..d285e0378 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -6,6 +6,8 @@ local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") local util = require("neogit.lib.util") local a = require("plenary.async") +local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") ---@class LogViewBuffer ---@field commits CommitLogEntry[] @@ -127,6 +129,25 @@ function M:open() end), }, n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local oid = self.buffer.ui:get_commit_under_cursor() + if not oid then + return + end + + local uri = git.remote.commit_url(https://github.com/oid) + if uri then + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Couldn't determine commit URL to open") + end + end, [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 0187a187d..986fca7d1 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -4,6 +4,8 @@ local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") +local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") ---@class ReflogViewBuffer ---@field entries ReflogEntry[] @@ -100,6 +102,25 @@ function M:open(_) end), }, n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local oid = self.buffer.ui:get_commit_under_cursor() + if not oid then + return + end + + local uri = git.remote.commit_url(https://github.com/oid) + if uri then + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Couldn't determine commit URL to open") + end + end, [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), From 814ecaacddfb5de7f8639583a3eaaf8034170145 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:11:19 +0200 Subject: [PATCH 418/437] Add spec for commit view --- spec/buffers/commit_buffer_spec.rb | 114 +++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 spec/buffers/commit_buffer_spec.rb diff --git a/spec/buffers/commit_buffer_spec.rb b/spec/buffers/commit_buffer_spec.rb new file mode 100644 index 000000000..0ada40be2 --- /dev/null +++ b/spec/buffers/commit_buffer_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Commit Buffer", :git, :nvim do + before do + nvim.keys("ll") + end + + it "can close the view with " do + nvim.keys("") + expect(nvim.filetype).to eq("NeogitLogView") + end + + it "can close the view with q" do + nvim.keys("q") + expect(nvim.filetype).to eq("NeogitLogView") + end + + it "can yank OID" do + nvim.keys("Y") + expect(nvim.screen.last.strip).to match(/\A[a-f0-9]{40}\z/) + end + + it "can open the bisect popup" do + nvim.keys("B") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the branch popup" do + nvim.keys("b") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the cherry pick popup" do + nvim.keys("A") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the commit popup" do + nvim.keys("c") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the diff popup" do + nvim.keys("d") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the pull popup" do + nvim.keys("p") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the fetch popup" do + nvim.keys("f") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the ignore popup" do + nvim.keys("i") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the log popup" do + nvim.keys("l") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the remote popup" do + nvim.keys("M") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the merge popup" do + nvim.keys("m") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the push popup" do + nvim.keys("P") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the rebase popup" do + nvim.keys("r") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the tag popup" do + nvim.keys("t") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the revert popup" do + nvim.keys("v") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the worktree popup" do + nvim.keys("w") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the reset popup" do + nvim.keys("X") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the stash popup" do + nvim.keys("Z") + expect(nvim.filetype).to eq("NeogitPopup") + end +end From 7d985f8550602199e9baf807501f25867ebadb55 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:11:33 +0200 Subject: [PATCH 419/437] Add missing popup actions to commit view. --- lua/neogit/buffers/commit_view/init.lua | 39 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 7e93fb37b..c57989942 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -287,19 +287,38 @@ function M:open(kind) vim.cmd("normal! zt") end end, - [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.commit_info.oid } } end), [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) p { commits = { self.commit_info.oid } } end), + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = { self.commit_info.oid } } + end), [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) p { commit = self.commit_info.oid } end), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + p { + section = { name = "log" }, + item = { name = self.commit_info.oid }, + } + end), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), + -- help + [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + p { + paths = { path and path.escaped_path }, + worktree_root = git.repo.worktree_root, + } + end), + [popups.mapping_for("LogPopup")] = popups.open("log"), [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), [popups.mapping_for("PushPopup")] = popups.open("push", function(p) p { commit = self.commit_info.oid } end), @@ -307,26 +326,18 @@ function M:open(kind) p { commit = self.commit_info.oid } end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + p { commit = self.commit_info.oid } + end), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) local item = self.buffer.ui:get_hunk_or_filename_under_cursor() or {} p { commits = { self.commit_info.oid }, hunk = item.hunk } end), - [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - p { commit = self.commit_info.oid } - end), + [popups.mapping_for("StashPopup")] = popups.open("stash"), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.commit_info.oid } end), - [popups.mapping_for("PullPopup")] = popups.open("pull"), - [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - p { - section = { name = "log" }, - item = { name = self.commit_info.oid }, - } - end), - [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) - p { commits = { self.commit_info.oid } } - end), + [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), [status_maps["Close"]] = function() self:close() end, From 9bc8ee711c3659c67725eb0f20a2ecfa60abc1db Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:12:52 +0200 Subject: [PATCH 420/437] Add missing return value annotations to status buffer actions --- lua/neogit/buffers/status/actions.lua | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index f363f8599..1c6dba44c 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -93,6 +93,7 @@ end local M = {} ---@param self StatusBuffer +---@return fun(): nil M.v_discard = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -217,6 +218,7 @@ M.v_discard = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_stage = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -273,6 +275,7 @@ M.v_stage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_unstage = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -318,6 +321,7 @@ M.v_unstage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_branch_popup = function(self) return popups.open("branch", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -325,6 +329,7 @@ M.v_branch_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_cherry_pick_popup = function(self) return popups.open("cherry_pick", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -332,6 +337,7 @@ M.v_cherry_pick_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_commit_popup = function(self) return popups.open("commit", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -342,6 +348,7 @@ M.v_commit_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_merge_popup = function(self) return popups.open("merge", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -352,6 +359,7 @@ M.v_merge_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_push_popup = function(self) return popups.open("push", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -362,6 +370,7 @@ M.v_push_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_rebase_popup = function(self) return popups.open("rebase", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -372,6 +381,7 @@ M.v_rebase_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_revert_popup = function(self) return popups.open("revert", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -379,6 +389,7 @@ M.v_revert_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_reset_popup = function(self) return popups.open("reset", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -389,6 +400,7 @@ M.v_reset_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_tag_popup = function(self) return popups.open("tag", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -399,6 +411,7 @@ M.v_tag_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_stash_popup = function(self) return popups.open("stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() @@ -407,6 +420,7 @@ M.v_stash_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_diff_popup = function(self) return popups.open("diff", function(p) local section = self.buffer.ui:get_selection().section @@ -416,6 +430,7 @@ M.v_diff_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_ignore_popup = function(self) return popups.open("ignore", function(p) p { paths = self.buffer.ui:get_filepaths_in_selection(), worktree_root = git.repo.worktree_root } @@ -423,6 +438,7 @@ M.v_ignore_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_bisect_popup = function(self) return popups.open("bisect", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -430,31 +446,37 @@ M.v_bisect_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.v_remote_popup = function(_self) return popups.open("remote") end ---@param _self StatusBuffer +---@return fun(): nil M.v_fetch_popup = function(_self) return popups.open("fetch") end ---@param _self StatusBuffer +---@return fun(): nil M.v_pull_popup = function(_self) return popups.open("pull") end ---@param _self StatusBuffer +---@return fun(): nil M.v_help_popup = function(_self) return popups.open("help") end ---@param _self StatusBuffer +---@return fun(): nil M.v_log_popup = function(_self) return popups.open("log") end ---@param self StatusBuffer +---@return fun(): nil M.v_margin_popup = function(self) return popups.open("margin", function(p) p { buffer = self } @@ -462,11 +484,13 @@ M.v_margin_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.v_worktree_popup = function(_self) return popups.open("worktree") end ---@param self StatusBuffer +---@return fun(): nil M.n_down = function(self) return function() if vim.v.count > 0 then @@ -482,6 +506,7 @@ M.n_down = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_up = function(self) return function() if vim.v.count > 0 then @@ -497,6 +522,7 @@ M.n_up = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_toggle = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -516,6 +542,7 @@ M.n_toggle = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_fold = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -535,6 +562,7 @@ M.n_open_fold = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_close_fold = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -550,11 +578,13 @@ M.n_close_fold = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_close = function(self) return require("neogit.lib.ui.helpers").close_topmost(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_or_scroll_down = function(self) return function() local commit = self.buffer.ui:get_commit_under_cursor() @@ -565,6 +595,7 @@ M.n_open_or_scroll_down = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_or_scroll_up = function(self) return function() local commit = self.buffer.ui:get_commit_under_cursor() @@ -575,6 +606,7 @@ M.n_open_or_scroll_up = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_refresh_buffer = function(self) return a.void(function() self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_refresh_buffer") @@ -582,6 +614,7 @@ M.n_refresh_buffer = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth1 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -600,6 +633,7 @@ M.n_depth1 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth2 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -627,6 +661,7 @@ M.n_depth2 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth3 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -656,6 +691,7 @@ M.n_depth3 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth4 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -682,6 +718,7 @@ M.n_depth4 = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_command_history = function(_self) return a.void(function() require("neogit.buffers.git_command_history"):new():show() @@ -689,6 +726,7 @@ M.n_command_history = function(_self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_show_refs = function(_self) return a.void(function() require("neogit.buffers.refs_view").new(git.refs.list_parsed(), git.repo.worktree_root):open() @@ -696,6 +734,7 @@ M.n_show_refs = function(_self) end ---@param self StatusBuffer +---@return fun(): nil M.n_yank_selected = function(self) return function() local yank = self.buffer.ui:get_yankable_under_cursor() @@ -714,6 +753,7 @@ M.n_yank_selected = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_discard = function(self) return a.void(function() git.index.update() @@ -947,6 +987,7 @@ M.n_discard = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_go_to_next_hunk_header = function(self) return function() local c = self.buffer.ui:get_component_under_cursor(function(c) @@ -978,6 +1019,7 @@ M.n_go_to_next_hunk_header = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_go_to_previous_hunk_header = function(self) return function() local function previous_hunk_header(self, line) @@ -1004,6 +1046,7 @@ M.n_go_to_previous_hunk_header = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_init_repo = function(_self) return function() git.init.init_repo() @@ -1011,6 +1054,7 @@ M.n_init_repo = function(_self) end ---@param self StatusBuffer +---@return fun(): nil M.n_rename = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1046,6 +1090,7 @@ M.n_rename = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1076,6 +1121,7 @@ M.n_untrack = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1103,6 +1149,7 @@ M.v_untrack = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage = function(self) return a.void(function() local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1183,6 +1230,7 @@ M.n_stage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage_all = function(self) return a.void(function() git.status.stage_all() @@ -1191,6 +1239,7 @@ M.n_stage_all = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage_unstaged = function(self) return a.void(function() git.status.stage_modified() @@ -1199,6 +1248,7 @@ M.n_stage_unstaged = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_unstage = function(self) return a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1235,6 +1285,7 @@ M.n_unstage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_unstage_staged = function(self) return a.void(function() git.status.unstage_all() @@ -1243,6 +1294,7 @@ M.n_unstage_staged = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_goto_file = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1264,6 +1316,7 @@ M.n_goto_file = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_tab_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1275,6 +1328,7 @@ M.n_tab_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1286,6 +1340,7 @@ M.n_split_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_vertical_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1297,6 +1352,7 @@ M.n_vertical_split_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_branch_popup = function(self) return popups.open("branch", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1304,6 +1360,7 @@ M.n_branch_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_bisect_popup = function(self) return popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1311,6 +1368,7 @@ M.n_bisect_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_cherry_pick_popup = function(self) return popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1318,6 +1376,7 @@ M.n_cherry_pick_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_commit_popup = function(self) return popups.open("commit", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1325,6 +1384,7 @@ M.n_commit_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_merge_popup = function(self) return popups.open("merge", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1332,6 +1392,7 @@ M.n_merge_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_push_popup = function(self) return popups.open("push", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1339,6 +1400,7 @@ M.n_push_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_rebase_popup = function(self) return popups.open("rebase", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1346,6 +1408,7 @@ M.n_rebase_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_revert_popup = function(self) return popups.open("revert", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1353,6 +1416,7 @@ M.n_revert_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_reset_popup = function(self) return popups.open("reset", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1360,6 +1424,7 @@ M.n_reset_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_tag_popup = function(self) return popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1367,6 +1432,7 @@ M.n_tag_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stash_popup = function(self) return popups.open("stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() @@ -1375,6 +1441,7 @@ M.n_stash_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_diff_popup = function(self) return popups.open("diff", function(p) local section = self.buffer.ui:get_selection().section @@ -1387,6 +1454,7 @@ M.n_diff_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_ignore_popup = function(self) return popups.open("ignore", function(p) local path = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1398,6 +1466,7 @@ M.n_ignore_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_help_popup = function(self) return popups.open("help", function(p) -- Since any other popup can be launched from help, build an ENV for any of them. @@ -1445,26 +1514,31 @@ M.n_help_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_remote_popup = function(_self) return popups.open("remote") end ---@param _self StatusBuffer +---@return fun(): nil M.n_fetch_popup = function(_self) return popups.open("fetch") end ---@param _self StatusBuffer +---@return fun(): nil M.n_pull_popup = function(_self) return popups.open("pull") end ---@param _self StatusBuffer +---@return fun(): nil M.n_log_popup = function(_self) return popups.open("log") end ---@param self StatusBuffer +---@return fun(): nil M.n_margin_popup = function(self) return popups.open("margin", function(p) p { buffer = self } @@ -1472,6 +1546,7 @@ M.n_margin_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_worktree_popup = function(_self) return popups.open("worktree") end @@ -1495,6 +1570,7 @@ M.n_open_tree = function(_self) end ---@param self StatusBuffer|nil +---@return fun(): nil M.n_command = function(self) local process = require("neogit.process") local runner = require("neogit.runner") @@ -1536,6 +1612,7 @@ M.n_command = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_next_section = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -1549,6 +1626,7 @@ M.n_next_section = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_prev_section = function(self) return function() local section = self.buffer.ui:get_current_section() From 6e2a1f0df2ef8293b632cdbf658bc86ac6c2f5dc Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:13:19 +0200 Subject: [PATCH 421/437] Extract tree url function to git.remote lib for consistency with commit url function. In status view, if you have a commit selected, open that commit's url. Otherwise, open the branch's tree url instead. --- lua/neogit/buffers/status/actions.lua | 24 ++++++++++++---------- lua/neogit/config.lua | 22 +++++++++++++++----- lua/neogit/lib/git/remote.lua | 29 +++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 1c6dba44c..59ae02e40 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1551,21 +1551,23 @@ M.n_worktree_popup = function(_self) return popups.open("worktree") end ----@param _self StatusBuffer -M.n_open_tree = function(_self) +---@param self StatusBuffer +---@return fun(): nil +M.n_open_tree = function(self) return a.void(function() - local template = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D" + local commit = self.buffer.ui:get_commit_under_cursor() + local branch = git.branch.current() + local url - local upstream = git.branch.upstream_remote() - if not upstream then - return + if commit then + url = git.remote.commit_url(https://github.com/commit) + elseif branch then + url = git.remote.tree_url(https://github.com/branch) end - local url = git.remote.get_url(https://github.com/upstream)[1] - local format_values = git.remote.parse(url) - format_values["branch_name"] = git.branch.current() - - vim.ui.open(util.format(template, format_values)) + if url then + vim.ui.open(url) + end end) end diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 82591352b..513bcd584 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -330,8 +330,9 @@ end ---@field refs_view? { [string]: NeogitConfigMappingsRefsView } A dictionary that uses Refs view editor commands to set a single keybind ---@class NeogitConfigGitService ----@field pull_request string ----@field commit string +---@field pull_request? string +---@field commit? string +---@field tree? string ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher @@ -342,7 +343,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 prompt_force_push? boolean Offer to force push when branches diverge ----@field git_services? NeogitConfigGitService[] Templartes to use when opening a pull request for a branch, or commit +---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit ---@field fetch_after_checkout? boolean Perform a fetch if the newly checked out branch has an upstream or pushRemote set ---@field telescope_sorter? function The sorter telescope will use ---@field process_spinner? boolean Hide/Show the process spinner @@ -404,18 +405,22 @@ function M.get_default_values() ["github.com"] = { pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", + tree = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D", }, ["bitbucket.org"] = { pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", + tree = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/branch/$%7Bbranch_name%7D", }, ["gitlab.com"] = { pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", + tree = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/tree/$%7Bbranch_name%7D?ref_type=heads", }, ["azure.com"] = { pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", commit = "", + tree = "", }, }, highlight = {}, @@ -1241,6 +1246,7 @@ function M.validate_config() validate_type(v, "git_services." .. k, "table") validate_type(v.pull_request, "git_services." .. k .. ".pull_request", "string") validate_type(v.commit, "git_services." .. k .. ".commit", "string") + validate_type(v.tree, "git_services." .. k .. ".tree", "string") end end @@ -1276,8 +1282,14 @@ function M.setup(opts) end if opts.use_default_keymaps == false then - M.values.mappings = - { status = {}, popup = {}, finder = {}, commit_editor = {}, rebase_editor = {}, refs_view = {} } + M.values.mappings = { + status = {}, + popup = {}, + finder = {}, + commit_editor = {}, + rebase_editor = {}, + refs_view = {} + } else -- Clear our any "false" user mappings from defaults for section, maps in pairs(opts.mappings or {}) do diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index a6df6754b..2d28ebcc8 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -158,8 +158,33 @@ function M.commit_url(https://github.com/oid) local uri = util.format(template, format_values) return uri - else - return nil + end +end + +---@param branch string +---@return string|nil +function M.tree_url(https://github.com/branch) + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local template + local url = M.get_url(https://github.com/upstream)[1] + + for s, v in pairs(require("neogit.config").values.git_services) do + if url:match(util.pattern_escape(s)) then + template = v.tree + break + end + end + + if template and template ~= "" then + local format_values = M.parse(url) + format_values["branch_name"] = branch + local uri = util.format(template, format_values) + + return uri end end From e106a39a8a37a9420f374545c876f8b52baf1c80 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:14:35 +0200 Subject: [PATCH 422/437] remove "find components" function from UI library. Not used. --- lua/neogit/config.lua | 2 +- lua/neogit/lib/ui/init.lua | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 513bcd584..fe8abd948 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -1288,7 +1288,7 @@ function M.setup(opts) finder = {}, commit_editor = {}, rebase_editor = {}, - refs_view = {} + refs_view = {}, } else -- Clear our any "false" user mappings from defaults diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index f2d6bfb1f..225cc5af5 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -40,6 +40,7 @@ function Ui.new(buf) return setmetatable({ buf = buf, layout = {} }, Ui) end +---@return Component|nil function Ui._find_component(components, f, options) for _, c in ipairs(components) do if c.tag == "col" or c.tag == "row" then @@ -64,24 +65,6 @@ function Ui:find_component(f, options) return Ui._find_component(self.layout, f, options or {}) end -function Ui._find_components(components, f, result, options) - for _, c in ipairs(components) do - if c.tag == "col" or c.tag == "row" then - Ui._find_components(c.children, f, result, options) - end - - if f(c) then - table.insert(result, c) - end - end -end - -function Ui:find_components(f, options) - local result = {} - Ui._find_components(self.layout, f, result, options or {}) - return result -end - ---@param fn? fun(c: Component): boolean ---@return Component|nil function Ui:get_component_under_cursor(fn) From b1d58330b523c954331037564fcf3caa9abc0cac Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:18:37 +0200 Subject: [PATCH 423/437] Add better notifications for OpenTree function --- lua/neogit/buffers/status/actions.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 59ae02e40..a28d2fec6 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1555,6 +1555,11 @@ end ---@return fun(): nil M.n_open_tree = function(self) return a.void(function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + local commit = self.buffer.ui:get_commit_under_cursor() local branch = git.branch.current() local url @@ -1566,7 +1571,10 @@ M.n_open_tree = function(self) end if url then + notification.info(("Opening %q in your browser."):format(url)) vim.ui.open(url) + else + notification.warn("Couldn't determine commit URL to open") end end) end From 35d59779ad1e7a2e2a164141e73b22da4ff0e2bc Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Sep 2025 10:54:12 +0200 Subject: [PATCH 424/437] fix: allow canceling "reset file" when in file-finder --- lua/neogit/popups/reset/actions.lua | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index bcf4e45c3..248874d93 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -80,21 +80,18 @@ function M.a_file(popup) end local files = FuzzyFinderBuffer.new(files):open_async { allow_multi = true } - if not files[1] then - return - end - - local success = git.reset.file(target, files) - if not success then - notification.error("Reset Failed") - else - if #files > 1 then - notification.info("Reset " .. #files .. " files") + if files and files[1] then + if git.reset.file(target, files) then + if #files > 1 then + notification.info("Reset " .. #files .. " files") + else + notification.info("Reset " .. files[1]) + end + + event.send("Reset", { commit = target, mode = "files", files = files }) else - notification.info("Reset " .. files[1]) + notification.error("Reset Failed") end - - event.send("Reset", { commit = target, mode = "files", files = files }) end end From 3cdec1ef13254eb578ac94f0ac9422965e0b081f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Sep 2025 10:54:36 +0200 Subject: [PATCH 425/437] fix: properly mask default git commands in history view --- lua/neogit/buffers/git_command_history.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 9229a7b9d..c9a2326a4 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -11,8 +11,9 @@ local text = Ui.text local col = Ui.col local row = Ui.row -local command_mask = - vim.pesc(" --no-pager --literal-pathspecs --no-optional-locks -c core.preloadindex=true -c color.ui=always") +local command_mask = vim.pesc( + " --no-pager --literal-pathspecs --no-optional-locks -c core.preloadindex=true -c color.ui=always -c diff.noprefix=false" +) local M = {} From d6112de97160a09c767e2b9dfba45e8942cbd5a4 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:16:38 +0800 Subject: [PATCH 426/437] Fix fzf-lua default border config --- lua/neogit/lib/finder.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 95c34c1e3..56cd96c2c 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -340,6 +340,8 @@ function Finder:find(on_select) fzf_opts = fzf_opts(self.opts), winopts = { height = self.opts.layout_config.height, + border = self.opts.border, + preview = { border = self.opts.border }, }, actions = fzf_actions(on_select, self.opts.allow_multi, self.opts.refocus_status), }) From a926b03b348c0be46a6099fc662b6df98b5374ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 19 Sep 2025 17:03:41 +0200 Subject: [PATCH 427/437] Update README.md --- README.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ff5be1813..ebe7bb90c 100644 --- a/README.md +++ b/README.md @@ -99,13 +99,28 @@ neogit.setup { log_date_format = nil, -- Show message with spinning animation when a git command is running. process_spinner = false, - -- Used to generate URL's for branch popup action "pull request". - git_services = { - ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", - ["bitbucket.org"] = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", - ["gitlab.com"] = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", - ["azure.com"] = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", - }, + -- Used to generate URL's for branch popup action "pull request", "open commit" and "open tree" + git_services = { + ["github.com"] = { + pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", + commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", + tree = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D", + }, + ["bitbucket.org"] = { + pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", + commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", + tree = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/branch/$%7Bbranch_name%7D", + }, + ["gitlab.com"] = { + pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", + commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", + tree = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/tree/$%7Bbranch_name%7D?ref_type=heads", + }, + ["azure.com"] = { + pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + commit = "", + tree = "", + }, -- Allows a different telescope sorter. Defaults to 'fuzzy_with_index_bias'. The example below will use the native fzf -- sorter instead. By default, this function returns `nil`. telescope_sorter = function() From 9230d48420803e4ec2d58286ebff64e679df1341 Mon Sep 17 00:00:00 2001 From: Jordan Patterson Date: Fri, 19 Sep 2025 18:47:29 -0600 Subject: [PATCH 428/437] Snacks: Pass through the search pattern if there were no matches. --- lua/neogit/lib/finder.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 56cd96c2c..8f7c31b17 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -185,8 +185,8 @@ local function snacks_confirm(on_select, allow_multi, refocus_status) local picker_selected = picker:selected { fallback = true } if #picker_selected == 0 then - complete(nil) - picker:close() + local prompt = picker:filter().pattern + table.insert(selection, prompt) elseif #picker_selected > 1 then for _, item in ipairs(picker_selected) do table.insert(selection, item.text) From 0530096b25a774c9b9ab043c0326441ad3af060f Mon Sep 17 00:00:00 2001 From: Jordan Patterson Date: Sat, 20 Sep 2025 19:31:12 -0600 Subject: [PATCH 429/437] Return after recursively calling interactively popup. --- lua/neogit/popups/rebase/actions.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index a05fbf0a6..ad32852e3 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -75,6 +75,7 @@ function M.interactively(popup) elseif choice == "s" then popup.state.env.commit = nil M.interactively(popup) + return else return end From f2c0a81d8f5440ee3239659c27b8751aea112a19 Mon Sep 17 00:00:00 2001 From: Jordan Patterson Date: Sat, 20 Sep 2025 19:32:34 -0600 Subject: [PATCH 430/437] Allow passing --root to rebase.onto. --- lua/neogit/lib/git/rebase.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index b5890e7b6..ed8aa0015 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -63,6 +63,10 @@ function M.onto_branch(branch, args) end function M.onto(start, newbase, args) + if vim.tbl_contains(args, "--root") then + start = "" + end + local result = rebase_command(git.cli.rebase.onto.args(newbase, start).arg_list(args)) if result:failure() then notification.error("Rebasing failed. Resolve conflicts before continuing") From abc6d39138827b8d7a1da8868a07edc707596cda Mon Sep 17 00:00:00 2001 From: Jordan Patterson Date: Sat, 20 Sep 2025 19:54:02 -0600 Subject: [PATCH 431/437] Use parent commit for rebase.subset. --- lua/neogit/popups/rebase/actions.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index ad32852e3..4076dd0dc 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -137,10 +137,18 @@ function M.subset(popup) ) :open_async()[1] end + if not start then + return + end - if start then - git.rebase.onto(start, newbase, popup:get_arguments()) + local args = popup:get_arguments() + local parent = git.log.parent(start) + if parent then + start = start .. "^" + else + table.insert(args, "--root") end + git.rebase.onto(start, newbase, args) end function M.continue() From 704ed34e957c8702df994bdcc8404e134d1e8212 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Sep 2025 14:52:03 +0200 Subject: [PATCH 432/437] Do not repeat "dot" graph segment in details. --- lua/neogit/buffers/common.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 2fd54566c..625ffbc40 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -109,10 +109,18 @@ M.List = Component.new(function(props) return container.tag("List")(children) end) -local function build_graph(graph) +---@return Component[] +local function build_graph(graph, opts) + opts = opts or { remove_dots = false } + if type(graph) == "table" then return util.map(graph, function(g) - return text(g.text, { highlight = string.format("NeogitGraph%s", g.color) }) + local char = g.text + if opts.remove_dots and vim.tbl_contains({ "", "", "", "", "•" }, char) then + char = "" + end + + return text(char, { highlight = string.format("NeogitGraph%s", g.color) }) end) else return { text(graph, { highlight = "Include" }) } @@ -190,10 +198,9 @@ M.CommitEntry = Component.new(function(commit, remotes, args) commit.rel_date = " " .. commit.rel_date end - local graph = args.graph and build_graph(commit.graph) or { text("") } - local details if args.details then + local graph = args.graph and build_graph(commit.graph, { remove_dots = true }) or { text("") } details = col.padding_left(#commit.abbreviated_commit + 1) { row(util.merge(graph, { text(" "), @@ -249,6 +256,7 @@ M.CommitEntry = Component.new(function(commit, remotes, args) end local date = (config.values.log_date_format == nil and commit.rel_date or commit.log_date) + local graph = args.graph and build_graph(commit.graph) or { text("") } return col.tag("commit")({ row( From 462ccdeb26409849353d2859393e113a92a35a4a Mon Sep 17 00:00:00 2001 From: Patrick-Beuks Date: Wed, 24 Sep 2025 09:03:43 +0200 Subject: [PATCH 433/437] docs: update get_services documentation for changes from #1817 --- doc/neogit.txt | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 73f776473..5616a1d92 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -113,12 +113,28 @@ to Neovim users. -- Show relative date by default. When set, use `strftime` to display dates commit_date_format = nil, log_date_format = nil, - -- Used to generate URL's for branch popup action "pull request". + -- Used to generate URL's for branch popup action "pull request" or opening a commit. git_services = { - ["github.com"] = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", - ["bitbucket.org"] = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", - ["gitlab.com"] = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", - ["azure.com"] = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + ["github.com"] = { + pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", + commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", + tree = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D", + }, + ["bitbucket.org"] = { + pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", + commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", + tree = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/branch/$%7Bbranch_name%7D", + }, + ["gitlab.com"] = { + pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", + commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", + tree = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/tree/$%7Bbranch_name%7D?ref_type=heads", + }, + ["azure.com"] = { + pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + commit = "", + tree = "", + }, }, -- Allows a different telescope sorter. Defaults to 'fuzzy_with_index_bias'. The example below will use the native fzf -- sorter instead. By default, this function returns `nil`. From add70101fab5913cad33ab2f84f1a6ee092e7220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20F=2E=20Bortl=C3=ADk?= Date: Fri, 26 Sep 2025 00:09:53 +0200 Subject: [PATCH 434/437] docs: fix formatting and syntax of git_services --- README.md | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ebe7bb90c..157d09cfa 100644 --- a/README.md +++ b/README.md @@ -100,27 +100,28 @@ neogit.setup { -- Show message with spinning animation when a git command is running. process_spinner = false, -- Used to generate URL's for branch popup action "pull request", "open commit" and "open tree" - git_services = { - ["github.com"] = { - pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", - commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", - tree = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D", - }, - ["bitbucket.org"] = { - pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", - commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", - tree = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/branch/$%7Bbranch_name%7D", - }, - ["gitlab.com"] = { - pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", - commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", - tree = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/tree/$%7Bbranch_name%7D?ref_type=heads", - }, - ["azure.com"] = { - pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", - commit = "", - tree = "", - }, + git_services = { + ["github.com"] = { + pull_request = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/compare/$%7Bbranch_name%7D?expand=1", + commit = "https://github.com/$%7Bowner%7D/$%7Brepository%7D/commit/$%7Boid%7D", + tree = "https://${host}/$%7Bowner%7D/$%7Brepository%7D/tree/$%7Bbranch_name%7D", + }, + ["bitbucket.org"] = { + pull_request = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/pull-requests/new?source=${branch_name}&t=1", + commit = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/commits/$%7Boid%7D", + tree = "https://bitbucket.org/$%7Bowner%7D/$%7Brepository%7D/branch/$%7Bbranch_name%7D", + }, + ["gitlab.com"] = { + pull_request = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/merge_requests/new?merge_request[source_branch]=${branch_name}", + commit = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/commit/$%7Boid%7D", + tree = "https://gitlab.com/$%7Bowner%7D/$%7Brepository%7D/-/tree/$%7Bbranch_name%7D?ref_type=heads", + }, + ["azure.com"] = { + pull_request = "https://dev.azure.com/$%7Bowner%7D/_git/$%7Brepository%7D/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + commit = "", + tree = "", + }, + }, -- Allows a different telescope sorter. Defaults to 'fuzzy_with_index_bias'. The example below will use the native fzf -- sorter instead. By default, this function returns `nil`. telescope_sorter = function() From 90dcfa7b2bd5f3f35d1a7afe24dde9e2bc482cd4 Mon Sep 17 00:00:00 2001 From: tedius-git Date: Mon, 29 Sep 2025 09:43:51 -0600 Subject: [PATCH 435/437] docs: mini.pick was moved to nvim-mini repository --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 157d09cfa..5ab8cae3c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Here's an example spec for [Lazy](https://github.com/folke/lazy.nvim), but you'r -- Only one of these is needed. "nvim-telescope/telescope.nvim", -- optional "ibhagwan/fzf-lua", -- optional - "echasnovski/mini.pick", -- optional + "nvim-mini/mini.pick", -- optional "folke/snacks.nvim", -- optional }, } From e90c3d0791d352ce4cb2be1132a84c14142bb021 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 Oct 2025 09:59:06 +0200 Subject: [PATCH 436/437] Do not evaluate modeline strings for any buffer. Fixes: https://github.com/NeogitOrg/neogit/issues/1842 --- lua/neogit/lib/buffer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 562e7e5af..2c567d25d 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -712,6 +712,7 @@ function Buffer.create(config) 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("modifiable", config.modifiable or false) buffer:set_buffer_option("modified", config.modifiable or false) From 0f48491ae0046796841aaa97d439267982fe72db Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 Oct 2025 10:03:09 +0200 Subject: [PATCH 437/437] Fix spelling --- lua/neogit/lib/git/stash.lua | 2 +- lua/neogit/lib/graph/kitty.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 83a622825..d82793476 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -93,7 +93,7 @@ function M.register(meta) local idx, message = line:match("stash@{(%d*)}: (.*)") idx = tonumber(idx) - assert(idx, "indx cannot be nil") + assert(idx, "index cannot be nil") ---@class StashItem local item = { diff --git a/lua/neogit/lib/graph/kitty.lua b/lua/neogit/lib/graph/kitty.lua index 4936abcc2..d9a36f2fa 100644 --- a/lua/neogit/lib/graph/kitty.lua +++ b/lua/neogit/lib/graph/kitty.lua @@ -998,7 +998,7 @@ function M.build(commits, color) end end - -- now lets get the intervals between the stopped connetors + -- now lets get the intervals between the stopped connectors -- and other connectors of the same commit hash local intervals = {} for _, j in ipairs(stopped) do