aboutsummaryrefslogtreecommitdiff
path: root/contrib/devtools
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@gmail.com>2013-11-22 05:49:51 +0100
committerWladimir J. van der Laan <laanwj@gmail.com>2013-11-24 12:44:31 +0100
commite5adec35c65b5194d0d4465b4d095f34e80dcc94 (patch)
tree85c90820804979bc77eb939a9c861becc1c14b39 /contrib/devtools
parentbe5d37c59757ac3b6deae1e3b238dcb2168207cb (diff)
contrib: add sipa's github-merge script
Diffstat (limited to 'contrib/devtools')
-rw-r--r--contrib/devtools/README.md38
-rwxr-xr-xcontrib/devtools/github-merge.sh173
2 files changed, 211 insertions, 0 deletions
diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md
new file mode 100644
index 0000000000..55d5d24cca
--- /dev/null
+++ b/contrib/devtools/README.md
@@ -0,0 +1,38 @@
+Contents
+===========
+This directory contains tools for developers working on this repository.
+
+github-merge.sh
+----------------
+
+A small script to automate merging pull-requests securely and sign them with GPG.
+
+For example:
+
+ ./github-merge.sh bitcoin/bitcoin 3077
+
+(in any git repository) will help you merge pull request #3077 for the
+bitcoin/bitcoin repository.
+
+What it does:
+* Fetch master and the pull request.
+* Locally construct a merge commit.
+* Show the diff that merge results in.
+* Ask you to verify the resulting source tree (so you can do a make
+check or whatever).
+* Ask you whether to GPG sign the merge commit.
+* Ask you whether to push the result upstream.
+
+This means that there are no potential race conditions (where a
+pullreq gets updated while you're reviewing it, but before you click
+merge), and when using GPG signatures, that even a compromised github
+couldn't mess with the sources.
+
+Setup
+---------
+Configuring the github-merge tool for the bitcoin repository is done in the following way:
+
+ git config githubmerge.repository bitcoin/bitcoin
+ git config githubmerge.testcmd "make -j4 check" (adapt to whatever you want to use for testing)
+ git config --global user.signingkey mykeyid (if you want to GPG sign)
+
diff --git a/contrib/devtools/github-merge.sh b/contrib/devtools/github-merge.sh
new file mode 100755
index 0000000000..e42b71a54a
--- /dev/null
+++ b/contrib/devtools/github-merge.sh
@@ -0,0 +1,173 @@
+#!/bin/bash
+
+# This script will locally construct a merge commit for a pull request on a
+# github repository, inspect it, sign it and optionally push it.
+
+# The following temporary branches are created/overwritten and deleted:
+# * pull/$PULL/base (the current master we're merging onto)
+# * pull/$PULL/head (the current state of the remote pull request)
+# * pull/$PULL/merge (github's merge)
+# * pull/$PULL/local-merge (our merge)
+
+# In case of a clean merge that is accepted by the user, the local branch with
+# name $BRANCH is overwritten with the merged result, and optionally pushed.
+
+REPO="$(git config --get githubmerge.repository)"
+if [[ "d$REPO" == "d" ]]; then
+ echo "ERROR: No repository configured. Use this command to set:" >&2
+ echo "git config githubmerge.repository <owner>/<repo>" >&2
+ echo "In addition, you can set the following variables:" >&2
+ echo "- githubmerge.host (default git@github.com)" >&2
+ echo "- githubmerge.branch (default master)" >&2
+ echo "- githubmerge.testcmd (default none)" >&2
+ exit 1
+fi
+
+HOST="$(git config --get githubmerge.host)"
+if [[ "d$HOST" == "d" ]]; then
+ HOST="git@github.com"
+fi
+
+BRANCH="$(git config --get githubmerge.branch)"
+if [[ "d$BRANCH" == "d" ]]; then
+ BRANCH="master"
+fi
+
+TESTCMD="$(git config --get githubmerge.testcmd)"
+
+PULL="$1"
+
+if [[ "d$PULL" == "d" ]]; then
+ echo "Usage: $0 pullnumber [branch]" >&2
+ exit 2
+fi
+
+if [[ "d$2" != "d" ]]; then
+ BRANCH="$2"
+fi
+
+# Initialize source branches.
+git checkout -q "$BRANCH"
+if git fetch -q "$HOST":"$REPO" "+refs/pull/$PULL/*:refs/heads/pull/$PULL/*"; then
+ if ! git log -1q "refs/heads/pull/$PULL/head" >/dev/null 2>&1; then
+ echo "ERROR: Cannot find head of pull request #$PULL on $HOST:$REPO." >&2
+ exit 3
+ fi
+ if ! git log -1q "refs/heads/pull/$PULL/merge" >/dev/null 2>&1; then
+ echo "ERROR: Cannot find merge of pull request #$PULL on $HOST:$REPO." >&2
+ exit 3
+ fi
+else
+ echo "ERROR: Cannot find pull request #$PULL on $HOST:$REPO." >&2
+ exit 3
+fi
+if git fetch -q "$HOST":"$REPO" +refs/heads/"$BRANCH":refs/heads/pull/"$PULL"/base; then
+ true
+else
+ echo "ERROR: Cannot find branch $BRANCH on $HOST:$REPO." >&2
+ exit 3
+fi
+git checkout -q pull/"$PULL"/base
+git branch -q -D pull/"$PULL"/local-merge 2>/dev/null
+git checkout -q -b pull/"$PULL"/local-merge
+TMPDIR="$(mktemp -d -t ghmXXXXX)"
+
+function cleanup() {
+ git checkout -q "$BRANCH"
+ git branch -q -D pull/"$PULL"/head 2>/dev/null
+ git branch -q -D pull/"$PULL"/base 2>/dev/null
+ git branch -q -D pull/"$PULL"/merge 2>/dev/null
+ git branch -q -D pull/"$PULL"/local-merge 2>/dev/null
+ rm -rf "$TMPDIR"
+}
+
+# Create unsigned merge commit.
+(
+ echo "Merge pull request #$PULL"
+ echo ""
+ git log --no-merges --topo-order --pretty='format:%h %s (%an)' pull/"$PULL"/base..pull/"$PULL"/head
+)>"$TMPDIR/message"
+if git merge -q --commit --no-edit --no-ff -m "$(<"$TMPDIR/message")" pull/"$PULL"/head; then
+ if [ "d$(git log --pretty='format:%s' -n 1)" != "dMerge pull request #$PULL" ]; then
+ echo "ERROR: Creating merge failed (already merged?)." >&2
+ cleanup
+ exit 4
+ fi
+else
+ echo "ERROR: Cannot be merged cleanly." >&2
+ git merge --abort
+ cleanup
+ exit 4
+fi
+
+# Run test command if configured.
+if [[ "d$TESTCMD" != "d" ]]; then
+ # Go up to the repository's root.
+ while [ ! -d .git ]; do cd ..; done
+ if ! $TESTCMD; then
+ echo "ERROR: Running $TESTCMD failed." >&2
+ cleanup
+ exit 5
+ fi
+ # Show the created merge.
+ git diff pull/"$PULL"/merge..pull/"$PULL"/local-merge >"$TMPDIR"/diff
+ git diff pull/"$PULL"/base..pull/"$PULL"/local-merge
+ if [[ "$(<"$TMPDIR"/diff)" != "" ]]; then
+ echo "WARNING: merge differs from github!" >&2
+ read -p "Type 'ignore' to continue. " -r >&2
+ if [[ "d$REPLY" =~ ^d[iI][gG][nN][oO][rR][eE]$ ]]; then
+ echo "Difference with github ignored." >&2
+ else
+ cleanup
+ exit 6
+ fi
+ fi
+ read -p "Press 'd' to accept the diff. " -n 1 -r >&2
+ echo
+ if [[ "d$REPLY" =~ ^d[dD]$ ]]; then
+ echo "Diff accepted." >&2
+ else
+ echo "ERROR: Diff rejected." >&2
+ cleanup
+ exit 6
+ fi
+else
+ # Verify the result.
+ echo "Dropping you on a shell so you can try building/testing the merged source." >&2
+ echo "Run 'git diff HEAD~' to show the changes being merged." >&2
+ echo "Type 'exit' when done." >&2
+ bash -i
+ read -p "Press 'm' to accept the merge. " -n 1 -r >&2
+ echo
+ if [[ "d$REPLY" =~ ^d[Mm]$ ]]; then
+ echo "Merge accepted." >&2
+ else
+ echo "ERROR: Merge rejected." >&2
+ cleanup
+ exit 7
+ fi
+fi
+
+# Sign the merge commit.
+read -p "Press 's' to sign off on the merge. " -n 1 -r >&2
+echo
+if [[ "d$REPLY" =~ ^d[Ss]$ ]]; then
+ if [[ "$(git config --get user.signingkey)" == "" ]]; then
+ echo "WARNING: No GPG signing key set, not signing. Set one using:" >&2
+ echo "git config --global user.signingkey <key>" >&2
+ git commit -q --signoff --amend --no-edit
+ else
+ git commit -q --gpg-sign --amend --no-edit
+ fi
+fi
+
+# Clean up temporary branches, and put the result in $BRANCH.
+git checkout -q "$BRANCH"
+git reset -q --hard pull/"$PULL"/local-merge
+cleanup
+
+# Push the result.
+read -p "Type 'push' to push the result to $HOST:$REPO, branch $BRANCH. " -r >&2
+if [[ "d$REPLY" =~ ^d[Pp][Uu][Ss][Hh]$ ]]; then
+ git push "$HOST":"$REPO" refs/heads/"$BRANCH"
+fi