diff options
| author | 2026-03-08 12:03:26 +0800 | |
|---|---|---|
| committer | 2026-03-08 12:03:26 +0800 | |
| commit | ae5c818674e2c9ca950ca7a9bf93f1283e7411b7 (patch) | |
| tree | 25d1702260993a8066690c93b3da81adea6d4258 /receivepack | |
| parent | receivepack: Trivial caps (diff) | |
| signature | No signature | |
receivepack, format/pack/ingest: Two-stage ingestion
Diffstat (limited to 'receivepack')
| -rw-r--r-- | receivepack/int_test.go | 55 | ||||
| -rw-r--r-- | receivepack/service/execute.go | 2 | ||||
| -rw-r--r-- | receivepack/service/ingest_quarantine.go | 57 |
3 files changed, 102 insertions, 12 deletions
diff --git a/receivepack/int_test.go b/receivepack/int_test.go index a5cd29ab..b144c387 100644 --- a/receivepack/int_test.go +++ b/receivepack/int_test.go @@ -797,6 +797,61 @@ func TestReceivePackGitPushCreatesBranch(t *testing.T) { }) } +func TestReceivePackGitPushRefUpdateWithoutNewObjectsSucceeds(t *testing.T) { + t.Parallel() + + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper + t.Parallel() + + sender := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo}) + blobID, treeID := sender.MakeSingleFileTree(t, "base.txt", []byte("base\n")) + commitID := sender.CommitTree(t, treeID, "base") + sender.UpdateRef(t, "refs/heads/main", commitID) + + receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) + receiver.HashObject(t, "blob", sender.RunBytes(t, "cat-file", "blob", blobID.String())) + receiver.HashObject(t, "tree", sender.RunBytes(t, "cat-file", "tree", treeID.String())) + receiver.HashObject(t, "commit", sender.RunBytes(t, "cat-file", "commit", commitID.String())) + receiver.UpdateRef(t, "refs/heads/main", commitID) + + repo := receiver.OpenRepository(t) + objectsRoot := receiver.OpenObjectsRoot(t) + + stdout, stderr, clientErr, serverErr := runGitPushFD( + t, + sender, + receivepack.Options{ + Algorithm: algo, + Refs: repo.Refs(), + ExistingObjects: repo.Objects(), + ObjectsRoot: objectsRoot, + }, + "push", "--porcelain", "fd::3,4/test", "refs/heads/main:refs/heads/topic", + ) + if clientErr != nil { + t.Fatalf("git push failed: %v\nstdout=%s\nstderr=%s", clientErr, stdout, stderr) + } + + if serverErr != nil { + t.Fatalf("ReceivePack: %v", serverErr) + } + + resolved, err := receiver.OpenRepository(t).Refs().ResolveFully("refs/heads/topic") + if err != nil { + t.Fatalf("ResolveFully(topic): %v", err) + } + + if resolved.ID != commitID { + t.Fatalf("refs/heads/topic = %s, want %s", resolved.ID, commitID) + } + + packs := receiver.Run(t, "count-objects", "-v") + if !strings.Contains(packs, "packs: 0") { + t.Fatalf("count-objects output shows unexpected promoted pack: %q", packs) + } + }) +} + func TestReceivePackGitPushAtomicDelete(t *testing.T) { t.Parallel() diff --git a/receivepack/service/execute.go b/receivepack/service/execute.go index faedff49..8f70fb83 100644 --- a/receivepack/service/execute.go +++ b/receivepack/service/execute.go @@ -77,7 +77,7 @@ func (service *Service) Execute(ctx context.Context, req *Request) (*Result, err return result, nil } - if req.PackExpected { + if req.PackExpected && quarantineRoot != nil { // Git migrates quarantined objects into permanent storage immediately // before starting ref updates. utils.WriteProgressf(service.opts.Progress, "promoting quarantine...\r") diff --git a/receivepack/service/ingest_quarantine.go b/receivepack/service/ingest_quarantine.go index ad6ce852..48815fa8 100644 --- a/receivepack/service/ingest_quarantine.go +++ b/receivepack/service/ingest_quarantine.go @@ -34,6 +34,51 @@ func (service *Service) ingestQuarantine( return "", nil, false } + pending, err := ingest.Ingest( + req.Pack, + service.opts.Algorithm, + ingest.Options{ + FixThin: true, + WriteRev: true, + Base: service.opts.ExistingObjects, + Progress: service.opts.Progress, + }, + ) + if err != nil { + utils.WriteProgressf(service.opts.Progress, "unpack failed: %v\n", err) + + result.UnpackError = err.Error() + fillCommandErrors(result, commands, err.Error()) + + return "", nil, false + } + + if pending.Header().ObjectCount == 0 { + discarded, err := pending.Discard() + if err != nil { + utils.WriteProgressf(service.opts.Progress, "unpack failed: %v\n", err) + + result.UnpackError = err.Error() + fillCommandErrors(result, commands, err.Error()) + + return "", nil, false + } + + result.Ingest = &ingest.Result{ + PackHash: discarded.PackHash, + ObjectCount: discarded.ObjectCount, + } + + utils.WriteProgressf( + service.opts.Progress, + "unpacking: done (%d objects, %s).\n", + discarded.ObjectCount, + discarded.PackHash, + ) + + return "", nil, true + } + utils.WriteProgressf(service.opts.Progress, "creating quarantine...\r") quarantineName, quarantineRoot, err := service.createQuarantineRoot() @@ -62,17 +107,7 @@ func (service *Service) ingestQuarantine( utils.WriteProgressf(service.opts.Progress, "creating quarantine: done.\n") utils.WriteProgressf(service.opts.Progress, "unpacking...\r") - ingested, err := ingest.Ingest( - req.Pack, - quarantinePackRoot, - service.opts.Algorithm, - ingest.Options{ - FixThin: true, - WriteRev: true, - Base: service.opts.ExistingObjects, - Progress: service.opts.Progress, - }, - ) + ingested, err := pending.Continue(quarantinePackRoot) _ = quarantinePackRoot.Close() |
