-
Notifications
You must be signed in to change notification settings - Fork 0
/
bb-merge.sh
executable file
·212 lines (191 loc) · 5.15 KB
/
bb-merge.sh
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
#!/usr/bin/env bash
function usage() {
echo "
Usage:
$0 \\
--workspace <workspace> \\
--repo <repo> \\
[?--action (dry-run|merge)] \\
[?--commits (all|first)] \\
<pr number>
Examples:
$0 --workspace my_workspace --repo my_repo 64
$0 --workspace my_workspace --repo my_repo --action merge 64
$0 --workspace my_workspace --repo my_repo --commits first 64
$0 --workspace my_workspace --repo my_repo --commits first --action merge 64
Flags:
-h | --help : Print this message
-w | --workspace : Bitbucket workspace
-r | --repo : Bitbucket repository
-c | --commits : Commits to include in message (all|first) (default: all)
-a | --action : Action for this script. (dry-run|merge) (default: dry-run)
"
}
WORKSPACE=
REPO=
PR_ID=
BB_USER=
COMMITS=all
ACTION=dry-run
while [ ! $# -eq 0 ]; do
case "$1" in
-h | --help)
usage
exit 0
;;
-w | --workspace)
shift
WORKSPACE=$1
;;
-r | --repo)
shift
REPO=$1
;;
-c | --commits)
shift
COMMITS=$1
;;
-a | --action)
shift
ACTION=$1
;;
*)
PR_ID=$1
break
;;
esac
shift
done
if [ -z "$WORKSPACE" ] || [ -z "$REPO" ] || [ -z "$PR_ID" ]; then
usage
exit 1
fi
if [ "$COMMITS" != all ] && [ "$COMMITS" != first ]; then
usage
exit 1
fi
if [ "$ACTION" != dry-run ] && [ "$ACTION" != merge ]; then
usage
exit 1
fi
# Ensure temporary .bb-creds file is removed
function cleanup {
rm -f .bb-creds
}
trap cleanup EXIT
# Read in username and app pass if they exist. They should be stored as
# `<var>=<value>` strings which are valid to evaluate.
eval $(2>/dev/null gpg -d .bb-creds.gpg)
if [ -z "$BB_APP_PW" ] || [ -z "$BB_USER" ]; then
echo "Input username (saved to .bb-creds.gpg file in current directory):"
read -p "> " BB_USER
echo "BB_USER=$BB_USER" > .bb-creds
echo "Input app password (saved to .bb-creds.gpg file in current directory):"
read -sp "(characters are hidden) > " BB_APP_PW
echo "BB_APP_PW=$BB_APP_PW" >> .bb-creds
gpg -c .bb-creds
rm .bb-creds
fi
# Commands should not fail from here on.
set -euo pipefail
echo
echo "Getting PR"
echo
# Care about:
# obj.id
# obj.title
# obj.participants[...].state === 'approved'
# obj.participants[...].user.display_name
fields="fields=participants.state,participants.user.display_name,id,title"
pr=$(curl --request GET \
-u $BB_USER:$BB_APP_PW \
--url "https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pullrequests/$PR_ID?$fields" \
--header 'Accept: application/json')
title=$(echo "$pr" | ./bb-obj.js pr title)
footer=$(echo "$pr" | ./bb-obj.js pr approvers)
echo "Getting commits"
echo
# Care about:
# obj.values[...].message (order is increasing age or decreasing recency)
# obj.next (for pagination)
fields="fields=values.message,next"
commits=$(curl --request GET \
-u $BB_USER:$BB_APP_PW \
--url "https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pullrequests/$PR_ID/commits?$fields")
body=$(echo "$commits" | ./bb-obj.js commits $COMMITS)
# note: "next" can **only** match the JSON key; any "next" in a JSON value
# (e.g. in a comment) will have the double-quotes escaped.
while [ $(echo $commits | grep -c '"next"') -eq 1 ]; do
next_url=$(echo $commits | sed 's/.*"next": "\([^"]\+\).*/\1/')
commits=$(curl --request GET \
-u $BB_USER:$BB_APP_PW \
--url "$next_url")
if [ $COMMITS == all ]; then
body=$(printf "%s\n\n%s" "$(echo "$commits" | ./bb-obj.js commits $COMMITS)" "$body")
else
body=$(echo "$commits" | ./bb-obj.js commits $COMMITS)
fi
done
if [ -z "$body" ]; then
# body can be empty if $COMMITS is "first" and no body in commit; only header
#
# Format commit as:
#
# title
# footer # footer includes a newline at the beginning if it exists
msg=$(printf "%s\n%s" "$title" "$footer")
else
# Format commit as:
#
# title
# # blank line
# body
# footer # footer includes a newline at the beginning if it exists
msg=$(printf "%s\n\n%s\n%s" "$title" "$body" "$footer")
msg=$(echo \
"$title
$body
$footer")
fi
echo
echo "Squashed commit message:"
echo
echo ------------------------------------------------------------------------
echo "$msg"
echo ------------------------------------------------------------------------
echo
# Exit early if doing a dry run.
if [ "$ACTION" == dry-run ]; then
exit 0
fi
# Double-quotes and newlines are escaped by sed and awk because the text is
# sent as JSON. Strip carriage returns as well, in case they exist.
#
# The awk `-vRS` sets the record separator to a regular expression meaning
# "empty string". Since any non-empty string won't match the regular
# expression, the record separator will never be found and the whole input
# string will be treated as one record.
commit_message=$(
echo -n "$msg" |
sed 's/"/\\\"/g' |
awk -vRS='^$' '{gsub(/\r/,"")}1' |
awk -vRS='^$' '{gsub(/\n/,"\\n")}1'
)
# Merge the PR
set +e
fields="fields=state,title"
ret=$(curl --request POST \
-u $BB_USER:$BB_APP_PW \
--url "https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pullrequests/$PR_ID/merge?$fields" \
--header 'Content-Type: application/json' \
-d "{
\"type\":\"squash\",
\"merge_strategy\":\"squash\",
\"close_source_branch\":true,
\"message\":\"$commit_message\"
}"
)
echo
echo Merge request returned:
echo $ret
echo