diff --git a/internal/builder/check_merge.go b/internal/builder/check_merge.go index b9423ab8..e656d4e9 100644 --- a/internal/builder/check_merge.go +++ b/internal/builder/check_merge.go @@ -35,9 +35,10 @@ type JudgedHead struct { Head, Trunk string } -// mergeIdentity is who a check's merge is made as: it is never pushed, so it names nobody's account. +// mergeIdentity is who a check's merge is made as: it is never pushed, so it names nobody's account. No hook of the +// clone's or the build machine's git configuration runs, and no setting there changes the merge. var mergeIdentity = []string{"-c", "user.name=mesh merge check", "-c", "user.email=merge-check@mesh.invalid", - "-c", "commit.gpgsign=false"} + "-c", "commit.gpgsign=false", "-c", "core.hooksPath=/dev/null", "-c", "merge.verifySignatures=false"} // mergeIntoTrunk makes, in the clone at tree, the merge of head into the head of origin/ as it stands in // that fresh clone: the trunk commit. A head that does not merge cleanly answers the files in conflict and leaves @@ -61,7 +62,7 @@ func mergeIntoTrunk(ctx context.Context, run Runner, tree, base, head string) (T } message := fmt.Sprintf("Check the merge of %s into %s at %s (made for a merge check, never pushed)", twelve(m.Head), base, twelve(m.Trunk)) - args := append(append([]string{}, mergeIdentity...), "merge", "--no-ff", "--no-edit", "--quiet", "-m", message, m.Head) + args := append(append([]string{}, mergeIdentity...), "merge", "--no-ff", "--no-edit", "--no-verify", "--quiet", "-m", message, m.Head) if _, err := run(ctx, tree, "git", args...); err != nil { out, _ := run(ctx, tree, "git", "diff", "--name-only", "--diff-filter=U") m.Conflicts = strings.Fields(out) diff --git a/internal/builder/check_merge_test.go b/internal/builder/check_merge_test.go index 4c6955a1..a35fe888 100644 --- a/internal/builder/check_merge_test.go +++ b/internal/builder/check_merge_test.go @@ -186,3 +186,27 @@ func gitOut(t *testing.T, dir string, args ...string) string { } return strings.TrimSpace(string(out)) } + +// No hook runs when a check makes its merge: not one in the clone, whatever the build machine's git configuration says. +func TestACheckMergeRunsNoHook(t *testing.T) { + repo, head, _ := aTrunkAndABranch(t, map[string]string{"a": "1"}, map[string]string{"b": "trunk"}, + map[string]string{"c": "head"}) + clone := filepath.Join(t.TempDir(), "clone") + if out, err := exec.Command("git", "clone", "--quiet", repo, clone).CombinedOutput(); err != nil { + t.Fatalf("%v\n%s", err, out) + } + ran := filepath.Join(t.TempDir(), "ran") + for _, hook := range []string{"pre-merge-commit", "commit-msg", "prepare-commit-msg", "post-merge"} { + if err := os.WriteFile(filepath.Join(clone, ".git", "hooks", hook), + []byte("#!/bin/sh\necho "+hook+" >> "+ran+"\n"), 0o755); err != nil { + t.Fatal(err) + } + } + m, err := mergeIntoTrunk(t.Context(), Command, clone, "main", head) + if err != nil || m.Merge == "" { + t.Fatalf("the merge was not made: %+v, %v", m, err) + } + if said, err := os.ReadFile(ran); err == nil { + t.Fatalf("a hook ran when the check made its merge: %s", said) + } +}