From bda82b3b3bd55b611357330897a7517a01402948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 2 Oct 2026 22:12:18 +0200 Subject: [PATCH] test(scenarios): show that squash-merging a pull loses the sync point The squash commit that records a sync point is only reachable through the pull's merge commit. A squash merge of the branch that pulled keeps the content and drops that history, so main still measures against the older sync point: status reports diverged for a purely local change, push is rejected, and after the recovery pull, push sends the remote a duplicate of the upstream commit. main is squash-merged, so any pull done on a feature branch ends up like this. The status test fails until this is fixed; the scenario README shows today's output. --- README.md | 3 + test/scenarios/squash-merged-pull/README.md | 89 ++++++++++++++++++++ test/scenarios/squash-merged-pull/setup.bash | 22 +++++ test/status.bats | 8 ++ 4 files changed, 122 insertions(+) create mode 100644 test/scenarios/squash-merged-pull/README.md create mode 100644 test/scenarios/squash-merged-pull/setup.bash diff --git a/README.md b/README.md index e5f29d6..b350ca9 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,9 @@ More scenarios: subtree inside another is refused. - [`shared-remote-url`](test/scenarios/shared-remote-url/README.md): two subtrees with the same remote URL act like two clones of one repo. +- [`squash-merged-pull`](test/scenarios/squash-merged-pull/README.md): a + known bug. After a pull on a branch that was squash-merged, `status` + reports `diverged` instead of `push`. [How the last sync point is found](docs/last-synced-commit/README.md) explains how the states are worked out and lists known limitations. diff --git a/test/scenarios/squash-merged-pull/README.md b/test/scenarios/squash-merged-pull/README.md new file mode 100644 index 0000000..91645b4 --- /dev/null +++ b/test/scenarios/squash-merged-pull/README.md @@ -0,0 +1,89 @@ +# Scenario: squash-merged-pull + +A known bug: the remote's change was pulled on a feature branch, and the +feature branch was squash-merged into `main`. Since then, the tool +misreads `main`. + +- **Monorepo (`vendor/a`)**: added at `seed` on `main`. On `feature`, + `git subtree pull --squash` brought in `upstream change`. `feature` was + squash-merged into `main` and deleted. Then a local commit on `main` + added `local.txt`. +- **Remote**: `seed`, then `upstream change`. It has never seen + `local.txt`. + +Only the local side changed since `upstream change`, so the right state is +`push`. But the pull's squash commit, which records the sync point, was +only reachable through the pull's merge commit on `feature`. A squash +merge keeps the content and drops that history, so `main` still sees the +sync point from `git subtree add`. Measured against it, both sides changed. + +## Output + +`scenario_squash_merged_pull` in [`setup.bash`](setup.bash) +builds this state. [How scenarios work](../README.md). + + + +`status` reports `diverged` instead of `push`: + +```scrut +$ git subtrees status +ok vendor/a -> $UPSTREAM (diverged) + local.txt | 1 + + 1 file changed, 1 insertion(+) +``` + +`push` is rejected, since the history it rebuilds starts at the old sync +point: + +```scrut +$ git subtrees push +git push using: vendor/a main +To $UPSTREAM + ! [rejected] bd16e2c5cc3967a824b55916f9d66708a8fda514 -> main (non-fast-forward) +error: failed to push some refs to '$UPSTREAM' +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. +!! vendor/a: push failed +!! Failed: vendor/a +[1] +``` + +`pull` recovers: it merges `upstream change` again, which changes no file, +and records a new sync point: + +```scrut +$ git subtrees pull +ok vendor/a fetched +Merge made by the 'ort' strategy. +ok vendor/a: pulled +``` + +But the push after it rebuilds the squash-merged commit too, so the remote +gets `feature (squash-merged)`, which repeats `upstream change`, next to +the original: + +```scrut +$ git subtrees push +git push using: vendor/a main +To $UPSTREAM + 6045a98..90babaf 90babaf10d3e73b0251a158cb615b5c563b07c80 -> main +ok vendor/a: pushed +``` + +```scrut +$ git -C "$UPSTREAM" log --graph --format='%s' main +* Merge commit '00a22274fa7c6e2d9b734b9eefe569b3f8cdd6f8' +|\ +| * upstream change +* | local change +* | feature (squash-merged) +|/ +* seed +``` diff --git a/test/scenarios/squash-merged-pull/setup.bash b/test/scenarios/squash-merged-pull/setup.bash new file mode 100644 index 0000000..9dfe7c4 --- /dev/null +++ b/test/scenarios/squash-merged-pull/setup.bash @@ -0,0 +1,22 @@ +# See README.md in this directory. +scenario_squash_merged_pull() { + local monorepo="$1" upstream="$2" + make_bare_repo "$upstream" + seed_bare_repo "$upstream" "seed" + init_monorepo "$monorepo" + add_subtree "$monorepo" "$upstream" "vendor/a" + seed_bare_repo "$upstream" "upstream change" + ( + cd "$monorepo" + git switch -q -c feature + git fetch -q vendor/a + git subtree pull -q --prefix=vendor/a vendor/a main --squash -m "pull vendor/a" >/dev/null 2>&1 + git switch -q main + git merge -q --squash feature >/dev/null + git commit -q -m "feature (squash-merged)" + git branch -q -D feature + echo "local change" >vendor/a/local.txt + git add vendor/a/local.txt + git commit -q -m "local change" + ) +} diff --git a/test/status.bats b/test/status.bats index 2de9a72..a301546 100644 --- a/test/status.bats +++ b/test/status.bats @@ -10,6 +10,7 @@ setup() { load 'scenarios/feature-branch-unchanged/setup' load 'scenarios/feature-branch-changed/setup' load 'scenarios/diverged-then-pulled/setup' + load 'scenarios/squash-merged-pull/setup' monorepo="$BATS_TEST_TMPDIR/monorepo" upstream="$BATS_TEST_TMPDIR/upstream.git" } @@ -174,3 +175,10 @@ setup() { [ "$status" -eq 1 ] [[ "$output" == *"--base needs a branch name"* ]] } + +@test "status: reports push after a pull on a squash-merged branch" { + scenario_squash_merged_pull "$monorepo" "$upstream" + cd "$monorepo" + run cmd_status + [[ "$output" == *"(push)"* ]] +}