Scenario: diamond
Two apps, each in a repository of its own, have the library spliced in at
c/. The monorepo clones both apps, and the library once more at c/,
to work on it directly. So two paths lead from the monorepo to the
library, one through each app, a diamond, as in a package manager's
dependency graph. The library is nested
in each app.
monorepo
/ | \
a/ | b/
\ | /
c/
Each path has a real copy, so each app's repository still works on its
own. Each copy has its own id,
since each repository ran clone itself, so the copies are independent
and can be at different versions. Here a change to the library is made
once, in the monorepo's c/, and then reaches every copy.
- Library (
$UPSTREAM, reached ashttps://git.example.com/c.git):c seed. - Apps (
$UPSTREAM-a,$UPSTREAM-b, reached ashttps://git.example.com/a.gitandb.git): a seed commit each, thenadd c, which spliced the library in atc/. - Monorepo: one commit, no splice yet.
Output
setup.bash builds this state:
$ build_scenario scenario_diamond
The monorepo brings them together
$ git splice clone https://git.example.com/a.git a
=== a: fetching https://git.example.com/a.git
ok a: cloned 6962bd0 from main
$ git splice clone https://git.example.com/b.git b
=== b: fetching https://git.example.com/b.git
ok b: cloned 8333d5f from main
$ git splice clone https://git.example.com/c.git c
=== c: fetching https://git.example.com/c.git
ok c: cloned 0bfc154 from main
The apps' c/.splice came along, so the monorepo has five splices,
three of them of the library:
$ git splice status
On branch main
up to date a
up to date a/c
up to date b
up to date b/c
up to date c
Each is a splice of its own, with its own id: each app ran clone for
the library, and so did the monorepo
(splice identity). All three share the
library's upstream, and with it one key and one fetch
(upstreams):
$ git grep 'id = ' -- '*/.splice' | tr '\t' ' '
a/.splice: id = dc1f63e7168fb5f0
a/c/.splice: id = f73be57fd033f814
b/.splice: id = a74f3d3a6a024a89
b/c/.splice: id = 7d96e5d8a73e4c4b
c/.splice: id = 27a7630097a94256
By their ids, the three copies of the library are independent splices (splice identity): each repository cloned the library itself. They're one library, at possibly different versions, as in a package manager's diamond: one app may still use an older version than the other. Keeping them at the same version is up to the monorepo, where that's wanted.
$ git splice key a/c
c
$ git splice key b/c
c
$ git splice key c
c
The copies cost little in the repository. Git stores a file by its
content, so the three file.txt are one object. Only the .splice
files differ, in their ids:
$ git ls-tree -r --abbrev=7 --format='%(objectname) %(path)' HEAD -- c a/c b/c
10b4956 a/c/.splice
3436b98 a/c/file.txt
cb3b983 b/c/.splice
3436b98 b/c/file.txt
b6f8f56 c/.splice
3436b98 c/file.txt
The checkout does have three copies of each file. A change to one copy reaches the others only by a push and their pulls, as below.
A change to the library, round trip
The change is made once, in the monorepo's c/, and goes to the
library's upstream:
$ echo "c fix" >>c/file.txt && git commit -q -a -m "c fix"
$ git splice push c
ok c: pushed 2f685c3 to main
Now a/c/ and b/c/ are behind the library's upstream:
$ git splice status
On branch main
behind 1 a/c
behind 1 b/c
up to date a
up to date b
up to date c
In terms of the sync model: R of c/, the
commit its push sent, is the library's upstream branch T now. a/c/ and
b/c/ still have the old content, so their R is the old commit.
log --graph leaves out c/, which is up to date:
$ git splice log --graph c a/c b/c
=== a/c (main)
> 2f685c3 (T) c fix (Test <test@example.com>)
o 0bfc154 (R) c seed (Test <test@example.com>)
=== b/c (main)
> 2f685c3 (T) c fix (Test <test@example.com>)
o 0bfc154 (R) c seed (Test <test@example.com>)
The copies of the library differ now: they're at different versions.
status shows it only indirectly, as two splices behind their upstream.
Comparing the folders, without .splice, shows it directly:
$ git diff --stat HEAD:c HEAD:a/c -- ':!.splice'
file.txt | 1 -
1 file changed, 1 deletion(-)
Each pulls it in, and a/ and b/ are then ahead of their upstreams,
by the new c/ content and c/.splice:
$ git splice pull a/c b/c
ok a/c fetched
ok b/c fetched
ok a/c: pulled 2f685c3
ok b/c: pulled 2f685c3
$ git splice status
On branch main
ahead 1 a
ahead 1 b
up to date a/c
up to date b/c
up to date c
$ git splice push a b
ok a: pushed 7f874c1 to main
ok b: pushed e9b20a7 to main
A clone of one app on its own, e.g. by an outside contributor, has the
library's new version, as plain files, with the .splice that lets
git-splice sync it from there:
$ git clone -q "$UPSTREAM-a" ../a-standalone && ls -A ../a-standalone/c
.splice
file.txt
$ cat ../a-standalone/c/file.txt
c seed
c fix
After the round trip, the three copies have the same files and every
splice is up to date. Their synced commits differ, though: c/ pushed
the change and didn't pull it, and a push doesn't move U(H):
$ git grep 'commit = ' -- c/.splice a/c/.splice b/c/.splice | tr '\t' ' '
a/c/.splice: commit = 2f685c31331f50c0186c1d8a91e398f13f179d83
b/c/.splice: commit = 2f685c31331f50c0186c1d8a91e398f13f179d83
c/.splice: commit = 0bfc154c1c749180f5927393b915bfb27ad635fd
So different synced commits don't mean different versions. Different files do, and the three copies have the same:
$ git diff --quiet HEAD:c HEAD:a/c -- ':!.splice' &&
> git diff --quiet HEAD:c HEAD:b/c -- ':!.splice' &&
> echo "the same files"
the same files
git-splice compares each copy only with its own upstream, not the
copies with each other. Copies with one id,
mirrors, are meant to stay
alike; these three aren't.