Scenario: squash-merged
The same rebased branch, taken into a protected main:
changes reach it only through pull requests, which are squash-merged.
This page goes there step by step, with a drawing after each.
Output
setup.bash builds the rebased branch:
$ build_scenario scenario_squash_merged
1. Pull on a pull request's branch
A pull request's branch syncs with the upstream branch named like it,
and the upstream has none for take-the-release. --branch patches
names the branch main follows, for this pull only. The folder then
has exactly n's files, so p and q stay dropped.
Without --branch, there's nothing to pull:
$ git switch -q -c take-the-release && git splice pull vendor/a
ok vendor/a fetched
ok vendor/a: upstream has no 'take-the-release' branch -- nothing to pull
$ git splice pull --branch patches -m "c: pull the release" vendor/a
ok vendor/a fetched
ok vendor/a: pulled 28d3fa4
2. Add a local change
A local change goes on top, b:
$ echo "local change" >vendor/a/local.txt && git add vendor/a/local.txt && git commit -q -m "b: local change"
$ git log --format=%s "$(git splice rebuild vendor/a)"
b: local change
other patch
m: patch, reworded upstream
k: seed
3. Squash-merge it
The squash merge puts c and b into one commit on main, s. It records
n, but its folder is n's plus local.txt. The synced commit before
it, a's q, isn't in n's history: the upstream rewrote patches
in between. A rebuild would have to join s with the history before
it, still built on p and q, and the next push would publish them
again. Nothing can rewrite s on a protected main, so status and
push refuse, and say the way out.
A pull request that only pulls goes through: its squash merge has exactly n's files, and the rebuild starts at n. So take the release in a pull request of its own, and the local changes in the next one.
The refusal needs the fetched patches branch's reflog, which has q
before n (fork point). A clone of the monorepo that
first fetched after the rewrite has no q in it. There, a synced
commit missing from n's history looks like a move to another
upstream branch, so the rebuild joins s with p and q, and the
next push publishes them again. git merge does the same after an
upstream rebase
(recovering from upstream rebase).
$ git switch -q main && git merge -q --squash take-the-release >/dev/null && git commit -q -m "s: take the release, and a local change (#1)"
$ git log --format=%s
s: take the release, and a local change (#1)
a: add vendor/a
initial commit
$ git splice status
!! vendor/a: [0-9a-f]{7} records 28d3fa4, which the upstream rewrote from f463f92, and has changes of its own -- commit the folder's changes on top of the upstream branch yourself, push them there, then pull: the rebuild would publish the rewritten-away commits again (regex)
[1]
$ git splice push vendor/a
!! vendor/a: [0-9a-f]{7} records 28d3fa4, which the upstream rewrote from f463f92, and has changes of its own -- commit the folder's changes on top of the upstream branch yourself, push them there, then pull: the rebuild would publish the rewritten-away commits again (regex)
!! Failed: vendor/a
[1]
4. The way out, by hand
Commit the folder's changes on top of patches in a clone of the
upstream, push them as x, and pull: the folder already has x's
files, so the pull, f, only records it, and p and q stay
dropped. On a protected main, f goes through a
pull request of its own too: it only changes .splice.
$ git clone -q -b patches "$UPSTREAM" ../fork && git -C ../fork config user.name "$(git config user.name)" && git -C ../fork config user.email "$(git config user.email)"
The folder's changes since the synced commit, without .splice, on top
of patches:
$ git diff --binary "$(git config --file vendor/a/.splice splice.commit)" HEAD:vendor/a -- . ':!.splice' | git -C ../fork apply
$ git -C ../fork add -A && git -C ../fork commit -q -m "x: local change" && git -C ../fork push -q origin HEAD:patches
$ git splice pull -m "f: record x" vendor/a
ok vendor/a fetched (patches moved 28d3fa4..d56a92e)
ok vendor/a: recorded d56a92e as the synced commit -- the folder already has its files
$ git splice status
On branch main
up to date vendor/a -> patches
$ git log --format=%s "$(git splice rebuild vendor/a)"
x: local change
other patch
m: patch, reworded upstream
k: seed