Migrate from go-git v5 to v6 · Entire

Home

Log in

Migrate from go-git v5 to v6

749b51f→main·

Soph·3mo ago·41 files·+642 added/-749 removed

Switch to go-git/go-git/v6 v6.0.0-alpha.1 and go-billy/v6. The v6 API removes the transport session abstraction (UploadPackSession, ReceivePackSession) and changes Endpoint to embed url.URL. Rather than adapting to v6's new session model, the v1 fetch and push paths were rewritten to use direct HTTP protocol handling via PostRPCStream, which eliminates the dual-stack transport problem called out in the rewrite memo and aligns with the goal of owning the smart HTTP layer directly.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Sessions

b5b72cefafb3View transcript

?\ Can you take a look at the go code (wasm) in /Users/soph/Work/entire/devenv/entire-io-worktree1 based a bit on that I wonder if something like this can be build:Codex·GPT-5.4·1 step

Changes

41

8 unmodified lines

9
10
11
12
12
13
14
15

8 unmodified lines

"os"
    "strings"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/soph/git-sync/internal/syncer"
)

Mcmd/git-sync/main.go+1/-1

13 unmodified lines

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
17
18
19
20
21
22
23
24
25
26
27
56 unmodified lines

84
85
86
91
87
88
89
90
84 unmodified lines

175
176
177
182
178
179
180
181
99 unmodified lines

281
282
283
288
289
284
285
286
287
288
289
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
290
318
291
292
293
294
5 unmodified lines

300
301
302
330
331
332
333
334
335
303
304
305
337
306
307
308
309
310
311
312
313
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
314
315
316
5 unmodified lines

322
323
324
372
373
374
375
376
377
325
326
327
379
380
381
382
383
328
329
330
331
385
386
387
388
332
333
334
335
390
391
392
393
394
395
396
397
398
336
337
338
339
340
404
405
341
342
343
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
344
345
346
425
426
347
348
349
350
351
352
428
429
430
431
432
433
434
435
436
437
353
354

13 unmodified lines

"testing"
    "time"

billy "github.com/go-git/go-billy/v5"
    "github.com/go-git/go-billy/v5/memfs"
    git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/format/pktline"
    "github.com/go-git/go-git/v5/plumbing/object"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/transport"
    transportclient "github.com/go-git/go-git/v5/plumbing/transport/client"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    transportserver "github.com/go-git/go-git/v5/plumbing/transport/server"
    "github.com/go-git/go-git/v5/storage/memory"
    billy "github.com/go-git/go-billy/v6"
    "github.com/go-git/go-billy/v6/memfs"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/object"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
    "github.com/go-git/go-git/v6/storage/memory"
    "github.com/soph/git-sync/internal/syncer"
)

56 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 3)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
84 unmodified lines

t.Helper()

fs := memfs.New()
    repo, err := git.Init(memory.NewStorage(), fs)
    repo, err := git.Init(memory.NewStorage(), git.WithWorkTree(fs))
    if err != nil {
        t.Fatalf("init source repo: %v", err)
    }
99 unmodified lines

}

func (s *smartHTTPRepoServer) handleInfoRefs(w http.ResponseWriter, r *http.Request) {
    service := r.URL.Query().Get("service")
    if service != transport.UploadPackServiceName && service != transport.ReceivePackServiceName {
    service := transport.Service(r.URL.Query().Get("service"))
    if service != transport.UploadPackService && service != transport.ReceivePackService {
        http.Error(w, "missing service", http.StatusBadRequest)
        return
    }

session, err := s.newSession(service)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

var ar *packp.AdvRefs
    switch service {
    case transport.UploadPackServiceName:
        ar, err = session.(transport.UploadPackSession).AdvertisedReferencesContext(r.Context())
    case transport.ReceivePackServiceName:
        ar, err = session.(transport.ReceivePackSession).AdvertisedReferencesContext(r.Context())
    }
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

ar.Prefix = [][]byte{
        []byte("# service=" + service),
        pktline.Flush,
    }

var buf bytes.Buffer
    if err := ar.Encode(&buf); err != nil {
    if err := transport.AdvertiseReferences(r.Context(), s.repo.Storer, &buf, service, false); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
5 unmodified lines

}

func (s *smartHTTPRepoServer) handleUploadPack(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer r.Body.Close()
    var buf bytes.Buffer
    wc := nopWriteCloser{&buf}

session, err := s.newSession(transport.UploadPackServiceName)
    err := transport.UploadPack(r.Context(), s.repo.Storer, r.Body, wc, &transport.UploadPackOptions{
        StatelessRPC: true,
    })
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

req := packp.NewUploadPackRequest()
    if err := req.Decode(bytes.NewReader(body)); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

resp, err := session.(transport.UploadPackSession).UploadPack(r.Context(), req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

var buf bytes.Buffer
    if err := resp.Encode(&buf); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

w.Header().Set("Content-Type", "application/x-git-upload-pack-result")
    if _, err := w.Write(buf.Bytes()); err != nil {
        s.t.Fatalf("write upload-pack response: %v", err)
5 unmodified lines

s.receivePacks++
    s.mu.Unlock()

body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer r.Body.Close()
    var buf bytes.Buffer
    wc := nopWriteCloser{&buf}

session, err := s.newSession(transport.ReceivePackServiceName)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    err := transport.ReceivePack(r.Context(), s.repo.Storer, r.Body, wc, &transport.ReceivePackOptions{
        StatelessRPC: true,
    })

req := packp.NewReferenceUpdateRequest()
    if err := req.Decode(bytes.NewReader(body)); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    w.Header().Set("Content-Type", "application/x-git-receive-pack-result")
    if buf.Len() > 0 {
        _, _ = w.Write(buf.Bytes())
    }

report, err := session.(transport.ReceivePackSession).ReceivePack(r.Context(), req)
    if report != nil {
        var buf bytes.Buffer
        if encErr := report.Encode(&buf); encErr == nil {
            w.Header().Set("Content-Type", "application/x-git-receive-pack-result")
            _, _ = w.Write(buf.Bytes())
        }
    }
    if err != nil {
        return
    }
}

func (s *smartHTTPRepoServer) newSession(service string) (interface{}, error) {
    loader := transportserver.MapLoader{}
// nopWriteCloser wraps an io.Writer to satisfy io.WriteCloser.
type nopWriteCloser struct{ io.Writer }

endpoint, err := transport.NewEndpoint(s.RepoURL())
    if err != nil {
        return nil, err
    }
    loader[endpoint.String()] = s.repo.Storer

srv := transportserver.NewServer(loader)
    switch service {
    case transport.UploadPackServiceName:
        return srv.NewUploadPackSession(endpoint, nil)
    case transport.ReceivePackServiceName:
        return srv.NewReceivePackSession(endpoint, nil)
    default:
        return nil, fmt.Errorf("unknown service %q", service)
    }
}
func (nopWriteCloser) Close() error { return nil }

func TestMain(m *testing.M) {
    originalHTTP := transportclient.Protocols["http"]
    originalHTTPS := transportclient.Protocols["https"]
    customHTTP := transporthttp.NewTransport(&transporthttp.TransportOptions{
        Client: &http.Client{},
    })
    transport.Register("http", customHTTP)
    transport.Register("https", customHTTP)

customHTTP := transporthttp.NewClient(&http.Client{})
    transportclient.InstallProtocol("http", customHTTP)
    transportclient.InstallProtocol("https", customHTTP)

code := m.Run()

transportclient.InstallProtocol("http", originalHTTP)
    transportclient.InstallProtocol("https", originalHTTPS)

os.Exit(code)
    os.Exit(m.Run())
}

Mcmd/git-sync/main_test.go+35/-119

2 unmodified lines

3
4
5
6
7
6
7
8
9
10
11
12
12
14
15
16
13
14
15
16
17
19
18
19
20
22
23
24
25
26
27
28
29
30
31
21
22
23
24
25
26
27
28
29

2 unmodified lines

go 1.25.2

require (
    github.com/go-git/go-billy/v5 v5.8.0
    github.com/go-git/go-git/v5 v5.17.0
    github.com/go-git/go-billy/v6 v6.0.0-20260410103409-85b6241850b5
    github.com/go-git/go-git/v6 v6.0.0-alpha.1
    github.com/zalando/go-keyring v0.2.8
)

require (
    dario.cat/mergo v1.0.0 // indirect
    github.com/Microsoft/go-winio v0.6.2 // indirect
    github.com/ProtonMail/go-crypto v1.1.6 // indirect
    github.com/cloudflare/circl v1.6.1 // indirect
    github.com/cyphar/filepath-securejoin v0.4.1 // indirect
    github.com/ProtonMail/go-crypto v1.4.1 // indirect
    github.com/cloudflare/circl v1.6.3 // indirect
    github.com/cyphar/filepath-securejoin v0.6.1 // indirect
    github.com/danieljoos/wincred v1.2.3 // indirect
    github.com/emirpasic/gods v1.18.1 // indirect
    github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
    github.com/go-git/gcfg/v2 v2.0.2 // indirect
    github.com/godbus/dbus/v5 v5.2.2 // indirect
    github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
    github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
    github.com/kevinburke/ssh_config v1.2.0 // indirect
    github.com/pjbgf/sha1cd v0.3.2 // indirect
    github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
    github.com/skeema/knownhosts v1.3.1 // indirect
    github.com/xanzy/ssh-agent v0.3.3 // indirect
    golang.org/x/crypto v0.45.0 // indirect
    golang.org/x/net v0.47.0 // indirect
    golang.org/x/sys v0.38.0 // indirect
    gopkg.in/warnings.v0 v0.1.2 // indirect
    github.com/kevinburke/ssh_config v1.6.0 // indirect
    github.com/klauspost/cpuid/v2 v2.3.0 // indirect
    github.com/pjbgf/sha1cd v0.5.0 // indirect
    github.com/sergi/go-diff v1.4.0 // indirect
    golang.org/x/crypto v0.48.0 // indirect
    golang.org/x/net v0.51.0 // indirect
    golang.org/x/sync v0.19.0 // indirect
    golang.org/x/sys v0.43.0 // indirect
)

Mgo.mod+14/-17

1
2
3
1
2
6
7
3
4
5
6
7
8
12
13
14
15
9
10
11
12
13
14
15
16
17
21
22
18
19
20
21
27
28
29
30
31
32
33
34
22
23
24
25
26
27
28
29
30
31
32
33
39
40
41
42
43
44
34
35
36
37
38
46
47
39
40
50
51
52
53
54
55
56
57
41
42
43
44
60
61
62
63
64
65
66
45
46
47
48
49
70
50
51
52
74
75
53
54
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
55
56
57
58
59
60
61
62
63
64
65
66
67
68
103
104
105
106
69
70
71

dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
github.com/go-git/go-billy/v6 v6.0.0-20260410103409-85b6241850b5 h1:r5Y4Hn9QwQj+u6vN0Ib1MipHkanYaG8Zj0kxsnv8Bu4=
github.com/go-git/go-billy/v6 v6.0.0-20260410103409-85b6241850b5/go.mod h1:CdBVp7CXl9l3sOyNEog46cP1Pvx/hjCe9AD0mtaIUYU=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67 h1:3hutPZF+/FBjR/9MdsLJ7e1mlt9pwHgwxMW7CrbmWII=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67/go.mod h1:xKt0pNHST9tYHvbiLxSY27CQWFwgIxBJuDrOE0JvbZw=
github.com/go-git/go-git/v6 v6.0.0-alpha.1 h1:tGohX5luKeO50DZD0/Zqd5dYjJzKtub91S4I+1qFVIs=
github.com/go-git/go-git/v6 v6.0.0-alpha.1/go.mod h1:qtzfNHlFsnq6vCw54aT4KvFWPK5bsOpTVtFC2sysdB8=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

Mgo.sum+34/-72

5 unmodified lines

6
7
8
9
10
9
10
11
12
13
13 unmodified lines

27
28
29
30
30
31
32
33
44 unmodified lines

78
79
80
81
82
81
82
83
84
85
2 unmodified lines

88
89
90
91
91
92
93
94
95
95
96
97
98
99
100
99
100
101
102
103

5 unmodified lines

"os/exec"
    "strings"

"github.com/go-git/go-git/v5/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
)

// Endpoint holds the authentication-related fields for a remote.
13 unmodified lines

if ep == nil {
        return nil, nil
    }
    if ep.Protocol != "http" && ep.Protocol != "https" {
    if ep.Scheme != "http" && ep.Scheme != "https" {
        return nil, nil
    }
    if username, password, ok, err := LookupEntireDBCredential(raw, ep); err != nil {
44 unmodified lines

}
    username := values["username"]
    if username == "" {
        if ep.User != "" {
            username = ep.User
        if ep.User != nil && ep.User.Username() != "" {
            username = ep.User.Username()
        } else {
            username = "git"
        }
2 unmodified lines

}

func credentialFillInput(ep *transport.Endpoint) string {
    if ep == nil || ep.Host == "" {
    if ep == nil || ep.Hostname() == "" {
        return ""
    }
    var b strings.Builder
    fmt.Fprintf(&b, "protocol=%s\nhost=%s\n", ep.Protocol, ep.Host)
    fmt.Fprintf(&b, "protocol=%s\nhost=%s\n", ep.Scheme, ep.Hostname())
    if path := strings.TrimPrefix(ep.Path, "/"); path != "" {
        fmt.Fprintf(&b, "path=%s\n", path)
    }
    if ep.User != "" {
        fmt.Fprintf(&b, "username=%s\n", ep.User)
    if ep.User != nil && ep.User.Username() != "" {
        fmt.Fprintf(&b, "username=%s\n", ep.User.Username())
    }
    b.WriteString("\n")
    return b.String()

Minternal/auth/auth.go+9/-9

3 unmodified lines

4
5
6
7
8
9
10
1 unmodified line

12
13
14
14
15
15
16
17
18
19
101 unmodified lines

121
122
123
123
124
125
126
124
125
126
127
128
129
130
131
132
11 unmodified lines

144
145
146
144
147
148
149
150
2 unmodified lines

153
154
155
153
154
155
156
157
158
159
160
161
162
163
68 unmodified lines

232
233
234
230
235
236
237
238
180 unmodified lines

419
420
421
417
422
423
424
425
426
422
427
428
429
430
21 unmodified lines

452
453
454
450
455
456
457
458
459
455
460
461
462
463

3 unmodified lines

"context"
    "errors"
    "fmt"
    "net/url"
    "os"
    "path/filepath"
    "strconv"
1 unmodified line

"testing"
    "time"

"github.com/go-git/go-git/v5/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
    "github.com/zalando/go-keyring"
)

101 unmodified lines

func TestCredentialFillInput(t *testing.T) {
    ep := &transport.Endpoint{
        Protocol: "https",
        Host:     "github.com",
        Path:     "/owner/repo.git",
        User:     "myuser",
        URL: url.URL{
            Scheme: "https",
            Host:   "github.com",
            Path:   "/owner/repo.git",
            User:   url.User("myuser"),
        },
    }

got := credentialFillInput(ep)
11 unmodified lines

}

func TestCredentialFillInputEmptyHost(t *testing.T) {
    ep := &transport.Endpoint{Protocol: "https"}
    ep := &transport.Endpoint{URL: url.URL{Scheme: "https"}}
    got := credentialFillInput(ep)
    if got != "" {
        t.Errorf("expected empty string for empty host, got %q", got)
2 unmodified lines

func TestCredentialFillInputNoUser(t *testing.T) {
    ep := &transport.Endpoint{
        Protocol: "https",
        Host:     "example.com",
        Path:     "/repo.git",
        URL: url.URL{
            Scheme: "https",
            Host:   "example.com",
            Path:   "/repo.git",
        },
    }
    got := credentialFillInput(ep)
    want := "protocol=https\nhost=example.com\npath=repo.git\n\n"
68 unmodified lines

t.Fatal(err)
    }

sshEP := &transport.Endpoint{Protocol: "ssh", Host: "example.com", Path: "/repo.git"}
    sshEP := &transport.Endpoint{URL: url.URL{Scheme: "ssh", Host: "example.com", Path: "/repo.git"}}

tests := []struct {
        name      string
180 unmodified lines

}{
        {
            name: "https host",
            ep:   &transport.Endpoint{Protocol: "https", Host: "example.com"},
            ep:   &transport.Endpoint{URL: url.URL{Scheme: "https", Host: "example.com"}},
            want: "https://example.com",
        },
        {
            name: "http host with port",
            ep:   &transport.Endpoint{Protocol: "http", Host: "example.com", Port: 8080},
            ep:   &transport.Endpoint{URL: url.URL{Scheme: "http", Host: "example.com:8080"}},
            want: "http://example.com:8080",
        },
        {
21 unmodified lines

}{
        {
            name: "host without port",
            ep:   &transport.Endpoint{Host: "example.com"},
            ep:   &transport.Endpoint{URL: url.URL{Host: "example.com"}},
            want: "example.com",
        },
        {
            name: "host with port",
            ep:   &transport.Endpoint{Host: "example.com", Port: 8080},
            ep:   &transport.Endpoint{URL: url.URL{Host: "example.com:8080"}},
            want: "example.com:8080",
        },
        {

Minternal/auth/auth_test.go+20/-15

13 unmodified lines

14
15
16
17
17
18
19
20
38 unmodified lines

59
60
61
62
62
63
64
65
65
66
67
68
69
70
71
72
69
70
71
72
1 unmodified line

74
75
76
80
81
82
83
77
78
79
80

13 unmodified lines

"strings"
    "time"

"github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v6/plumbing/transport"
    "github.com/zalando/go-keyring"
)

38 unmodified lines

}

func endpointBaseURL(ep *transport.Endpoint) string {
    if ep == nil || ep.Host == "" {
    if ep == nil || ep.Hostname() == "" {
        return ""
    }
    scheme := ep.Protocol
    scheme := ep.Scheme
    if scheme == "" {
        scheme = "https"
    }
    host := ep.Host
    if ep.Port > 0 {
        host = fmt.Sprintf("%s:%d", host, ep.Port)
    }
    host := ep.Host // includes port if present in url.URL
    return scheme + "://" + host
}

1 unmodified line

if ep == nil {
        return ""
    }
    if ep.Port > 0 {
        return fmt.Sprintf("%s:%d", ep.Host, ep.Port)
    }
    return ep.Host
    return ep.Host // includes port if present in url.URL
}

func lookupEntireDBToken(host, baseURL string, skipTLS bool) (string, error) {

Minternal/auth/entiredb.go+5/-11

3 unmodified lines

4
5
6
7
7
8
9
10

3 unmodified lines

package convert

import (
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"

"github.com/soph/git-sync/internal/gitproto"
    "github.com/soph/git-sync/internal/planner"

Minternal/convert/convert.go+1/-1

5 unmodified lines

6
7
8
9
9
10
11
12

5 unmodified lines

"sort"
    "strings"

"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
)

// V2Capabilities represents a parsed protocol v2 capability advertisement.

Minternal/gitproto/capability.go+1/-1

2 unmodified lines

3
4
5
6
6
7
8
9

2 unmodified lines

import (
    "testing"

"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
)

func TestV2CapabilitiesFetchSupports(t *testing.T) {

Minternal/gitproto/capability_test.go+1/-1

3 unmodified lines

4
5
6
7
7
8
9
10

3 unmodified lines

"fmt"
    "io"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

// ToPushCommands converts a slice of PushPlans to PushCommands.

Minternal/gitproto/convert.go+1/-1

4 unmodified lines

5
6
7
8
8
9
10
11

4 unmodified lines

"strings"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

func TestToPushCommands(t *testing.T) {

Minternal/gitproto/convert_test.go+1/-1

1
2
3
4
5
6
7
8
9
10
10
11
12
13
14
15
16
17
18
11
12
13
14
15
16
17
18
19
20
21
22
89 unmodified lines

112
113
114
114
115
116
117
118
43 unmodified lines

162
163
164
164
165
166
167
168
32 unmodified lines

201
202
203
203
204
205
206
207
73 unmodified lines

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
318
319
320
321
322
323
324
325
326
2 unmodified lines

329
330
331
291
332
333
293
334
335
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
336
337
315
316
317
338
339
340
341
322
342
343
344
345
346
347
348
349
350
4 unmodified lines

355
356
357
333
358
359
335
360
361
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
362
363
357
358
359
360
364
365
363
364
365
366
367
368
366
367
368
369
370
371
372
373
374
375
376
377
64 unmodified lines

442
443
444
439
440
441
442
443
444
445
446
447
448
449

package gitproto

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "io"
    "strings"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/format/packfile"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v5/utils/ioutil"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/format/packfile"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/sideband"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/plumbing/transport"
    "github.com/go-git/go-git/v6/utils/ioutil"
)

// DesiredRef describes a single ref we want to fetch from source.
89 unmodified lines

if err != nil {
        return err
    }
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackServiceName, body, true, "upload-pack fetch")
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, true, "upload-pack fetch")
    if err != nil {
        return err
    }
43 unmodified lines

if err != nil {
        return err
    }
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackServiceName, body, true, "upload-pack fetch")
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, true, "upload-pack fetch")
    if err != nil {
        return err
    }
32 unmodified lines

if err != nil {
        return nil, err
    }
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackServiceName, body, true, "upload-pack fetch")
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, true, "upload-pack fetch")
    if err != nil {
        return nil, err
    }
73 unmodified lines

// --- V1 fetch implementation ---

// buildV1UploadPackBody encodes a v1 upload-pack request body for stateless-rpc HTTP.
func buildV1UploadPackBody(
    adv *packp.AdvRefs,
    desired map[plumbing.ReferenceName]DesiredRef,
    targetRefs map[plumbing.ReferenceName]plumbing.Hash,
    includeTags bool,
) ([]byte, *capability.List, error) {
    wants := collectWants(desired)
    haves := SortedUniqueHashes(refValues(targetRefs))
    if len(wants) == 0 {
        return nil, nil, git.NoErrAlreadyUpToDate
    }

req := packp.NewUploadRequest()
    req.Wants = wants
    if adv.Capabilities.Supports(capability.NoProgress) {
        _ = req.Capabilities.Set(capability.NoProgress)
    }
    if includeTags && adv.Capabilities.Supports(capability.IncludeTag) {
        _ = req.Capabilities.Set(capability.IncludeTag)
    }
    // Prefer sideband64k over sideband (issue #4).
    if sb := PreferredSideband(adv.Capabilities); sb != "" {
        _ = req.Capabilities.Set(sb)
    }
    if adv.Capabilities.Supports(capability.OFSDelta) {
        _ = req.Capabilities.Set(capability.OFSDelta)
    }

var buf bytes.Buffer
    if err := req.Encode(&buf); err != nil {
        return nil, nil, fmt.Errorf("encode upload-request: %w", err)
    }
    uphav := &packp.UploadHaves{Haves: haves, Done: true}
    if err := uphav.Encode(&buf); err != nil {
        return nil, nil, fmt.Errorf("encode upload-haves: %w", err)
    }
    return buf.Bytes(), req.Capabilities, nil
}

func fetchToStoreV1(
    ctx context.Context,
    store storer.Storer,
2 unmodified lines

desired map[plumbing.ReferenceName]DesiredRef,
    targetRefs map[plumbing.ReferenceName]plumbing.Hash,
) error {
    session, err := conn.Transport.NewUploadPackSession(conn.Endpoint, conn.Auth)
    body, caps, err := buildV1UploadPackBody(adv, desired, targetRefs, hasTag(desired))
    if err != nil {
        return fmt.Errorf("open source upload-pack session: %w", err)
        return err
    }
    defer session.Close()

req := packp.NewUploadPackRequestFromCapabilities(adv.Capabilities)
    for _, ref := range desired {
        req.Wants = append(req.Wants, ref.SourceHash)
    }
    req.Wants = SortedUniqueHashes(req.Wants)
    req.Haves = SortedUniqueHashes(refValues(targetRefs))
    if len(req.Wants) == 0 {
        return git.NoErrAlreadyUpToDate
    }
    if adv.Capabilities.Supports(capability.NoProgress) {
        _ = req.Capabilities.Set(capability.NoProgress)
    }
    if hasTag(desired) && adv.Capabilities.Supports(capability.IncludeTag) {
        _ = req.Capabilities.Set(capability.IncludeTag)
    }

reader, err := session.UploadPack(ctx, req)
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, false, "upload-pack fetch")
    if err != nil {
        if errors.Is(err, transport.ErrEmptyUploadPackRequest) {
            return git.NoErrAlreadyUpToDate
        }
        return fmt.Errorf("source upload-pack: %w", err)
    }
    defer ioutil.CheckClose(reader, &err)

sbReader := buildSidebandReader(req.Capabilities, reader, nil)
    // Decode server response (ACK/NAK) then read pack with sideband demux.
    var srvResp packp.ServerResponse
    if decErr := srvResp.Decode(reader); decErr != nil {
        return fmt.Errorf("decode server response: %w", decErr)
    }
    sbReader := buildSidebandReader(caps, reader, nil)
    return packfile.UpdateObjectStorage(store, sbReader)
}

4 unmodified lines

desired map[plumbing.ReferenceName]DesiredRef,
    targetRefs map[plumbing.ReferenceName]plumbing.Hash,
) (io.ReadCloser, error) {
    session, err := conn.Transport.NewUploadPackSession(conn.Endpoint, conn.Auth)
    body, caps, err := buildV1UploadPackBody(adv, desired, targetRefs, hasTag(desired))
    if err != nil {
        return nil, fmt.Errorf("open source upload-pack session: %w", err)
        return nil, err
    }

req := packp.NewUploadPackRequestFromCapabilities(adv.Capabilities)
    for _, ref := range desired {
        req.Wants = append(req.Wants, ref.SourceHash)
    }
    req.Wants = SortedUniqueHashes(req.Wants)
    req.Haves = SortedUniqueHashes(refValues(targetRefs))
    if len(req.Wants) == 0 {
        _ = session.Close()
        return nil, git.NoErrAlreadyUpToDate
    }
    if adv.Capabilities.Supports(capability.NoProgress) {
        _ = req.Capabilities.Set(capability.NoProgress)
    }
    if hasTag(desired) && adv.Capabilities.Supports(capability.IncludeTag) {
        _ = req.Capabilities.Set(capability.IncludeTag)
    }

reader, err := session.UploadPack(ctx, req)
    reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, false, "upload-pack fetch")
    if err != nil {
        _ = session.Close()
        if errors.Is(err, transport.ErrEmptyUploadPackRequest) {
            return nil, git.NoErrAlreadyUpToDate
        }
        return nil, fmt.Errorf("source upload-pack: %w", err)
    }
    return &sessionRC{
        Reader: buildSidebandReader(req.Capabilities, reader, nil),
        closeFn: func() error {
            _ = reader.Close()
            return session.Close()
        },

var srvResp packp.ServerResponse
    if decErr := srvResp.Decode(reader); decErr != nil {
        _ = reader.Close()
        return nil, fmt.Errorf("decode server response: %w", decErr)
    }
    return &wrappedRC{
        Reader: buildSidebandReader(caps, reader, nil),
        Closer: reader,
    }, nil
}

64 unmodified lines

io.Closer
}

type sessionRC struct {
    io.Reader
    closeFn func() error
}

func (r *sessionRC) Close() error {
    if r.closeFn == nil {
        return nil
    }
    return r.closeFn()
}

Minternal/gitproto/fetch.go+74/-79

2 unmodified lines

3
4
5
6
6
7
8
9

2 unmodified lines

import (
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

func TestCollectWants(t *testing.T) {

Minternal/gitproto/fetch_helpers_test.go+1/-1

4 unmodified lines

5
6
7
8
9
10
8
9
10
11
12
13
119 unmodified lines

133
134
135
136
137
136
137
138
139
139
140
141
141
142
143
144
144
145
146
147
148
149
150
150
151
152
153
154
155
156
157
158
159
160
152
153
154
155
156
157
158
159
160

4 unmodified lines

"io"
    "testing"

"github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/sideband"
)

func TestCapabilities(t *testing.T) {
119 unmodified lines

}
}

func TestSessionRCClose(t *testing.T) {
    // With closeFn
func TestWrappedRCClose(t *testing.T) {
    // wrappedRC should close the underlying closer.
    called := false
    rc := &sessionRC{
    rc := &wrappedRC{
        Reader: bytes.NewBufferString("data"),
        closeFn: func() error {
        Closer: closerFunc(func() error {
            called = true
            return nil
        },
        }),
    }
    if err := rc.Close(); err != nil {
        t.Fatalf("Close() error: %v", err)
    }
    if !called {
        t.Error("closeFn was not called")
        t.Error("underlying closer was not called")
    }

// With nil closeFn
    rc = &sessionRC{
        Reader:  bytes.NewBufferString("data"),
        closeFn: nil,
    }
    if err := rc.Close(); err != nil {
        t.Fatalf("Close() with nil closeFn error: %v", err)
    }
}

type closerFunc func() error

func (f closerFunc) Close() error { return f() }

func TestDecodeV2LSRefs(t *testing.T) {
    // Build a valid ls-refs response:
    // Each line: "<hash> <refname>\n"

Minternal/gitproto/fetch_test.go+13/-18

6 unmodified lines

7
8
9
10
10
11
12
13
91 unmodified lines

105
106
107
108
109
108
109
110
111
113
112
113
114
115
116
118
117
118
119
120
122
121
122
123
124
125
127
126
127
128
129

6 unmodified lines

"fmt"
    "io"

"github.com/go-git/go-git/v5/plumbing/format/pktline"
    "github.com/go-git/go-git/v6/plumbing/format/pktline"
)

// PacketType represents the type of a pkt-line packet.
91 unmodified lines

// EncodeCommand builds a pkt-line encoded v2 command request.
func EncodeCommand(command string, capArgs, cmdArgs []string) ([]byte, error) {
    var buf bytes.Buffer
    enc := pktline.NewEncoder(&buf)
    if err := enc.EncodeString("command=" + command + "\n"); err != nil {
    if _, err := pktline.Writef(&buf, "command=%s\n", command); err != nil {
        return nil, err
    }
    for _, arg := range capArgs {
        if err := enc.EncodeString(arg + "\n"); err != nil {
        if _, err := pktline.Writef(&buf, "%s\n", arg); err != nil {
            return nil, err
        }
    }
    if len(cmdArgs) > 0 {
        if _, err := buf.WriteString("0001"); err != nil {
        if err := pktline.WriteDelim(&buf); err != nil {
            return nil, err
        }
        for _, arg := range cmdArgs {
            if err := enc.EncodeString(arg + "\n"); err != nil {
            if _, err := pktline.Writef(&buf, "%s\n", arg); err != nil {
                return nil, err
            }
        }
    }
    if err := enc.Flush(); err != nil {
    if err := pktline.WriteFlush(&buf); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil

Minternal/gitproto/pktline.go+6/-7

1
2
3
4
5
6
7
8
9
9
10
11
12
13
14
10
11
12
13
14
15
16
17
18
19
31 unmodified lines

51
52
53
52
53
54
55
54
55
56
57
58
59
60
61
62
63
64
65
66
59
60
61
62
63
64
65
66
67
68
69
11 unmodified lines

81
82
83
87
88
84
85
86
87
88
93
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
6 unmodified lines

142
143
144
106
145
146
147
148
110
111
149
150
151
152
153
154
155
156
157
158
159
160
161
162
5 unmodified lines

168
169
170
123
171
172
173
174
175
176
130
131
132
133
134
135
136
137
177
178
179
180
181
142
143
182
183
184
185
186
187
145
146
147
148
188
189
190
191
4 unmodified lines

196
197
198
159
199
200
201
202
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
203
204
205
206

package gitproto

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "os"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/format/packfile"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/format/packfile"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/sideband"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/plumbing/transport"
)

// PushCommand represents a single ref update command.
31 unmodified lines

return PushObjects(ctx, p.Conn, p.Adv, commands, store, hashes, p.Verbose)
}

// preparePush opens a receive-pack session and builds the base request with
// sideband negotiation. Shared by PushObjects, PushPack, and PushCommands.
func preparePush(
    conn *Conn,
// buildUpdateRequest builds the receive-pack update request.
func buildUpdateRequest(
    adv *packp.AdvRefs,
    commands []PushCommand,
    verbose bool,
) (transport.ReceivePackSession, *packp.ReferenceUpdateRequest, bool, bool, error) {
    session, err := conn.Transport.NewReceivePackSession(conn.Endpoint, conn.Auth)
    if err != nil {
        return nil, nil, false, false, fmt.Errorf("open target receive-pack session: %w", err)
    }

req := packp.NewReferenceUpdateRequestFromCapabilities(adv.Capabilities)
    req.Progress = progressWriter(verbose)
) (*packp.UpdateRequests, bool, bool, error) {
    req := packp.NewUpdateRequests()
    if sb := PreferredSideband(adv.Capabilities); sb != "" {
        _ = req.Capabilities.Set(sb)
    }
    if adv.Capabilities.Supports(capability.ReportStatus) {
        _ = req.Capabilities.Set(capability.ReportStatus)
    }

hasDelete := false
    hasUpdates := false
11 unmodified lines

if hasDelete {
        if !adv.Capabilities.Supports(capability.DeleteRefs) {
            _ = session.Close()
            return nil, nil, false, false, fmt.Errorf("target does not support delete-refs")
            return nil, false, false, fmt.Errorf("target does not support delete-refs")
        }
        _ = req.Capabilities.Set(capability.DeleteRefs)
    }

return session, req, hasDelete, hasUpdates, nil
    _ = verbose // progress handling is server-side in HTTP mode
    return req, hasDelete, hasUpdates, nil
}

// sendReceivePack encodes and POSTs a receive-pack request, then decodes the report.
func sendReceivePack(
    ctx context.Context,
    conn *Conn,
    req *packp.UpdateRequests,
    packData io.Reader,
) error {
    var buf bytes.Buffer
    if err := req.Encode(&buf); err != nil {
        return fmt.Errorf("encode update-request: %w", err)
    }
    if packData != nil {
        if _, err := io.Copy(&buf, packData); err != nil {
            return fmt.Errorf("buffer pack data: %w", err)
        }
    }
    reader, err := PostRPCStream(ctx, conn, transport.ReceivePackService, buf.Bytes(), false, "receive-pack push")
    if err != nil {
        return fmt.Errorf("target receive-pack: %w", err)
    }
    defer reader.Close()

// Unwrap sideband if negotiated.
    var respReader io.Reader = reader
    if req.Capabilities.Supports(capability.Sideband64k) {
        respReader = sideband.NewDemuxer(sideband.Sideband64k, reader)
    } else if req.Capabilities.Supports(capability.Sideband) {
        respReader = sideband.NewDemuxer(sideband.Sideband, reader)
    }

if req.Capabilities.Supports(capability.ReportStatus) {
        report := packp.NewReportStatus()
        if err := report.Decode(respReader); err != nil {
            return fmt.Errorf("decode report-status: %w", err)
        }
        if err := report.Error(); err != nil {
            return err
        }
    }
    return nil
}

// PushObjects pushes locally-materialized objects to the target.
6 unmodified lines

hashes []plumbing.Hash,
    verbose bool,
) error {
    session, req, _, hasUpdates, err := preparePush(conn, adv, commands, verbose)
    req, _, hasUpdates, err := buildUpdateRequest(adv, commands, verbose)
    if err != nil {
        return err
    }
    defer session.Close()
    return executePush(ctx, session, store, req, hashes, hasUpdates, !adv.Capabilities.Supports(capability.OFSDelta))
    if !hasUpdates {
        return sendReceivePack(ctx, conn, req, nil)
    }

useRefDeltas := !adv.Capabilities.Supports(capability.OFSDelta)
    var packBuf bytes.Buffer
    enc := packfile.NewEncoder(&packBuf, store, useRefDeltas)
    if _, err := enc.Encode(hashes, 10); err != nil {
        return fmt.Errorf("encode packfile: %w", err)
    }
    return sendReceivePack(ctx, conn, req, &packBuf)
}

// PushPack pushes a pack stream (relay) to the target.
5 unmodified lines

pack io.ReadCloser,
    verbose bool,
) error {
    // Validate no deletes in pack push before opening session.
    for _, cmd := range commands {
        if cmd.Delete {
            return fmt.Errorf("pack push only supports create and update actions")
        }
    }

session, req, _, _, err := preparePush(conn, adv, commands, verbose)
    if err != nil {
        return err
    }
    defer session.Close()

req.Packfile = pack
    report, err := session.ReceivePack(ctx, req)
    req, _, _, err := buildUpdateRequest(adv, commands, verbose)
    if err != nil {
        _ = pack.Close()
        return err
    }
    if closeErr := pack.Close(); closeErr != nil {
        return closeErr

err = sendReceivePack(ctx, conn, req, pack)
    closeErr := pack.Close()
    if err != nil {
        return err
    }
    if report != nil {
        return report.Error()
    }
    return nil
    return closeErr
}

// PushCommands sends ref update commands without a pack (for ref-only changes).
4 unmodified lines

commands []PushCommand,
    verbose bool,
) error {
    session, req, _, _, err := preparePush(conn, adv, commands, verbose)
    req, _, _, err := buildUpdateRequest(adv, commands, verbose)
    if err != nil {
        return err
    }
    defer session.Close()
    return executePush(ctx, session, nil, req, nil, false, false)
}

func executePush(
    ctx context.Context,
    session transport.ReceivePackSession,
    store storer.Storer,
    req *packp.ReferenceUpdateRequest,
    hashes []plumbing.Hash,
    sendPack bool,
    useRefDeltas bool,
) error {
    if !sendPack {
        report, err := session.ReceivePack(ctx, req)
        if err != nil {
            return err
        }
        if report != nil {
            return report.Error()
        }
        return nil
    }

rd, wr := io.Pipe()
    req.Packfile = rd
    done := make(chan error, 1)

go func() {
        enc := packfile.NewEncoder(wr, store, useRefDeltas)
        if _, err := enc.Encode(hashes, 10); err != nil {
            done <- wr.CloseWithError(err)
            return
        }
        done <- wr.Close()
    }()

report, err := session.ReceivePack(ctx, req)
    if err != nil {
        _ = rd.Close()
        return err
    }
    if err := <-done; err != nil {
        return err
    }
    if report != nil {
        return report.Error()
    }
    return nil
    return sendReceivePack(ctx, conn, req, nil)
}

func progressWriter(verbose bool) io.Writer {

Minternal/gitproto/push.go+81/-89

2 unmodified lines

3
4
5
6
6
7
8
9
10
10
11
12
11
12
13
14
15
16
17
15 unmodified lines

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
36
37
38
74
75
76
77
78
79
80
81
82
83
4 unmodified lines

88
89
90
91
92
93
94
95
96
97
98
99
55
56
57
58
59
100
101
102
103
104
105
106
107
108
109
3 unmodified lines

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
6 unmodified lines

181
182
183
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
117
118

2 unmodified lines

import (
    "bytes"
    "context"
    "errors"
    "io"
    "net/http"
    "net/http/httptest"
    "testing"

"github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/transport"
)

func TestOpenV2PackStreamCloseClosesBody(t *testing.T) {
15 unmodified lines

}
}

// fakeReceivePackServer returns an httptest.Server that responds to
// git-receive-pack POST requests. If reportErr is non-empty, the
// report-status will indicate failure.
func fakeReceivePackServer(t *testing.T, reportErr string) *httptest.Server {
    t.Helper()
    return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Consume the request body.
        _, _ = io.Copy(io.Discard, r.Body)
        _ = r.Body.Close()

w.Header().Set("Content-Type", "application/x-git-receive-pack-result")
        w.WriteHeader(http.StatusOK)

if reportErr != "" {
            // Write a minimal report-status with an error.
            report := packp.NewReportStatus()
            report.UnpackStatus = reportErr
            _ = report.Encode(w)
        }
        // If no reportErr, write nothing -- PushPack will not try to
        // decode report-status when the capability is not negotiated.
    }))
}

func connForServer(t *testing.T, srv *httptest.Server) *Conn {
    t.Helper()
    ep, err := transport.NewEndpoint(srv.URL + "/repo.git")
    if err != nil {
        t.Fatalf("parse endpoint: %v", err)
    }
    return NewConn(ep, "test", nil, srv.Client().Transport)
}

func TestPushPackClosesPackOnSuccess(t *testing.T) {
    srv := fakeReceivePackServer(t, "")
    defer srv.Close()

pack := &trackingReadCloser{ReadCloser: io.NopCloser(bytes.NewBufferString("PACK"))}
    conn := &Conn{
        Transport: fakeTransport{receivePackSession: &fakeReceivePackSession{}},
    }
    conn := connForServer(t, srv)
    adv := packp.NewAdvRefs()
    adv.Capabilities = capability.NewList()

err := PushPack(context.Background(), conn, adv, []PushCommand{{
        Name: "refs/heads/main",
        New:  plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
    }}, pack, false)
    if err != nil {
        t.Fatalf("PushPack returned error: %v", err)
4 unmodified lines

}

func TestPushPackClosesPackOnReceivePackError(t *testing.T) {
    // Server that returns HTTP 500 so the POST fails.
    srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        _, _ = io.Copy(io.Discard, r.Body)
        _ = r.Body.Close()
        http.Error(w, "receive-pack failed", http.StatusInternalServerError)
    }))
    defer srv.Close()

pack := &trackingReadCloser{ReadCloser: io.NopCloser(bytes.NewBufferString("PACK"))}
    conn := &Conn{
        Transport: fakeTransport{receivePackSession: &fakeReceivePackSession{
            err: errors.New("receive-pack failed"),
        }},
    }
    conn := connForServer(t, srv)
    adv := packp.NewAdvRefs()
    adv.Capabilities = capability.NewList()

err := PushPack(context.Background(), conn, adv, []PushCommand{{
        Name: "refs/heads/main",
        New:  plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
    }}, pack, false)
    if err == nil {
        t.Fatal("expected PushPack to return an error")
3 unmodified lines

}
}

func TestBuildUpdateRequest(t *testing.T) {
    adv := packp.NewAdvRefs()
    _ = adv.Capabilities.Set(capability.ReportStatus)
    _ = adv.Capabilities.Set(capability.DeleteRefs)
    _ = adv.Capabilities.Set(capability.Sideband64k)

req, hasDelete, hasUpdates, err := buildUpdateRequest(adv, []PushCommand{
        {Name: "refs/heads/main", New: plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")},
        {Name: "refs/heads/old", Old: plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), Delete: true},
    }, false)
    if err != nil {
        t.Fatalf("buildUpdateRequest: %v", err)
    }
    if !hasDelete {
        t.Error("expected hasDelete = true")
    }
    if !hasUpdates {
        t.Error("expected hasUpdates = true")
    }
    if len(req.Commands) != 2 {
        t.Fatalf("expected 2 commands, got %d", len(req.Commands))
    }
    if !req.Capabilities.Supports(capability.ReportStatus) {
        t.Error("expected report-status capability")
    }
}

func TestBuildUpdateRequestDeleteWithoutCapability(t *testing.T) {
    adv := packp.NewAdvRefs()
    // No delete-refs capability.

_, _, _, err := buildUpdateRequest(adv, []PushCommand{
        {Name: "refs/heads/old", Old: plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), Delete: true},
    }, false)
    if err == nil {
        t.Fatal("expected error when target does not support delete-refs")
    }
}

func TestPushPackRejectsDeletes(t *testing.T) {
    pack := &trackingReadCloser{ReadCloser: io.NopCloser(bytes.NewBufferString("PACK"))}
    // PushPack should reject delete commands before even trying to connect.
    adv := packp.NewAdvRefs()
    adv.Capabilities = capability.NewList()
    // Use a nil-transport conn -- we should never reach the network.
    ep, _ := transport.NewEndpoint("https://example.com/repo.git")
    conn := &Conn{Endpoint: ep, HTTP: &http.Client{}}

err := PushPack(context.Background(), conn, adv, []PushCommand{
        {Name: "refs/heads/old", Delete: true},
    }, pack, false)
    if err == nil {
        t.Fatal("expected error for delete in pack push")
    }
}

type trackingReadCloser struct {
    io.ReadCloser
    closed bool
6 unmodified lines

}
    return nil
}

type fakeTransport struct {
    receivePackSession transport.ReceivePackSession
}

func (t fakeTransport) NewUploadPackSession(*transport.Endpoint, transport.AuthMethod) (transport.UploadPackSession, error) {
    return nil, errors.New("not implemented")
}

func (t fakeTransport) NewReceivePackSession(*transport.Endpoint, transport.AuthMethod) (transport.ReceivePackSession, error) {
    return t.receivePackSession, nil
}

type fakeReceivePackSession struct {
    err error
}

func (s *fakeReceivePackSession) AdvertisedReferences() (*packp.AdvRefs, error) {
    return packp.NewAdvRefs(), nil
}

func (s *fakeReceivePackSession) AdvertisedReferencesContext(context.Context) (*packp.AdvRefs, error) {
    return s.AdvertisedReferences()
}

func (s *fakeReceivePackSession) ReceivePack(context.Context, *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) {
    if s.err != nil {
        return nil, s.err
    }
    return nil, nil
}

func (s *fakeReceivePackSession) Close() error { return nil }

Minternal/gitproto/push_test.go+110/-45

5 unmodified lines

6
7
8
9
10
11
9
10
11
12
13
14
16 unmodified lines

31
32
33
34
34
35
36
37
27 unmodified lines

65
66
67
68
68
69
70
71
42 unmodified lines

114
115
116
117
117
118
119
120
13 unmodified lines

134
135
136
137
137
138
139
140

5 unmodified lines

"fmt"
    "strings"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/transport"
)

// RefService encapsulates the result of source ref discovery and the negotiated
16 unmodified lines

return refs, &RefService{Protocol: "v1", V1Adv: adv}, nil

case "auto", "v2":
        data, err := RequestInfoRefs(ctx, conn, transport.UploadPackServiceName, "version=2")
        data, err := RequestInfoRefs(ctx, conn, transport.UploadPackService, "version=2")
        if err != nil {
            return nil, nil, err
        }
27 unmodified lines

}

// AdvertisedRefsV1 fetches and decodes v1 advertised refs for the given service.
func AdvertisedRefsV1(ctx context.Context, conn *Conn, service string) (*packp.AdvRefs, error) {
func AdvertisedRefsV1(ctx context.Context, conn *Conn, service transport.Service) (*packp.AdvRefs, error) {
    data, err := RequestInfoRefs(ctx, conn, service, "")
    if err != nil {
        return nil, err
42 unmodified lines

}

func listSourceRefsV1(ctx context.Context, conn *Conn) (*packp.AdvRefs, []*plumbing.Reference, error) {
    adv, err := AdvertisedRefsV1(ctx, conn, transport.UploadPackServiceName)
    adv, err := AdvertisedRefsV1(ctx, conn, transport.UploadPackService)
    if err != nil {
        return nil, nil, err
    }
13 unmodified lines

if err != nil {
        return nil, err
    }
    data, err := PostRPC(ctx, conn, transport.UploadPackServiceName, body, true, "upload-pack ls-refs")
    data, err := PostRPC(ctx, conn, transport.UploadPackService, body, true, "upload-pack ls-refs")
    if err != nil {
        return nil, err
    }

Minternal/gitproto/refs.go+7/-7

3 unmodified lines

4
5
6
7
8
9
7
8
9
10
11
12

3 unmodified lines

"context"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
)

func TestRefHashMap(t *testing.T) {

Minternal/gitproto/refs_test.go+3/-3

7 unmodified lines

8
9
10
11
12
13
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
13 unmodified lines

47
48
49
35
50
51
52
53
16 unmodified lines

70
71
72
58
59
73
74
75
76
77
78
79
80
81
66
82
83
84
85
4 unmodified lines

90
91
92
77
93
94
95
96
11 unmodified lines

108
109
110
95
111
112
113
114
13 unmodified lines

128
129
130
115
116
131
132
133
134
135
136
137
121
122
138
139
140
141
142
5 unmodified lines

148
149
150
134
151
152
153
154

7 unmodified lines

"io"
    "net/http"

"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
)

// httpError checks an HTTP response status and returns an error for non-2xx responses.
func httpError(res *http.Response) error {
    if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices {
        return nil
    }
    var reason string
    if res.Body != nil {
        var buf bytes.Buffer
        if n, _ := buf.ReadFrom(res.Body); n > 0 {
            reason = buf.String()
        }
    }
    return fmt.Errorf("http %d: %s %s", res.StatusCode, res.Request.URL.Redacted(), reason)
}

// StatsPhaseHeader is the HTTP header used to annotate requests with the
// current git-sync stats phase for round-trip tracking.
const StatsPhaseHeader = "X-Git-Sync-Stats-Phase"
13 unmodified lines

return &Conn{
        Label:     label,
        Endpoint:  ep,
        Transport: transporthttp.NewClient(httpClient),
        Transport: transporthttp.NewTransport(&transporthttp.TransportOptions{Client: httpClient}),
        HTTP:      httpClient,
        Auth:      auth,
    }
16 unmodified lines

}

// RequestInfoRefs fetches /info/refs for the given service.
func RequestInfoRefs(ctx context.Context, conn *Conn, service, gitProtocol string) ([]byte, error) {
    url := fmt.Sprintf("%s/info/refs?service=%s", conn.Endpoint.String(), service)
func RequestInfoRefs(ctx context.Context, conn *Conn, service transport.Service, gitProtocol string) ([]byte, error) {
    svc := service.String()
    url := fmt.Sprintf("%s/info/refs?service=%s", conn.Endpoint.String(), svc)
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Accept", "*/*")
    req.Header.Set("User-Agent", capability.DefaultAgent())
    req.Header.Set(StatsPhaseHeader, service+" info-refs")
    req.Header.Set(StatsPhaseHeader, svc+" info-refs")
    if gitProtocol != "" {
        req.Header.Set("Git-Protocol", gitProtocol)
    }
4 unmodified lines

return nil, err
    }
    defer res.Body.Close()
    if err := transporthttp.NewErr(res); err != nil {
    if err := httpError(res); err != nil {
        return nil, err
    }
    // Bound the read to prevent unbounded memory allocation (issue #9).
11 unmodified lines

// PostRPC sends a buffered POST to the given service and returns the full response body.
// Responses are bounded to prevent unbounded memory allocation (issue #9).
func PostRPC(ctx context.Context, conn *Conn, service string, body []byte, v2 bool, phase string) ([]byte, error) {
func PostRPC(ctx context.Context, conn *Conn, service transport.Service, body []byte, v2 bool, phase string) ([]byte, error) {
    reader, err := PostRPCStream(ctx, conn, service, body, v2, phase)
    if err != nil {
        return nil, err
13 unmodified lines

// PostRPCStream sends a POST to the given service and returns the response body
// as a streaming reader. Caller must close the returned ReadCloser.
func PostRPCStream(ctx context.Context, conn *Conn, service string, body []byte, v2 bool, phase string) (io.ReadCloser, error) {
    url := fmt.Sprintf("%s/%s", conn.Endpoint.String(), service)
func PostRPCStream(ctx context.Context, conn *Conn, service transport.Service, body []byte, v2 bool, phase string) (io.ReadCloser, error) {
    svc := service.String()
    url := fmt.Sprintf("%s/%s", conn.Endpoint.String(), svc)
    req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", fmt.Sprintf("application/x-%s-request", service))
    req.Header.Set("Accept", fmt.Sprintf("application/x-%s-result", service))
    req.Header.Set("Content-Type", fmt.Sprintf("application/x-%s-request", svc))
    req.Header.Set("Accept", fmt.Sprintf("application/x-%s-result", svc))
    req.Header.Set("User-Agent", capability.DefaultAgent())
    req.Header.Set(StatsPhaseHeader, phase)
    if v2 {
5 unmodified lines

if err != nil {
        return nil, err
    }
    if err := transporthttp.NewErr(res); err != nil {
    if err := httpError(res); err != nil {
        _ = res.Body.Close()
        return nil, err
    }

Minternal/gitproto/smarthttp.go+31/-14

6 unmodified lines

7
8
9
10
11
10
11
12
13
14

6 unmodified lines

"testing"
    "time"

func TestNewConn(t *testing.T) {

Minternal/gitproto/smarthttp_test.go+2/-2

3 unmodified lines

4
5
6
7
8
9
7
8
9
10
11
12

3 unmodified lines

"fmt"
    "testing"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/storage/memory"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/storage/memory"
)

func BenchmarkBuildDesiredRefs(b *testing.B) {

Minternal/planner/benchmark_test.go+3/-3

3 unmodified lines

4
5
6
7
8
9
7
8
9
10
11
12

3 unmodified lines

"fmt"
    "sort"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/object"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/object"
    "github.com/go-git/go-git/v6/plumbing/storer"
)

// BootstrapBatch holds the checkpoint plan for a single branch during batched bootstrap.

Minternal/planner/checkpoint.go+3/-3

3 unmodified lines

4
5
6
7
7
8
9
10

3 unmodified lines

"fmt"
    "strings"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

// NormalizeMapping validates and normalizes a single ref mapping.

Minternal/planner/mapping.go+1/-1

4 unmodified lines

5
6
7
8
9
10
8
9
10
11
12
13

4 unmodified lines

"fmt"
    "sort"

// PlanConfig holds configuration for plan generation.

Minternal/planner/planner.go+3/-3

5 unmodified lines

6
7
8
9
10
11
12
13
14
9
10
11
12
13
14
15
16
17

5 unmodified lines

"testing"
    "time"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/object"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/storage/memory"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/object"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/storage/memory"
)

func TestSelectBranches(t *testing.T) {

Minternal/planner/planner_test.go+6/-6

1
2
3
4
5
6
4
5
6
7
8
9

package planner

import (
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
)

// CanBootstrapRelay checks whether all desired target refs are absent on the target,

Minternal/planner/relay.go+3/-3

5 unmodified lines

6
7
8
9
9
10
11
12

5 unmodified lines

"sort"
    "strings"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

// RefKind distinguishes branch refs from tag refs.

Minternal/planner/types.go+1/-1

14 unmodified lines

15
16
17
18
19
20
21
22
18
19
20
21
22
23
24
25
491 unmodified lines

517
518
519
520
520
521
522
523
523
524
525
526

14 unmodified lines

"strconv"
    "strings"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/storage/memory"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/storage/memory"

"github.com/soph/git-sync/internal/convert"
    "github.com/soph/git-sync/internal/gitproto"
491 unmodified lines

return "", "", false
    }
    ep := conn.Endpoint
    if ep.Protocol != "http" && ep.Protocol != "https" {
    if ep.Scheme != "http" && ep.Scheme != "https" {
        return "", "", false
    }
    if !strings.EqualFold(ep.Host, "github.com") {
    if !strings.EqualFold(ep.Hostname(), "github.com") {
        return "", "", false
    }
    path := strings.TrimSuffix(strings.Trim(ep.Path, "/"), ".git")

Minternal/strategy/bootstrap/bootstrap.go+7/-7

8 unmodified lines

9
10
11
12
13
14
12
13
14
15
16
17

8 unmodified lines

"net/http/httptest"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/plumbing/transport"

"github.com/soph/git-sync/internal/gitproto"
    "github.com/soph/git-sync/internal/planner"

Minternal/strategy/bootstrap/bootstrap_test.go+3/-3

7 unmodified lines

8
9
10
11
11
12
13
14

7 unmodified lines

"fmt"
    "io"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"

"github.com/soph/git-sync/internal/convert"
    "github.com/soph/git-sync/internal/gitproto"

Minternal/strategy/incremental/incremental.go+1/-1

6 unmodified lines

7
8
9
10
10
11
12
13

6 unmodified lines

"io"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"

"github.com/soph/git-sync/internal/convert"
    "github.com/soph/git-sync/internal/gitproto"

Minternal/strategy/incremental/incremental_test.go+1/-1

6 unmodified lines

7
8
9
10
11
12
10
11
12
13
14
15

6 unmodified lines

"context"
    "fmt"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/storer"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/storer"

"github.com/soph/git-sync/internal/convert"
    "github.com/soph/git-sync/internal/gitproto"

Minternal/strategy/materialized/materialized.go+3/-3

2 unmodified lines

3
4
5
6
6
7
8
9

2 unmodified lines

import (
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"

"github.com/soph/git-sync/internal/convert"
    "github.com/soph/git-sync/internal/gitproto"

Minternal/strategy/materialized/materialized_test.go+1/-1

10 unmodified lines

11
12
13
14
15
14
15
16
17
18
62 unmodified lines

81
82
83
84
85
86
84
85
86
49 unmodified lines

136
137
138
142
143
144
139
140
141
31 unmodified lines

173
174
175
182
183
184

10 unmodified lines

"testing"
    "time"

"github.com/go-git/go-git/v5/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"

"github.com/soph/git-sync/internal/auth"
)
62 unmodified lines

t.Fatalf("new endpoint: %v", err)
    }
    credHost := ep.Host
    if ep.Port > 0 {
        credHost = ep.Host + ":8080"
    }
    writeEntireDBHostsFile(t, configDir, credHost, "test-user")
    // Store token with a future expiration so it's not treated as expired (issue #7).
    futureExpiry := time.Now().Unix() + 3600
49 unmodified lines

t.Fatalf("new endpoint: %v", err)
    }
    credHost := ep.Host
    if ep.Port > 0 {
        credHost = ep.Host + ":" + itoa(ep.Port)
    }
    writeEntireDBHostsFile(t, configDir, credHost, "test-user")
    // Expired token: expiration in the past
    if err := auth.WriteStoredToken("entire:"+credHost, "test-user", "expired-token|1"); err != nil {
31 unmodified lines

}
}

func itoa(n int) string {
    return fmt.Sprintf("%d", n)
}

Minternal/syncer/auth_test.go+2/-11

6 unmodified lines

7
8
9
10
11
12
13
14
15
10
11
12
13
14
15
16
17
18
7 unmodified lines

26
27
28
29
29
30
31
32
23 unmodified lines

56
57
58
59
59
60
61
62
31 unmodified lines

94
95
96
97
97
98
99
100
31 unmodified lines

132
133
134
135
135
136
137
138

6 unmodified lines

"testing"
    "time"

billy "github.com/go-git/go-billy/v5"
    "github.com/go-git/go-billy/v5/memfs"
    git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/storage/memory"
    billy "github.com/go-git/go-billy/v6"
    "github.com/go-git/go-billy/v6/memfs"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/storage/memory"
)

func BenchmarkRunBootstrapEmptyTarget(b *testing.B) {
7 unmodified lines

b.ResetTimer()
    for i := 0; i < b.N; i++ {
        b.StopTimer()
        targetRepo, err := git.Init(memory.NewStorage(), nil)
        targetRepo, err := git.Init(memory.NewStorage())
        if err != nil {
            b.Fatalf("init target repo: %v", err)
        }
23 unmodified lines

sourceRepo, sourceFS := newBenchRepo(b)
        makeBenchCommits(b, sourceRepo, sourceFS, 2)

targetRepo, err := git.Init(memory.NewStorage(), nil)
        targetRepo, err := git.Init(memory.NewStorage())
        if err != nil {
            b.Fatalf("init target repo: %v", err)
        }
31 unmodified lines

sourceRepo, sourceFS := newBenchRepo(b)
        makeBenchCommits(b, sourceRepo, sourceFS, 3)

func newBenchRepo(b *testing.B) (*git.Repository, billy.Filesystem) {
    b.Helper()
    fs := memfs.New()
    repo, err := git.Init(memory.NewStorage(), fs)
    repo, err := git.Init(memory.NewStorage(), git.WithWorkTree(fs))
    if err != nil {
        b.Fatalf("init repo: %v", err)
    }

Minternal/syncer/benchmark_test.go+10/-10

14 unmodified lines

15
16
17
18
18
19
20
21

14 unmodified lines

"sync"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/soph/git-sync/internal/gitproto"
    "github.com/soph/git-sync/internal/planner"
    bstrap "github.com/soph/git-sync/internal/strategy/bootstrap"

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

14 unmodified lines

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
6 unmodified lines

41
42
43
45
44
45
46
47
40 unmodified lines

88
89
90
92
91
92
93
94
33 unmodified lines

128
129
130
132
131
132
133
134
66 unmodified lines

201
202
203
205
204
205
206
207
68 unmodified lines

276
277
278
280
279
280
281
282
61 unmodified lines

344
345
346
348
347
348
349
350
42 unmodified lines

393
394
395
397
396
397
398
399
46 unmodified lines

446
447
448
450
449
450
451
452
12 unmodified lines

465
466
467
469
468
469
470
471
55 unmodified lines

527
528
529
531
530
531
532
533
29 unmodified lines

563
564
565
567
566
567
568
569
44 unmodified lines

614
615
616
618
617
618
619
620
24 unmodified lines

645
646
647
649
648
649
650
651
24 unmodified lines

676
677
678
680
679
680
681
682
96 unmodified lines

779
780
781
783
782
783
784
785
34 unmodified lines

820
821
822
824
823
824
825
826
41 unmodified lines

868
869
870
872
871
872
873
874
81 unmodified lines

956
957
958
960
959
960
961
962
140 unmodified lines

1103
1104
1105
1107
1106
1107
1108
1109
43 unmodified lines

1153
1154
1155
1157
1156
1157
1158
1159
51 unmodified lines

1211
1212
1213
1215
1214
1215
1216
1217
107 unmodified lines

1325
1326
1327
1329
1330
1328
1329
1330
1331
1332
19 unmodified lines

1352
1353
1354
1356
1355
1356
1357
1358
134 unmodified lines

1493
1494
1495
1497
1498
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1503
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1531
1515
1516
1517
1518
1 unmodified line

1520
1521
1522
1539
1523
1524
1525
1 unmodified line

1527
1528
1529
1547
1530
1531
1532
1533
1534
1552
1535
1536
1537
1538
18 unmodified lines

1557
1558
1559
1577
1578
1579
1580
1581
1560
1561
1562
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1563
1596
1564
1565
1566
1567
1568
1569
1570
1571
3 unmodified lines

1575
1576
1577
1606
1578
1579
1580
1581
28 unmodified lines

1610
1611
1612
1641
1613
1643
1614
1615
1616
1617
1618
1648
1619
1620
1621
1622
32 unmodified lines

1655
1656
1657
1687
1688
1658
1659
1660
1661
3 unmodified lines

1665
1666
1667
1698
1668
1669
1670
1671
1672
1703
1673
1674
1675
1676
35 unmodified lines

1712
1713
1714
1745
1746
1747
1748
1749
1715
1716
1751
1752
1753
1754
1755
1717
1718
1719
1720
1721
1722
1723
1724
1725
1757
1726
1727
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1728
1729
1730
1731
1 unmodified line

1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1780
1748
1749
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1750
1751
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1752
1753
1754
1755
1809
1810
1756
1812
1813
1814
1815
1816
1817
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1823
1767
1768
1769
1770
1771
1772
1773
1830
1831
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1833
1834
1835
1790
1791
1792
1793
1794
1837
1795
1796
1797
1798
1839
1840
1841
1842
1843
1844
1845
1846
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
89 unmodified lines

1903
1904
1905
1945
1946
1906
1907
1908
1948
1949
1950
1909
1910
1911
1912
1913
1914
1954
1955
1915
1916
1917
1918
1919
1920
1921
1922
1923

14 unmodified lines

"testing"
    "time"

billy "github.com/go-git/go-billy/v5"
    "github.com/go-git/go-billy/v5/memfs"
    git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/format/packfile"
    "github.com/go-git/go-git/v5/plumbing/format/pktline"
    "github.com/go-git/go-git/v5/plumbing/object"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v5/plumbing/revlist"
    "github.com/go-git/go-git/v5/plumbing/transport"
    transportclient "github.com/go-git/go-git/v5/plumbing/transport/client"
    transporthttp "github.com/go-git/go-git/v5/plumbing/transport/http"
    transportserver "github.com/go-git/go-git/v5/plumbing/transport/server"
    "github.com/go-git/go-git/v5/storage/memory"
    billy "github.com/go-git/go-billy/v6"
    "github.com/go-git/go-billy/v6/memfs"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/format/packfile"
    "github.com/go-git/go-git/v6/plumbing/format/pktline"
    "github.com/go-git/go-git/v6/plumbing/object"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp/sideband"
    "github.com/go-git/go-git/v6/plumbing/revlist"
    "github.com/go-git/go-git/v6/plumbing/transport"
    transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
    "github.com/go-git/go-git/v6/storage/memory"
    "github.com/soph/git-sync/internal/auth"
    "github.com/soph/git-sync/internal/gitproto"
    "github.com/soph/git-sync/internal/planner"
6 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 6)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
40 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeLargeCommits(t, sourceRepo, sourceFS, 20, 200_000)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
33 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 2)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
66 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 4)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
68 unmodified lines

t.Fatalf("resolve unrelated head: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
61 unmodified lines

t.Fatalf("resolve source head: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
42 unmodified lines

t.Fatalf("resolve source head: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
46 unmodified lines

t.Fatalf("resolve source head: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
12 unmodified lines

defer targetServer.Close()

failDeleteOnce := true
    targetServer.commandHook = func(req *packp.ReferenceUpdateRequest) *packp.ReportStatus {
    targetServer.commandHook = func(req *packp.UpdateRequests) *packp.ReportStatus {
        if !failDeleteOnce || len(req.Commands) != 1 {
            return nil
        }
55 unmodified lines

t.Fatalf("set lightweight tag: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
29 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 3)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
44 unmodified lines

t.Fatalf("set source tag: %v", err)
    }

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
24 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 2)

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 10)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
96 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 3)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
34 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 2)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
41 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 4)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
81 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 1)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
140 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 2)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
43 unmodified lines

sourceRepo, sourceFS := newSourceRepo(t)
    makeCommits(t, sourceRepo, sourceFS, 4)

targetRepo, err := git.Init(memory.NewStorage(), nil)
    targetRepo, err := git.Init(memory.NewStorage())
    if err != nil {
        t.Fatalf("init target repo: %v", err)
    }
51 unmodified lines

t.Helper()

fs := memfs.New()
    repo, err := git.Init(memory.NewStorage(), fs)
    repo, err := git.Init(memory.NewStorage(), git.WithWorkTree(fs))
    if err != nil {
        t.Fatalf("init source repo: %v", err)
    }
107 unmodified lines

type metricKind string

const (
    serviceUploadPack  = transport.UploadPackServiceName
    serviceReceivePack = transport.ReceivePackServiceName
    serviceUploadPack  = string(transport.UploadPackService)
    serviceReceivePack = string(transport.ReceivePackService)

metricInfoRefs metricKind = "info_refs"
    metricPack     metricKind = "pack"
19 unmodified lines

receivePackBodyLimit int64
    receivePackNoThin    bool
    commandHook          func(*packp.ReferenceUpdateRequest) *packp.ReportStatus
    commandHook          func(*packp.UpdateRequests) *packp.ReportStatus

mu      sync.Mutex
    metrics []exchangeMetric
134 unmodified lines

return
    }

session, err := s.newSession(service)
    if err != nil {
    var buf bytes.Buffer
    svc := transport.Service(service)
    if err := transport.AdvertiseReferences(r.Context(), s.repo.Storer, &buf, svc, false); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

var ar *packp.AdvRefs
    switch service {
    case serviceUploadPack:
        ar, err = session.(transport.UploadPackSession).AdvertisedReferencesContext(r.Context())
    case serviceReceivePack:
        ar, err = session.(transport.ReceivePackSession).AdvertisedReferencesContext(r.Context())
    }
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    if service == serviceReceivePack && s.receivePackNoThin {
        _ = ar.Capabilities.Set(capability.Capability("no-thin"))
    }

ar.Prefix = [][]byte{
        []byte("# service=" + service),
        pktline.Flush,
    }

var buf bytes.Buffer
    if err := ar.Encode(&buf); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
        // For no-thin, we need to decode, modify capabilities, and re-encode
        ar := packp.NewAdvRefs()
        if err := ar.Decode(bytes.NewReader(buf.Bytes())); err == nil {
            _ = ar.Capabilities.Set(capability.Capability("no-thin"))
            buf.Reset()
            _ = ar.Encode(&buf)
        }
    }

w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", service))
    if _, err := w.Write(buf.Bytes()); err != nil {
            s.tb.Fatalf("write advertised refs: %v", err)
        s.tb.Fatalf("write advertised refs: %v", err)
    }

s.recordMetric(service, metricInfoRefs, 0, int64(buf.Len()), 0, 0)
1 unmodified line

func (s *smartHTTPRepoServer) handleInfoRefsV2(w http.ResponseWriter, r *http.Request) {
    var buf bytes.Buffer
    enc := pktline.NewEncoder(&buf)
    lines := []string{
        "version 2\n",
        "ls-refs=unborn\n",
1 unmodified line

"agent=test-server\n",
    }
    for _, line := range lines {
        if err := enc.EncodeString(line); err != nil {
        if _, err := pktline.WriteString(&buf, line); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
    if err := enc.Flush(); err != nil {
    if err := pktline.WriteFlush(&buf); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
18 unmodified lines

return
    }

session, err := s.newSession(serviceUploadPack)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    wantCount := strings.Count(string(body), "want ")
    haveCount := strings.Count(string(body), "have ")

req := packp.NewUploadPackRequest()
    if err := req.Decode(bytes.NewReader(body)); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

var buf bytes.Buffer
    if err := resp.Encode(&buf); err != nil {
    reader := io.NopCloser(bytes.NewReader(body))
    writer := nopWriteCloser{&buf}
    if err := transport.UploadPack(r.Context(), s.repo.Storer, reader, writer, &transport.UploadPackOptions{
        StatelessRPC: true,
    }); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
3 unmodified lines

s.tb.Fatalf("write upload-pack response: %v", err)
    }

s.recordMetric(serviceUploadPack, metricPack, int64(len(body)), int64(buf.Len()), len(req.Wants), strings.Count(string(body), "have "))
    s.recordMetric(serviceUploadPack, metricPack, int64(len(body)), int64(buf.Len()), wantCount, haveCount)
}

func (s *smartHTTPRepoServer) handleUploadPackV2(w http.ResponseWriter, r *http.Request, body []byte) {
28 unmodified lines

}

var buf bytes.Buffer
    enc := pktline.NewEncoder(&buf)
    for _, ref := range refs {
        if err := enc.EncodeString(ref.Hash().String() + " " + ref.Name().String() + "\n"); err != nil {
        if _, err := pktline.WriteString(&buf, ref.Hash().String()+" "+ref.Name().String()+"\n"); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
    if err := enc.Flush(); err != nil {
    if err := pktline.WriteFlush(&buf); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
32 unmodified lines

}

var buf bytes.Buffer
    pkt := pktline.NewEncoder(&buf)
    if err := pkt.EncodeString("packfile\n"); err != nil {
    if _, err := pktline.WriteString(&buf, "packfile\n"); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
3 unmodified lines

end = pack.Len()
        }
        payload := append([]byte{1}, pack.Bytes()[offset:end]...)
        if err := pkt.Encode(payload); err != nil {
        if _, err := pktline.Write(&buf, payload); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
    if err := pkt.Flush(); err != nil {
    if err := pktline.WriteFlush(&buf); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
35 unmodified lines

return
    }

session, err := s.newSession(serviceReceivePack)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    hasPack := bytes.Contains(body, []byte("PACK"))

req := packp.NewReferenceUpdateRequest()
    if err := req.Decode(bytes.NewReader(body)); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // For no-PACK requests, handle manually since transport.ReceivePack
    // expects a packfile when there are create/update commands.
    if !hasPack {
        req := packp.NewUpdateRequests()
        if err := req.Decode(bytes.NewReader(body)); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

if !bytes.Contains(body, []byte("PACK")) {
        if s.commandHook != nil {
            if report := s.commandHook(req); report != nil {
                var buf bytes.Buffer
                if err := report.Encode(&buf); err != nil {
                    http.Error(w, err.Error(), http.StatusInternalServerError)
                    return
                }

w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-result", serviceReceivePack))
                if _, err := w.Write(buf.Bytes()); err != nil {
                    s.tb.Fatalf("write receive-pack command hook response: %v", err)
                }
                s.recordMetric(serviceReceivePack, metricPack, int64(len(body)), int64(buf.Len()), 0, 0)
                s.writeReceivePackReport(w, report, req.Capabilities, len(body))
                return
            }
        }
1 unmodified line

report := packp.NewReportStatus()
        report.UnpackStatus = "ok"
        for _, cmd := range req.Commands {
            status := "ok"
            if cmd.New.IsZero() {
                if err := s.repo.Storer.RemoveReference(cmd.Name); err != nil {
                    status = err.Error()
                }
            } else {
                if err := s.repo.Storer.SetReference(plumbing.NewHashReference(cmd.Name, cmd.New)); err != nil {
                    status = err.Error()
                }
            }
            report.CommandStatuses = append(report.CommandStatuses, &packp.CommandStatus{
                ReferenceName: cmd.Name,
                Status:        "ok",
                Status:        status,
            })
            if cmd.New.IsZero() {
                if err := s.repo.Storer.RemoveReference(cmd.Name); err != nil {
                    http.Error(w, err.Error(), http.StatusInternalServerError)
                    return
                }
                continue
            }
            if err := s.repo.Storer.SetReference(plumbing.NewHashReference(cmd.Name, cmd.New)); err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
        }

var buf bytes.Buffer
        if err := report.Encode(&buf); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-result", serviceReceivePack))
        if _, err := w.Write(buf.Bytes()); err != nil {
            s.tb.Fatalf("write receive-pack command response: %v", err)
        }
        s.recordMetric(serviceReceivePack, metricPack, int64(len(body)), int64(buf.Len()), 0, 0)
        s.writeReceivePackReport(w, report, req.Capabilities, len(body))
        return
    }

report, err := session.(transport.ReceivePackSession).ReceivePack(r.Context(), req)

var buf bytes.Buffer
    if report != nil {
        if err := report.Encode(&buf); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
    reader := io.NopCloser(bytes.NewReader(body))
    writer := nopWriteCloser{&buf}
    rpErr := transport.ReceivePack(r.Context(), s.repo.Storer, reader, writer, &transport.ReceivePackOptions{
        StatelessRPC: true,
    })

w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-result", serviceReceivePack))
    if _, err := w.Write(buf.Bytes()); err != nil {
        s.tb.Fatalf("write receive-pack response: %v", err)
    }
    if err != nil {
    if rpErr != nil {
        return
    }

s.recordMetric(serviceReceivePack, metricPack, int64(len(body)), int64(buf.Len()), 0, 0)
}

func (s *smartHTTPRepoServer) newSession(service string) (interface{}, error) {
    loader := transportserver.MapLoader{}
// writeReceivePackReport encodes a report-status, optionally wrapped in
// sideband (matching what transport.ReceivePack would produce), and writes it.
func (s *smartHTTPRepoServer) writeReceivePackReport(w http.ResponseWriter, report *packp.ReportStatus, caps *capability.List, bodyLen int) {
    var buf bytes.Buffer
    var writer io.Writer = &buf
    useSideband := false
    if caps != nil && !caps.Supports(capability.NoProgress) {
        if caps.Supports(capability.Sideband64k) {
            writer = sideband.NewMuxer(sideband.Sideband64k, &buf)
            useSideband = true
        } else if caps.Supports(capability.Sideband) {
            writer = sideband.NewMuxer(sideband.Sideband, &buf)
            useSideband = true
        }
    }

endpoint, err := transport.NewEndpoint(s.RepoURL())
    if err != nil {
        return nil, err
    wc := nopWriteCloser{writer}
    if err := report.Encode(wc); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    loader[endpoint.String()] = s.repo.Storer
    if useSideband {
        _ = pktline.WriteFlush(&buf)
    }

srv := transportserver.NewServer(loader)
    switch service {
    case serviceUploadPack:
        return srv.NewUploadPackSession(endpoint, nil)
    case serviceReceivePack:
        return srv.NewReceivePackSession(endpoint, nil)
    default:
        return nil, fmt.Errorf("unknown service %q", service)
    w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-result", serviceReceivePack))
    if _, err := w.Write(buf.Bytes()); err != nil {
        s.tb.Fatalf("write receive-pack report: %v", err)
    }
    s.recordMetric(serviceReceivePack, metricPack, int64(bodyLen), int64(buf.Len()), 0, 0)
}

// nopWriteCloser wraps an io.Writer to satisfy io.WriteCloser.
type nopWriteCloser struct{ io.Writer }

func (nopWriteCloser) Close() error { return nil }

func (s *smartHTTPRepoServer) recordMetric(service string, kind metricKind, in, out int64, wants, haves int) {
    s.mu.Lock()
    defer s.mu.Unlock()
89 unmodified lines

}

func TestMain(m *testing.M) {
    originalHTTP := transportclient.Protocols["http"]
    originalHTTPS := transportclient.Protocols["https"]
    originalHTTP, _ := transport.Get("http")
    originalHTTPS, _ := transport.Get("https")

customHTTP := transporthttp.NewClient(&http.Client{})
    transportclient.InstallProtocol("http", customHTTP)
    transportclient.InstallProtocol("https", customHTTP)
    customHTTP := transporthttp.NewTransport(&transporthttp.TransportOptions{Client: &http.Client{}})
    transport.Register("http", customHTTP)
    transport.Register("https", customHTTP)

code := m.Run()

transportclient.InstallProtocol("http", originalHTTP)
    transportclient.InstallProtocol("https", originalHTTPS)
    if originalHTTP != nil {
        transport.Register("http", originalHTTP)
    }
    if originalHTTPS != nil {
        transport.Register("https", originalHTTPS)
    }

os.Exit(code)
}

Minternal/syncer/integration_test.go+134/-169

6 unmodified lines

7
8
9
10
10
11
12
13

6 unmodified lines

"path/filepath"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v6/plumbing"
)

const liveLinuxEnv = "GITSYNC_E2E_LIVE_LINUX"

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

12 unmodified lines

13
14
15
16
17
18
19
20
21
16
17
18
19
20
21
22
23
24
329 unmodified lines

354
355
356
357
357
358
359
360

12 unmodified lines

"sort"
    "strings"

git "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/protocol/packp"
    "github.com/go-git/go-git/v5/plumbing/storer"
    "github.com/go-git/go-git/v5/plumbing/transport"
    "github.com/go-git/go-git/v5/storage/memory"
    git "github.com/go-git/go-git/v6"
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/go-git/go-git/v6/plumbing/protocol/packp"
    "github.com/go-git/go-git/v6/plumbing/storer"
    "github.com/go-git/go-git/v6/plumbing/transport"
    "github.com/go-git/go-git/v6/storage/memory"

"github.com/soph/git-sync/internal/auth"
    "github.com/soph/git-sync/internal/convert"
329 unmodified lines

if err != nil {
            return nil, fmt.Errorf("create target transport: %w", err)
        }
        s.targetAdv, err = gitproto.AdvertisedRefsV1(ctx, s.targetConn, transport.ReceivePackServiceName)
        s.targetAdv, err = gitproto.AdvertisedRefsV1(ctx, s.targetConn, transport.ReceivePackService)
        if err != nil {
            return nil, fmt.Errorf("list target refs: %w", err)
        }

Minternal/syncer/syncer.go+7/-7