Files
lazygit/pkg/gui/controllers/helpers/branch_stack.go
T
Stefan HallerandClaude Opus 5.5 fb1b6ed1c1 Don't offer to push or pull branches merged into a local main branch
When pushing master, lazygit offers to push other branches along with
it if they were merged into master locally and have unpushed commits.
Pulling master offers to update such branches in the same way. These
branches aren't part of a stack, so the menu only adds an extra step to
an ordinary push or pull.

This happens because a branch counts as stacked below the current one
if its tip is one of the current branch's commits that aren't merged
into a main branch yet. A commit's merged status only looks at the
remote branch of each main branch. So until master is pushed, the
commits that were merged into it locally count as unmerged. The same
goes for pushing or pulling a feature branch on top of such a master.

Treat the commits that a local main branch contains as merged too, so
that the search for stacked branches stops at a main branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:12:36 +01:00

78 lines
2.5 KiB
Go

package helpers
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/samber/lo"
)
// Returns the branches stacked below the given branch: 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 BranchesBelowInStack(
commits []*models.Commit,
branches []*models.Branch,
branch *models.Branch,
mainBranches []string,
) []*models.Branch {
branchesByTip := map[string][]*models.Branch{}
mainBranchTips := []string{}
for _, other := range branches {
if lo.Contains(mainBranches, other.Name) {
mainBranchTips = append(mainBranchTips, other.CommitHash)
} else if other.Name != branch.Name {
branchesByTip[other.CommitHash] = append(branchesByTip[other.CommitHash], other)
}
}
mergedLocally := reachableCommits(commits, mainBranchTips)
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 can be reached from
// any of the given tips by following their parents
func reachableCommits(commits []*models.Commit, tips []string) *set.Set[string] {
commitsByHash := map[string]*models.Commit{}
for _, commit := range commits {
if !commit.IsTODO() {
commitsByHash[commit.Hash()] = commit
}
}
reachable := set.New[string]()
pending := append([]string{}, tips...)
for len(pending) > 0 {
hash := pending[len(pending)-1]
pending = pending[:len(pending)-1]
commit, ok := commitsByHash[hash]
if !ok || reachable.Includes(hash) {
continue
}
reachable.Add(hash)
pending = append(pending, commit.Parents()...)
}
return reachable
}