aboutsummaryrefslogtreecommitdiff
path: root/contrib/devtools/github-merge.sh
blob: a83a2a59be307cb72b0ad25ec173f16151c7959d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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 -q -1 "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 -q -1 "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