Skip to main content

Splice identity

What a splice's id names, what it's for, and what it means when two folders have the same one: they're meant to be mirrors.

  • Upstream ($UPSTREAM, reached as https://git.example.com/a.git): seed.
  • A fork of it ($UPSTREAM-fork, reached as https://git.example.com/a-fork.git): seed, then fork fix.
  • Monorepo: one commit, no splice yet.

What an id is​

clone and init write an id into the .splice they create. The id names that mount, the act of making this folder a splice of an upstream. It names neither a folder nor an upstream:

What happensThe id
clone or initis new, even for an upstream another folder already splices
git mv of the foldermoves along with .splice
a new URL, or another upstreamstays: the upstream moved, or got a second home
cp -r of the folder, or a nested .splice reaching the monorepo twiceis copied: now two folders have it

A splice keeps its id however its folder or its upstream changes. The id is derived from the path, HEAD, the upstream's URL and the commit spliced in when the splice is made, not random, so the same command on the same commit writes the same file (the design).

So the id says which folders are meant to stay alike:

Different idsThe same id
Meaningindependent splices, even of one upstream, e.g. a library at two versionsone splice in two places: mirrors, meant to have the same files
Different contentfinedrift, to resolve
Made byclone or inita copy, or one nested splice reached twice

Mirrors can't be enforced, only reported, and git-splice doesn't report them yet: mirrors explains why, and what you can do meanwhile.

Output​

setup.bash builds this state:

$ build_scenario scenario_splice_identity

Each clone is a new splice​

$ git splice clone https://git.example.com/a.git vendor/a
=== vendor/a: fetching https://git.example.com/a.git
ok vendor/a: cloned bde4164 from main
$ cat vendor/a/.splice | tr '\t' ' '
[splice]
id = 224f2c2563d3b6f7
commit = bde416459fbcc09c9b585f3b65a94cab3f68bfcd
[upstream "origin"]
url = https://git.example.com/a.git

A second clone of the same upstream, at the same commit, is another splice, with another id:

$ git splice clone https://git.example.com/a.git libs/a
=== libs/a: fetching https://git.example.com/a.git
ok libs/a: cloned bde4164 from main
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
libs/a/.splice: id = a841bb3c200928e9
vendor/a/.splice: id = 224f2c2563d3b6f7

What the two have in common is their upstream: they share its fetched refs, and they end up at different versions when only one of them is pulled (upstreams).

A move keeps the id​

$ mkdir third_party && git mv libs/a third_party/a && git commit -q -m "move libs/a"
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
third_party/a/.splice: id = a841bb3c200928e9
vendor/a/.splice: id = 224f2c2563d3b6f7

The rebuild doesn't follow the move yet: it reads the folder's history under its current path only, so the move starts it there, and commits from before the move that weren't pushed yet reach upstream folded into it. Push before moving (moved-with-unpushed-commits, #4). Where a .splice is new at a path, or has another id, the splice's history starts: its mount. That's what keeps two splices that swap paths apart (swapped-splices).

A copy shares the id​

A plain copy, e.g. to give a second app its own copy of the library, copies .splice too:

$ cp -r vendor/a vendor/a-copy && git add vendor/a-copy && git commit -q -m "copy vendor/a"
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
third_party/a/.splice: id = a841bb3c200928e9
vendor/a-copy/.splice: id = 224f2c2563d3b6f7
vendor/a/.splice: id = 224f2c2563d3b6f7

The copy declares a mirror of vendor/a/. For a copy that should go its own way, git splice clone gives the same files, at the same synced commit, with an id of its own. Every command works on a splice by its path, and nothing compares ids across paths, so each copy is pulled and pushed on its own. Both are splices of their own, and the copy's history starts with the copy, its mount:

$ git splice status
On branch main

up to date third_party/a
up to date vendor/a-copy
up to date vendor/a

A copy isn't the only way to get the same id twice. If two upstreams contain the same nested splice, e.g. because one of them splices in the other, both bring its .splice into the monorepo, each in its own folder. Two upstreams that each cloned the library themselves give it two ids, as in the diamond.

A mirror drifts​

Nothing keeps mirrors alike: each is pulled on its own. The upstream moves on, and only vendor/a/ is pulled:

$ seed_bare_repo "$UPSTREAM" "upstream change"
$ git splice pull vendor/a
ok vendor/a fetched (main moved bde4164..6045a98)
ok vendor/a: pulled 6045a98
$ git grep -e 'id = ' -e 'commit = ' -- vendor/a/.splice vendor/a-copy/.splice | tr '\t' ' '
vendor/a-copy/.splice: id = 224f2c2563d3b6f7
vendor/a-copy/.splice: commit = bde416459fbcc09c9b585f3b65a94cab3f68bfcd
vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/.splice: commit = 6045a986b5afb475cd0af0caf6bb622db918a25b
$ git splice status
On branch main

behind 1 third_party/a
behind 1 vendor/a-copy

up to date vendor/a

vendor/a-copy/ is meant to mirror vendor/a/, but has other files now: it has drifted. status only says it's behind its upstream, just as third_party/a/, which is independent and may stay behind. A report of mirrors that differ is possible, but not there yet (mirrors).

Until then, you can compare mirrors yourself. Their folders, without .splice, should have the same tree, so git diff between the two folders in HEAD shows what's different:

$ git diff --stat HEAD:vendor/a HEAD:vendor/a-copy -- ':!.splice'
file.txt | 1 -
1 file changed, 1 deletion(-)

Pulling the copy resolves the drift:

$ git splice pull vendor/a-copy
ok vendor/a-copy fetched
ok vendor/a-copy: pulled 6045a98
$ git diff --quiet HEAD:vendor/a HEAD:vendor/a-copy -- ':!.splice' && echo "mirrors"
mirrors

Different synced commits alone aren't drift: a push doesn't move U(H), so a mirror that pushed a change has an older U(H) than one that pulled it, with the same files (diamond).

Mirrors that swap paths​

Mirrors are one splice, so the rebuild can't tell them apart either. Two splices that swap paths each start a new history with the swap (swapped-splices); two mirrors don't. Each path keeps its own history, whichever mirror's files it has now.

A commit in each mirror, not pushed yet:

$ echo "not for upstream" >vendor/a/secret.txt && git add vendor/a && git commit -q -m "a secret"
$ echo "copy change" >>vendor/a-copy/file.txt && git commit -q -am "copy change"

Then they swap paths:

$ git mv vendor/a tmp && git mv vendor/a-copy vendor/a && git mv tmp vendor/a-copy && git commit -q -m "swap the mirrors"

vendor/a/ has the copy's files now, without the secret. But its history is still the one of the path: a push sends a secret, then the swap, which removes it again and brings in the copy's change.

$ git splice log vendor/a
=== vendor/a (main)
< c5e6540 swap the mirrors (Test <test@example.com>)
< 920226f a secret (Test <test@example.com>)

Nothing in the monorepo tells this swap apart from edits to one splice that make its files look like the other mirror's. Keeping mirrors alike avoids it: push one, and pull the others right away (mirrors).

A fork is a splice of its own​

To vendor a fork next to the original, clone it:

$ git splice clone https://git.example.com/a-fork.git vendor/a-fork
=== vendor/a-fork: fetching https://git.example.com/a-fork.git
ok vendor/a-fork: cloned c09993d from main
$ git grep -e 'id = ' -e 'url = ' -- vendor/a/.splice vendor/a-fork/.splice | tr '\t' ' '
vendor/a-fork/.splice: id = c9c8218874380302
vendor/a-fork/.splice: url = https://git.example.com/a-fork.git
vendor/a/.splice: id = 224f2c2563d3b6f7
vendor/a/.splice: url = https://git.example.com/a.git

A copy of vendor/a/ whose URL is changed to the fork's would keep the id, and so declare a mirror of something that isn't one. The id can't tell that from an upstream that moved to a new URL, which keeps it on purpose, and nothing warns about it yet.

Where the id must not repeat​

A splice can't be inside itself: a .splice with the same id as one above it means an upstream splices in, directly or not, something that contains it. Every pull then nests one more copy (cycle). Nothing refuses it yet (#86).