diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go index 8b79bd8cd49..e7c342fc995 100644 --- a/pkg/commands/git_commands/commit_loader.go +++ b/pkg/commands/git_commands/commit_loader.go @@ -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"). diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index a66c720c9ad..6267f194b68 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -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() } diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index cf097b67e54..b2909cc2cef 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -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, }, diff --git a/pkg/gui/controllers/helpers/suggestions_helper.go b/pkg/gui/controllers/helpers/suggestions_helper.go index 8784d82fcb3..4b318da5dd1 100644 --- a/pkg/gui/controllers/helpers/suggestions_helper.go +++ b/pkg/gui/controllers/helpers/suggestions_helper.go @@ -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() diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 708f8fc2835..4e6a7e66a22 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -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, diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 2e83fed9bde..742b9ebf709 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -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",