summaryrefslogtreecommitdiffstats
path: root/github-merge-pr.sh
blob: 825ff0baeda84db1a62f7df7ffef22c70fffded0 (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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/bin/bash

# How to setup the repository to make this correctly work
# 1. Clone repo and make sure you can correctly push to that
#    (example with openwrt main repo you need to use ssh remote url)
# 2. Make sure you can correctly push and force push to the github
#    repository
#
# Make sure to install the extra git-filter-repo package.
#
# Usage: github-merge-pr.sh PR_NUMBER BRANCH REPO_NAME
#
# BRANCH is optional and defaults to main.
# REPO_NAME is optional and defaults to openwrt. It does
# describe the repository name to use to pull PR info from.

# Script .config variables
# Github repository owner or organization name.
# By default set to openwrt, set this line in .config to overwrite
# the default value.
GITHUB_REPO_OWNER="openwrt"

# Your repository token, generate this token at your profile page:
# - Navigate to https://github.com/settings/tokens
# - Click on "Generate new token"
# - Enter a description, e.g. "pr.sh" and pick the "repo" scope
# - Hit "Generate token"
# Set this line in .config to provide a GITHUB_TOKEN and add comments
#GITHUB_TOKEN="d41d8cd98f00b204e9800998ecf8427e"
#
# Set this line in .config to use SSH key to rebase PR branch
# GITHUB_USE_SSH=1

SCRIPT_DIR="$(dirname $0)"
[ -h $0 ] && SCRIPT_DIR="$(dirname $(readlink $0))"

# Everything in .config will overwrite the default values set up
[ -f "$SCRIPT_DIR/.config" ] && source "$SCRIPT_DIR"/.config

PRID="$1"
BRANCH="${2:-main}"
GITHUB_REPO_NAME="${3:-openwrt}"
DRY_RUN="$4"

GIT=git
REPO="$GITHUB_REPO_OWNER"/"$GITHUB_REPO_NAME"

yesno() {
	local prompt="$1"
	local default="${2:-n}"
	local input

	while [ 1 ]; do
		printf "%s y/n [%s] > " "$prompt" "$default"
		read input
		case "${input:-$default}" in
			y*) return 0 ;;
			n*) return 1 ;;
		esac
	done
}

if ! command -v jq &> /dev/null; then
	echo "jq could not be found! This script require jq!"
	exit 1
fi

if [ -z "$PRID" -o -n "${PRID//[0-9]*/}" ]; then
	echo "Usage: $0 <PR-ID> [rebase-branch] [dry-run]" >&2
	exit 1
fi

if [ -n "$DRY_RUN" ]; then
	GIT="echo git"
fi

if [ -z "$(git branch --list "$BRANCH")" ]; then
	echo "Given rebase branch '$BRANCH' does not exist!" >&2
	exit 2
fi

if [ -n "$GITHUB_TOKEN" ]; then
	CURL_CMD=" --user \"$GITHUB_TOKEN:x-oauth-basic\" "
else
	CURL_CMD=" "
fi

if ! PR_INFO="$(curl $CURL_CMD -f -s "https://api.github.com/repos/$REPO/pulls/$PRID")"; then
	echo "Failed fetch PR #$PRID info" >&2
	exit 3
fi

if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "false" ]; then
	echo "PR #$PRID can't be force pushed by maintainers. Trying to merge as is!" >&2
fi

if [ "$(echo "$PR_INFO" | jq -r ".mergeable")" == "false" ]; then
	echo "PR #$PRID is not mergeable for Github.com. Check the PR!" >&2
	exit 5
fi

echo "Pulling current $BRANCH from origin"
$GIT checkout $BRANCH
$GIT fetch origin $BRANCH

if ! $GIT rebase origin/$BRANCH; then
	echo "Failed to rebase $BRANCH with origin/$BRANCH" >&2
	exit 7
fi

PR_USER="$(echo "$PR_INFO" | jq -r ".head.user.login")"
PR_BRANCH="$(echo "$PR_INFO" | jq -r ".head.ref")"
LOCAL_PR_BRANCH="$PR_BRANCH"-"$PR_USER"
if [ "$GITHUB_USE_SSH" = "1" ]; then
	PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.ssh_url")"
else
	PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.html_url")"
fi

if ! $GIT remote get-url $PR_USER &> /dev/null || [ -n "$DRY_RUN" ]; then
	echo "Adding $PR_USER with repo $PR_REPO to remote"
	$GIT remote add  $PR_USER $PR_REPO
fi

echo "Fetching remote $PR_USER"
$GIT fetch $PR_USER $PR_BRANCH

if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ] || [ "$IGNORE_MERGEABLE" = "1" ]; then
	echo "Creating branch $LOCAL_PR_BRANCH for $PR_BRANCH"
	if ! $GIT checkout -b $LOCAL_PR_BRANCH $PR_USER/$PR_BRANCH; then
		echo "Failed to checkout new branch $PR_BRANCH from $PR_USER/$PR_BRANCH" >&2
		exit 8
	fi

	echo "Rebasing $LOCAL_PR_BRANCH on top of $BRANCH"
	if ! $GIT rebase origin/$BRANCH; then
		echo "Failed to rebase $PR_BRANCH with origin/$BRANCH" >&2
		exit 9
	fi

	# Remove any previous Link: https://github.com/$REPO/pull/$PRID tag
	if ! $GIT filter-repo --message-callback "
		return message.replace(b\"Link: https://github.com/$REPO/pull/$PRID\n\",b\"\")
	" --refs $BRANCH..$LOCAL_PR_BRANCH --force; then
		echo "Failed to remove previous Link: https://github.com/$REPO/pull/$PRID tag" >&2
		exit 9
	fi

	# Add to each commit Link: https://github.com/$REPO/pull/$PRID
	if ! $GIT filter-repo --message-callback "
		return message + b\"Link: https://github.com/$REPO/pull/$PRID\"
	" --refs $BRANCH..$LOCAL_PR_BRANCH --force; then
		echo "Failed to add Link: Pull Request tag" >&2
		exit 9
	fi

	# Remove any previous SoB tag if the Pull Request Author and Committer match
	if ! $GIT filter-repo --message-callback "
		return message.replace(b\"Signed-off-by: $(git config user.name) <$(git config user.email)>\n\",b\"\")
	" --refs $BRANCH..$LOCAL_PR_BRANCH --force; then
		echo "Failed to remove previous Committer SoB tag" >&2
		exit 9
	fi

	# Add Committer SoB to better reference who merged the thing last
	if ! $GIT rebase origin/$BRANCH $LOCAL_PR_BRANCH --exec "git commit -s --amend -C HEAD"; then
		echo "Failed to add Committer SoB tag" >&2
		exit 9
	fi

	if [ "$GITHUB_NO_PUSH" = "1" ]; then
		echo "next commands:"
		if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then
			echo "$GIT push $PR_USER HEAD:$PR_BRANCH --force"
			echo "$GIT checkout $BRANCH"
			echo "$GIT merge --ff-only $PR_USER/$PR_BRANCH"
		else
			echo "$GIT checkout $BRANCH"
			echo "$GIT merge --ff-only $LOCAL_PR_BRANCH"
		fi
		echo "$GIT push"
		echo "$GIT branch -D $LOCAL_PR_BRANCH"
		exit 20
	fi

	echo "Force pushing $LOCAL_PR_BRANCH to HEAD:$PR_BRANCH for $PR_USER"
	if ! $GIT push $PR_USER HEAD:$PR_BRANCH --force; then
		echo "Failed to force push HEAD to $PR_USER" >&2
		exit 10
	fi

	echo "Returning to $BRANCH"
	$GIT checkout $BRANCH
fi

if [ -n "$($GIT log origin/$BRANCH..HEAD)" ]; then
	echo "Working on dirty branch for $BRANCH! Please reset $BRANCH to origin/$BRANCH" >&2
	exit 11
fi

echo "Actually merging the PR #$PRID from branch $PR_USER/$PR_BRANCH"
if ! $GIT merge --ff-only $PR_USER/$PR_BRANCH; then
	echo "Failed to merge $PR_USER/$PR_BRANCH on $BRANCH" >&2
else
	if yesno "Push to openwrt $BRANCH" "y"; then
		echo "Pushing to openwrt git server"
		if ! $GIT push; then
			echo "Failed to push to $BRANCH but left branch as is." >&2
			exit 12
		fi

		# Default close comment
		COMMENT="Thanks! Rebased on top of $BRANCH and merged!"

		if [ -n "$GITHUB_TOKEN" ] && [ -z "$DRY_RUN" ]; then
			echo ""
			echo "Enter a comment and hit <enter> to close the PR at Github automatically now."
			echo "Hit <ctrl>-<c> to exit."
			echo ""
			echo "If you do not provide a comment, the default will be: "
			echo "[$COMMENT]"

			echo -n "Comment > "
			read usercomment

			echo "Sending message to PR..."

			comment="${usercomment:-$COMMENT}"
			comment="${comment//\\/\\\\}"
			comment="${comment//\"/\\\"}"
			comment="$(printf '{"body":"%s"}' "$comment")"

			if ! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request POST --data "$comment" "https://api.github.com/repos/$REPO/issues/$PRID/comments" || \
			! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request PATCH --data '{"state":"closed"}' "https://api.github.com/repos/$REPO/pulls/$PRID"
			then
				echo ""                                                     >&2
				echo "Something failed while sending comment to the PR via ">&2
				echo "the Github API, please review the state manually at " >&2
				echo "https://github.com/$REPO/pull/$PRID"                  >&2
				exit 6
			fi
		fi

		echo -e "\n"
		echo "The PR has been merged!"
		echo -e "\n"
	fi
fi

if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then
	echo "Deleting branch $LOCAL_PR_BRANCH"
	$GIT branch -D $LOCAL_PR_BRANCH
fi

exit 0