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:
commit
a1838d33e7
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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) })
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user