From f7bb668100d3f042e7562da1e045ab454b40d12d Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 22 Mar 2026 13:22:11 +0100 Subject: [PATCH] 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. --- AGENTS.md | 10 ++ shell/resolve-pr-conflicts.sh | 184 ++++++++++++++++++++++++++++++++++ shell/wt.fish | 2 +- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100755 shell/resolve-pr-conflicts.sh diff --git a/AGENTS.md b/AGENTS.md index 6ce87df985..8dead44aa4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 +``` + +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. diff --git a/shell/resolve-pr-conflicts.sh b/shell/resolve-pr-conflicts.sh new file mode 100755 index 0000000000..8db4b45961 --- /dev/null +++ b/shell/resolve-pr-conflicts.sh @@ -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 # full run +# ./shell/resolve-pr-conflicts.sh --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 [--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 diff --git a/shell/wt.fish b/shell/wt.fish index 4cee7d7137..d54a4b755a 100644 --- a/shell/wt.fish +++ b/shell/wt.fish @@ -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 && git merge origin/'. 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).