1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-25 12:24:47 +02:00

Allow user to filter the files view to only show untracked files (#4226)

This handles the situation where the user's own config says to not show
untracked files, as is often the case with bare repos managing a user's
dotfiles.

- **PR Description**

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
This commit is contained in:
Jesse Duffield 2025-02-07 09:08:23 +11:00 committed by GitHub
commit a1838d33e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 100 additions and 4 deletions

View File

@ -32,13 +32,17 @@ func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config F
type GetStatusFileOptions struct { type GetStatusFileOptions struct {
NoRenames bool 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
} }
func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File { func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
// check if config wants us ignoring untracked files // check if config wants us ignoring untracked files
untrackedFilesSetting := self.config.GetShowUntrackedFiles() untrackedFilesSetting := self.config.GetShowUntrackedFiles()
if untrackedFilesSetting == "" { if opts.ForceShowUntracked || untrackedFilesSetting == "" {
untrackedFilesSetting = "all" untrackedFilesSetting = "all"
} }
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)

View File

@ -777,6 +777,13 @@ func (self *FilesController) handleStatusFilterPressed() error {
}, },
Key: 't', Key: 't',
}, },
{
Label: self.c.Tr.FilterUntrackedFiles,
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayUntracked)
},
Key: 'T',
},
{ {
Label: self.c.Tr.ResetFilter, Label: self.c.Tr.ResetFilter,
OnPress: func() error { OnPress: func() error {
@ -789,10 +796,20 @@ func (self *FilesController) handleStatusFilterPressed() error {
} }
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error { func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
previousFilter := self.context().GetFilter()
self.context().FileTreeViewModel.SetStatusFilter(filter) self.context().FileTreeViewModel.SetStatusFilter(filter)
// Whenever we switch between untracked and other filters, we need to refresh the files view
// because the untracked files filter applies when running `git status`.
if previousFilter != filter && (previousFilter == filetree.DisplayUntracked || filter == filetree.DisplayUntracked) {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
} else {
self.c.PostRefreshUpdate(self.context()) self.c.PostRefreshUpdate(self.context())
return nil return nil
} }
}
func (self *FilesController) edit(nodes []*filetree.FileNode) error { func (self *FilesController) edit(nodes []*filetree.FileNode) error {
return self.c.Helpers().Files.EditFiles(lo.FilterMap(nodes, return self.c.Helpers().Files.EditFiles(lo.FilterMap(nodes,

View File

@ -570,7 +570,9 @@ func (self *RefreshHelper) refreshStateFiles() error {
} }
files := self.c.Git().Loaders.FileLoader. files := self.c.Git().Loaders.FileLoader.
GetStatusFiles(git_commands.GetStatusFileOptions{}) GetStatusFiles(git_commands.GetStatusFileOptions{
ForceShowUntracked: self.c.Contexts().Files.ForceShowUntracked(),
})
conflictFileCount := 0 conflictFileCount := 0
for _, file := range files { for _, file := range files {

View File

@ -16,6 +16,7 @@ const (
DisplayStaged DisplayStaged
DisplayUnstaged DisplayUnstaged
DisplayTracked DisplayTracked
DisplayUntracked
// this shows files with merge conflicts // this shows files with merge conflicts
DisplayConflicted DisplayConflicted
) )
@ -40,6 +41,7 @@ type IFileTree interface {
FilterFiles(test func(*models.File) bool) []*models.File FilterFiles(test func(*models.File) bool) []*models.File
SetStatusFilter(filter FileTreeDisplayFilter) SetStatusFilter(filter FileTreeDisplayFilter)
ForceShowUntracked() bool
Get(index int) *FileNode Get(index int) *FileNode
GetFile(path string) *models.File GetFile(path string) *models.File
GetAllItems() []*FileNode GetAllItems() []*FileNode
@ -87,6 +89,8 @@ func (self *FileTree) getFilesForDisplay() []*models.File {
return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges }) return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges })
case DisplayTracked: case DisplayTracked:
return self.FilterFiles(func(file *models.File) bool { return file.Tracked }) return self.FilterFiles(func(file *models.File) bool { return file.Tracked })
case DisplayUntracked:
return self.FilterFiles(func(file *models.File) bool { return !file.Tracked })
case DisplayConflicted: case DisplayConflicted:
return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts }) return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
default: default:
@ -94,6 +98,10 @@ func (self *FileTree) getFilesForDisplay() []*models.File {
} }
} }
func (self *FileTree) ForceShowUntracked() bool {
return self.filter == DisplayUntracked
}
func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File { func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File {
return lo.Filter(self.getFiles(), func(file *models.File, _ int) bool { return test(file) }) return lo.Filter(self.getFiles(), func(file *models.File, _ int) bool { return test(file) })
} }

View File

@ -88,6 +88,7 @@ type TranslationSet struct {
FilterStagedFiles string FilterStagedFiles string
FilterUnstagedFiles string FilterUnstagedFiles string
FilterTrackedFiles string FilterTrackedFiles string
FilterUntrackedFiles string
ResetFilter string ResetFilter string
MergeConflictsTitle string MergeConflictsTitle string
Checkout string Checkout string
@ -1113,6 +1114,7 @@ func EnglishTranslationSet() *TranslationSet {
FilterStagedFiles: "Show only staged files", FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files", FilterUnstagedFiles: "Show only unstaged files",
FilterTrackedFiles: "Show only tracked files", FilterTrackedFiles: "Show only tracked files",
FilterUntrackedFiles: "Show only untracked files",
ResetFilter: "Reset filter", ResetFilter: "Reset filter",
NoChangedFiles: "No changed files", NoChangedFiles: "No changed files",
SoftReset: "Soft reset", SoftReset: "Soft reset",

View File

@ -0,0 +1,62 @@
package filter_and_search
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FilterByFileStatus = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Filtering to show untracked files in repo that hides them by default",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
// need to set untracked files to not be displayed in git config
shell.SetConfig("status.showUntrackedFiles", "no")
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(
Contains(`file-tracked`).IsSelected(),
).
Press(keys.Files.OpenStatusFilter).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Show only untracked files")).
Confirm()
}).
Lines(
Contains(`file-untracked`).IsSelected(),
).
Press(keys.Files.OpenStatusFilter).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Show only tracked files")).
Confirm()
}).
Lines(
Contains(`file-tracked`).IsSelected(),
).
Press(keys.Files.OpenStatusFilter).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Reset filter")).
Confirm()
}).
Lines(
Contains(`file-tracked`).IsSelected(),
)
},
})

View File

@ -186,6 +186,7 @@ var tests = []*components.IntegrationTest{
file.StageChildrenRangeSelect, file.StageChildrenRangeSelect,
file.StageDeletedRangeSelect, file.StageDeletedRangeSelect,
file.StageRangeSelect, file.StageRangeSelect,
filter_and_search.FilterByFileStatus,
filter_and_search.FilterCommitFiles, filter_and_search.FilterCommitFiles,
filter_and_search.FilterFiles, filter_and_search.FilterFiles,
filter_and_search.FilterFuzzy, filter_and_search.FilterFuzzy,