aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/vcs/git_test.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2026-02-13 14:56:42 +0100
committerAleksandr Nogikh <nogikh@google.com>2026-02-16 13:38:59 +0000
commit9a9eeb63872d625a7653bbafdf3fb01fb717bb8d (patch)
tree8962766a46552633b699ed109e1d4c4bc27a5f18 /pkg/vcs/git_test.go
parent1141f03b552018b61c969c59b55606a923421c9e (diff)
pkg/vcs: consider merge commits in base commit detection
Merge commits are important in two contexts: 1) They may bring together blobs created in different commits. 2) They may create new blob hashes when file changes are merged. 3) They replace blob hashes we were looking for. A particularly unpleasant case for the previous approach is when a blob hash disappears after a merge and tips of the branches are no longer to trust as the files of interest have also been replaced by the merges. Add a test that sets up this complicated setup and change the logic to make the test pass: 1) Add a -m flag to consider merge commits. 2) Increase -n as there are lots of merge commits in the Linux kernel. 3) Consider all parents of merge commits. Fixes #6777.
Diffstat (limited to 'pkg/vcs/git_test.go')
-rw-r--r--pkg/vcs/git_test.go73
1 files changed, 73 insertions, 0 deletions
diff --git a/pkg/vcs/git_test.go b/pkg/vcs/git_test.go
index 35a3df696..f2fd9e059 100644
--- a/pkg/vcs/git_test.go
+++ b/pkg/vcs/git_test.go
@@ -4,6 +4,8 @@
package vcs
import (
+ "fmt"
+ "os/exec"
"reflect"
"sort"
"testing"
@@ -598,3 +600,74 @@ index fa49b07..01c887f 100644
require.Nil(t, base)
})
}
+
+func TestBaseForDiffMerge(t *testing.T) {
+ // This is a quite convoluted setup that somewhat resembles the
+ // situations observed in the Linux kernel.
+
+ repo := MakeTestRepo(t, t.TempDir())
+ repo.Git("checkout", "-b", "master")
+ repo.CommitChangeset("init", FileContent{"readme.txt", "readme"})
+
+ repo.Git("checkout", "-b", "branchA")
+ repo.CommitChangeset("c1", FileContent{"a.txt", "A"})
+
+ repo.Git("checkout", "master")
+ repo.Git("checkout", "-b", "branchB")
+ repo.CommitChangeset("c2", FileContent{"b.txt", "B"})
+
+ // Merge branchB into branchA. Resolve conflict to "Merged".
+ repo.Git("checkout", "branchA")
+ repo.Git("merge", "branchB")
+ commitM, err := repo.repo.Commit(HEAD)
+ require.NoError(t, err)
+
+ // Prepare a merge conflict of master with branchA and branchB.
+ repo.Git("checkout", "master")
+ repo.CommitChangeset("c3",
+ FileContent{"a.txt", "A2"}, FileContent{"b.txt", "B2"})
+
+ // Merge master into branchA, resolve the conflict.
+ repo.Git("checkout", "branchA")
+ if err := exec.Command("git", "-C", repo.Dir, "merge", "master").Run(); err == nil {
+ t.Fatalf("conflict expected during merge -> branchA")
+ }
+ repo.CommitChangeset("merge master->branchA",
+ FileContent{"a.txt", "Merged"}, FileContent{"b.txt", "Merged"})
+ // Further bury the changes.
+ repo.CommitChangeset("unrelated", FileContent{"c.txt", "C"})
+
+ // Merge master into branchB, resolve the conflict.
+ repo.Git("checkout", "branchB")
+ if err := exec.Command("git", "-C", repo.Dir, "merge", "master").Run(); err == nil {
+ t.Fatalf("conflict expected during merge -> branchB")
+ }
+ repo.CommitChangeset("merge master->branchB",
+ FileContent{"a.txt", "MergedB"}, FileContent{"b.txt", "MergedB"})
+ // Further bury the changes.
+ repo.CommitChangeset("unrelated", FileContent{"d.txt", "D"})
+
+ hashes, err := repo.repo.fileHashes(commitM.Hash, []string{"a.txt", "b.txt"})
+ require.NoError(t, err)
+
+ diff := []byte(fmt.Sprintf(`diff --git a/a.txt b/a.txt
+index %s..123456 100644
+--- a/a.txt
++++ b/a.txt
+@@ -1 +1 @@
+-Merged
++WorkDir
+
+diff --git a/b.txt b/b.txt
+index %s..123456 100644
+--- a/b.txt
++++ b/b.txt
+@@ -1 +1 @@
+-Merged
++WorkDir
+`, hashes["a.txt"], hashes["b.txt"]))
+ bases, err := repo.repo.BaseForDiff(diff, &debugtracer.TestTracer{T: t})
+ require.NoError(t, err)
+ require.Len(t, bases, 1)
+ assert.Equal(t, commitM.Hash, bases[0].Hash)
+}