|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# This script will locally construct a merge commit for a pull request on a |
| 4 | +# github repository, inspect it, sign it and optionally push it. |
| 5 | + |
| 6 | +# The following temporary branches are created/overwritten and deleted: |
| 7 | +# * pull/$PULL/base (the current master we're merging onto) |
| 8 | +# * pull/$PULL/head (the current state of the remote pull request) |
| 9 | +# * pull/$PULL/merge (github's merge) |
| 10 | +# * pull/$PULL/local-merge (our merge) |
| 11 | + |
| 12 | +# In case of a clean merge that is accepted by the user, the local branch with |
| 13 | +# name $BRANCH is overwritten with the merged result, and optionally pushed. |
| 14 | + |
| 15 | +REPO="$(git config --get githubmerge.repository)" |
| 16 | +if [[ "d$REPO" == "d" ]]; then |
| 17 | + echo "ERROR: No repository configured. Use this command to set:" >&2 |
| 18 | + echo "git config githubmerge.repository <owner>/<repo>" >&2 |
| 19 | + echo "In addition, you can set the following variables:" >&2 |
| 20 | + echo "- githubmerge.host (default [email protected])" >&2 |
| 21 | + echo "- githubmerge.branch (default master)" >&2 |
| 22 | + echo "- githubmerge.testcmd (default none)" >&2 |
| 23 | + exit 1 |
| 24 | +fi |
| 25 | + |
| 26 | +HOST="$(git config --get githubmerge.host)" |
| 27 | +if [[ "d$HOST" == "d" ]]; then |
| 28 | + |
| 29 | +fi |
| 30 | + |
| 31 | +BRANCH="$(git config --get githubmerge.branch)" |
| 32 | +if [[ "d$BRANCH" == "d" ]]; then |
| 33 | + BRANCH="master" |
| 34 | +fi |
| 35 | + |
| 36 | +TESTCMD="$(git config --get githubmerge.testcmd)" |
| 37 | + |
| 38 | +PULL="$1" |
| 39 | + |
| 40 | +if [[ "d$PULL" == "d" ]]; then |
| 41 | + echo "Usage: $0 pullnumber [branch]" >&2 |
| 42 | + exit 2 |
| 43 | +fi |
| 44 | + |
| 45 | +if [[ "d$2" != "d" ]]; then |
| 46 | + BRANCH="$2" |
| 47 | +fi |
| 48 | + |
| 49 | +# Initialize source branches. |
| 50 | +git checkout -q "$BRANCH" |
| 51 | +if git fetch -q "$HOST":"$REPO" "+refs/pull/$PULL/*:refs/heads/pull/$PULL/*"; then |
| 52 | + if ! git log -1q "refs/heads/pull/$PULL/head" >/dev/null 2>&1; then |
| 53 | + echo "ERROR: Cannot find head of pull request #$PULL on $HOST:$REPO." >&2 |
| 54 | + exit 3 |
| 55 | + fi |
| 56 | + if ! git log -1q "refs/heads/pull/$PULL/merge" >/dev/null 2>&1; then |
| 57 | + echo "ERROR: Cannot find merge of pull request #$PULL on $HOST:$REPO." >&2 |
| 58 | + exit 3 |
| 59 | + fi |
| 60 | +else |
| 61 | + echo "ERROR: Cannot find pull request #$PULL on $HOST:$REPO." >&2 |
| 62 | + exit 3 |
| 63 | +fi |
| 64 | +if git fetch -q "$HOST":"$REPO" +refs/heads/"$BRANCH":refs/heads/pull/"$PULL"/base; then |
| 65 | + true |
| 66 | +else |
| 67 | + echo "ERROR: Cannot find branch $BRANCH on $HOST:$REPO." >&2 |
| 68 | + exit 3 |
| 69 | +fi |
| 70 | +git checkout -q pull/"$PULL"/base |
| 71 | +git branch -q -D pull/"$PULL"/local-merge 2>/dev/null |
| 72 | +git checkout -q -b pull/"$PULL"/local-merge |
| 73 | +TMPDIR="$(mktemp -d -t ghmXXXXX)" |
| 74 | + |
| 75 | +function cleanup() { |
| 76 | + git checkout -q "$BRANCH" |
| 77 | + git branch -q -D pull/"$PULL"/head 2>/dev/null |
| 78 | + git branch -q -D pull/"$PULL"/base 2>/dev/null |
| 79 | + git branch -q -D pull/"$PULL"/merge 2>/dev/null |
| 80 | + git branch -q -D pull/"$PULL"/local-merge 2>/dev/null |
| 81 | + rm -rf "$TMPDIR" |
| 82 | +} |
| 83 | + |
| 84 | +# Create unsigned merge commit. |
| 85 | +( |
| 86 | + echo "Merge pull request #$PULL" |
| 87 | + echo "" |
| 88 | + git log --no-merges --topo-order --pretty='format:%h %s (%an)' pull/"$PULL"/base..pull/"$PULL"/head |
| 89 | +)>"$TMPDIR/message" |
| 90 | +if git merge -q --commit --no-edit --no-ff -m "$(<"$TMPDIR/message")" pull/"$PULL"/head; then |
| 91 | + if [ "d$(git log --pretty='format:%s' -n 1)" != "dMerge pull request #$PULL" ]; then |
| 92 | + echo "ERROR: Creating merge failed (already merged?)." >&2 |
| 93 | + cleanup |
| 94 | + exit 4 |
| 95 | + fi |
| 96 | +else |
| 97 | + echo "ERROR: Cannot be merged cleanly." >&2 |
| 98 | + git merge --abort |
| 99 | + cleanup |
| 100 | + exit 4 |
| 101 | +fi |
| 102 | + |
| 103 | +# Run test command if configured. |
| 104 | +if [[ "d$TESTCMD" != "d" ]]; then |
| 105 | + # Go up to the repository's root. |
| 106 | + while [ ! -d .git ]; do cd ..; done |
| 107 | + if ! $TESTCMD; then |
| 108 | + echo "ERROR: Running $TESTCMD failed." >&2 |
| 109 | + cleanup |
| 110 | + exit 5 |
| 111 | + fi |
| 112 | + # Show the created merge. |
| 113 | + git diff pull/"$PULL"/merge..pull/"$PULL"/local-merge >"$TMPDIR"/diff |
| 114 | + git diff pull/"$PULL"/base..pull/"$PULL"/local-merge |
| 115 | + if [[ "$(<"$TMPDIR"/diff)" != "" ]]; then |
| 116 | + echo "WARNING: merge differs from github!" >&2 |
| 117 | + read -p "Type 'ignore' to continue. " -r >&2 |
| 118 | + if [[ "d$REPLY" =~ ^d[iI][gG][nN][oO][rR][eE]$ ]]; then |
| 119 | + echo "Difference with github ignored." >&2 |
| 120 | + else |
| 121 | + cleanup |
| 122 | + exit 6 |
| 123 | + fi |
| 124 | + fi |
| 125 | + read -p "Press 'd' to accept the diff. " -n 1 -r >&2 |
| 126 | + echo |
| 127 | + if [[ "d$REPLY" =~ ^d[dD]$ ]]; then |
| 128 | + echo "Diff accepted." >&2 |
| 129 | + else |
| 130 | + echo "ERROR: Diff rejected." >&2 |
| 131 | + cleanup |
| 132 | + exit 6 |
| 133 | + fi |
| 134 | +else |
| 135 | + # Verify the result. |
| 136 | + echo "Dropping you on a shell so you can try building/testing the merged source." >&2 |
| 137 | + echo "Run 'git diff HEAD~' to show the changes being merged." >&2 |
| 138 | + echo "Type 'exit' when done." >&2 |
| 139 | + bash -i |
| 140 | + read -p "Press 'm' to accept the merge. " -n 1 -r >&2 |
| 141 | + echo |
| 142 | + if [[ "d$REPLY" =~ ^d[Mm]$ ]]; then |
| 143 | + echo "Merge accepted." >&2 |
| 144 | + else |
| 145 | + echo "ERROR: Merge rejected." >&2 |
| 146 | + cleanup |
| 147 | + exit 7 |
| 148 | + fi |
| 149 | +fi |
| 150 | + |
| 151 | +# Sign the merge commit. |
| 152 | +read -p "Press 's' to sign off on the merge. " -n 1 -r >&2 |
| 153 | +echo |
| 154 | +if [[ "d$REPLY" =~ ^d[Ss]$ ]]; then |
| 155 | + if [[ "$(git config --get user.signingkey)" == "" ]]; then |
| 156 | + echo "WARNING: No GPG signing key set, not signing. Set one using:" >&2 |
| 157 | + echo "git config --global user.signingkey <key>" >&2 |
| 158 | + git commit -q --signoff --amend --no-edit |
| 159 | + else |
| 160 | + git commit -q --gpg-sign --amend --no-edit |
| 161 | + fi |
| 162 | +fi |
| 163 | + |
| 164 | +# Clean up temporary branches, and put the result in $BRANCH. |
| 165 | +git checkout -q "$BRANCH" |
| 166 | +git reset -q --hard pull/"$PULL"/local-merge |
| 167 | +cleanup |
| 168 | + |
| 169 | +# Push the result. |
| 170 | +read -p "Type 'push' to push the result to $HOST:$REPO, branch $BRANCH. " -r >&2 |
| 171 | +if [[ "d$REPLY" =~ ^d[Pp][Uu][Ss][Hh]$ ]]; then |
| 172 | + git push "$HOST":"$REPO" refs/heads/"$BRANCH" |
| 173 | +fi |
0 commit comments