Wladimir J. van der Laan
11 years ago
3 changed files with 215 additions and 0 deletions
@ -0,0 +1,38 @@
@@ -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) |
||||
|
@ -0,0 +1,173 @@
@@ -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 |
Loading…
Reference in new issue