Add HTTP client injection to gitsync · Entire
Add HTTP client injection to gitsync
cd93f12→main·
Soph·3mo ago·7 files·+101 added/-31 removed
Sessions
15ba661d1843View transcript
Changes
7
internal
gitproto
Msmarthttp.go+8
syncer
Mauth_test.go+22/-2
Mgit_http_backend_test.go+5/-5
Msyncer.go+26/-12
Msyncer_test.go+2/-3
pkg/gitsync
Mclient.go+19/-8
Mclient_test.go+19/-1
49 unmodified lines
50
51
52
53
54
55
56
57
58
59
60
61
62
63
49 unmodified lines
// NewConn creates a new connection to the given endpoint.
func NewConn(ep *transport.Endpoint, label string, auth transport.AuthMethod, rt http.RoundTripper) *Conn {
httpClient := &http.Client{Transport: rt}
return NewConnWithHTTPClient(ep, label, auth, httpClient)
}
// NewConnWithHTTPClient creates a new connection using the provided HTTP client.
func NewConnWithHTTPClient(ep *transport.Endpoint, label string, auth transport.AuthMethod, httpClient *http.Client) *Conn {
if httpClient == nil {
httpClient = &http.Client{Transport: http.DefaultTransport}
}
return &Conn{
Label: label,
Endpoint: ep,
}
}
Minternal/gitproto/smarthttp.go+8
51 unmodified lines
52 53 54 55 55 56 57 58 10 unmodified lines
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 97 unmodified lines
193 194 195 175
51 unmodified lines
conn, err := newConn(Endpoint{ URL: "https://example.com/repo.git", SkipTLSVerify: true, }, "source", stats) }, "source", stats, nil) if err != nil { t.Fatalf("new conn: %v", err) } 10 unmodified lines
} }
func TestNewConnUsesProvidedHTTPClient(t *testing.T) { stats := newStats(false) baseTransport := http.DefaultTransport baseClient := &http.Client{Transport: baseTransport}
conn, err := newConn(Endpoint{URL: "https://example.com/repo.git"}, "source", stats, baseClient) if err != nil { t.Fatalf("new conn: %v", err) } if conn.HTTP == baseClient { t.Fatalf("expected cloned HTTP client, got original pointer") } rt, ok := conn.HTTP.Transport.(*countingRoundTripper) if !ok { t.Fatalf("expected countingRoundTripper, got %T", conn.HTTP.Transport) } if rt.base != baseTransport { t.Fatalf("wrapped base transport = %T, want %T", rt.base, baseTransport) } }
func TestResolveAuthMethodUsesEntireDBStoredToken(t *testing.T) { configDir := t.TempDir() tokenStorePath := filepath.Join(t.TempDir(), "tokens.json")
97 unmodified lines
t.Fatalf("write hosts: %v", err) } }
Minternal/syncer/auth_test.go+22/-2
514 unmodified lines
515 516 517 518 518 519 520 521 90 unmodified lines
612 613 614 615 615 616 617 618 13 unmodified lines
632 633 634 635 635 636 637 638 637 638 639 640 641
514 unmodified lines
}
stats := newStats(false) sourceConn, err := newConn(cfg.Source, "source", stats) sourceConn, err := newConn(cfg.Source, "source", stats, nil) if err != nil { t.Fatalf("create source transport: %v", err) } 90 unmodified lines
}
stats := newStats(false) sourceConn, err := newConn(cfg.Source, "source", stats) sourceConn, err := newConn(cfg.Source, "source", stats, nil) if err != nil { t.Fatalf("create source transport: %v", err) } 13 unmodified lines
plan := func(limit int64) []plumbing.Hash { t.Helper() checkpoints, err := bstrap.PlanCheckpoints(context.Background(), bstrap.Params{ SourceConn: sourceConn, SourceConn: sourceConn, SourceService: sourceService, BatchMaxPack: limit, Verbose: cfg.Verbose, BatchMaxPack: limit, Verbose: cfg.Verbose, }, ref) if err != nil { t.Fatalf("plan checkpoints with limit %d: %v", limit, err) } }
Minternal/syncer/git_http_backend_test.go+5/-5
8 unmodified lines
9
10
11
12
13
14
15
39 unmodified lines
55
56
57
58
59
60
61
211 unmodified lines
273
274
275
274
276
277
278
279
8 unmodified lines
288
289
290
289
290
291
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
22 unmodified lines
331
332
333
320
321
322
323
324
325
334
335
336
337
338
339
340
341
342
19 unmodified lines
362
363
364
351
365
366
367
368
7 unmodified lines
376
377
378
365
379
380
381
382
8 unmodified lines
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"sort"
"strings"
39 unmodified lines
type Config struct {
Source Endpoint
Target Endpoint
HTTPClient *http.Client
Branches []string
Mappings []RefMapping
IncludeTags bool
211 unmodified lines
// --- Session setup ---
func newConn(raw Endpoint, label string, stats *statsCollector) (*gitproto.Conn, error) {
func newConn(raw Endpoint, label string, stats *statsCollector, httpClient *http.Client) (*gitproto.Conn, error) {
ep, err := transport.NewEndpoint(raw.URL)
if err != nil {
return nil, err
}
8 unmodified lines
if err != nil {
return nil, err
}
baseRT := gitproto.NewHTTPTransport(raw.SkipTLSVerify)
rt := &countingRoundTripper{base: baseRT, label: label, stats: stats}
return gitproto.NewConn(ep, label, authMethod, rt), nil
client := instrumentHTTPClient(httpClient, raw.SkipTLSVerify, label, stats)
return gitproto.NewConnWithHTTPClient(ep, label, authMethod, client), nil
}
func instrumentHTTPClient(base *http.Client, skipTLS bool, label string, stats *statsCollector) *http.Client {
if base == nil {
base = &http.Client{Transport: gitproto.NewHTTPTransport(skipTLS)}
}
clone := *base
baseRT := clone.Transport
if baseRT == nil {
baseRT = gitproto.NewHTTPTransport(skipTLS)
}
clone.Transport = &countingRoundTripper{base: baseRT, label: label, stats: stats}
return &clone
}
func planConfig(cfg Config) planner.PlanConfig {
22 unmodified lines
}
type targetSession struct {
conn *gitproto.Conn
adv *packp.AdvRefs
refMap map[plumbing.ReferenceName]plumbing.Hash
features gitproto.TargetFeatures
policy planner.RelayTargetPolicy
pusher gitproto.Pusher
conn *gitproto.Conn
adv *packp.AdvRefs
refMap map[plumbing.ReferenceName]plumbing.Hash
features gitproto.TargetFeatures
policy planner.RelayTargetPolicy
pusher gitproto.Pusher
}
// newSession performs the shared setup: protocol validation, mapping validation,
19 unmodified lines
}))
s.sourceConn, err = newConn(cfg.Source, "source", s.stats)
s.sourceConn, err = newConn(cfg.Source, "source", s.stats, cfg.HTTPClient)
if err != nil {
return nil, fmt.Errorf("create source transport: %w", err)
}
7 unmodified lines
s.sourceRefMap = gitproto.RefHashMap(sourceRefs)
if needTarget {
targetConn, err := newConn(cfg.Target, "target", s.stats)
targetConn, err := newConn(cfg.Target, "target", s.stats, cfg.HTTPClient)
if err != nil {
return nil, fmt.Errorf("create target transport: %w", err)
}
}
Minternal/syncer/syncer.go+26/-12
7 unmodified lines
8 9 10 11 11 12 13 14 8 unmodified lines
23 24 25 26 26 27 28 29 1 unmodified line
31 32 33 34
7 unmodified lines
func TestGitHubOwnerRepo(t *testing.T) { stats := newStats(false) conn, err := newConn(Endpoint{URL: "https://github.com/torvalds/linux.git"}, "source", stats) conn, err := newConn(Endpoint{URL: "https://github.com/torvalds/linux.git"}, "source", stats, nil) if err != nil { t.Fatalf("new conn: %v", err) } 8 unmodified lines
func TestGitHubOwnerRepoRejectsNonGitHubSource(t *testing.T) { stats := newStats(false) conn, err := newConn(Endpoint{URL: "https://gitlab.com/group/project.git"}, "source", stats) conn, err := newConn(Endpoint{URL: "https://gitlab.com/group/project.git"}, "source", stats, nil) if err != nil { t.Fatalf("new conn: %v", err) } 1 unmodified line
t.Fatalf("expected non-github source to be rejected") } }
Minternal/syncer/syncer_test.go+2/-3
2 unmodified lines
3
4
5
6
7
8
9
10
11
11
12
13
14
15
16
14
17
18
19
20
21
22
18
19
23
24
25
26
1 unmodified line
28
29
30
27
31
32
33
34
5 unmodified lines
40
41
42
39
43
44
45
46
5 unmodified lines
52
53
54
51
55
56
57
58
13 unmodified lines
72
73
74
71
75
76
77
78
79
80
81
82
83
84
85
86
87
88
2 unmodified lines
import (
"context"
"fmt"
"net/http"
"github.com/soph/git-sync/internal/syncer"
)
// Options configures a Client. It is intentionally small in the first public cut.
type Options struct{}
type Options struct {
HTTPClient *http.Client
}
// Client provides the public orchestration API for git-sync.
type Client struct{}
type Client struct {
httpClient *http.Client
}
// New constructs a new Client.
func New(opts Options) *Client {
_ = opts
return &Client{}
return &Client{httpClient: opts.HTTPClient}
}
// Probe inspects a source remote and optional target remote.
1 unmodified line
if err := req.Validate(); err != nil {
return ProbeResult{}, err
}
result, err := syncer.Probe(ctx, buildProbeConfig(req))
result, err := syncer.Probe(ctx, c.buildProbeConfig(req))
if err != nil {
return ProbeResult{}, err
}
5 unmodified lines
if err := req.Validate(); err != nil {
return PlanResult{}, err
}
result, err := syncer.Run(ctx, buildSyncConfig(req.Source, req.SourceAuth, req.Target, req.TargetAuth, req.Scope, req.Policy, req.CollectStats, true))
result, err := syncer.Run(ctx, c.buildSyncConfig(req.Source, req.SourceAuth, req.Target, req.TargetAuth, req.Scope, req.Policy, req.CollectStats, true))
if err != nil {
return PlanResult{}, err
}
5 unmodified lines
if err := req.Validate(); err != nil {
return SyncResult{}, err
}
result, err := syncer.Run(ctx, buildSyncConfig(req.Source, req.SourceAuth, req.Target, req.TargetAuth, req.Scope, req.Policy, req.CollectStats, false))
result, err := syncer.Run(ctx, c.buildSyncConfig(req.Source, req.SourceAuth, req.Target, req.TargetAuth, req.Scope, req.Policy, req.CollectStats, false))
if err != nil {
return SyncResult{}, err
}
13 unmodified lines
return cfg
}
func buildSyncConfig(source Endpoint, sourceAuth EndpointAuth, target Endpoint, targetAuth EndpointAuth, scope RefScope, policy SyncPolicy, collectStats, dryRun bool) syncer.Config {
func (c *Client) buildProbeConfig(req ProbeRequest) syncer.Config {
cfg := buildProbeConfig(req)
cfg.HTTPClient = c.httpClient
return cfg
}
func (c *Client) buildSyncConfig(source Endpoint, sourceAuth EndpointAuth, target Endpoint, targetAuth EndpointAuth, scope RefScope, policy SyncPolicy, collectStats, dryRun bool) syncer.Config {
return syncer.Config{
Source: syncer.Endpoint{URL: source.URL, Username: sourceAuth.Username, Token: sourceAuth.Token, BearerToken: sourceAuth.BearerToken, SkipTLSVerify: sourceAuth.SkipTLSVerify},
Target: syncer.Endpoint{URL: target.URL, Username: targetAuth.Username, Token: targetAuth.Token, BearerToken: targetAuth.BearerToken, SkipTLSVerify: targetAuth.SkipTLSVerify},
HTTPClient: c.httpClient,
Branches: append([]string(nil), scope.Branches...),
Mappings: append([]RefMapping(nil), scope.Mappings...),
IncludeTags: policy.IncludeTags,
}