|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 4 | +# or more contributor license agreements. See the NOTICE file |
| 5 | +# distributed with this work for additional information |
| 6 | +# regarding copyright ownership. The ASF licenses this file |
| 7 | +# to you under the Apache License, Version 2.0 (the |
| 8 | +# "License"); you may not use this file except in compliance |
| 9 | +# with the License. You may obtain a copy of the License at |
| 10 | +# |
| 11 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +# |
| 13 | +# Unless required by applicable law or agreed to in writing, |
| 14 | +# software distributed under the License is distributed on an |
| 15 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 16 | +# KIND, either express or implied. See the License for the |
| 17 | +# specific language governing permissions and limitations |
| 18 | +# under the License. |
| 19 | + |
| 20 | +# Should we clean-up? |
| 21 | +cleanup=1 |
| 22 | + |
| 23 | +clean_up_and_exit() { |
| 24 | + if [ "${cleanup}" -eq 1 ]; then |
| 25 | + echo |
| 26 | + git branch -D pr/${prId} >/dev/null 2>&1 |
| 27 | + rm ${jsonTmp} ${tmpMessageFile} >/dev/null 2>&1 |
| 28 | + fi |
| 29 | + exit $1 |
| 30 | +} |
| 31 | + |
| 32 | +# Arguments |
| 33 | +argument=$1 |
| 34 | +prId=${argument} |
| 35 | +force=0 |
| 36 | +if [[ "${2}" == "--force" ]]; then |
| 37 | + force=1 |
| 38 | +fi |
| 39 | + |
| 40 | +# Some of us got used to a git pr alias that you had to feed with the PR url |
| 41 | +# Let's make this script backwards compatible with the previous one. |
| 42 | +if [[ ${argument} =~ https://github.com.* ]]; then |
| 43 | + prId=$(echo "${argument}" | awk -F/ {'print $7'}) |
| 44 | + echo "INFO: Found PR id ${prId} from url" |
| 45 | +fi |
| 46 | + |
| 47 | +# Check the arguments |
| 48 | +if [ -z ${prId} ]; then |
| 49 | + echo "Usage: git pr pool-request-number [ --force ]" |
| 50 | + clean_up_and_exit 1 |
| 51 | +fi |
| 52 | + |
| 53 | +# Vars we need |
| 54 | +jsonTmp="${PWD}/${prId}.json" |
| 55 | +tmpMessageFile="${PWD}/.git-tmp-message.txt" |
| 56 | + |
| 57 | +# We need UTF-8 to support the GitHub '...' 3-dots-in-1-char, for example. |
| 58 | +export LANG="en_EN.UTF-8" |
| 59 | + |
| 60 | +if [ "${prId}" -eq "${prId}" 2>/dev/null ]; then |
| 61 | + # Get json data from Github API |
| 62 | + curl -s https://api.github.com/repos/apache/cloudstack/pulls/${prId} > ${jsonTmp} |
| 63 | +else |
| 64 | + echo "ERROR: Pull-request id must be an integer, not '${prId}'" |
| 65 | + clean_up_and_exit 1 |
| 66 | +fi |
| 67 | + |
| 68 | +# Get vars from the GitHub API and parse the returned json |
| 69 | +prAuthor=$(cat ${jsonTmp} | python -c " |
| 70 | +try: |
| 71 | + import sys, json |
| 72 | + print json.load(sys.stdin)['user']['login'].encode('utf-8').decode('ascii','ignore') |
| 73 | +except: |
| 74 | + print '' |
| 75 | +") |
| 76 | + |
| 77 | +prTitle=$(cat ${jsonTmp} | python -c " |
| 78 | +try: |
| 79 | + import sys, json |
| 80 | + print json.load(sys.stdin)['title'].encode('utf-8').decode('ascii','ignore') |
| 81 | +except: |
| 82 | + print '' |
| 83 | +") |
| 84 | + |
| 85 | +prBody=$(cat ${jsonTmp} | python -c " |
| 86 | +try: |
| 87 | + import sys, json |
| 88 | + print json.load(sys.stdin)['body'].encode('utf-8').decode('ascii','ignore') |
| 89 | +except: |
| 90 | + print '' |
| 91 | +") |
| 92 | + |
| 93 | +prOriginBranch=$(cat ${jsonTmp} | python -c " |
| 94 | +try: |
| 95 | + import sys, json |
| 96 | + print json.load(sys.stdin)['head']['label'].encode('utf-8').decode('ascii','ignore') |
| 97 | +except: |
| 98 | + print '' |
| 99 | +" | sed -e "s/:/\//") |
| 100 | + |
| 101 | +prState=$(cat ${jsonTmp} | python -c " |
| 102 | +try: |
| 103 | + import sys, json |
| 104 | + print json.load(sys.stdin)['state'].encode('utf-8').decode('ascii','ignore') |
| 105 | +except: |
| 106 | + print 'Unknown' |
| 107 | +") |
| 108 | + |
| 109 | +prMergeableState=$(cat ${jsonTmp} | python -c " |
| 110 | +try: |
| 111 | + import sys, json |
| 112 | + print json.load(sys.stdin)['mergeable_state'].encode('utf-8').decode('ascii','ignore') |
| 113 | +except: |
| 114 | + print 'Unknown' |
| 115 | +") |
| 116 | + |
| 117 | +prDestinationBranch=$(cat ${jsonTmp} | python -c " |
| 118 | +try: |
| 119 | + import sys, json |
| 120 | + print json.load(sys.stdin)['base']['ref'].encode('utf-8').decode('ascii','ignore') |
| 121 | +except: |
| 122 | + print 'Unknown' |
| 123 | +") |
| 124 | + |
| 125 | +prCommits=$(cat ${jsonTmp} | python -c " |
| 126 | +try: |
| 127 | + import sys, json |
| 128 | + print json.load(sys.stdin)['commits'] |
| 129 | +except: |
| 130 | + print 'Unknown' |
| 131 | +") |
| 132 | + |
| 133 | +# Do some sanity checking |
| 134 | +if [ ${#prAuthor} -eq 0 ]; then |
| 135 | + echo "ERROR: We couldn't grab the PR author. Something went wrong querying the GitHub API." |
| 136 | + clean_up_and_exit 1 |
| 137 | +fi |
| 138 | + |
| 139 | +if [ ${#prTitle} -eq 0 ]; then |
| 140 | + echo "ERROR: We couldn't grab the PR title. Something went wrong querying the GitHub API." |
| 141 | + clean_up_and_exit 1 |
| 142 | +fi |
| 143 | + |
| 144 | +if [ ${#prOriginBranch} -eq 0 ]; then |
| 145 | + echo "ERROR: We couldn't grab the PR branch name. Something went wrong querying the GitHub API." |
| 146 | + clean_up_and_exit 1 |
| 147 | +fi |
| 148 | + |
| 149 | +currentBranch=$(git branch | grep "^*" | sed -e "s/^[*] //") |
| 150 | +if [ "${prDestinationBranch}" != "${currentBranch}" ] && [ ${force} -lt 1 ]; then |
| 151 | + echo "ERROR: This PR is made against branch '${prDestinationBranch}' while your current checked out branch is '${currentBranch}'." |
| 152 | + echo "ERROR: Please make sure you're in the right branch and run this scipt again." |
| 153 | + clean_up_and_exit 1 |
| 154 | +elif [ "${prDestinationBranch}" != "${currentBranch}" ] && [ ${force} -eq 1 ]; then |
| 155 | + echo "WARNING: You used --force to merge to '${currentBranch}' while this PR is for branch '${prDestinationBranch}'." |
| 156 | +fi |
| 157 | + |
| 158 | +if [ "${prState}" != "open" ] && [ ${force} -lt 1 ]; then |
| 159 | + echo "ERROR: We couldn't merge the PR because the state is not 'open' but '${prState}'." |
| 160 | + echo "ERROR: In general it's a bad idea to merge closed PRs!" |
| 161 | + echo "ERROR: Run this script again with --force if you know what you're doing" |
| 162 | + echo "ERROR: (continuing work on an abandoned PR in which case you'd merge to a branch in your fork" |
| 163 | + echo "ERROR: and send that as a new PR). Ask for help on @dev if unsure." |
| 164 | + clean_up_and_exit 1 |
| 165 | +elif [ "${prState}" != "open" ] &&[ ${force} -eq 1 ]; then |
| 166 | + echo "WARNING: You used --force to merge a PR with state '${prState}'." |
| 167 | +fi |
| 168 | + |
| 169 | +if [ "${prMergeableState}" != "clean" ] && [ ${force} -lt 1 ]; then |
| 170 | + echo "ERROR: We couldn't merge the PR because it cannot be merged 'clean' (GitHub reports '${prMergeableState}')." |
| 171 | + echo "ERROR: This can be caused by a Travis build in progress, a failed Travis build or an unclean merge (conflicts)" |
| 172 | + echo "ERROR: Run this script again with --force if you know what you're doing. Ask for help on @dev if unsure." |
| 173 | + clean_up_and_exit 1 |
| 174 | +elif [ "${prMergeableState}" != "clean" ] && [ ${force} -eq 1 ]; then |
| 175 | + echo "WARNING: You used --force to merge a PR with non-clean merge state '${prMergeableState}'." |
| 176 | +fi |
| 177 | + |
| 178 | +github_remote=$(git remote -v | grep "apache/cloudstack.git" | head -n 1 | cut -f1) |
| 179 | +if [ ${#github_remote} -eq 0 ]; then |
| 180 | + echo "ERROR: We couldn't find a git remote pointing to 'apache/cloudstack.git' to merge the PR from." |
| 181 | + echo "INFO: Current remotes:" |
| 182 | + git remote -v |
| 183 | + clean_up_and_exit 1 |
| 184 | +fi |
| 185 | + |
| 186 | +echo "INFO: Using remote repository '${github_remote}' to fetch PR (should point to github.com/apache/cloudstack.git)" |
| 187 | +echo "INFO: PR #${prId} against branch '${prDestinationBranch}' from '${prAuthor}': '${prTitle}'" |
| 188 | +echo "INFO: has state '${prState}' and mergable state '${prMergeableState}', about to be merged in branch '${currentBranch}'." |
| 189 | + |
| 190 | +# Construct commit merge message |
| 191 | +echo "Merge pull request #${prId} from ${prOriginBranch}" > ${tmpMessageFile} |
| 192 | +echo "" >> ${tmpMessageFile} |
| 193 | +echo "${prTitle}${prBody}" >> ${tmpMessageFile} |
| 194 | + |
| 195 | +# Are you sure? |
| 196 | +echo "ATTENTION: Merging pull request #${prId} from ${prOriginBranch} into '${currentBranch}' branch in 5 seconds. CTRL+c to abort.." |
| 197 | +sec=5 |
| 198 | +while [ $sec -ge 0 ]; do |
| 199 | + echo -n "${sec} " |
| 200 | + sec=$((sec-1)) |
| 201 | + sleep 1 |
| 202 | +done |
| 203 | +echo "INFO: Executing the merge now.. Git output below:" |
| 204 | +echo "INFO: ***********************************************************************************" |
| 205 | + |
| 206 | +# Do the actual merge |
| 207 | +git fetch ${github_remote} pull/${prId}/head:pr/${prId} |
| 208 | +git merge --no-ff --log -m "$(cat .git-tmp-message.txt)" pr/${prId} |
| 209 | +if [ $? -eq 0 ]; then |
| 210 | + git commit --amend -s --allow-empty-message -m '' |
| 211 | +else |
| 212 | + echo "ERROR: Merge failed, aborting." |
| 213 | + git merge --abort |
| 214 | +fi |
| 215 | + |
| 216 | +# What's next |
| 217 | +echo "INFO: ***********************************************************************************" |
| 218 | +echo "INFO: Merged successfully! Please double check using 'git log -p' and 'git push' when you're sure." |
| 219 | +echo "INFO: About commits: there should be ${prCommits} from the PR plus 1 merge commit." |
| 220 | +echo |
| 221 | + |
| 222 | +clean_up_and_exit 0 |
0 commit comments