Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to filter by changes (-S flag) #4383

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/Searching.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,10 @@ You can filter the files view to only show staged/unstaged files by pressing `<c
You can filter the commits view to only show commits which contain changes to a given file path.

You can do this in a couple of ways:
1) Start lazygit with the -f flag e.g. `lazygit -f my/path`
2) From within lazygit, press `<c-s>` and then enter the path of the file you want to filter by

1. Start lazygit with the -f flag e.g. `lazygit -f my/path`
2. From within lazygit, press `<c-s>` and then enter the path of the file you want to filter by

## Filtering by changes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Filtering by changes
## Filtering by patch content (pickaxe search)


You can filter the commits view to only show commits that contain changes to a given string by pressing `<c-s>` and selecting `Enter changes to filter by`. This feature uses Git's `-S` flag.
2 changes: 2 additions & 0 deletions pkg/commands/git_commands/commit_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type GetCommitsOptions struct {
Limit bool
FilterPath string
FilterAuthor string
FilterChanges string
IncludeRebaseCommits bool
RefName string // e.g. "HEAD" or "my_branch"
RefForPushedStatus string // the ref to use for determining pushed/unpushed status
Expand Down Expand Up @@ -513,6 +514,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
Arg(prettyFormat).
Arg("--abbrev=40").
ArgIf(opts.FilterAuthor != "", "--author="+opts.FilterAuthor).
ArgIf(opts.FilterChanges != "", "-S"+opts.FilterChanges).
ArgIf(opts.Limit, "-300").
ArgIf(opts.FilterPath != "", "--follow").
Arg("--no-show-signature").
Expand Down
3 changes: 2 additions & 1 deletion pkg/commands/git_commands/reflog_commit_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewReflogCommitLoader(common *common.Common, cmd oscommands.ICmdObjBuilder)

// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit
// if none is passed (i.e. it's value is nil) then we get all the reflog commits
func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string, filterAuthor string) ([]*models.Commit, bool, error) {
func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string, filterAuthor string, filterChanges string) ([]*models.Commit, bool, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now has too many positional parameters: let's introduce a CommitFilters struct which contains Path, Author, and PickaxeString fields.

Seems Pickaxe is the official term (https://git-scm.com/book/en/v2/Git-Tools-Searching):

we can use the -S option (colloquially referred to as the Git “pickaxe” option) to tell Git to show us only those commits that changed the number of occurrences of that string.

commits := make([]*models.Commit, 0)

cmdArgs := NewGitCmd("log").
Expand All @@ -33,6 +33,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
Arg("--format=%h%x00%ct%x00%gs%x00%p").
ArgIf(filterAuthor != "", "--author="+filterAuthor).
ArgIf(filterPath != "", "--follow", "--", filterPath).
ArgIf(filterChanges != "", "-S", "filterChanges").
ToArgv()

cmdObj := self.cmd.New(cmdArgs).DontLog()
Expand Down
21 changes: 21 additions & 0 deletions pkg/gui/controllers/filtering_menu_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ func (self *FilteringMenuAction) Call() error {
Tooltip: tooltip,
})

menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.FilterChangesOption,
OnPress: func() error {
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.EnterChanges,
HandleConfirm: func(response string) error {
return self.setFilteringChanges(strings.TrimSpace(response))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't trim space now that we support filtering by patch content. Often you actually do care about trailing/leading space when doing that.

},
})

return nil
},
Tooltip: tooltip,
})

if self.c.Modes().Filtering.Active() {
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.ExitFilterMode,
Expand All @@ -112,6 +127,12 @@ func (self *FilteringMenuAction) setFilteringAuthor(author string) error {
return self.setFiltering()
}

func (self *FilteringMenuAction) setFilteringChanges(changes string) error {
self.c.Modes().Filtering.Reset()
self.c.Modes().Filtering.SetChanges(changes)
return self.setFiltering()
}

func (self *FilteringMenuAction) setFiltering() error {
self.c.Modes().Filtering.SetSelectedCommitHash(self.c.Contexts().LocalCommits.GetSelectedCommitHash())

Expand Down
21 changes: 19 additions & 2 deletions pkg/gui/controllers/helpers/mode_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ type ModeStatus struct {
Reset func() error
}

func (self *ModeHelper) getFilter() string {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return two strings: one for the type of thing we're filtering by, and one for the value. That removes the ambiguity

filtering := self.c.Modes().Filtering

if path := filtering.GetPath(); path != "" {
return path
}

if author := filtering.GetAuthor(); author != "" {
return author
}

if changes := filtering.GetChanges(); changes != "" {
return changes
}

return ""
}

func (self *ModeHelper) Statuses() []ModeStatus {
return []ModeStatus{
{
Expand All @@ -72,12 +90,11 @@ func (self *ModeHelper) Statuses() []ModeStatus {
{
IsActive: self.c.Modes().Filtering.Active,
Description: func() string {
filterContent := lo.Ternary(self.c.Modes().Filtering.GetPath() != "", self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor())
return self.withResetButton(
fmt.Sprintf(
"%s '%s'",
self.c.Tr.FilteringBy,
filterContent,
self.getFilter(),
),
style.FgRed,
)
Expand Down
12 changes: 7 additions & 5 deletions pkg/gui/controllers/helpers/refresh_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
Limit: self.c.Contexts().LocalCommits.GetLimitCommits(),
FilterPath: self.c.Modes().Filtering.GetPath(),
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
FilterChanges: self.c.Modes().Filtering.GetChanges(),
IncludeRebaseCommits: true,
RefName: self.refForLog(),
RefForPushedStatus: checkedOutBranchName,
Expand Down Expand Up @@ -356,6 +357,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
Limit: self.c.Contexts().SubCommits.GetLimitCommits(),
FilterPath: self.c.Modes().Filtering.GetPath(),
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
FilterChanges: self.c.Modes().Filtering.GetChanges(),
IncludeRebaseCommits: false,
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(),
Expand Down Expand Up @@ -456,7 +458,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
// which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here
var err error
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "", "")
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "", "", "")
if err != nil {
self.c.Log.Error(err)
}
Expand Down Expand Up @@ -623,9 +625,9 @@ func (self *RefreshHelper) refreshReflogCommits() error {
lastReflogCommit = model.ReflogCommits[0]
}

refresh := func(stateCommits *[]*models.Commit, filterPath string, filterAuthor string) error {
refresh := func(stateCommits *[]*models.Commit, filterPath string, filterAuthor string, filterChanges string) error {
commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader.
GetReflogCommits(lastReflogCommit, filterPath, filterAuthor)
GetReflogCommits(lastReflogCommit, filterPath, filterAuthor, filterChanges)
if err != nil {
return err
}
Expand All @@ -638,12 +640,12 @@ func (self *RefreshHelper) refreshReflogCommits() error {
return nil
}

if err := refresh(&model.ReflogCommits, "", ""); err != nil {
if err := refresh(&model.ReflogCommits, "", "", ""); err != nil {
return err
}

if self.c.Modes().Filtering.Active() {
if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor()); err != nil {
if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor(), self.c.Modes().Filtering.GetChanges()); err != nil {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even here I'd just go self.c.Modes().Filtering.GetFilters() and have that return a CommitFilters struct.

return err
}
} else {
Expand Down
1 change: 1 addition & 0 deletions pkg/gui/controllers/helpers/sub_commits_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
Limit: true,
FilterPath: self.c.Modes().Filtering.GetPath(),
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
FilterChanges: self.c.Modes().Filtering.GetChanges(),
IncludeRebaseCommits: false,
RefName: opts.Ref.FullRefName(),
RefForPushedStatus: opts.Ref.FullRefName(),
Expand Down
12 changes: 11 additions & 1 deletion pkg/gui/modes/filtering/filtering.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ type Filtering struct {
path string // the filename that gets passed to git log
author string // the author that gets passed to git log
selectedCommitHash string // the commit that was selected before we entered filtering mode
changes string // changes that geds passed to git log after the -S flag
}

func New(path string, author string) Filtering {
return Filtering{path: path, author: author}
}

func (m *Filtering) Active() bool {
return m.path != "" || m.author != ""
return m.path != "" || m.author != "" || m.changes != ""
}

func (m *Filtering) Reset() {
m.path = ""
m.author = ""
m.changes = ""
}

func (m *Filtering) SetPath(path string) {
Expand All @@ -31,10 +33,18 @@ func (m *Filtering) SetAuthor(author string) {
m.author = author
}

func (m *Filtering) SetChanges(changes string) {
m.changes = changes
}

func (m *Filtering) GetAuthor() string {
return m.author
}

func (m *Filtering) GetChanges() string {
return m.changes
}

func (m *Filtering) SetSelectedCommitHash(hash string) {
m.selectedCommitHash = hash
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,10 @@ type TranslationSet struct {
ExitFilterMode string
FilterPathOption string
FilterAuthorOption string
FilterChangesOption string
EnterFileName string
EnterAuthor string
EnterChanges string
FilteringMenuTitle string
WillCancelExistingFilterTooltip string
MustExitFilterModeTitle string
Expand Down Expand Up @@ -1637,8 +1639,10 @@ func EnglishTranslationSet() *TranslationSet {
ExitFilterMode: "Stop filtering",
FilterPathOption: "Enter path to filter by",
FilterAuthorOption: "Enter author to filter by",
FilterChangesOption: "Enter changes to filter by",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FilterChangesOption: "Enter changes to filter by",
FilterChangesOption: "Enter patch string to filter by (pickaxe search)",

EnterFileName: "Enter path:",
EnterAuthor: "Enter author:",
EnterChanges: "Enter changes:",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EnterChanges: "Enter changes:",
EnterChanges: "Enter string:",

FilteringMenuTitle: "Filtering",
WillCancelExistingFilterTooltip: "Note: this will cancel the existing filter",
MustExitFilterModeTitle: "Command not available",
Expand Down
62 changes: 62 additions & 0 deletions pkg/integration/tests/filter_by_changes/type_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package filter_by_changes

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var TypeChanges = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Filter commits by author using the typed in author",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
shell.CreateNCommits(4)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to explicitly add files and associated commits, just to make this more readable. At the moment the reader has to look up the definition of CreateNCommits to see the relationship between the commit messages and their corresponding file changes. The shell.CreateNCommits is really a convenience function for when you just need multiple commits, but in this case we specifically want to be filtering based on the contents of those commits.

},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().
Focus().
Press(keys.Universal.FilteringMenu)

t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Enter changes to filter by")).
Confirm()

t.ExpectPopup().Prompt().
Title(Equals("Enter changes:")).
Type("file01").
Confirm()

t.Views().Commits().
IsFocused().
Lines(
Contains("commit 01"),
)

t.Views().Information().Content(Contains("Filtering by 'file01'"))

t.Views().Status().
Focus().
Press(keys.Universal.FilteringMenu)

t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Enter changes to filter by")).
Confirm()

t.ExpectPopup().Prompt().
Title(Equals("Enter changes:")).
Type("file02").
Confirm()

t.Views().Commits().
IsFocused().
Lines(
Contains("commit 02"),
)

t.Views().Information().Content(Contains("Filtering by 'file02'"))
},
})
2 changes: 2 additions & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/integration/tests/file"
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_and_search"
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_author"
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_changes"
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_path"
"github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase"
"github.com/jesseduffield/lazygit/pkg/integration/tests/misc"
Expand Down Expand Up @@ -212,6 +213,7 @@ var tests = []*components.IntegrationTest{
filter_by_path.KeepSameCommitSelectedOnExit,
filter_by_path.SelectFile,
filter_by_path.TypeFile,
filter_by_changes.TypeChanges,
interactive_rebase.AdvancedInteractiveRebase,
interactive_rebase.AmendCommitWithConflict,
interactive_rebase.AmendFirstCommit,
Expand Down