Merge pull request #99 from entireio/simplify/de-slop · Entire
Log in
Merge pull request #99 from entireio/simplify/de-slop
c796488→main·
Soph·1w ago·38 files·+821 added/-1,649 removed
Simplify: remove pass-through layers, dead code, and copy-pasted helpers
Changes
38
Mclient.go+55/-89
cmd
git-sync-bench
Mmain.go+1/-9
git-sync
Mbootstrap.go+4/-10
Mconvert_sha256.go+1/-7
Mflags.go+25/-10
internal
sha256convert
Msha256convert.go+8/-33
versioninfo
Mversioninfo.go+8
Mroot.go+1/-2
Msyncplan.go+6/-15
Mversion.go+1/-2
internal
auth
Mauth.go+6/-7
convert
Mconvert.go-14
gitproto
Dconvert.go-60
Dconvert_test.go-211
Mfetch.go+5/-63
Mfetch_test.go+10/-34
Mpktline.go-6
Mpktline_test.go-27
Mpush.go+22/-28
Areaders.go+70
Areaders_test.go+183
internalbridge
Dconfig.go-149
Dmodel.go-240
Dmodel_test.go-97
planner
Mcheckpoint.go-28
Mtypes.go-37
strategy
bootstrap
Mbootstrap.go+7/-59
incremental
Mincremental.go+2/-30
Mincremental_test.go-98
materialized
Mmaterialized_test.go-99
replicate
Mreplicate.go+1/-31
syncer
Mprogress.go+5/-25
Mprogress_test.go-22
Msyncer.go+30/-38
Aresults.go+257
Aresults_test.go+97
Mtypes.go-34
unstable
Mclient.go+16/-35
5 unmodified lines
6
7
8
9
9
10
11
12
23 unmodified lines
36
37
38
39
39
40
41
42
43
43
44
45
46
1 unmodified line
48
49
50
51
51
52
53
54
55
55
56
57
58
59
59
60
61
62
1 unmodified line
64
65
66
67
67
68
69
70
71
71
72
73
74
75
75
76
77
78
2 unmodified lines
81
82
83
84
84
85
86
87
87
88
89
90
89
90
91
92
93
94
95
96
97
98
92
99
100
94
101
102
96
97
103
104
99
100
101
102
103
104
105
106
107
108
109
110
105
106
107
113
114
108
109
110
116
111
112
118
113
114
120
115
116
122
123
124
125
126
127
128
129
130
131
132
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
52 unmodified lines
192
193
194
191
192
195
196
197
198
199
200
201
202
203
204
205
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
206
207
208
3 unmodified lines
212
213
214
245
246
247
248
215
216
217
5 unmodified lines
"fmt"
"net/http"
"entire.io/entire/git-sync/internal/internalbridge"
"entire.io/entire/git-sync/internal/syncer"
"entire.io/entire/git-sync/internal/validation"
)
23 unmodified lines
if err != nil {
return ProbeResult{}, err
}
result, err := internalbridge.Probe(ctx, cfg)
result, err := syncer.Probe(ctx, cfg)
if err != nil {
return ProbeResult{}, fmt.Errorf("probe: %w", err)
}
return internalbridge.FromProbeResult(result), nil
return fromProbeResult(result), nil
}
// Plan computes ref actions without pushing.
1 unmodified line
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)
cfg, err := c.buildSyncConfig(ctx, SyncRequest(req), true)
if err != nil {
return PlanResult{}, err
}
result, err := internalbridge.Run(ctx, cfg)
result, err := syncer.Run(ctx, cfg)
if err != nil {
return PlanResult{}, fmt.Errorf("plan: %w", err)
}
return internalbridge.FromSyncResult(result), nil
return fromSyncResult(result), nil
}
// Sync executes a sync between two remotes.
1 unmodified line
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)
cfg, err := c.buildSyncConfig(ctx, req, false)
if err != nil {
return SyncResult{}, err
}
result, err := internalbridge.Run(ctx, cfg)
result, err := syncer.Run(ctx, cfg)
if err != nil {
return SyncResult{}, fmt.Errorf("sync: %w", err)
}
return internalbridge.FromSyncResult(result), nil
return fromSyncResult(result), nil
}
// Replicate executes source-authoritative relay-only replication between two remotes.
2 unmodified lines
return c.Sync(ctx, req)
}
func (c *Client) buildProbeConfig(ctx context.Context, req ProbeRequest) (internalbridge.Config, error) {
func (c *Client) buildProbeConfig(ctx context.Context, req ProbeRequest) (syncer.Config, error) {
sourceAuth, err := c.authFor(ctx, req.Source, SourceRole)
if err != nil {
return internalbridge.Config{}, err
return syncer.Config{}, err
}
var target *internalbridge.Endpoint
targetAuth := internalbridge.EndpointAuth{}
cfg := syncer.Config{
Source: syncerEndpoint(req.Source, sourceAuth),
HTTPClient: c.httpClient,
IncludeTags: req.IncludeTags,
AllRefs: req.AllRefs,
ExcludeRefPrefixes: append([]string(nil), req.ExcludeRefPrefixes...),
ShowStats: req.CollectStats,
ProtocolMode: string(req.Protocol),
}
if req.Target != nil {
resolvedTargetAuth, err := c.authFor(ctx, *req.Target, TargetRole)
targetAuth, err := c.authFor(ctx, *req.Target, TargetRole)
if err != nil {
return internalbridge.Config{}, err
return syncer.Config{}, err
}
target = ptr(bridgeEndpoint(*req.Target))
targetAuth = bridgeEndpointAuth(resolvedTargetAuth)
cfg.Target = syncerEndpoint(*req.Target, targetAuth)
}
return internalbridge.ProbeConfig(
bridgeEndpoint(req.Source),
bridgeEndpointAuth(sourceAuth),
target,
targetAuth,
internalbridge.ProtocolMode(req.Protocol),
req.IncludeTags,
req.AllRefs,
req.CollectStats,
req.ExcludeRefPrefixes,
c.httpClient,
), nil
return cfg, 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)
func (c *Client) buildSyncConfig(ctx context.Context, req SyncRequest, dryRun bool) (syncer.Config, error) {
sourceAuth, err := c.authFor(ctx, req.Source, SourceRole)
if err != nil {
return internalbridge.Config{}, err
return syncer.Config{}, err
}
targetAuth, err := c.authFor(ctx, target, TargetRole)
targetAuth, err := c.authFor(ctx, req.Target, TargetRole)
if err != nil {
return internalbridge.Config{}, err
return syncer.Config{}, err
}
return internalbridge.SyncConfig(
bridgeEndpoint(source),
bridgeEndpointAuth(sourceAuth),
bridgeEndpoint(target),
bridgeEndpointAuth(targetAuth),
bridgeScope(scope),
bridgePolicy(policy),
collectStats,
dryRun,
c.httpClient,
), nil
return syncer.Config{
Source: syncerEndpoint(req.Source, sourceAuth),
Target: syncerEndpoint(req.Target, targetAuth),
HTTPClient: c.httpClient,
Branches: append([]string(nil), req.Scope.Branches...),
Mappings: validationMappings(req.Scope.Mappings),
AllRefs: req.Scope.AllRefs,
ExcludeRefPrefixes: append([]string(nil), req.Scope.ExcludeRefPrefixes...),
ExcludeRefs: append([]string(nil), req.Scope.ExcludeRefs...),
IncludeTags: req.Policy.IncludeTags,
DryRun: dryRun,
ShowStats: req.CollectStats,
Mode: string(req.Policy.Mode),
ForceWithLease: req.Policy.ForceWithLease,
ForceBlind: req.Policy.ForceBlind,
Prune: req.Policy.Prune,
BestEffort: req.Policy.BestEffort,
ProtocolMode: string(req.Policy.Protocol),
MaterializedMaxObjects: syncer.DefaultMaterializedMaxObjects,
}, nil
}
func (c *Client) authFor(ctx context.Context, endpoint Endpoint, role EndpointRole) (EndpointAuth, error) {
52 unmodified lines
return nil
}
func bridgeEndpoint(ep Endpoint) internalbridge.Endpoint {
return internalbridge.Endpoint{
func syncerEndpoint(ep Endpoint, auth EndpointAuth) syncer.Endpoint {
return syncer.Endpoint{
URL: ep.URL,
Username: auth.Username,
Token: auth.Token,
BearerToken: auth.BearerToken,
SkipTLSVerify: auth.SkipTLSVerify,
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,
AllRefs: scope.AllRefs,
ExcludeRefPrefixes: append([]string(nil), scope.ExcludeRefPrefixes...),
ExcludeRefs: append([]string(nil), scope.ExcludeRefs...),
}
}
func bridgePolicy(policy SyncPolicy) internalbridge.SyncPolicy {
return internalbridge.SyncPolicy{
Mode: internalbridge.OperationMode(policy.Mode),
IncludeTags: policy.IncludeTags,
ForceWithLease: policy.ForceWithLease,
ForceBlind: policy.ForceBlind,
Prune: policy.Prune,
BestEffort: policy.BestEffort,
Protocol: internalbridge.ProtocolMode(policy.Protocol),
}
}
func validateOperationMode(mode OperationMode) error {
switch mode {
case "", ModeSync, ModeReplicate:
3 unmodified lines
}
}
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 {
Mclient.go+55/-89
382 unmodified lines
383
384
385
386
387
388
389
390
391
392
393
394
386
387
388
389
382 unmodified lines
return nil
}
slices.Sort(input)
out := input[:0]
var prev string
for i, item := range input {
if i == 0 || item != prev {
out = append(out, item)
prev = item
}
}
return out
return slices.Compact(input)
}
func normalizeRepoURL(raw string) (string, error) {
Mcmd/git-sync-bench/main.go+1/-9
4 unmodified lines
5
6
7
8
8
9
10
25 unmodified lines
36
37
38
40
41
42
43
44
45
46
47
48
39
40
41
42
43
44
45
46
4 unmodified lines
"fmt"
gitsync "entire.io/entire/git-sync"
"entire.io/entire/git-sync/internal/validation"
"entire.io/entire/git-sync/unstable"
"github.com/spf13/cobra"
)
25 unmodified lines
if branches != "" {
req.Scope.Branches = splitCSV(branches)
}
for _, raw := range mappings {
mapping, err := validation.ParseMapping(raw)
if err != nil {
return fmt.Errorf("parse mapping %q: %w", raw, err)
}
req.Scope.Mappings = append(req.Scope.Mappings, gitsync.RefMapping{
Source: mapping.Source,
Target: mapping.Target,
})
parsed, err := parseMappings(mappings)
if err != nil {
return err
}
req.Scope.Mappings = parsed
if req.Source.URL == "" || req.Target.URL == "" {
return errors.New("bootstrap requires source and target repository URLs")
Mcmd/git-sync/bootstrap.go+4/-10
73 unmodified lines
74
75
76
77
78
79
80
81
82
83
77
78
79
80
73 unmodified lines
cmd.Flags().BoolVar(&req.SourceFollowInfoRefsRedirect, "source-follow-info-refs-redirect",
envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"),
"send follow-up source RPCs to the final /info/refs redirect host")
addSecretFlag(cmd, &req.SourceAuth.Token, "source-token", "GITSYNC_SOURCE_TOKEN", "source token/password")
cmd.Flags().StringVar(&req.SourceAuth.Username, "source-username",
envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username")
addSecretFlag(cmd, &req.SourceAuth.BearerToken, "source-bearer-token", "GITSYNC_SOURCE_BEARER_TOKEN", "source bearer token")
cmd.Flags().BoolVar(&req.SourceAuth.SkipTLSVerify, "source-insecure-skip-tls-verify",
envBool("GITSYNC_SOURCE_INSECURE_SKIP_TLS_VERIFY"),
"skip TLS certificate verification for the source")
addSourceAuth(cmd, &req.SourceAuth)
cmd.Flags().StringVar(&req.TargetDir, "target-dir", "", "directory to initialize as a SHA256 bare repository")
allRefsFlag(cmd, allRefsUsageScopeOnly, &req.AllRefs)
Mcmd/git-sync/convert_sha256.go+1/-7
125 unmodified lines
126
127
128
129
129
130
131
132
46 unmodified lines
179
180
181
182
183
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
185
186
200
201
202
203
204
3 unmodified lines
208
209
210
196
211
212
213
214
3 unmodified lines
218
219
220
206
221
222
223
224
3 unmodified lines
228
229
230
216
231
232
218
233
234
220
235
236
125 unmodified lines
}
func newProtocolFlag() protocolModeFlag {
return protocolModeFlag(protocolMode(envOr("GITSYNC_PROTOCOL", validation.ProtocolAuto)))
return protocolModeFlag(envOr("GITSYNC_PROTOCOL", validation.ProtocolAuto))
}
func envOr(key, fallback string) string {
46 unmodified lines
return out
}
type protocolMode gitsync.ProtocolMode
type operationMode gitsync.OperationMode
// parseMappings converts raw --map values (src:dst form) into ref mappings.
// Returns nil (not an empty slice) when no mappings were given, matching the
// zero value the request structs start with.
func parseMappings(raw []string) ([]gitsync.RefMapping, error) {
if len(raw) == 0 {
return nil, nil
}
out := make([]gitsync.RefMapping, 0, len(raw))
for _, value := range raw {
mapping, err := validation.ParseMapping(value)
if err != nil {
return nil, fmt.Errorf("parse mapping %q: %w", value, err)
}
out = append(out, gitsync.RefMapping{Source: mapping.Source, Target: mapping.Target})
}
return out, nil
}
type protocolModeFlag protocolMode
type operationModeFlag operationMode
type protocolModeFlag gitsync.ProtocolMode
type operationModeFlag gitsync.OperationMode
func (p *protocolModeFlag) String() string { return string(*p) }
func (p *protocolModeFlag) Type() string { return "string" }
3 unmodified lines
if err != nil {
return fmt.Errorf("normalize protocol: %w", err)
}
*p = protocolModeFlag(protocolMode(gitsync.ProtocolMode(mode)))
*p = protocolModeFlag(mode)
return nil
}
3 unmodified lines
func (m *operationModeFlag) Set(value string) error {
switch gitsync.OperationMode(value) {
case gitsync.ModeSync, gitsync.ModeReplicate:
*m = operationModeFlag(operationMode(value))
*m = operationModeFlag(value)
return nil
default:
return fmt.Errorf("unsupported mode %q", value)
3 unmodified lines
// defaultOperationMode returns the starting value for the --mode flag.
// Subcommands that pin a mode (sync, replicate) pass it in; plan passes ""
// and gets sync as the default, letting --mode override it.
func defaultOperationMode(defaultMode gitsync.OperationMode) operationMode {
func defaultOperationMode(defaultMode gitsync.OperationMode) operationModeFlag {
if defaultMode != "" {
return operationMode(defaultMode)
return operationModeFlag(defaultMode)
}
return operationMode(gitsync.ModeSync)
return operationModeFlag(gitsync.ModeSync)
}
Mcmd/git-sync/flags.go+25/-10
41 unmodified lines
42
43
44
45
45
46
47
903 unmodified lines
951
952
953
955
954
955
956
957
9 unmodified lines
967
968
969
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
970
971
972
552 unmodified lines
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1559
1560
1561
1562
1563
1564
1565
1541
1542
1543
41 unmodified lines
formatcfg "github.com/go-git/go-git/v6/plumbing/format/config"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/go-git/go-git/v6/plumbing/storer"
transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
"github.com/go-git/go-git/v6/storage/filesystem"
gitsync "entire.io/entire/git-sync"
903 unmodified lines
SkipTLSVerify: req.SourceAuth.SkipTLSVerify,
}, ep)
httpClient := &http.Client{Transport: gitproto.NewHTTPTransport(req.SourceAuth.SkipTLSVerify)}
conn := gitproto.NewHTTPConnWithClient(ep, "source", normalizeAuth(authMethod), httpClient)
conn := gitproto.NewHTTPConnWithClient(ep, "source", authMethod, httpClient)
conn.FollowInfoRefsRedirect = req.SourceFollowInfoRefsRedirect
mode := string(req.ProtocolMode)
9 unmodified lines
return conn, svc, refs, nil
}
func normalizeAuth(m auth.Method) gitproto.AuthMethod {
if m == nil {
return nil
}
// auth.Method and gitproto.AuthMethod share the same Authorizer signature.
// Wrap so we can pass either *transporthttp.BasicAuth or *transporthttp.TokenAuth.
if a, ok := m.(*transporthttp.BasicAuth); ok {
return a
}
if a, ok := m.(*transporthttp.TokenAuth); ok {
return a
}
return authAdapter{m: m}
}
type authAdapter struct{ m auth.Method }
func (a authAdapter) Authorizer(req *http.Request) error {
if err := a.m.Authorizer(req); err != nil {
return fmt.Errorf("authorize request: %w", err)
}
return nil
}
// translator walks the SHA1 source store, rewrites object content with
// SHA256-mapped hashes, and writes the result into the target bare repo
// via SetEncodedObject. The target storer is configured for SHA256 (see
552 unmodified lines
return b.String(), count, nil
}
// resolveCacheEntry holds a memoized (Hash, matchResult) pair from
// resolveMessageRef. Stored in t.resolveCache keyed by lowercased prefix.
type resolveCacheEntry struct {
hash plumbing.Hash
result matchResult
}
// resolveMessageRef classifies a hex prefix against the reachable set.
// Returns matchUnique with the resolved SHA1 when exactly one commit
// or tag in scope matches; matchAmbiguous when more than one does;
// matchNone otherwise (no match, or the match is a blob/tree — those
// are filtered so incidental hex collisions on content hashes aren't
// rewritten).
// resolveCacheEntry holds a memoized (Hash, matchResult) pair from
// resolveMessageRef. Stored in t.resolveCache keyed by lowercased prefix.
type resolveCacheEntry struct {
hash plumbing.Hash
result matchResult
}
func (t *translator) resolveMessageRef(prefix string) (plumbing.Hash, matchResult) {
// Canonicalize to lowercase: hashPattern is case-insensitive so
// the caller can match `ABCD1234` in a message, but reachable
Mcmd/git-sync/internal/sha256convert/sha256convert.go+8/-33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package versioninfo
import "fmt"
var (
Version = "dev"
Commit = "none"
Date = "unknown"
)
// String renders the one-line build description shared by `git-sync version`
// and `git-sync --version`.
func String() string {
return fmt.Sprintf("git-sync %s (commit %s, built %s)", Version, Commit, Date)
}
Mcmd/git-sync/internal/versioninfo/versioninfo.go+8
26 unmodified lines
27
28
29
30
31
30
31
32
33
26 unmodified lines
return cmd.Help()
},
}
cmd.SetVersionTemplate(fmt.Sprintf("git-sync %s (commit %s, built %s)\n",
versioninfo.Version, versioninfo.Commit, versioninfo.Date))
cmd.SetVersionTemplate(versioninfo.String() + "\n")
cmd.AddCommand(newSyncCmd())
cmd.AddCommand(newReplicateCmd())
Mcmd/git-sync/root.go+1/-2
4 unmodified lines
5
6
7
8
8
9
10
17 unmodified lines
28
29
30
32
31
32
33
34
19 unmodified lines
54
55
56
58
59
60
61
62
63
64
65
66
57
58
59
60
61
62
63
64
3 unmodified lines
68
69
70
77
78
79
80
71
72
73
74
4 unmodified lines
"fmt"
gitsync "entire.io/entire/git-sync"
"entire.io/entire/git-sync/internal/validation"
"entire.io/entire/git-sync/unstable"
"github.com/spf13/cobra"
)
17 unmodified lines
sourceAuth gitsync.EndpointAuth
targetAuth gitsync.EndpointAuth
branches string
modeValue = operationModeFlag(defaultOperationMode(defaultMode))
modeValue = defaultOperationMode(defaultMode)
protocolVal = newProtocolFlag()
legacyForce bool
req = unstable.SyncRequest{DryRun: dryRun}
19 unmodified lines
if req.Source.URL == "" || req.Target.URL == "" {
return fmt.Errorf("%s requires source and target repository URLs", name)
3 unmodified lines
Auth: gitsync.StaticAuthProvider{Source: sourceAuth, Target: targetAuth},
})
var (
result unstable.Result
err error
)
var result unstable.Result
ctx := cmd.Context()
switch {
case dryRun:
Mcmd/git-sync/syncplan.go+6/-15
11 unmodified lines
12
13
14
15
16
15
16
17
18
11 unmodified lines
Use: "version",
Short: "Show build information",
Run: func(cmd *cobra.Command, _ []string) {
fmt.Fprintf(cmd.OutOrStdout(), "git-sync %s (commit %s, built %s)\n",
versioninfo.Version, versioninfo.Commit, versioninfo.Date)
fmt.Fprintln(cmd.OutOrStdout(), versioninfo.String())
},
}
}
Mcmd/git-sync/version.go+1/-2
2 unmodified lines
3
4
5
6
6
7
8
9
10
11
12
13
14
15
16
16
17
18
19
20
21
17
18
19
20
21
22
23
2 unmodified lines
import (
"context"
"fmt"
"net/http"
"net/url"
"os/exec"
"strings"
transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
"entire.io/entire/git-sync/internal/gitproto"
)
const defaultGitUsername = "git"
// Method authorizes outbound HTTP requests for a remote. It is satisfied
// by *transporthttp.BasicAuth and *transporthttp.TokenAuth, whose Authorizer
// methods replaced the Method interface that go-git removed in v6 alpha.2.
type Method interface {
Authorizer(req *http.Request) error
}
// Method authorizes outbound HTTP requests for a remote. It aliases
// gitproto.AuthMethod so values returned by Resolve flow into gitproto
// connection constructors without conversion.
type Method = gitproto.AuthMethod
// Endpoint holds the authentication-related fields for a remote.
type Endpoint struct {
Minternal/auth/auth.go+6/-7
44 unmodified lines
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
48
49
50
44 unmodified lines
return out
}
// PlansToPushPlans converts planner BranchPlans to gitproto PushPlans.
func PlansToPushPlans(plans []planner.BranchPlan) []gitproto.PushPlan {
out := make([]gitproto.PushPlan, len(plans))
for i, p := range plans {
out[i] = gitproto.PushPlan{
TargetRef: p.TargetRef,
TargetHash: p.TargetHash,
SourceHash: p.SourceHash,
Delete: p.Action == planner.ActionDelete,
}
}
return out
}
// PlansToPushCommands converts planner BranchPlans directly to gitproto PushCommands.
// When forceBlind is true, non-delete commands send a zero expected-old so
// receive-pack overwrites regardless of current target value; see SyncPolicy.
Minternal/convert/convert.go-14
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
package gitproto
import (
"errors"
"fmt"
"io"
"github.com/go-git/go-git/v6/plumbing"
)
// ToPushCommands converts a slice of PushPlans to PushCommands.
// Used by all strategy packages to avoid copy-pasting the conversion.
func ToPushCommands(plans []PushPlan) []PushCommand {
cmds := make([]PushCommand, 0, len(plans))
for _, p := range plans {
cmd := PushCommand{Name: p.TargetRef, Old: p.TargetHash}
if p.Delete {
cmd.Delete = true
} else {
cmd.New = p.SourceHash
}
cmds = append(cmds, cmd)
}
return cmds
}
// PushPlan is a minimal interface for plan-to-command conversion.
type PushPlan struct {
TargetRef plumbing.ReferenceName
TargetHash plumbing.Hash
SourceHash plumbing.Hash
Delete bool
}
// LimitPackReader wraps a ReadCloser with a byte limit. Shared across strategies.
func LimitPackReader(r io.ReadCloser, maxBytes int64) io.ReadCloser {
if maxBytes <= 0 {
return r
}
return &packLimitRC{ReadCloser: r, max: maxBytes}
}
type packLimitRC struct {
io.ReadCloser
max int64
read int64
}
func (r *packLimitRC) Read(p []byte) (int, error) {
n, err := r.ReadCloser.Read(p)
r.read += int64(n)
if r.read > r.max {
return n, fmt.Errorf("source pack exceeded max-pack-bytes limit (%d)", r.max)
}
if err != nil && !errors.Is(err, io.EOF) {
return n, fmt.Errorf("read: %w", err)
}
return n, err //nolint:wrapcheck // io.EOF must pass through for io.Reader contract
}
Dinternal/gitproto/convert.go-60
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
package gitproto
import (
"io"
"strings"
"testing"
"github.com/go-git/go-git/v6/plumbing"
)
func TestToPushCommands(t *testing.T) {
hashA := plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
hashB := plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
tests := []struct {
name string
plan PushPlan
wantNew plumbing.Hash
wantOld plumbing.Hash
wantDelete bool
}{
{
name: "create command",
plan: PushPlan{
TargetRef: "refs/heads/main",
TargetHash: plumbing.ZeroHash,
SourceHash: hashA,
},
wantNew: hashA,
wantOld: plumbing.ZeroHash,
},
{
name: "update command",
plan: PushPlan{
TargetRef: "refs/heads/main",
TargetHash: hashA,
SourceHash: hashB,
},
wantNew: hashB,
wantOld: hashA,
},
{
name: "delete command",
plan: PushPlan{
TargetRef: "refs/heads/old-branch",
TargetHash: hashA,
Delete: true,
},
wantOld: hashA,
wantDelete: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmds := ToPushCommands([]PushPlan{tt.plan})
if len(cmds) != 1 {
t.Fatalf("expected 1 command, got %d", len(cmds))
}
cmd := cmds[0]
if cmd.Name != tt.plan.TargetRef {
t.Errorf("Name = %s, want %s", cmd.Name, tt.plan.TargetRef)
}
if cmd.Old != tt.wantOld {
t.Errorf("Old = %s, want %s", cmd.Old, tt.wantOld)
}
if cmd.Delete != tt.wantDelete {
t.Errorf("Delete = %v, want %v", cmd.Delete, tt.wantDelete)
}
if !tt.wantDelete && cmd.New != tt.wantNew {
t.Errorf("New = %s, want %s", cmd.New, tt.wantNew)
}
})
}
}
func TestToPushCommandsMultiple(t *testing.T) {
plans := []PushPlan{
{TargetRef: "refs/heads/a", SourceHash: plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")},
{TargetRef: "refs/heads/b", SourceHash: plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")},
{TargetRef: "refs/heads/c", TargetHash: plumbing.NewHash("cccccccccccccccccccccccccccccccccccccccc"), Delete: true},
}
cmds := ToPushCommands(plans)
if len(cmds) != 3 {
t.Fatalf("expected 3 commands, got %d", len(cmds))
}
}
func TestToPushCommandsEmpty(t *testing.T) {
cmds := ToPushCommands(nil)
if len(cmds) != 0 {
t.Fatalf("expected 0 commands for nil input, got %d", len(cmds))
}
}
func TestLimitPackReaderWithinLimit(t *testing.T) {
data := "hello world"
rc := io.NopCloser(strings.NewReader(data))
limited := LimitPackReader(rc, 1024)
defer limited.Close()
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
func TestLimitPackReaderExceedsLimit(t *testing.T) {
data := "this is more than ten bytes of data"
rc := io.NopCloser(strings.NewReader(data))
limited := LimitPackReader(rc, 10)
defer limited.Close()
_, err := io.ReadAll(limited)
if err == nil {
t.Fatal("expected error when exceeding limit, got nil")
}
if !strings.Contains(err.Error(), "source pack exceeded max-pack-bytes limit") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestLimitPackReaderZeroLimitPassesThrough(t *testing.T) {
data := "unlimited data"
rc := io.NopCloser(strings.NewReader(data))
limited := LimitPackReader(rc, 0)
defer limited.Close()
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
func TestLimitPackReaderNegativeLimitPassesThrough(t *testing.T) {
data := "unlimited data"
rc := io.NopCloser(strings.NewReader(data))
limited := LimitPackReader(rc, -1)
defer limited.Close()
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
func TestSortedUniqueHashes(t *testing.T) {
hashA := plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
hashB := plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
hashC := plumbing.NewHash("cccccccccccccccccccccccccccccccccccccccc")
tests := []struct {
name string
input []plumbing.Hash
want []plumbing.Hash
}{
{
name: "deduplicates repeated hashes",
input: []plumbing.Hash{hashA, hashB, hashA, hashC, hashB},
want: []plumbing.Hash{hashA, hashB, hashC},
},
{
name: "already sorted and unique is unchanged",
input: []plumbing.Hash{hashA, hashB, hashC},
want: []plumbing.Hash{hashA, hashB, hashC},
},
{
name: "reverse order gets sorted",
input: []plumbing.Hash{hashC, hashB, hashA},
want: []plumbing.Hash{hashA, hashB, hashC},
},
{
name: "single element",
input: []plumbing.Hash{hashB},
want: []plumbing.Hash{hashB},
},
{
name: "empty input",
input: []plumbing.Hash{},
want: []plumbing.Hash{},
},
{
name: "nil input",
input: nil,
want: []plumbing.Hash{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SortedUniqueHashes(tt.input)
if len(got) != len(tt.want) {
t.Fatalf("len = %d, want %d", len(got), len(tt.want))
}
for i := range got {
if got[i] != tt.want[i] {
t.Errorf("index %d: got %s, want %s", i, got[i], tt.want[i])
}
}
})
}
}
Dinternal/gitproto/convert_test.go-211
27 unmodified lines
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
31
32
33
50
34
35
36
37
41 unmodified lines
79
80
81
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
82
83
84
85
86
87
148
88
89
90
91
92
93
27 unmodified lines
IsTag bool
}
// FetchFeatures summarizes negotiated source fetch features used by strategies.
type FetchFeatures struct {
Filter bool
IncludeTag bool
}
func (s *RefService) FetchFeatures() FetchFeatures {
if s == nil || s.Protocol != "v2" || s.V2Caps == nil {
return FetchFeatures{}
}
return FetchFeatures{
Filter: s.V2Caps.FetchSupports("filter"),
IncludeTag: s.V2Caps.FetchSupports("include-tag"),
}
}
// SupportsBootstrapBatch centralizes the source-side capability check for the
// batched bootstrap strategy.
func (s *RefService) SupportsBootstrapBatch() bool {
return s != nil && s.Protocol == "v2" && s.FetchFeatures().Filter
return s != nil && s.Protocol == "v2" && s.V2Caps != nil && s.V2Caps.FetchSupports("filter")
}
// FetchToStore fetches objects from source into the given store, using the
41 unmodified lines
}
}
// FetchCommitGraph fetches only the commit graph (tree:0 filter) for a ref.
// Requires v2 with filter support. Optional haves let the source skip commits
// already reachable from those hashes, which is valuable when planning later
// branches that share history with an already-planned trunk.
func (s *RefService) FetchCommitGraph(
ctx context.Context,
store storer.Storer,
conn Conn,
ref DesiredRef,
haves []plumbing.Hash,
) error {
if s.Protocol != "v2" {
return errors.New("commit graph fetch requires protocol v2")
}
if !s.V2Caps.FetchSupports("filter") {
return errors.New("source does not advertise fetch filter support")
}
sortedHaves := SortedUniqueHashes(haves)
cmdArgs := make([]string, 0, 4+len(sortedHaves))
cmdArgs = append(cmdArgs,
"ofs-delta",
"no-progress",
"filter tree:0",
"want "+ref.SourceHash.String(),
)
for _, h := range sortedHaves {
cmdArgs = append(cmdArgs, "have "+h.String())
}
cmdArgs = append(cmdArgs, "done")
body, err := EncodeCommand("fetch", s.V2Caps.RequestCapabilities(), cmdArgs)
if err != nil {
return err
}
reader, err := PostRPCStream(ctx, conn, transport.UploadPackService, body, true, "upload-pack fetch")
if err != nil {
return err
}
defer ioutil.CheckClose(reader, &err)
// Commit-graph fetches are short and not user-facing; skip progress.
return storeV2FetchPack(store, reader, false, nil)
}
// FetchCommitParents fetches a commit graph (tree:0 filter) and
// FetchCommitParents fetches a commit graph (v2 fetch with tree:0 filter) and
// extracts (commit -> parent hashes) directly from the pack stream
// without materializing the full object set into a store. Peak memory
// during this call is dominated by the result map plus a small fixed
// delta-resolution cache; no commit content is retained.
//
// Same wire protocol as FetchCommitGraph (v2 fetch with tree:0 filter).
// Optional haves let the source skip commits already reachable from those
// hashes, which is valuable when planning later branches that share history
// with an already-planned trunk.
// Returns an empty map (not nil) when the source has no new commits.
func (s *RefService) FetchCommitParents(
ctx context.Context,
Minternal/gitproto/fetch.go+5/-63
58 unmodified lines
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
62
63
64
51 unmodified lines
116
117
118
138
139
140
141
142
143
144
145
146
147
148
119
120
121
183 unmodified lines
305
306
307
338
308
309
340
310
311
312
313
314
315
316
317
318
346
319
320
321
322
323
324
325
353
326
327
328
329
330
331
332
333
334
335
58 unmodified lines
}
}
func TestFetchFeatures(t *testing.T) {
v2Caps := &V2Capabilities{
Caps: map[string]string{
"fetch": "shallow filter include-tag",
},
}
rs := &RefService{Protocol: "v2", V2Caps: v2Caps}
features := rs.FetchFeatures()
if !features.Filter || !features.IncludeTag {
t.Fatalf("FetchFeatures() = %+v, want filter and include-tag enabled", features)
}
rs = &RefService{Protocol: "v1"}
features = rs.FetchFeatures()
if features.Filter || features.IncludeTag {
t.Fatalf("FetchFeatures() for v1 = %+v, want zero value", features)
}
}
func TestSupportsBootstrapBatch(t *testing.T) {
if (&RefService{Protocol: "v1"}).SupportsBootstrapBatch() {
t.Fatal("v1 service should not support bootstrap batching")
51 unmodified lines
}
}
func TestProgressWriter(t *testing.T) {
w := progressWriter(false, nil)
if w != nil {
t.Error("progressWriter(false, nil) should return nil")
}
w = progressWriter(true, nil)
if w == nil {
t.Error("progressWriter(true, nil) should return non-nil writer")
}
}
func TestWrappedRCClose(t *testing.T) {
// wrappedRC should close the underlying closer.
called := false
183 unmodified lines
}
}
func TestFetchCommitGraphRequiresV2(t *testing.T) {
func TestFetchCommitParentsRequiresV2(t *testing.T) {
rs := &RefService{Protocol: "v1"}
err := rs.FetchCommitGraph(t.Context(), nil, nil, DesiredRef{}, nil)
parents, err := rs.FetchCommitParents(t.Context(), nil, DesiredRef{}, nil)
if err == nil {
t.Fatal("expected error for non-v2 protocol")
}
if parents != nil {
t.Fatalf("expected nil parents on error, got %v", parents)
}
}
func TestFetchCommitGraphRequiresFilter(t *testing.T) {
func TestFetchCommitParentsRequiresFilter(t *testing.T) {
caps := &V2Capabilities{
Caps: map[string]string{
"fetch": "shallow",
},
}
rs := &RefService{Protocol: "v2", V2Caps: caps}
err := rs.FetchCommitGraph(t.Context(), nil, nil, DesiredRef{}, nil)
parents, err := rs.FetchCommitParents(t.Context(), nil, DesiredRef{}, nil)
if err == nil {
t.Fatal("expected error when filter not supported")
}
if parents != nil {
t.Fatalf("expected nil parents on error, got %v", parents)
}
}
func TestFetchPackV1ContextCanceled(t *testing.T) {
Minternal/gitproto/fetch_test.go+10/-34
2 unmodified lines
3
4
5
6
6
7
8
183 unmodified lines
192
193
194
196
197
198
199
200
195
196
197
2 unmodified lines
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"io"
183 unmodified lines
}
}
// HashHex is a helper to encode a 20-byte hash as lowercase hex.
func HashHex(h [20]byte) string {
return hex.EncodeToString(h[:])
}
// FormatPktLine encodes a single pkt-line from a string payload.
func FormatPktLine(s string) string {
n := len(s) + 4
Minternal/gitproto/pktline.go-6
394 unmodified lines
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
394 unmodified lines
t.Errorf("payload = %q, want %q", payload, "ABCDEF")
}
}
func TestHashHex(t *testing.T) {
tests := []struct {
name string
hash [20]byte
want string
}{
{
name: "zero hash",
hash: [20]byte{},
want: "0000000000000000000000000000000000000000",
},
{
name: "known hash",
hash: [20]byte{0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
want: "deadbeef0102030405060708090a0b0c0d0e0f10",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := HashHex(tt.hash)
if got != tt.want {
t.Errorf("HashHex() = %q, want %q", got, tt.want)
}
})
}
}
Minternal/gitproto/pktline_test.go-27
662 unmodified lines
663
664
665
666
666
667
668
669
2 unmodified lines
672
673
674
675
675
676
677
678
679
680
681
682
683
684
685
686
687
688
679
680
681
682
683
684
685
690
691
692
693
694
695
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
122 unmodified lines
826
827
828
825
826
827
828
829
830
831
832
833
834
829
830
831
662 unmodified lines
return
case <-ticker.C:
fmt.Fprintf(dest, "encoding pack: %s, elapsed %s\r",
humanizeBytes(cw.Count()), time.Since(start).Round(time.Second))
HumanBytes(cw.Count()), time.Since(start).Round(time.Second))
}
}
}()
2 unmodified lines
close(stop)
<-done
fmt.Fprintf(dest, "encoded pack: %s in %s\n",
humanizeBytes(cw.Count()), time.Since(start).Round(time.Second))
HumanBytes(cw.Count()), time.Since(start).Round(time.Second))
}
}
// humanizeBytes renders n in IEC units with one decimal place for KB+
// (e.g. "47.3 MB"). Anything below 1 KB is shown as raw bytes.
func humanizeBytes(n int64) string {
const (
kb = 1024
mb = kb * 1024
gb = mb * 1024
)
switch {
case n < kb:
// HumanBytes renders a byte count in IEC-ish binary units (e.g. "47.3 MB"),
// scaling decimal precision down as the value grows. Shared by the progress
// and batching output across syncer, strategy, and gitproto.
func HumanBytes(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
case n < mb:
return fmt.Sprintf("%.1f KB", float64(n)/float64(kb))
case n < gb:
return fmt.Sprintf("%.1f MB", float64(n)/float64(mb))
default:
return fmt.Sprintf("%.1f GB", float64(n)/float64(gb))
}
div, exp := int64(unit), 0
for x := n / unit; x >= unit; x /= unit {
div *= unit
exp++
}
value := float64(n) / float64(div)
suffix := []string{"KB", "MB", "GB", "TB", "PB"}[exp]
if value >= 100 {
return fmt.Sprintf("%.0f %s", value, suffix)
}
if value >= 10 {
return fmt.Sprintf("%.1f %s", value, suffix)
}
return fmt.Sprintf("%.2f %s", value, suffix)
}
// PushPack pushes a pack stream (relay) to the target.
122 unmodified lines
return emptyPackSHA1
}
func progressWriter(verbose bool, dest io.Writer) io.Writer {
if !verbose {
return nil
}
if dest == nil {
dest = os.Stderr
}
return dest
}
// progressSink returns a line-prefixing io.Writer suitable for
// sideband.Demuxer.Progress. When verbose is false it returns nil so the
// demuxer discards progress frames without allocating. Passing a non-nil
Minternal/gitproto/push.go+22/-28
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
package gitproto
import (
"errors"
"fmt"
"io"
"sync"
)
// This file holds shared io.ReadCloser wrappers for pack streams produced
// and consumed by this package.
// CloseOnce wraps a ReadCloser so repeated Close calls only close the
// underlying reader once. Strategies use it for pack readers that are closed
// both by PushPack and by the caller's retry/error cleanup, so double closes
// do not surface spurious failures. Passing an already-wrapped or nil reader
// returns it unchanged.
func CloseOnce(rc io.ReadCloser) io.ReadCloser {
if rc == nil {
return nil
}
if _, ok := rc.(*closeOnceReadCloser); ok {
return rc
}
return &closeOnceReadCloser{ReadCloser: rc}
}
type closeOnceReadCloser struct {
io.ReadCloser
once sync.Once
}
func (c *closeOnceReadCloser) Close() error {
var err error
c.once.Do(func() {
err = c.ReadCloser.Close()
})
if err != nil {
return fmt.Errorf("close pack reader: %w", err)
}
return nil
}
type packLimitRC struct {
io.ReadCloser
max int64
read int64
}
Ainternal/gitproto/readers.go+70
package gitproto
import (
"io"
"strings"
"testing"
"github.com/go-git/go-git/v6/plumbing"
)
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
got, err := io.ReadAll(limited)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != data {
t.Errorf("got %q, want %q", got, data)
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SortedUniqueHashes(tt.input)
if len(got) != len(tt.want) {
t.Fatalf("len = %d, want %d", len(got), len(tt.want))
}
for i := range got {
if got[i] != tt.want[i] {
t.Errorf("index %d: got %s, want %s", i, got[i], tt.want[i])
}
}
})
}
}
type countingCloser struct {
io.Reader
closes int
}
func (c *countingCloser) Close() error {
c.closes++
return nil
}
func TestCloseOnce(t *testing.T) {
if CloseOnce(nil) != nil {
t.Fatal("CloseOnce(nil) should return nil")
}
cc := &countingCloser{Reader: strings.NewReader("data")}
wrapped := CloseOnce(cc)
if again := CloseOnce(wrapped); again != wrapped {
t.Fatal("CloseOnce should return an already-wrapped reader unchanged")
}
if err := wrapped.Close(); err != nil {
t.Fatalf("Close() error: %v", err)
}
if err := wrapped.Close(); err != nil {
t.Fatalf("second Close() error: %v", err)
}
if cc.closes != 1 {
t.Fatalf("underlying closer closed %d times, want 1", cc.closes)
}
}
func TestHumanBytes(t *testing.T) {
t.Parallel()
cases := []struct {
in int64
want string
}{
{0, "0 B"},
{42, "42 B"},
{1023, "1023 B"},
{1024, "1.00 KB"},
{1500, "1.46 KB"},
{int64(15 * 1024), "15.0 KB"},
{int64(1024 * 1024), "1.00 MB"},
{int64(150 * 1024 * 1024), "150 MB"},
{int64(3) << 30, "3.00 GB"},
{int64(2) << 40, "2.00 TB"},
{int64(5) << 50, "5.00 PB"},
}
for _, c := range cases {
if got := HumanBytes(c.in); got != c.want {
t.Errorf("HumanBytes(%d) = %q, want %q", c.in, got, c.want)
}
}
}
Ainternal/gitproto/readers_test.go+183
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
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 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
AllRefs bool
ExcludeRefPrefixes []string
ExcludeRefs []string
}
type SyncPolicy struct {
Mode OperationMode
IncludeTags bool
ForceWithLease bool
ForceBlind bool
Prune bool
BestEffort bool
Protocol ProtocolMode
}
func ProbeConfig(source Endpoint, sourceAuth EndpointAuth, target *Endpoint, targetAuth EndpointAuth, protocol ProtocolMode, includeTags, allRefs, collectStats bool, excludeRefPrefixes []string, httpClient *http.Client) Config {
cfg := syncer.Config{
Source: ToSyncerEndpoint(source, sourceAuth),
HTTPClient: httpClient,
IncludeTags: includeTags,
AllRefs: allRefs,
ExcludeRefPrefixes: append([]string(nil), excludeRefPrefixes...),
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),
AllRefs: scope.AllRefs,
ExcludeRefPrefixes: append([]string(nil), scope.ExcludeRefPrefixes...),
ExcludeRefs: append([]string(nil), scope.ExcludeRefs...),
IncludeTags: policy.IncludeTags,
DryRun: dryRun,
ShowStats: collectStats,
Mode: operationModeString(policy.Mode),
ForceWithLease: policy.ForceWithLease,
ForceBlind: policy.ForceBlind,
Prune: policy.Prune,
BestEffort: policy.BestEffort,
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
}
Dinternal/internalbridge/config.go-149
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) RefKindOther RefKind = RefKind(planner.RefKindOther) )
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) ActionWarn Action = Action(planner.ActionWarn) )
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 SideBytes struct {
Label string json:"label"
Bytes int64 json:"bytes"
Display string json:"display,omitempty"
ActiveNanos int64 json:"activeNanos,omitempty"
IdleNanos int64 json:"idleNanos,omitempty"
}
type Stats struct {
Enabled bool json:"enabled"
Items map[string]*ServiceStats json:"items"
Sides []SideBytes json:"sides,omitempty"
ElapsedNanos int64 json:"elapsedNanos,omitempty"
}
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"
SourceHEAD string json:"sourceHead,omitempty"
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"
Warned int json:"warned"
}
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"
SourceHEAD string json:"sourceHead,omitempty"
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)), SourceHEAD: result.SourceHEAD.String(), 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, Warned: result.Warned, }, Execution: ExecutionSummary{ DryRun: result.DryRun, Protocol: result.Protocol, OperationMode: result.OperationMode, Relay: result.Relay, TransferMode: result.RelayMode, Reason: result.RelayReason, BootstrapSuggested: result.BootstrapSuggested, SourceHEAD: result.SourceHEAD.String(), 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, } } if len(stats.Sides) > 0 { out.Sides = make([]SideBytes, 0, len(stats.Sides)) for _, side := range stats.Sides { out.Sides = append(out.Sides, SideBytes{ Label: side.Label, Bytes: side.Bytes, Display: side.Display, ActiveNanos: side.ActiveNanos, IdleNanos: side.IdleNanos, }) } } out.ElapsedNanos = stats.ElapsedNanos 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() }
Dinternal/internalbridge/model.go-240
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/git-sync/internal/planner" "entire.io/entire/git-sync/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") } }
Dinternal/internalbridge/model\_test.go-97
304 unmodified lines
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 308 309 310
304 unmodified lines
return chain, nil }
// FirstParentChainFromMap walks a first-parent map from tip back to root. // The map key is a commit hash, the value is its first parent hash. // A zero-value parent marks the root. Returns the chain in root-to-tip order. func FirstParentChainFromMap(parents map[plumbing.Hash]plumbing.Hash, tip plumbing.Hash) ([]plumbing.Hash, error) { chain := make([]plumbing.Hash, 0, len(parents)) current := tip seen := make(map[plumbing.Hash]struct{}, len(parents)) for { if _, ok := seen[current]; ok { return nil, fmt.Errorf("cycle detected at %s", current) } seen[current] = struct{}{} chain = append(chain, current) parent, ok := parents[current] if !ok { return nil, fmt.Errorf("commit %s not found in parent map", current) } if parent.IsZero() { break } current = parent } for i, j := 0, len(chain)-1; i < j; i, j = i+1, j-1 { chain[i], chain[j] = chain[j], chain[i] } return chain, nil }
// SampledCheckpointCandidates generates a set of candidate indices to probe, // sorted from largest (preferred) to smallest. func SampledCheckpointCandidates(lo, hi int, prevSpan int) []int {
Minternal/planner/checkpoint.go-28
221 unmodified lines
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 258 259 260 261 225 226 227
221 unmodified lines
return out }
// DesiredSubset returns the subset of desired refs that match the given plans. func DesiredSubset( desired map[plumbing.ReferenceName]DesiredRef, plans []BranchPlan, ) map[plumbing.ReferenceName]DesiredRef { out := make(map[plumbing.ReferenceName]DesiredRef, len(plans)) for _, plan := range plans { if ref, ok := desired[plan.TargetRef]; ok { out[plan.TargetRef] = ref } } return out }
// SingleDesired builds a single-entry desired ref map. func SingleDesired(sourceRef, targetRef plumbing.ReferenceName, hash plumbing.Hash) map[plumbing.ReferenceName]DesiredRef { return map[plumbing.ReferenceName]DesiredRef{ targetRef: { Kind: RefKindBranch, Label: targetRef.Short(), SourceRef: sourceRef, TargetRef: targetRef, SourceHash: hash, }, } }
// SingleHaveMap builds a single-entry have map for fetch negotiation. func SingleHaveMap(hash plumbing.Hash) map[plumbing.ReferenceName]plumbing.Hash { if hash.IsZero() { return nil } return map[plumbing.ReferenceName]plumbing.Hash{ plumbing.ReferenceName("refs/gitsync/have"): hash, } }
// BootstrapTempRef returns the temporary ref name used during batched bootstrap. func BootstrapTempRef(targetRef plumbing.ReferenceName) plumbing.ReferenceName { return plumbing.ReferenceName("refs/gitsync/bootstrap/heads/" + targetRef.Short())
Minternal/planner/types.go-37
13 unmodified lines
14 15 16 17 17 18 19 146 unmodified lines
166 167 168 170 169 170 171 172 14 unmodified lines
187 188 189 191 190 191 192 193 243 unmodified lines
437 438 439 441 440 441 442 443 17 unmodified lines
461 462 463 465 466 464 465 466 467 468 126 unmodified lines
595 596 597 599 598 599 600 601 80 unmodified lines
682 683 684 686 685 686 687 688 719 unmodified lines
1408 1409 1410 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1411 1412 1413 82 unmodified lines
1496 1497 1498 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550
13 unmodified lines
"regexp" "strconv" "strings" "sync"
git "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" 146 unmodified lines
return result, fmt.Errorf("fetch source pack: %w", err) } packReader = gitproto.LimitPackReader(packReader, p.MaxPackBytes) packReader = closeOnce(packReader) packReader = gitproto.CloseOnce(packReader)
p.log("bootstrap pushing refs to target", "ref_count", len(plans)) if p.OnPhase != nil { 14 unmodified lines
p.log("bootstrap retrying with batched mode after batchable push failure", "target_max_pack_bytes", autoBatch, "reason", reason) p.notice(fmt.Sprintf("%s — switching to batched mode (limit %s)", reason, humanBytes(autoBatch))) reason, gitproto.HumanBytes(autoBatch))) p.TargetMaxPack = autoBatch return executeBatched(ctx, p, plans, result) } 243 unmodified lines
if err != nil { return result, fmt.Errorf("fetch source batch pack for %s: %w", batch.Plan.TargetRef, err) } packReader = closeOnce(packReader) packReader = gitproto.CloseOnce(packReader)
// Peek at the PACK header (12 bytes) to get the object count. // If the estimated pack size (objectCount × calibrated 17 unmodified lines
"calibrated_bytes_per_object", calibratedBytesPerObject)
p.notice(fmt.Sprintf(
"estimated pack %s exceeds target limit %s — splitting %d → %d packs (%s each)",
humanBytes(estimated), humanBytes(p.TargetMaxPack),
oldRemaining, newCount, humanBytes(perPack),
gitproto.HumanBytes(estimated), gitproto.HumanBytes(p.TargetMaxPack),
oldRemaining, newCount, gitproto.HumanBytes(perPack),
))
batch.Checkpoints = append(batch.Checkpoints[:idx], expanded...)
subdivided = true
126 unmodified lines
"error", pushErr.Error()) limitText := "" if limit > 0 { limitText = fmt.Sprintf(" (target limit %s)", humanBytes(limit)) limitText = fmt.Sprintf(" (target limit %s)", gitproto.HumanBytes(limit)) } reason := "target rejected pack" if abortedEarly { 80 unmodified lines
} } else { packReader = gitproto.LimitPackReader(packReader, p.MaxPackBytes) packReader = closeOnce(packReader) packReader = gitproto.CloseOnce(packReader) cmds := convert.PlansToPushCommands(tailPlans, false) if err := p.TargetPusher.PushPack(ctx, cmds, packReader); err != nil { _ = packReader.Close() 719 unmodified lines
return limit, true }
// humanBytes renders a byte count in IEC-ish binary units. Local copy // because importing the syncer's formatter would invert the dependency // direction; the bootstrap package is meant to be standalone. func humanBytes(n int64) string { const unit = 1024 if n < unit { return fmt.Sprintf("%d B", n) } div, exp := int64(unit), 0 for x := n / unit; x >= unit; x /= unit { div *= unit exp++ } value := float64(n) / float64(div) suffix := []string{"KB", "MB", "GB", "TB", "PB"}[exp] if value >= 100 { return fmt.Sprintf("%.0f %s", value, suffix) } if value >= 10 { return fmt.Sprintf("%.1f %s", value, suffix) } return fmt.Sprintf("%.2f %s", value, suffix) }
func isTargetBodyLimitError(err error) bool { if err == nil { return false 82 unmodified lines
} p.Logger.Info(msg, args...) }
type closeOnceReadCloser struct { io.ReadCloser
once sync.Once }
func closeOnce(rc io.ReadCloser) io.ReadCloser { if rc == nil { return nil } if _, ok := rc.(*closeOnceReadCloser); ok { return rc } return &closeOnceReadCloser{ReadCloser: rc} }
Minternal/strategy/bootstrap/bootstrap.go+7/-59
7 unmodified lines
8 9 10 11 11 12 13 47 unmodified lines
61 62 63 65 64 65 66 67 12 unmodified lines
80 81 82 84 83 84 85 86 4 unmodified lines
91 92 93 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
7 unmodified lines
"errors" "fmt" "io" "sync"
"github.com/go-git/go-git/v6/plumbing"
47 unmodified lines
return Result{}, fmt.Errorf("fetch source pack: %w", err) } packReader = gitproto.LimitPackReader(packReader, p.MaxPackBytes) packReader = closeOnce(packReader) packReader = gitproto.CloseOnce(packReader) if err := p.TargetPusher.PushPack(ctx, cmds, packReader); err != nil { _ = packReader.Close() return Result{}, fmt.Errorf("push target refs: %w", err) 12 unmodified lines
return Result{}, fmt.Errorf("fetch source tag pack: %w", err) } packReader = gitproto.LimitPackReader(packReader, p.MaxPackBytes) packReader = closeOnce(packReader) packReader = gitproto.CloseOnce(packReader) if err := p.TargetPusher.PushPack(ctx, cmds, packReader); err != nil { _ = packReader.Close() return Result{}, fmt.Errorf("push target refs: %w", err) 4 unmodified lines
return Result{}, nil }
type closeOnceReadCloser struct { io.ReadCloser
once sync.Once }
Minternal/strategy/incremental/incremental.go+2/-30
13 unmodified lines
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
17
18
19
13 unmodified lines
"entire.io/entire/git-sync/internal/planner"
)
func TestPlansToPushPlans(t *testing.T) {
hash1 := plumbing.NewHash("1111111111111111111111111111111111111111")
hash2 := plumbing.NewHash("2222222222222222222222222222222222222222")
hash3 := plumbing.NewHash("3333333333333333333333333333333333333333")
tests := []struct {
name string
plans []planner.BranchPlan
expect []gitproto.PushPlan
}{
{
name: "create plan",
plans: []planner.BranchPlan{
{
TargetRef: plumbing.NewBranchReferenceName("main"),
TargetHash: plumbing.ZeroHash,
SourceHash: hash1,
Action: planner.ActionCreate,
},
},
expect: []gitproto.PushPlan{
{
TargetRef: plumbing.NewBranchReferenceName("main"),
TargetHash: plumbing.ZeroHash,
SourceHash: hash1,
Delete: false,
},
},
},
{
name: "update plan",
plans: []planner.BranchPlan{
{
TargetRef: plumbing.NewBranchReferenceName("main"),
TargetHash: hash1,
SourceHash: hash2,
Action: planner.ActionUpdate,
},
},
expect: []gitproto.PushPlan{
{
TargetRef: plumbing.NewBranchReferenceName("main"),
TargetHash: hash1,
SourceHash: hash2,
Delete: false,
},
},
},
{
name: "delete plan",
plans: []planner.BranchPlan{
{
TargetRef: plumbing.NewBranchReferenceName("old"),
TargetHash: hash3,
SourceHash: plumbing.ZeroHash,
Action: planner.ActionDelete,
},
},
expect: []gitproto.PushPlan{
{
TargetRef: plumbing.NewBranchReferenceName("old"),
TargetHash: hash3,
SourceHash: plumbing.ZeroHash,
Delete: true,
},
},
},
{
name: "empty input",
plans: []planner.BranchPlan{},
expect: []gitproto.PushPlan{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := convert.PlansToPushPlans(tt.plans)
if len(got) != len(tt.expect) {
t.Fatalf("expected %d push plans, got %d", len(tt.expect), len(got))
}
for i := range tt.expect {
if got[i].TargetRef != tt.expect[i].TargetRef {
t.Errorf("[%d] TargetRef = %s, want %s", i, got[i].TargetRef, tt.expect[i].TargetRef)
}
if got[i].TargetHash != tt.expect[i].TargetHash {
t.Errorf("[%d] TargetHash = %s, want %s", i, got[i].TargetHash, tt.expect[i].TargetHash)
}
if got[i].SourceHash != tt.expect[i].SourceHash {
t.Errorf("[%d] SourceHash = %s, want %s", i, got[i].SourceHash, tt.expect[i].SourceHash)
}
if got[i].Delete != tt.expect[i].Delete {
t.Errorf("[%d] Delete = %v, want %v", i, got[i].Delete, tt.expect[i].Delete)
}
}
})
}
}
type fakeSourceService struct {
fetchPack func(context.Context, gitproto.Conn, map[plumbing.ReferenceName]gitproto.DesiredRef, map[plumbing.ReferenceName]plumbing.Hash) (io.ReadCloser, error)
}
Minternal/strategy/incremental/incremental_test.go-98
7 unmodified lines
8
9
10
11
11
12
13
14
15
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
16
17
18
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"
)
func TestDefaultMaxMaterializedObjectsExported(t *testing.T) {
// Verify the constant is exported and has a reasonable positive value.
if DefaultMaxMaterializedObjects <= 0 {
Minternal/strategy/materialized/materialized_test.go-99
5 unmodified lines
6
7
8
9
9
10
11
53 unmodified lines
65
66
67
69
68
69
70
71
9 unmodified lines
81
82
83
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
5 unmodified lines
"errors"
"fmt"
"io"
"sync"
"github.com/go-git/go-git/v6/plumbing"
53 unmodified lines
return Result{}, fmt.Errorf("fetch source pack: %w", err)
}
packReader = gitproto.LimitPackReader(packReader, p.MaxPackBytes)
packReader = closeOnce(packReader)
packReader = gitproto.CloseOnce(packReader)
if err := p.TargetPusher.PushPack(ctx, convert.PlansToPushCommands(updatePlans, false), packReader); err != nil {
_ = packReader.Close()
return Result{}, fmt.Errorf("push target refs: %w", err)
9 unmodified lines
return Result{Relay: true, RelayMode: "replicate", RelayReason: "replicate-overwrite-relay"}, nil
}
type closeOnceReadCloser struct {
io.ReadCloser
once sync.Once
}
func closeOnce(rc io.ReadCloser) io.ReadCloser {
if rc == nil {
return nil
}
if _, ok := rc.(*closeOnceReadCloser); ok {
return rc
}
// PushPack and the caller both close the relay reader; wrap it so retries and
// error cleanup do not surface spurious double-close failures.
return &closeOnceReadCloser{ReadCloser: rc}
}
Minternal/strategy/replicate/replicate.go+1/-31
7 unmodified lines
8
9
10
11
12
13
14
15
277 unmodified lines
293
294
295
294
296
297
298
299
2 unmodified lines
302
303
304
303
305
306
307
308
102 unmodified lines
411
412
413
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
414
415
416
2 unmodified lines
419
420
421
442
422
423
7 unmodified lines
"strings"
"sync"
"time"
"entire.io/entire/git-sync/internal/gitproto"
)
// progressReporter renders live per-side throughput to a writer (typically
277 unmodified lines
var rateText string
if !done && instantBytesPerSec > 0 {
rateText = formatBytes(int64(instantBytesPerSec)) + "/s"
rateText = gitproto.HumanBytes(int64(instantBytesPerSec)) + "/s"
} else {
rateDur := fallbackDur
if side.ActiveNanos > 0 {
2 unmodified lines
rateText = formatRate(side.Bytes, rateDur)
}
rate := formatBytes(side.Bytes) + " @ " + rateText
rate := gitproto.HumanBytes(side.Bytes) + " @ " + rateText
if done {
rate += doneMark
}
102 unmodified lines
return (fi.Mode() & os.ModeCharDevice) != 0
}
// formatBytes renders byte counts in IEC-ish human units (binary base).
func formatBytes(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
div, exp := int64(unit), 0
for x := n / unit; x >= unit; x /= unit {
div *= unit
exp++
}
value := float64(n) / float64(div)
suffix := []string{"KB", "MB", "GB", "TB", "PB"}[exp]
if value >= 100 {
return fmt.Sprintf("%.0f %s", value, suffix)
}
if value >= 10 {
return fmt.Sprintf("%.1f %s", value, suffix)
}
return fmt.Sprintf("%.2f %s", value, suffix)
}
// formatRate renders a bytes/second average over the supplied duration.
// Returns "0 B/s" until the duration is large enough to be meaningful,
// avoiding misleadingly large rates from sub-millisecond samples.
2 unmodified lines
return "0 B/s"
}
rate := float64(bytes) / dur.Seconds()
return formatBytes(int64(rate)) + "/s"
return gitproto.HumanBytes(int64(rate)) + "/s"
}
Minternal/syncer/progress.go+5/-25
6 unmodified lines
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
10
11
12
6 unmodified lines
"time"
)
func TestFormatBytes(t *testing.T) {
t.Parallel()
cases := []struct {
in int64
want string
}{
{0, "0 B"},
{42, "42 B"},
{1023, "1023 B"},
{1024, "1.00 KB"},
{1500, "1.46 KB"},
{int64(15 * 1024), "15.0 KB"},
{int64(1024 * 1024), "1.00 MB"},
{int64(150 * 1024 * 1024), "150 MB"},
}
for _, c := range cases {
if got := formatBytes(c.in); got != c.want {
t.Errorf("formatBytes(%d) = %q, want %q", c.in, got, c.want)
}
}
}
func TestFormatRate(t *testing.T) {
t.Parallel()
const zeroRate = "0 B/s"
Minternal/syncer/progress_test.go-22
436 unmodified lines
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
433 unmodified lines
900
901
902
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
903
904
905
906
84 unmodified lines
991
992
993
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
994
995
996
997
998
999
1000
1001
281 unmodified lines
1283
1284
1285
1292
1293
1286
1287
1288
436 unmodified lines
result.Deleted += deleted
}
// classifyPlans splits planned actions into pushable plans, tallying skips and
// blocks into result. Under dry-run, pushable plans are counted as skipped and
// none are returned. ActionWarn is not produced by planning; it is only set
// after a push by applyRejections.
func classifyPlans(plans []BranchPlan, dryRun bool, result *Result) []BranchPlan {
pushPlans := make([]BranchPlan, 0, len(plans))
for _, plan := range plans {
switch plan.Action {
case ActionCreate, ActionUpdate, ActionDelete:
if dryRun {
result.Skipped++
continue
}
pushPlans = append(pushPlans, plan)
case ActionSkip:
result.Skipped++
case ActionBlock:
result.Blocked++
case ActionWarn:
}
}
return pushPlans
}
// tallyActions counts ref pushes and deletes from a classified plan slice.
// ActionWarn/Skip/Block don't contribute; rejections are tracked separately
// in Result.Warned via applyRejections.
433 unmodified lines
SourceHEAD: s.sourceService.HeadTarget,
}
pushPlans := make([]BranchPlan, 0, len(plans))
for _, plan := range plans {
switch plan.Action {
case ActionCreate, ActionUpdate, ActionDelete:
if s.cfg.DryRun {
result.Skipped++
continue
}
pushPlans = append(pushPlans, plan)
case ActionSkip:
result.Skipped++
case ActionBlock:
result.Blocked++
case ActionWarn:
// not produced by planning; only set after a push by applyRejections.
}
}
pushPlans := classifyPlans(plans, s.cfg.DryRun, &result)
if !s.cfg.DryRun && result.Blocked > 0 {
return result, fmt.Errorf("blocked %d ref update(s); rerun with --force-with-lease (or --force-blind) where appropriate", result.Blocked)
84 unmodified lines
SourceHEAD: s.sourceService.HeadTarget,
}
pushPlans := make([]BranchPlan, 0, len(plans))
relayPlans := make([]BranchPlan, 0, len(plans))
for _, plan := range plans {
switch plan.Action {
case ActionCreate, ActionUpdate, ActionDelete:
if s.cfg.DryRun {
result.Skipped++
continue
}
pushPlans = append(pushPlans, plan)
if plan.Action != ActionDelete {
relayPlans = append(relayPlans, plan)
}
case ActionSkip:
result.Skipped++
case ActionBlock:
result.Blocked++
case ActionWarn:
// not produced by planning; only set after a push by applyRejections.
pushPlans := classifyPlans(plans, s.cfg.DryRun, &result)
relayPlans := make([]BranchPlan, 0, len(pushPlans))
for _, plan := range pushPlans {
if plan.Action != ActionDelete {
relayPlans = append(relayPlans, plan)
}
}
281 unmodified lines
if s.target != nil {
result.TargetURL = s.cfg.Target.URL
result.TargetCaps = gitproto.AdvRefsCaps(s.target.adv)
result.Stats = s.stats.snapshot()
result.Measurement = s.measurementDone()
}
return result
}
Minternal/syncer/syncer.go+30/-38
package gitsync
import ( "github.com/go-git/go-git/v6/plumbing"
"entire.io/entire/git-sync/internal/planner" "entire.io/entire/git-sync/internal/syncer" )
// RefKind classifies a ref in results. type RefKind string
const ( RefKindBranch = RefKind(planner.RefKindBranch) RefKindTag = RefKind(planner.RefKindTag) RefKindOther = RefKind(planner.RefKindOther) )
// Action is the planned or executed per-ref action. type Action string
const ( ActionCreate = Action(planner.ActionCreate) ActionUpdate = Action(planner.ActionUpdate) ActionDelete = Action(planner.ActionDelete) ActionSkip = Action(planner.ActionSkip) ActionBlock = Action(planner.ActionBlock) ActionWarn = Action(planner.ActionWarn) )
// RefResult describes the outcome for a single ref.
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"
}
// RefPlan is a planned ref action; it has the same shape as RefResult. type RefPlan = RefResult
// RefInfo is a ref name and its hash as reported by a probe.
type RefInfo struct {
Name string json:"name"
Hash string json:"hash"
}
// ServiceStats aggregates request counters for one git service.
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"
}
// SideBytes reports transfer volume and timing for one side of a sync.
type SideBytes struct {
Label string json:"label"
Bytes int64 json:"bytes"
Display string json:"display,omitempty"
ActiveNanos int64 json:"activeNanos,omitempty"
IdleNanos int64 json:"idleNanos,omitempty"
}
// Stats aggregates transfer statistics when CollectStats was requested.
type Stats struct {
Enabled bool json:"enabled"
Items map[string]*ServiceStats json:"items"
Sides []SideBytes json:"sides,omitempty"
ElapsedNanos int64 json:"elapsedNanos,omitempty"
}
// Measurement reports process resource usage when measurement was enabled.
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"
}
// ProbeResult is the outcome of a Probe.
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"
SourceHEAD string json:"sourceHead,omitempty"
Stats Stats json:"stats"
Measurement Measurement json:"measurement"
}
// SyncCounts tallies per-ref outcomes of a sync.
type SyncCounts struct {
Applied int json:"applied"
Skipped int json:"skipped"
Blocked int json:"blocked"
Deleted int json:"deleted"
Warned int json:"warned"
}
// BatchSummary reports batched push progress.
type BatchSummary struct {
Enabled bool json:"enabled"
Planned int json:"planned"
Done int json:"done"
}
// ExecutionSummary describes how a sync was executed.
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"
SourceHEAD string json:"sourceHead,omitempty"
Batch BatchSummary json:"batch"
}
// SyncResult is the outcome of a Sync or Replicate.
type SyncResult struct {
Refs []RefResult json:"refs"
Counts SyncCounts json:"counts"
Execution ExecutionSummary json:"execution"
Stats Stats json:"stats"
Measurement Measurement json:"measurement"
}
// PlanResult is the outcome of a Plan; it has the same shape as SyncResult. type PlanResult = SyncResult
func fromStats(stats syncer.Stats) Stats { out := Stats{ Enabled: stats.Enabled, Items: make(map[string]*ServiceStats, len(stats.Items)), ElapsedNanos: stats.ElapsedNanos, } for key, item := range stats.Items { out.Items[key] = &ServiceStats{ Name: item.Name, Requests: item.Requests, RequestBytes: item.RequestBytes, ResponseBytes: item.ResponseBytes, Wants: item.Wants, Haves: item.Haves, Commands: item.Commands, } } if len(stats.Sides) > 0 { out.Sides = make([]SideBytes, 0, len(stats.Sides)) for _, side := range stats.Sides { out.Sides = append(out.Sides, SideBytes{ Label: side.Label, Bytes: side.Bytes, Display: side.Display, ActiveNanos: side.ActiveNanos, IdleNanos: side.IdleNanos, }) } } return out }
func hashString(hash plumbing.Hash) string { if hash.IsZero() { return "" } return hash.String() }
Aresults.go+257
package gitsync
import (
"testing"
"github.com/go-git/go-git/v6/plumbing"
"entire.io/entire/git-sync/internal/planner"
"entire.io/entire/git-sync/internal/syncer"
)
Aresults\_test.go+97
2 unmodified lines
3 4 5 6 7 6 7 8 146 unmodified lines
155 156 157 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
2 unmodified lines
import ( "context" "errors"
"entire.io/entire/git-sync/internal/internalbridge" )
// ProtocolMode controls source-side protocol negotiation. 146 unmodified lines
Policy SyncPolicy json:"policy"
CollectStats bool json:"collectStats"
}
type RefKind = internalbridge.RefKind
const ( RefKindBranch RefKind = internalbridge.RefKindBranch RefKindTag RefKind = internalbridge.RefKindTag RefKindOther RefKind = internalbridge.RefKindOther )
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 ActionWarn Action = internalbridge.ActionWarn )
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
Mtypes.go-34
7 unmodified lines
8 9 10 11 11 12 13 219 unmodified lines
233 234 235 237 236 237 238 239 32 unmodified lines
272 273 274 276 275 276 277 278 2 unmodified lines
281 282 283 285 284 285 286 287 24 unmodified lines
312 313 314 316 315 316 317 318 15 unmodified lines
334 335 336 338 337 338 339 340 17 unmodified lines
358 359 360 362 363 364 365 366 367 368 369 370 371 372 373 374 375 361 377 378 379 380 381 382 383 384 385 386 387 388 362 363 364 365 366 367 368 369 370 371 372 392 373 374 394 375 376 377 378 379 399 380 381
7 unmodified lines
"github.com/go-git/go-git/v6/plumbing"
"entire.io/entire/git-sync" "entire.io/entire/git-sync/internal/internalbridge" "entire.io/entire/git-sync/internal/syncer" "entire.io/entire/git-sync/internal/validation" ) 219 unmodified lines
ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, Progress: req.Options.Progress, ProtocolMode: protocolString(req.Protocol), ProtocolMode: string(req.Protocol), Verbose: req.Options.Verbose, } if req.Target != nil { 32 unmodified lines
ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, Progress: req.Options.Progress, Mode: operationModeString(req.Policy.Mode), Mode: string(req.Policy.Mode), ForceWithLease: req.Policy.ForceWithLease, ForceBlind: req.Policy.ForceBlind, Prune: req.Policy.Prune, 2 unmodified lines
TargetMaxPackBytes: req.Options.TargetMaxPackBytes, TargetMaxRefUpdates: req.Options.TargetMaxRefUpdates, MaterializedMaxObjects: maxObjects, ProtocolMode: protocolString(req.Policy.Protocol), ProtocolMode: string(req.Policy.Protocol), Verbose: req.Options.Verbose, BootstrapStrategy: req.Options.BootstrapStrategy, }, nil 24 unmodified lines
MaxPackBytes: req.Options.MaxPackBytes, TargetMaxPackBytes: req.Options.TargetMaxPackBytes, TargetMaxRefUpdates: req.Options.TargetMaxRefUpdates, ProtocolMode: protocolString(req.Protocol), ProtocolMode: string(req.Protocol), Verbose: req.Options.Verbose, BootstrapStrategy: req.Options.BootstrapStrategy, }, nil 15 unmodified lines
ShowStats: req.Options.CollectStats, MeasureMemory: req.Options.MeasureMemory, Progress: req.Options.Progress, ProtocolMode: protocolString(req.Protocol), ProtocolMode: string(req.Protocol), Verbose: req.Options.Verbose, }, nil } 17 unmodified lines
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, }, ) return syncer.Endpoint{ URL: endpoint.URL, Username: auth.Username, Token: auth.Token, BearerToken: auth.BearerToken, SkipTLSVerify: auth.SkipTLSVerify, FollowInfoRefsRedirect: endpoint.FollowInfoRefsRedirect, } }
func validationMappings(mappings []gitsync.RefMapping) []validation.RefMapping { bridgeMappings := make([]internalbridge.RefMapping, 0, len(mappings)) out := make([]validation.RefMapping, 0, len(mappings)) for _, mapping := range mappings { bridgeMappings = append(bridgeMappings, internalbridge.RefMapping{ out = append(out, validation.RefMapping{ Source: mapping.Source, Target: mapping.Target, }) } return internalbridge.ToValidationMappings(bridgeMappings) return out }
Munstable/client.go+16/-35