Skip to content
Merged
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
30 changes: 30 additions & 0 deletions tests/test_conflict_resolution_resume.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,35 @@ grep -q -- "--base" "$CALLS" && fail "E: base must NOT be edited"
[[ "$(git -C "$ORIGIN" rev-parse child)" == "$CHILD_BEFORE" ]] || fail "E: child was pushed"
ok "E: malformed marker dies, PR untouched, label kept"

# ---------------------------------------------------------------------------
echo "### Scenario F: recorded base branch is gone -> give up cleanly, no crash"
setup_repo
# Advance main with the squash commit so the child is not already up to date and
# the resume would actually reach the merge step.
git checkout -q main
echo squash > s.txt && git add s.txt && git commit -qm squash
SQUASH2=$(git rev-parse main)
git push -q origin main
git checkout -q child
# The kept parent branch was deleted (auto-delete head branches left enabled, or
# manual deletion). Before the up-front check, the resume tried to merge the
# missing ref, failed `git merge --abort` because no merge was in progress, and
# exited nonzero after re-posting a misleading conflict comment and the label,
# repeating on every push.
git push -q origin ":parent"
MOCK_LABELS="autorestack-needs-conflict-resolution"
PR_BASE="parent" # matches marker -> not a manual retarget
MOCK_COMMENTS_FILE="$WORK/comments.txt"
{ echo "### conflict"; echo; marker parent main "$SQUASH2"; } > "$MOCK_COMMENTS_FILE"
run_resume

grep -q "EXIT=" "$WORK/out.log" && fail "F: script exited nonzero: $(cat "$WORK/out.log")"
grep -q "remove-label autorestack-needs-conflict-resolution" "$CALLS" || fail "F: label not removed"
grep -q -- "add-label" "$CALLS" && fail "F: conflict label must NOT be re-added"
grep -q "gh pr comment" "$CALLS" || fail "F: no explanatory comment posted"
grep -q -- "--base" "$CALLS" && fail "F: base must NOT be edited"
[[ "$(git -C "$ORIGIN" rev-parse child)" == "$CHILD_BEFORE" ]] || fail "F: child was pushed"
ok "F: missing base branch detected, no crash, label removed"

echo
echo "All conflict-resume tests passed 🎉 ($PASS scenarios)"
24 changes: 22 additions & 2 deletions update-pr-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ list_child_prs() {
log_cmd gh pr list --base "$MERGED_BRANCH" --json number,headRefName --jq '.[] | "\(.number) \(.headRefName)"'
}

# A failed git merge does not always leave a merge in progress: when the ref to
# merge does not exist ("not something we can merge"), there is no MERGE_HEAD,
# and `git merge --abort` itself fails ("There is no merge to abort"). Only
# abort when a merge is actually in progress.
abort_merge_if_in_progress() {
if git rev-parse --verify --quiet MERGE_HEAD >/dev/null; then
log_cmd git merge --abort
fi
}

# Args: head branch, base branch, PR number. git commands use the branch; gh
# commands use the number, since a head branch can carry several PRs.
update_direct_target() {
Expand All @@ -167,14 +177,14 @@ update_direct_target() {
if ! log_cmd git merge --no-edit "origin/$MERGED_BRANCH"; then
CONFLICTS+=("origin/$MERGED_BRANCH")
BASE_MERGE_CLEAN=false
log_cmd git merge --abort
abort_merge_if_in_progress
fi
# Only try merging the pre-squash target state if it's not already
# included in the merged branch — otherwise the first merge covers it.
if ! git merge-base --is-ancestor SQUASH_COMMIT~ "origin/$MERGED_BRANCH"; then
if ! log_cmd git merge --no-edit SQUASH_COMMIT~; then
CONFLICTS+=( "$(git rev-parse SQUASH_COMMIT~) # $TARGET_BRANCH just before $MERGED_BRANCH was merged" )
log_cmd git merge --abort
abort_merge_if_in_progress
fi
fi

Expand Down Expand Up @@ -332,6 +342,16 @@ continue_after_resolution() {
return
fi

# Same check for the old base: the resume re-merges origin/$OLD_BASE, so if
# that branch is gone (auto-delete head branches left enabled, or deleted
# manually) the merge can never succeed and the label would re-trigger a
# failing run on every push. Give up cleanly instead.
if ! git rev-parse --verify --quiet "origin/$OLD_BASE" >/dev/null; then
echo "⚠️ Recorded base branch '$OLD_BASE' no longer exists; abandoning resume of $PR_BRANCH."
abandon_resume "$PR_NUMBER" "ℹ️ The branch this PR was based on (\`$OLD_BASE\`) no longer exists, so autorestack stepped back. If this PR still needs its base updated, update its base manually."
return
fi

# The squash-merge run pushed the base merge and asked the user to resolve the
# pre-squash merge, but it never recorded the squash itself. Finish that now:
# re-run the same merge sequence as the squash-merge path. With the user's
Expand Down
Loading