-
Notifications
You must be signed in to change notification settings - Fork 38.1k
contrib: add sipa's github-merge script #3302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the git calls to configure here for the bitcoin repository may make sense:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good idea