Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 18 additions & 2 deletions tests/test_update_pr_stack.sh
Original file line number Diff line number Diff line change
@@ -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)"
Expand Down Expand Up @@ -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 \
Expand All @@ -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"

Expand Down
18 changes: 10 additions & 8 deletions update-pr-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading