|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# mangos-backport - a bash helper for backporting in git for MaNGOS project |
| 4 | +# Copyright (C) 2009 freghar <compmancz@gmail.com> |
| 5 | +# |
| 6 | +# This program is free software; you can redistribute it and/or modify |
| 7 | +# it under the terms of the GNU General Public License as published by |
| 8 | +# the Free Software Foundation; either version 2 of the License, or |
| 9 | +# (at your option) any later version. |
| 10 | +# |
| 11 | +# This program is distributed in the hope that it will be useful, |
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | +# GNU General Public License for more details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU General Public License |
| 17 | +# along with this program; if not, write to the Free Software |
| 18 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 19 | +# 02110-1301, USA |
| 20 | +# |
| 21 | + |
| 22 | +# The script works pretty simple - each time an empty commit is made, |
| 23 | +# copying author and message from the original one. |
| 24 | +# Then the original commit is cherry-picked -n (no commit) |
| 25 | +# and the changes are git commit --amended to the prepared empty commit. |
| 26 | + |
| 27 | +### general definitions |
| 28 | + |
| 29 | +## internals |
| 30 | +BATCH_PROCESS=0 |
| 31 | +VERBOSE=0 |
| 32 | + |
| 33 | +## user-tunable |
| 34 | +PORT_FROM="SD2-WotLK" |
| 35 | +AUTORESOLVE="sd2_revision_nr.h" |
| 36 | +GIT_AMEND_OPTS="-s" |
| 37 | +GIT_RECOVER="git reset --hard HEAD^" |
| 38 | +CONFLICT_RETVAL=2 # for batch usage |
| 39 | +GIT_PATH="./" |
| 40 | + |
| 41 | + |
| 42 | +### print error to stderr |
| 43 | +function print_error { |
| 44 | + echo -e "${@}" 1>&2 |
| 45 | +} |
| 46 | + |
| 47 | +### prints help |
| 48 | +function print_help { |
| 49 | + echo -e "Usage: ${0##*/} [-vb] <revspec>" \ |
| 50 | + "\nBackports a specified commit to current branch." \ |
| 51 | + "\n\n -b Batch processing (no interaction)." \ |
| 52 | + "\n (runs amend without calling \$EDITOR)" \ |
| 53 | + "\n -v Be verbose." \ |
| 54 | + "\n -n Never automatically commit." \ |
| 55 | + "\n" |
| 56 | +} |
| 57 | + |
| 58 | +### verbose print |
| 59 | +function verbose_print { |
| 60 | + [[ ${VERBOSE} > 0 ]] && echo "${@}" |
| 61 | +} |
| 62 | + |
| 63 | +### runs a command and handles it's output verbosity |
| 64 | +#function verbose_run { |
| 65 | +# if [[ ${VERBOSE} > 0 ]]; then |
| 66 | +# "${@}" |
| 67 | +# return $? |
| 68 | +# else |
| 69 | +# "${@}" 1 > /dev/null |
| 70 | +# return $? |
| 71 | +# fi |
| 72 | +#} |
| 73 | + |
| 74 | +### catches output of a command and returns it's retval |
| 75 | +#function catch_out { |
| 76 | +# pick_out=$(${@} 2>&1) |
| 77 | +# return $? |
| 78 | +#} |
| 79 | + |
| 80 | +### recover from an error, deleting empty commit |
| 81 | +function git_recover { |
| 82 | + print_error "----------" |
| 83 | + print_error "Caught an error, checking for last commit ..." |
| 84 | + |
| 85 | + # check if the last commit is empty, |
| 86 | + # ie. it has the same tree object dependency as it's parent |
| 87 | + local head_tree=$(git log -1 --pretty="%T" HEAD) |
| 88 | + local prev_tree=$(git log -1 --pretty="%T" HEAD^) |
| 89 | + if [[ $head_tree == $prev_tree ]]; then |
| 90 | + print_error "Last commit empty, running ${GIT_RECOVER}" \ |
| 91 | + "\nto previous HEAD (should be ${CURRENT_HEAD})." |
| 92 | + print_error "----------" |
| 93 | + ${GIT_RECOVER} |
| 94 | + else |
| 95 | + print_error "Last commit isn't empty (or git log failed) -" \ |
| 96 | + "something strange happened,\ncheck git log" \ |
| 97 | + "and do the cleanup (if needed) by hand." |
| 98 | + fi |
| 99 | + exit 1 |
| 100 | +} |
| 101 | + |
| 102 | +### amend the empty commit, assigning new tree |
| 103 | +function git_autoamend { |
| 104 | + |
| 105 | + # if the index is empty, there's nothing to amend |
| 106 | + if [[ -z $(git diff-index --cached HEAD) ]]; then |
| 107 | + git_retval=$? |
| 108 | + [[ $git_retval != 0 ]] && git_recover |
| 109 | + |
| 110 | + print_error "The index is empty, nothing to amend. This should" \ |
| 111 | + "not happen during normal\nworkflow, so you" \ |
| 112 | + "probably did something crazy like picking\na" \ |
| 113 | + "commit to a branch where it already exists." |
| 114 | + git_recover |
| 115 | + fi |
| 116 | + |
| 117 | + verbose_print "----------" |
| 118 | + if [[ ${BATCH_PROCESS} > 0 ]]; then |
| 119 | + if [[ ${VERBOSE} > 0 ]]; then |
| 120 | + git commit ${GIT_AMEND_OPTS} --amend -C HEAD |
| 121 | + git_retval=$? |
| 122 | + else |
| 123 | + git commit ${GIT_AMEND_OPTS} --amend -C HEAD 1> /dev/null |
| 124 | + git_retval=$? |
| 125 | + [[ $git_retval == 0 ]] && echo \ |
| 126 | + "Commit ${COMMIT_HASH} picked." |
| 127 | + fi |
| 128 | + else |
| 129 | + git commit ${GIT_AMEND_OPTS} --amend -c HEAD |
| 130 | + git_retval=$? |
| 131 | + fi |
| 132 | + [[ $git_retval != 0 ]] && git_recover |
| 133 | +} |
| 134 | + |
| 135 | + |
| 136 | +### main() |
| 137 | + |
| 138 | +## arguments |
| 139 | + |
| 140 | +# arg parsing |
| 141 | +while getopts "vbn" OPTION; do |
| 142 | + case $OPTION in |
| 143 | + v) |
| 144 | + VERBOSE=1 |
| 145 | + ;; |
| 146 | + b) |
| 147 | + BATCH_PROCESS=1 |
| 148 | + ;; |
| 149 | + n) |
| 150 | + NO_AUTOCOMMIT=1 |
| 151 | + ;; |
| 152 | + \?) |
| 153 | + print_help |
| 154 | + exit 1 |
| 155 | + ;; |
| 156 | + esac |
| 157 | +done; |
| 158 | +shift $(( $OPTIND - 1 )) |
| 159 | +ORIG_REF=${1} |
| 160 | + |
| 161 | +# check for needed arguments |
| 162 | +if [[ -z ${ORIG_REF} ]]; then |
| 163 | + print_help |
| 164 | + exit 1 |
| 165 | +fi |
| 166 | + |
| 167 | +## startup checks |
| 168 | + |
| 169 | +# check for needed commands |
| 170 | +for cmd in git grep sed wc; do |
| 171 | + if [[ -z $(which ${cmd}) ]]; then |
| 172 | + print_error "error: ${cmd}: command not found" |
| 173 | + exit 1 |
| 174 | + fi |
| 175 | +done; |
| 176 | + |
| 177 | +# are we in git root dir? |
| 178 | +if [[ ! -d .git/ ]]; then |
| 179 | + print_error "error: not in repository root directory" |
| 180 | + exit 1 |
| 181 | +fi |
| 182 | + |
| 183 | +# is the index clean? |
| 184 | +if [[ ! -z $(git diff-index HEAD) ]]; then |
| 185 | + print_error "error: dirty index, run mixed/hard reset first" |
| 186 | + exit 1 |
| 187 | +fi |
| 188 | + |
| 189 | +## original commit infos |
| 190 | + |
| 191 | +# current HEAD commit hash |
| 192 | +CURRENT_HEAD=$(git show -s --pretty=format:'%h' HEAD) |
| 193 | +[[ $? != 0 ]] && exit 1 |
| 194 | + |
| 195 | +# author with email |
| 196 | +COMMIT_AUTHOR=$(git show -s --pretty=format:'%an <%ae>' ${ORIG_REF}) |
| 197 | +[[ $? != 0 ]] && exit 1 |
| 198 | + |
| 199 | +# commit object hash (abbrev) |
| 200 | +COMMIT_HASH=$(git show -s --pretty=format:'%h' ${ORIG_REF}) |
| 201 | +[[ $? != 0 ]] && exit 1 |
| 202 | + |
| 203 | +# subject (with removed revision number) |
| 204 | +COMMIT_SUBJECT=$(git show -s --pretty=format:'%s' ${ORIG_REF} | sed -r 's/\[[a-z]?[0-9]*\] //') |
| 205 | +[[ $? != 0 ]] && exit 1 |
| 206 | +COMMIT_REVISION=$(git show -s --pretty=format:'%s' ${ORIG_REF} | sed -r 's/\[([a-z]?[0-9]*).*/\1/') |
| 207 | +[[ $? != 0 ]] && exit 1 |
| 208 | +if [ "$COMMIT_REVISION" != "" ]; then COMMIT_REVISION="[$COMMIT_REVISION] -"; fi |
| 209 | + |
| 210 | +# body |
| 211 | +COMMIT_BODY=$(git show -s --pretty=format:'%b' ${ORIG_REF}) |
| 212 | +[[ $? != 0 ]] && exit 1 |
| 213 | + |
| 214 | +# whole message (yea, it could be done without echo) |
| 215 | +COMMIT_MESSAGE=$(echo -e "${COMMIT_SUBJECT}\n\n${COMMIT_BODY}\n\n(based on commit $PORT_FROM$COMMIT_REVISION ${COMMIT_HASH})") |
| 216 | +[[ $? != 0 ]] && exit 1 |
| 217 | + |
| 218 | +## new empty commit ready, so create it |
| 219 | +verbose_print "Creating new empty commit on current HEAD (${CURRENT_HEAD}}." |
| 220 | +verbose_print "----------" |
| 221 | +if [[ ${VERBOSE} > 0 ]]; then |
| 222 | + git commit --author="${COMMIT_AUTHOR}" -m "${COMMIT_MESSAGE}" \ |
| 223 | + --allow-empty |
| 224 | + [[ $? != 0 ]] && exit 1 |
| 225 | +else |
| 226 | + git commit --author="${COMMIT_AUTHOR}" -m "${COMMIT_MESSAGE}" \ |
| 227 | + --allow-empty 1> /dev/null |
| 228 | + [[ $? != 0 ]] && exit 1 |
| 229 | +fi |
| 230 | + |
| 231 | + |
| 232 | +## first, try cherry-picking the commit and catch conflicts. |
| 233 | +## - if there are none, simply amend and exit |
| 234 | +## - if there are none related to $AUTORESOLVE, only prepare |
| 235 | +## the backported commit |
| 236 | +## - when multiple conflicts occur, including $AUTORESOLVE, |
| 237 | +## do the resolution for the one file, prepare the commit |
| 238 | +## and let user resolve the rest (+ amend) |
| 239 | +## - when only single ($AUTORESOLVE) conflict occur, resolve it |
| 240 | +## and fire up $EDITOR to autocommit |
| 241 | + |
| 242 | +pick_out=$(git cherry-pick -n ${ORIG_REF} 2>&1) |
| 243 | +pick_retval=$? |
| 244 | + |
| 245 | +# exit if there was a fatal app error |
| 246 | +if [[ $pick_retval > 1 ]]; then |
| 247 | + print_error "${pick_out}" |
| 248 | + git_recover |
| 249 | +fi |
| 250 | + |
| 251 | +# get a list of unmerged files |
| 252 | +unmerged_files=$(git diff-files --diff-filter=U | sed 's/^[^\t]*\t//') |
| 253 | +git_retval=$? |
| 254 | +if [[ $git_retval != 0 ]]; then |
| 255 | + print_error "${pick_out}" |
| 256 | + git_recover |
| 257 | +fi |
| 258 | + |
| 259 | +# simply amend if the pick was successful |
| 260 | +if [[ $pick_retval == 0 && -z $unmerged_files ]]; then |
| 261 | + verbose_print "${pick_out}" |
| 262 | + verbose_print "----------" |
| 263 | + if [[ ${NO_AUTOCOMMIT} > 0 ]]; then |
| 264 | + verbose_print "No conflicts to resolve, nothing to do." |
| 265 | + verbose_print "Please run git commit ${GIT_AMEND_OPTS} --amend" \ |
| 266 | + "after making all necessary changes." |
| 267 | + verbose_print "Use ${GIT_RECOVER} to recover." |
| 268 | + else |
| 269 | + verbose_print "No conflicts to resolve, running amend." |
| 270 | + git_autoamend |
| 271 | + fi |
| 272 | + exit 0 |
| 273 | +fi |
| 274 | + |
| 275 | +# sanity check |
| 276 | +if [[ -z $unmerged_files ]]; then |
| 277 | + print_error "${pick_out}" |
| 278 | + print_error "----------" |
| 279 | + print_error "git cherry-pick failed with status 1," \ |
| 280 | + "\nbut no unmerged files were found." |
| 281 | + git_recover |
| 282 | +fi |
| 283 | + |
| 284 | +# if $AUTORESOLVE isn't there (but other conflicts are), simply exit |
| 285 | +if [[ -z $(echo "${unmerged_files}" | grep ${AUTORESOLVE}) ]]; then |
| 286 | + print_error "${pick_out}" |
| 287 | + echo "----------" |
| 288 | + verbose_print "${AUTORESOLVE} not found as unmerged." |
| 289 | + echo "Please run git commit ${GIT_AMEND_OPTS} --amend" \ |
| 290 | + "after resolving all conflicts." |
| 291 | + echo "To recover from the resolution, use ${GIT_RECOVER}." |
| 292 | + exit ${CONFLICT_RETVAL} |
| 293 | +fi |
| 294 | + |
| 295 | +# do the resolution - use old version of the file |
| 296 | +if [[ -f ${AUTORESOLVE} ]]; then |
| 297 | + verbose_print "${pick_out}" |
| 298 | + verbose_print "----------" |
| 299 | + verbose_print "Auto-resolving ${AUTORESOLVE} using old version." |
| 300 | + git show :2:${AUTORESOLVE} > ${AUTORESOLVE} |
| 301 | + [[ $? != 0 ]] && git_recover |
| 302 | + git add ${AUTORESOLVE} |
| 303 | + [[ $? != 0 ]] && git_recover |
| 304 | +# echo "Resolution of ${AUTORESOLVE} finished successfuly." |
| 305 | +else |
| 306 | + print_error "${pick_out}" |
| 307 | + print_error "----------" |
| 308 | + print_error "error: ${AUTORESOLVE} not found, cannot resolve" |
| 309 | + git_recover |
| 310 | +fi |
| 311 | + |
| 312 | +# if $AUTORESOLVE was the only conflict, amend the commit |
| 313 | +if [[ $(echo "${unmerged_files}" | wc -l) == 1 ]]; then |
| 314 | + verbose_print "----------" |
| 315 | + if [[ ${NO_AUTOCOMMIT} > 0 ]]; then |
| 316 | + verbose_print "All done, autocommit disabled, nothing to do." |
| 317 | + verbose_print "Please run git commit ${GIT_AMEND_OPTS} --amend" \ |
| 318 | + "after making all necessary changes." |
| 319 | + verbose_print "Use ${GIT_RECOVER} to recover." |
| 320 | + else |
| 321 | + verbose_print "All done, running git commit ${GIT_AMEND_OPTS} --amend ..." |
| 322 | + git_autoamend |
| 323 | + fi |
| 324 | + exit 0 |
| 325 | + |
| 326 | +# else let the user do all other conflict resolutions |
| 327 | +else |
| 328 | + print_error "${pick_out}" |
| 329 | + echo "----------" |
| 330 | + echo "Please run git commit ${GIT_AMEND_OPTS} --amend" \ |
| 331 | + "after resolving all conflicts." |
| 332 | + echo "To recover from the resolution, use ${GIT_RECOVER}." |
| 333 | + exit ${CONFLICT_RETVAL} |
| 334 | +fi |
0 commit comments