Skip to content

Commit 274d144

Browse files
feat(tooling): Add community contribution PR mirroring (#9602)
1 parent 11e677e commit 274d144

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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

Comments
 (0)