trees: show its phases as N of M, with progress, elapsed time and ETA (closes #113)
check / check (push) Canceled after 0s

trees now runs in four phases, open, read, group and write, each under
the progress display scan uses, until the summary line. open covers a
wait on a busy database. read counts the records and reads them in one
read-only transaction, so the two agree during a scan; group counts the
directories, and write the groups. The rows reach stdout through a
writer. When stdout and stderr are the same file, it clears the display
and holds its lock while writing, so no row shares a line with the
display on a shared terminal; otherwise it writes without the lock, so
a slow reader of stdout cannot stop the display. Each rate names what
its phase counts.

Judgement call: opening the database is a phase of its own, so there are four phases, not the three the issue sketches.

Model: opus-5-5
This commit was merged in pull request #121.
This commit is contained in:
2026-10-16 21:00:14 +02:00
parent 8dc73aa3f5
commit 8e0facdcd3
11 changed files with 643 additions and 99 deletions
+10 -5
View File
@@ -84,6 +84,10 @@ type fileMeta struct {
hashed bool
}
// scanUnit is what every scan phase counts, as its progress display
// names it.
const scanUnit = "files"
// scanPhase names one phase of a scan with its place among all of them,
// as "[2/5] walk", for the phase's progress display. The list here is
// the one place the phases are counted.
@@ -441,7 +445,7 @@ func (s *scanState) loadIndex(ctx context.Context, roots []string) error {
// display the scan looks hung before the walk begins. Counting them
// first gives the display its total.
prog := newProgress(progressParams{
label: scanPhase("load"), total: -1,
label: scanPhase("load"), unit: scanUnit, total: -1,
waitingFor: "counting the records",
})
defer prog.finish()
@@ -492,7 +496,8 @@ func (s *scanState) walkPhase(
}
prog := newProgress(progressParams{
label: scanPhase("walk"), total: estimate, estimate: true,
label: scanPhase("walk"), unit: scanUnit, total: estimate,
estimate: true,
})
var (
@@ -683,7 +688,7 @@ func (s *scanState) readRuns(ctx context.Context, workers int,
defer pool.stop()
prog := newProgress(progressParams{
label: scanPhase(label), total: int64(len(runs)),
label: scanPhase(label), unit: scanUnit, total: int64(len(runs)),
})
defer prog.finish()
@@ -772,7 +777,7 @@ func (s *scanState) updatePhase(ctx context.Context) error {
total := len(s.batch) + len(s.sentinels) + len(deletes)
prog := newProgress(progressParams{
label: scanPhase("update"), total: int64(total),
label: scanPhase("update"), unit: scanUnit, total: int64(total),
})
defer prog.finish()
@@ -860,7 +865,7 @@ func (s *scanState) contentCandidates(
// record. The checks start there, against a count of the records
// the search returns, and so do their rate and ETA.
prog := newProgress(progressParams{
label: scanPhase("content"), total: -1,
label: scanPhase("content"), unit: scanUnit, total: -1,
waitingFor: "searching the database",
})
defer prog.finish()