Files
lazygit/pkg/gui/branchstack/branchstack.go
T
Stefan HallerandClaude Opus 5.5 633bedf4fd Pass only the branch name to BranchesBelow
The commits panel will need to call it. All it knows is the name of the
checked-out branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:43:36 +01:00

93 lines
3.2 KiB
Go

package branchstack
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/samber/lo"
)
// Returns the branches stacked below the branch with the given name: those
// whose tip is one of the given commits of the branch that hasn't been
// merged into a main branch yet. The branch itself and the main branches
// are left out. The result is in the order in which the tips appear in
// commits, so the branch closest to the given one comes first.
//
// A commit counts as merged if the remote branch of a main branch contains it
// (this is what the commit's status says), but also if the local main branch
// contains it. So if a branch was merged into the local main branch, it isn't
// stacked below a branch on top of that main branch, and if the given branch
// is itself a main branch, nothing is stacked below it.
//
// Only the commits passed in are looked at, so a branch whose tip is further
// down the history than the loaded commits is not found.
func BranchesBelow(
commits []*models.Commit,
branches []*models.Branch,
branchName string,
mainBranches []string,
) []*models.Branch {
branchesByTip := map[string][]*models.Branch{}
for _, other := range branches {
if !lo.Contains(mainBranches, other.Name) && other.Name != branchName {
branchesByTip[other.CommitHash] = append(branchesByTip[other.CommitHash], other)
}
}
mergedLocally := CommitsMergedIntoLocalMainBranch(commits, branches, mainBranches)
result := []*models.Branch{}
for _, commit := range commits {
if commit.IsTODO() || commit.Status == models.StatusMerged || mergedLocally.Includes(commit.Hash()) {
continue
}
result = append(result, branchesByTip[commit.Hash()]...)
}
return result
}
// Returns the hashes of those of the given commits that a local main branch
// contains, but whose status doesn't say that they are merged. The status
// looks at the remote branch of a main branch if it has one, so these are
// commits that were merged into a local main branch but not pushed yet.
func CommitsMergedIntoLocalMainBranch(
commits []*models.Commit,
branches []*models.Branch,
mainBranches []string,
) *set.Set[string] {
result := set.New[string]()
mainBranchTips := lo.FilterMap(branches, func(branch *models.Branch, _ int) (string, bool) {
return branch.CommitHash, lo.Contains(mainBranches, branch.Name)
})
if len(mainBranchTips) == 0 {
return result
}
// The parents of a merged commit are merged too, so the walk never needs
// to go past a merged commit. Usually the tip of a local main branch is
// merged itself, so there is nothing to walk; this keeps it cheap enough to
// do on every render of the commits list.
unmergedCommitsByHash := map[string]*models.Commit{}
for _, commit := range commits {
if !commit.IsTODO() && commit.Status != models.StatusMerged {
unmergedCommitsByHash[commit.Hash()] = commit
}
}
pending := mainBranchTips
for len(pending) > 0 {
hash := pending[len(pending)-1]
pending = pending[:len(pending)-1]
commit, ok := unmergedCommitsByHash[hash]
if !ok || result.Includes(hash) {
continue
}
result.Add(hash)
pending = append(pending, commit.Parents()...)
}
return result
}