diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go index 36ab8ef6742..9329aa0d5ab 100644 --- a/pkg/commands/git_commands/file_loader.go +++ b/pkg/commands/git_commands/file_loader.go @@ -30,20 +30,37 @@ func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config F } } +type ShowUntrackedMode int + +const ( + // Use the value from git config (show all if not set) + ShowUntrackedModeAuto ShowUntrackedMode = iota + // Show untracked files regardless of git config. Useful for users with bare + // repos for dotfiles who default to hiding untracked files, but want to + // occasionally see them to `git add` a new file. + ShowUntrackedModeOn + // Hide untracked files regardless of git config. + ShowUntrackedModeOff +) + type GetStatusFileOptions struct { - NoRenames bool - // If true, we'll show untracked files even if the user has set the config to hide them. - // This is useful for users with bare repos for dotfiles who default to hiding untracked files, - // but want to occasionally see them to `git add` a new file. - ForceShowUntracked bool + NoRenames bool + ShowUntracked ShowUntrackedMode } func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File { // check if config wants us ignoring untracked files untrackedFilesSetting := self.config.GetShowUntrackedFiles() - if opts.ForceShowUntracked || untrackedFilesSetting == "" { + switch opts.ShowUntracked { + case ShowUntrackedModeOn: untrackedFilesSetting = "all" + case ShowUntrackedModeOff: + untrackedFilesSetting = "no" + default: // ShowUntrackedModeAuto + if untrackedFilesSetting == "" { + untrackedFilesSetting = "all" + } } untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 126018da681..1b2477f3a5d 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -169,7 +169,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) { if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) { fileWg.Add(1) refresh("files", func() { - _ = self.refreshFilesAndSubmodules() + _ = self.refreshFilesAndSubmodules(options.TakeOverUntrackedFilesFromPreviousModel) fileWg.Done() }) } @@ -539,7 +539,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele self.refreshStatus() } -func (self *RefreshHelper) refreshFilesAndSubmodules() error { +func (self *RefreshHelper) refreshFilesAndSubmodules(takeOverUntrackedFilesFromPreviousModel bool) error { self.c.Mutexes().RefreshingFilesMutex.Lock() self.c.State().SetIsRefreshingFiles(true) defer func() { @@ -551,7 +551,7 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error { return err } - if err := self.refreshStateFiles(); err != nil { + if err := self.refreshStateFiles(takeOverUntrackedFilesFromPreviousModel); err != nil { return err } @@ -564,7 +564,7 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error { return nil } -func (self *RefreshHelper) refreshStateFiles() error { +func (self *RefreshHelper) refreshStateFiles(takeOverUntrackedFilesFromPreviousModel bool) error { fileTreeViewModel := self.c.Contexts().Files.FileTreeViewModel prevConflictFileCount := 0 @@ -599,11 +599,27 @@ func (self *RefreshHelper) refreshStateFiles() error { } } + var previousUntrackedFiles []*models.File + if takeOverUntrackedFilesFromPreviousModel { + previousUntrackedFiles = lo.Filter(self.c.Model().Files, + func(file *models.File, _ int) bool { return file.ShortStatus == "??" }) + } + + showUntracked := git_commands.ShowUntrackedModeAuto + if takeOverUntrackedFilesFromPreviousModel { + showUntracked = git_commands.ShowUntrackedModeOff + } else if self.c.Contexts().Files.ForceShowUntracked() { + showUntracked = git_commands.ShowUntrackedModeOn + } files := self.c.Git().Loaders.FileLoader. GetStatusFiles(git_commands.GetStatusFileOptions{ - ForceShowUntracked: self.c.Contexts().Files.ForceShowUntracked(), + ShowUntracked: showUntracked, }) + if takeOverUntrackedFilesFromPreviousModel { + files = append(files, previousUntrackedFiles...) + } + conflictFileCount := 0 for _, file := range files { if file.HasMergeConflicts { diff --git a/pkg/gui/controllers/staging_controller.go b/pkg/gui/controllers/staging_controller.go index f667dd212c3..3d85ea13ca2 100644 --- a/pkg/gui/controllers/staging_controller.go +++ b/pkg/gui/controllers/staging_controller.go @@ -226,11 +226,16 @@ func (self *StagingController) DiscardSelection() error { } func (self *StagingController) applySelectionAndRefresh(reverse bool) error { + isTracked := self.c.Contexts().Files.GetSelectedFile().Tracked + if err := self.applySelection(reverse); err != nil { return err } - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}}) + self.c.Refresh(types.RefreshOptions{ + Scope: []types.RefreshableView{types.FILES, types.STAGING}, + TakeOverUntrackedFilesFromPreviousModel: isTracked, + }) return nil } @@ -281,11 +286,16 @@ func (self *StagingController) applySelection(reverse bool) error { } func (self *StagingController) EditHunkAndRefresh() error { + isTracked := self.c.Contexts().Files.GetSelectedFile().Tracked + if err := self.editHunk(); err != nil { return err } - self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}}) + self.c.Refresh(types.RefreshOptions{ + Scope: []types.RefreshableView{types.FILES, types.STAGING}, + TakeOverUntrackedFilesFromPreviousModel: isTracked, + }) return nil } diff --git a/pkg/gui/types/refresh.go b/pkg/gui/types/refresh.go index 8092ee36ee9..4a5d5e4811b 100644 --- a/pkg/gui/types/refresh.go +++ b/pkg/gui/types/refresh.go @@ -44,4 +44,9 @@ type RefreshOptions struct { // keeps the selection index the same. Useful after checking out a detached // head, and selecting index 0. KeepBranchSelectionIndex bool + + // If true, call git status with --untracked-files=no to skip enumerating untracked files, and + // keep the ones we have in the model. Useful as a performance optimization when we know the + // untracked files can't have changed, e.g. after staging/unstaging hunks of tracked files. + TakeOverUntrackedFilesFromPreviousModel bool }