Skip to content

Commit b772b84

Browse files
chmouelpipelines-as-code[bot]
authored andcommitted
feat: Enable mirror script to update existing PRs
* Implemented logic to detect and update an already existing mirrored pull request, preventing the creation of duplicate PRs when the script was rerun. * Added functionality to automatically detect the original pull request number from the current branch name, which streamlined the workflow for maintainers. * Introduced `fzf` and `jq` as new script dependencies to support enhanced PR selection and JSON parsing capabilities. * Updated various messages and prompts with clearer language and emojis, improving the overall user experience. * make sure we run pre-commit hooks before pushing changes to the fork unless -n is passed to the script. Signed-off-by: Chmouel Boudjnah <[email protected]>
1 parent 1f9ba8c commit b772b84

File tree

1 file changed

+120
-65
lines changed

1 file changed

+120
-65
lines changed

β€Žhack/mirror-pr.sh

Lines changed: 120 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,119 @@
11
#!/usr/bin/env bash
2-
# A script to mirror an external contributor's pull request to a maintainer's fork
3-
# for the purpose of running E2E tests.
42
#
5-
# Prerequisites:
6-
# 1. GitHub CLI (`gh`) must be installed and authenticated (`gh auth login`).
7-
# 2. You must have a fork of the repository.
8-
# 3. You must have a git remote configured for the upstream repository (e.g., "upstream").
9-
#
10-
# Usage:
11-
# ./mirror-pr.sh <PR_NUMBER> <FORK_REMOTE>
12-
#
13-
# Example:
14-
# ./mirror-pr.sh 1234 my-github-user
15-
#
16-
# if no PR number is provided, it will prompt you to select one using `fzf`.
3+
no_verify=
4+
show_help() {
5+
cat <<EOF
6+
πŸͺž Mirror an external contributor's pull request to a maintainer's fork for E2E tests.
7+
8+
πŸ› οΈ Prerequisites:
9+
1. πŸ™ GitHub CLI (gh) must be installed and authenticated (gh auth login).
10+
2. 🍴 You must have a fork of the repository.
11+
3. πŸ”— You must have a git remote configured for the upstream repository (e.g., "upstream").
12+
4. πŸ‘Ύ You need fzf and jq installed for selecting PRs and parsing JSON.
13+
14+
▢️ Usage:
15+
./mirror-pr.sh <PR_NUMBER> <FORK_REMOTE>
16+
17+
πŸ’‘ Example:
18+
./mirror-pr.sh 1234 my-github-user
19+
20+
If no PR number or not fork are provided, it will prompt you to select one
21+
using fzf.
22+
23+
EOF
24+
grep -E "[ ]*[a-zA-Z0-9-]\) ##" $0 |
25+
sed -e 's/^[ ]*/-/' \
26+
-e 's/-\([0-9A-Za-z]*\)[ ]*|[ ]*\([0-9A-Za-z]*\)/-\1, -\2/' \
27+
-e 's/##//' -e 's/)[ ]*/ - /' |
28+
awk -F" - " '{printf "%-10s %s\n", $1, $2}'
29+
30+
cat <<EOF
31+
32+
EOF
33+
}
34+
while getopts "hn" opt; do
35+
case $opt in
36+
n) ## do not run pre-commit checks
37+
no_verify=yes
38+
;;
39+
h)
40+
echo "usage: $(basename $(readlink -f $0))"
41+
show_help
42+
exit 0
43+
;;
44+
*)
45+
echo "unknown option: -${OPTARG}" >&2
46+
show_help
47+
exit 1
48+
;;
49+
esac
50+
done
51+
shift $((OPTIND - 1))
1752

1853
set -eo pipefail
1954

2055
if ! command -v gh &>/dev/null; then
21-
echo "Error: GitHub CLI ('gh') is not installed. ❌ Please install it to continue."
22-
echo "See: https://cli.github.com/"
56+
echo "πŸ›‘ Error: GitHub CLI ('gh') is not installed. Please install it to continue."
57+
echo "πŸ”— See: https://cli.github.com/"
2358
exit 1
2459
fi
2560

26-
echo "βœ… GitHub CLI is installed."
61+
echo "βœ… GitHub CLI is installed. Ready to proceed!"
2762

2863
PR_NUMBER=${1:-}
2964
FORK_REMOTE=${GH_FORK_REMOTE:-$2}
3065
UPSTREAM_REPO=${GH_UPSTREAM_REPO:-"openshift-pipelines/pipelines-as-code"}
3166

32-
# Check if there is any changes in the current branch or bail out
67+
# πŸ›‘οΈ Check for uncommitted changes
3368
if ! git diff-index --quiet HEAD --; then
34-
echo "❌ Error: There are uncommitted changes in the current branch. Please commit or stash them before running this script."
69+
echo "πŸ“ Error: There are uncommitted changes in the current branch. Please commit or stash them before running this script."
3570
exit 1
3671
fi
3772

3873
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
3974
resetgitbranch() {
4075
new_branch_name=$(git rev-parse --abbrev-ref HEAD)
41-
echo "πŸ”„ Resetting to original branch ${CURRENT_BRANCH} from ${new_branch_name}"
76+
echo "↩️ Resetting to original branch ${CURRENT_BRANCH} from ${new_branch_name}"
4277
git checkout "$CURRENT_BRANCH" || true
4378
}
4479
trap resetgitbranch EXIT
4580

81+
# 🎯 Select PR number if not provided
4682
if [[ -z ${PR_NUMBER} ]]; then
47-
PR_SELECTION=$(gh pr list --repo "$UPSTREAM_REPO" --json number,title,author --template '{{range .}}{{.number}}: {{.title}} (by {{.author.login}})
48-
{{end}}' | grep -v "\[MIRRORED\]" | fzf --prompt="Select PR: ")
49-
PR_NUMBER=$(echo "$PR_SELECTION" | awk -F: '{print $1}' | xargs)
83+
if [[ ${CURRENT_BRANCH} =~ test-pr-([0-9]+)-([a-zA-Z0-9_-]+) ]]; then
84+
PR_NUMBER="${BASH_REMATCH[1]}"
85+
else
86+
PR_SELECTION=$(gh pr list --repo "$UPSTREAM_REPO" --json number,title,author --template '{{range .}}{{.number}}: {{.title}} (by {{.author.login}})
87+
{{end}}' | grep -v "\[MIRRORED\]" | fzf --prompt="πŸ”Ž Select PR: ")
88+
PR_NUMBER=$(echo "$PR_SELECTION" | awk -F: '{print $1}' | xargs)
89+
fi
5090
fi
5191

92+
# πŸ” Check if a mirrored PR already exists
93+
already_opened_pr=$(
94+
gh pr list --repo $UPSTREAM_REPO \
95+
--json number,headRepositoryOwner,headRepository,headRefName |
96+
jq -r --arg pn "$PR_NUMBER" \
97+
'.[] | select(.headRefName | test("^test-pr-\($pn)-.*")) | "[email protected]:\(.headRepositoryOwner.login)/\(.headRepository.name).git"'
98+
)
99+
[[ -n ${already_opened_pr} ]] && FORK_REMOTE=${already_opened_pr}
100+
101+
# 🌿 Select fork remote if not provided
52102
if [[ -z $FORK_REMOTE ]]; then
53-
FORK_REMOTE=$(git remote | awk '{print $1}' | grep -v origin | sort -u | fzf -1 --prompt="Select fork remote: ")
103+
FORK_REMOTE=$(git remote | awk '{print $1}' | grep -v origin | sort -u | fzf -1 --prompt="🌿 Select fork remote: ")
54104
fi
55105

56106
if [[ -z "$PR_NUMBER" || -z "$FORK_REMOTE" ]]; then
57-
echo "Usage: $0 <PR_NUMBER> <YOUR_REMOTE_FORK>"
58-
echo "Example: $0 1234 my-github-user"
59-
echo "UPSTREAM_REPO is ${UPSTREAM_REPO} unless you configure the env variable GH_UPSTREAM_REPO."
107+
echo "ℹ️ Usage: $0 <PR_NUMBER> <YOUR_REMOTE_FORK>"
108+
echo "πŸ’‘ Example: $0 1234 my-github-user"
109+
echo "πŸ”Ž UPSTREAM_REPO is ${UPSTREAM_REPO} unless you configure the env variable GH_UPSTREAM_REPO."
60110
exit 1
61111
fi
62112

63-
# --- Main Logic ---
113+
echo "πŸ” Fetching details for PR #${PR_NUMBER} from ${UPSTREAM_REPO}..."
114+
echo "🌿 Fork remote: ${FORK_REMOTE}"
64115

65-
echo "πŸ”„ Fetching details for PR #${PR_NUMBER} from ${UPSTREAM_REPO}..."
66-
67-
# Fetch PR title and author using GitHub CLI
116+
# πŸ“‹ Fetch PR title and author
68117
PR_TITLE=$(gh pr view "$PR_NUMBER" --repo "$UPSTREAM_REPO" --json title -q .title)
69118
PR_AUTHOR=$(gh pr view "$PR_NUMBER" --repo "$UPSTREAM_REPO" --json author -q .author.login)
70119
PR_URL="https://github.com/${UPSTREAM_REPO}/pull/${PR_NUMBER}"
@@ -74,71 +123,77 @@ if [[ -z "$PR_TITLE" ]]; then
74123
exit 1
75124
fi
76125

77-
echo " - Title: $PR_TITLE"
78-
echo " - Author: $PR_AUTHOR"
126+
echo "πŸ“ - Title: $PR_TITLE"
127+
echo "πŸ‘€ - Author: $PR_AUTHOR"
79128

80-
# 1. Checkout the PR locally
81-
echo "πŸ”„ Checking out PR #${PR_NUMBER} locally..."
129+
# 1️⃣ Checkout the PR locally
130+
echo "πŸ“₯ Checking out PR #${PR_NUMBER} locally..."
82131
gh pr checkout --force "$PR_NUMBER" --repo "$UPSTREAM_REPO"
83132

84-
# 2. Push the branch to your fork
133+
# 2️⃣ Push the branch to your fork
85134
NEW_BRANCH_NAME="test-pr-${PR_NUMBER}-${PR_AUTHOR}"
86135

87-
# check if we didn't already have a pull request open for this branch
88-
already_opened_pr=$(
89-
gh pr list --repo "$UPSTREAM_REPO" --head \
90-
"${NEW_BRANCH_NAME}" --json url --jq '.[0].url'
91-
)
92-
93136
if [[ -n ${already_opened_pr} ]]; then
94-
echo "πŸ”„ A pull request already exists for this branch, pushing to the pull request target: ${already_opened_pr}"
95-
96-
gh pr list --repo openshift-pipelines/pipelines-as-code --head ${NEW_BRANCH_NAME} --json headRepositoryOwner,headRepository --jq '.[0].headRepositoryOwner.login + "/" + .[0].headRepository.name'
97-
)"
98-
echo "πŸ”¨ Pushing changes to existing pull request branch '${NEW_BRANCH_NAME}' fork (${FORK_REMOTE})..."
137+
echo "πŸ” A pull request already exists for this branch, pushing to the pull request target: ${already_opened_pr}"
138+
echo "🚚 Pushing changes to existing pull request branch '${NEW_BRANCH_NAME}' fork (${FORK_REMOTE})..."
99139
else
100-
101-
echo "πŸ”¨ Pushing changes to a new branch '${NEW_BRANCH_NAME}' on your fork (${FORK_REMOTE})..."
140+
echo "πŸš€ Pushing changes to a new branch '${NEW_BRANCH_NAME}' on your fork (${FORK_REMOTE})..."
102141
fi
103142

104-
# Force push in case the branch already exists from a previous test run
105-
git push "$FORK_REMOTE" "HEAD:${NEW_BRANCH_NAME}" --force
143+
# 🚨 Force push in case the branch already exists from a previous test run
144+
if [[ -n ${no_verify} ]]; then
145+
echo "⚠️ Skipping pre-push verification due to --no-verify flag."
146+
else
147+
if ! command -v "pre-commit" >/dev/null 2>&1; then
148+
echo "⚠️ You need to have the 'pre-commit' tool installed to run this script."
149+
exit 1
150+
fi
151+
echo "🚜 Running pre-commit checks before pushing..."
152+
pre-commit run --all-files --show-diff-on-failure || {
153+
echo "❗ Pre-commit checks failed. Please fix the issues before pushing."
154+
echo "You can fix user errors locally and pushing to the user branch."
155+
echo "git commit --amend the commit (or add a new commit) and then run this command"
156+
gh pr view "$PR_NUMBER" --repo "$UPSTREAM_REPO" --json headRefName,headRepositoryOwner,headRepository |
157+
jq -r '"git push --force-with-lease [email protected]:\(.headRepositoryOwner.login)/\(.headRepository.name).git HEAD:\(.headRefName)"'
158+
echo "(or use --force if you know what you are doing)"
159+
exit 1
160+
}
161+
fi
162+
git push "$FORK_REMOTE" "HEAD:${NEW_BRANCH_NAME}" --force --no-verify
106163

107164
if [[ -n ${already_opened_pr} ]]; then
108-
echo "πŸ”— Pull request has successfully been synched ${already_opened_pr}"
109165
exit 0
110166
fi
111167

112-
# 3. Create a new Pull Request from the fork to the upstream repo
113-
MIRRORED_PR_TITLE="[MIRRORED] ${PR_TITLE}"
114-
MIRRORED_PR_BODY="Mirrors ${PR_URL} to run E2E tests. Original author: @${PR_AUTHOR}"
168+
# 3️⃣ Create a new Pull Request from the fork to the upstream repo
169+
MIRRORED_PR_TITLE="πŸͺž [MIRRORED] ${PR_TITLE}"
170+
MIRRORED_PR_BODY="πŸ”„ Mirrors ${PR_URL} to run E2E tests. Original author: @${PR_AUTHOR}"
115171
DO_NOT_MERGE_LABEL="do-not-merge" # You might need to create this label in your repo if it doesn't exist
116172

117-
echo "πŸ”„ Creating a new mirrored pull request on ${UPSTREAM_REPO}..."
173+
echo "πŸ†• Creating a new mirrored pull request on ${UPSTREAM_REPO}..."
118174

119-
# Create the PR as a draft to prevent accidental merges before tests run.
120-
# The --head flag specifies the branch in your fork.
175+
# πŸ“ Create the PR as a draft to prevent accidental merges before tests run.
121176
CREATED_PR_URL=$(gh pr create \
122177
--repo "$UPSTREAM_REPO" \
123178
--title "$MIRRORED_PR_TITLE" \
124179
--body "$MIRRORED_PR_BODY" \
125180
--head "${FORK_REMOTE}:${NEW_BRANCH_NAME}" \
126181
--label "$DO_NOT_MERGE_LABEL" \
127-
--draft) # Using --draft is safer
182+
--draft)
128183

129-
# Check if the PR was created successfully
184+
# βœ… Check if the PR was created successfully
130185
if [[ -z "$CREATED_PR_URL" ]]; then
131-
echo "❌ Error: Failed to create the mirrored pull request."
186+
echo "❗ Error: Failed to create the mirrored pull request."
132187
exit 1
133188
fi
134189

135190
gh pr comment "$PR_NUMBER" --repo "$UPSTREAM_REPO" --body \
136-
":rocket: **Mirrored PR Created for E2E Testing**<br><br>\
191+
"πŸš€ **Mirrored PR Created for E2E Testing**<br><br>\
137192
A mirrored PR has been opened for end-to-end testing: [View PR](${CREATED_PR_URL})<br><br>\
138-
:hourglass_flowing_sand: Follow progress there for E2E results.<br>\
193+
⏳ Follow progress there for E2E results.<br>\
139194
If you need to update the PR with new changes, please ask a maintainer to rerun \`hack/mirror-pr.sh\`."
140195

141-
echo "βœ… Successfully created mirrored pull request!"
196+
echo "πŸŽ‰ Successfully created mirrored pull request!"
142197
echo " ${CREATED_PR_URL}"
143198

144-
echo "πŸš€ Done."
199+
echo "🏁 Done."

0 commit comments

Comments
Β (0)