Skip to content
Merged
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
200 changes: 200 additions & 0 deletions tooling/mirror-community-pull-request.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/bin/bash
set -e

#
# Handling community pull requests.
#
# The main way to handle community contributions is to contribute directly on the contributor's pull request.
# If not enabled, please ask the contributor to enable the option "Allow edits by maintainers".
# This allows maintainers to push commits to the contributor's branch.
# Then, use this script to mirror the PR to a new branch in the main repository.
# This allows running CI with the maintainer's permissions.
#
# Few hints:
# - Avoid merge commits with master and rebase the contributor's branch instead.
# - Avoid pushing changes to the mirror branch as it will be overwritten when rerunning the script.
#
# Usage: mirror-community-pull-request.sh <pr-number> [<target-branch>]

REPO="DataDog/dd-trace-java"
PR_NUMBER=$1
TARGET_BRANCH=${2:-master}
MIRROR_BRANCH="community-pr-$PR_NUMBER"

#
# Check arguments.
#
# Check if no arguments are provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <pr-number> [<target-branch>]"
echo "<pr-number>: PR number to mirror"
echo "<target-branch>: Target branch for the mirror (default: master)"
echo ""
echo "This script mirrors a community PR to allow running CI with your own account."
echo "It creates a new branch 'community-pr-<number>' and pushes the PR changes."
exit 1
fi
# Check PR number is provided
if [ -z "$PR_NUMBER" ]; then
echo "❌ PR number is not provided"
exit 1
fi
# Validate PR number is numeric
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "❌ PR number must be numeric"
exit 1
fi

#
# Check requirements.
#
echo "- Checking requirements"
# Check gh is installed
gh --version 1>/dev/null 2>&1 || { echo "❌ gh is not installed. Please install GitHub CLI."; exit 1; }
# Check jq is installed
jq --version 1>/dev/null 2>&1 || { echo "❌ jq is not installed. Please install jq."; exit 1; }
# Check there are no local changes
git diff --exit-code || { echo "❌ There are local changes. Please commit or stash them."; exit 1; }

#
# Fetch PR information.
#
echo "- Fetching PR #$PR_NUMBER details"
# Check if PR exists and get details
PR_DATA=$(gh pr view "$PR_NUMBER" --json headRepository,headRepositoryOwner,headRefName,title,number,state,author 2>/dev/null || echo "")
if [ -z "$PR_DATA" ]; then
echo "❌ PR #$PR_NUMBER not found"
exit 1
fi
# Parse PR details
FORK_REPO=$(echo "$PR_DATA" | jq -r '(.headRepository.nameWithOwner // (.headRepositoryOwner.login + "/" + .headRepository.name)) // empty')
FORK_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName // empty')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // empty')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // empty')
PR_LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")')
if [ -z "$FORK_REPO" ] || [ -z "$FORK_BRANCH" ]; then
echo "❌ Could not determine fork repository or branch for PR #$PR_NUMBER"
exit 1
fi

#
# Create mirror branch.
#
echo "- Mirroring PR #$PR_NUMBER from $FORK_REPO:$FORK_BRANCH to $REPO:$MIRROR_BRANCH"
# Check if mirror branch already exists
echo "- Checking if mirror branch $MIRROR_BRANCH already exists locally"
if git show-ref --verify --quiet "refs/heads/$MIRROR_BRANCH" 2>/dev/null; then
echo -n "Branch $MIRROR_BRANCH already exists locally. Delete it to recreate? (y/n)"
read -r ANSWER
if [ "$ANSWER" = "y" ]; then
git branch -D "$MIRROR_BRANCH"
else
echo "Aborting."
exit 1
fi
fi
# Check if mirror branch exists on remote
echo "- Checking if mirror branch $MIRROR_BRANCH already exists on remote"
if git show-ref --verify --quiet "refs/remotes/origin/$MIRROR_BRANCH" 2>/dev/null; then
echo -n "Branch $MIRROR_BRANCH already exists on remote. Force push over it? (y/n)"
read -r ANSWER
if [ "$ANSWER" != "y" ]; then
echo "Aborting."
exit 1
fi
fi
# Fetch the PR branch from the fork directly (no remote needed)
git fetch --quiet "https://github.com/$FORK_REPO.git" "$FORK_BRANCH"
# Get list of commits from the PR to re-sign them
echo "- Getting list of commits from PR"
PR_COMMITS=$(gh pr view "$PR_NUMBER" --json commits --jq '.commits[].oid')
if [ -z "$PR_COMMITS" ]; then
echo "❌ No commits found in PR #$PR_NUMBER"
exit 1
fi
# Get current git branch for cleanup
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Create the mirror branch from target branch (we'll cherry-pick commits)
echo "- Creating mirror branch $MIRROR_BRANCH from $TARGET_BRANCH"
git fetch --quiet origin "$TARGET_BRANCH"
git checkout -b "$MIRROR_BRANCH" "origin/$TARGET_BRANCH"

#
# Mirror commits.
#
# Cherry-pick each commit with signature to ensure all commits are signed
echo "- Cherry-picking and signing commits from PR"
for COMMIT in $PR_COMMITS; do
echo " - Cherry-picking $COMMIT"
# Check if this is a merge commit
CHERRY_PICK_ARGS="-S"
PARENT_COUNT=$(git rev-list --parents -n 1 "$COMMIT" 2>/dev/null | wc -w)
if [ "$PARENT_COUNT" -gt 2 ]; then
CHERRY_PICK_ARGS="$CHERRY_PICK_ARGS -m 1"
fi
if ! git cherry-pick "$CHERRY_PICK_ARGS" "$COMMIT"; then
# Check if it's an empty commit
if ! git diff --cached --quiet || ! git diff --quiet; then
echo "❌ Failed to cherry-pick merge commit $COMMIT"
echo "You may need to resolve conflicts manually"
exit 1
else
echo " (empty commit, skipping)"
git cherry-pick --skip
fi
fi
done
# Push the mirror branch to origin
echo "- Pushing $MIRROR_BRANCH to origin"
git push -u origin "$MIRROR_BRANCH" --no-verify --force-with-lease

#
# Create PR if it doesn't exist.
#
echo "- Checking if PR already exists for branch $MIRROR_BRANCH"
EXISTING_PR=$(gh pr list --head "$MIRROR_BRANCH" --json number --jq '.[0].number // empty' 2>/dev/null)
if [ -n "$EXISTING_PR" ]; then
MIRROR_PR_URL="https://github.com/$REPO/pull/$EXISTING_PR"
else
echo "- Creating new PR for mirror branch $MIRROR_BRANCH"
# Create PR body with reference to original
MIRROR_PR_BODY="This PR mirrors the changes from the original community contribution to enable CI testing with maintainer privileges.

**Original PR:** https://github.com/$REPO/pull/$PR_NUMBER
**Original Author:** @$PR_AUTHOR
**Original Branch:** $FORK_REPO:$FORK_BRANCH

Closes #$PR_NUMBER

---

*This is an automated mirror created to run CI checks. See tooling/mirror-community-pull-request.sh for details.*"

# Create the PR
CREATE_ARGS=(--base "$TARGET_BRANCH" --head "$MIRROR_BRANCH" --title "🪞 $PR_NUMBER - $PR_TITLE" --body "$MIRROR_PR_BODY")
if [ -n "$PR_LABELS" ]; then
CREATE_ARGS+=(--label "$PR_LABELS")
fi
MIRROR_PR_NUMBER=$(gh pr create "${CREATE_ARGS[@]}" 2>/dev/null | grep -o '[0-9]*$')
if [ -n "$MIRROR_PR_NUMBER" ]; then
echo "- Created mirror PR: #$MIRROR_PR_NUMBER"
MIRROR_PR_URL="https://github.com/$REPO/pull/$MIRROR_PR_NUMBER"
else
echo "- Failed to create PR automatically"
MIRROR_PR_URL="https://github.com/$REPO/compare/$TARGET_BRANCH...$MIRROR_BRANCH"
fi
fi

echo ""
echo "✅ Successfully mirrored PR #$PR_NUMBER"
echo " Original: https://github.com/$REPO/pull/$PR_NUMBER (@$PR_AUTHOR)"
echo " Mirror: $MIRROR_PR_URL"
echo " Branch: $REPO:$MIRROR_BRANCH"

#
# Clean up.
#
echo "- Restoring original branch"
git checkout "$CURRENT_BRANCH"

echo "Done."