|
| 1 | +#!/bin/bash |
| 2 | +set -e |
| 3 | + |
| 4 | +# |
| 5 | +# Handling community pull requests. |
| 6 | +# |
| 7 | +# The main way to handle community contributions is to contribute directly on the contributor's pull request. |
| 8 | +# If not enabled, please ask the contributor to enable the option "Allow edits by maintainers". |
| 9 | +# This allows maintainers to push commits to the contributor's branch. |
| 10 | +# Then, use this script to mirror the PR to a new branch in the main repository. |
| 11 | +# This allows running CI with the maintainer's permissions. |
| 12 | +# |
| 13 | +# Few hints: |
| 14 | +# - Avoid merge commits with master and rebase the contributor's branch instead. |
| 15 | +# - Avoid pushing changes to the mirror branch as it will be overwritten when rerunning the script. |
| 16 | +# |
| 17 | +# Usage: mirror-community-pull-request.sh <pr-number> [<target-branch>] |
| 18 | + |
| 19 | +REPO="DataDog/dd-trace-java" |
| 20 | +PR_NUMBER=$1 |
| 21 | +TARGET_BRANCH=${2:-master} |
| 22 | +MIRROR_BRANCH="community-pr-$PR_NUMBER" |
| 23 | + |
| 24 | +# |
| 25 | +# Check arguments. |
| 26 | +# |
| 27 | +# Check if no arguments are provided |
| 28 | +if [ $# -eq 0 ]; then |
| 29 | + echo "Usage: $0 <pr-number> [<target-branch>]" |
| 30 | + echo "<pr-number>: PR number to mirror" |
| 31 | + echo "<target-branch>: Target branch for the mirror (default: master)" |
| 32 | + echo "" |
| 33 | + echo "This script mirrors a community PR to allow running CI with your own account." |
| 34 | + echo "It creates a new branch 'community-pr-<number>' and pushes the PR changes." |
| 35 | + exit 1 |
| 36 | +fi |
| 37 | +# Check PR number is provided |
| 38 | +if [ -z "$PR_NUMBER" ]; then |
| 39 | + echo "❌ PR number is not provided" |
| 40 | + exit 1 |
| 41 | +fi |
| 42 | +# Validate PR number is numeric |
| 43 | +if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then |
| 44 | + echo "❌ PR number must be numeric" |
| 45 | + exit 1 |
| 46 | +fi |
| 47 | + |
| 48 | +# |
| 49 | +# Check requirements. |
| 50 | +# |
| 51 | +echo "- Checking requirements" |
| 52 | +# Check gh is installed |
| 53 | +gh --version 1>/dev/null 2>&1 || { echo "❌ gh is not installed. Please install GitHub CLI."; exit 1; } |
| 54 | +# Check jq is installed |
| 55 | +jq --version 1>/dev/null 2>&1 || { echo "❌ jq is not installed. Please install jq."; exit 1; } |
| 56 | +# Check there are no local changes |
| 57 | +git diff --exit-code || { echo "❌ There are local changes. Please commit or stash them."; exit 1; } |
| 58 | + |
| 59 | +# |
| 60 | +# Fetch PR information. |
| 61 | +# |
| 62 | +echo "- Fetching PR #$PR_NUMBER details" |
| 63 | +# Check if PR exists and get details |
| 64 | +PR_DATA=$(gh pr view "$PR_NUMBER" --json headRepository,headRepositoryOwner,headRefName,title,number,state,author 2>/dev/null || echo "") |
| 65 | +if [ -z "$PR_DATA" ]; then |
| 66 | + echo "❌ PR #$PR_NUMBER not found" |
| 67 | + exit 1 |
| 68 | +fi |
| 69 | +# Parse PR details |
| 70 | +FORK_REPO=$(echo "$PR_DATA" | jq -r '(.headRepository.nameWithOwner // (.headRepositoryOwner.login + "/" + .headRepository.name)) // empty') |
| 71 | +FORK_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName // empty') |
| 72 | +PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // empty') |
| 73 | +PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // empty') |
| 74 | +PR_LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")') |
| 75 | +if [ -z "$FORK_REPO" ] || [ -z "$FORK_BRANCH" ]; then |
| 76 | + echo "❌ Could not determine fork repository or branch for PR #$PR_NUMBER" |
| 77 | + exit 1 |
| 78 | +fi |
| 79 | + |
| 80 | +# |
| 81 | +# Create mirror branch. |
| 82 | +# |
| 83 | +echo "- Mirroring PR #$PR_NUMBER from $FORK_REPO:$FORK_BRANCH to $REPO:$MIRROR_BRANCH" |
| 84 | +# Check if mirror branch already exists |
| 85 | +echo "- Checking if mirror branch $MIRROR_BRANCH already exists locally" |
| 86 | +if git show-ref --verify --quiet "refs/heads/$MIRROR_BRANCH" 2>/dev/null; then |
| 87 | + echo -n "Branch $MIRROR_BRANCH already exists locally. Delete it to recreate? (y/n)" |
| 88 | + read -r ANSWER |
| 89 | + if [ "$ANSWER" = "y" ]; then |
| 90 | + git branch -D "$MIRROR_BRANCH" |
| 91 | + else |
| 92 | + echo "Aborting." |
| 93 | + exit 1 |
| 94 | + fi |
| 95 | +fi |
| 96 | +# Check if mirror branch exists on remote |
| 97 | +echo "- Checking if mirror branch $MIRROR_BRANCH already exists on remote" |
| 98 | +if git show-ref --verify --quiet "refs/remotes/origin/$MIRROR_BRANCH" 2>/dev/null; then |
| 99 | + echo -n "Branch $MIRROR_BRANCH already exists on remote. Force push over it? (y/n)" |
| 100 | + read -r ANSWER |
| 101 | + if [ "$ANSWER" != "y" ]; then |
| 102 | + echo "Aborting." |
| 103 | + exit 1 |
| 104 | + fi |
| 105 | +fi |
| 106 | +# Fetch the PR branch from the fork directly (no remote needed) |
| 107 | +git fetch --quiet "https://github.com/$FORK_REPO.git" "$FORK_BRANCH" |
| 108 | +# Get list of commits from the PR to re-sign them |
| 109 | +echo "- Getting list of commits from PR" |
| 110 | +PR_COMMITS=$(gh pr view "$PR_NUMBER" --json commits --jq '.commits[].oid') |
| 111 | +if [ -z "$PR_COMMITS" ]; then |
| 112 | + echo "❌ No commits found in PR #$PR_NUMBER" |
| 113 | + exit 1 |
| 114 | +fi |
| 115 | +# Get current git branch for cleanup |
| 116 | +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) |
| 117 | +# Create the mirror branch from target branch (we'll cherry-pick commits) |
| 118 | +echo "- Creating mirror branch $MIRROR_BRANCH from $TARGET_BRANCH" |
| 119 | +git fetch --quiet origin "$TARGET_BRANCH" |
| 120 | +git checkout -b "$MIRROR_BRANCH" "origin/$TARGET_BRANCH" |
| 121 | + |
| 122 | +# |
| 123 | +# Mirror commits. |
| 124 | +# |
| 125 | +# Cherry-pick each commit with signature to ensure all commits are signed |
| 126 | +echo "- Cherry-picking and signing commits from PR" |
| 127 | +for COMMIT in $PR_COMMITS; do |
| 128 | + echo " - Cherry-picking $COMMIT" |
| 129 | + # Check if this is a merge commit |
| 130 | + CHERRY_PICK_ARGS="-S" |
| 131 | + PARENT_COUNT=$(git rev-list --parents -n 1 "$COMMIT" 2>/dev/null | wc -w) |
| 132 | + if [ "$PARENT_COUNT" -gt 2 ]; then |
| 133 | + CHERRY_PICK_ARGS="$CHERRY_PICK_ARGS -m 1" |
| 134 | + fi |
| 135 | + if ! git cherry-pick "$CHERRY_PICK_ARGS" "$COMMIT"; then |
| 136 | + # Check if it's an empty commit |
| 137 | + if ! git diff --cached --quiet || ! git diff --quiet; then |
| 138 | + echo "❌ Failed to cherry-pick merge commit $COMMIT" |
| 139 | + echo "You may need to resolve conflicts manually" |
| 140 | + exit 1 |
| 141 | + else |
| 142 | + echo " (empty commit, skipping)" |
| 143 | + git cherry-pick --skip |
| 144 | + fi |
| 145 | + fi |
| 146 | +done |
| 147 | +# Push the mirror branch to origin |
| 148 | +echo "- Pushing $MIRROR_BRANCH to origin" |
| 149 | +git push -u origin "$MIRROR_BRANCH" --no-verify --force-with-lease |
| 150 | + |
| 151 | +# |
| 152 | +# Create PR if it doesn't exist. |
| 153 | +# |
| 154 | +echo "- Checking if PR already exists for branch $MIRROR_BRANCH" |
| 155 | +EXISTING_PR=$(gh pr list --head "$MIRROR_BRANCH" --json number --jq '.[0].number // empty' 2>/dev/null) |
| 156 | +if [ -n "$EXISTING_PR" ]; then |
| 157 | + MIRROR_PR_URL="https://github.com/$REPO/pull/$EXISTING_PR" |
| 158 | +else |
| 159 | + echo "- Creating new PR for mirror branch $MIRROR_BRANCH" |
| 160 | + # Create PR body with reference to original |
| 161 | + MIRROR_PR_BODY="This PR mirrors the changes from the original community contribution to enable CI testing with maintainer privileges. |
| 162 | +
|
| 163 | +**Original PR:** https://github.com/$REPO/pull/$PR_NUMBER |
| 164 | +**Original Author:** @$PR_AUTHOR |
| 165 | +**Original Branch:** $FORK_REPO:$FORK_BRANCH |
| 166 | +
|
| 167 | +Closes #$PR_NUMBER |
| 168 | +
|
| 169 | +--- |
| 170 | +
|
| 171 | +*This is an automated mirror created to run CI checks. See tooling/mirror-community-pull-request.sh for details.*" |
| 172 | + |
| 173 | + # Create the PR |
| 174 | + CREATE_ARGS=(--base "$TARGET_BRANCH" --head "$MIRROR_BRANCH" --title "🪞 $PR_NUMBER - $PR_TITLE" --body "$MIRROR_PR_BODY") |
| 175 | + if [ -n "$PR_LABELS" ]; then |
| 176 | + CREATE_ARGS+=(--label "$PR_LABELS") |
| 177 | + fi |
| 178 | + MIRROR_PR_NUMBER=$(gh pr create "${CREATE_ARGS[@]}" 2>/dev/null | grep -o '[0-9]*$') |
| 179 | + if [ -n "$MIRROR_PR_NUMBER" ]; then |
| 180 | + echo "- Created mirror PR: #$MIRROR_PR_NUMBER" |
| 181 | + MIRROR_PR_URL="https://github.com/$REPO/pull/$MIRROR_PR_NUMBER" |
| 182 | + else |
| 183 | + echo "- Failed to create PR automatically" |
| 184 | + MIRROR_PR_URL="https://github.com/$REPO/compare/$TARGET_BRANCH...$MIRROR_BRANCH" |
| 185 | + fi |
| 186 | +fi |
| 187 | + |
| 188 | +echo "" |
| 189 | +echo "✅ Successfully mirrored PR #$PR_NUMBER" |
| 190 | +echo " Original: https://github.com/$REPO/pull/$PR_NUMBER (@$PR_AUTHOR)" |
| 191 | +echo " Mirror: $MIRROR_PR_URL" |
| 192 | +echo " Branch: $REPO:$MIRROR_BRANCH" |
| 193 | + |
| 194 | +# |
| 195 | +# Clean up. |
| 196 | +# |
| 197 | +echo "- Restoring original branch" |
| 198 | +git checkout "$CURRENT_BRANCH" |
| 199 | + |
| 200 | +echo "Done." |
0 commit comments