From 8006a0a44f25e45723b253d4bcd8ea01b45873a2 Mon Sep 17 00:00:00 2001 From: Vinayak Mohanty Date: Thu, 7 May 2026 04:19:32 +0530 Subject: [PATCH 01/13] refactor: migrate away from cheggaaa/pb v1 --- .github/dependabot.yml | 2 ++ core/commands/add.go | 36 +++++++++++++++--------------------- core/commands/cat.go | 8 ++++---- core/commands/dag/export.go | 14 +++++--------- core/commands/get.go | 35 +++++++++++++++++------------------ go.mod | 11 ++++++----- go.sum | 29 ++++++++++++----------------- 7 files changed, 61 insertions(+), 74 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cde71238334..aad7ca357cf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,8 @@ updates: - dependency-name: "github.com/cockroachdb/pebble*" - dependency-name: "github.com/syndtr/goleveldb" - dependency-name: "github.com/dgraph-io/badger*" + # pb v1/v2 replaced by pb/v3; ignore the old module path + - dependency-name: "github.com/cheggaaa/pb" groups: ipfs-ecosystem: patterns: diff --git a/core/commands/add.go b/core/commands/add.go index 1cd063120ce..1c10d805bec 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -13,7 +13,7 @@ import ( "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/ipfs/kubo/core/commands/cmdutils" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" uio "github.com/ipfs/boxo/ipld/unixfs/io" mfs "github.com/ipfs/boxo/mfs" @@ -252,7 +252,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import cmds.BoolOption(quietOptionName, "q", "Write minimal output."), cmds.BoolOption(quieterOptionName, "Q", "Write only final hash."), cmds.BoolOption(silentOptionName, "Write no output."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), // Basic Add Behavior cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk."), cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object."), @@ -292,10 +292,10 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import silent, _ := req.Options[silentOptionName].(bool) if !quiet && !silent { - // ipfs cli progress bar defaults to true unless quiet or silent is used + // default to showing progress only when stderr is a terminal _, found := req.Options[progressOptionName].(bool) if !found { - req.Options[progressOptionName] = true + req.Options[progressOptionName] = isStderrTTY() } } @@ -732,11 +732,8 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import var bar *pb.ProgressBar if progress { - bar = pb.New64(0).SetUnits(pb.U_BYTES) - bar.ManualUpdate = true - bar.ShowTimeLeft = false - bar.ShowPercent = false - bar.Output = os.Stderr + bar = pb.New64(0).Set(pb.Bytes, true).Set(pb.Static, true).SetWriter(os.Stderr) + bar.SetTemplateString(`{{counters . }} {{speed . }}`) bar.Start() } @@ -786,18 +783,17 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import } lastBytes = output.Bytes delta := prevFiles + lastBytes - totalProgress - totalProgress = bar.Add64(delta) + bar.Add64(delta) + totalProgress = bar.Current() } if progress { - bar.Update() + bar.Write() } case size := <-sizeChan: if progress { - bar.Total = size - bar.ShowPercent = true - bar.ShowBar = true - bar.ShowTimeLeft = true + bar.SetTotal(size) + bar.SetTemplateString(`{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}`) } case <-req.Context.Done(): // don't set or print error here, that happens in the goroutine below @@ -805,12 +801,10 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import } } - if progress && bar.Total == 0 && bar.Get() != 0 { - bar.Total = bar.Get() - bar.ShowPercent = true - bar.ShowBar = true - bar.ShowTimeLeft = true - bar.Update() + if progress && bar.Total() == 0 && bar.Current() != 0 { + bar.SetTotal(bar.Current()) + bar.SetTemplateString(`{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}`) + bar.Write() } } diff --git a/core/commands/cat.go b/core/commands/cat.go index 38a3e8dfaf6..f06d87f9fcb 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -9,7 +9,7 @@ import ( "github.com/ipfs/kubo/core/commands/cmdenv" "github.com/ipfs/kubo/core/commands/cmdutils" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" cmds "github.com/ipfs/go-ipfs-cmds" iface "github.com/ipfs/kubo/core/coreiface" @@ -33,7 +33,7 @@ var CatCmd = &cmds.Command{ Options: []cmds.Option{ cmds.Int64Option(offsetOptionName, "o", "Byte offset to begin reading from."), cmds.Int64Option(lengthOptionName, "l", "Maximum number of bytes to read."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data.").WithDefault(true), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { api, err := cmdenv.GetApi(env, req) @@ -101,8 +101,8 @@ var CatCmd = &cmds.Command{ reader := val req := res.Request() - progress, _ := req.Options[progressOptionName].(bool) - if progress { + progressExplicit, specified := req.Options[progressOptionName].(bool) + if (specified && progressExplicit) || (!specified && isStderrTTY()) { var bar *pb.ProgressBar bar, reader = progressBarForReader(os.Stderr, val, int64(res.Length())) bar.Start() diff --git a/core/commands/dag/export.go b/core/commands/dag/export.go index 48223f86083..2cd0e0a561b 100644 --- a/core/commands/dag/export.go +++ b/core/commands/dag/export.go @@ -8,7 +8,7 @@ import ( "os" "time" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" cid "github.com/ipfs/go-cid" cmds "github.com/ipfs/go-ipfs-cmds" ipld "github.com/ipfs/go-ipld-format" @@ -103,9 +103,8 @@ func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { val, specified := res.Request().Options[progressOptionName] if !specified { // default based on TTY availability - errStat, _ := os.Stderr.Stat() - if (errStat.Mode() & os.ModeCharDevice) != 0 { - showProgress = true + if errStat, err := os.Stderr.Stat(); err == nil { + showProgress = (errStat.Mode() & os.ModeCharDevice) != 0 } } else if val.(bool) { showProgress = true @@ -116,11 +115,8 @@ func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { return cmds.Copy(re, res) } - bar := pb.New64(0).SetUnits(pb.U_BYTES) - bar.Output = os.Stderr - bar.ShowSpeed = true - bar.ShowElapsedTime = true - bar.RefreshRate = 500 * time.Millisecond + bar := pb.New64(0).Set(pb.Bytes, true).SetWriter(os.Stderr).SetRefreshRate(500 * time.Millisecond) + bar.SetTemplateString(`{{counters . }} {{speed . }} {{etime . }}`) bar.Start() var processedOneResponse bool diff --git a/core/commands/get.go b/core/commands/get.go index 804836b9afd..ef318aba933 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -16,7 +16,7 @@ import ( "github.com/ipfs/kubo/core/commands/cmdutils" "github.com/ipfs/kubo/core/commands/e" - "github.com/cheggaaa/pb" + "github.com/cheggaaa/pb/v3" "github.com/ipfs/boxo/files" "github.com/ipfs/boxo/tar" cmds "github.com/ipfs/go-ipfs-cmds" @@ -58,7 +58,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. cmds.BoolOption(archiveOptionName, "a", "Output a TAR archive."), cmds.BoolOption(compressOptionName, "C", "Compress the output with GZIP compression."), cmds.IntOption(compressionLevelOptionName, "l", "The level of compression (1-9)."), - cmds.BoolOption(progressOptionName, "p", "Stream progress data.").WithDefault(true), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, PreRun: func(req *cmds.Request, env cmds.Environment) error { _, err := getCompressOptions(req) @@ -140,7 +140,8 @@ may also specify the level of compression by specifying '-l=<1-9>'. } archive, _ := req.Options[archiveOptionName].(bool) - progress, _ := req.Options[progressOptionName].(bool) + progressExplicit, specified := req.Options[progressOptionName].(bool) + showProgress := (specified && progressExplicit) || (!specified && isStderrTTY()) gw := getWriter{ Out: os.Stdout, @@ -148,7 +149,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. Archive: archive, Compression: cmplvl, Size: int64(res.Length()), - Progress: progress, + Progress: showProgress, } return gw.Write(outReader, outPath) @@ -177,21 +178,19 @@ func progressBarForReader(out io.Writer, r io.Reader, l int64) (*pb.ProgressBar, } func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { - // setup bar reader - // TODO: get total length of files - bar := pb.New64(l).SetUnits(pb.U_BYTES) - bar.Output = out - - // the progress bar lib doesn't give us a way to get the width of the output, - // so as a hack we just use a callback to measure the output, then get rid of it - bar.Callback = func(line string) { - terminalWidth := len(line) - bar.Callback = nil - log.Infof("terminal width: %v\n", terminalWidth) - } + bar := pb.New64(l).Set(pb.Bytes, true).SetWriter(out) return bar } +//returns true when os.Stderr is connected to a terminal. +func isStderrTTY() bool { + stat, err := os.Stderr.Stat() + if err != nil { + return false + } + return (stat.Mode() & os.ModeCharDevice) != 0 +} + func getOutPath(req *cmds.Request) string { outPath, _ := req.Options[outputOptionName].(string) if outPath == "" { @@ -260,8 +259,8 @@ func (gw *getWriter) writeExtracted(r io.Reader, fpath string) error { bar := makeProgressBar(gw.Err, gw.Size) bar.Start() defer bar.Finish() - defer bar.Set64(gw.Size) - progressCb = bar.Add64 + defer bar.SetCurrent(gw.Size) + progressCb = func(n int64) int64 { bar.Add64(n); return bar.Current() } } extractor := &tar.Extractor{Path: fpath, Progress: progressCb} diff --git a/go.mod b/go.mod index 0975cdc47da..3b20723bcb0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/caddyserver/certmagic v0.23.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/ceramicnetwork/go-dag-jose v0.1.1 - github.com/cheggaaa/pb v1.0.29 + github.com/cheggaaa/pb/v3 v3.1.7 github.com/cockroachdb/pebble/v2 v2.1.4 github.com/coreos/go-systemd/v22 v22.7.0 github.com/dustin/go-humanize v1.0.1 @@ -103,6 +103,7 @@ require ( github.com/Jorropo/jsync v1.0.1 // indirect github.com/RaduBerinde/axisds v0.1.0 // indirect github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -126,7 +127,7 @@ require ( github.com/dgraph-io/ristretto v0.0.2 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dunglas/httpsfv v1.1.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect @@ -178,9 +179,9 @@ require ( github.com/libp2p/go-yamux/v5 v5.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect @@ -224,7 +225,7 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/quic-go/webtransport-go v0.10.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/slok/go-http-metrics v0.13.0 // indirect diff --git a/go.sum b/go.sum index ab66a549db5..3bfbcb56872 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/RaduBerinde/axisds v0.1.0 h1:YItk/RmU5nvlsv/awo2Fjx97Mfpt4JfgtEVAGPrL github.com/RaduBerinde/axisds v0.1.0/go.mod h1:UHGJonU9z4YYGKJxSaC6/TNcLOBptpmM5m2Cksbnw0Y= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 h1:bsU8Tzxr/PNz75ayvCnxKZWEYdLMPDkUgticP4a4Bvk= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54/go.mod h1:0tr7FllbE9gJkHq7CVeeDDFAFKQVy5RnCSSNBOvdqbc= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -116,8 +118,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -194,9 +196,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= @@ -591,20 +592,15 @@ github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -815,8 +811,8 @@ github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRC github.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI= github.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -1230,7 +1226,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= From 74145e4bfb206840da4a8e1746cd07a64771ae1f Mon Sep 17 00:00:00 2001 From: Vinayak Mohanty Date: Thu, 7 May 2026 04:29:49 +0530 Subject: [PATCH 02/13] updated changelogs --- docs/changelogs/v0.42.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelogs/v0.42.md b/docs/changelogs/v0.42.md index 4e7acd7056e..9305f801f9c 100644 --- a/docs/changelogs/v0.42.md +++ b/docs/changelogs/v0.42.md @@ -14,6 +14,7 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. - [๐Ÿšจ ERROR log for listeners blocked by `Swarm.AddrFilters` or `Addresses.NoAnnounce`](#-error-log-for-listeners-blocked-by-swarmaddrfilters-or-addressesnoannounce) - [๐Ÿ“Š OpenTelemetry: scope info now exposed as labels](#-opentelemetry-scope-info-now-exposed-as-labels) - [๐Ÿ“ฆ๏ธ Dependency updates](#-dependency-updates) + - [๐Ÿ”ง Progress bars migrated to cheggaaa/pb v3, TTY auto-detection fixed](#-progress-bars-migrated-to-cheggaaapbv3-tty-auto-detection-fixed) - [๐Ÿ“ Changelog](#-changelog) - [๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors](#-contributors) @@ -39,6 +40,11 @@ The Prometheus endpoint no longer emits the `otel_scope_info` metric. Each metri - update `go-libp2p-pubsub` to [v0.16.0](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.16.0) - update `go-fuse/v2` to [v2.10.1](https://github.com/hanwen/go-fuse/releases/tag/v2.10.1) +- update `cheggaaa/pb` to [v3.1.7](https://github.com/cheggaaa/pb/releases/tag/v3.1.7) + +#### ๐Ÿ”ง Progress bars migrated to cheggaaa/pb v3, TTY auto-detection fixed + +`ipfs cat`, `ipfs get`, `ipfs add`, and `ipfs dag export` now use `cheggaaa/pb/v3` instead of the stale v1 (which Dependabot kept trying to bump to the incompatible v2 line). Progress bar behavior is unchanged, but a pre-existing bug is fixed: `--progress` previously defaulted to `true` unconditionally, so a progress bar was written to stderr even when stderr was piped or redirected. All four commands now auto-detect whether stderr is a terminal and suppress the bar when it is not, while still respecting an explicit `--progress=true/false` flag. ### ๐Ÿ“ Changelog From d5a0c3c6609329ef3bac99c87477a47b56d5081f Mon Sep 17 00:00:00 2001 From: Vinayak Mohanty Date: Thu, 7 May 2026 18:18:48 +0530 Subject: [PATCH 03/13] fix: add space after comment slashes for consistency --- core/commands/get.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commands/get.go b/core/commands/get.go index ef318aba933..d39fa5d3dd1 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -182,7 +182,7 @@ func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { return bar } -//returns true when os.Stderr is connected to a terminal. +// returns true when os.Stderr is connected to a terminal. func isStderrTTY() bool { stat, err := os.Stderr.Stat() if err != nil { From 27ebfbb05fe3da86119109def4a8967cc9e851bc Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 02:49:51 +0200 Subject: [PATCH 04/13] refactor: share terminal detection in cmdenv Replace three duplicate TTY checks (get.go, dag/export.go, dag/stat.go) with `cmdenv.IsTerminal(*os.File)` backed by `mattn/go-isatty`. The helper uses `IsTerminal || IsCygwinTerminal`, which also detects MSYS2 and Git Bash on Windows. Those terminals expose stdio as a named pipe rather than a character device, so the previous `ModeCharDevice` check suppressed the progress bar on real terminals. - core/commands/cmdenv/tty.go: new helper - core/commands/{add,cat,get}.go: drop local isStderrTTY - core/commands/dag/{export,stat}.go: drop inline stat() block - go.mod: promote mattn/go-isatty to direct (was indirect via pb/v3) --- core/commands/add.go | 2 +- core/commands/cat.go | 2 +- core/commands/cmdenv/tty.go | 14 ++++++++++++++ core/commands/dag/export.go | 5 +---- core/commands/dag/stat.go | 5 +---- core/commands/get.go | 11 +---------- go.mod | 2 +- 7 files changed, 20 insertions(+), 21 deletions(-) create mode 100644 core/commands/cmdenv/tty.go diff --git a/core/commands/add.go b/core/commands/add.go index 1c10d805bec..8160a9dc3db 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -295,7 +295,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import // default to showing progress only when stderr is a terminal _, found := req.Options[progressOptionName].(bool) if !found { - req.Options[progressOptionName] = isStderrTTY() + req.Options[progressOptionName] = cmdenv.IsTerminal(os.Stderr) } } diff --git a/core/commands/cat.go b/core/commands/cat.go index f06d87f9fcb..db6eecffc37 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -102,7 +102,7 @@ var CatCmd = &cmds.Command{ req := res.Request() progressExplicit, specified := req.Options[progressOptionName].(bool) - if (specified && progressExplicit) || (!specified && isStderrTTY()) { + if (specified && progressExplicit) || (!specified && cmdenv.IsTerminal(os.Stderr)) { var bar *pb.ProgressBar bar, reader = progressBarForReader(os.Stderr, val, int64(res.Length())) bar.Start() diff --git a/core/commands/cmdenv/tty.go b/core/commands/cmdenv/tty.go new file mode 100644 index 00000000000..5bf232dd0aa --- /dev/null +++ b/core/commands/cmdenv/tty.go @@ -0,0 +1,14 @@ +package cmdenv + +import ( + "os" + + "github.com/mattn/go-isatty" +) + +// IsTerminal reports whether f is connected to a terminal, +// including MSYS/Cygwin-style terminals on Windows. +func IsTerminal(f *os.File) bool { + fd := f.Fd() + return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) +} diff --git a/core/commands/dag/export.go b/core/commands/dag/export.go index 2cd0e0a561b..137423a7fda 100644 --- a/core/commands/dag/export.go +++ b/core/commands/dag/export.go @@ -102,10 +102,7 @@ func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { var showProgress bool val, specified := res.Request().Options[progressOptionName] if !specified { - // default based on TTY availability - if errStat, err := os.Stderr.Stat(); err == nil { - showProgress = (errStat.Mode() & os.ModeCharDevice) != 0 - } + showProgress = cmdenv.IsTerminal(os.Stderr) } else if val.(bool) { showProgress = true } diff --git a/core/commands/dag/stat.go b/core/commands/dag/stat.go index 0ce1e4246f4..752d9b53c3c 100644 --- a/core/commands/dag/stat.go +++ b/core/commands/dag/stat.go @@ -99,10 +99,7 @@ func finishCLIStat(res cmds.Response, re cmds.ResponseEmitter) error { var showProgress bool val, specified := res.Request().Options[progressOptionName] if !specified { - // Auto-detect: show progress only if stderr is a TTY - if errStat, err := os.Stderr.Stat(); err == nil { - showProgress = (errStat.Mode() & os.ModeCharDevice) != 0 - } + showProgress = cmdenv.IsTerminal(os.Stderr) } else { showProgress = val.(bool) } diff --git a/core/commands/get.go b/core/commands/get.go index d39fa5d3dd1..8a2fef85880 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -141,7 +141,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. archive, _ := req.Options[archiveOptionName].(bool) progressExplicit, specified := req.Options[progressOptionName].(bool) - showProgress := (specified && progressExplicit) || (!specified && isStderrTTY()) + showProgress := (specified && progressExplicit) || (!specified && cmdenv.IsTerminal(os.Stderr)) gw := getWriter{ Out: os.Stdout, @@ -182,15 +182,6 @@ func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { return bar } -// returns true when os.Stderr is connected to a terminal. -func isStderrTTY() bool { - stat, err := os.Stderr.Stat() - if err != nil { - return false - } - return (stat.Mode() & os.ModeCharDevice) != 0 -} - func getOutPath(req *cmds.Request) string { outPath, _ := req.Options[outputOptionName].(string) if outPath == "" { diff --git a/go.mod b/go.mod index 2f8ea3a5c1d..a85a062daab 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.5 github.com/libp2p/go-libp2p-testing v0.12.0 github.com/libp2p/go-socket-activation v0.1.1 + github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.72 github.com/multiformats/go-multiaddr v0.16.1 github.com/multiformats/go-multiaddr-dns v0.5.0 @@ -180,7 +181,6 @@ require ( github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect From 2f455e80bf4ac70e19c4a9743f3ca30cf8e46aec Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 02:56:07 +0200 Subject: [PATCH 05/13] refactor: cmdenv.ShouldShowProgress helper Collapse the explicit-flag-or-TTY-default logic at four call sites (`cat`, `get`, `dag export`, `dag stat`) into a single helper. --- core/commands/cat.go | 4 +--- core/commands/cmdenv/progress.go | 17 +++++++++++++++++ core/commands/dag/export.go | 11 +---------- core/commands/dag/stat.go | 9 +-------- core/commands/get.go | 3 +-- 5 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 core/commands/cmdenv/progress.go diff --git a/core/commands/cat.go b/core/commands/cat.go index db6eecffc37..d43e67b05c3 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -100,9 +100,7 @@ var CatCmd = &cmds.Command{ case io.Reader: reader := val - req := res.Request() - progressExplicit, specified := req.Options[progressOptionName].(bool) - if (specified && progressExplicit) || (!specified && cmdenv.IsTerminal(os.Stderr)) { + if cmdenv.ShouldShowProgress(res.Request(), progressOptionName) { var bar *pb.ProgressBar bar, reader = progressBarForReader(os.Stderr, val, int64(res.Length())) bar.Start() diff --git a/core/commands/cmdenv/progress.go b/core/commands/cmdenv/progress.go new file mode 100644 index 00000000000..2e35e611841 --- /dev/null +++ b/core/commands/cmdenv/progress.go @@ -0,0 +1,17 @@ +package cmdenv + +import ( + "os" + + cmds "github.com/ipfs/go-ipfs-cmds" +) + +// ShouldShowProgress reports whether a progress bar should be rendered +// based on a boolean option. An explicit `--=true|false` always +// wins; when unset, it defaults to whether stderr is a terminal. +func ShouldShowProgress(req *cmds.Request, flag string) bool { + if v, ok := req.Options[flag].(bool); ok { + return v + } + return IsTerminal(os.Stderr) +} diff --git a/core/commands/dag/export.go b/core/commands/dag/export.go index 137423a7fda..b472bbf11e6 100644 --- a/core/commands/dag/export.go +++ b/core/commands/dag/export.go @@ -99,16 +99,7 @@ func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment } func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { - var showProgress bool - val, specified := res.Request().Options[progressOptionName] - if !specified { - showProgress = cmdenv.IsTerminal(os.Stderr) - } else if val.(bool) { - showProgress = true - } - - // simple passthrough, no progress - if !showProgress { + if !cmdenv.ShouldShowProgress(res.Request(), progressOptionName) { return cmds.Copy(re, res) } diff --git a/core/commands/dag/stat.go b/core/commands/dag/stat.go index 752d9b53c3c..6602789bdb8 100644 --- a/core/commands/dag/stat.go +++ b/core/commands/dag/stat.go @@ -95,14 +95,7 @@ func dagStat(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) } func finishCLIStat(res cmds.Response, re cmds.ResponseEmitter) error { - // Determine whether to show progress based on TTY detection or explicit flag - var showProgress bool - val, specified := res.Request().Options[progressOptionName] - if !specified { - showProgress = cmdenv.IsTerminal(os.Stderr) - } else { - showProgress = val.(bool) - } + showProgress := cmdenv.ShouldShowProgress(res.Request(), progressOptionName) var dagStats *DagStatSummary for { diff --git a/core/commands/get.go b/core/commands/get.go index 8a2fef85880..96fd1525e4b 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -140,8 +140,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. } archive, _ := req.Options[archiveOptionName].(bool) - progressExplicit, specified := req.Options[progressOptionName].(bool) - showProgress := (specified && progressExplicit) || (!specified && cmdenv.IsTerminal(os.Stderr)) + showProgress := cmdenv.ShouldShowProgress(req, progressOptionName) gw := getWriter{ Out: os.Stdout, From 86764fb9fa262f6e79cf1d38d22b87866f584a86 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 02:56:11 +0200 Subject: [PATCH 06/13] refactor: dedupe `ipfs add` progress template The full bar template (counters, bar, speed, percent, ETA) was inlined at two call sites in add.go. Move it to a file-level const. --- core/commands/add.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index 8160a9dc3db..cdf6f2335e0 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -76,6 +76,10 @@ const ( const ( adderOutChanSize = 8 + + // pb/v3 template rendering counters, bar, speed, percent, and ETA + // once the upload total is known. + progressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}` ) var AddCmd = &cmds.Command{ @@ -793,7 +797,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import case size := <-sizeChan: if progress { bar.SetTotal(size) - bar.SetTemplateString(`{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}`) + bar.SetTemplateString(progressBarFullTemplate) } case <-req.Context.Done(): // don't set or print error here, that happens in the goroutine below @@ -803,7 +807,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import if progress && bar.Total() == 0 && bar.Current() != 0 { bar.SetTotal(bar.Current()) - bar.SetTemplateString(`{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}`) + bar.SetTemplateString(progressBarFullTemplate) bar.Write() } } From 8063ef689cc7b2965131f3db15e9b21b5ab1ca48 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:16:54 +0200 Subject: [PATCH 07/13] fix: progress bar shows MiB/s, not MiB p/s pb v3's speed element defaults to suffix "%s p/s", so even with pb.Bytes set, `ipfs add`, `ipfs cat`, `ipfs get`, and `ipfs dag export` rendered the rate as "713.04 MiB p/s" instead of "713.04 MiB/s". Pass explicit format args to the speed and rtime template elements: rate now renders as "MiB/s", and the unknown-state fallback reads "?/s" / "ETA ?" instead of bare "?". The four templates move to package-level consts. --- core/commands/add.go | 14 ++++++++++---- core/commands/dag/export.go | 9 ++++++++- core/commands/get.go | 9 +++++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index cdf6f2335e0..8c0446db1f7 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -77,9 +77,15 @@ const ( const ( adderOutChanSize = 8 - // pb/v3 template rendering counters, bar, speed, percent, and ETA - // once the upload total is known. - progressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . }} {{percent .}} {{rtime . "ETA %s"}}` + // pb/v3 template used before the upload total is known: only the + // running byte counter and current speed. + progressBarInitTemplate = `{{counters . }} {{speed . "%s/s" "?/s"}}` + + // pb/v3 template used once the upload total is known: byte counter, + // bar, speed, percent, and ETA. Explicit format args override pb's + // defaults so the rate renders as "MiB/s" (not "MiB p/s") and the + // remaining time falls back to "ETA ?" while speed is unknown. + progressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` ) var AddCmd = &cmds.Command{ @@ -737,7 +743,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import var bar *pb.ProgressBar if progress { bar = pb.New64(0).Set(pb.Bytes, true).Set(pb.Static, true).SetWriter(os.Stderr) - bar.SetTemplateString(`{{counters . }} {{speed . }}`) + bar.SetTemplateString(progressBarInitTemplate) bar.Start() } diff --git a/core/commands/dag/export.go b/core/commands/dag/export.go index b472bbf11e6..3efad02f46b 100644 --- a/core/commands/dag/export.go +++ b/core/commands/dag/export.go @@ -20,6 +20,13 @@ import ( selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" ) +// pb/v3 template for `ipfs dag export`: byte counter, speed, and +// elapsed time. No bar/percent/ETA because the total size of the +// CAR stream is not known up front. The explicit "%s/s" speed +// format overrides pb's default "p/s" suffix so the rate renders +// as "MiB/s". +const progressBarTemplate = `{{counters . }} {{speed . "%s/s" "?/s"}} {{etime . }}` + func dagExport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { // Accept CID or a content path p, err := cmdutils.PathOrCidPath(req.Arguments[0]) @@ -104,7 +111,7 @@ func finishCLIExport(res cmds.Response, re cmds.ResponseEmitter) error { } bar := pb.New64(0).Set(pb.Bytes, true).SetWriter(os.Stderr).SetRefreshRate(500 * time.Millisecond) - bar.SetTemplateString(`{{counters . }} {{speed . }} {{etime . }}`) + bar.SetTemplateString(progressBarTemplate) bar.Start() var processedOneResponse bool diff --git a/core/commands/get.go b/core/commands/get.go index 96fd1525e4b..1bd1b62cadb 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -29,6 +29,12 @@ const ( archiveOptionName = "archive" compressOptionName = "compress" compressionLevelOptionName = "compression-level" + + // pb/v3 template for downloads with a known total size. Explicit + // format args override pb's defaults so the rate renders as "MiB/s" + // (not "MiB p/s") and the remaining time falls back to "ETA ?" + // while speed is unknown. + progressBarTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` ) var GetCmd = &cmds.Command{ @@ -177,8 +183,7 @@ func progressBarForReader(out io.Writer, r io.Reader, l int64) (*pb.ProgressBar, } func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { - bar := pb.New64(l).Set(pb.Bytes, true).SetWriter(out) - return bar + return pb.New64(l).Set(pb.Bytes, true).SetTemplateString(progressBarTemplate).SetWriter(out) } func getOutPath(req *cmds.Request) string { From b331dac145b0fe90f323b4906251b5c11da56e9d Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:26:06 +0200 Subject: [PATCH 08/13] docs: rewrite v0.42 progress bar entry Describe only the user-visible changes; skip library-migration detail and intermediate-state claims that never shipped. --- docs/changelogs/v0.42.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/changelogs/v0.42.md b/docs/changelogs/v0.42.md index b889849b4d1..52117c293a7 100644 --- a/docs/changelogs/v0.42.md +++ b/docs/changelogs/v0.42.md @@ -17,8 +17,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. - [๐Ÿ› Reliable shutdown and container health checks](#-reliable-shutdown-and-container-health-checks) - [๐Ÿšจ ERROR log for listeners blocked by `Swarm.AddrFilters` or `Addresses.NoAnnounce`](#-error-log-for-listeners-blocked-by-swarmaddrfilters-or-addressesnoannounce) - [๐Ÿ“Š OpenTelemetry: scope info now exposed as labels](#-opentelemetry-scope-info-now-exposed-as-labels) + - [๐Ÿ”ง Cleaner progress bars](#-cleaner-progress-bars) - [๐Ÿ“ฆ๏ธ Dependency updates](#-dependency-updates) - - [๐Ÿ”ง Progress bars migrated to cheggaaa/pb v3, TTY auto-detection fixed](#-progress-bars-migrated-to-cheggaaapbv3-tty-auto-detection-fixed) - [๐Ÿ“ Changelog](#-changelog) - [๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors](#-contributors) @@ -90,6 +90,12 @@ Kubo now logs an ERROR when an [`Addresses.Swarm`](https://github.com/ipfs/kubo/ The Prometheus endpoint no longer emits the `otel_scope_info` metric. Each metric now carries `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels identifying the instrumentation library that produced it. Update dashboards or queries that read `otel_scope_info` to consume these labels instead. See [`docs/metrics.md`](https://github.com/ipfs/kubo/blob/master/docs/metrics.md) for details. +#### ๐Ÿ”ง Cleaner progress bars + +`ipfs add`, `ipfs cat`, and `ipfs get` now hide their progress bar when stderr is piped or redirected, so a command like `ipfs add file 2> log.txt` no longer fills the log with progress-bar noise. Pass `--progress=true` to force the bar on, or `--progress=false` to hide it. + +`ipfs dag export` and `ipfs dag stat` now correctly recognize MSYS2 and Git Bash terminals on Windows. Previously the bar was suppressed there even when running interactively. + #### ๐Ÿ“ฆ๏ธ Dependency updates - update `go-libp2p-pubsub` to [v0.16.0](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.16.0) @@ -97,10 +103,6 @@ The Prometheus endpoint no longer emits the `otel_scope_info` metric. Each metri - update `go-fuse/v2` to [v2.10.1](https://github.com/hanwen/go-fuse/releases/tag/v2.10.1) - update `cheggaaa/pb` to [v3.1.7](https://github.com/cheggaaa/pb/releases/tag/v3.1.7) -#### ๐Ÿ”ง Progress bars migrated to cheggaaa/pb v3, TTY auto-detection fixed - -`ipfs cat`, `ipfs get`, `ipfs add`, and `ipfs dag export` now use `cheggaaa/pb/v3` instead of the stale v1 (which Dependabot kept trying to bump to the incompatible v2 line). Progress bar behavior is unchanged, but a pre-existing bug is fixed: `--progress` previously defaulted to `true` unconditionally, so a progress bar was written to stderr even when stderr was piped or redirected. All four commands now auto-detect whether stderr is a terminal and suppress the bar when it is not, while still respecting an explicit `--progress=true/false` flag. - ### ๐Ÿ“ Changelog ### ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors From a524008a1b0c73829f1c80a775fe2ae64ee0eab6 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:35:01 +0200 Subject: [PATCH 09/13] chore: drop unused pb v1 dependabot ignore The `github.com/cheggaaa/pb` (v1) module path is no longer in `go.mod` after the migration to `pb/v3`, so the ignore rule never fires. --- .github/dependabot.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aad7ca357cf..cde71238334 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,8 +18,6 @@ updates: - dependency-name: "github.com/cockroachdb/pebble*" - dependency-name: "github.com/syndtr/goleveldb" - dependency-name: "github.com/dgraph-io/badger*" - # pb v1/v2 replaced by pb/v3; ignore the old module path - - dependency-name: "github.com/cheggaaa/pb" groups: ipfs-ecosystem: patterns: From a2a72799c6f68ae3742de475e7ec9f74207af07a Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:41:43 +0200 Subject: [PATCH 10/13] fix(dag): unify --progress help text Match the wording used by `add`, `cat`, and `get`: "Stream progress data. Defaults to true when stderr is a terminal." --- core/commands/dag/dag.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/commands/dag/dag.go b/core/commands/dag/dag.go index 0e737ec6c11..e4103581971 100644 --- a/core/commands/dag/dag.go +++ b/core/commands/dag/dag.go @@ -286,7 +286,7 @@ CAR file follows the CARv1 format: https://ipld.io/specs/transport/car/carv1/ cmds.StringArg("root", true, false, "CID of a root to recursively export").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption(progressOptionName, "p", "Display progress on CLI. Defaults to true when STDERR is a TTY."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, Run: dagExport, PostRun: cmds.PostRunMap{ @@ -352,7 +352,7 @@ Note: This command skips duplicate blocks in reporting both size and the number cmds.StringArg("root", true, true, "CID of a DAG root to get statistics for").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption(progressOptionName, "p", "Show progress on stderr. Auto-detected if stderr is a terminal."), + cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."), }, Run: dagStat, Type: DagStatSummary{}, From 7a7a91cf9e7d55e25602d977c950ca13a112a42e Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:41:48 +0200 Subject: [PATCH 11/13] fix(add): finalize progress bar after upload Call `bar.Finish()` and a final `bar.Write()` after the progress loop. Without it, fast adds (under ~500ms, where pb/v3's EWMA never accumulates a speed sample) render `?/s ... ETA ?` in the last frame. Finishing the bar switches the speed element to its absolute-rate branch (total/elapsed), so the final frame now reads e.g. `792.04 MiB/s 100.00% 100ms`. --- core/commands/add.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index 8c0446db1f7..56347f2f2e1 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -811,9 +811,18 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import } } - if progress && bar.Total() == 0 && bar.Current() != 0 { - bar.SetTotal(bar.Current()) - bar.SetTemplateString(progressBarFullTemplate) + if progress { + // If size discovery never reported, treat the + // observed bytes as the total so the final frame + // renders the bar and percent. + if bar.Total() == 0 && bar.Current() != 0 { + bar.SetTotal(bar.Current()) + bar.SetTemplateString(progressBarFullTemplate) + } + // Finish first so the speed element switches to + // the absolute-rate branch (total/elapsed) when + // EWMA never accumulated a sample on fast adds. + bar.Finish() bar.Write() } } From 1da1724099c6bd81ac5ee63023b402dca33468f4 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 03:41:53 +0200 Subject: [PATCH 12/13] test(cmdenv): cover ShouldShowProgress Exercise the explicit-true, explicit-false, unset, and non-bool paths. Unset and non-bool fall back to IsTerminal(os.Stderr), which the test compares against directly so it works in both TTY and CI environments. --- core/commands/cmdenv/progress_test.go | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 core/commands/cmdenv/progress_test.go diff --git a/core/commands/cmdenv/progress_test.go b/core/commands/cmdenv/progress_test.go new file mode 100644 index 00000000000..c33409ad0c5 --- /dev/null +++ b/core/commands/cmdenv/progress_test.go @@ -0,0 +1,46 @@ +package cmdenv + +import ( + "os" + "testing" + + cmds "github.com/ipfs/go-ipfs-cmds" +) + +func TestShouldShowProgress(t *testing.T) { + const flag = "progress" + makeReq := func(opts map[string]any) *cmds.Request { + if opts == nil { + opts = map[string]any{} + } + return &cmds.Request{Options: opts} + } + + t.Run("explicit true wins regardless of TTY", func(t *testing.T) { + if !ShouldShowProgress(makeReq(map[string]any{flag: true}), flag) { + t.Error("expected true for --progress=true") + } + }) + + t.Run("explicit false wins regardless of TTY", func(t *testing.T) { + if ShouldShowProgress(makeReq(map[string]any{flag: false}), flag) { + t.Error("expected false for --progress=false") + } + }) + + t.Run("unset defaults to IsTerminal(stderr)", func(t *testing.T) { + got := ShouldShowProgress(makeReq(nil), flag) + want := IsTerminal(os.Stderr) + if got != want { + t.Errorf("ShouldShowProgress(unset) = %v, want IsTerminal(os.Stderr) = %v", got, want) + } + }) + + t.Run("non-bool value treated as unset", func(t *testing.T) { + got := ShouldShowProgress(makeReq(map[string]any{flag: "yes"}), flag) + want := IsTerminal(os.Stderr) + if got != want { + t.Errorf("ShouldShowProgress(non-bool) = %v, want IsTerminal(os.Stderr) = %v", got, want) + } + }) +} From 9cd18f80110fa4c60134acd477525dcec6ef9636 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 25 May 2026 14:13:14 +0200 Subject: [PATCH 13/13] refactor: share full progress bar template Move the "total known" pb/v3 template to cmdenv.ProgressBarFullTemplate so add.go and get.go reference the same string instead of keeping byte-identical local copies. The add init template and dag/export streaming template stay local because each is single-use and shaped differently. --- core/commands/add.go | 13 ++++--------- core/commands/cmdenv/progress.go | 7 +++++++ core/commands/get.go | 8 +------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index 56347f2f2e1..3d6725822ba 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -78,14 +78,9 @@ const ( adderOutChanSize = 8 // pb/v3 template used before the upload total is known: only the - // running byte counter and current speed. + // running byte counter and current speed. Swapped for + // cmdenv.ProgressBarFullTemplate once size discovery reports. progressBarInitTemplate = `{{counters . }} {{speed . "%s/s" "?/s"}}` - - // pb/v3 template used once the upload total is known: byte counter, - // bar, speed, percent, and ETA. Explicit format args override pb's - // defaults so the rate renders as "MiB/s" (not "MiB p/s") and the - // remaining time falls back to "ETA ?" while speed is unknown. - progressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` ) var AddCmd = &cmds.Command{ @@ -803,7 +798,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import case size := <-sizeChan: if progress { bar.SetTotal(size) - bar.SetTemplateString(progressBarFullTemplate) + bar.SetTemplateString(cmdenv.ProgressBarFullTemplate) } case <-req.Context.Done(): // don't set or print error here, that happens in the goroutine below @@ -817,7 +812,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import // renders the bar and percent. if bar.Total() == 0 && bar.Current() != 0 { bar.SetTotal(bar.Current()) - bar.SetTemplateString(progressBarFullTemplate) + bar.SetTemplateString(cmdenv.ProgressBarFullTemplate) } // Finish first so the speed element switches to // the absolute-rate branch (total/elapsed) when diff --git a/core/commands/cmdenv/progress.go b/core/commands/cmdenv/progress.go index 2e35e611841..a4b32d4b4c9 100644 --- a/core/commands/cmdenv/progress.go +++ b/core/commands/cmdenv/progress.go @@ -6,6 +6,13 @@ import ( cmds "github.com/ipfs/go-ipfs-cmds" ) +// ProgressBarFullTemplate is the pb/v3 template used by transfer +// commands once the total byte count is known: byte counter, bar, +// speed, percent, and ETA. Explicit format args override pb's +// defaults so the rate renders as "MiB/s" (not "MiB p/s") and the +// remaining time falls back to "ETA ?" while speed is unknown. +const ProgressBarFullTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` + // ShouldShowProgress reports whether a progress bar should be rendered // based on a boolean option. An explicit `--=true|false` always // wins; when unset, it defaults to whether stderr is a terminal. diff --git a/core/commands/get.go b/core/commands/get.go index 1bd1b62cadb..f5c7160331d 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -29,12 +29,6 @@ const ( archiveOptionName = "archive" compressOptionName = "compress" compressionLevelOptionName = "compression-level" - - // pb/v3 template for downloads with a known total size. Explicit - // format args override pb's defaults so the rate renders as "MiB/s" - // (not "MiB p/s") and the remaining time falls back to "ETA ?" - // while speed is unknown. - progressBarTemplate = `{{counters . }} {{bar . }} {{speed . "%s/s" "?/s"}} {{percent . }} {{rtime . "ETA %s" "%s" "ETA ?"}}` ) var GetCmd = &cmds.Command{ @@ -183,7 +177,7 @@ func progressBarForReader(out io.Writer, r io.Reader, l int64) (*pb.ProgressBar, } func makeProgressBar(out io.Writer, l int64) *pb.ProgressBar { - return pb.New64(l).Set(pb.Bytes, true).SetTemplateString(progressBarTemplate).SetWriter(out) + return pb.New64(l).Set(pb.Bytes, true).SetTemplateString(cmdenv.ProgressBarFullTemplate).SetWriter(out) } func getOutPath(req *cmds.Request) string {