Add a log menu option to filter the commit graph by refs

Sometimes you want the graph limited to a handful of branches or tags
rather than just the checked-out branch or the entire --all graph.
Add a "Filter graph by refs" entry to the log menu that prompts for
space-separated ref names and passes them to git log as extra starting
points alongside the current ref.
This commit is contained in:
Radu Alexe 2026-07-22 23:12:57 +03:00
parent d8b07ee4f5
commit f13d7b0d2e
6 changed files with 83 additions and 1 deletions

View file

@ -64,6 +64,10 @@ type GetCommitsOptions struct {
RefForPushedStatus models.Ref // the ref to use for determining pushed/unpushed status
// determines if we show the whole git graph i.e. pass the '--all' flag
All bool
// If non-empty, these refs are added to the log command as additional
// starting points, so the graph shows them alongside RefName. Takes
// precedence over All.
FilterRefs []string
// If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string
MainBranches *MainBranches
@ -588,8 +592,9 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) *oscommands.CmdObj {
cmdArgs := NewGitCmd("log").
Arg(refSpec).
Arg(opts.FilterRefs...).
ArgIf(gitLogOrder != "default", "--"+gitLogOrder).
ArgIf(opts.All, "--all").
ArgIf(opts.All && len(opts.FilterRefs) == 0, "--all").
Arg("--oneline").
Arg(prettyFormat).
Arg("--abbrev=40").

View file

@ -149,6 +149,10 @@ type LocalCommitsViewModel struct {
// If this is true we'll use git log --all when fetching the commits.
showWholeGitGraph bool
// If non-empty, the git graph is restricted to these refs (plus HEAD)
// instead of showing just the current branch or the whole graph.
filterRefs []string
}
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
@ -243,6 +247,14 @@ func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool {
return self.showWholeGitGraph
}
func (self *LocalCommitsViewModel) SetFilterRefs(refs []string) {
self.filterRefs = refs
}
func (self *LocalCommitsViewModel) GetFilterRefs() []string {
return self.filterRefs
}
func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
return self.getModel()
}

View file

@ -707,6 +707,7 @@ type capturedCommitState struct {
selectionRange *localCommitSelectionRange
limitCommits bool
showWholeGitGraph bool
filterRefs []string
filterPath string
filterAuthor string
mainBranches *git_commands.MainBranches
@ -729,6 +730,7 @@ func (self *RefreshHelper) captureCommitsState(commitSelection types.CommitSelec
selectionRange: selectionRange,
limitCommits: self.c.Contexts().LocalCommits.GetLimitCommits(),
showWholeGitGraph: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
filterRefs: self.c.Contexts().LocalCommits.GetFilterRefs(),
filterPath: self.c.Modes().Filtering.GetPath(),
filterAuthor: self.c.Modes().Filtering.GetAuthor(),
mainBranches: self.c.Model().MainBranches,
@ -805,6 +807,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit(captured capturedCommitState,
RefName: refName,
RefForPushedStatus: checkedOutRef,
All: captured.showWholeGitGraph,
FilterRefs: captured.filterRefs,
MainBranches: captured.mainBranches,
HashPool: captured.hashPool,
},

View file

@ -233,6 +233,40 @@ func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Su
return FilterFunc(refNames, self.c.UserConfig().Gui.UseFuzzySearch())
}
// GetMultiRefsSuggestionsFunc is like GetRefsSuggestionsFunc, but for prompts
// accepting multiple space-separated refs: suggestions fuzzily match the last
// (possibly partial) ref in the input, and accepting a suggestion completes
// that ref while keeping the ones already typed.
func (self *SuggestionsHelper) GetMultiRefsSuggestionsFunc() func(string) []*types.Suggestion {
remoteBranchNames := self.getRemoteBranchNames("/")
localBranchNames := self.getBranchNames()
tagNames := self.getTagNames()
additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
return func(input string) []*types.Suggestion {
prefix := ""
lastToken := input
if idx := strings.LastIndex(input, " "); idx != -1 {
prefix = input[:idx+1]
lastToken = input[idx+1:]
}
matches := refNames
if lastToken != "" {
matches = utils.FilterStrings(lastToken, refNames, true)
}
return lo.Map(matches, func(match string, _ int) *types.Suggestion {
return &types.Suggestion{
Value: prefix + match,
Label: match,
}
})
}
}
func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
authors := lo.Map(lo.Values(self.c.Model().Authors), func(author *models.Author, _ int) string {
return author.Combined()

View file

@ -1270,6 +1270,28 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
})
},
},
{
Label: self.c.Tr.FilterGraphByRefs,
Tooltip: self.c.Tr.FilterGraphByRefsTooltip,
OnPress: func() error {
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.FilterGraphByRefsPrompt,
InitialContent: strings.Join(self.context().GetFilterRefs(), " "),
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetMultiRefsSuggestionsFunc(),
AllowEmptyInput: true,
HandleConfirm: func(response string) error {
self.context().SetFilterRefs(strings.Fields(response))
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
self.c.Refresh(
types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}},
)
return nil
})
},
})
return nil
},
},
{
Label: self.c.Tr.ShowGitGraph,
Tooltip: self.c.Tr.ShowGitGraphTooltip,

View file

@ -821,6 +821,9 @@ type TranslationSet struct {
SortOrderPrompt string
SortCommits string
SortCommitsTooltip string
FilterGraphByRefs string
FilterGraphByRefsTooltip string
FilterGraphByRefsPrompt string
CantChangeContextSizeError string
CantChangeRenameThresholdError string
OpenCommitInBrowser string
@ -1967,6 +1970,9 @@ func EnglishTranslationSet() *TranslationSet {
SortBasedOnReflog: "(based on reflog)",
SortCommits: "Commit sort order",
SortCommitsTooltip: "Change the sort order of the commits in the commit log.\n\nThe default can be changed in the config file with the key 'git.log.sortOrder'.",
FilterGraphByRefs: "Filter graph by refs",
FilterGraphByRefsTooltip: "Restrict the git graph in the commits panel to the given refs (branches, tags, remote branches) in addition to the current branch. Leave empty to reset.",
FilterGraphByRefsPrompt: "Refs to show in the graph (space-separated, empty to reset):",
CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
CantChangeRenameThresholdError: "Cannot change the rename similarity threshold while in patch building mode, because the custom patch can't cope with a rename turning into a delete and add underneath it.",
OpenCommitInBrowser: "Open commit in browser",