Files
Brad Fitzpatrick 25877455e7 misc/git_hook/githook: don't flag large blobs already on the remote
The pre-push large-blob check diffed the pushed tree only against the
remote's old tree for the same ref. After rebasing a stale branch past
an unrelated large-file change on the default branch, that diff shows
the large file as changed even though the exact blob is already on the
remote via main, rejecting the push with a false positive.

Diff against every available base tree instead: the remote's old
commit for the ref plus the merge base with the remote's default
branch. Only flag a file that is a large addition relative to all
bases, so blobs the remote already has are not reported, while
genuinely new large files on the branch are still rejected.

Updates tailscale/corp#9863

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia0c98fc1f5ab67a2913f948aeff605c72641ada7
2026-08-10 12:00:07 +02:00

111 lines
3.4 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package githook
import (
"os"
"os/exec"
"strings"
"testing"
)
// testMaxSize is the large-blob threshold used by the tests, chosen
// small so test fixtures stay tiny.
const testMaxSize = 100
func git(t *testing.T, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Env = append(cmd.Environ(),
"GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@example.com",
"GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@example.com",
)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
return strings.TrimSpace(string(out))
}
func writeAndCommit(t *testing.T, name, contents, msg string) string {
t.Helper()
if err := os.WriteFile(name, []byte(contents), 0o644); err != nil {
t.Fatal(err)
}
git(t, "add", "-A")
git(t, "commit", "-q", "-m", msg)
return git(t, "rev-parse", "HEAD")
}
// setupRepo creates a scratch repo laying out the rebase scenario:
//
// c1 (initial) -- c2 (adds big.bin on main, origin/main)
// \
// f1 (old branch tip, as previously pushed)
//
// It returns the three commit hashes.
func setupRepo(t *testing.T) (c1, c2, f1 string) {
t.Chdir(t.TempDir())
git(t, "init", "-q", "-b", "main")
c1 = writeAndCommit(t, "small.txt", "hello", "initial")
git(t, "checkout", "-q", "-b", "feature")
f1 = writeAndCommit(t, "feature.txt", "feature work", "feature commit")
git(t, "checkout", "-q", "main")
c2 = writeAndCommit(t, "big.bin", strings.Repeat("x", 2*testMaxSize), "add big file on main")
git(t, "update-ref", "refs/remotes/origin/main", c2)
return c1, c2, f1
}
// TestCheckLargeBlobsRebasePastLargeFile verifies that pushing a
// branch rebased past an unrelated large-file change on main is not
// flagged: the large blob is already on the remote via main.
func TestCheckLargeBlobsRebasePastLargeFile(t *testing.T) {
t.Setenv(skipLargeFileCheckEnv, "")
_, _, f1 := setupRepo(t)
git(t, "checkout", "-q", "feature")
git(t, "rebase", "-q", "main")
f2 := git(t, "rev-parse", "HEAD")
err := checkLargeBlobs("origin", push{
localRef: "refs/heads/feature",
localSHA: f2,
remoteRef: "refs/heads/feature",
remoteSHA: f1,
}, testMaxSize)
if err != nil {
t.Errorf("checkLargeBlobs = %v; want nil (big.bin is already on origin/main)", err)
}
}
// TestCheckLargeBlobsNewLargeFile verifies that a genuinely new large
// blob on the branch is flagged, both for updates to an existing
// remote ref and for new refs.
func TestCheckLargeBlobsNewLargeFile(t *testing.T) {
t.Setenv(skipLargeFileCheckEnv, "")
_, _, f1 := setupRepo(t)
git(t, "checkout", "-q", "feature")
git(t, "rebase", "-q", "main")
f2 := writeAndCommit(t, "mine.bin", strings.Repeat("y", 2*testMaxSize), "add my own big file")
for _, remoteSHA := range []string{f1, zeroRef} {
err := checkLargeBlobs("origin", push{
localRef: "refs/heads/feature",
localSHA: f2,
remoteRef: "refs/heads/feature",
remoteSHA: remoteSHA,
}, testMaxSize)
if err == nil {
t.Errorf("remoteSHA=%s: checkLargeBlobs = nil; want error flagging mine.bin", remoteSHA)
} else if !strings.Contains(err.Error(), "mine.bin") {
t.Errorf("remoteSHA=%s: error %v does not mention mine.bin", remoteSHA, err)
} else if strings.Contains(err.Error(), "big.bin") {
t.Errorf("remoteSHA=%s: error %v should not mention big.bin", remoteSHA, err)
}
}
}