[Home](/content/site-root.html)

Log in

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

`d06e573`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

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

move package to root level

## Changes

51

- M.golangci.yaml+1/-1

- MREADME.md+4/-4

- Aclient.go+257

- Aclient\_test.go+275

- cmd

- git-sync-bench

- Mmain.go+3/-3

- Mmain\_test.go+1/-1

- git-sync

- Mmain.go+3/-3

- Mmain\_test.go+1/-1

- Rdoc.go

- docs

- Marchitecture.md+6/-6

- Membedding.md+8/-8

- Aexample\_test.go+32

- Mgo.mod+1/-1

- internal

- convert

- Mconvert.go+2/-2

- Mconvert\_test.go+2/-2

- planner

- Mplanner.go+1/-1

- Mplanner\_test.go+1/-1

- Mtypes.go+1/-1

- strategy

- bootstrap

- Mbootstrap.go+3/-3

- Mbootstrap\_test.go+2/-2

- incremental

- Mincremental.go+3/-3

- Mincremental\_test.go+3/-3

- materialized

- Mmaterialized.go+3/-3

- Mmaterialized\_test.go+4/-4

- replicate

- Mreplicate.go+3/-3

- Mreplicate\_test.go+2/-2

- syncer

- Mauth\_test.go+1/-1

- Mbenchmark\_test.go+1/-1

- Mgit\_http\_backend\_test.go+3/-3

- Mintegration\_test.go+4/-4

- Mstats.go+1/-1

- Msyncer.go+9/-9

- Msyncer\_test.go+1/-1

- internalbridge

- Aconfig.go+137

- Amodel.go+209

- Amodel\_test.go+97

- mise-tasks/lint

- Mlicenses+1/-1

- pkg/gitsync

- Dclient.go-257

- Dclient\_test.go-275

- Dexample\_test.go-32

- internalbridge

- Dconfig.go-137

- Dmodel.go-209

- Dmodel\_test.go-97

- Dtypes.go-151

- unstable

- Dclient.go-318

- Dclient\_test.go-62

- Ddoc.go-9

- Atypes.go+151

- unstable

- Aclient.go+318

- Aclient\_test.go+62

- Adoc.go+9

```
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

- `pkg/gitsync`
- `gitsync`
  - stable public embedding API
  - typed `Probe`, `Plan`, `Sync`, and `Replicate` requests/results
  - auth and HTTP client injection for worker-style callers
- `pkg/gitsync/unstable`
- `unstable`
  - explicitly non-stable first-party tooling surface
  - advanced controls, `Bootstrap`, `Fetch`, and CLI-oriented knobs
- `internal/gitproto`
19 unmodified lines

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

- `pkg/gitsync` is the stable library boundary.
- `gitsync` is the stable library boundary.
  Callers express orchestration intent through typed probe, plan, sync, and replicate requests. Auth and transport are injected. Execution strategy remains internal.
- `pkg/gitsync/unstable` is the escape hatch for advanced controls.
- `unstable` is the escape hatch for advanced controls.
  It exists so the CLI and benchmark tool can use batching limits, memory measurement, verbose progress, bootstrap, and fetch without widening the stable API prematurely.

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

That split is intentional:

- external embedders should depend on `pkg/gitsync`
- first-party tools inside this repo may use `pkg/gitsync/unstable`
- external embedders should depend on `gitsync`
- first-party tools inside this repo may use `unstable`
- strategy selection, batching heuristics, and materialized fallback controls are not yet treated as stable product contracts

## 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:

- use `pkg/gitsync`
- avoid depending on `pkg/gitsync/unstable` unless you are acting like first-party tooling
- use `gitsync`
- avoid depending on `unstable` unless you are acting like first-party tooling

## Stable vs Unstable

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

- `Probe`
- `Plan`
2 unmodified lines

- typed requests and results
- injected auth and HTTP client support

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:

- `Bootstrap`
- `Fetch`
2 unmodified lines

- verbose execution controls
- other engine-adjacent tuning

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:

- requests carry endpoint identity
- `AuthProvider` resolves source and target auth
71 unmodified lines

- temp refs
- exact relay strategy names beyond coarse execution summary

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
