ref: 74584d2dee4f349b5b3535669fa304b95f0f6e52
parent: 399530fa393b7a23a01e1546273f9abea9c8e87a
author: Runxi Yu <me@runxiyu.org>
date: Sat Mar 7 22:31:12 EST 2026
format/pack/ingest: Add more progress
--- a/format/pack/ingest/api.go
+++ b/format/pack/ingest/api.go
@@ -16,6 +16,10 @@
WriteRev bool
// Base supplies existing objects for thin-pack fixup.
Base objectstore.Store
+ // Progress receives human-readable progress messages.
+ //
+ // When nil, no progress output is emitted.
+ Progress io.Writer
// RequireTrailingEOF requires the source to hit EOF after the pack trailer.
//
// This is suitable for exact pack-file readers, but should be disabled for
--- a/format/pack/ingest/ingest.go
+++ b/format/pack/ingest/ingest.go
@@ -1,7 +1,11 @@
package ingest
-import "fmt"
+import (
+ "fmt"
+ "codeberg.org/lindenii/furgit/internal/utils"
+)
+
// ingest initializes transaction state and executes the ingest pipeline.
func ingest(state *ingestState) (out Result, err error) {err = openTemporaryArtifacts(state)
@@ -47,6 +51,7 @@
return Result{}, err}
+ utils.WriteProgressf(state.opts.Progress, "writing index: start\n")
err = state.packFile.Sync()
if err != nil { return Result{}, &DestinationWriteError{Op: fmt.Sprintf("sync pack: %v", err)}@@ -56,10 +61,17 @@
if err != nil { return Result{}, err}
+ utils.WriteProgressf(state.opts.Progress, "writing index: done\n")
+ if state.opts.WriteRev {+ utils.WriteProgressf(state.opts.Progress, "writing reverse index: start\n")
+ }
err = writeRev(state)
if err != nil { return Result{}, err+ }
+ if state.opts.WriteRev {+ utils.WriteProgressf(state.opts.Progress, "writing reverse index: done\n")
}
return finalizeArtifacts(state)
--- /dev/null
+++ b/format/pack/ingest/progress_step.go
@@ -1,0 +1,9 @@
+package ingest
+
+func progressStep(total uint32) uint32 {+ if total <= 200 {+ return 1
+ }
+
+ return total / 200
+}
--- a/format/pack/ingest/resolve_all.go
+++ b/format/pack/ingest/resolve_all.go
@@ -1,16 +1,41 @@
package ingest
-import "errors"
+import (
+ "errors"
+ "codeberg.org/lindenii/furgit/internal/utils"
+)
+
// resolveAll resolves all delta records and finalizes ObjectID/RealType for every record.
func resolveAll(state *ingestState) error {state.unresolvedRefDeltas = state.unresolvedRefDeltas[:0]
+ var pending uint32
for idx := range state.records {+ if !state.records[idx].resolved {+ pending++
+ }
+ }
+
+ if pending == 0 {+ return nil
+ }
+
+ step := progressStep(pending)
+ var done uint32
+ utils.WriteProgressf(state.opts.Progress, "resolving deltas: 0%% (0/%d)\r", pending)
+
+ for idx := range state.records { if state.records[idx].resolved {continue
}
+ done++
+ if done%step == 0 || done == pending {+ percent := done * 100 / pending
+ utils.WriteProgressf(state.opts.Progress, "resolving deltas: %3d%% (%d/%d)\r", percent, done, pending)
+ }
+
visiting := make(map[int]struct{})ty, content, err := resolveRecord(state, idx, visiting)
@@ -36,6 +61,8 @@
state.objectToRecord[id] = idx
state.baseCache.add(idx, ty, content)
}
+
+ utils.WriteProgressf(state.opts.Progress, "resolving deltas: 100%% (%d/%d), done.\n", pending, pending)
return nil
}
--- a/format/pack/ingest/scan.go
+++ b/format/pack/ingest/scan.go
@@ -3,6 +3,7 @@
import (
"fmt"
+ "codeberg.org/lindenii/furgit/internal/utils"
"codeberg.org/lindenii/furgit/objectid"
)
@@ -29,7 +30,11 @@
state.ofsDeltas = make([]ofsDeltaRef, 0, state.objectCountHeader)
state.refDeltas = make([]refDeltaRef, 0, state.objectCountHeader)
- for range state.objectCountHeader {+ total := state.objectCountHeader
+ step := progressStep(total)
+ utils.WriteProgressf(state.opts.Progress, "receiving objects: 0%% (0/%d)\r", total)
+
+ for i := uint32(0); i < total; i++ {nextOffset, err := scanOneEntry(state, state.stream.consumed)
if err != nil {return err
@@ -38,7 +43,15 @@
if nextOffset != state.stream.consumed { return fmt.Errorf("format/pack/ingest: internal stream offset mismatch")}
+
+ done := i + 1
+ if done%step == 0 || done == total {+ percent := done * 100 / total
+ utils.WriteProgressf(state.opts.Progress, "receiving objects: %3d%% (%d/%d)\r", percent, done, total)
+ }
}
+
+ utils.WriteProgressf(state.opts.Progress, "receiving objects: 100%% (%d/%d), done.\n", total, total)
err = state.stream.finishAndFlushTrailer(state.opts.RequireTrailingEOF)
if err != nil {--- a/format/pack/ingest/thin_fix.go
+++ b/format/pack/ingest/thin_fix.go
@@ -4,6 +4,7 @@
"fmt"
"codeberg.org/lindenii/furgit/internal/intconv"
+ "codeberg.org/lindenii/furgit/internal/utils"
)
// maybeFixThin appends missing bases and rewrites pack header/trailer when needed.
@@ -12,6 +13,12 @@
return nil
}
+ utils.WriteProgressf(
+ state.opts.Progress,
+ "fixing thin pack: %d unresolved bases\r",
+ len(state.unresolvedRefDeltas),
+ )
+
if !state.opts.FixThin { return &ThinPackUnresolvedError{Count: len(state.unresolvedRefDeltas)}}
@@ -47,7 +54,8 @@
state.stream.consumed = consumed
baseIDs := unresolvedThinBaseIDs(state)
- for _, id := range baseIDs {+ total := len(baseIDs)
+ for i, id := range baseIDs {ty, content, err := state.opts.Base.ReadBytesContent(id)
if err != nil {continue
@@ -59,11 +67,17 @@
}
state.thinFixed = true
+
+ utils.WriteProgressf(state.opts.Progress, "fixing thin pack: %d/%d\r", i+1, total)
}
err = rewritePackHeaderAndTrailer(state)
if err != nil {return err
+ }
+
+ if state.thinFixed {+ utils.WriteProgressf(state.opts.Progress, "fixing thin pack: done.\n")
}
return nil
--- a/receivepack/service/ingest_quarantine.go
+++ b/receivepack/service/ingest_quarantine.go
@@ -86,6 +86,7 @@
}
utils.WriteProgressf(
+ service.opts.Progress,
"receiving objects: unpack ok, %d objects (%s)\n",
ingested.ObjectCount,
ingested.PackHash,
--
⑨