Update sideband '\r' progress in place above the throughput ticker · Entire

Update sideband '\r' progress in place above the throughput ticker

2b634e4→main·

Sideband progress from go-git ("source: Counting objects: 10%\r" → "Counting objects: 11%\r") was scrolling the scrollback because sessionStderr treated '\r' and '\n' as equivalent line ends and funneled both through notify(), which always emits a fresh row. The user gets one row per percentage update, which buries the useful state.

Promote the renderer to a 2-row live region: an optional transient row above (driven by '\r'-terminated sideband chunks) and the throughput ticker below. Each draw repositions the cursor to the top of the region with '\x1b[%dA\r', erases to end of screen with '\x1b[J', and re-emits both rows. sessionStderr now distinguishes terminators: '\r' calls setTransient (immediate redraw, in-place overwrite), '\n' calls notify (scrolls a permanent line above the region and clears the now-stale transient).

Result: "Compressing 89% (263/295)" updates a single row in place the way git itself shows it, while the throughput ticker keeps running below and slog/notice lines still scroll cleanly above.

Sessions

b2292eb61fceView transcript

Changes

11 unmodified lines

12
13
14
15
16
15
16
17
18
19
20
21
22
23
24
25
4 unmodified lines

30
31
32
27
28
33
34
35
36
37
38
39
25 unmodified lines

65
66
67
60
61
62
63
64
65
68
69
70
71
72
73
67
68
69
70
71
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
75
76
77
117
118
119
79
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
5 unmodified lines

141
142
143
91
144
145
146
147
29 unmodified lines

177
178
179
127
128
180
181
182
183
99 unmodified lines

283
284
285
286
287
288
289
290
291
235
292
293
294
295
296
297
298
299

11 unmodified lines
```go
// progressReporter renders live per-side throughput to a writer (typically
// os.Stderr) by sampling the statsCollector's atomic byte counters on a
// fixed interval. It is intentionally a one-line in-place renderer so it
// stays out of the way of the final command output.
// fixed interval.

// The visible region is at most two rows: an optional "transient" line
// above (used for in-place sideband progress like "source: Compressing
// objects: 89%") and the throughput ticker below. Each redraw uses
// cursor-up + erase-to-end-of-screen to overwrite the whole region in
// place, so '\r'-terminated sideband updates from go-git read as a
// single updating row instead of scrolling line by line.

// Type progressReporter struct {...}

const clearLine = "\r\x1b[2K"
// ANSI control sequences we use to redraw the live region in place.
//...

// notify writes a one-time message above the live progress line. The
// current frame is cleared first so the message lands on a clean row,
// and lastLen is reset so the next tick redraws the progress below.
// Safe to call from any goroutine, including while the ticker is
// running.
//...

```go
// TestSessionStderrCRUpdatesTransient verifies that '\r'-terminated
// sideband progress (git's "Compressing 89%\r" → "Compressing 90%\r"
// pattern) goes to the transient row instead of scrolling the
// scrollback. Subsequent updates should overwrite the transient slot
// rather than each landing on a new row.
func TestSessionStderrCRUpdatesTransient(t *testing.T) {

t.Parallel()
stats := newStats(true)
stats.setSideDisplay("source", "github.com")
stats.side("source").bytes.Store(1024)

var buf bytes.Buffer
p := newProgressReporter(&buf, stats, 0)
p.render(false)

sess := &syncSession{progress: p}
sink := &sessionStderr{s: sess}

// Two consecutive in-place sideband updates plus one final \n line.
if _, err := sink.Write([]byte("source: Compressing 50%\r")); err != nil {
    t.Fatalf("write: %v", err)
}
if _, err := sink.Write([]byte("source: Compressing 75%\r")); err != nil {
    t.Fatalf("write: %v", err)
}
if _, err := sink.Write([]byte("source: Compressing 100%, done.\n")); err != nil {
    t.Fatalf("write: %v", err)
}

if p.transient != "" {
    t.Errorf("permanent line should clear transient, got %q", p.transient)
}
out := buf.String()
if !strings.Contains(out, "Compressing 50%") ||
    !strings.Contains(out, "Compressing 75%") ||
    !strings.Contains(out, "Compressing 100%, done.") {
    t.Errorf("all three sideband states should be in the output stream:
%s", out)
}
}