aboutsummaryrefslogtreecommitdiff
path: root/receivepack
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-08 12:03:26 +0800
committerGravatar Runxi Yu2026-03-08 12:03:26 +0800
commitae5c818674e2c9ca950ca7a9bf93f1283e7411b7 (patch)
tree25d1702260993a8066690c93b3da81adea6d4258 /receivepack
parentreceivepack: Trivial caps (diff)
signatureNo signature
receivepack, format/pack/ingest: Two-stage ingestion
Diffstat (limited to 'receivepack')
-rw-r--r--receivepack/int_test.go55
-rw-r--r--receivepack/service/execute.go2
-rw-r--r--receivepack/service/ingest_quarantine.go57
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()