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
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
bash tests/test_rebase_workflow.sh
bash tests/test_mixed_workflows.sh
bash tests/test_conflict_resolution_resume.sh
bash tests/test_merge_commit_skip.sh

e2e-tests:
name: E2E Tests
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:

### Notes

* Currently only supports squash merges
* Currently only supports squash merges; PRs merged with a merge commit are detected and skipped (history isn't rewritten, so stacked PRs stay valid as-is)
* If a merge hits a conflict, you'll need to resolve it manually; pushing the resolution automatically continues the stack update
* Very large stacks might hit GitHub rate limits

Expand Down
69 changes: 69 additions & 0 deletions tests/test_merge_commit_skip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash
#
# A PR merged with a merge commit (not squashed) must be left alone: history is
# not rewritten, so stacked children stay valid and need no synthetic merge.

set -ueo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../command_utils.sh"

simulate_push() {
log_cmd git update-ref "refs/remotes/origin/$1" "$1"
}

TEST_REPO=$(mktemp -d)
cd "$TEST_REPO"
echo "Created test repo at $TEST_REPO"

log_cmd git init -b main
log_cmd git config user.email "test@example.com"
log_cmd git config user.name "Test User"

echo "line" > file.txt
log_cmd git add file.txt
log_cmd git commit -m "Initial commit"
simulate_push main

log_cmd git checkout -b feature1
echo "f1" >> file.txt
log_cmd git add file.txt
log_cmd git commit -m "Add feature 1"
simulate_push feature1

log_cmd git checkout -b feature2
echo "f2" >> file.txt
log_cmd git add file.txt
log_cmd git commit -m "Add feature 2"
simulate_push feature2

# Merge feature1 into main with a real merge commit
log_cmd git checkout main
log_cmd git merge --no-ff --no-edit feature1
MERGE_COMMIT=$(git rev-parse HEAD)
simulate_push main

FEATURE2_BEFORE=$(git rev-parse feature2)

OUT=$(env \
SQUASH_COMMIT="$MERGE_COMMIT" \
MERGED_BRANCH=feature1 \
TARGET_BRANCH=main \
GH="$SCRIPT_DIR/mock_gh.sh" \
GIT="$SCRIPT_DIR/mock_git.sh" \
"$SCRIPT_DIR/../update-pr-stack.sh" 2>&1)
echo "$OUT"

if ! grep -q "merged with a merge commit" <<<"$OUT"; then
echo "❌ Expected the merge-commit skip message"
exit 1
fi
if grep -q "pr edit" <<<"$OUT"; then
echo "❌ No PR must be retargeted"
exit 1
fi
if [[ "$(git rev-parse feature2)" != "$FEATURE2_BEFORE" ]]; then
echo "❌ feature2 must not be modified"
exit 1
fi
echo "✅ Merge-commit merge skipped, stack untouched"
6 changes: 6 additions & 0 deletions update-pr-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ main() {

log_cmd git update-ref SQUASH_COMMIT "$SQUASH_COMMIT"

# A merge-commit merge does not rewrite history; stacked PRs stay valid.
if git rev-parse --verify --quiet SQUASH_COMMIT^2 >/dev/null; then
echo "✓ '$MERGED_BRANCH' was merged with a merge commit, not squashed; nothing to do"
return 0
fi

# Find all PRs directly targeting the merged PR's head
INITIAL_NUMBERS=()
INITIAL_TARGETS=()
Expand Down
Loading