shithub: furgit

Download patch

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,
--