forked from cowboy/dotfiles
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gitpr
executable file
·280 lines (219 loc) · 8.13 KB
/
gitpr
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env bash
function help() {
pr=123
local_ref=foo
script="$(basename "$0") $pr"
cat <<HELP
-=[ GitHub Pull Request Helper - "Cowboy" Ben Alman - http://benalman.com/ ]=-
Usage: $(basename "$0") pull_request_id [ step ]
-=[ Description ]============================================================-
The "step" argument can be used to force execution of a particular step,
but by default this script will automatically choose the next step, from
STEP 1 to STEP 2 and finally to STEP 3. You'll have to execute STEP 4
manually, once everything is done.
-=[ Detailed workflow example ]==============================================-
For this example, assume PR $pr has been filed against the "$local_ref" branch.
-=[ STEP 1 ]=-
You run "$script" and this executes:
$(help_step1)
-=[ STEP 2 ]=-
You run "$script" and this executes:
$(help_step2)
-=[ STEP 3 ]=-
You run "$script" and this executes:
$(help_step3)
-=[ STEP 4 ]=-
You run "$script 4" (explicitly specifying the 4) and this executes:
$(help_step4)
-=[ Very important notes ]===================================================-
Before running this script, ensure that your local "$local_ref" branch is
up-to-date!
At the beginning of STEP 1, any existing "pr$pr" and "pr$pr-squash"
branches will be deleted.
At the beginning of STEP 2, any existing pr$pr-squash" branch will be
deleted.
If you want to hide per-step explanations, set ENV var GPR_SHH=1. For
example, create an alias like: alias gpr='GPR_SHH=1 gpr'
-=[ License ]================================================================-
Copyright (c) 2012 "Cowboy" Ben Alman
Licensed under the MIT license.
http://benalman.com/about/license/
HELP
[[ "$1" ]]; exit
}
function help_step1() {
echo " Fetch and rebase PR $pr into \"pr$pr\" branch"
[[ "$1" ]] && return
cat <<HELP
1. Fetch the repo and branch associated with PR $pr.
2. Checkout a new "pr$pr" branch at the HEAD of the fetched branch.
3. Rebase "$local_ref" branch onto "pr$pr" branch.
4. Display a list of files changed in the PR.
Note that you may need to resolve conflicts. If the rebase is successful,
test the PR and commit fixes. Commit as many times as necessary; It doesn't
matter because all PR-related commits will be squash merged in STEP 2.
HELP
}
function help_step2() {
echo " Perform squash merge into \"pr$pr-squash\" branch"
[[ "$1" ]] && return
cat <<HELP
1. Checkout a new "pr$pr-squash" branch from "$local_ref" branch.
2. Squash merge "pr$pr" branch into "pr$pr-squash" branch. (no commit)
3. Append "Closes gh-$pr." to SQUASH_MSG.
4. Commit using the PR branch's HEAD author name.
Since all the commits in the PR have been squashed into one commit, you will
need to edit the commit message. When done, inspect the commit log, ensuring
everything is perfect; you should see a single, beautiful commit.
HELP
}
function help_step3() {
echo " Perform final merge into \"$local_ref\" branch"
[[ "$1" ]] && return
cat <<HELP
1. Checkout "$local_ref" branch.
2. Merge "$pr-squash" branch into "$local_ref" branch.
If STEP 2 was successful, there should be no conflicts here. If there are,
you're doing it wrong. Just double-check the commit log and push when done.
HELP
}
function help_step4() {
echo " Cleanup temporary branches and tags"
[[ "$1" ]] && return
cat <<HELP
1. Temporary "pr$pr" and "pr$pr-squash" branches are deleted.
2. Temporary "_pr${pr}_author_head" tag is deleted.
HELP
}
function header() {
if [[ "$GPR_SHH" ]]; then
echo "-=[ STEP $1 ]=================================================================-"
else
echo "-=[ STEP $1 Overview ]========================================================-"
help_step$1
echo
echo "-=[ Actual ]=================================================================-"
fi
}
[[ ! "$1" || "$1" == "-h" || "$1" == "--help" ]] && help $1
# Generate and store an OAUTH token.
if [[ "$1" == "auth" ]]; then
read -p 'Enter GitHub username: '
json="$(curl -fsSL --data '{"note":"gpr","scopes":["repo"]}' https://api.github.com/authorizations -u "$REPLY")"
if [[ "$json" ]]; then
token="$(node -pe "($json).token")"
git config --global --remove-section gpr 2>/dev/null
git config --global --add gpr.token $token
echo "Authorization successful, token saved."
exit
else
echo "Error authorizing with GitHub, please try again."
exit 5
fi
fi
pr="$1"; shift
script="$(basename "$0") $pr"
branch="pr$pr"
repo="$(git remote show -n origin | perl -ne '/Fetch URL: .*github\.com[:\/](.*\/.*)\.git/ && print $1')"
# Let's fetch some JSON.
token="$(git config --get gpr.token)"
json="$(curl -fsSL "https://api.github.com/repos/$repo/pulls/$pr?access_token=$token" 2>/dev/null)"
if [[ $? != 0 || ! "$json" ]]; then
echo "Error fetching GitHub API data for $repo PR $pr!"
echo "If you're trying to access a private repo and haven't yet done so, please run"
echo "the \"$(basename "$0") auth\" command to generate a GitHub auth token."
exit 2
fi
# Let's parse some JSON.
remote_url="$(node -pe "($json).head.repo.git_url")"
remote_ref="$(node -pe "($json).head.ref")"
local_url="$(node -pe "($json).base.repo.git_url")"
local_ref="$(node -pe "($json).base.ref")"
num_commits="$(node -pe "($json).commits")"
# Let's get the project's .git folder.
git_dir="$(git rev-parse --show-toplevel)/.git"
function del_branch() {
if [[ "$(git branch | grep " $1\$")" ]]; then
git checkout "$local_ref" 2>/dev/null
git branch -D "$1" 2>/dev/null
fi
}
# Use the specified step, otherwise attempt to auto-detect it.
if [[ "$1" ]]; then
step=$1
elif [[ "$(git branch | grep " $branch-squash\$")" ]]; then
# STEP 3 should never auto-execute twice.
if [[ "$(git branch --contains "$(git rev-parse $branch-squash)" | grep " $local_ref\$")" ]]; then
echo "Error merging branch \"$branch-squash\" into \"$local_ref\" branch! (already done)"
echo
echo "Redo the last step with: $script 3"
exit 4
fi
step=3
elif [[ "$(git branch | grep " $branch\$")" ]]; then
step=2
else
step=1
fi
# Let's do some stuff.
if [[ $step == 1 ]]; then
header 1
# Clean up any prior work on this PR.
del_branch "$branch"
del_branch "$branch-squash"
# Fetch remote, create a branch, etc.
if [[ "$remote_url" == "$local_url" ]]; then
git fetch origin "$remote_ref"
else
git fetch "$remote_url" "$remote_ref"
fi
git checkout -b "$branch" FETCH_HEAD
# Save ref to last PR author commit for later use
git tag --force "_${branch}_author_head" FETCH_HEAD
# Rebase!
git rebase "$local_ref"
if [[ $? != 0 ]]; then
echo "Error while attempting rebase!"
exit 3
fi
echo
echo "Changed files in HEAD~$num_commits:"
git --no-pager diff --name-only HEAD~"$num_commits"
echo
echo "-=[ Next Steps ]=============================================================-"
echo "$(help_step2 1) with: $script"
echo " Or redo the current step with: $script 1"
elif [[ $step == 2 ]]; then
header 2
# Clean up any prior squashes for this PR.
del_branch "$branch-squash"
# Create branch and squash merge all commits.
git checkout -b "$branch-squash" "$local_ref"
git merge --squash "$branch"
# Append useful information to commit message.
squash_msg_file="$git_dir/SQUASH_MSG"
echo -e "\nCloses gh-$pr." >> "$squash_msg_file"
# Retrieve author name and email from stored commit, and commit.
author="$(git log "_${branch}_author_head" -n1 --format="%an <%ae>")"
git commit --author="$author"
echo
echo "-=[ Next Steps ]=============================================================-"
echo "$(help_step3 1) with: $script"
echo " Or redo the current step with: $script 2"
elif [[ $step == 3 ]]; then
header 3
# Actually merge squashed commits into branch.
git checkout "$local_ref"
git merge "$branch-squash"
echo
echo "-=[ Next Steps ]=============================================================-"
echo "$(help_step4 1) with: $script 4"
echo " Or redo the current step with: $script 3"
elif [[ $step == 4 ]]; then
header 4
del_branch "$branch"
del_branch "$branch-squash"
git tag -d "_${branch}_author_head" 2>/dev/null
echo
echo "All done."
fi