Add optional target capability probe · Entire

Add optional target capability probe

77295dbmain·

Soph·3mo ago·5 files·+97 added/-6 removed

Sessions

7aa99a6074b0View transcript

[?
Can you take a look at the go code (wasm) in /Users/soph/Work/entire/devenv/entire-io-worktree1 based a bit on that I wonder if something like this can be build:Codex·GPT-5.4·1 step](/content/gh/entireio/git-sync/session/019d6d29-8cf7-7fe3-adc9-8c3e4d9d5603#timeline-7aa99a6074b0/index.html)

Changes

5

9 unmodified lines
10
11
12
13
13
14
15
16
89 unmodified lines

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

9 unmodified lines

The command surface is:

- `git-sync probe`: inspect a source remote
- `git-sync probe`: inspect a source remote, and optionally a target remote
- `git-sync fetch`: exercise source-side fetch negotiation without pushing
- `git-sync plan`: compute source-to-target ref actions without pushing
- `git-sync sync`: execute the planned changes against the target
89 unmodified lines

<source-url>
```

Probe both source and target remotes to inspect source fetch capabilities and target `receive-pack` capabilities:

```bash
go run ./cmd/git-sync probe \
  --stats \
  <source-url> \
  <target-url>
```

Fetch from a source remote into memory without pushing anywhere:

```bash

MREADME.md+10/-1

117 unmodified lines

118
119
120
121
122
123
124
125
126
127
128
129
130
6 unmodified lines

137
138
139
136
140
141
142
143
144
145
146
111 unmodified lines

258
259
260
254
261
262
263
264

117 unmodified lines

cfg := syncer.Config{}
    fs.StringVar(&cfg.Source.URL, "source-url", "", "source repository URL")
    fs.StringVar(&cfg.Target.URL, "target-url", "", "optional target repository URL")
    fs.StringVar(&cfg.Source.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
    fs.StringVar(&cfg.Target.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
    fs.StringVar(&cfg.Source.Username, "source-username", envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username")
    fs.StringVar(&cfg.Target.Username, "target-username", envOr("GITSYNC_TARGET_USERNAME", "git"), "target basic auth username")
    fs.StringVar(&cfg.Source.BearerToken, "source-bearer-token", envOr("GITSYNC_SOURCE_BEARER_TOKEN", ""), "source bearer token")
    fs.StringVar(&cfg.Target.BearerToken, "target-bearer-token", envOr("GITSYNC_TARGET_BEARER_TOKEN", ""), "target bearer token")
    fs.BoolVar(&cfg.IncludeTags, "tags", false, "include tag ref prefixes in probe")
    fs.StringVar(&cfg.ProtocolMode, "protocol", envOr("GITSYNC_PROTOCOL", "auto"), "protocol mode: auto, v1, or v2")
    fs.BoolVar(&cfg.ShowStats, "stats", false, "print transfer statistics")
6 unmodified lines

if cfg.Source.URL == "" && len(positional) > 0 {
        cfg.Source.URL = positional[0]
    }
    if len(positional) > 1 {
    if cfg.Target.URL == "" && len(positional) > 1 {
        cfg.Target.URL = positional[1]
    }
    if len(positional) > 2 {
        return usageError("too many positional arguments")
    }
    if cfg.Source.URL == "" {
111 unmodified lines

}

func usageError(message string) error {
    usage := "usage:\n  git-sync sync [flags] <source-url> <target-url>\n  git-sync plan [flags] <source-url> <target-url>\n  git-sync probe [flags] <source-url>\n  git-sync fetch [flags] <source-url>\n\nsync/plan flags:\n  --branch main,dev\n  --map main:stable\n  --tags\n  --force\n  --prune\n  --stats\n  --protocol auto|v1|v2\n  --source-token ...\n  --target-token ...\n  --source-username git\n  --target-username git\n  --source-bearer-token ...\n  --target-bearer-token ...\n  -v\n\nprobe flags:\n  --tags\n  --stats\n  --protocol auto|v1|v2\n  --source-token ...\n  --source-username git\n  --source-bearer-token ...\n\nfetch flags:\n  --branch main,dev\n  --tags\n  --stats\n  --protocol auto|v1|v2\n  --have-ref main\n  --have <hash>\n  --source-token ...\n  --source-username git\n  --source-bearer-token ...\n"
    usage := "usage:\n  git-sync sync [flags] <source-url> <target-url>\n  git-sync plan [flags] <source-url> <target-url>\n  git-sync probe [flags] <source-url> [target-url]\n  git-sync fetch [flags] <source-url>\n\nsync/plan flags:\n  --branch main,dev\n  --map main:stable\n  --tags\n  --force\n  --prune\n  --stats\n  --protocol auto|v1|v2\n  --source-token ...\n  --target-token ...\n  --source-username git\n  --target-username git\n  --source-bearer-token ...\n  --target-bearer-token ...\n  -v\n\nprobe flags:\n  --tags\n  --stats\n  --protocol auto|v1|v2\n  --source-token ...\n  --source-username git\n  --source-bearer-token ...\n  --target-token ...\n  --target-username git\n  --target-bearer-token ...\n\nfetch flags:\n  --branch main,dev\n  --tags\n  --stats\n  --protocol auto|v1|v2\n  --have-ref main\n  --have <hash>\n  --source-token ...\n  --source-username git\n  --source-bearer-token ...\n"
    if message == "" {
        return errors.New(strings.TrimSpace(usage))
    }
}

Mcmd/git-sync/main.go+9/-2

263 unmodified lines

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

263 unmodified lines

}

func TestProbe_IntegrationTargetCapabilities(t *testing.T) { sourceRepo, sourceFS := newSourceRepo(t) makeCommits(t, sourceRepo, sourceFS, 1)

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

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

result, err := Probe(context.Background(), Config{ Source: Endpoint{URL: sourceServer.RepoURL()}, Target: Endpoint{URL: targetServer.RepoURL()}, ProtocolMode: protocolModeAuto, ShowStats: true, }) if err != nil { t.Fatalf("probe with target failed: %v", err) } if result.TargetURL != targetServer.RepoURL() { t.Fatalf("unexpected target url %q", result.TargetURL) } if len(result.TargetCaps) == 0 { t.Fatalf("expected target capabilities") } }

func TestFetch_IntegrationProtocolV2Source(t *testing.T) { sourceRepo, sourceFS := newSourceRepo(t) makeCommits(t, sourceRepo, sourceFS, 4)


Minternal/syncer/integration_test.go+31

297 unmodified lines

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

297 unmodified lines

}

func advCapabilities(adv *packp.AdvRefs) []string { if adv == nil || adv.Capabilities == nil { return nil } all := adv.Capabilities.All() items := make([]string, 0, len(all)) for _, cap := range all { values := adv.Capabilities.Get(cap) if len(values) == 0 { items = append(items, string(cap)) continue } for _, value := range values { items = append(items, string(cap)+"="+value) } } sort.Strings(items) return items }

func listSourceRefsV1(ctx context.Context, conn *transportConn) (*packp.AdvRefs, []*plumbing.Reference, error) { adv, err := advertisedRefsV1(ctx, conn, transport.UploadPackServiceName) if err != nil {


Minternal/syncer/protocol_v2.go+20

98 unmodified lines

99 100 101 102 103 104 105 106 107 108 109 110 80 unmodified lines

191 192 193 192 194 195 196 197 198 199 200 201 202 203 197 unmodified lines

401 402 403 396 404 405 406 407 1 unmodified line

409 410 411 404 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

98 unmodified lines

type ProbeResult struct { SourceURL string TargetURL string RequestedMode string Protocol string RefPrefixes []string Capabilities []string TargetCaps []string Refs []RefInfo Stats Stats } 80 unmodified lines

lines = append(lines, "ref-prefixes: "+strings.Join(r.RefPrefixes, ", ")) } if len(r.Capabilities) > 0 { lines = append(lines, "capabilities: "+strings.Join(r.Capabilities, ", ")) lines = append(lines, "source-capabilities: "+strings.Join(r.Capabilities, ", ")) } if r.TargetURL != "" { lines = append(lines, "target: "+r.TargetURL) } if len(r.TargetCaps) > 0 { lines = append(lines, "target-capabilities: "+strings.Join(r.TargetCaps, ", ")) }

lines = append(lines, fmt.Sprintf("refs: %d", len(r.Refs)))

197 unmodified lines

return refInfos[i].Name < refInfos[j].Name }

return ProbeResult{ result := ProbeResult{ SourceURL: cfg.Source.URL, RequestedMode: cfg.ProtocolMode, Protocol: service.protocol, Capabilities: sourceCapabilities(service), Refs: refInfos, Stats: stats.snapshot(), }, nil }

if cfg.Target.URL != "" { targetConn, err := newTransportConn(cfg.Target, "target", stats) if err != nil { return ProbeResult{}, fmt.Errorf("create target transport: %w", err) } targetAdv, err := advertisedRefsV1(ctx, targetConn, transport.ReceivePackServiceName) if err != nil { return ProbeResult{}, fmt.Errorf("list target refs: %w", err) } result.TargetURL = cfg.Target.URL result.TargetCaps = advCapabilities(targetAdv) result.Stats = stats.snapshot() }

return result, nil }

func Fetch(ctx context.Context, cfg Config, haveRefs []string, haveHashes []plumbing.Hash) (FetchResult, error) {


Minternal/syncer/syncer.go+27/-3