Skip to content

Commit d4cda96

Browse files
committed
Merge pull request #3302
e5adec3 contrib: add sipa's github-merge script (Wladimir J. van der Laan)
2 parents eef8a67 + e5adec3 commit d4cda96

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

contrib/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ tests each pull and when master is tested using jenkins.
5252
### [Verify SF Binaries](/contrib/verifysfbinaries) ###
5353
This script attempts to download and verify the signature file SHA256SUMS.asc from SourceForge.
5454

55+
### [Developer tools](/control/devtools) ###
56+
Specific tools for developers working on this repository.
57+
Contains the script `github-merge.sh` for merging github pull requests securely and signing them using GPG.
58+

contrib/devtools/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Contents
2+
===========
3+
This directory contains tools for developers working on this repository.
4+
5+
github-merge.sh
6+
----------------
7+
8+
A small script to automate merging pull-requests securely and sign them with GPG.
9+
10+
For example:
11+
12+
./github-merge.sh bitcoin/bitcoin 3077
13+
14+
(in any git repository) will help you merge pull request #3077 for the
15+
bitcoin/bitcoin repository.
16+
17+
What it does:
18+
* Fetch master and the pull request.
19+
* Locally construct a merge commit.
20+
* Show the diff that merge results in.
21+
* Ask you to verify the resulting source tree (so you can do a make
22+
check or whatever).
23+
* Ask you whether to GPG sign the merge commit.
24+
* Ask you whether to push the result upstream.
25+
26+
This means that there are no potential race conditions (where a
27+
pullreq gets updated while you're reviewing it, but before you click
28+
merge), and when using GPG signatures, that even a compromised github
29+
couldn't mess with the sources.
30+
31+
Setup
32+
---------
33+
Configuring the github-merge tool for the bitcoin repository is done in the following way:
34+
35+
git config githubmerge.repository bitcoin/bitcoin
36+
git config githubmerge.testcmd "make -j4 check" (adapt to whatever you want to use for testing)
37+
git config --global user.signingkey mykeyid (if you want to GPG sign)
38+

contrib/devtools/github-merge.sh

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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

Comments
 (0)