docs: add PR conflict resolution guide and helper script (#11060)

Add a "Resolving Conflicts in GitHub PRs" section to AGENTS.md with
step-by-step instructions for force-fetching refs, rebasing, resolving
lockfile conflicts, and verifying mergeability.

Add shell/resolve-pr-conflicts.sh that automates the workflow: fetches
metadata, force-updates the base ref, rebases, auto-resolves lockfile
conflicts via pnpm install, force-pushes, and verifies the result.
This commit is contained in:
Zoltan Kochan
2026-03-22 13:22:11 +01:00
committed by GitHub
parent d5be835735
commit f7bb668100
3 changed files with 195 additions and 1 deletions

View File

@@ -187,6 +187,16 @@ try {
}
```
## Resolving Conflicts in GitHub PRs
Use `shell/resolve-pr-conflicts.sh` to resolve PR conflicts:
```bash
./shell/resolve-pr-conflicts.sh <PR_NUMBER>
```
The script force-fetches the base branch (avoiding stale refs), rebases, auto-resolves `pnpm-lock.yaml` conflicts via `pnpm install`, force-pushes, and verifies GitHub sees the PR as mergeable. For non-lockfile conflicts it will pause and list the files that need manual resolution.
## Key Configuration Files
- `pnpm-workspace.yaml`: Defines the workspace structure.

184
shell/resolve-pr-conflicts.sh Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env bash
# Resolves merge conflicts for a GitHub PR by rebasing onto the latest base branch.
#
# Usage:
# ./shell/resolve-pr-conflicts.sh <PR_NUMBER> # full run
# ./shell/resolve-pr-conflicts.sh <PR_NUMBER> --continue # finish after manual resolution
#
# Prerequisites:
# - gh CLI authenticated with access to pnpm/pnpm
# - "origin" remote must point to pnpm/pnpm (not a fork)
# - You must be on the PR's head branch (the script will checkout via gh if not)
#
# This script:
# 1. Checks out the PR branch if needed
# 2. Force-fetches the base branch to avoid stale refs
# 3. Rebases the current branch onto it
# 4. Auto-resolves pnpm-lock.yaml conflicts via lockfile-only install
# 5. For other conflicts, exits with the list of files needing manual resolution
# 6. After manual resolution, call with --continue to finish (rebase continue + push + verify)
set -euo pipefail
PR_NUMBER="${1:?Usage: $0 <PR_NUMBER> [--continue]}"
CONTINUE_MODE="${2:-}"
REPO="pnpm/pnpm"
# Verify origin points to pnpm/pnpm (strict match for HTTPS or SSH)
ORIGIN_URL=$(git remote get-url origin 2>/dev/null || echo "")
if [[ ! "$ORIGIN_URL" =~ github\.com[:/]pnpm/pnpm(\.git)?$ ]]; then
echo "ERROR: 'origin' remote does not point to pnpm/pnpm."
echo " Current origin: $ORIGIN_URL"
echo " Expected: https://github.com/pnpm/pnpm.git (or git@github.com:pnpm/pnpm.git)"
exit 1
fi
# Get PR metadata
echo "Fetching PR #${PR_NUMBER} metadata..."
HEAD_OWNER=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRepositoryOwner --jq .headRepositoryOwner.login)
HEAD_BRANCH=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefName --jq .headRefName)
# Ensure we're on the PR branch (do this before determining push remote so gh can set up fork remotes)
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
if [ "$CURRENT_BRANCH" != "$HEAD_BRANCH" ]; then
echo "Not on PR branch ($CURRENT_BRANCH != $HEAD_BRANCH). Checking out via gh..."
gh pr checkout "$PR_NUMBER"
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT_BRANCH" != "$HEAD_BRANCH" ]; then
echo "ERROR: Failed to checkout PR branch. Current branch: $CURRENT_BRANCH"
exit 1
fi
fi
# Determine push remote (after checkout, since gh pr checkout may add the fork remote)
REMOTE="origin"
if [ "$HEAD_OWNER" != "pnpm" ]; then
if git remote get-url "$HEAD_OWNER" &>/dev/null; then
REMOTE="$HEAD_OWNER"
else
# Try to auto-add the fork remote from the PR's clone URL
FORK_URL="https://github.com/$HEAD_OWNER/pnpm.git"
echo "Adding remote '$HEAD_OWNER' -> $FORK_URL"
git remote add "$HEAD_OWNER" "$FORK_URL"
REMOTE="$HEAD_OWNER"
fi
fi
# Helper: regenerate lockfile without running lifecycle scripts
regenerate_lockfile() {
echo "Regenerating pnpm-lock.yaml..."
pnpm install --lockfile-only --no-frozen-lockfile --ignore-scripts
git add pnpm-lock.yaml
}
# --continue mode: finish a previously paused rebase, then push
if [ "$CONTINUE_MODE" = "--continue" ]; then
echo "Continuing rebase..."
# Regenerate lockfile if it was among the conflicted files
if git diff --name-only --diff-filter=U 2>/dev/null | grep -q "pnpm-lock.yaml"; then
echo "Auto-resolving pnpm-lock.yaml..."
git checkout --ours pnpm-lock.yaml
git add pnpm-lock.yaml
regenerate_lockfile
fi
if ! GIT_EDITOR=true git rebase --continue; then
echo "ERROR: 'git rebase --continue' failed. Resolve remaining conflicts and re-run with --continue."
exit 1
fi
echo "Force-pushing to $REMOTE/$HEAD_BRANCH..."
git push "$REMOTE" "HEAD:$HEAD_BRANCH" --force-with-lease
echo "Waiting for GitHub to update mergeability..."
sleep 10
MERGEABLE=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json mergeable --jq .mergeable)
echo "PR mergeable: $MERGEABLE"
[ "$MERGEABLE" = "MERGEABLE" ] && echo "Conflicts resolved successfully!" || echo "WARNING: GitHub still reports conflicts. Re-run this script."
exit 0
fi
# Full mode: fetch, rebase, resolve
BASE_BRANCH=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json baseRefName --jq .baseRefName)
echo "Base: $BASE_BRANCH Head: $HEAD_OWNER:$HEAD_BRANCH"
# Force-update the base branch ref (use + prefix to force non-fast-forward updates)
echo "Force-fetching origin/$BASE_BRANCH..."
git fetch origin "+refs/heads/$BASE_BRANCH:refs/remotes/origin/$BASE_BRANCH"
# Verify against GitHub
GITHUB_SHA=$(gh api "repos/$REPO/branches/$BASE_BRANCH" --jq '.commit.sha')
LOCAL_SHA=$(git rev-parse "origin/$BASE_BRANCH")
if [ "$GITHUB_SHA" != "$LOCAL_SHA" ]; then
echo "ERROR: Local origin/$BASE_BRANCH ($LOCAL_SHA) doesn't match GitHub ($GITHUB_SHA)"
exit 1
fi
echo "Base branch ref verified: $LOCAL_SHA"
# Rebase
echo "Rebasing onto origin/$BASE_BRANCH..."
if git rebase "origin/$BASE_BRANCH"; then
echo "Rebase completed cleanly."
else
echo "Conflicts detected. Attempting auto-resolution..."
CONFLICTED=$(git diff --name-only --diff-filter=U)
MANUAL_FILES=()
for file in $CONFLICTED; do
if [ "$file" = "pnpm-lock.yaml" ]; then
echo " Auto-resolving pnpm-lock.yaml (will regenerate)..."
git checkout --ours pnpm-lock.yaml
git add pnpm-lock.yaml
else
MANUAL_FILES+=("$file")
fi
done
if [ ${#MANUAL_FILES[@]} -gt 0 ]; then
# Regenerate lockfile now if it was conflicted, before pausing
if echo "$CONFLICTED" | grep -q "pnpm-lock.yaml"; then
regenerate_lockfile
fi
echo ""
echo "MANUAL_RESOLUTION_NEEDED"
echo "The following files have conflicts that need manual resolution:"
for f in "${MANUAL_FILES[@]}"; do
echo " $f"
done
echo ""
echo "After resolving, stage the files with 'git add' and run:"
echo " $0 $PR_NUMBER --continue"
exit 1
fi
# All conflicts were auto-resolved — regenerate lockfile and continue
if echo "$CONFLICTED" | grep -q "pnpm-lock.yaml"; then
regenerate_lockfile
fi
if ! GIT_EDITOR=true git rebase --continue; then
echo "ERROR: 'git rebase --continue' failed. Resolve remaining conflicts and run: $0 $PR_NUMBER --continue"
exit 1
fi
fi
# Force push
echo "Force-pushing to $REMOTE/$HEAD_BRANCH..."
git push "$REMOTE" "HEAD:$HEAD_BRANCH" --force-with-lease
# Verify
echo "Waiting for GitHub to update mergeability..."
sleep 10
MERGEABLE=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json mergeable --jq .mergeable)
MERGE_STATE=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json mergeStateStatus --jq .mergeStateStatus)
echo "PR status: mergeable=$MERGEABLE mergeStateStatus=$MERGE_STATE"
if [ "$MERGEABLE" = "MERGEABLE" ]; then
echo "Conflicts resolved successfully!"
else
echo "WARNING: GitHub still reports conflicts. Main may have moved again — re-run this script."
fi

View File

@@ -17,7 +17,7 @@ function wt
claude --dangerously-skip-permissions "Review and fix PR #$pr_number. Steps:
1. Use gh to read the PR description, diff, and all review comments (both PR-level and inline).
2. Understand the intent of the PR and what each change does.
3. Resolve any conflicts with the base branch: use 'gh pr view $pr_number --json baseRefName' to get the base branch name, then run 'git fetch origin <base> && git merge origin/<base>'. If there are merge conflicts, resolve them. Do NOT skip this step. Do NOT assume the branch is up to date — always fetch and merge to be sure.
3. Resolve any conflicts with the base branch by running './shell/resolve-pr-conflicts.sh $pr_number'. This force-fetches the base branch (avoiding stale refs), rebases, and auto-resolves lockfile conflicts. If it prints MANUAL_RESOLUTION_NEEDED, read the listed conflicted files, resolve the conflict markers, run 'git add' on each resolved file, then run './shell/resolve-pr-conflicts.sh $pr_number --continue' to finish the rebase and push. Do NOT skip this step. Do NOT assume the branch is up to date.
4. Address every review comment — fix the code as requested or as appropriate.
5. Look for any other bugs, issues, or style problems in the changed code and fix those too.
6. Run the relevant tests to verify your fixes work (check CLAUDE.md for how to run tests).