diff --git a/README.md b/README.md index 6e19752..9b6094e 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ This action tries to fix that in a transparent way. Install it, and hopefully th 1. Triggers when a PR is squash merged 2. Finds PRs that were based on the merged branch (direct children only) 3. Creates a synthetic merge commit with three parents (child tip, deleted branch tip, squash commit) to preserve history without re-introducing code -4. Updates the direct child PRs to base on trunk now that the bottom change has landed -5. Pushes updated branches and deletes the merged branch +4. Pushes the updated branches +5. Updates the direct child PRs to base on trunk now that the bottom change has landed +6. Deletes the merged branch **Note:** Indirect descendants (grandchildren, etc.) are intentionally not modified. Their PR diffs remain correct because the merge-base calculation still works—the synthetic merge commit includes the original parent commit as an ancestor. When their direct parent is eventually merged, they become direct children and get updated at that point. diff --git a/tests/test_update_pr_stack.sh b/tests/test_update_pr_stack.sh index 4317f5e..2930aac 100755 --- a/tests/test_update_pr_stack.sh +++ b/tests/test_update_pr_stack.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -eo pipefail # Get script directory (needed for static mock files) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -71,6 +71,8 @@ echo "Simulated Squash commit (via cherry-pick): $SQUASH_COMMIT" echo "Running update-pr-stack.sh..." # The update script sources command_utils.sh itself +# Capture stdout+stderr interleaved so command ordering can be asserted. +RUN_LOG="$TEST_REPO/update_run.log" run_update_pr_stack() { log_cmd \ env \ @@ -79,10 +81,24 @@ run_update_pr_stack() { TARGET_BRANCH=main \ GH="$SCRIPT_DIR/mock_gh.sh" \ GIT="$SCRIPT_DIR/mock_git.sh" \ - $SCRIPT_DIR/../update-pr-stack.sh + $SCRIPT_DIR/../update-pr-stack.sh 2>&1 | tee "$RUN_LOG" } run_update_pr_stack +# The head must be pushed before the PR is retargeted (a failed push must leave +# the PR untouched on its old base), and the merged branch deleted only after +# the retarget (deleting a PR's base branch closes the PR). +push_line=$(grep -n "git push origin feature2" "$RUN_LOG" | head -1 | cut -d: -f1 || true) +edit_line=$(grep -n "pr edit feature2 --base main" "$RUN_LOG" | head -1 | cut -d: -f1 || true) +delete_line=$(grep -n "git push origin :feature1" "$RUN_LOG" | head -1 | cut -d: -f1 || true) +if [[ -n "$push_line" && -n "$edit_line" && -n "$delete_line" \ + && "$push_line" -lt "$edit_line" && "$edit_line" -lt "$delete_line" ]]; then + echo "✅ Ordering: push head, then retarget base, then delete merged branch" +else + echo "❌ Wrong ordering (push=$push_line edit=$edit_line delete=$delete_line)" + exit 1 +fi + # Verify the results cd "$TEST_REPO" diff --git a/update-pr-stack.sh b/update-pr-stack.sh index a2d8ca5..aa08cc7 100755 --- a/update-pr-stack.sh +++ b/update-pr-stack.sh @@ -311,20 +311,22 @@ main() { fi done - # Only update base branches for successfully updated PRs + # Push the heads before retargeting: a failed push then leaves each PR + # intact on its old base, and the head already contains TARGET_BRANCH when + # the base flips to it. + if [[ "${#UPDATED_TARGETS[@]}" -gt 0 ]]; then + log_cmd git push origin "${UPDATED_TARGETS[@]}" + fi + for BRANCH in "${UPDATED_TARGETS[@]}"; do log_cmd gh pr edit "$BRANCH" --base "$TARGET_BRANCH" done - # Push updated branches; only delete merged branch if no conflicts + # Deleting a PR's base branch closes the PR, so this must come after the + # retargets. Keep the branch for reference while conflicted PRs remain. if [[ "${#CONFLICTED_TARGETS[@]}" -eq 0 ]]; then - # No conflicts - safe to delete merged branch - log_cmd git push origin ":$MERGED_BRANCH" "${UPDATED_TARGETS[@]}" + log_cmd git push origin ":$MERGED_BRANCH" else - # Some conflicts - keep merged branch for reference during manual resolution - if [[ "${#UPDATED_TARGETS[@]}" -gt 0 ]]; then - log_cmd git push origin "${UPDATED_TARGETS[@]}" - fi echo "⚠️ Keeping branch '$MERGED_BRANCH' - still referenced by conflicted PRs: ${CONFLICTED_TARGETS[*]}" fi }