git@cosmocr.at/gitweb.git @ HEAD · summary refs log
HEAD / internal / gitrepo / push_test.go /
internal/gitrepo/push_test.go · 5.3 KiB · raw
view: SourceMarkdown ☑ force

package gitrepo

import ( "context" "errors" "strings" "testing"

"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/util"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"

"gitweb/internal/store"

)

// recordingStore records Put/Delete calls for assertions. Reads are unused by // writeBack (it reads file bytes from the billy fs), so they are stubs. type recordingStore struct { puts []string dels []string }

func (r *recordingStore) Stat(string) (bool, error) { return false, store.ErrNotFound } func (r *recordingStore) Get(string) ([]byte, error) { return nil, store.ErrNotFound } func (r *recordingStore) List(string) ([]store.Entry, error) { return nil, store.ErrNotFound } func (r *recordingStore) Seq(string) (int64, error) { return 0, nil } func (r *recordingStore) Put(p string, _ []byte) error { r.puts = append(r.puts, p); return nil } func (r *recordingStore) Delete(p string) error { r.dels = append(r.dels, p); return nil }

func writeFile(t *testing.T, fs billy.Filesystem, name, content string) { t.Helper() if err := util.WriteFile(fs, name, []byte(content), 0o644); err != nil { t.Fatal(err) } }

func TestWriteBackDiffAndOrdering(t *testing.T) { fs := memfs.New() // Initial state. writeFile(t, fs, "HEAD", "ref: refs/heads/main\n") writeFile(t, fs, "objects/pack/pack-old.pack", "OLD") writeFile(t, fs, "refs/heads/main", "aaaa\n") writeFile(t, fs, "refs/heads/stale", "bbbb\n")

before, err := walkManifest(fs, defaultMaxRepoBytes)
if err != nil {
	t.Fatal(err)
}

// Simulate a push: new packfile, updated main ref, deleted stale ref.
writeFile(t, fs, "objects/pack/pack-new.pack", "NEW")
writeFile(t, fs, "refs/heads/main", "cccc\n")
if err := fs.Remove("refs/heads/stale"); err != nil {
	t.Fatal(err)
}

after, err := walkManifest(fs, defaultMaxRepoBytes)
if err != nil {
	t.Fatal(err)
}

rec := &recordingStore{}
p := NewPusher(rec, newTestCache(t, rec, CacheOptions{}))
if err := p.writeBack(fs, before, after, "u@d.com/r.git"); err != nil {
	t.Fatalf("writeBack: %v", err)
}

// Expected: the new packfile and the changed ref are Put; the removed ref is
// Deleted. HEAD and the old packfile are unchanged and must not be rewritten.
wantPuts := map[string]bool{
	"u@d.com/r.git/objects/pack/pack-new.pack": true,
	"u@d.com/r.git/refs/heads/main":            true,
}
if len(rec.puts) != len(wantPuts) {
	t.Fatalf("puts = %v, want keys %v", rec.puts, wantPuts)
}
for _, p := range rec.puts {
	if !wantPuts[p] {
		t.Errorf("unexpected put %q", p)
	}
}
// Object writes must precede ref writes.
if !strings.Contains(rec.puts[0], "/objects/") {
	t.Errorf("expected object put first, got order %v", rec.puts)
}
// The stale ref must be deleted.
if len(rec.dels) != 1 || rec.dels[0] != "u@d.com/r.git/refs/heads/stale" {
	t.Fatalf("dels = %v, want [u@d.com/r.git/refs/heads/stale]", rec.dels)
}

}

func TestFailReport(t *testing.T) { rs := packp.NewReportStatus() rs.UnpackStatus = "ok" rs.CommandStatuses = []*packp.CommandStatus{ {ReferenceName: "refs/heads/main", Status: "ok"}, {ReferenceName: "refs/heads/other", Status: "already failed"}, } failReport(rs, errors.New("store broke:\n\tdetail"))

if got := rs.CommandStatuses[0].Status; got != "store broke: detail" {
	t.Errorf("ok status = %q, want flattened error", got)
}
// A status that already carries a failure is not overwritten.
if got := rs.CommandStatuses[1].Status; got != "already failed" {
	t.Errorf("failed status overwritten: %q", got)
}
if failReport(nil, errors.New("x")) != nil {
	t.Error("failReport(nil) != nil")
}

}

// TestReceivePackCleansUpOnError: a failed materialization must not leave a // push-* directory behind in the cache root. func TestReceivePackCleansUpOnError(t *testing.T) { rec := &recordingStore{} // every read fails with ErrNotFound dir := t.TempDir() p := NewPusher(rec, newTestCache(t, rec, CacheOptions{Dir: dir}))

_, err := p.ReceivePack(context.Background(), "u@d.com/r.git", packp.NewReferenceUpdateRequest())
if err == nil {
	t.Fatal("expected error from failing store")
}
if got := dirCount(t, dir); got != 0 {
	t.Fatalf("%d dirs left in cache root after failed push, want 0", got)
}

}

// TestReceivePackSemaphoreHonorsContext: with the push semaphore full, a // cancelled context aborts instead of blocking forever. func TestReceivePackSemaphoreHonorsContext(t *testing.T) { rec := &recordingStore{} p := NewPusher(rec, newTestCache(t, rec, CacheOptions{})) for range cap(p.sem) { p.sem <- struct{}{} } ctx, cancel := context.WithCancel(context.Background()) cancel() _, err := p.ReceivePack(ctx, "u@d.com/r.git", packp.NewReferenceUpdateRequest()) if !errors.Is(err, context.Canceled) { t.Fatalf("err = %v, want context.Canceled", err) } }

func TestWalkManifestSkipsTmp(t *testing.T) { fs := memfs.New() writeFile(t, fs, "HEAD", "x") writeFile(t, fs, "objects/tmp_pack_123", "scratch") writeFile(t, fs, "._packed-refs123", "scratch") man, err := walkManifest(fs, defaultMaxRepoBytes) if err != nil { t.Fatal(err) } if , ok := man["objects/tmp_pack_123"]; ok { t.Error("walkManifest included a tmp scratch file") } if _, ok := man["._packed-refs123"]; ok { t.Error("walkManifest included a ._packed-refs temp file") } if _, ok := man["HEAD"]; !ok { t.Error("walkManifest missing HEAD") } }