Skip to content

Commit 2754b40

Browse files
authored
Merge pull request #1110 from entireio/migration-completion-output
Checkpoints v2 migration: Keep user in the loop after the progress bar completes
2 parents 4fae3ed + 967f525 commit 2754b40

4 files changed

Lines changed: 43 additions & 35 deletions

File tree

cmd/entire/cli/explain.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ func runExplain(ctx context.Context, w, errW io.Writer, sessionID, commitRef, ch
377377
func runExplainAuto(ctx context.Context, w, errW io.Writer, target string, noPager, verbose, full, rawTranscript, generate, force, searchAll bool) error {
378378
stop := startSpinner(errW, "Loading checkpoints")
379379
lookup, lookupErr := newExplainCheckpointLookup(ctx)
380-
stop("")
380+
stop(false)
381381
if generate {
382382
if err := runExplainAutoAmbiguityGuard(ctx, target, lookup, lookupErr); err != nil {
383383
return err
@@ -524,7 +524,7 @@ func runExplainCheckpointWithLookup(ctx context.Context, w, errW io.Writer, chec
524524
v2OK = true
525525
}
526526
}
527-
stop("")
527+
stop(false)
528528
if v1Err == nil || v2OK {
529529
if freshLookup, freshErr := newExplainCheckpointLookup(ctx); freshErr == nil {
530530
lookup = freshLookup
@@ -586,14 +586,14 @@ func runExplainCheckpointWithLookup(ctx context.Context, w, errW io.Writer, chec
586586

587587
resolvedReader, summary, content, err := loadCheckpointForExplain(ctx, errW, lookup, fullCheckpointID, full, generate, rawTranscript)
588588
if err != nil {
589-
stopLoad("")
589+
stopLoad(false)
590590
return err
591591
}
592592
v2Reader, isCheckpointsV2 := resolvedReader.(*checkpoint.V2GitStore)
593593

594594
// Handle summary generation — uses raw transcript.
595595
if generate {
596-
stopLoad("") // generation prints its own progress to w/errW
596+
stopLoad(false) // generation prints its own progress to w/errW
597597
if err := generateCheckpointSummary(ctx, w, errW, lookup.v1Store, lookup.v2Store, fullCheckpointID, summary, content, force); err != nil {
598598
return err
599599
}
@@ -606,14 +606,14 @@ func runExplainCheckpointWithLookup(ctx context.Context, w, errW io.Writer, chec
606606
content, err = readLatestSessionContentForExplain(ctx, resolvedReader, fullCheckpointID, summary)
607607
}
608608
if err != nil {
609-
stopLoad("")
609+
stopLoad(false)
610610
return fmt.Errorf("failed to reload checkpoint: %w", err)
611611
}
612612
}
613613

614614
// Handle raw transcript output
615615
if rawTranscript {
616-
stopLoad("")
616+
stopLoad(false)
617617
rawLog, _, rawErr := checkpoint.ResolveRawSessionLogForCheckpoint(ctx, fullCheckpointID, lookup.v1Store, lookup.v2Store, lookup.preferCheckpointsV2)
618618
if rawErr != nil {
619619
return fmt.Errorf("failed to read raw transcript: %w", rawErr)
@@ -646,7 +646,7 @@ func runExplainCheckpointWithLookup(ctx context.Context, w, errW io.Writer, chec
646646

647647
// Format and output. Stop spinner BEFORE any write to w to keep stderr
648648
// frames and stdout content from interleaving.
649-
stopLoad("")
649+
stopLoad(false)
650650
output := formatCheckpointOutput(summary, content, fullCheckpointID, associatedCommits, author, verbose, full, w)
651651
outputExplainContent(w, output, noPager)
652652
return nil

cmd/entire/cli/migrate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,14 @@ func runMigrateCheckpointsV2(ctx context.Context, cmd *cobra.Command, force bool
102102
// AggregateTranscriptTimestamps in the packer.
103103
var repairResult *strategy.RepairV2GenerationMetadataResult
104104
if len(freshlyPackedRefs) > 0 {
105+
stopRepair := startSpinner(cmd.ErrOrStderr(), "Repairing archived generation metadata")
105106
var repairErr error
106107
repairResult, repairErr = strategy.RepairV2GenerationMetadata(ctx, freshlyPackedRefs)
107108
if repairErr != nil {
109+
stopRepair(false)
108110
return fmt.Errorf("failed to repair archived v2 generation metadata: %w", repairErr)
109111
}
112+
stopRepair(true)
110113
printV2GenerationRepairResult(out, cmd.ErrOrStderr(), repairResult)
111114
}
112115

@@ -256,9 +259,13 @@ func migrateCheckpointsV2(ctx context.Context, repo *git.Repository, v1Store *ch
256259
progress.Increment()
257260
}
258261

262+
progress.Finish()
263+
stopFinalize := startSpinner(progressOut, "Packing migrated raw transcripts")
259264
if err := packer.finalize(ctx, !fullCurrentExistsBefore); err != nil {
265+
stopFinalize(false)
260266
return result, packer.writtenRefs, fmt.Errorf("failed to pack migrated raw transcripts: %w", err)
261267
}
268+
stopFinalize(true)
262269

263270
return result, packer.writtenRefs, nil
264271
}

cmd/entire/cli/migrate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ func TestMigrateCheckpointsV2_ForceOverwritesExisting(t *testing.T) {
425425
require.NoError(t, err)
426426
assert.Equal(t, 1, result3.migrated)
427427
assert.Equal(t, 0, result3.skipped)
428-
assert.Empty(t, stdout.String())
428+
assert.Equal(t, "✓ Packing migrated raw transcripts\n", stdout.String())
429429

430430
// Verify checkpoint still readable in v2
431431
summary, readErr := v2Store.ReadCommitted(context.Background(), cpID)
@@ -580,7 +580,7 @@ func TestMigrateCmd_RepairsArchivedGenerationMetadata(t *testing.T) {
580580

581581
require.NoError(t, cmd.Execute())
582582
assert.Contains(t, stdout.String(), "Archived generation metadata repair: 1 repaired")
583-
assert.Empty(t, stderr.String())
583+
assert.Equal(t, "✓ Packing migrated raw transcripts\n✓ Repairing archived generation metadata\n", stderr.String())
584584

585585
v2Store := checkpoint.NewV2GitStore(repo, migrateRemoteName)
586586
gen, genErr := v2Store.ReadGenerationFromRef(plumbing.ReferenceName(paths.V2FullRefPrefix + "0000000000007"))
@@ -1129,7 +1129,7 @@ func TestMigrateCheckpointsV2_CompactionSkipped(t *testing.T) {
11291129
require.NoError(t, migrateErr)
11301130
assert.Equal(t, 1, result.migrated)
11311131
assert.Equal(t, 1, result.compactTranscriptSkipped)
1132-
assert.Empty(t, stdout.String())
1132+
assert.Equal(t, "✓ Packing migrated raw transcripts\n", stdout.String())
11331133
}
11341134

11351135
func TestMigrateCheckpointsV2_TaskCheckpoint(t *testing.T) {

cmd/entire/cli/progress.go

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,15 @@ const (
2222
)
2323

2424
// startSpinner prints msg followed by an animated spinner to w when the
25-
// operation takes longer than spinnerInitialDelay. Returns a stop function
26-
// that clears the spinner line and prints suffix (with a newline) if
27-
// non-empty. Fast operations that call stop before the initial delay
28-
// elapses produce no output at all.
29-
//
30-
// When w is not a terminal (CI, redirected output, agent subprocess), the
31-
// spinner and the suppression message are both omitted — non-interactive
32-
// callers get clean output without progress chatter.
33-
func startSpinner(w io.Writer, msg string) func(suffix string) {
25+
// operation takes longer than spinnerInitialDelay. stop(true) leaves
26+
// "✓ msg" on the line; stop(false) erases the line and writes nothing.
27+
// On non-terminal writers the animation is omitted but stop(true) still
28+
// prints the completion line.
29+
func startSpinner(w io.Writer, msg string) func(success bool) {
3430
if !interactive.IsTerminalWriter(w) {
35-
return func(suffix string) {
36-
if suffix != "" {
37-
fmt.Fprintln(w, suffix)
31+
return func(success bool) {
32+
if success {
33+
fmt.Fprintf(w, "✓ %s\n", msg)
3834
}
3935
}
4036
}
@@ -63,25 +59,25 @@ func startSpinner(w io.Writer, msg string) func(suffix string) {
6359
}
6460
}
6561
}()
66-
return func(suffix string) {
62+
return func(success bool) {
6763
close(done)
6864
<-stopped
69-
// \r\033[K is a no-op on a line that was never drawn; on a drawn
70-
// line it returns the cursor and clears it.
71-
fmt.Fprint(w, "\r\033[K")
72-
if suffix != "" {
73-
fmt.Fprintln(w, suffix)
65+
if success {
66+
fmt.Fprintf(w, "\r\033[K✓ %s\n", msg)
67+
return
7468
}
69+
fmt.Fprint(w, "\r\033[K")
7570
}
7671
}
7772

7873
type progressBar struct {
79-
w io.Writer
80-
label string
81-
total int
82-
current int
83-
width int
84-
enabled bool
74+
w io.Writer
75+
label string
76+
total int
77+
current int
78+
width int
79+
enabled bool
80+
finished bool
8581
}
8682

8783
func startProgressBar(w io.Writer, label string, total int) *progressBar {
@@ -112,7 +108,12 @@ func (p *progressBar) Increment() {
112108
}
113109

114110
func (p *progressBar) Finish() {
115-
if !p.enabled {
111+
if !p.enabled || p.finished {
112+
return
113+
}
114+
p.finished = true
115+
if p.current >= p.total {
116+
fmt.Fprintln(p.w)
116117
return
117118
}
118119
fmt.Fprint(p.w, "\r\033[K")

0 commit comments

Comments
 (0)