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
21 changes: 20 additions & 1 deletion tests/test_conflict_resolution_resume.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ echo "gh $*" >> "$CALLS"
if [[ "$1 $2" == "pr view" ]]; then
case "$*" in
*--json\ labels*) printf '%s\n' "${MOCK_LABELS:-}";;
*--json\ comments*) cat "${MOCK_COMMENTS_FILE:-/dev/null}";;
*--json\ comments*)
# The comments file stands for our own comments only, so the query
# must restrict itself to those.
[[ "$*" == *viewerDidAuthor* ]] || { echo "comments query must filter by viewerDidAuthor" >&2; exit 1; }
cat "${MOCK_COMMENTS_FILE:-/dev/null}";;
*) echo "unhandled pr view: $*" >&2; exit 1;;
esac
elif [[ "$1 $2" == "pr comment" ]]; then
Expand Down Expand Up @@ -167,5 +171,20 @@ grep -q -- "--base" "$CALLS" && fail "D: base must NOT be edited"
[[ "$(git -C "$ORIGIN" rev-parse child)" == "$CHILD_BEFORE" ]] || fail "D: child was pushed"
ok "D: missing target detected, no branch mutation, label removed"

# ---------------------------------------------------------------------------
echo "### Scenario E: malformed state marker -> no mutation"
setup_repo
MOCK_LABELS="autorestack-needs-conflict-resolution"
PR_BASE="parent"
MOCK_COMMENTS_FILE="$WORK/comments.txt"
{ echo "### conflict"; echo; echo '<!-- autorestack-state: base=parent target=main -->'; } > "$MOCK_COMMENTS_FILE"
run_resume

grep -q "remove-label autorestack-needs-conflict-resolution" "$CALLS" || fail "E: label not removed"
grep -q "gh pr comment" "$CALLS" || fail "E: no explanatory comment posted"
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 handled, no branch mutation, label removed"

echo
echo "All conflict-resume tests passed 🎉 ($PASS scenarios)"
18 changes: 14 additions & 4 deletions update-pr-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ format_state_marker() {
"$STATE_MARKER_PREFIX" "$1" "$2" "$3"
}

# Echoes the most recent state-marker line found in our PR comments, or nothing.
# A failed comments fetch aborts the run: treating it as "no marker" would make
# the caller abandon the resume and drop the conflict label for good.
# Echoes the most recent state-marker line found in PR comments, or nothing.
# Only comments posted with our own token count (viewerDidAuthor): anyone can
# comment a marker, and acting on a forged one would merge and push an
# attacker-chosen commit. A failed comments fetch aborts the run: treating it
# as "no marker" would make the caller abandon the resume and drop the
# conflict label for good.
read_state_marker() {
local PR_NUMBER="$1"
local BODIES
if ! BODIES=$(gh pr view "$PR_NUMBER" --json comments --jq '.comments[].body'); then
if ! BODIES=$(gh pr view "$PR_NUMBER" --json comments \
--jq '.comments[] | select(.viewerDidAuthor) | .body'); then
echo "Error: could not read comments of PR #$PR_NUMBER" >&2
exit 1
fi
Expand Down Expand Up @@ -252,6 +256,12 @@ continue_after_resolution() {
read -r OLD_BASE NEW_TARGET SQUASH_HASH < <(parse_state_marker "$MARKER")
echo "Recorded state: base=$OLD_BASE target=$NEW_TARGET squash=$SQUASH_HASH"

if [[ -z "$OLD_BASE" || -z "$NEW_TARGET" || -z "$SQUASH_HASH" ]]; then
echo "⚠️ State marker on $PR_BRANCH is malformed; cannot resume safely. Removing the label."
abandon_resume "$PR_NUMBER" "ℹ️ autorestack found a malformed state marker on this PR, so it will not update the stack automatically. If this PR still needs its base updated, update its base manually."
return
fi

# The PR was left based on the merged parent branch. If the payload shows a
# different base, a human retargeted the PR; the recorded target is stale,
# so step back before any mutation.
Expand Down
Loading