From 8cc49e3be539b5f5f146830e81b36f7ec67f1fdb Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 28 Jul 2025 14:02:57 +0200 Subject: [PATCH 1/2] Add test demonstrating the problem When filtering to show only tracked files, pressing `a` would also stage untracked files, which is confusing and undesired. --- ...ly_tracked_files_in_tracked_only_filter.go | 57 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 58 insertions(+) create mode 100644 pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go diff --git a/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go b/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go new file mode 100644 index 000000000..aa812c50e --- /dev/null +++ b/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go @@ -0,0 +1,57 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Staging all files in tracked only view should stage only tracked files", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file-tracked", "foo") + + shell.Commit("first commit") + + shell.CreateFile("file-untracked", "bar") + shell.UpdateFile("file-tracked", "baz") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Lines( + Equals("▼ /").IsSelected(), + Equals(" M file-tracked"), + Equals(" ?? file-untracked"), + ). + Press(keys.Files.OpenStatusFilter). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Filtering")). + Select(Contains("Show only tracked files")). + Confirm() + }). + Lines( + Equals(" M file-tracked"), + ). + Press(keys.Files.ToggleStagedAll). + Press(keys.Files.OpenStatusFilter). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Filtering")). + Select(Contains("No filter")). + Confirm() + }). + Lines( + Equals("▼ /").IsSelected(), + Equals(" M file-tracked"), // 'M' is now in the left column, so file is staged + /* EXPECTED: + Equals(" ?? file-untracked"), + ACTUAL: */ + Equals(" A file-untracked"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 32fab95ab..57d7a05d0 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -229,6 +229,7 @@ var tests = []*components.IntegrationTest{ filter_and_search.NestedFilter, filter_and_search.NestedFilterTransient, filter_and_search.NewSearch, + filter_and_search.StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter, filter_and_search.StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter, filter_by_author.SelectAuthor, filter_by_author.TypeAuthor, From 9c0e103b9035a5ee39f32ca0bd6878f2e7f00628 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 28 Jul 2025 13:55:47 +0200 Subject: [PATCH 2/2] Stage only tracked files when staging all in filter-by-tracked-files view Also, fix two other commands that stage all files under the hood: - when continuing a rebase after resolving conflicts, we auto-stage all files, but in this case we never want to include untracked files, regardless of the filter - likewise, pressing ctrl-f to find a base commit for fixup stages all files for convenience, but again, this should only stage files that are already tracked --- pkg/commands/git_commands/working_tree.go | 6 ++++-- pkg/gui/controllers/files_controller.go | 3 ++- pkg/gui/controllers/helpers/fixup_helper.go | 2 +- pkg/gui/controllers/helpers/merge_and_rebase_helper.go | 2 +- pkg/gui/controllers/helpers/working_tree_helper.go | 4 ++-- ..._all_stages_only_tracked_files_in_tracked_only_filter.go | 3 --- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index deb9e6a59..e4922a4f2 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -49,8 +49,10 @@ func (self *WorkingTreeCommands) StageFiles(paths []string, extraArgs []string) } // StageAll stages all files -func (self *WorkingTreeCommands) StageAll() error { - cmdArgs := NewGitCmd("add").Arg("-A").ToArgv() +func (self *WorkingTreeCommands) StageAll(onlyTrackedFiles bool) error { + cmdArgs := NewGitCmd("add"). + ArgIfElse(onlyTrackedFiles, "-u", "-A"). + ToArgv() return self.cmd.New(cmdArgs).Run() } diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 9f95c86f6..20567a510 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -634,7 +634,8 @@ func (self *FilesController) toggleStagedAllWithLock() error { return err } - if err := self.c.Git().WorkingTree.StageAll(); err != nil { + onlyTrackedFiles := self.context().GetFilter() == filetree.DisplayTracked + if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil { return err } } else { diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go index 37a0cf688..f44c23da8 100644 --- a/pkg/gui/controllers/helpers/fixup_helper.go +++ b/pkg/gui/controllers/helpers/fixup_helper.go @@ -133,7 +133,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error { Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning, HandleConfirm: func() error { if !hasStagedChanges { - if err := self.c.Git().WorkingTree.StageAll(); err != nil { + if err := self.c.Git().WorkingTree.StageAll(true); err != nil { return err } self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}}) diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 82bef8fd2..fbe59caa6 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -244,7 +244,7 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved, HandleConfirm: func() error { self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - if err := self.c.Git().WorkingTree.StageAll(); err != nil { + if err := self.c.Git().WorkingTree.StageAll(true); err != nil { return err } diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go index 33620acd6..e7bc74fa9 100644 --- a/pkg/gui/controllers/helpers/working_tree_helper.go +++ b/pkg/gui/controllers/helpers/working_tree_helper.go @@ -207,7 +207,7 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro Prompt: self.c.Tr.NoFilesStagedPrompt, HandleConfirm: func() error { self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - if err := self.c.Git().WorkingTree.StageAll(); err != nil { + if err := self.c.Git().WorkingTree.StageAll(false); err != nil { return err } self.syncRefresh() @@ -228,7 +228,7 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error { noStagedFiles := !self.AnyStagedFiles() if noStagedFiles && self.c.UserConfig().Gui.SkipNoStagedFilesWarning { self.c.LogAction(self.c.Tr.Actions.StageAllFiles) - err := self.c.Git().WorkingTree.StageAll() + err := self.c.Git().WorkingTree.StageAll(false) if err != nil { return err } diff --git a/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go b/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go index aa812c50e..004cb5eb7 100644 --- a/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go +++ b/pkg/integration/tests/filter_and_search/stage_all_stages_only_tracked_files_in_tracked_only_filter.go @@ -48,10 +48,7 @@ var StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter = NewIntegrationTest(NewIn Lines( Equals("▼ /").IsSelected(), Equals(" M file-tracked"), // 'M' is now in the left column, so file is staged - /* EXPECTED: Equals(" ?? file-untracked"), - ACTUAL: */ - Equals(" A file-untracked"), ) }, })