#! /bin/bash function usage_exit { >&2 echo "Usage: ghmerge or ghmerge [] -- ... Options may include addrev options and gitaddrev filter args. Option style arguments: --help Print this help and exit --tools Merge a tools PR (rather than openssl PR) --web Merge a web PR (rather than openssl PR) --remote Repo to merge with (rather than git.openssl.org), usually 'upstream' --target Merge target (rather than current branch), usually 'master' --ref A synonym for --target --cherry-pick [] Cherry-pick the last n (1 <= n <= 99, default: 1) commits, rather than rebasing --squash Squash new commits non-interactively (allows editing msg) --noautosquash Do not automatically squash fixups in interactive rebase --nobuild Do not call 'openssbuild' before merging Examples: ghmerge 12345 mattcaswell ghmerge 12345 paulidale t8m --nobuild --myemail=dev@ddvo.net ghmerge edd05b7^^^^..19692bb2c32 --squash -- 12345 levitte ghmerge 12345 slontis --target openssl-3.0 ghmerge --nobuild --target openssl-3.0 --cherry-pick 3 16051 paulidale" exit 9 } set -o errexit WHAT="" PICK=no INTERACTIVE=yes AUTOSQUASH="--autosquash" REMOTE="" TARGET="" BUILD=yes [ -z ${CC+x} ] && CC="ccache gcc" # opensslbuild will otherwise use "ccache clang-3.6" if [ ! -d .git ] ; then echo Not at a top-level git directory exit 1 fi PRNUM= TEAM="" ADDREVOPTS="" # Parse JCL. shopt -s extglob while [ $# -ne 0 ]; do case "$1" in --help) usage_exit ;; --tools) WHAT=tools ; BUILD=no ; shift ;; --web) WHAT=web ; BUILD=no ; shift ;; --cherry-pick) shift; PICK=1; if [ "$1" != "" ] && [ $1 -ge 1 ] 2>/dev/null && [ $1 -le 99 ]; then PICK=$1 ; shift fi ;; --noautosquash) AUTOSQUASH="" ; shift ;; --squash) INTERACTIVE=no ; shift ;; --nobuild) BUILD=no ; shift ;; --remote) if [ $# -lt 2 ] ; then echo "Missing argument of '$1'" usage_exit fi shift; REMOTE=$1; shift ;; --target|--ref) if [ $# -lt 2 ] ; then echo "Missing argument of '$1'" usage_exit fi shift; TARGET=$1; shift ;; --) if [ $# -lt 3 ] ; then echo "Missing ... after '--'" usage_exit fi shift; PRNUM=$1 ; shift TEAM="$TEAM $*" break ;; -*) # e.g., --verbose, --trivial, --myemail=... ADDREVOPTS="$ADDREVOPTS $1" shift ;; +([[:digit:]]) ) # e.g., 1453 PRNUM=$1; shift ;; @*) # e.g., @t8m TEAM="$TEAM $1"; shift ;; +([[:alnum:]-]) ) # e.g., levitte if [[ $1 =~ ^[0-9a-f]{7,}+$ ]]; then # e.g., edd05b7 ADDREVOPTS="$ADDREVOPTS $1" else TEAM="$TEAM $1" fi shift ;; *) # e.g., edd05b7^^^^..19692bb2c32 ADDREVOPTS="$ADDREVOPTS $1"; shift ;; esac done if [ "$WHAT" = "" ] ; then WHAT="openssl" else ADDREVOPTS="$ADDREVOPTS --$WHAT" fi ADDREVOPTS=${ADDREVOPTS# } # chop any leading ' ' [ "$REMOTE" = "" ] && REMOTE=`git remote -v | awk '/git.openssl.org.*(push)/{ print $1; }' | head -n 1` # usually this will be 'upstream' if [ "$REMOTE" = "" ] ; then echo Cannot find git remote with URL including 'git.openssl.org' exit 1 fi if [ "$PRNUM" = "" -o "$TEAM" = "" ] ; then usage_exit fi PR_URL=https://api.github.com/repos/openssl/$WHAT/pulls/$PRNUM if ! wget --quiet $PR_URL -O /tmp/gh$$; then echo "Error getting $PR_URL" exit 1 fi set -- `python -c ' from __future__ import print_function import json, sys; input = json.load(sys.stdin) print(str(input["head"]["label"]).replace(":", " "), str(input["head"]["repo"]["ssh_url"]))' /dev/null || true fi if [ "$CHERRYPICKING" == 1 ] ; then echo "Hint: maybe --cherry-pick was not given a suitable parameter." git cherry-pick --abort 2>/dev/null || true fi if [ "$ORIG_TARGET_HEAD" != "" ]; then echo Restoring original commit HEAD of $TARGET git reset --hard "$ORIG_TARGET_HEAD" fi if [ "$TARGET" != "$ORIG_REF" ] || [ "$WORK_USED" != "" ]; then echo Returning to previous branch $ORIG_REF git checkout -q $ORIG_REF fi if [ "$WORK_USED" != "" ]; then git branch -qD $WORK_USED fi if [ "$STASH_OUT" != "No local changes to save" ]; then git stash pop -q # restore original state of $ORIG_REF, pruning any leftover commits added locally fi } trap 'cleanup' EXIT [ "$TARGET" = "" ] && TARGET=$ORIG_REF if [ "$TARGET" != "$ORIG_REF" ]; then echo -n "Press Enter to checkout $TARGET: "; read foo git checkout $TARGET fi echo -n "Press Enter to pull the latest $REMOTE/$TARGET: "; read foo REBASING=1 git pull $REMOTE $TARGET || exit 1 REBASING= # append new commits from $REPO/$BRANCH if [ "$PICK" == "no" ]; then echo Rebasing $REPO/$BRANCH on $TARGET... git fetch $REPO $BRANCH && git checkout -b $WORK FETCH_HEAD WORK_USED=$WORK REBASING=1 git rebase $TARGET || (echo 'Fix or Ctrl-d to abort' ; read || exit 1) REBASING= else echo Cherry-picking $REPO/$BRANCH to $TARGET... git checkout -b $WORK $TARGET WORK_USED=$WORK CHERRYPICKING=1 git fetch $REPO $BRANCH && (git cherry-pick FETCH_HEAD~$PICK..FETCH_HEAD || exit 1) CHERRYPICKING= fi echo Diff against $REMOTE/$TARGET git diff $REMOTE/$TARGET if [ "$INTERACTIVE" == "yes" ] ; then echo -n "Press Enter to interactively rebase $AUTOSQUASH on $REMOTE/$TARGET: "; read foo REBASING=1 git rebase -i $AUTOSQUASH $REMOTE/$TARGET || exit 1 REBASING= echo "Calling addrev $ADDREVOPTS --prnum=$PRNUM $TEAM $REMOTE/$TARGET.." addrev $ADDREVOPTS --prnum=$PRNUM $TEAM $REMOTE/$TARGET.. fi echo echo Log since $REMOTE/$TARGET git log $REMOTE/$TARGET.. git checkout $TARGET if [ "$INTERACTIVE" != "yes" ] ; then echo -n "Press Enter to non-interactively merge --squash $BRANCH to $REMOTE/$TARGET: "; read foo ORIG_TARGET_HEAD=`git show -s --format="%H"` git merge --ff-only --no-commit --squash $WORK AUTHOR=`git show --no-patch --pretty="format:%an <%ae>" $WORK` git commit --author="$AUTHOR" addrev $ADDREVOPTS --prnum=$PRNUM $TEAM $REMOTE/${TARGET}.. else # echo -n "Press Enter to merge to $TARGET: "; read foo echo echo "Merging to $TARGET" ORIG_TARGET_HEAD=`git show -s --format="%H"` git merge --ff-only $WORK fi echo New log since $REMOTE/$TARGET git log $REMOTE/$TARGET.. if [ "$BUILD" == "yes" ] ; then echo Rebuilding... CC="$CC" opensslbuild >/dev/null # any STDERR output will be shown fi while true ; do echo -n "Enter 'y'/'yes' to push to $REMOTE/$TARGET or 'n'/'no' to abort: " read x x="`echo $x | tr A-Z a-z`" if [ "$x" = "y" -o "$x" = "yes" -o "$x" = "n" -o "$x" = "no" ] ; then break fi done if [ "$x" = "y" -o "$x" = "yes" ] ; then git push -v $REMOTE $TARGET ORIG_TARGET_HEAD= fi