syncer: warn that SSH stats and progress are incomplete · Entire

syncer: warn that SSH stats and progress are incomplete

9aa3e87→main ·
Soph·2mo ago·2 files·+87 added/-2 removed

Sessions

1ae05f312d98 View transcript

?\ yes, start implementing, make a new branch, make meaningful commits, and add tests as you go, when it makes sense create tests first Codex·GPT-5.4·1 step

Changes

2

535 unmodified lines

536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
83 unmodified lines

644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
5 unmodified lines

671
672
673
674
675
676
677
10 unmodified lines

688
689
690
691
692
693
694
22 unmodified lines

717
718
719
683
720
721
722

535 unmodified lines

return false
}

func sshStatsWarning(cfg Config, sourceConn, targetConn gitproto.Conn) string {
    if !cfg.Progress && !cfg.ShowStats {
        return ""
    }
    hasSSH := false
    if _, ok := sourceConn.(*gitproto.SSHConn); ok {
        hasSSH = true
    }
    if !hasSSH && targetConn != nil {
        if _, ok := targetConn.(*gitproto.SSHConn); ok {
            hasSSH = true
        }
    }
    if !hasSSH {
        return ""
    }
    return "warning: SSH transport does not yet expose byte-counted throughput; --progress and --show-stats output will omit SSH transfer bytes"
}

// --- Session setup (issue #12) ---

// syncSession holds the shared state for a sync operation, reducing
83 unmodified lines

stats:           newStats(cfg.ShowStats),
    measurementDone: startMeasurement(cfg.MeasureMemory),
}
var warnedSSHStats bool
warnSSHStats := func(sourceConn, targetConn gitproto.Conn) {
    if warnedSSHStats {
        return
    }
    warning := sshStatsWarning(cfg, sourceConn, targetConn)
    if warning == "" {
        return
    }
    warnedSSHStats = true
    out := cfg.progressOut
    if out == nil {
        out = os.Stderr
    }
    fmt.Fprintln(out, warning)
}
if cfg.Verbose {
    s.logger = slog.New(slog.NewTextHandler(&sessionStderr{s: s}, &slog.HandlerOptions{
        Level: slog.LevelInfo,
5 unmodified lines

return nil, fmt.Errorf("create source transport: %w", err)
}
s.sourceConn.SetProgressWriter(&sessionStderr{s: s})
warnSSHStats(s.sourceConn, nil)

refPrefixes := planner.RefPrefixes(planConfig(cfg))
sourceRefs, sourceService, err := gitproto.ListSourceRefs(ctx, s.sourceConn, cfg.ProtocolMode, refPrefixes)
10 unmodified lines

return nil, fmt.Errorf("create target transport: %w", err)
    }
targetConn.SetProgressWriter(&sessionStderr{s: s})
warnSSHStats(s.sourceConn, targetConn)
targetAdv, err := gitproto.AdvertisedRefsV1(ctx, targetConn, transport.ReceivePackService)
if err != nil {
    return nil, fmt.Errorf("list target refs: %w", err)
}
22 unmodified lines

}
        }
    }

// Start the live progress ticker only after auth resolution and the
    // initial ref-listing round trips have completed. The auth path may
    // shell out to `git credential fill`, which inherits our stderr and

Minternal/syncer/syncer.go +37/-1

1 unmodified line

2
3
4
5
6
7
8
9
236 unmodified lines

246
247
248
249
250
251
252
253
248
254
255
256
257
14 unmodified lines

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317

1 unmodified line

import (
    "context"
    "os"
    "path/filepath"
    "strings"
    "testing"

236 unmodified lines

func TestNewConnBuildsSSHTransport(t *testing.T) {
    orig := gitproto.SSHLookPath
    t.Cleanup(func() { gitproto.SSHLookPath = orig })
    script := filepath.Join(t.TempDir(), "ssh-stub.sh")
    if err := os.WriteFile(script, []byte("#!/bin/sh\nexit 1\n"), 0o755); err != nil {
        t.Fatalf("write ssh stub: %v", err)
    }
    gitproto.SSHLookPath = func(string) (string, error) {
        return "/usr/bin/ssh", nil
        return script, nil
    }

stats := newStats(false)
14 unmodified lines

})
    }
}

func TestSSHStatsWarning(t *testing.T) {
    tests := []struct {
        name   string
        cfg    Config
        source gitproto.Conn
        target gitproto.Conn
        want   bool
    }{
        {
        name:   "no flags",
        cfg:    Config{},
        source: &gitproto.SSHConn{},
        },
        {
        name:   "http only",
        cfg:    Config{Progress: true},
        source: &gitproto.HTTPConn{},
        target: &gitproto.HTTPConn{},
        },
        {
        name:   "progress with ssh source",
        cfg:    Config{Progress: true},
        source: &gitproto.SSHConn{},
        want:   true,
        },
        {
        name:   "show stats with ssh target",
        cfg:    Config{ShowStats: true},
        source: &gitproto.HTTPConn{},
        target: &gitproto.SSHConn{},
        want:   true,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := sshStatsWarning(tt.cfg, tt.source, tt.target)
            if (got != "") != tt.want {
                t.Fatalf("sshStatsWarning() = %q, want warning=%t", got, tt.want)
            }
        })
    }
}