shithub: furgit

Download patch

ref: 4a17412255e294e99f2d11e9b8458e30bbe58292
parent: c75a034d25ca87f3d209a8e82c743b8a7e96573b
author: Runxi Yu <me@runxiyu.org>
date: Sun Mar 8 10:15:55 EDT 2026

format/pack/ingest: Use progress API

--- a/format/pack/ingest/api.go
+++ b/format/pack/ingest/api.go
@@ -23,6 +23,10 @@
 	//
 	// When nil, no progress output is emitted.
 	Progress io.Writer
+	// ProgressFlush flushes transport output after progress writes.
+	//
+	// When nil, no explicit flush is attempted.
+	ProgressFlush func() error
 	// 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
@@ -2,8 +2,6 @@
 
 import (
 	"fmt"
-
-	"codeberg.org/lindenii/furgit/internal/utils"
 )
 
 // ingest initializes transaction state and executes the ingest pipeline.
@@ -51,7 +49,7 @@
 		return Result{}, err
 	}
 
-	utils.BestEffortFprintf(state.opts.Progress, "writing index...\r")
+	writeProgress(state, "writing index...\r")
 
 	err = state.packFile.Sync()
 	if err != nil {
@@ -63,10 +61,10 @@
 		return Result{}, err
 	}
 
-	utils.BestEffortFprintf(state.opts.Progress, "writing index: done.\n")
+	writeProgress(state, "writing index: done.\n")
 
 	if state.opts.WriteRev {
-		utils.BestEffortFprintf(state.opts.Progress, "writing reverse index...\r")
+		writeProgress(state, "writing reverse index...\r")
 	}
 
 	err = writeRev(state)
@@ -75,7 +73,7 @@
 	}
 
 	if state.opts.WriteRev {
-		utils.BestEffortFprintf(state.opts.Progress, "writing reverse index: done.\n")
+		writeProgress(state, "writing reverse index: done.\n")
 	}
 
 	return finalizeArtifacts(state)
--- a/format/pack/ingest/progress_step.go
+++ /dev/null
@@ -1,9 +1,0 @@
-package ingest
-
-func progressStep(total uint32) uint32 {
-	if total <= 200 {
-		return 1
-	}
-
-	return total / 200
-}
--- /dev/null
+++ b/format/pack/ingest/progress_write.go
@@ -1,0 +1,10 @@
+package ingest
+
+import "codeberg.org/lindenii/furgit/internal/utils"
+
+func writeProgress(state *ingestState, format string, args ...any) {
+	utils.BestEffortFprintf(state.opts.Progress, format, args...)
+	if state.opts.ProgressFlush != nil {
+		_ = state.opts.ProgressFlush()
+	}
+}
--- a/format/pack/ingest/resolve_all.go
+++ b/format/pack/ingest/resolve_all.go
@@ -3,7 +3,7 @@
 import (
 	"errors"
 
-	"codeberg.org/lindenii/furgit/internal/utils"
+	"codeberg.org/lindenii/furgit/internal/progress"
 )
 
 // resolveAll resolves all delta records and finalizes ObjectID/RealType for every record.
@@ -22,11 +22,14 @@
 		return nil
 	}
 
-	step := progressStep(pending)
-
 	var done uint32
 
-	utils.BestEffortFprintf(state.opts.Progress, "resolving deltas:   0%% (0/%d)\r", pending)
+	meter := progress.New(progress.Options{
+		Writer: state.opts.Progress,
+		Flush:  state.opts.ProgressFlush,
+		Title:  "resolving deltas",
+		Total:  uint64(pending),
+	})
 
 	for idx := range state.records {
 		if state.records[idx].resolved {
@@ -34,10 +37,7 @@
 		}
 
 		done++
-		if done%step == 0 || done == pending {
-			percent := done * 100 / pending
-			utils.BestEffortFprintf(state.opts.Progress, "resolving deltas: %3d%% (%d/%d)\r", percent, done, pending)
-		}
+		meter.Set(uint64(done), 0)
 
 		visiting := make(map[int]struct{})
 
@@ -65,7 +65,7 @@
 		state.baseCache.add(idx, ty, content)
 	}
 
-	utils.BestEffortFprintf(state.opts.Progress, "resolving deltas: 100%% (%d/%d), done.\n", pending, pending)
+	meter.Stop("done")
 
 	return nil
 }
--- a/format/pack/ingest/scan.go
+++ b/format/pack/ingest/scan.go
@@ -3,7 +3,7 @@
 import (
 	"fmt"
 
-	"codeberg.org/lindenii/furgit/internal/utils"
+	"codeberg.org/lindenii/furgit/internal/progress"
 	"codeberg.org/lindenii/furgit/objectid"
 )
 
@@ -21,7 +21,7 @@
 		state.algo.Size(),
 	)
 
-	utils.BestEffortFprintf(state.opts.Progress, "validating pack header...\r")
+	writeProgress(state, "validating pack header...\r")
 
 	err = seedStreamWithPackHeader(state)
 	if err != nil {
@@ -28,7 +28,7 @@
 		return err
 	}
 
-	utils.BestEffortFprintf(state.opts.Progress, "validating pack header: done.\n")
+	writeProgress(state, "validating pack header: done.\n")
 
 	state.records = make([]objectRecord, 0, state.objectCountHeader)
 	state.ofsDeltas = make([]ofsDeltaRef, 0, state.objectCountHeader)
@@ -35,8 +35,13 @@
 	state.refDeltas = make([]refDeltaRef, 0, state.objectCountHeader)
 
 	total := state.objectCountHeader
-	step := progressStep(total)
-	utils.BestEffortFprintf(state.opts.Progress, "receiving objects:   0%% (0/%d)\r", total)
+	meter := progress.New(progress.Options{
+		Writer:     state.opts.Progress,
+		Flush:      state.opts.ProgressFlush,
+		Title:      "receiving objects",
+		Total:      uint64(total),
+		Throughput: true,
+	})
 
 	for i := range total {
 		nextOffset, err := scanOneEntry(state, state.stream.consumed)
@@ -49,13 +54,10 @@
 		}
 
 		done := i + 1
-		if done%step == 0 || done == total {
-			percent := done * 100 / total
-			utils.BestEffortFprintf(state.opts.Progress, "receiving objects: %3d%% (%d/%d)\r", percent, done, total)
-		}
+		meter.Set(uint64(done), state.stream.consumed)
 	}
 
-	utils.BestEffortFprintf(state.opts.Progress, "receiving objects: 100%% (%d/%d), done.\n", total, total)
+	meter.Stop("done")
 
 	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,7 +4,7 @@
 	"fmt"
 
 	"codeberg.org/lindenii/furgit/internal/intconv"
-	"codeberg.org/lindenii/furgit/internal/utils"
+	"codeberg.org/lindenii/furgit/internal/progress"
 )
 
 // maybeFixThin appends missing bases and rewrites pack header/trailer when needed.
@@ -13,8 +13,8 @@
 		return nil
 	}
 
-	utils.BestEffortFprintf(
-		state.opts.Progress,
+	writeProgress(
+		state,
 		"fixing thin pack: %d unresolved bases\r",
 		len(state.unresolvedRefDeltas),
 	)
@@ -56,9 +56,13 @@
 	baseIDs := unresolvedThinBaseIDs(state)
 
 	total := len(baseIDs)
-	if total > 0 {
-		utils.BestEffortFprintf(state.opts.Progress, "fixing thin pack:   0%% (0/%d)\r", total)
-	}
+	meter := progress.New(progress.Options{
+		Writer: state.opts.Progress,
+		Flush:  state.opts.ProgressFlush,
+		Title:  "fixing thin pack",
+		Total:  uint64(total),
+		Sparse: true,
+	})
 
 	for i, id := range baseIDs {
 		ty, content, err := state.opts.Base.ReadBytesContent(id)
@@ -74,8 +78,7 @@
 		state.thinFixed = true
 
 		done := i + 1
-		percent := done * 100 / total
-		utils.BestEffortFprintf(state.opts.Progress, "fixing thin pack: %3d%% (%d/%d)\r", percent, done, total)
+		meter.Set(uint64(done), 0)
 	}
 
 	err = rewritePackHeaderAndTrailer(state)
@@ -84,7 +87,7 @@
 	}
 
 	if state.thinFixed {
-		utils.BestEffortFprintf(state.opts.Progress, "fixing thin pack: 100%% (%d/%d), done.\n", total, total)
+		meter.Stop("done")
 	}
 
 	return nil
--