Merge pull request #17 from entireio/soph/move-to-root-package · Entire

Home

Log in

Merge pull request #17 from entireio/soph/move-to-root-package

d06e573→main·

Soph·2mo ago·51 files·+1,626 added/-1,626 removed

move package to root level

Changes

51

96 unmodified lines

97
98
99
100
100
101
102
103

96 unmodified lines

- github.com/go-git/go-git/v6/storage.Storer
        - github.com/go-git/go-git/v6/plumbing/storer.EncodedObjectIter
        - github.com/go-git/go-billy/v6.Filesystem
        - entire.io/entire/git-sync/internal/auth.Method
        - entire.io/entire/gitsync/internal/auth.Method
    nolintlint:
      require-explanation: true
      require-specific: true

M.golangci.yaml+1/-1

64 unmodified lines

65
66
67
68
68
69
70
71
72
72
73
74
75
76
76
77
78
78
79
80
81

64 unmodified lines

`git-sync` now has a two-tier Go API:

- `pkg/gitsync`
- `gitsync`
  - stable embedding surface for queue workers and other external callers
  - typed `Probe`, `Plan`, `Sync`, and `Replicate` requests/results
  - injected auth and HTTP client support
- `pkg/gitsync/unstable`
- `unstable`
  - explicitly non-stable surface for first-party tooling and advanced controls
  - includes `Bootstrap`, `Fetch`, batching and measurement knobs, and CLI-oriented execution options

If you are embedding `git-sync` outside this repo, prefer `pkg/gitsync`. The CLI and benchmark command use `pkg/gitsync/unstable` because they still need direct access to advanced engine controls that are intentionally not part of the stable API.
If you are embedding `git-sync` outside this repo, prefer `gitsync`. The CLI and benchmark command use `unstable` because they still need direct access to advanced engine controls that are intentionally not part of the stable API.

The stable `pkg/gitsync` results are shaped for workers:
The stable `gitsync` results are shaped for workers:

- `Refs`
  - per-ref outcomes

MREADME.md+4/-4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257

package gitsync

import (
    "context"
    "errors"
    "fmt"
    "net/http"

"entire.io/entire/gitsync/internal/validation"
    "entire.io/entire/gitsync/internalbridge"
)

// Options configures a Client. It is intentionally small in the first public cut.
type Options struct {
    HTTPClient *http.Client
    Auth       AuthProvider
}

// Client provides the public orchestration API for git-sync.
type Client struct {
    httpClient *http.Client
    auth       AuthProvider
}

// New constructs a new Client.
func New(opts Options) *Client {
    return &Client{httpClient: opts.HTTPClient, auth: opts.Auth}
}

// Probe inspects a source remote and optional target remote.
func (c *Client) Probe(ctx context.Context, req ProbeRequest) (ProbeResult, error) {
    if err := req.Validate(); err != nil {
        return ProbeResult{}, err
    }
    cfg, err := c.buildProbeConfig(ctx, req)
    if err != nil {
        return ProbeResult{}, err
    }
    result, err := internalbridge.Probe(ctx, cfg)
    if err != nil {
        return ProbeResult{}, fmt.Errorf("probe: %w", err)
    }
    return internalbridge.FromProbeResult(result), nil
}

// Plan computes ref actions without pushing.
func (c *Client) Plan(ctx context.Context, req PlanRequest) (PlanResult, error) {
    if err := req.Validate(); err != nil {
        return PlanResult{}, err
    }
    cfg, err := c.buildSyncConfig(ctx, req.Source, req.Target, req.Scope, req.Policy, req.CollectStats, true)
    if err != nil {
        return PlanResult{}, err
    }
    result, err := internalbridge.Run(ctx, cfg)
    if err != nil {
        return PlanResult{}, fmt.Errorf("plan: %w", err)
    }
    return internalbridge.FromSyncResult(result), nil
}

// Sync executes a sync between two remotes.
func (c *Client) Sync(ctx context.Context, req SyncRequest) (SyncResult, error) {
    if err := req.Validate(); err != nil {
        return SyncResult{}, err
    }
    cfg, err := c.buildSyncConfig(ctx, req.Source, req.Target, req.Scope, req.Policy, req.CollectStats, false)
    if err != nil {
        return SyncResult{}, err
    }
    result, err := internalbridge.Run(ctx, cfg)
    if err != nil {
        return SyncResult{}, fmt.Errorf("sync: %w", err)
    }
    return internalbridge.FromSyncResult(result), nil
}

// Replicate executes source-authoritative relay-only replication between two remotes.
func (c *Client) Replicate(ctx context.Context, req SyncRequest) (SyncResult, error) {
    req.Policy.Mode = ModeReplicate
    return c.Sync(ctx, req)
}

func (c *Client) buildProbeConfig(ctx context.Context, req ProbeRequest) (internalbridge.Config, error) {
    sourceAuth, err := c.authFor(ctx, req.Source, SourceRole)
    if err != nil {
        return internalbridge.Config{}, err
    }
    var target *internalbridge.Endpoint
    targetAuth := internalbridge.EndpointAuth{}
    if req.Target != nil {
        resolvedTargetAuth, err := c.authFor(ctx, *req.Target, TargetRole)
        if err != nil {
            return internalbridge.Config{}, err
        }
        target = ptr(bridgeEndpoint(*req.Target))
        targetAuth = bridgeEndpointAuth(resolvedTargetAuth)
    }
    return internalbridge.ProbeConfig(
        bridgeEndpoint(req.Source),
        bridgeEndpointAuth(sourceAuth),
        target,
        targetAuth,
        internalbridge.ProtocolMode(req.Protocol),
        req.IncludeTags,
        req.CollectStats,
        c.httpClient,
    ), nil
}

func (c *Client) buildSyncConfig(ctx context.Context, source Endpoint, target Endpoint, scope RefScope, policy SyncPolicy, collectStats, dryRun bool) (internalbridge.Config, error) {
    sourceAuth, err := c.authFor(ctx, source, SourceRole)
    if err != nil {
        return internalbridge.Config{}, err
    }
    targetAuth, err := c.authFor(ctx, target, TargetRole)
    if err != nil {
        return internalbridge.Config{}, err
    }
    return internalbridge.SyncConfig(
        bridgeEndpoint(source),
        bridgeEndpointAuth(sourceAuth),
        bridgeEndpoint(target),
        bridgeEndpointAuth(targetAuth),
        bridgeScope(scope),
        bridgePolicy(policy),
        collectStats,
        dryRun,
        c.httpClient,
    ), nil
}

func (c *Client) authFor(ctx context.Context, endpoint Endpoint, role EndpointRole) (EndpointAuth, error) {
    if c.auth == nil {
        return EndpointAuth{}, nil
    }
    auth, err := c.auth.AuthFor(ctx, endpoint, role)
    if err != nil {
        return EndpointAuth{}, fmt.Errorf("resolve auth for %s: %w", role, err)
    }
    return auth, nil
}

func (r SyncRequest) Validate() error {
    if r.Source.URL == "" {
        return errors.New("source URL is required")
    }
    if r.Target.URL == "" {
        return errors.New("target URL is required")
    }
    if err := validateOperationMode(r.Policy.Mode); err != nil {
        return err
    }
    if _, err := validation.NormalizeProtocolMode(string(r.Policy.Protocol)); err != nil {
        return fmt.Errorf("normalize protocol: %w", err)
    }
    if _, err := validation.ValidateMappings(validationMappings(r.Scope.Mappings)); err != nil {
        return fmt.Errorf("validate mappings: %w", err)
    }
    return nil
}

func (r PlanRequest) Validate() error {
    if r.Source.URL == "" {
        return errors.New("source URL is required")
    }
    if r.Target.URL == "" {
        return errors.New("target URL is required")
    }
    if err := validateOperationMode(r.Policy.Mode); err != nil {
        return err
    }
    if _, err := validation.NormalizeProtocolMode(string(r.Policy.Protocol)); err != nil {
        return fmt.Errorf("normalize protocol: %w", err)
    }
    if _, err := validation.ValidateMappings(validationMappings(r.Scope.Mappings)); err != nil {
        return fmt.Errorf("validate mappings: %w", err)
    }
    return nil
}

func (r ProbeRequest) Validate() error {
    if r.Source.URL == "" {
        return errors.New("source URL is required")
    }
    if r.Target != nil && r.Target.URL == "" {
        return errors.New("target URL is required when target endpoint is provided")
    }
    if _, err := validation.NormalizeProtocolMode(string(r.Protocol)); err != nil {
        return fmt.Errorf("normalize protocol: %w", err)
    }
    return nil
}

func bridgeEndpoint(ep Endpoint) internalbridge.Endpoint {
    return internalbridge.Endpoint{
        URL:                    ep.URL,
        FollowInfoRefsRedirect: ep.FollowInfoRefsRedirect,
    }
}

func bridgeEndpointAuth(auth EndpointAuth) internalbridge.EndpointAuth {
    return internalbridge.EndpointAuth{
        Username:      auth.Username,
        Token:         auth.Token,
        BearerToken:   auth.BearerToken,
        SkipTLSVerify: auth.SkipTLSVerify,
    }
}

func bridgeScope(scope RefScope) internalbridge.RefScope {
    mappings := make([]internalbridge.RefMapping, 0, len(scope.Mappings))
    for _, mapping := range scope.Mappings {
        mappings = append(mappings, internalbridge.RefMapping{
            Source: mapping.Source,
            Target: mapping.Target,
        })
    }
    return internalbridge.RefScope{
        Branches: append([]string(nil), scope.Branches...),
        Mappings: mappings,
    }
}

func bridgePolicy(policy SyncPolicy) internalbridge.SyncPolicy {
    return internalbridge.SyncPolicy{
        Mode:        internalbridge.OperationMode(policy.Mode),
        IncludeTags: policy.IncludeTags,
        Force:       policy.Force,
        Prune:       policy.Prune,
        Protocol:    internalbridge.ProtocolMode(policy.Protocol),
    }
}

func validateOperationMode(mode OperationMode) error {
    switch mode {
    case "", ModeSync, ModeReplicate:
        return nil
    default:
        return fmt.Errorf("unsupported operation mode %q", mode)
    }
}

func ptr[T any](v T) *T {
    return &v
}

func validationMappings(mappings []RefMapping) []validation.RefMapping {
    out := make([]validation.RefMapping, 0, len(mappings))
    for _, mapping := range mappings {
        out = append(out, validation.RefMapping{
            Source: mapping.Source,
            Target: mapping.Target,
        })
    }
    return out
}

Aclient.go+257

package gitsync

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

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/transport"

"entire.io/entire/gitsync/internal/syncertest" )

type errAuthProvider struct{}

func (errAuthProvider) AuthFor(_ context.Context, _ Endpoint, _ EndpointRole) (EndpointAuth, error) { return EndpointAuth{}, errors.New("boom") }

func TestValidateRequests(t *testing.T) { if err := (ProbeRequest{}).Validate(); err == nil { t.Fatalf("expected probe validation error") } if err := (PlanRequest{}).Validate(); err == nil { t.Fatalf("expected plan validation error") } if err := (SyncRequest{}).Validate(); err == nil { t.Fatalf("expected sync validation error") } if err := (ProbeRequest{ Source: Endpoint{URL: "https://source.example/repo.git"}, Protocol: "bogus", }).Validate(); err == nil { t.Fatalf("expected invalid probe protocol validation error") } if err := (SyncRequest{ Source: Endpoint{URL: "https://source.example/repo.git"}, Target: Endpoint{URL: "https://target.example/repo.git"}, Policy: SyncPolicy{Protocol: "bogus"}, }).Validate(); err == nil { t.Fatalf("expected invalid sync protocol validation error") } if err := (PlanRequest{ Source: Endpoint{URL: "https://source.example/repo.git"}, Target: Endpoint{URL: "https://target.example/repo.git"}, Scope: RefScope{ Mappings: []RefMapping{ {Source: "main", Target: "stable"}, {Source: "release", Target: "stable"}, }, }, }).Validate(); err == nil { t.Fatalf("expected duplicate mapping validation error") } }

func TestClientReturnsAuthProviderErrors(t *testing.T) { _, err := New(Options{Auth: errAuthProvider{}}).buildProbeConfig(context.Background(), ProbeRequest{ Source: Endpoint{URL: "https://source.example/repo.git"}, }) if err == nil { t.Fatalf("expected auth provider error") } }

func TestClientSyncEndToEndWithLocalRepos(t *testing.T) { sourceRepo, sourceFS := syncertest.NewMemoryRepo(t) syncertest.MakeCommits(t, sourceRepo, sourceFS, 1) targetRepo, _ := syncertest.NewMemoryRepo(t)

sourceServer := newSmartHTTPRepoServer(t, sourceRepo) targetServer := newSmartHTTPRepoServer(t, targetRepo) defer sourceServer.Close() defer targetServer.Close()

client := New(Options{}) result, err := client.Sync(context.Background(), SyncRequest{ Source: Endpoint{URL: sourceServer.RepoURL()}, Target: Endpoint{URL: targetServer.RepoURL()}, Scope: RefScope{Branches: []string{"master"}}, Policy: SyncPolicy{Protocol: ProtocolV1}, }) if err != nil { t.Fatalf("client sync: %v", err) } if len(result.Refs) != 1 || result.Refs[0].Action != ActionCreate { t.Fatalf("unexpected ref results: %+v", result.Refs) } if result.Counts.Applied != 1 { t.Fatalf("applied = %d, want 1", result.Counts.Applied) }

targetRef, err := targetRepo.Reference(plumbing.NewBranchReferenceName("master"), true) if err != nil { t.Fatalf("resolve target ref: %v", err) } sourceRef, err := sourceRepo.Reference(plumbing.NewBranchReferenceName("master"), true) if err != nil { t.Fatalf("resolve source ref: %v", err) } if targetRef.Hash() != sourceRef.Hash() { t.Fatalf("target hash = %s, want %s", targetRef.Hash(), sourceRef.Hash()) } }

func TestClientReplicateRejectsUnsupportedMode(t *testing.T) { err := (SyncRequest{ Source: Endpoint{URL: "https://source.example/repo.git"}, Target: Endpoint{URL: "https://target.example/repo.git"}, Policy: SyncPolicy{Mode: "bogus"}, }).Validate() if err == nil { t.Fatalf("expected invalid operation mode validation error") } }

type smartHTTPRepoServer struct { tb testing.TB repo *git.Repository repoPath string server *httptest.Server }

func newSmartHTTPRepoServer(tb testing.TB, repo *git.Repository) *smartHTTPRepoServer { tb.Helper()

s := &smartHTTPRepoServer{ tb: tb, repo: repo, repoPath: "/repo.git", } s.server = httptest.NewServer(http.HandlerFunc(s.handle)) return s }

func (s *smartHTTPRepoServer) Close() { s.server.Close() }

func (s *smartHTTPRepoServer) RepoURL() string { return s.server.URL + s.repoPath }

func (s *smartHTTPRepoServer) handle(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && r.URL.Path == s.repoPath+"/info/refs": s.handleInfoRefs(w, r) case r.Method == http.MethodPost && r.URL.Path == s.repoPath+"/git-upload-pack": s.handleUploadPack(w, r) case r.Method == http.MethodPost && r.URL.Path == s.repoPath+"/git-receive-pack": s.handleReceivePack(w, r) default: http.NotFound(w, r) } }

func (s *smartHTTPRepoServer) handleInfoRefs(w http.ResponseWriter, r *http.Request) { service := r.URL.Query().Get("service") if service != "git-upload-pack" && service != "git-receive-pack" { http.Error(w, "missing service", http.StatusBadRequest) return }

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

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) } }

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 reader := io.NopCloser(bytes.NewReader(body)) writer := nopWriteCloser{&buf} if err := transport.UploadPack(r.Context(), s.repo.Storer, reader, writer, &transport.UploadPackRequest{ StatelessRPC: true, }); 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.tb.Fatalf("write upload-pack response: %v", err) } }

func (s *smartHTTPRepoServer) handleReceivePack(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()

if !bytes.Contains(body, []byte("PACK")) { req := packp.NewUpdateRequests() if err := req.Decode(bytes.NewReader(body)); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }

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: status, }) } s.writeReceivePackReport(w, report) return }

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

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

func (s *smartHTTPRepoServer) writeReceivePackReport(w http.ResponseWriter, report *packp.ReportStatus) { var buf bytes.Buffer if err := report.Encode(nopWriteCloser{&buf}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }

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

type nopWriteCloser struct{ io.Writer }

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


Aclient\_test.go+275

14 unmodified lines

15 16 17 18 19 20 18 19 20 21 22 23

14 unmodified lines

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

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/git-sync/pkg/gitsync" "entire.io/entire/git-sync/pkg/gitsync/unstable" "entire.io/entire/gitsync" "entire.io/entire/gitsync/internal/validation" "entire.io/entire/gitsync/unstable" )

type scenario string


Mcmd/git-sync-bench/main.go+3/-3

2 unmodified lines

3 4 5 6 6 7 8 9

2 unmodified lines

import ( "testing"

"entire.io/entire/git-sync/pkg/gitsync/unstable" "entire.io/entire/gitsync/unstable" )

func TestSummarizeRuns(t *testing.T) {


Mcmd/git-sync-bench/main\_test.go+1/-1

8 unmodified lines

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

8 unmodified lines

"os" "strings"

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/git-sync/pkg/gitsync" "entire.io/entire/git-sync/pkg/gitsync/unstable" "entire.io/entire/gitsync" "entire.io/entire/gitsync/internal/validation" "entire.io/entire/gitsync/unstable" "github.com/go-git/go-git/v6/plumbing" )


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

13 unmodified lines

14 15 16 17 17 18 19 20

13 unmodified lines

"testing" "time"

"entire.io/entire/git-sync/pkg/gitsync/unstable" "entire.io/entire/gitsync/unstable" billy "github.com/go-git/go-billy/v6" "github.com/go-git/go-billy/v6/memfs" git "github.com/go-git/go-git/v6"


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

1

No patch available.


Rdoc.go

89 unmodified lines

90 91 92 93 93 94 95 96 97 97 98 99 100 19 unmodified lines

120 121 122 123 123 124 125 125 126 127 128 7 unmodified lines

136 137 138 139 140 139 140 141 142 143

89 unmodified lines

Package Model

The project now separates embedding concerns from first-party tooling concerns:

The stable result contract is also intentionally worker-oriented: 7 unmodified lines

That split is intentional:

Protocol Boundaries


Mdocs/architecture.md+6/-6

3 unmodified lines

4 5 6 7 8 7 8 9 10 11 12 12 13 14 15 2 unmodified lines

18 19 20 21 21 22 23 24 2 unmodified lines

27 28 29 30 30 31 32 33 15 unmodified lines

49 50 51 52 52 53 54 55 27 unmodified lines

83 84 85 86 86 87 88 89 71 unmodified lines

161 162 163 164 164

3 unmodified lines

For most embedders, there are two important rules:

Stable vs Unstable

Use pkg/gitsync when you want a durable worker-facing API: Use gitsync when you want a durable worker-facing API:

Use pkg/gitsync/unstable only when you need controls that are intentionally not yet stable: Use unstable only when you need controls that are intentionally not yet stable:

The CLI and benchmark command use pkg/gitsync/unstable because they still need those controls. External workers should generally not. The CLI and benchmark command use unstable because they still need those controls. External workers should generally not.

Worker Shape

15 unmodified lines

"context" "net/http"

"entire.io/entire/git-sync/pkg/gitsync" "entire.io/entire/gitsync" )

func runSync(ctx context.Context) error { 27 unmodified lines

Auth Injection

pkg/gitsync uses one auth ownership model: gitsync uses one auth ownership model:

Those are implementation details or advanced controls that currently belong in pkg/gitsync/unstable, not the stable embedding contract. Those are implementation details or advanced controls that currently belong in unstable, not the stable embedding contract.


Mdocs/embedding.md+8/-8

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

package gitsync_test

import ( "context" "net/http"

"entire.io/entire/gitsync" )

func ExampleClient_Sync() { client := gitsync.New(gitsync.Options{ HTTPClient: &http.Client{}, Auth: gitsync.StaticAuthProvider{ Source: gitsync.EndpointAuth{Token: "source-token"}, Target: gitsync.EndpointAuth{Token: "target-token"}, }, })

if _, err := client.Sync(context.Background(), gitsync.SyncRequest{ Source: gitsync.Endpoint{URL: "https://github.example/source/repo.git"}, Target: gitsync.Endpoint{URL: "https://git.example/target/repo.git"}, Scope: gitsync.RefScope{Branches: []string{"main"}}, Policy: gitsync.SyncPolicy{ IncludeTags: true, Protocol: gitsync.ProtocolAuto, }, }); err != nil { return // network error expected in example environment }

// Output: }


Aexample\_test.go+32

1 1 2 3 4

module entire.io/entire/git-sync module entire.io/entire/gitsync

go 1.26.2


Mgo.mod+1/-1

5 unmodified lines

6 7 8 9 10 9 10 11 12 13

5 unmodified lines

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

"entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" )

// DesiredRefs converts planner desired refs to gitproto desired refs.


Minternal/convert/convert.go+2/-2

4 unmodified lines

5 6 7 8 9 8 9 10 11 12

4 unmodified lines

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

func TestDesiredRefsForPlans(t *testing.T) {


Minternal/convert/convert\_test.go+2/-2

4 unmodified lines

5 6 7 8 8 9 10 11

4 unmodified lines

"fmt" "sort"

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/gitsync/internal/validation" "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"


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

5 unmodified lines

6 7 8 9 9 10 11 12

5 unmodified lines

"testing" "time"

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/gitsync/internal/validation" 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"


Minternal/planner/planner\_test.go+1/-1

5 unmodified lines

6 7 8 9 9 10 11 12

5 unmodified lines

"sort" "strings"

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/gitsync/internal/validation" "github.com/go-git/go-git/v6/plumbing" )


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

22 unmodified lines

23 24 25 26 27 28 26 27 28 29 30 31

22 unmodified lines

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

"entire.io/entire/git-sync/internal/convert" "entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" "entire.io/entire/gitsync/internal/convert" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" )

const (


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

17 unmodified lines

18 19 20 21 22 21 22 23 24 25

17 unmodified lines

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

func TestIsTargetBodyLimitError(t *testing.T) {


Minternal/strategy/bootstrap/bootstrap\_test.go+2/-2

11 unmodified lines

12 13 14 15 16 17 15 16 17 18 19 20

11 unmodified lines

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

// Params holds the inputs for an incremental relay execution.


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

8 unmodified lines

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

8 unmodified lines

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

func TestPlansToPushPlans(t *testing.T) {


Minternal/strategy/incremental/incremental\_test.go+3/-3

11 unmodified lines

12 13 14 15 16 17 15 16 17 18 19 20

11 unmodified lines

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

// Params holds the inputs for a materialized push.


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

7 unmodified lines

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

7 unmodified lines

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

"entire.io/entire/git-sync/internal/convert" "entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" "entire.io/entire/git-sync/internal/syncertest" "entire.io/entire/gitsync/internal/convert" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" "entire.io/entire/gitsync/internal/syncertest" )

func TestPlansToPushPlans(t *testing.T) {


Minternal/strategy/materialized/materialized\_test.go+4/-4

9 unmodified lines

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

9 unmodified lines

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

// Params holds the inputs for a replication relay execution.


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

7 unmodified lines

8 9 10 11 12 11 12 13 14 15

7 unmodified lines

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

type fakeSourceService struct {


Minternal/strategy/replicate/replicate\_test.go+2/-2

13 unmodified lines

14 15 16 17 17 18 19 20

13 unmodified lines

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

"entire.io/entire/git-sync/internal/auth" "entire.io/entire/gitsync/internal/auth" )

func TestResolveAuthMethodPrefersExplicitToken(t *testing.T) {


Minternal/syncer/auth\_test.go+1/-1

1 unmodified line

2 3 4 5 5 6 7 8

1 unmodified line

import ( "context" "entire.io/entire/git-sync/internal/syncertest" "entire.io/entire/gitsync/internal/syncertest" 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"


Minternal/syncer/benchmark\_test.go+1/-1

14 unmodified lines

15 16 17 18 19 20 18 19 20 21 22 23

14 unmodified lines

"sync" "testing"

"entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" bstrap "entire.io/entire/git-sync/internal/strategy/bootstrap" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" bstrap "entire.io/entire/gitsync/internal/strategy/bootstrap" "github.com/go-git/go-git/v6/plumbing" )


Minternal/syncer/git\_http\_backend\_test.go+3/-3

13 unmodified lines

14 15 16 17 18 19 20 17 18 19 20 21 22 23

13 unmodified lines

"testing" "time"

"entire.io/entire/git-sync/internal/auth" "entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" "entire.io/entire/git-sync/internal/syncertest" "entire.io/entire/gitsync/internal/auth" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" "entire.io/entire/gitsync/internal/syncertest" billy "github.com/go-git/go-billy/v6" git "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing"


Minternal/syncer/integration\_test.go+4/-4

6 unmodified lines

7 8 9 10 10 11 12 13

6 unmodified lines

"strings" "sync"

"entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/gitsync/internal/gitproto" )

// ServiceStats tracks transfer statistics for a single service.


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

20 unmodified lines

21 22 23 24 25 26 27 28 29 30 31 32 24 25 26 27 28 29 30 31 32 33 34 35

20 unmodified lines

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

"entire.io/entire/git-sync/internal/auth" "entire.io/entire/git-sync/internal/convert" "entire.io/entire/git-sync/internal/gitproto" "entire.io/entire/git-sync/internal/planner" bstrap "entire.io/entire/git-sync/internal/strategy/bootstrap" "entire.io/entire/git-sync/internal/strategy/incremental" "entire.io/entire/git-sync/internal/strategy/materialized" repstrat "entire.io/entire/git-sync/internal/strategy/replicate" "entire.io/entire/git-sync/internal/validation" "entire.io/entire/gitsync/internal/auth" "entire.io/entire/gitsync/internal/convert" "entire.io/entire/gitsync/internal/gitproto" "entire.io/entire/gitsync/internal/planner" bstrap "entire.io/entire/gitsync/internal/strategy/bootstrap" "entire.io/entire/gitsync/internal/strategy/incremental" "entire.io/entire/gitsync/internal/strategy/materialized" repstrat "entire.io/entire/gitsync/internal/strategy/replicate" "entire.io/entire/gitsync/internal/validation" )

const (


Minternal/syncer/syncer.go+9/-9

2 unmodified lines

3 4 5 6 6 7 8 9

2 unmodified lines

import ( "testing"

bstrap "entire.io/entire/git-sync/internal/strategy/bootstrap" bstrap "entire.io/entire/gitsync/internal/strategy/bootstrap" )

func TestGitHubOwnerRepo(t *testing.T) {


Minternal/syncer/syncer\_test.go+1/-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137

package internalbridge

import ( "context" "net/http"

"entire.io/entire/gitsync/internal/syncer" "entire.io/entire/gitsync/internal/validation" )

type ProtocolMode string type OperationMode string

type Config struct { raw syncer.Config }

const ProtocolAuto ProtocolMode = validation.ProtocolAuto const ProtocolV1 ProtocolMode = validation.ProtocolV1 const ProtocolV2 ProtocolMode = validation.ProtocolV2

const ModeSync OperationMode = "sync" const ModeReplicate OperationMode = "replicate"

type RefMapping struct { Source string Target string }

type Endpoint struct { URL string FollowInfoRefsRedirect bool }

type EndpointAuth struct { Username string Token string BearerToken string SkipTLSVerify bool }

type RefScope struct { Branches []string Mappings []RefMapping }

type SyncPolicy struct { Mode OperationMode IncludeTags bool Force bool Prune bool Protocol ProtocolMode }

func ProbeConfig(source Endpoint, sourceAuth EndpointAuth, target *Endpoint, targetAuth EndpointAuth, protocol ProtocolMode, includeTags, collectStats bool, httpClient *http.Client) Config { cfg := syncer.Config{ Source: ToSyncerEndpoint(source, sourceAuth), HTTPClient: httpClient, IncludeTags: includeTags, ShowStats: collectStats, ProtocolMode: protocolString(protocol), } if target != nil { cfg.Target = ToSyncerEndpoint(*target, targetAuth) } return Config{raw: cfg} }

func SyncConfig(source Endpoint, sourceAuth EndpointAuth, target Endpoint, targetAuth EndpointAuth, scope RefScope, policy SyncPolicy, collectStats, dryRun bool, httpClient *http.Client) Config { return Config{raw: syncer.Config{ Source: ToSyncerEndpoint(source, sourceAuth), Target: ToSyncerEndpoint(target, targetAuth), HTTPClient: httpClient, Branches: append([]string(nil), scope.Branches...), Mappings: ToValidationMappings(scope.Mappings), IncludeTags: policy.IncludeTags, DryRun: dryRun, ShowStats: collectStats, Mode: operationModeString(policy.Mode), Force: policy.Force, Prune: policy.Prune, ProtocolMode: protocolString(policy.Protocol), MaterializedMaxObjects: syncer.DefaultMaterializedMaxObjects, }} }

func Probe(ctx context.Context, cfg Config) (syncer.ProbeResult, error) { result, err := syncer.Probe(ctx, cfg.raw) if err != nil { return syncer.ProbeResult{}, err //nolint:wrapcheck // pass-through layer, caller wraps with context } return result, nil }

func Run(ctx context.Context, cfg Config) (syncer.Result, error) { result, err := syncer.Run(ctx, cfg.raw) if err != nil { return syncer.Result{}, err //nolint:wrapcheck // pass-through layer, caller wraps with context } return result, nil }

func ToSyncerEndpoint(endpoint Endpoint, auth EndpointAuth) syncer.Endpoint { return syncer.Endpoint{ URL: endpoint.URL, Username: auth.Username, Token: auth.Token, BearerToken: auth.BearerToken, SkipTLSVerify: auth.SkipTLSVerify, FollowInfoRefsRedirect: endpoint.FollowInfoRefsRedirect, } }

func protocolString(mode ProtocolMode) string { if mode == "" { return string(ProtocolAuto) } return string(mode) }

func operationModeString(mode OperationMode) string { if mode == "" { return string(ModeSync) } return string(mode) }

func ToValidationMappings(mappings []RefMapping) []validation.RefMapping { out := make([]validation.RefMapping, 0, len(mappings)) for _, mapping := range mappings { out = append(out, validation.RefMapping{ Source: mapping.Source, Target: mapping.Target, }) } return out }


Ainternalbridge/config.go+137

package internalbridge

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

"entire.io/entire/gitsync/internal/planner"
    "entire.io/entire/gitsync/internal/syncer"
)

type RefKind string

const (
    RefKindBranch RefKind = RefKind(planner.RefKindBranch)
    RefKindTag    RefKind = RefKind(planner.RefKindTag)
)

type Action string

const (
    ActionCreate Action = Action(planner.ActionCreate)
    ActionUpdate Action = Action(planner.ActionUpdate)
    ActionDelete Action = Action(planner.ActionDelete)
    ActionSkip   Action = Action(planner.ActionSkip)
    ActionBlock  Action = Action(planner.ActionBlock)
)

type RefResult struct {
    Branch     string  `json:"branch"`
    SourceRef  string  `json:"sourceRef"`
    TargetRef  string  `json:"targetRef"`
    SourceHash string  `json:"sourceHash"`
    TargetHash string  `json:"targetHash"`
    Kind       RefKind `json:"kind"`
    Action     Action  `json:"action"`
    Reason     string  `json:"reason"`
}

type RefPlan = RefResult

type RefInfo struct {
    Name string `json:"name"`
    Hash string `json:"hash"`
}

type ServiceStats struct {
    Name          string `json:"name"`
    Requests      int    `json:"requests"`
    RequestBytes  int64  `json:"requestBytes"`
    ResponseBytes int64  `json:"responseBytes"`
    Wants         int    `json:"wants"`
    Haves         int    `json:"haves"`
    Commands      int    `json:"commands"`
}

type Stats struct {
    Enabled bool                     `json:"enabled"`
    Items   map[string]*ServiceStats `json:"items"`
}

type Measurement struct {
    Enabled            bool   `json:"enabled"`
    ElapsedMillis      int64  `json:"elapsedMillis"`
    PeakAllocBytes     uint64 `json:"peakAllocBytes"`
    PeakHeapInuseBytes uint64 `json:"peakHeapInuseBytes"`
    TotalAllocBytes    uint64 `json:"totalAllocBytes"`
    GCCount            uint32 `json:"gcCount"`
}

type ProbeResult struct {
    SourceURL     string      `json:"sourceUrl"`
    TargetURL     string      `json:"targetUrl,omitempty"`
    RequestedMode string      `json:"requestedMode"`
    Protocol      string      `json:"protocol"`
    RefPrefixes   []string    `json:"refPrefixes"`
    Capabilities  []string    `json:"sourceCapabilities"`
    TargetCaps    []string    `json:"targetCapabilities,omitempty"`
    Refs          []RefInfo   `json:"refs"`
    Stats         Stats       `json:"stats"`
    Measurement   Measurement `json:"measurement"`
}

type SyncCounts struct {
    Applied int `json:"applied"`
    Skipped int `json:"skipped"`
    Blocked int `json:"blocked"`
    Deleted int `json:"deleted"`
}

type BatchSummary struct {
    Enabled bool `json:"enabled"`
    Planned int  `json:"planned"`
    Done    int  `json:"done"`
}

type ExecutionSummary struct {
    DryRun             bool         `json:"dryRun"`
    Protocol           string       `json:"protocol"`
    OperationMode      string       `json:"operationMode"`
    Relay              bool         `json:"relay"`
    TransferMode       string       `json:"transferMode"`
    Reason             string       `json:"reason"`
    BootstrapSuggested bool         `json:"bootstrapSuggested"`
    Batch              BatchSummary `json:"batch"`
}

type SyncResult struct {
    Refs        []RefResult      `json:"refs"`
    Counts      SyncCounts       `json:"counts"`
    Execution   ExecutionSummary `json:"execution"`
    Stats       Stats            `json:"stats"`
    Measurement Measurement      `json:"measurement"`
}

type PlanResult = SyncResult

func FromProbeResult(result syncer.ProbeResult) ProbeResult {
    out := ProbeResult{
        SourceURL:     result.SourceURL,
        TargetURL:     result.TargetURL,
        RequestedMode: result.RequestedMode,
        Protocol:      result.Protocol,
        RefPrefixes:   append([]string(nil), result.RefPrefixes...),
        Capabilities:  append([]string(nil), result.Capabilities...),
        TargetCaps:    append([]string(nil), result.TargetCaps...),
        Refs:          make([]RefInfo, 0, len(result.Refs)),
        Stats:         FromStats(result.Stats),
        Measurement:   FromMeasurement(result.Measurement),
    }
    for _, ref := range result.Refs {
        out.Refs = append(out.Refs, RefInfo{Name: ref.Name, Hash: ref.Hash.String()})
    }
    return out
}

func FromSyncResult(result syncer.Result) SyncResult {
    out := SyncResult{
        Refs: make([]RefResult, 0, len(result.Plans)),
        Counts: SyncCounts{
            Applied: result.Pushed,
            Skipped: result.Skipped,
            Blocked: result.Blocked,
            Deleted: result.Deleted,
        },
        Execution: ExecutionSummary{
            DryRun:             result.DryRun,
            Protocol:           result.Protocol,
            OperationMode:      result.OperationMode,
            Relay:              result.Relay,
            TransferMode:       result.RelayMode,
            Reason:             result.RelayReason,
            BootstrapSuggested: result.BootstrapSuggested,
            Batch: BatchSummary{
                Enabled: result.Batching,
                Planned: result.PlannedBatchCount,
                Done:    result.BatchCount,
            },
        },
        Stats:       FromStats(result.Stats),
        Measurement: FromMeasurement(result.Measurement),
    }
    for _, plan := range result.Plans {
        out.Refs = append(out.Refs, RefResult{
            Branch:     plan.Branch,
            SourceRef:  plan.SourceRef.String(),
            TargetRef:  plan.TargetRef.String(),
            SourceHash: HashString(plan.SourceHash),
            TargetHash: HashString(plan.TargetHash),
            Kind:       RefKind(plan.Kind),
            Action:     Action(plan.Action),
            Reason:     plan.Reason,
        })
    }
    return out
}

func FromStats(stats syncer.Stats) Stats {
    out := Stats{Enabled: stats.Enabled, Items: make(map[string]*ServiceStats, len(stats.Items))}
    for key, item := range stats.Items {
        copyItem := *item
        out.Items[key] = &ServiceStats{
            Name:          copyItem.Name,
            Requests:      copyItem.Requests,
            RequestBytes:  copyItem.RequestBytes,
            ResponseBytes: copyItem.ResponseBytes,
            Wants:         copyItem.Wants,
            Haves:         copyItem.Haves,
            Commands:      copyItem.Commands,
        }
    }
    return out
}

func FromMeasurement(m syncer.Measurement) Measurement {
    return Measurement{
        Enabled:            m.Enabled,
        ElapsedMillis:      m.ElapsedMillis,
        PeakAllocBytes:     m.PeakAllocBytes,
        PeakHeapInuseBytes: m.PeakHeapInuseBytes,
        TotalAllocBytes:    m.TotalAllocBytes,
        GCCount:            m.GCCount,
    }
}

func HashString(hash plumbing.Hash) string {
    if hash.IsZero() {
        return ""
    }
    return hash.String()
}

Ainternalbridge/model.go+209

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

package internalbridge

import (
    "testing"

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

"entire.io/entire/gitsync/internal/planner"
    "entire.io/entire/gitsync/internal/syncer"
)

func TestHashStringZeroHashIsEmpty(t *testing.T) {
    got := HashString(plumbing.ZeroHash)
    if got != "" {
        t.Fatalf("HashString(zero) = %q, want empty string", got)
    }
}

func TestFromProbeResultCopiesStableFields(t *testing.T) {
    got := FromProbeResult(syncer.ProbeResult{
        SourceURL:     "https://source.example/repo.git",
        TargetURL:     "https://target.example/repo.git",
        RequestedMode: "auto",
        Protocol:      "v2",
        RefPrefixes:   []string{"refs/heads/"},
        Capabilities:  []string{"ls-refs", "fetch"},
        TargetCaps:    []string{"report-status"},
        Refs: []syncer.RefInfo{
            {Name: "refs/heads/main", Hash: plumbing.NewHash("1111111111111111111111111111111111111111")},
        },
        Stats: syncer.Stats{
            Enabled: true,
            Items: map[string]*syncer.ServiceStats{
                "source": {Name: "source", Requests: 2, Wants: 3},
            },
        },
        Measurement: syncer.Measurement{Enabled: true, ElapsedMillis: 42},
    })

if got.SourceURL != "https://source.example/repo.git" || got.TargetURL != "https://target.example/repo.git" {
        t.Fatalf("unexpected URLs: %+v", got)
    }
    if len(got.Refs) != 1 || got.Refs[0].Hash != "1111111111111111111111111111111111111111" {
        t.Fatalf("unexpected refs: %+v", got.Refs)
    }
    if !got.Stats.Enabled || got.Stats.Items["source"].Requests != 2 || got.Measurement.ElapsedMillis != 42 {
        t.Fatalf("unexpected stats/measurement: %+v %+v", got.Stats, got.Measurement)
    }
}

func TestFromSyncResultShapesStableSummary(t *testing.T) {
    got := FromSyncResult(syncer.Result{
        Plans: []planner.BranchPlan{
            {
                Branch:     "main",
                SourceRef:  plumbing.ReferenceName("refs/heads/main"),
                TargetRef:  plumbing.ReferenceName("refs/heads/main"),
                SourceHash: plumbing.NewHash("1111111111111111111111111111111111111111"),
                TargetHash: plumbing.NewHash("2222222222222222222222222222222222222222"),
                Kind:       planner.RefKindBranch,
                Action:     planner.ActionUpdate,
                Reason:     "fast-forward",
            },
        },
        Pushed:             1,
        Skipped:            2,
        Blocked:            3,
        Deleted:            4,
        DryRun:             true,
        OperationMode:      "replicate",
        Relay:              true,
        RelayMode:          "incremental-relay",
        RelayReason:        "fast-forward",
        Batching:           true,
        BatchCount:         5,
        PlannedBatchCount:  6,
        TempRefs:           []string{"refs/gitsync/bootstrap/heads/main/1"},
        BootstrapSuggested: true,
        Protocol:           "v2",
    })

if len(got.Refs) != 1 || got.Refs[0].Branch != "main" {
        t.Fatalf("unexpected refs: %+v", got.Refs)
    }
    if got.Counts.Applied != 1 || got.Counts.Skipped != 2 || got.Counts.Blocked != 3 || got.Counts.Deleted != 4 {
        t.Fatalf("unexpected counts: %+v", got.Counts)
    }
    if !got.Execution.DryRun || !got.Execution.Relay || got.Execution.OperationMode != "replicate" || got.Execution.TransferMode != "incremental-relay" || got.Execution.Reason != "fast-forward" {
        t.Fatalf("unexpected execution summary: %+v", got.Execution)
    }
    if !got.Execution.Batch.Enabled || got.Execution.Batch.Done != 5 || got.Execution.Batch.Planned != 6 {
        t.Fatalf("unexpected batch summary: %+v", got.Execution.Batch)
    }
    if !got.Execution.BootstrapSuggested {
        t.Fatalf("expected bootstrap suggestion in execution summary")
    }
}

Ainternalbridge/model_test.go+97

45 unmodified lines

46
47
48
49
49
50
51
52

45 unmodified lines

VIOLATIONS=()
while IFS=',' read -r package _url license; do
    # Skip internal packages
    if [[ "$package" == entire.io/entire/git-sync/* ]]; then
    if [[ "$package" == entire.io/entire/gitsync/* ]]; then
        continue
    fi

Mmise-tasks/lint/licenses+1/-1

package gitsync

import ( "context" "errors" "fmt" "net/http"

"entire.io/entire/git-sync/internal/validation" "entire.io/entire/git-sync/pkg/gitsync/internalbridge" )

// Options configures a Client. It is intentionally small in the first public cut. type Options struct { HTTPClient *http.Client Auth AuthProvider }

// Client provides the public orchestration API for git-sync. type Client struct { httpClient *http.Client auth AuthProvider }

// New constructs a new Client. func New(opts Options) *Client { return &Client{httpClient: opts.HTTPClient, auth: opts.Auth} }

func bridgeEndpoint(ep Endpoint) internalbridge.Endpoint { return internalbridge.Endpoint{ URL: ep.URL, FollowInfoRefsRedirect: ep.FollowInfoRefsRedirect, } }

func ptr[T any](v T) *T { return &v }

Dpkg/gitsync/client.go-257

package gitsync

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

"entire.io/entire/git-sync/internal/syncertest" )

type errAuthProvider struct{}

func (errAuthProvider) AuthFor(_ context.Context, _ Endpoint, _ EndpointRole) (EndpointAuth, error) { return EndpointAuth{}, errors.New("boom") }

type smartHTTPRepoServer struct { tb testing.TB repo *git.Repository repoPath string server *httptest.Server }

func newSmartHTTPRepoServer(tb testing.TB, repo *git.Repository) *smartHTTPRepoServer { tb.Helper()

s := &smartHTTPRepoServer{ tb: tb, repo: repo, repoPath: "/repo.git", } s.server = httptest.NewServer(http.HandlerFunc(s.handle)) return s }

func (s *smartHTTPRepoServer) Close() { s.server.Close() }

func (s *smartHTTPRepoServer) RepoURL() string { return s.server.URL + s.repoPath }

type nopWriteCloser struct{ io.Writer }

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


Dpkg/gitsync/client\_test.go-275

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

package gitsync_test

import ( "context" "net/http"

"entire.io/entire/git-sync/pkg/gitsync" )

// Output: }


Dpkg/gitsync/example\_test.go-32

package internalbridge

import (
    "context"
    "net/http"

"entire.io/entire/git-sync/internal/syncer"
    "entire.io/entire/git-sync/internal/validation"
)

type ProtocolMode string
type OperationMode string

type Config struct {
    raw syncer.Config
}

const ModeSync OperationMode = "sync"
const ModeReplicate OperationMode = "replicate"

type RefMapping struct {
    Source string
    Target string
}

type Endpoint struct {
    URL                    string
    FollowInfoRefsRedirect bool
}

type EndpointAuth struct {
    Username      string
    Token         string
    BearerToken   string
    SkipTLSVerify bool
}

type RefScope struct {
    Branches []string
    Mappings []RefMapping
}

type SyncPolicy struct {
    Mode        OperationMode
    IncludeTags bool
    Force       bool
    Prune       bool
    Protocol    ProtocolMode
}

func protocolString(mode ProtocolMode) string {
    if mode == "" {
        return string(ProtocolAuto)
    }
    return string(mode)
}

func operationModeString(mode OperationMode) string {
    if mode == "" {
        return string(ModeSync)
    }
    return string(mode)
}

Dpkg/gitsync/internalbridge/config.go-137

package internalbridge

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

"entire.io/entire/git-sync/internal/planner"
    "entire.io/entire/git-sync/internal/syncer"
)

type RefKind string

const (
    RefKindBranch RefKind = RefKind(planner.RefKindBranch)
    RefKindTag    RefKind = RefKind(planner.RefKindTag)
)

type Action string

type RefPlan = RefResult

type RefInfo struct {
    Name string `json:"name"`
    Hash string `json:"hash"`
}

type Stats struct {
    Enabled bool                     `json:"enabled"`
    Items   map[string]*ServiceStats `json:"items"`
}

type SyncCounts struct {
    Applied int `json:"applied"`
    Skipped int `json:"skipped"`
    Blocked int `json:"blocked"`
    Deleted int `json:"deleted"`
}

type BatchSummary struct {
    Enabled bool `json:"enabled"`
    Planned int  `json:"planned"`
    Done    int  `json:"done"`
}

type PlanResult = SyncResult

func HashString(hash plumbing.Hash) string {
    if hash.IsZero() {
        return ""
    }
    return hash.String()
}

Dpkg/gitsync/internalbridge/model.go-209

package internalbridge

import ( "testing"

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

"entire.io/entire/git-sync/internal/planner" "entire.io/entire/git-sync/internal/syncer" )

Dpkg/gitsync/internalbridge/model_test.go-97

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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

package gitsync

import (
    "context"
    "entire.io/entire/git-sync/pkg/gitsync/internalbridge"
)

// ProtocolMode controls source-side protocol negotiation.
type ProtocolMode string

const (
    ProtocolAuto ProtocolMode = "auto"
    ProtocolV1   ProtocolMode = "v1"
    ProtocolV2   ProtocolMode = "v2"
)

// OperationMode controls high-level sync semantics.
type OperationMode string

const (
    ModeSync      OperationMode = "sync"
    ModeReplicate OperationMode = "replicate"
)

// Endpoint identifies a remote Git endpoint.
type Endpoint struct {
    URL string `json:"url"`

// FollowInfoRefsRedirect, when true, rewrites this endpoint's
    // effective host to the final URL returned by /info/refs after
    // HTTP redirects. Subsequent git RPCs (git-upload-pack,
    // git-receive-pack) then target the redirected host directly.
    // Matches vanilla git's smart-HTTP behaviour for discovery-aware
    // servers that 307 /info/refs to a hosting replica.
    FollowInfoRefsRedirect bool `json:"followInfoRefsRedirect,omitempty"`
}

// EndpointAuth carries explicit per-request auth and TLS settings.
// It is resolved through an AuthProvider rather than embedded in Endpoint so
// endpoint identity does not also become the public auth-precedence boundary.
type EndpointAuth struct {
    Username      string `json:"username"`
    Token         string `json:"token"`
    BearerToken   string `json:"bearerToken"`
    SkipTLSVerify bool   `json:"skipTlsVerify"`
}

// EndpointRole identifies whether auth is being resolved for the source or target.
type EndpointRole string

const (
    SourceRole EndpointRole = "source"
    TargetRole EndpointRole = "target"
)

// AuthProvider resolves auth for a request endpoint.
type AuthProvider interface {
    AuthFor(ctx context.Context, endpoint Endpoint, role EndpointRole) (EndpointAuth, error)
}

// StaticAuthProvider returns fixed source and target auth values.
type StaticAuthProvider struct {
    Source EndpointAuth `json:"source"`
    Target EndpointAuth `json:"target"`
}

// AuthFor implements AuthProvider.
func (p StaticAuthProvider) AuthFor(_ context.Context, _ Endpoint, role EndpointRole) (EndpointAuth, error) { //nolint:unparam // implements AuthProvider interface
    if role == TargetRole {
        return p.Target, nil
    }
    return p.Source, nil
}

// RefMapping is an explicit source-to-target ref mapping.
type RefMapping struct {
    Source string `json:"source"`
    Target string `json:"target"`
}

// RefScope constrains which refs a request manages.
type RefScope struct {
    Branches []string     `json:"branches"`
    Mappings []RefMapping `json:"mappings"`
}

// SyncPolicy controls high-level sync behavior.
type SyncPolicy struct {
    Mode        OperationMode `json:"mode"`
    IncludeTags bool          `json:"includeTags"`
    Force       bool          `json:"force"`
    Prune       bool          `json:"prune"`
    Protocol    ProtocolMode  `json:"protocol"`
}

// ProbeRequest inspects source refs and optional target capabilities.
type ProbeRequest struct {
    Source       Endpoint     `json:"source"`
    Target       *Endpoint    `json:"target"`
    IncludeTags  bool         `json:"includeTags"`
    Protocol     ProtocolMode `json:"protocol"`
    CollectStats bool         `json:"collectStats"`
}

// PlanRequest computes ref actions without pushing.
type PlanRequest struct {
    Source       Endpoint   `json:"source"`
    Target       Endpoint   `json:"target"`
    Scope        RefScope   `json:"scope"`
    Policy       SyncPolicy `json:"policy"`
    CollectStats bool       `json:"collectStats"`
}

// SyncRequest executes a sync between two remotes.
type SyncRequest struct {
    Source       Endpoint   `json:"source"`
    Target       Endpoint   `json:"target"`
    Scope        RefScope   `json:"scope"`
    Policy       SyncPolicy `json:"policy"`
    CollectStats bool       `json:"collectStats"`
}

type RefKind = internalbridge.RefKind

const (
    RefKindBranch RefKind = internalbridge.RefKindBranch
    RefKindTag    RefKind = internalbridge.RefKindTag
)

type Action = internalbridge.Action

const (
    ActionCreate Action = internalbridge.ActionCreate
    ActionUpdate Action = internalbridge.ActionUpdate
    ActionDelete Action = internalbridge.ActionDelete
    ActionSkip   Action = internalbridge.ActionSkip
    ActionBlock  Action = internalbridge.ActionBlock
)

type RefResult = internalbridge.RefResult
type RefPlan = internalbridge.RefPlan
type RefInfo = internalbridge.RefInfo
type ServiceStats = internalbridge.ServiceStats
type Stats = internalbridge.Stats
type Measurement = internalbridge.Measurement
type ProbeResult = internalbridge.ProbeResult
type SyncCounts = internalbridge.SyncCounts
type BatchSummary = internalbridge.BatchSummary
type ExecutionSummary = internalbridge.ExecutionSummary
type SyncResult = internalbridge.SyncResult
type PlanResult = internalbridge.PlanResult

Dpkg/gitsync/types.go-151

package unstable

import ( "context" "fmt" "net/http"

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

"entire.io/entire/git-sync/internal/syncer" "entire.io/entire/git-sync/internal/validation" "entire.io/entire/git-sync/pkg/gitsync" "entire.io/entire/git-sync/pkg/gitsync/internalbridge" )

const DefaultMaterializedMaxObjects = syncer.DefaultMaterializedMaxObjects

type ( Result = syncer.Result ProbeResult = syncer.ProbeResult FetchResult = syncer.FetchResult RefInfo = syncer.RefInfo Stats = syncer.Stats Measurement = syncer.Measurement )

type Options struct { HTTPClient *http.Client Auth gitsync.AuthProvider }

type Client struct { httpClient *http.Client auth gitsync.AuthProvider }

type AdvancedOptions struct { CollectStats bool json:"collectStats" MeasureMemory bool json:"measureMemory" Verbose bool json:"verbose" MaxPackBytes int64 json:"maxPackBytes" TargetMaxPackBytes int64 json:"targetMaxPackBytes" MaterializedMaxObjects int json:"materializedMaxObjects" }

type ProbeRequest struct { Source gitsync.Endpoint Target *gitsync.Endpoint IncludeTags bool Protocol gitsync.ProtocolMode Options AdvancedOptions }

type SyncRequest struct { Source gitsync.Endpoint Target gitsync.Endpoint Scope gitsync.RefScope Policy gitsync.SyncPolicy DryRun bool Options AdvancedOptions }

type BootstrapRequest struct { Source gitsync.Endpoint Target gitsync.Endpoint Scope gitsync.RefScope IncludeTags bool Protocol gitsync.ProtocolMode Options AdvancedOptions }

type FetchRequest struct { Source gitsync.Endpoint Scope gitsync.RefScope IncludeTags bool Protocol gitsync.ProtocolMode HaveRefs []string HaveHashes []plumbing.Hash Options AdvancedOptions }

func New(opts Options) *Client { return &Client{httpClient: opts.HTTPClient, auth: opts.Auth} }

func (c *Client) Probe(ctx context.Context, req ProbeRequest) (ProbeResult, error) { cfg, err := c.buildProbeConfig(ctx, req) if err != nil { return ProbeResult{}, err } result, err := syncer.Probe(ctx, cfg) if err != nil { return ProbeResult{}, fmt.Errorf("probe: %w", err) } return result, nil }

func (c *Client) Plan(ctx context.Context, req SyncRequest) (Result, error) { planReq := req planReq.DryRun = true cfg, err := c.buildSyncConfig(ctx, planReq) if err != nil { return Result{}, err } result, err := syncer.Run(ctx, cfg) if err != nil { return Result{}, fmt.Errorf("plan: %w", err) } return result, nil }

func (c *Client) Sync(ctx context.Context, req SyncRequest) (Result, error) { cfg, err := c.buildSyncConfig(ctx, req) if err != nil { return Result{}, err } result, err := syncer.Run(ctx, cfg) if err != nil { return Result{}, fmt.Errorf("sync: %w", err) } return result, nil }

func (c *Client) Replicate(ctx context.Context, req SyncRequest) (Result, error) { req.Policy.Mode = gitsync.ModeReplicate cfg, err := c.buildSyncConfig(ctx, req) if err != nil { return Result{}, err } result, err := syncer.Run(ctx, cfg) if err != nil { return Result{}, fmt.Errorf("replicate: %w", err) } return result, nil }

func (c *Client) Bootstrap(ctx context.Context, req BootstrapRequest) (Result, error) { cfg, err := c.buildBootstrapConfig(ctx, req) if err != nil { return Result{}, err } result, err := syncer.Bootstrap(ctx, cfg) if err != nil { return Result{}, fmt.Errorf("bootstrap: %w", err) } return result, nil }

func (c *Client) Fetch(ctx context.Context, req FetchRequest) (FetchResult, error) { cfg, err := c.buildFetchConfig(ctx, req) if err != nil { return FetchResult{}, err } result, err := syncer.Fetch(ctx, cfg, append([]string(nil), req.HaveRefs...), append([]plumbing.Hash(nil), req.HaveHashes...)) if err != nil { return FetchResult{}, fmt.Errorf("fetch: %w", err) } return result, nil }

func (c *Client) buildProbeConfig(ctx context.Context, req ProbeRequest) (syncer.Config, error) { source, err := c.resolveEndpoint(ctx, req.Source, gitsync.SourceRole) if err != nil { return syncer.Config{}, err } cfg := syncer.Config{ Source: source, HTTPClient: c.httpClient, IncludeTags: req.IncludeTags, ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, ProtocolMode: protocolString(req.Protocol), Verbose: req.Options.Verbose, } if req.Target != nil { target, err := c.resolveEndpoint(ctx, *req.Target, gitsync.TargetRole) if err != nil { return syncer.Config{}, err } cfg.Target = target } return cfg, nil }

func (c *Client) buildSyncConfig(ctx context.Context, req SyncRequest) (syncer.Config, error) { source, err := c.resolveEndpoint(ctx, req.Source, gitsync.SourceRole) if err != nil { return syncer.Config{}, err } target, err := c.resolveEndpoint(ctx, req.Target, gitsync.TargetRole) if err != nil { return syncer.Config{}, err } maxObjects := req.Options.MaterializedMaxObjects if maxObjects == 0 { maxObjects = DefaultMaterializedMaxObjects } return syncer.Config{ Source: source, Target: target, HTTPClient: c.httpClient, Branches: append([]string(nil), req.Scope.Branches...), Mappings: validationMappings(req.Scope.Mappings), IncludeTags: req.Policy.IncludeTags, DryRun: req.DryRun, ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, Mode: operationModeString(req.Policy.Mode), Force: req.Policy.Force, Prune: req.Policy.Prune, MaxPackBytes: req.Options.MaxPackBytes, TargetMaxPackBytes: req.Options.TargetMaxPackBytes, MaterializedMaxObjects: maxObjects, ProtocolMode: protocolString(req.Policy.Protocol), Verbose: req.Options.Verbose, }, nil }

func (c *Client) buildBootstrapConfig(ctx context.Context, req BootstrapRequest) (syncer.Config, error) { source, err := c.resolveEndpoint(ctx, req.Source, gitsync.SourceRole) if err != nil { return syncer.Config{}, err } target, err := c.resolveEndpoint(ctx, req.Target, gitsync.TargetRole) if err != nil { return syncer.Config{}, err } return syncer.Config{ Source: source, Target: target, HTTPClient: c.httpClient, Branches: append([]string(nil), req.Scope.Branches...), Mappings: validationMappings(req.Scope.Mappings), IncludeTags: req.IncludeTags, ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, MaxPackBytes: req.Options.MaxPackBytes, TargetMaxPackBytes: req.Options.TargetMaxPackBytes, ProtocolMode: protocolString(req.Protocol), Verbose: req.Options.Verbose, }, nil }

func (c *Client) buildFetchConfig(ctx context.Context, req FetchRequest) (syncer.Config, error) { source, err := c.resolveEndpoint(ctx, req.Source, gitsync.SourceRole) if err != nil { return syncer.Config{}, err } return syncer.Config{ Source: source, HTTPClient: c.httpClient, Branches: append([]string(nil), req.Scope.Branches...), IncludeTags: req.IncludeTags, ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, ProtocolMode: protocolString(req.Protocol), Verbose: req.Options.Verbose, }, nil }

func (c *Client) authFor(ctx context.Context, endpoint gitsync.Endpoint, role gitsync.EndpointRole) (gitsync.EndpointAuth, error) { if c.auth == nil { return gitsync.EndpointAuth{}, nil } auth, err := c.auth.AuthFor(ctx, endpoint, role) if err != nil { return gitsync.EndpointAuth{}, fmt.Errorf("resolve auth for %s: %w", role, err) } return auth, nil }

func (c *Client) resolveEndpoint(ctx context.Context, endpoint gitsync.Endpoint, role gitsync.EndpointRole) (syncer.Endpoint, error) { auth, err := c.authFor(ctx, endpoint, role) if err != nil { return syncer.Endpoint{}, err } return syncerEndpoint(endpoint, auth), nil }

func protocolString(mode gitsync.ProtocolMode) string { if mode == "" { return string(gitsync.ProtocolAuto) } return string(mode) }

func operationModeString(mode gitsync.OperationMode) string { if mode == "" { return string(gitsync.ModeSync) } return string(mode) }

func syncerEndpoint(endpoint gitsync.Endpoint, auth gitsync.EndpointAuth) syncer.Endpoint { return internalbridge.ToSyncerEndpoint( internalbridge.Endpoint{ URL: endpoint.URL, FollowInfoRefsRedirect: endpoint.FollowInfoRefsRedirect, }, internalbridge.EndpointAuth{ Username: auth.Username, Token: auth.Token, BearerToken: auth.BearerToken, SkipTLSVerify: auth.SkipTLSVerify, }, ) }

func validationMappings(mappings []gitsync.RefMapping) []validation.RefMapping { bridgeMappings := make([]internalbridge.RefMapping, 0, len(mappings)) for _, mapping := range mappings { bridgeMappings = append(bridgeMappings, internalbridge.RefMapping{ Source: mapping.Source, Target: mapping.Target, }) } return internalbridge.ToValidationMappings(bridgeMappings) }


Dpkg/gitsync/unstable/client.go-318

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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

package unstable

import ( "context" "net/http" "testing"

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

"entire.io/entire/git-sync/pkg/gitsync" )

func TestBuildSyncConfigCarriesAdvancedOptions(t *testing.T) { cfg, err := New(Options{ HTTPClient: &http.Client{}, Auth: gitsync.StaticAuthProvider{ Source: gitsync.EndpointAuth{Token: "src"}, Target: gitsync.EndpointAuth{Token: "dst"}, }, }).buildSyncConfig(context.Background(), SyncRequest{ Source: gitsync.Endpoint{URL: "https://source.example/repo.git", FollowInfoRefsRedirect: true}, Target: gitsync.Endpoint{URL: "https://target.example/repo.git", FollowInfoRefsRedirect: true}, Scope: gitsync.RefScope{Branches: []string{"main"}}, Policy: gitsync.SyncPolicy{IncludeTags: true, Force: true, Prune: true}, DryRun: true, Options: AdvancedOptions{ CollectStats: true, MeasureMemory: true, Verbose: true, MaterializedMaxObjects: 123, }, }) if err != nil { t.Fatalf("buildSyncConfig: %v", err) } if !cfg.DryRun || !cfg.ShowStats || !cfg.MeasureMemory || !cfg.Verbose { t.Fatalf("advanced booleans not propagated: %+v", cfg) } if cfg.MaterializedMaxObjects != 123 { t.Fatalf("materialized max objects = %d, want 123", cfg.MaterializedMaxObjects) } if cfg.Source.Token != "src" || cfg.Target.Token != "dst" { t.Fatalf("auth not propagated: %+v %+v", cfg.Source, cfg.Target) } if !cfg.Source.FollowInfoRefsRedirect || !cfg.Target.FollowInfoRefsRedirect { t.Fatalf("follow-info-refs redirect flags not propagated: %+v %+v", cfg.Source, cfg.Target) } }

func TestBuildFetchConfigCopiesHaveHashesAtCallSite(t *testing.T) { req := FetchRequest{ Source: gitsync.Endpoint{URL: "https://source.example/repo.git"}, HaveHashes: []plumbing.Hash{plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")}, } cfg, err := New(Options{}).buildFetchConfig(context.Background(), req) if err != nil { t.Fatalf("buildFetchConfig: %v", err) } if cfg.Source.URL == "" { t.Fatalf("source URL not set") } }


Dpkg/gitsync/unstable/client\_test.go-62

1 2 3 4 5 6 7 8 9

// Package unstable exposes advanced git-sync controls and commands that are // intentionally outside the stable pkg/gitsync surface. // // This package exists for first-party consumers such as the CLI and benchmark // tool that still need direct access to engine-adjacent controls like batch // sizing, heap measurement, verbose progress, and fetch/bootstrap entrypoints. // // The API in this package is explicitly not stable. package unstable


Dpkg/gitsync/unstable/doc.go-9

package gitsync

import (
    "context"
    "entire.io/entire/gitsync/internalbridge"
)

// ProtocolMode controls source-side protocol negotiation.
type ProtocolMode string

const (
    ProtocolAuto ProtocolMode = "auto"
    ProtocolV1   ProtocolMode = "v1"
    ProtocolV2   ProtocolMode = "v2"
)

// OperationMode controls high-level sync semantics.
type OperationMode string

const (
    ModeSync      OperationMode = "sync"
    ModeReplicate OperationMode = "replicate"
)

// Endpoint identifies a remote Git endpoint.
type Endpoint struct {
    URL string `json:"url"`

// EndpointRole identifies whether auth is being resolved for the source or target.
type EndpointRole string

const (
    SourceRole EndpointRole = "source"
    TargetRole EndpointRole = "target"
)

// RefMapping is an explicit source-to-target ref mapping.
type RefMapping struct {
    Source string `json:"source"`
    Target string `json:"target"`
}

// RefScope constrains which refs a request manages.
type RefScope struct {
    Branches []string     `json:"branches"`
    Mappings []RefMapping `json:"mappings"`
}

type RefKind = internalbridge.RefKind

const (
    RefKindBranch RefKind = internalbridge.RefKindBranch
    RefKindTag    RefKind = internalbridge.RefKindTag
)

type Action = internalbridge.Action

Atypes.go+151

package unstable

import (
    "context"
    "fmt"
    "net/http"

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

"entire.io/entire/gitsync"
    "entire.io/entire/gitsync/internal/syncer"
    "entire.io/entire/gitsync/internal/validation"
    "entire.io/entire/gitsync/internalbridge"
)

const DefaultMaterializedMaxObjects = syncer.DefaultMaterializedMaxObjects

type Options struct {
    HTTPClient *http.Client
    Auth       gitsync.AuthProvider
}

type Client struct {
    httpClient *http.Client
    auth       gitsync.AuthProvider
}

type ProbeRequest struct {
    Source      gitsync.Endpoint
    Target      *gitsync.Endpoint
    IncludeTags bool
    Protocol    gitsync.ProtocolMode
    Options     AdvancedOptions
}

func New(opts Options) *Client {
    return &Client{httpClient: opts.HTTPClient, auth: opts.Auth}
}

func protocolString(mode gitsync.ProtocolMode) string {
    if mode == "" {
        return string(gitsync.ProtocolAuto)
    }
    return string(mode)
}

func operationModeString(mode gitsync.OperationMode) string {
    if mode == "" {
        return string(gitsync.ModeSync)
    }
    return string(mode)
}

Aunstable/client.go+318

package unstable

import (
    "context"
    "net/http"
    "testing"

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

"entire.io/entire/gitsync"
)

Aunstable/client\_test.go+62

1 2 3 4 5 6 7 8 9

// Package unstable exposes advanced git-sync controls and commands that are // intentionally outside the stable gitsync surface. // // This package exists for first-party consumers such as the CLI and benchmark // tool that still need direct access to engine-adjacent controls like batch // sizing, heap measurement, verbose progress, and fetch/bootstrap entrypoints. // // The API in this package is explicitly not stable. package unstable


Aunstable/doc.go+9