mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
415 lines
16 KiB
Go
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())
|
|
}
|
|
}
|