Cycle
A splice whose upstream, directly or through other splices, splices in the monorepo. Then the monorepo contains a copy of itself, which contains a copy of itself, and so on.
M ──> A
^ │
└─────┘ A's upstream splices M in at i/
A package manager can allow cycles, since it refers to packages and
installs each once. A splice contains its files, though, and a nested
splice is part of the files of the one above it
(nesting). So a cycle is an endless tree of folders. It's
forbidden, and the splice identity
shows it: a splice inside itself has the same id as a splice above
it. Two folders side by side with one id are fine, e.g. a copy.
Not prevented yet. Nothing refuses a cycle
(#86). The plan:
clone, merge and pull refuse to commit a .splice with the id of
one above it. That comes one level late, since the monorepo has no
.splice of its own: here, vendor/a/i/ is a copy of the monorepo, but
nothing above it has its id. An upstream URL that is one of the
monorepo's own remotes would show it one level earlier.
- Monorepo (published as
https://git.example.com/m.git):vendor/a/, a splice of a's upstream. - a's upstream (
https://git.example.com/a.git):seed, thenadd i, which spliced the monorepo in ati/, withvendor/a/in it.
Output
setup.bash builds this state:
$ build_scenario scenario_cycle
Every pull nests one more copy
The first pull brings in a's i/, a copy of the monorepo with
vendor/a/ in it:
$ git splice pull vendor/a
ok vendor/a fetched (main moved bde4164..885fc31)
ok vendor/a: pulled 885fc31
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/i/.splice: id = 0ceff458bb3bb1f7
vendor/a/i/vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/i/vendor/a/ has the id of vendor/a/, above it: the cycle.
Each pull terminates: it reads which splices there are once, before it
merges anything, so a .splice that a merge brings in is pulled only by
the next run. But each run finds a deeper one:
$ git splice pull --all
ok vendor/a fetched
ok vendor/a/i fetched
ok vendor/a/i/vendor/a fetched
ok vendor/a: nothing to pull
ok vendor/a/i: nothing to pull
ok vendor/a/i/vendor/a: pulled 885fc31
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/i/.splice: id = 0ceff458bb3bb1f7
vendor/a/i/vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/i/vendor/a/i/.splice: id = 0ceff458bb3bb1f7
vendor/a/i/vendor/a/i/vendor/a/.splice: id = 224f2c2563d3b6f7
Once the monorepo is pushed, the i/ splices sync with a version that
has every level so far, so a pull nests them all once more. The copies
of the nested .splice files then conflict, as both sides pulled them
to different commits (the first of the
design's limits).
The monorepo pushes to itself
vendor/a/i/ is a splice of the monorepo's own upstream. So its push
sends a copy of the monorepo, from when a's upstream last pulled it, to
the monorepo's main. A change in the copy:
$ echo "note" >vendor/a/i/note.txt && git add vendor/a/i/note.txt && git commit -q -m "note in i"
$ git splice push vendor/a/i
ok vendor/a/i: pushed 298b249 to main
Nobody pushed the monorepo since the setup, so this was a fast-forward.
The monorepo's upstream now has the copy as main, which is neither the
monorepo's history nor its tree:
$ git -C "$UPSTREAM-m" log --format=%s main
note in i
splice: pull vendor/a/i/vendor/a from main at 885fc31
add vendor/a
initial commit
$ git -C "$UPSTREAM-m" ls-tree --name-only main
note.txt
vendor
$ git ls-tree --name-only HEAD
vendor
And the monorepo's own push is rejected:
$ git push -q https://git.example.com/m.git main
To $UPSTREAM-m
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '$UPSTREAM-m'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
[1]