Skip to content
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: 9 additions & 0 deletions docs/Searching.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ You can filter the commits view to only show commits which contain changes to a
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

## Hiding merge commits

You can hide merge commits from the commits view to get a cleaner, linear history.

1) From within lazygit, press `<c-s>` to open the filtering menu
2) Select "Hide merge commits" to toggle this option on/off

**Note:** When hiding merge commits, the commit graph will be automatically disabled as merge commits are essential for proper graph visualization.
3 changes: 3 additions & 0 deletions pkg/commands/git_commands/commit_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type GetCommitsOptions struct {
RefToShowDivergenceFrom string
MainBranches *MainBranches
HashPool *utils.StringPool
// If true, exclude merge commits from the output
HideMerges bool
}

// GetCommits obtains the commits of the current branch
Expand Down Expand Up @@ -589,6 +591,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) *oscommands.CmdObj {
ArgIf(gitLogOrder != "default", "--"+gitLogOrder).
ArgIf(opts.All, "--all").
Arg("--oneline").
ArgIf(opts.HideMerges, "--no-merges").
Arg(prettyFormat).
Arg("--abbrev=40").
ArgIf(opts.FilterAuthor != "", "--author="+opts.FilterAuthor).
Expand Down
43 changes: 38 additions & 5 deletions pkg/commands/git_commands/commit_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,39 @@ func TestGetCommits(t *testing.T) {
expectedCommitOpts: []models.NewCommitOpts{},
expectedError: nil,
},
{
testName: "should hide merge commits when HideMerges is true",
logOrder: "topo-order",
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false, HideMerges: true},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--no-merges", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),

expectedCommitOpts: []models.NewCommitOpts{},
expectedError: nil,
},
{
testName: "should not hide merge commits when HideMerges is false",
logOrder: "topo-order",
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false, HideMerges: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),

expectedCommitOpts: []models.NewCommitOpts{},
expectedError: nil,
},
{
testName: "should hide merge commits when HideMerges is true with other options",
logOrder: "date-order",
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: &models.Branch{Name: "mybranch"}, IncludeRebaseCommits: false, HideMerges: true, All: true},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rev-list", "refs/heads/mybranch", "^mybranch@{u}"}, "", nil).
ExpectGitArgs([]string{"log", "HEAD", "--date-order", "--oneline", "--all", "--no-merges", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),

expectedCommitOpts: []models.NewCommitOpts{},
expectedError: nil,
},
{
testName: "should return commits if they are present",
logOrder: "topo-order",
Expand All @@ -72,12 +105,12 @@ func TestGetCommits(t *testing.T) {
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:+%H%x00%at%x00%aN%x00%ae%x00%P%x00%m%x00%D%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
// here it's seeing which of our commits are not on any of the main branches yet
ExpectGitArgs([]string{"rev-list", "HEAD", "^refs/remotes/origin/master", "^refs/remotes/origin/main"},
"0eea75e8c631fba6b58135697835d58ba4c18dbc\nb21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164\ne94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c\nd8084cd558925eb7c9c38afeed5725c21653ab90\n65f910ebd85283b5cce9bf67d03d3f1a9ea3813a\n", nil),
Expand Down
4 changes: 4 additions & 0 deletions pkg/gui/context/local_commits_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ func shouldShowGraph(c *ContextCommon) bool {
return false
}

if c.Modes().Filtering.GetHideMerges() {
return false
}

value := c.UserConfig().Git.Log.ShowGraph

switch value {
Expand Down
19 changes: 19 additions & 0 deletions pkg/gui/controllers/filtering_menu_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ func (self *FilteringMenuAction) Call() error {
Tooltip: tooltip,
})

menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.FilterHideMerges,
Tooltip: self.c.Tr.FilterHideMergesTooltip,
Widget: types.MakeMenuCheckBox(self.c.Modes().Filtering.GetHideMerges()),
OnPress: func() error {
currentValue := self.c.Modes().Filtering.GetHideMerges()
self.c.Modes().Filtering.SetHideMerges(!currentValue)

self.c.Refresh(types.RefreshOptions{
Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(),
Then: func() {
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().LocalCommits.HandleFocus(types.OnFocusOpts{})
},
})
return nil
},
})

if self.c.Modes().Filtering.Active() {
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.ExitFilterMode,
Expand Down
2 changes: 2 additions & 0 deletions pkg/gui/controllers/helpers/refresh_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
HideMerges: self.c.Modes().Filtering.GetHideMerges(),
},
)
if err != nil {
Expand Down Expand Up @@ -367,6 +368,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef(),
MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
HideMerges: self.c.Modes().Filtering.GetHideMerges(),
},
)
if err != nil {
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 @@ -43,6 +43,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom,
MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
HideMerges: self.c.Modes().Filtering.GetHideMerges(),
},
)
if err != nil {
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,10 +4,11 @@ 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
hideMerges bool // whether to hide merge commits
}

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

func (m *Filtering) Active() bool {
Expand All @@ -17,6 +18,7 @@ func (m *Filtering) Active() bool {
func (m *Filtering) Reset() {
m.path = ""
m.author = ""
m.hideMerges = false
}

func (m *Filtering) SetPath(path string) {
Expand All @@ -42,3 +44,11 @@ func (m *Filtering) SetSelectedCommitHash(hash string) {
func (m *Filtering) GetSelectedCommitHash() string {
return m.selectedCommitHash
}

func (m *Filtering) SetHideMerges(hideMerges bool) {
m.hideMerges = hideMerges
}

func (m *Filtering) GetHideMerges() bool {
return m.hideMerges
}
4 changes: 4 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,8 @@ type TranslationSet struct {
ExitFilterMode string
FilterPathOption string
FilterAuthorOption string
FilterHideMerges string
FilterHideMergesTooltip string
EnterFileName string
EnterAuthor string
FilteringMenuTitle string
Expand Down Expand Up @@ -1704,6 +1706,8 @@ func EnglishTranslationSet() *TranslationSet {
ExitFilterMode: "Stop filtering",
FilterPathOption: "Enter path to filter by",
FilterAuthorOption: "Enter author to filter by",
FilterHideMerges: "Hide merge commits",
FilterHideMergesTooltip: "Exclude merge commits from the list",
EnterFileName: "Enter path:",
EnterAuthor: "Enter author:",
FilteringMenuTitle: "Filtering",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@
"FilterBy": "フィルター条件",
"ExitFilterMode": "フィルタリングを停止",
"FilterPathOption": "フィルタリングするパスを入力",
"FilterHideMerges": "マージコミットを非表示",
"FilterHideMergesTooltip": "リストからマージコミットを除外",
"FilterAuthorOption": "フィルタリングする作者を入力",
"EnterFileName": "パスを入力:",
"EnterAuthor": "作者を入力:",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@
"GotoBottom": "맨 아래로 스크롤 ",
"ResetInParentheses": "(reset)",
"OpenFilteringMenu": "View filter-by-path options",
"FilterHideMerges": "병합 커밋 숨기기",
"FilterHideMergesTooltip": "목록에서 병합 커밋 제외",
"ExitFilterMode": "Stop filtering by path",
"MustExitFilterModePrompt": "Command not available in filtered mode. Exit filtered mode?",
"EnterRefName": "Ref 입력:",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@
"FilterBy": "Filter bij",
"ExitFilterMode": "Stop met filteren bij pad",
"FilterPathOption": "Vulin pad om op te filteren",
"FilterHideMerges": "Verberg merge commits",
"FilterHideMergesTooltip": "Sluit merge commits uit van de lijst",
"EnterFileName": "Vulin path:",
"FilteringMenuTitle": "Filteren",
"MustExitFilterModeTitle": "Command niet beschikbaar",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@
"FilterBy": "Filtruj przez",
"ExitFilterMode": "Zatrzymaj filtrowanie",
"FilterPathOption": "Wprowadź ścieżkę do filtrowania",
"FilterHideMerges": "Ukryj commity merge",
"FilterHideMergesTooltip": "Wyklucz commity merge z listy",
"FilterAuthorOption": "Wprowadź autora do filtrowania",
"EnterFileName": "Wprowadź ścieżkę:",
"EnterAuthor": "Wprowadź autora:",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@
"UpstreamGenericName": "upstream da branch selecionada",
"CreatingTag": "Criando etiqueta",
"ForceTag": "Forçar Etiqueta",
"FilterHideMerges": "Ocultar commits de merge",
"FilterHideMergesTooltip": "Excluir commits de merge da lista",
"GitFlowOptions": "Exibir opções do git-flow",
"Actions": {},
"Bisect": {},
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@
"FilterBy": "Фильтровать по",
"ExitFilterMode": "Прекратить фильтрацию по пути",
"FilterPathOption": "Введите путь для фильтрации",
"FilterHideMerges": "Скрыть merge коммиты",
"FilterHideMergesTooltip": "Исключить merge коммиты из списка",
"EnterFileName": "Введите путь:",
"FilteringMenuTitle": "Фильтрация",
"MustExitFilterModeTitle": "Команда недоступна",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@
"FilterBy": "过滤",
"ExitFilterMode": "停止按路径过滤",
"FilterPathOption": "输入要过滤的路径",
"FilterHideMerges": "隐藏合并提交",
"FilterHideMergesTooltip": "从列表中排除合并提交",
"FilterAuthorOption": "输入作者进行过滤",
"EnterFileName": "输入路径:",
"EnterAuthor": "输入作者:",
Expand Down
2 changes: 2 additions & 0 deletions pkg/i18n/translations/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@
"FilterBy": "篩選路徑",
"ExitFilterMode": "停止按路徑篩選",
"FilterPathOption": "輸入要依路徑篩選的路徑",
"FilterHideMerges": "隱藏合併提交",
"FilterHideMergesTooltip": "從列表中排除合併提交",
"EnterFileName": "輸入路徑:",
"FilteringMenuTitle": "篩選",
"MustExitFilterModeTitle": "命令不可用",
Expand Down
75 changes: 75 additions & 0 deletions pkg/integration/tests/filter_and_search/hide_merge_commits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package filter_and_search

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

var HideMergeCommits = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Test hiding merge commits functionality",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
// Create a repo with merge commits to test the functionality
shell.CreateFile("main.go", "package main\n\nfunc main() {\n\tprintln(\"Hello, World!\")\n}")
shell.EmptyCommit("Initial commit")

// Create a feature branch
shell.NewBranch("feature-branch")
shell.CreateFile("feature.go", "package main\n\nfunc feature() {\n\tprintln(\"Feature!\")\n}")
shell.EmptyCommit("Add feature")

// Switch back to master (default branch) and create another commit
shell.Checkout("master")
shell.CreateFile("utils.go", "package main\n\nfunc utils() {\n\tprintln(\"Utils!\")\n}")
shell.EmptyCommit("Add utils")

// Merge feature branch into master (this creates a merge commit)
shell.Merge("feature-branch")

// Create another commit after merge
shell.CreateFile("final.go", "package main\n\nfunc final() {\n\tprintln(\"Final!\")\n}")
shell.EmptyCommit("Final commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
// Verify that merge commit is initially visible
Content(Contains(`Merge branch 'feature-branch'`)).
Content(Contains(`Final commit`)).
Content(Contains(`Add utils`)).
Content(Contains(`Add feature`)).
Content(Contains(`Initial commit`)).
// Open filtering menu
Press(keys.Universal.FilteringMenu).
// Check that Hide merge commits option is available and toggle it
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Hide merge commits")).
Confirm()
}).
// Verify that merge commit is hidden
Content(DoesNotContain(`Merge branch 'feature-branch'`)).
Content(Contains(`Final commit`)).
Content(Contains(`Add utils`)).
Content(Contains(`Add feature`)).
Content(Contains(`Initial commit`)).
// Open filtering menu again to toggle off
Press(keys.Universal.FilteringMenu).
// Verify Hide merge commits is still selected and toggle it off
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Filtering")).
Select(Contains("Hide merge commits")).
Confirm()
}).
// Verify that merge commit is visible again
Content(Contains(`Merge branch 'feature-branch'`)).
Content(Contains(`Final commit`)).
Content(Contains(`Add utils`)).
Content(Contains(`Add feature`)).
Content(Contains(`Initial commit`))
},
})
1 change: 1 addition & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ var tests = []*components.IntegrationTest{
filter_and_search.FilterRemotes,
filter_and_search.FilterSearchHistory,
filter_and_search.FilterUpdatesWhenModelChanges,
filter_and_search.HideMergeCommits,
filter_and_search.NestedFilter,
filter_and_search.NestedFilterTransient,
filter_and_search.NewSearch,
Expand Down