Let the caller of buildAheadBehindForEachRefArgs choose the refs

The function always asked about every ref under refs/heads. Sorting refs
by ancestry needs the ahead-behind values of a handful of named refs, and
for remote branches those live under refs/remotes, so take the patterns
as an argument. The bases can be commit hashes as well as ref names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-09-19 16:47:00 +02:00
co-authored by Claude Opus 5
parent 980a0cc65b
commit 36192a0495
3 changed files with 36 additions and 18 deletions
+9 -7
View File
@@ -74,23 +74,25 @@ func selectBehindForBranch(aheadBehinds []aheadBehind) int {
}).behind
}
// The output format is:
// Builds a for-each-ref command that reports, for each ref matched by one of
// refPatterns, how far it is ahead and behind each of the bases. A base is a
// ref name or a commit hash. The output format is:
//
// <refname>\x00<ahead> <behind>\x00<ahead> <behind>...\n
//
// with one ahead-behind field per base, in the same order as mainBranchRefs.
// with one ahead-behind field per base, in the same order as bases.
//
// Requires git >= 2.41 (when %(ahead-behind:...) was added).
func buildAheadBehindForEachRefArgs(mainBranchRefs []string) []string {
formatParts := make([]string, 0, 1+len(mainBranchRefs))
func buildAheadBehindForEachRefArgs(bases []string, refPatterns []string) []string {
formatParts := make([]string, 0, 1+len(bases))
formatParts = append(formatParts, "%(refname)")
for _, ref := range mainBranchRefs {
formatParts = append(formatParts, "%(ahead-behind:"+ref+")")
for _, base := range bases {
formatParts = append(formatParts, "%(ahead-behind:"+base+")")
}
format := strings.Join(formatParts, "%00")
return NewGitCmd("for-each-ref").
Arg("--format=" + format).
Arg("refs/heads").
Arg(refPatterns...).
ToArgv()
}
+26 -10
View File
@@ -197,15 +197,17 @@ func TestSelectBehindForBranch(t *testing.T) {
func TestBuildAheadBehindForEachRefArgs(t *testing.T) {
type scenario struct {
testName string
mainBranchRefs []string
expected []string
testName string
bases []string
refPatterns []string
expected []string
}
scenarios := []scenario{
{
testName: "single base",
mainBranchRefs: []string{"refs/heads/master"},
testName: "single base",
bases: []string{"refs/heads/master"},
refPatterns: []string{"refs/heads"},
expected: []string{
"git",
"for-each-ref",
@@ -214,8 +216,9 @@ func TestBuildAheadBehindForEachRefArgs(t *testing.T) {
},
},
{
testName: "two bases",
mainBranchRefs: []string{"refs/heads/master", "refs/remotes/origin/develop"},
testName: "two bases",
bases: []string{"refs/heads/master", "refs/remotes/origin/develop"},
refPatterns: []string{"refs/heads"},
expected: []string{
"git",
"for-each-ref",
@@ -224,8 +227,9 @@ func TestBuildAheadBehindForEachRefArgs(t *testing.T) {
},
},
{
testName: "four bases",
mainBranchRefs: []string{"refs/heads/a", "refs/heads/b", "refs/heads/c", "refs/heads/d"},
testName: "four bases",
bases: []string{"refs/heads/a", "refs/heads/b", "refs/heads/c", "refs/heads/d"},
refPatterns: []string{"refs/heads"},
expected: []string{
"git",
"for-each-ref",
@@ -233,11 +237,23 @@ func TestBuildAheadBehindForEachRefArgs(t *testing.T) {
"refs/heads",
},
},
{
testName: "commit hashes as bases, individual refs as patterns",
bases: []string{"1234567", "89abcde"},
refPatterns: []string{"refs/heads/a", "refs/remotes/origin/b"},
expected: []string{
"git",
"for-each-ref",
"--format=%(refname)%00%(ahead-behind:1234567)%00%(ahead-behind:89abcde)",
"refs/heads/a",
"refs/remotes/origin/b",
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
result := buildAheadBehindForEachRefArgs(s.mainBranchRefs)
result := buildAheadBehindForEachRefArgs(s.bases, s.refPatterns)
assert.Equal(t, s.expected, result)
})
}
+1 -1
View File
@@ -214,7 +214,7 @@ func (self *BranchLoader) getBehindBaseBranchValuesFast(
t := time.Now()
output, err := self.cmd.New(
buildAheadBehindForEachRefArgs(mainBranchRefs),
buildAheadBehindForEachRefArgs(mainBranchRefs, []string{"refs/heads"}),
).DontLog().RunWithOutput()
if err != nil {
return err