Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
pkg-proxy/internal/gitcache/mirror_test.go
Simon Chrzanowski 0efe54fd73 Let the submodule test add a ':' path on Windows
Git for Windows refuses it with core.protectNTFS on.
2026-11-08 19:11:36 +01:00

415 lines
16 KiB
Go

package gitcache
import (
"context"
"errors"
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"testing"
"time"
)
func TestMirrorClonesRepository(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "", true)
if err != nil {
t.Fatalf("Mirror: %v", err)
}
if got.Cached || got.Bytes <= 0 {
t.Errorf("first mirror = %+v, want fetched bytes", got)
}
if !strings.Contains(tc.cachedRefs(t), "refs/tags/v1") {
t.Errorf("cached refs %q lack the tag", tc.cachedRefs(t))
}
got, err = tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "", true)
if err != nil || !got.Cached {
t.Errorf("second mirror = %+v, %v, want cached", got, err)
}
}
func TestMirrorFetchesCommitOnNoBranch(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
runGit(t, upstream.bare(), "config", "uploadpack.allowAnySHA1InWant", "true")
sha := upstream.pushPullRequest(work, 1, nil)
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, true)
if err != nil {
t.Fatalf("Mirror: %v", err)
}
if got.Cached {
t.Errorf("mirror = %+v, want fetched", got)
}
if refs := tc.wantedRefs(t); len(refs) != 1 {
t.Errorf("wanted refs %v, want one", refs)
}
sessions := upstream.gitSessions.Load()
got, err = tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, true)
if err != nil || !got.Cached {
t.Errorf("second mirror = %+v, %v, want cached", got, err)
}
if n := upstream.gitSessions.Load() - sessions; n != 0 {
t.Errorf("second mirror fetched %d times, want none", n)
}
}
func TestMirrorFindsCommitOnABranch(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
sha := upstream.commit(work, "second")
runGit(t, work, "push", "--quiet", "origin", "main")
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, false)
if err != nil || got.Cached {
t.Errorf("mirror = %+v, %v, want fetched", got, err)
}
}
func TestMirrorDoesNotFetchByIDWhenTurnedOff(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
runGit(t, upstream.bare(), "config", "uploadpack.allowAnySHA1InWant", "true")
sha := upstream.pushPullRequest(work, 1, nil)
tc := newTestCache(t, upstream.server.URL, nil)
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, false)
if !errors.Is(err, ErrNotFound) {
t.Errorf("mirror of a commit on no branch with fetching by ID off = %v, want ErrNotFound", err)
}
if refs := tc.wantedRefs(t); len(refs) != 0 {
t.Errorf("wanted refs %v, want none", refs)
}
}
func TestMirrorReportsMissingCommit(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", strings.Repeat("ab", 20), true)
if !errors.Is(err, ErrNotFound) {
t.Errorf("mirror of a commit the upstream lacks = %v, want ErrNotFound", err)
}
}
func TestMirrorRefusesInvalidCommit(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "main", true); err == nil {
t.Error("mirrored a branch name as a commit")
}
}
func TestMirrorCountsRefreshThatFetched(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "", true); err != nil {
t.Fatalf("Mirror: %v", err)
}
upstream.commit(work, "second")
runGit(t, work, "push", "--quiet", "origin", "main")
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "", true)
if err != nil {
t.Fatalf("Mirror: %v", err)
}
if got.Cached || got.Bytes <= 0 {
t.Errorf("mirror after a push = %+v, want fetched bytes", got)
}
}
// TestMirrorRenewsWantedCommit checks mirroring a commit on no branch again
// makes its 7 days count from that run, not from the first fetch.
func TestMirrorRenewsWantedCommit(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
runGit(t, upstream.bare(), "config", "uploadpack.allowAnySHA1InWant", "true")
sha := upstream.pushPullRequest(work, 1, nil)
tc := newTestCache(t, upstream.server.URL, nil)
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, true); err != nil {
t.Fatalf("Mirror: %v", err)
}
cached := filepath.Join(tc.dir, "up", "repo.git")
for _, ref := range tc.wantedRefs(t) {
runGit(t, cached, "update-ref", "-d", ref)
}
old := wantedRef(sha, time.Now().Add(-wantedMaxAge+time.Hour))
runGit(t, cached, "update-ref", old, sha)
start := time.Now().Add(-time.Second)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, true)
if err != nil || !got.Cached {
t.Fatalf("second mirror = %+v, %v, want cached", got, err)
}
if !slices.ContainsFunc(tc.wantedRefs(t), func(ref string) bool {
return ref != old && wantedRefTime(t, ref, sha).After(start)
}) {
t.Errorf("wanted refs %v, want one written by the second mirror", tc.wantedRefs(t))
}
}
func TestMirrorDoesNotPinCommitOnABranch(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
sha := upstream.commit(work, "second")
runGit(t, work, "push", "--quiet", "origin", "main")
tc := newTestCache(t, upstream.server.URL, nil)
for range 2 {
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", sha, true); err != nil {
t.Fatalf("Mirror: %v", err)
}
}
if refs := tc.wantedRefs(t); len(refs) != 0 {
t.Errorf("wanted refs %v, want none for a commit on a branch", refs)
}
}
func wantedRefTime(t *testing.T, ref, oid string) time.Time {
t.Helper()
created, got, _ := strings.Cut(strings.TrimPrefix(ref, wantedPrefix), "/")
unix, err := strconv.ParseInt(created, 10, 64)
if err != nil || got != oid {
return time.Time{}
}
return time.Unix(unix, 0)
}
// TestMirrorReportsRefreshTooLarge checks a commit that is cached still
// fails to mirror when the refresh doesn't fit in max_repo_size, since the
// repository is dropped once released.
func TestMirrorReportsRefreshTooLarge(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
runGit(t, upstream.bare(), "config", "uploadpack.allowAnySHA1InWant", "true")
pinned := upstream.pushPullRequest(work, 1, randomBytes(100<<10))
base := repoSize(t, upstream, "repo")
tc := newTestCache(t, upstream.server.URL, func(o *Options) { o.MaxRepoSize = base + 150<<10 })
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", pinned, true); err != nil {
t.Fatalf("Mirror: %v", err)
}
if err := os.WriteFile(filepath.Join(work, "grown.bin"), randomBytes(80<<10), 0o600); err != nil {
t.Fatal(err)
}
runGit(t, work, "add", "grown.bin")
upstream.commit(work, "grown")
runGit(t, work, "push", "--quiet", "origin", "main")
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", pinned, true)
if !errors.Is(err, ErrTooLarge) {
t.Errorf("mirror with a refresh past max_repo_size = %v, want ErrTooLarge", err)
}
}
func TestMirrorResolvesTag(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
tagged := runGit(t, work, "rev-parse", "v1^{commit}")
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "v1", true)
if err != nil {
t.Fatalf("Mirror: %v", err)
}
if got.Commit != tagged {
t.Errorf("commit %q, want %s the tag points at", got.Commit, tagged)
}
}
func TestMirrorReportsMissingTag(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
for _, version := range []string{"v9", "main"} {
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", version, true)
if !errors.Is(err, ErrNotFound) {
t.Errorf("mirror at %q = %v, want ErrNotFound", version, err)
}
}
}
func TestMirrorRefusesInvalidTag(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
for _, version := range []string{"a..b", "v1^{tree}", "v1~1", "v 1", "v1:x"} {
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", version, true)
if err == nil || errors.Is(err, ErrNotFound) {
t.Errorf("mirror at %q = %v, want an invalid version error", version, err)
}
}
}
func TestMirrorListsSubmodules(t *testing.T) {
upstream := newFakeUpstream(t)
libWork := upstream.newRepo("lib")
libCommit := runGit(t, libWork, "rev-parse", "HEAD")
work := upstream.newRepo("repo")
gitmodules := "[submodule \"vendor/lib\"]\n\tpath = vendor/lib\n\turl = ../lib.git\n" +
"[submodule \"other\"]\n\tpath = third_party/other\n\turl = https://example.com/other.git\n" +
"[submodule \"colon\"]\n\tpath = :colon\n\turl = ../lib.git\n"
if err := os.WriteFile(filepath.Join(work, ".gitmodules"), []byte(gitmodules), 0o600); err != nil {
t.Fatal(err)
}
otherCommit := strings.Repeat("ab", 20)
runGit(t, work, "add", ".gitmodules")
runGit(t, work, "update-index", "--add", "--cacheinfo", "160000,"+libCommit+",vendor/lib")
runGit(t, work, "update-index", "--add", "--cacheinfo", "160000,"+otherCommit+",third_party/other")
// Git for Windows refuses ':' in paths unless NTFS protection is off.
runGit(t, work, "-c", "core.protectNTFS=false", "update-index", "--add", "--cacheinfo", "160000,"+libCommit+",:colon")
runGit(t, work, "commit", "--quiet", "-m", "submodules")
runGit(t, work, "push", "--quiet", "origin", "main")
commit := runGit(t, work, "rev-parse", "HEAD")
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", commit, true)
if err != nil {
t.Fatalf("Mirror: %v", err)
}
want := []Submodule{
{Path: ":colon", URL: "../lib.git", Commit: libCommit},
{Path: "third_party/other", URL: "https://example.com/other.git", Commit: otherCommit},
{Path: "vendor/lib", URL: "../lib.git", Commit: libCommit},
}
if !slices.Equal(got.Submodules, want) {
t.Errorf("submodules %+v, want %+v", got.Submodules, want)
}
// Without a version, nothing names a commit to read submodules from.
got, err = tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "", true)
if err != nil || len(got.Submodules) != 0 {
t.Errorf("unversioned mirror = %+v, %v, want no submodules", got, err)
}
}
// TestMirrorWithEmptyGitmodules checks a .gitmodules with no path or url,
// for which git config --get-regexp finds nothing, lists no submodules.
func TestMirrorWithEmptyGitmodules(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
for i, content := range []string{"", "[submodule \"x\"]\n\tbranch = main\n"} {
if err := os.WriteFile(filepath.Join(work, ".gitmodules"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
runGit(t, work, "add", ".gitmodules")
runGit(t, work, "commit", "--quiet", "--allow-empty", "-m", "gitmodules "+strconv.Itoa(i))
}
runGit(t, work, "push", "--quiet", "origin", "main")
tc := newTestCache(t, upstream.server.URL, nil)
for _, rev := range []string{"HEAD~1", "HEAD"} {
commit := runGit(t, work, "rev-parse", rev)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", commit, true)
if err != nil || len(got.Submodules) != 0 {
t.Errorf("mirror of %s = %+v, %v, want no submodules", rev, got, err)
}
}
}
// TestSubmodulesReportsCancel checks a cancelled context is reported, not
// read as a commit without .gitmodules.
func TestSubmodulesReportsCancel(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
commit := runGit(t, work, "rev-parse", "HEAD")
ctx, cancel := context.WithCancel(context.Background())
cancel()
tc := newTestCache(t, upstream.server.URL, nil)
if _, err := tc.submodules(ctx, "up/repo.git", work, commit); !errors.Is(err, context.Canceled) {
t.Errorf("submodules with a cancelled context = %v, want context.Canceled", err)
}
}
// TestMirrorSkipsBrokenGitmodules checks a .gitmodules git can't read, one
// with a syntax error or committed as a symlink, lists no submodules and
// logs a warning instead of failing the mirror.
func TestMirrorSkipsBrokenGitmodules(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
gitmodules := filepath.Join(work, ".gitmodules")
if err := os.WriteFile(gitmodules, []byte("[submodule \"x\"\n\tpath = x\n"), 0o600); err != nil {
t.Fatal(err)
}
runGit(t, work, "add", ".gitmodules")
runGit(t, work, "commit", "--quiet", "-m", "broken gitmodules")
if err := os.Remove(gitmodules); err != nil {
t.Fatal(err)
}
if err := os.Symlink("../outside/.gitmodules", gitmodules); err != nil {
t.Fatal(err)
}
runGit(t, work, "add", ".gitmodules")
runGit(t, work, "commit", "--quiet", "-m", "symlinked gitmodules")
runGit(t, work, "push", "--quiet", "origin", "main")
tc := newTestCache(t, upstream.server.URL, nil)
for _, rev := range []string{"HEAD~1", "HEAD"} {
commit := runGit(t, work, "rev-parse", rev)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", commit, true)
if err != nil || len(got.Submodules) != 0 {
t.Errorf("mirror of %s = %+v, %v, want no submodules", rev, got, err)
}
if !strings.Contains(tc.logs.String(), "commit="+commit) {
t.Errorf("no warning naming %s in the log", commit)
}
}
}
func TestMirrorAcceptsUppercaseCommit(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
commit := runGit(t, work, "rev-parse", "HEAD")
tc := newTestCache(t, upstream.server.URL, nil)
got, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", strings.ToUpper(commit), true)
if err != nil || got.Commit != commit {
t.Errorf("mirror of the uppercase commit ID = %+v, %v, want commit %s", got, err, commit)
}
}
func TestMirrorExplainsShortCommit(t *testing.T) {
upstream := newFakeUpstream(t)
work := upstream.newRepo("repo")
commit := runGit(t, work, "rev-parse", "HEAD")
tc := newTestCache(t, upstream.server.URL, nil)
_, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", commit[:12], true)
if !errors.Is(err, ErrNotFound) || !strings.Contains(err.Error(), "full commit ID") {
t.Errorf("mirror of a short commit ID = %v, want ErrNotFound saying it is no full commit ID", err)
}
}
// TestMirrorWarnsOfStaleTag checks a tag served from the cache when the
// refresh fails is mirrored with a warning, since the tag may have moved.
func TestMirrorWarnsOfStaleTag(t *testing.T) {
upstream := newFakeUpstream(t)
upstream.newRepo("repo")
tc := newTestCache(t, upstream.server.URL, nil)
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "v1", true); err != nil {
t.Fatalf("Mirror: %v", err)
}
upstream.status.Store(http.StatusServiceUnavailable)
if _, err := tc.Mirror(context.Background(), "up/repo.git", upstream.server.URL+"/repo", "v1", true); err != nil {
t.Fatalf("mirror of a cached tag with the upstream down: %v", err)
}
if !strings.Contains(tc.logs.String(), "tag=v1") {
t.Errorf("no warning naming the tag in the log:\n%s", tc.logs.String())
}
}