Add atomic writes, humanized sizes, debug logging, and -v/-q per-command

- Atomic writes for mfer gen: writes to temp file, renames on success,
  cleans up temp on error/interrupt. Prevents empty manifests on Ctrl-C.
- Humanized byte sizes using dustin/go-humanize (e.g., "10 MiB" not "10485760")
- Progress lines clear when done (using ANSI escape \r\033[K])
- Debug logging when files are added to manifest (mfer gen -vv)
- Move -v/-q flags from global to per-command for better UX
- Add tests for atomic write behavior with failing filesystem mock
This commit is contained in:
2025-12-17 15:57:20 -08:00
parent 444a4c8f45
commit c218fe56e9
12 changed files with 276 additions and 86 deletions

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"time"
"github.com/dustin/go-humanize"
"github.com/multiformats/go-multihash"
"github.com/spf13/afero"
"github.com/urfave/cli/v2"
@@ -217,7 +218,7 @@ func (mfa *CLIApp) freshenManifestOperation(ctx *cli.Context) error {
// Phase 2: Hash changed and new files
if filesToHash > 0 {
log.Infof("hashing %d files (%.1f MB)...", filesToHash, float64(totalHashBytes)/1e6)
log.Infof("hashing %d files (%s)...", filesToHash, humanize.IBytes(uint64(totalHashBytes)))
}
startHash := time.Now()
@@ -255,11 +256,11 @@ func (mfa *CLIApp) freshenManifestOperation(ctx *cli.Context) error {
}
}
if eta > 0 {
log.Progressf("Hashing: %d/%d files, %.1f MB/s, ETA %s",
hashedFiles, filesToHash, rate/1e6, eta.Round(time.Second))
log.Progressf("Hashing: %d/%d files, %s/s, ETA %s",
hashedFiles, filesToHash, humanize.IBytes(uint64(rate)), eta.Round(time.Second))
} else {
log.Progressf("Hashing: %d/%d files, %.1f MB/s",
hashedFiles, filesToHash, rate/1e6)
log.Progressf("Hashing: %d/%d files, %s/s",
hashedFiles, filesToHash, humanize.IBytes(uint64(rate)))
}
}
})
@@ -315,12 +316,11 @@ func (mfa *CLIApp) freshenManifestOperation(ctx *cli.Context) error {
}
totalDuration := time.Since(mfa.startupTime)
var hashRate float64
if hashedBytes > 0 {
hashDuration := time.Since(startHash)
hashRate = float64(hashedBytes) / hashDuration.Seconds() / 1e6
log.Infof("hashed %.1f MB in %.1fs (%.1f MB/s)",
float64(hashedBytes)/1e6, totalDuration.Seconds(), hashRate)
hashRate := float64(hashedBytes) / hashDuration.Seconds()
log.Infof("hashed %s in %.1fs (%s/s)",
humanize.IBytes(uint64(hashedBytes)), totalDuration.Seconds(), humanize.IBytes(uint64(hashRate)))
}
log.Infof("wrote %d files to %s", len(entries), manifestPath)