Skip to main content

Scenario: a failed push below

The rebuild of a splice records each splice inside it at its own rebuild: the commit that splice's upstream gets with the same push. If that push fails, push --all stops before the splice above; pushed on its own anyway, the upstream above gets the record of a commit the upstream below doesn't have. A clone of the upstream above has everything that commit is made of, so it makes it again, and counts what the upstream below lacks as its own: a plain pull merges it.

Output​

setup.bash builds this state:

$ build_scenario scenario_recursive_pull

The monorepo changes c/, in m1:

$ echo "c in the monorepo" >>vendor/a/b/c/file.txt &&
> git commit -q -am "m1: c in the monorepo"

m1 is in each rebuild above c/, as the copy m1':

The tree: the monorepo at m1, outlined orange; vendor/a/ at a1, then dashed m1', outlined orange; b/ at b1, then the same; c/ at c1, then the same; d/ at d1

Meanwhile c's upstream gets c2:

$ git clone -q https://git.example.com/c.git ../c-work &&
> echo "c upstream" >>../c-work/other.txt &&
> git -C ../c-work add other.txt &&
> git -C ../c-work commit -q -m "c2: c upstream" &&
> git -C ../c-work push -q origin main

push --all stops at c, whose upstream moved on, and doesn't push the splices around it:

$ git splice push --all
To $UPSTREAM-c.git
! [rejected] 30bd30abe9a17ddfcf3e4f0626dedab790bc0e07 -> main (fetch first)
error: failed to push some refs to '$UPSTREAM-c.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
!! vendor/a/b/c: push failed
!! vendor/a/b: not pushed, since vendor/a/b/c below it failed
ok vendor/a/d: nothing to push
!! vendor/a: not pushed, since vendor/a/b/c below it failed
!! Failed: vendor/a/b/c vendor/a/b vendor/a
[1]

Pushed anyway, b's upstream records c at 30bd30a, the copy of m1 in c's rebuild, which c's upstream doesn't have:

$ git splice push vendor/a/b
ok vendor/a/b: pushed e879cb1 to main

b/'s m1' is solid now: its upstream has it. c/'s is still dashed, though b's copy records it: c's push was rejected. vendor/a/ wasn't pushed:

The tree after the push of vendor/a/b: the monorepo at m1; vendor/a/ at a1, then dashed m1'; b/ at b1, then m1', solid: b's upstream has it; c/ at c1, then dashed m1', which c's upstream doesn't have; d/ at d1

$ git -C "$UPSTREAM-b.git" show main:c/.splice | git config --file - splice.commit | cut -c1-7
30bd30a
$ git -C "$UPSTREAM-c.git" log --format="%h %s" main
5f049ad c2: c upstream
8c169b6 c1: c seed

In a clone of b's upstream​

$ git clone -q https://git.example.com/b.git ../b-work &&
> cd ../b-work &&
> git config user.name "Colleague" &&
> git config user.email "colleague@example.com" &&
> git splice fetch c
ok c fetched

Its HEAD is b's commit m1: c in the monorepo, which records c/ at m1', 30bd30a, the copy that never reached c's upstream:

The tree of the clone: its HEAD, the commit m1 of b's upstream; c/ at m1', which the inner upstream doesn't have

It can't fetch 30bd30a, but it makes it again: the commit of b's that recorded it, on the record before it, c1, gives the same hash. Only a copy of its own history comes out the same, so what c's upstream lacks is its own:

$ git splice status
?? c: synced commit 30bd30a isn't on main -- made it again here: 8c169b6 and 1 commit of this branch's own
On branch main

ahead 1, behind 1 c
$ git splice log c
?? c: synced commit 30bd30a isn't on main -- made it again here: 8c169b6 and 1 commit of this branch's own
=== c (main)
< 30bd30a m1: c in the monorepo (Test <test@example.com>)
> 5f049ad c2: c upstream (Test <test@example.com>)

pull --rebase refuses: the commit of b's that made the record is the last sync itself, so the rebase would replay it, and the record in it would meet the pull's. Keeping the pull's record, also when a conflict interrupts the rebase, needs a pull that carries its state from one run to the next (#446):

$ git splice pull --rebase c
ok c fetched
?? c: synced commit 30bd30a isn't on main -- made it again here: 8c169b6 and 1 commit of this branch's own
!! c: synced commit 30bd30a was made again here -- --rebase doesn't replay what's above 8c169b6; pull without --rebase
!! Failed: c
[1]

A plain pull merges instead:

$ git splice pull c
ok c fetched
?? c: synced commit 30bd30a isn't on main -- made it again here: 8c169b6 and 1 commit of this branch's own
ok c: pulled 5f049ad

The pull, pc, records c2, and c/ has m1's line on top, as this branch's own, in its rebuild pc':

The tree of the clone after the pull: at pc; c/ at c2, then dashed pc&#39;, with m1&#39;s line as the clone&#39;s own

$ cat c/file.txt c/other.txt
c1: c seed
c in the monorepo
c upstream

c/'s history in the clone: c2 on c1 in c's upstream, m1' beside it, made again here and on no upstream branch, and the pull's rebuild pc', which joins both:

The clone&#39;s b1, m1 and the pull pc, B at m1 and H at pc; in the lane c/, c&#39;s upstream commits c1 and c2, T and U(H) at c2; m1&#39;, dashed, on c1; and pc&#39;, dashed, R, a merge of m1&#39; and c2