|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# Test mixed merge/rebase workflows where feature2 is NOT fully up to date |
| 4 | +# with feature1 at the time of squash-merge. |
| 5 | +# |
| 6 | +# Scenarios: |
| 7 | +# A) feature2 rebased onto an intermediate state of feature1, then feature1 |
| 8 | +# got more commits that feature2 never picked up. |
| 9 | +# B) feature2 merged feature1 once, then rebased onto a later feature1 state, |
| 10 | +# but feature1 got yet more commits after that. |
| 11 | + |
| 12 | +set -e |
| 13 | + |
| 14 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 15 | +source "$SCRIPT_DIR/../command_utils.sh" |
| 16 | + |
| 17 | +simulate_push() { |
| 18 | + log_cmd git update-ref "refs/remotes/origin/$1" "$1" |
| 19 | +} |
| 20 | + |
| 21 | +# Extract just the +/- lines from a diff (ignoring headers and context) |
| 22 | +diff_changes() { |
| 23 | + grep '^[+-]' | grep -v '^[+-][+-][+-]' |
| 24 | +} |
| 25 | + |
| 26 | +run_scenario() { |
| 27 | + local SCENARIO_NAME="$1" |
| 28 | + echo "" |
| 29 | + echo "============================================" |
| 30 | + echo " Scenario $SCENARIO_NAME" |
| 31 | + echo "============================================" |
| 32 | + echo "" |
| 33 | + |
| 34 | + TEST_REPO=$(mktemp -d) |
| 35 | + cd "$TEST_REPO" |
| 36 | + |
| 37 | + log_cmd git init -b main |
| 38 | + log_cmd git config user.email "test@example.com" |
| 39 | + log_cmd git config user.name "Test User" |
| 40 | + |
| 41 | + # 10-line file to keep changes well-separated |
| 42 | + for i in $(seq 1 10); do echo "line $i" >> file.txt; done |
| 43 | + log_cmd git add file.txt |
| 44 | + log_cmd git commit -m "Initial commit" |
| 45 | + simulate_push main |
| 46 | +} |
| 47 | + |
| 48 | +assert_diff_is() { |
| 49 | + local LABEL="$1" |
| 50 | + local EXPECTED="$2" |
| 51 | + local ACTUAL |
| 52 | + ACTUAL=$(log_cmd git diff main...feature2 | diff_changes) |
| 53 | + if [[ "$ACTUAL" == "$EXPECTED" ]]; then |
| 54 | + echo "✅ $LABEL" |
| 55 | + else |
| 56 | + echo "❌ $LABEL" |
| 57 | + echo "Expected changed lines:" |
| 58 | + echo "$EXPECTED" |
| 59 | + echo "Actual changed lines:" |
| 60 | + echo "$ACTUAL" |
| 61 | + exit 1 |
| 62 | + fi |
| 63 | +} |
| 64 | + |
| 65 | +assert_idempotent() { |
| 66 | + local BEFORE AFTER |
| 67 | + BEFORE=$(git rev-parse feature2) |
| 68 | + run_update_pr_stack |
| 69 | + cd "$TEST_REPO" |
| 70 | + AFTER=$(git rev-parse feature2) |
| 71 | + if [[ "$BEFORE" == "$AFTER" ]]; then |
| 72 | + echo "✅ Idempotent" |
| 73 | + else |
| 74 | + echo "❌ Idempotence failed" |
| 75 | + exit 1 |
| 76 | + fi |
| 77 | +} |
| 78 | + |
| 79 | +squash_merge_feature1() { |
| 80 | + log_cmd git checkout main |
| 81 | + log_cmd git merge --squash feature1 |
| 82 | + log_cmd git commit -m "Squash merge feature1" |
| 83 | + SQUASH_COMMIT=$(git rev-parse HEAD) |
| 84 | + simulate_push main |
| 85 | + echo "Squash commit: $SQUASH_COMMIT" |
| 86 | +} |
| 87 | + |
| 88 | +run_update_pr_stack() { |
| 89 | + log_cmd \ |
| 90 | + env \ |
| 91 | + SQUASH_COMMIT=$SQUASH_COMMIT \ |
| 92 | + MERGED_BRANCH=feature1 \ |
| 93 | + TARGET_BRANCH=main \ |
| 94 | + GH="$SCRIPT_DIR/mock_gh.sh" \ |
| 95 | + GIT="$SCRIPT_DIR/mock_git.sh" \ |
| 96 | + $SCRIPT_DIR/../update-pr-stack.sh |
| 97 | +} |
| 98 | + |
| 99 | +######################################################################## |
| 100 | +# Scenario A: rebase onto intermediate feature1, then feature1 advances |
| 101 | +######################################################################## |
| 102 | +run_scenario "A: rebased onto intermediate feature1, then feature1 advances" |
| 103 | + |
| 104 | +# feature1: two commits (lines 2 and 3) |
| 105 | +log_cmd git checkout -b feature1 |
| 106 | +sed -i '2s/.*/f1 commit 1/' file.txt |
| 107 | +log_cmd git add file.txt |
| 108 | +log_cmd git commit -m "f1: commit 1" |
| 109 | +simulate_push feature1 |
| 110 | + |
| 111 | +# feature2: branched from feature1 after commit 1, changes line 9 |
| 112 | +log_cmd git checkout -b feature2 |
| 113 | +sed -i '9s/.*/f2 content/' file.txt |
| 114 | +log_cmd git add file.txt |
| 115 | +log_cmd git commit -m "f2: change line 9" |
| 116 | +simulate_push feature2 |
| 117 | + |
| 118 | +# feature1 gets commit 2 |
| 119 | +log_cmd git checkout feature1 |
| 120 | +sed -i '3s/.*/f1 commit 2/' file.txt |
| 121 | +log_cmd git add file.txt |
| 122 | +log_cmd git commit -m "f1: commit 2" |
| 123 | +simulate_push feature1 |
| 124 | + |
| 125 | +# Developer rebases feature2 onto feature1 (picks up commit 2) |
| 126 | +log_cmd git checkout feature2 |
| 127 | +log_cmd git rebase feature1 |
| 128 | +simulate_push feature2 |
| 129 | + |
| 130 | +# feature1 gets commit 3 — feature2 does NOT pick this up |
| 131 | +log_cmd git checkout feature1 |
| 132 | +sed -i '4s/.*/f1 commit 3/' file.txt |
| 133 | +log_cmd git add file.txt |
| 134 | +log_cmd git commit -m "f1: commit 3" |
| 135 | +simulate_push feature1 |
| 136 | + |
| 137 | +echo "" |
| 138 | +echo "=== Graph before squash-merge ===" |
| 139 | +log_cmd git log --graph --oneline --all |
| 140 | +echo "" |
| 141 | + |
| 142 | +squash_merge_feature1 |
| 143 | +run_update_pr_stack |
| 144 | +cd "$TEST_REPO" |
| 145 | + |
| 146 | +# The diff should show only feature2's unique change |
| 147 | +assert_diff_is "Scenario A: diff shows only f2's change" "$(cat <<'EOF' |
| 148 | +-line 9 |
| 149 | ++f2 content |
| 150 | +EOF |
| 151 | +)" |
| 152 | +assert_idempotent |
| 153 | + |
| 154 | +######################################################################## |
| 155 | +# Scenario B: merge then rebase, then feature1 advances again |
| 156 | +######################################################################## |
| 157 | +run_scenario "B: merge then rebase, then feature1 advances again" |
| 158 | + |
| 159 | +# feature1: commit 1 (line 2) |
| 160 | +log_cmd git checkout -b feature1 |
| 161 | +sed -i '2s/.*/f1 commit 1/' file.txt |
| 162 | +log_cmd git add file.txt |
| 163 | +log_cmd git commit -m "f1: commit 1" |
| 164 | +simulate_push feature1 |
| 165 | + |
| 166 | +# feature2 from feature1, changes line 9 |
| 167 | +log_cmd git checkout -b feature2 |
| 168 | +sed -i '9s/.*/f2 content/' file.txt |
| 169 | +log_cmd git add file.txt |
| 170 | +log_cmd git commit -m "f2: change line 9" |
| 171 | +simulate_push feature2 |
| 172 | + |
| 173 | +# feature1 gets commit 2 |
| 174 | +log_cmd git checkout feature1 |
| 175 | +sed -i '3s/.*/f1 commit 2/' file.txt |
| 176 | +log_cmd git add file.txt |
| 177 | +log_cmd git commit -m "f1: commit 2" |
| 178 | +simulate_push feature1 |
| 179 | + |
| 180 | +# Developer merges feature1 into feature2 |
| 181 | +log_cmd git checkout feature2 |
| 182 | +log_cmd git merge --no-edit feature1 |
| 183 | +simulate_push feature2 |
| 184 | + |
| 185 | +# feature1 gets commit 3 |
| 186 | +log_cmd git checkout feature1 |
| 187 | +sed -i '4s/.*/f1 commit 3/' file.txt |
| 188 | +log_cmd git add file.txt |
| 189 | +log_cmd git commit -m "f1: commit 3" |
| 190 | +simulate_push feature1 |
| 191 | + |
| 192 | +# Developer rebases feature2 onto latest feature1 (rewrite away the merge) |
| 193 | +log_cmd git checkout feature2 |
| 194 | +log_cmd git rebase feature1 |
| 195 | +simulate_push feature2 |
| 196 | + |
| 197 | +# feature1 gets commit 4 — feature2 does NOT pick this up |
| 198 | +log_cmd git checkout feature1 |
| 199 | +sed -i '5s/.*/f1 commit 4/' file.txt |
| 200 | +log_cmd git add file.txt |
| 201 | +log_cmd git commit -m "f1: commit 4" |
| 202 | +simulate_push feature1 |
| 203 | + |
| 204 | +echo "" |
| 205 | +echo "=== Graph before squash-merge ===" |
| 206 | +log_cmd git log --graph --oneline --all |
| 207 | +echo "" |
| 208 | + |
| 209 | +squash_merge_feature1 |
| 210 | +run_update_pr_stack |
| 211 | +cd "$TEST_REPO" |
| 212 | + |
| 213 | +assert_diff_is "Scenario B: diff shows only f2's change" "$(cat <<'EOF' |
| 214 | +-line 9 |
| 215 | ++f2 content |
| 216 | +EOF |
| 217 | +)" |
| 218 | +assert_idempotent |
| 219 | + |
| 220 | +######################################################################## |
| 221 | +echo "" |
| 222 | +echo "All mixed-workflow tests passed! 🎉" |
0 commit comments