#!/bin/bash # How to setup the repository to make this correctly work # 1. Clone repo and make sure you can correctly push to that # (example with openwrt main repo you need to use ssh remote url) # 2. Make sure you can correctly push and force push to the github # repository # # Make sure to install the extra git-filter-repo package. # # Usage: github-merge-pr.sh PR_NUMBER BRANCH REPO_NAME # # BRANCH is optional and defaults to main. # REPO_NAME is optional and defaults to openwrt. It does # describe the repository name to use to pull PR info from. # Script .config variables # Github repository owner or organization name. # By default set to openwrt, set this line in .config to overwrite # the default value. GITHUB_REPO_OWNER="openwrt" # Your repository token, generate this token at your profile page: # - Navigate to https://github.com/settings/tokens # - Click on "Generate new token" # - Enter a description, e.g. "pr.sh" and pick the "repo" scope # - Hit "Generate token" # Set this line in .config to provide a GITHUB_TOKEN and add comments #GITHUB_TOKEN="d41d8cd98f00b204e9800998ecf8427e" # # Set this line in .config to use SSH key to rebase PR branch # GITHUB_USE_SSH=1 SCRIPT_DIR="$(dirname $0)" [ -h $0 ] && SCRIPT_DIR="$(dirname $(readlink $0))" # Everything in .config will overwrite the default values set up [ -f "$SCRIPT_DIR/.config" ] && source "$SCRIPT_DIR"/.config PRID="$1" BRANCH="${2:-main}" GITHUB_REPO_NAME="${3:-openwrt}" DRY_RUN="$4" GIT=git REPO="$GITHUB_REPO_OWNER"/"$GITHUB_REPO_NAME" yesno() { local prompt="$1" local default="${2:-n}" local input while [ 1 ]; do printf "%s y/n [%s] > " "$prompt" "$default" read input case "${input:-$default}" in y*) return 0 ;; n*) return 1 ;; esac done } if ! command -v jq &> /dev/null; then echo "jq could not be found! This script require jq!" exit 1 fi if [ -z "$PRID" -o -n "${PRID//[0-9]*/}" ]; then echo "Usage: $0 [rebase-branch] [dry-run]" >&2 exit 1 fi if [ -n "$DRY_RUN" ]; then GIT="echo git" fi if [ -z "$(git branch --list "$BRANCH")" ]; then echo "Given rebase branch '$BRANCH' does not exist!" >&2 exit 2 fi if [ -n "$GITHUB_TOKEN" ]; then CURL_CMD=" --user \"$GITHUB_TOKEN:x-oauth-basic\" " else CURL_CMD=" " fi if ! PR_INFO="$(curl $CURL_CMD -f -s "https://api.github.com/repos/$REPO/pulls/$PRID")"; then echo "Failed fetch PR #$PRID info" >&2 exit 3 fi if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "false" ]; then echo "PR #$PRID can't be force pushed by maintainers. Trying to merge as is!" >&2 fi if [ "$(echo "$PR_INFO" | jq -r ".mergeable")" == "false" ]; then echo "PR #$PRID is not mergeable for Github.com. Check the PR!" >&2 exit 5 fi echo "Pulling current $BRANCH from origin" $GIT checkout $BRANCH $GIT fetch origin $BRANCH if ! $GIT rebase origin/$BRANCH; then echo "Failed to rebase $BRANCH with origin/$BRANCH" >&2 exit 7 fi PR_USER="$(echo "$PR_INFO" | jq -r ".head.user.login")" PR_BRANCH="$(echo "$PR_INFO" | jq -r ".head.ref")" LOCAL_PR_BRANCH="$PR_BRANCH"-"$PR_USER" if [ "$GITHUB_USE_SSH" = "1" ]; then PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.ssh_url")" else PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.html_url")" fi if ! $GIT remote get-url $PR_USER &> /dev/null || [ -n "$DRY_RUN" ]; then echo "Adding $PR_USER with repo $PR_REPO to remote" $GIT remote add $PR_USER $PR_REPO fi echo "Fetching remote $PR_USER" $GIT fetch $PR_USER $PR_BRANCH if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ] || [ "$IGNORE_MERGEABLE" = "1" ]; then echo "Creating branch $LOCAL_PR_BRANCH for $PR_BRANCH" if ! $GIT checkout -b $LOCAL_PR_BRANCH $PR_USER/$PR_BRANCH; then echo "Failed to checkout new branch $PR_BRANCH from $PR_USER/$PR_BRANCH" >&2 exit 8 fi echo "Rebasing $LOCAL_PR_BRANCH on top of $BRANCH" if ! $GIT rebase origin/$BRANCH; then echo "Failed to rebase $PR_BRANCH with origin/$BRANCH" >&2 exit 9 fi # Remove any previous Link: https://github.com/$REPO/pull/$PRID tag if ! $GIT filter-repo --message-callback " return message.replace(b\"Link: https://github.com/$REPO/pull/$PRID\n\",b\"\") " --refs $BRANCH..$LOCAL_PR_BRANCH --force; then echo "Failed to remove previous Link: https://github.com/$REPO/pull/$PRID tag" >&2 exit 9 fi # Add to each commit Link: https://github.com/$REPO/pull/$PRID if ! $GIT filter-repo --message-callback " return message + b\"Link: https://github.com/$REPO/pull/$PRID\" " --refs $BRANCH..$LOCAL_PR_BRANCH --force; then echo "Failed to add Link: Pull Request tag" >&2 exit 9 fi # Remove any previous SoB tag if the Pull Request Author and Committer match if ! $GIT filter-repo --message-callback " return message.replace(b\"Signed-off-by: $(git config user.name) <$(git config user.email)>\n\",b\"\") " --refs $BRANCH..$LOCAL_PR_BRANCH --force; then echo "Failed to remove previous Committer SoB tag" >&2 exit 9 fi # Add Committer SoB to better reference who merged the thing last if ! $GIT rebase origin/$BRANCH $LOCAL_PR_BRANCH --exec "git commit -s --amend -C HEAD"; then echo "Failed to add Committer SoB tag" >&2 exit 9 fi if [ "$GITHUB_NO_PUSH" = "1" ]; then echo "next commands:" if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then echo "$GIT push $PR_USER HEAD:$PR_BRANCH --force" echo "$GIT checkout $BRANCH" echo "$GIT merge --ff-only $PR_USER/$PR_BRANCH" else echo "$GIT checkout $BRANCH" echo "$GIT merge --ff-only $LOCAL_PR_BRANCH" fi echo "$GIT push" echo "$GIT branch -D $LOCAL_PR_BRANCH" exit 20 fi echo "Force pushing $LOCAL_PR_BRANCH to HEAD:$PR_BRANCH for $PR_USER" if ! $GIT push $PR_USER HEAD:$PR_BRANCH --force; then echo "Failed to force push HEAD to $PR_USER" >&2 exit 10 fi echo "Returning to $BRANCH" $GIT checkout $BRANCH fi if [ -n "$($GIT log origin/$BRANCH..HEAD)" ]; then echo "Working on dirty branch for $BRANCH! Please reset $BRANCH to origin/$BRANCH" >&2 exit 11 fi echo "Actually merging the PR #$PRID from branch $PR_USER/$PR_BRANCH" if ! $GIT merge --ff-only $PR_USER/$PR_BRANCH; then echo "Failed to merge $PR_USER/$PR_BRANCH on $BRANCH" >&2 else if yesno "Push to openwrt $BRANCH" "y"; then echo "Pushing to openwrt git server" if ! $GIT push; then echo "Failed to push to $BRANCH but left branch as is." >&2 exit 12 fi # Default close comment COMMENT="Thanks! Rebased on top of $BRANCH and merged!" if [ -n "$GITHUB_TOKEN" ] && [ -z "$DRY_RUN" ]; then echo "" echo "Enter a comment and hit to close the PR at Github automatically now." echo "Hit - to exit." echo "" echo "If you do not provide a comment, the default will be: " echo "[$COMMENT]" echo -n "Comment > " read usercomment echo "Sending message to PR..." comment="${usercomment:-$COMMENT}" comment="${comment//\\/\\\\}" comment="${comment//\"/\\\"}" comment="$(printf '{"body":"%s"}' "$comment")" if ! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request POST --data "$comment" "https://api.github.com/repos/$REPO/issues/$PRID/comments" || \ ! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request PATCH --data '{"state":"closed"}' "https://api.github.com/repos/$REPO/pulls/$PRID" then echo "" >&2 echo "Something failed while sending comment to the PR via ">&2 echo "the Github API, please review the state manually at " >&2 echo "https://github.com/$REPO/pull/$PRID" >&2 exit 6 fi fi echo -e "\n" echo "The PR has been merged!" echo -e "\n" fi fi if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then echo "Deleting branch $LOCAL_PR_BRANCH" $GIT branch -D $LOCAL_PR_BRANCH fi exit 0