diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index 3b818d83f..cff946a09 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -34,11 +34,15 @@ func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj { // StageFile stages a file func (self *WorkingTreeCommands) StageFile(path string) error { - return self.StageFiles([]string{path}) + return self.StageFiles([]string{path}, nil) } -func (self *WorkingTreeCommands) StageFiles(paths []string) error { - cmdArgs := NewGitCmd("add").Arg("--").Arg(paths...).ToArgv() +func (self *WorkingTreeCommands) StageFiles(paths []string, extraArgs []string) error { + cmdArgs := NewGitCmd("add"). + Arg(extraArgs...). + Arg("--"). + Arg(paths...). + ToArgv() return self.cmd.New(cmdArgs).Run() } diff --git a/pkg/commands/git_commands/working_tree_test.go b/pkg/commands/git_commands/working_tree_test.go index e56818c1b..0016e9fe8 100644 --- a/pkg/commands/git_commands/working_tree_test.go +++ b/pkg/commands/git_commands/working_tree_test.go @@ -30,7 +30,7 @@ func TestWorkingTreeStageFiles(t *testing.T) { instance := buildWorkingTreeCommands(commonDeps{runner: runner}) - assert.NoError(t, instance.StageFiles([]string{"test.txt", "test2.txt"})) + assert.NoError(t, instance.StageFiles([]string{"test.txt", "test2.txt"}, nil)) runner.CheckForMissingCalls() } diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index ede3a32c4..723c16681 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -421,13 +421,19 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e unstagedSelectedNodes := filterNodesHaveUnstagedChanges(selectedNodes) if len(unstagedSelectedNodes) > 0 { + var extraArgs []string + + if self.context().GetFilter() == filetree.DisplayTracked { + extraArgs = []string{"-u"} + } + self.c.LogAction(self.c.Tr.Actions.StageFile) if err := self.optimisticChange(unstagedSelectedNodes, self.optimisticStage); err != nil { return err } - if err := self.c.Git().WorkingTree.StageFiles(toPaths(unstagedSelectedNodes)); err != nil { + if err := self.c.Git().WorkingTree.StageFiles(toPaths(unstagedSelectedNodes), extraArgs); err != nil { return err } } else { diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index cac9310d1..e267f8360 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -565,7 +565,7 @@ func (self *RefreshHelper) refreshStateFiles() error { if len(pathsToStage) > 0 { self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles) - if err := self.c.Git().WorkingTree.StageFiles(pathsToStage); err != nil { + if err := self.c.Git().WorkingTree.StageFiles(pathsToStage, nil); err != nil { return err } } diff --git a/pkg/gui/filetree/file_tree.go b/pkg/gui/filetree/file_tree.go index bd201b7dd..fe18db0c0 100644 --- a/pkg/gui/filetree/file_tree.go +++ b/pkg/gui/filetree/file_tree.go @@ -88,9 +88,11 @@ func (self *FileTree) getFilesForDisplay() []*models.File { case DisplayUnstaged: return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges }) case DisplayTracked: - return self.FilterFiles(func(file *models.File) bool { return file.Tracked }) + // untracked but staged files are technically not tracked by git + // but including such files in the filtered mode helps see what files are getting committed + return self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges }) case DisplayUntracked: - return self.FilterFiles(func(file *models.File) bool { return !file.Tracked }) + return self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) }) case DisplayConflicted: return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts }) default: diff --git a/pkg/integration/tests/filter_and_search/filter_by_file_status.go b/pkg/integration/tests/filter_and_search/filter_by_file_status.go index f2335d28c..0c5f95e1d 100644 --- a/pkg/integration/tests/filter_and_search/filter_by_file_status.go +++ b/pkg/integration/tests/filter_and_search/filter_by_file_status.go @@ -21,12 +21,16 @@ var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{ shell.CreateFile("file-untracked", "bar") shell.UpdateFile("file-tracked", "baz") + + shell.CreateFile("file-staged-but-untracked", "qux") + shell.GitAdd("file-staged-but-untracked") }, Run: func(t *TestDriver, keys config.KeybindingConfig) { t.Views().Files(). Focus(). Lines( - Contains(`file-tracked`).IsSelected(), + Equals("A file-staged-but-untracked").IsSelected(), + Equals(" M file-tracked"), ). Press(keys.Files.OpenStatusFilter). Tap(func() { @@ -36,7 +40,7 @@ var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{ Confirm() }). Lines( - Contains(`file-untracked`).IsSelected(), + Equals("?? file-untracked").IsSelected(), ). Press(keys.Files.OpenStatusFilter). Tap(func() { @@ -46,7 +50,8 @@ var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{ Confirm() }). Lines( - Contains(`file-tracked`).IsSelected(), + Equals("A file-staged-but-untracked").IsSelected(), + Equals(" M file-tracked"), ). Press(keys.Files.OpenStatusFilter). Tap(func() { @@ -56,7 +61,8 @@ var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{ Confirm() }). Lines( - Contains(`file-tracked`).IsSelected(), + Equals("A file-staged-but-untracked").IsSelected(), + Equals(" M file-tracked"), ) }, }) diff --git a/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go b/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go new file mode 100644 index 000000000..aa9220b95 --- /dev/null +++ b/pkg/integration/tests/filter_and_search/staging_folder_stages_only_tracked_files_in_tracked_only_filter.go @@ -0,0 +1,56 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Staging entire folder in tracked only view, should stage only tracked files", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + }, + SetupRepo: func(shell *Shell) { + shell.CreateDir("test") + shell.CreateFileAndAdd("test/file-tracked", "foo") + + shell.Commit("first commit") + + shell.CreateFile("test/file-untracked", "bar") + shell.UpdateFile("test/file-tracked", "baz") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Lines( + Equals("▼ test").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("▼ test").IsSelected(), + Equals(" M file-tracked"), + ). + PressPrimaryAction(). + Press(keys.Files.OpenStatusFilter). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Filtering")). + Select(Contains("No filter")). + Confirm() + }). + Lines( + Equals("▼ test").IsSelected(), + Equals(" M file-tracked"), // 'M' is now in the left column, so file is staged + Equals(" ?? file-untracked"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 6ff5c2f07..c826faf7c 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -207,6 +207,7 @@ var tests = []*components.IntegrationTest{ filter_and_search.NestedFilter, filter_and_search.NestedFilterTransient, filter_and_search.NewSearch, + filter_and_search.StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter, filter_by_author.SelectAuthor, filter_by_author.TypeAuthor, filter_by_path.CliArg,