mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-11-14 00:33:35 -04:00
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>
78 lines
2.5 KiB
Go
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
|
|
}
|