Document bootstrap chain ordering with worked example · Entire

Document bootstrap chain ordering with worked example

725f972→main·

Soph·2mo ago·1 file·+74 added/-0 removed

Add a "Bootstrap chain ordering" section to docs/usage.md explaining when --bootstrap-strategy=topo helps. Walks through a small merge graph showing why first-parent fails on a side-branch that doesn't fit and how topo places sub-pack boundaries inside the side branch instead. Also restates the temp-ref non-fast-forward server requirement that's currently only documented in the Go doc-comment, so users hitting the flag from the CLI don't have to dig into the package source to find it.

Sessions

49173870af1dView transcript

Changes

1

36 unmodified lines

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

36 unmodified lines

<target-url>
```

### Bootstrap chain ordering

Batched bootstrap walks a chain of source commits and places sub-pack boundaries (checkpoints) along it, so each push fits under `--target-max-pack-bytes`. Two orderings are available via `--bootstrap-strategy`:

- `first-parent` (default) walks only the first-parent backbone. Each step from one checkpoint to the next is the smallest unit the planner can subdivide.
- `topo` includes every reachable commit in topological order (parents before children, hash-tie-broken for stable resume).

The default is fine for most repos. `topo` is for the case where a single first-parent step pulls in a large side-branch ancestry and cannot be subdivided. Concrete example: assume the target's pack-body limit fits about two commits' worth of objects.

```
     root ── A ──────────────── M ── tip       (first-parent backbone)
              \                /
               S1 ─ S2 ─ S3 ─ S4              (side branch, merged at M)
```

Under `first-parent`, the planner only knows `root → A → M → tip`:

```
checkpoint 1:  root → A   pack {A}                  ✅ fits
checkpoint 2:  A → M      pack {S1, S2, S3, S4, M}  ❌ 5 commits, too big
checkpoint 3:  M → tip    pack {tip}                ✅ fits
```

The `A → M` step is one indivisible unit because no checkpoint can land on `S2` — `S2` isn't on the backbone. The bootstrap fails: the pack exceeds the limit and can't be split further.

Under `topo`, every reachable commit is a candidate checkpoint:

```
chain:  root → A → S1 → S2 → S3 → S4 → M → tip

checkpoint 1:  root → A    pack {A}        ✅
checkpoint 2:  A → S2      pack {S1, S2}   ✅
checkpoint 3:  S2 → S4     pack {S3, S4}   ✅
checkpoint 4:  S4 → M      pack {M}        ✅ tiny — merge content already pushed
checkpoint 5:  M → tip     pack {tip}      ✅
```

Trade-offs:

- **Cost**: more source-side enumeration (chain length grows with every reachable commit, not just the backbone). For a linear repo the two strategies are identical; for a heavily-merged repo `topo` walks every side-branch commit too.
- **Server requirement**: under `topo`, successive checkpoints aren't always in an ancestor-descendant relationship (topological order can interleave parallel branches), so the internal `refs/gitsync/bootstrap/heads/<branch>` temp ref may receive non-fast-forward updates between checkpoints. The temp ref is internal scaffolding — user-visible refs (`refs/heads`, `refs/tags`) only get a single fast-forward update at cutover — but targets that enforce `receive.denyNonFastforwards` across all refs (rather than just `refs/heads`) will reject those temp-ref updates and fail the bootstrap. Major hosts (GitHub, GitLab, Bitbucket, Cloudflare) do not enable this by default.

```bash
git-sync sync \
  --target-max-pack-bytes 100000000 \
  --bootstrap-strategy topo \
  <source-url> \
  <target-url>
```

Add `--measure-memory` to any command to sample elapsed time and Go heap usage:

```bash