]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | #!/usr/bin/env bash |
2 | set -e | |
11fdf7f2 | 3 | # |
9f95a23c TL |
4 | # ceph-backport.sh - Ceph backporting script |
5 | # | |
6 | # Credits: This script is based on work done by Loic Dachary | |
7 | # | |
8 | # | |
9 | # This script automates the process of staging a backport starting from a | |
10 | # Backport tracker issue. | |
11 | # | |
12 | # Setup: | |
13 | # | |
14 | # ceph-backport.sh --setup | |
15 | # | |
16 | # Usage and troubleshooting: | |
17 | # | |
18 | # ceph-backport.sh --help | |
19 | # ceph-backport.sh --usage | less | |
20 | # ceph-backport.sh --troubleshooting | less | |
21 | # | |
22 | ||
23 | full_path="$0" | |
24 | ||
f67539c2 | 25 | SCRIPT_VERSION="16.0.0.6848" |
9f95a23c TL |
26 | active_milestones="" |
27 | backport_pr_labels="" | |
28 | backport_pr_number="" | |
29 | backport_pr_title="" | |
30 | backport_pr_url="" | |
31 | deprecated_backport_common="$HOME/bin/backport_common.sh" | |
32 | existing_pr_milestone_number="" | |
33 | github_token="" | |
34 | github_token_file="$HOME/.github_token" | |
35 | github_user="" | |
36 | milestone="" | |
37 | non_interactive="" | |
38 | original_issue="" | |
39 | original_issue_url="" | |
40 | original_pr="" | |
41 | original_pr_url="" | |
42 | redmine_key="" | |
43 | redmine_key_file="$HOME/.redmine_key" | |
44 | redmine_login="" | |
45 | redmine_user_id="" | |
46 | setup_ok="" | |
47 | this_script=$(basename "$full_path") | |
48 | ||
49 | if [[ $* == *--debug* ]]; then | |
50 | set -x | |
51 | fi | |
52 | ||
53 | # associative array keyed on "component" strings from PR titles, mapping them to | |
54 | # GitHub PR labels that make sense in backports | |
55 | declare -A comp_hash=( | |
56 | ["auth"]="core" | |
57 | ["bluestore"]="bluestore" | |
58 | ["build/ops"]="build/ops" | |
59 | ["ceph.spec"]="build/ops" | |
60 | ["ceph-volume"]="ceph-volume" | |
61 | ["cephfs"]="cephfs" | |
62 | ["cmake"]="build/ops" | |
63 | ["config"]="config" | |
64 | ["client"]="cephfs" | |
65 | ["common"]="common" | |
66 | ["core"]="core" | |
67 | ["dashboard"]="dashboard" | |
68 | ["deb"]="build/ops" | |
69 | ["doc"]="documentation" | |
70 | ["grafana"]="monitoring" | |
71 | ["mds"]="cephfs" | |
72 | ["messenger"]="core" | |
73 | ["mon"]="core" | |
74 | ["msg"]="core" | |
75 | ["mgr/dashboard"]="dashboard" | |
76 | ["mgr/prometheus"]="monitoring" | |
77 | ["mgr"]="core" | |
78 | ["monitoring"]="monitoring" | |
79 | ["orch"]="orchestrator" | |
80 | ["osd"]="core" | |
81 | ["perf"]="performance" | |
82 | ["prometheus"]="monitoring" | |
83 | ["pybind"]="pybind" | |
84 | ["py3"]="python3" | |
85 | ["python3"]="python3" | |
86 | ["qa"]="tests" | |
87 | ["rbd"]="rbd" | |
88 | ["rgw"]="rgw" | |
89 | ["rpm"]="build/ops" | |
90 | ["tests"]="tests" | |
91 | ["tool"]="tools" | |
92 | ) | |
93 | ||
94 | declare -A flagged_pr_hash=() | |
95 | ||
96 | function abort_due_to_setup_problem { | |
97 | error "problem detected in your setup" | |
98 | info "Run \"${this_script} --setup\" to fix" | |
99 | false | |
100 | } | |
101 | ||
102 | function assert_fail { | |
103 | local message="$1" | |
104 | error "(internal error) $message" | |
105 | info "This could be reported as a bug!" | |
106 | false | |
107 | } | |
108 | ||
109 | function backport_pr_needs_label { | |
110 | local check_label="$1" | |
111 | local label | |
112 | local needs_label="yes" | |
113 | while read -r label ; do | |
114 | if [ "$label" = "$check_label" ] ; then | |
115 | needs_label="" | |
116 | fi | |
117 | done <<< "$backport_pr_labels" | |
118 | echo "$needs_label" | |
119 | } | |
120 | ||
121 | function backport_pr_needs_milestone { | |
122 | if [ "$existing_pr_milestone_number" ] ; then | |
123 | echo "" | |
124 | else | |
125 | echo "yes" | |
126 | fi | |
127 | } | |
128 | ||
129 | function bail_out_github_api { | |
130 | local api_said="$1" | |
131 | local hint="$2" | |
132 | info "GitHub API said:" | |
133 | log bare "$api_said" | |
134 | if [ "$hint" ] ; then | |
135 | info "(hint) $hint" | |
136 | fi | |
137 | abort_due_to_setup_problem | |
138 | } | |
139 | ||
140 | function blindly_set_pr_metadata { | |
141 | local pr_number="$1" | |
142 | local json_blob="$2" | |
143 | curl -u ${github_user}:${github_token} --silent --data-binary "$json_blob" "https://api.github.com/repos/ceph/ceph/issues/${pr_number}" >/dev/null 2>&1 || true | |
144 | } | |
145 | ||
146 | function check_milestones { | |
147 | local milestones_to_check | |
148 | milestones_to_check="$(echo "$1" | tr '\n' ' ' | xargs)" | |
149 | info "Active milestones: $milestones_to_check" | |
150 | for m in $milestones_to_check ; do | |
151 | info "Examining all PRs targeting base branch \"$m\"" | |
152 | vet_prs_for_milestone "$m" | |
153 | done | |
154 | dump_flagged_prs | |
155 | } | |
156 | ||
157 | function check_tracker_status { | |
158 | local -a ok_statuses=("new" "need more info") | |
159 | local ts="$1" | |
160 | local error_msg | |
161 | local tslc="${ts,,}" | |
162 | local tslc_is_ok= | |
163 | for oks in "${ok_statuses[@]}"; do | |
164 | if [ "$tslc" = "$oks" ] ; then | |
165 | debug "Tracker status $ts is OK for backport to proceed" | |
166 | tslc_is_ok="yes" | |
167 | break | |
168 | fi | |
169 | done | |
170 | if [ "$tslc_is_ok" ] ; then | |
171 | true | |
172 | else | |
173 | if [ "$tslc" = "in progress" ] ; then | |
174 | error_msg="backport $redmine_url is already in progress" | |
175 | else | |
176 | error_msg="backport $redmine_url is closed (status: ${ts})" | |
177 | fi | |
178 | if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then | |
179 | warning "$error_msg" | |
180 | else | |
181 | error "$error_msg" | |
182 | fi | |
183 | fi | |
184 | echo "$tslc_is_ok" | |
185 | } | |
186 | ||
187 | function cherry_pick_phase { | |
188 | local base_branch | |
189 | local default_val | |
190 | local i | |
191 | local merged | |
192 | local number_of_commits | |
193 | local offset | |
194 | local sha1_to_cherry_pick | |
195 | local singular_or_plural_commit | |
196 | local yes_or_no_answer | |
197 | populate_original_issue | |
198 | if [ -z "$original_issue" ] ; then | |
199 | error "Could not find original issue" | |
200 | info "Does ${redmine_url} have a \"Copied from\" relation?" | |
201 | false | |
202 | fi | |
203 | info "Parent issue: ${original_issue_url}" | |
204 | ||
205 | populate_original_pr | |
206 | if [ -z "$original_pr" ]; then | |
207 | error "Could not find original PR" | |
208 | info "Is the \"Pull request ID\" field of ${original_issue_url} populated?" | |
209 | false | |
210 | fi | |
211 | info "Parent issue ostensibly fixed by: ${original_pr_url}" | |
212 | ||
213 | verbose "Examining ${original_pr_url}" | |
214 | remote_api_output=$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${original_pr}") | |
215 | base_branch=$(echo "${remote_api_output}" | jq -r '.base.label') | |
216 | if [ "$base_branch" = "ceph:master" ] ; then | |
217 | true | |
218 | else | |
219 | if [ "$FORCE" ] ; then | |
220 | warning "base_branch ->$base_branch<- is something other than \"ceph:master\"" | |
221 | info "--force was given, so continuing anyway" | |
222 | else | |
223 | error "${original_pr_url} is targeting ${base_branch}: cowardly refusing to perform automated cherry-pick" | |
224 | info "Out of an abundance of caution, the script only automates cherry-picking of commits from PRs targeting \"ceph:master\"." | |
225 | info "You can still use the script to stage the backport, though. Just prepare the local branch \"${local_branch}\" manually and re-run the script." | |
226 | false | |
227 | fi | |
228 | fi | |
229 | merged=$(echo "${remote_api_output}" | jq -r '.merged') | |
230 | if [ "$merged" = "true" ] ; then | |
231 | true | |
232 | else | |
233 | error "${original_pr_url} is not merged yet" | |
234 | info "Cowardly refusing to perform automated cherry-pick" | |
235 | false | |
236 | fi | |
237 | number_of_commits=$(echo "${remote_api_output}" | jq '.commits') | |
238 | if [ "$number_of_commits" -eq "$number_of_commits" ] 2>/dev/null ; then | |
239 | # \$number_of_commits is set, and is an integer | |
240 | if [ "$number_of_commits" -eq "1" ] ; then | |
241 | singular_or_plural_commit="commit" | |
242 | else | |
243 | singular_or_plural_commit="commits" | |
244 | fi | |
245 | else | |
246 | error "Could not determine the number of commits in ${original_pr_url}" | |
247 | bail_out_github_api "$remote_api_output" | |
248 | fi | |
249 | info "Found $number_of_commits $singular_or_plural_commit in $original_pr_url" | |
250 | ||
251 | set -x | |
252 | git fetch "$upstream_remote" | |
253 | ||
254 | if git show-ref --verify --quiet "refs/heads/$local_branch" ; then | |
255 | if [ "$FORCE" ] ; then | |
256 | if [ "$non_interactive" ] ; then | |
257 | git checkout "$local_branch" | |
258 | git reset --hard "${upstream_remote}/${milestone}" | |
259 | else | |
260 | echo | |
261 | echo "A local branch $local_branch already exists and the --force option was given." | |
262 | echo "If you continue, any local changes in $local_branch will be lost!" | |
263 | echo | |
264 | default_val="y" | |
265 | echo -n "Do you really want to overwrite ${local_branch}? (default: ${default_val}) " | |
266 | yes_or_no_answer="$(get_user_input "$default_val")" | |
267 | [ "$yes_or_no_answer" ] && yes_or_no_answer="${yes_or_no_answer:0:1}" | |
268 | if [ "$yes_or_no_answer" = "y" ] ; then | |
269 | git checkout "$local_branch" | |
270 | git reset --hard "${upstream_remote}/${milestone}" | |
271 | else | |
272 | info "OK, bailing out!" | |
273 | false | |
274 | fi | |
275 | fi | |
276 | else | |
277 | set +x | |
278 | maybe_restore_set_x | |
279 | error "Cannot initialize $local_branch - local branch already exists" | |
280 | false | |
281 | fi | |
282 | else | |
283 | git checkout "${upstream_remote}/${milestone}" -b "$local_branch" | |
284 | fi | |
285 | ||
286 | git fetch "$upstream_remote" "pull/$original_pr/head:pr-$original_pr" | |
287 | ||
288 | set +x | |
289 | maybe_restore_set_x | |
290 | info "Attempting to cherry pick $number_of_commits commits from ${original_pr_url} into local branch $local_branch" | |
291 | offset="$((number_of_commits - 1))" || true | |
292 | for ((i=offset; i>=0; i--)) ; do | |
293 | info "Running \"git cherry-pick -x\" on $(git log --oneline --max-count=1 --no-decorate "pr-${original_pr}~${i}")" | |
294 | sha1_to_cherry_pick=$(git rev-parse --verify "pr-${original_pr}~${i}") | |
295 | set -x | |
296 | if git cherry-pick -x "$sha1_to_cherry_pick" ; then | |
297 | set +x | |
298 | maybe_restore_set_x | |
299 | else | |
300 | set +x | |
301 | maybe_restore_set_x | |
302 | [ "$VERBOSE" ] && git status | |
303 | error "Cherry pick failed" | |
304 | info "Next, manually fix conflicts and complete the current cherry-pick" | |
305 | if [ "$i" -gt "0" ] >/dev/null 2>&1 ; then | |
306 | info "Then, cherry-pick the remaining commits from ${original_pr_url}, i.e.:" | |
307 | for ((j=i-1; j>=0; j--)) ; do | |
308 | info "-> missing commit: $(git log --oneline --max-count=1 --no-decorate "pr-${original_pr}~${j}")" | |
309 | done | |
310 | info "Finally, re-run the script" | |
311 | else | |
312 | info "Then re-run the script" | |
313 | fi | |
314 | false | |
315 | fi | |
316 | done | |
317 | info "Cherry picking completed without conflicts" | |
318 | } | |
319 | ||
320 | function clear_line { | |
321 | log overwrite " \r" | |
322 | } | |
323 | ||
324 | function clip_pr_body { | |
325 | local pr_body="$*" | |
326 | local clipped="" | |
327 | local last_line_was_blank="" | |
328 | local line="" | |
329 | local pr_json_tempfile=$(mktemp) | |
330 | echo "$pr_body" | sed -n '/<!--.*/q;p' > "$pr_json_tempfile" | |
331 | while IFS= read -r line; do | |
332 | if [ "$(trim_whitespace "$line")" ] ; then | |
333 | last_line_was_blank="" | |
334 | clipped="${clipped}${line}\n" | |
335 | else | |
336 | if [ "$last_line_was_blank" ] ; then | |
337 | true | |
338 | else | |
339 | clipped="${clipped}\n" | |
340 | fi | |
341 | fi | |
342 | done < "$pr_json_tempfile" | |
343 | rm "$pr_json_tempfile" | |
344 | echo "$clipped" | |
345 | } | |
346 | ||
347 | function debug { | |
348 | log debug "$@" | |
349 | } | |
350 | ||
351 | function display_version_message_and_exit { | |
f67539c2 | 352 | echo "$this_script: Ceph backporting script, version $SCRIPT_VERSION" |
9f95a23c TL |
353 | exit 0 |
354 | } | |
355 | ||
356 | function dump_flagged_prs { | |
357 | local url= | |
358 | clear_line | |
359 | if [ "${#flagged_pr_hash[@]}" -eq "0" ] ; then | |
360 | info "All backport PRs appear to have milestone set correctly" | |
361 | else | |
362 | warning "Some backport PRs had problematic milestone settings" | |
363 | log bare "===========" | |
364 | log bare "Flagged PRs" | |
365 | log bare "-----------" | |
366 | for url in "${!flagged_pr_hash[@]}" ; do | |
367 | log bare "$url - ${flagged_pr_hash[$url]}" | |
368 | done | |
369 | log bare "===========" | |
370 | fi | |
371 | } | |
372 | ||
373 | function eol { | |
374 | local mtt="$1" | |
375 | error "$mtt is EOL" | |
376 | false | |
377 | } | |
378 | ||
379 | function error { | |
380 | log error "$@" | |
381 | } | |
382 | ||
383 | function existing_pr_routine { | |
384 | local base_branch | |
385 | local clipped_pr_body | |
386 | local new_pr_body | |
387 | local new_pr_title | |
388 | local pr_body | |
389 | local pr_json_tempfile | |
390 | local remote_api_output | |
391 | local update_pr_body | |
392 | remote_api_output="$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${backport_pr_number}")" | |
393 | backport_pr_title="$(echo "$remote_api_output" | jq -r '.title')" | |
394 | if [ "$backport_pr_title" = "null" ] ; then | |
395 | error "could not get PR title of existing PR ${backport_pr_number}" | |
396 | bail_out_github_api "$remote_api_output" | |
397 | fi | |
398 | existing_pr_milestone_number="$(echo "$remote_api_output" | jq -r '.milestone.number')" | |
399 | if [ "$existing_pr_milestone_number" = "null" ] ; then | |
400 | existing_pr_milestone_number="" | |
401 | fi | |
402 | backport_pr_labels="$(echo "$remote_api_output" | jq -r '.labels[].name')" | |
403 | pr_body="$(echo "$remote_api_output" | jq -r '.body')" | |
404 | if [ "$pr_body" = "null" ] ; then | |
405 | error "could not get PR body of existing PR ${backport_pr_number}" | |
406 | bail_out_github_api "$remote_api_output" | |
407 | fi | |
408 | base_branch=$(echo "${remote_api_output}" | jq -r '.base.label') | |
409 | base_branch="${base_branch#ceph:}" | |
410 | if [ -z "$(is_active_milestone "$base_branch")" ] ; then | |
411 | error "existing PR $backport_pr_url is targeting $base_branch which is not an active milestone" | |
412 | info "Cowardly refusing to work on a backport to $base_branch" | |
413 | false | |
414 | fi | |
415 | clipped_pr_body="$(clip_pr_body "$pr_body")" | |
416 | verbose_en "Clipped body of existing PR ${backport_pr_number}:\n${clipped_pr_body}" | |
417 | if [[ "$backport_pr_title" =~ ^${milestone}: ]] ; then | |
418 | verbose "Existing backport PR ${backport_pr_number} title has ${milestone} prepended" | |
419 | else | |
420 | warning "Existing backport PR ${backport_pr_number} title does NOT have ${milestone} prepended" | |
421 | new_pr_title="${milestone}: $backport_pr_title" | |
422 | if [[ "$new_pr_title" =~ \" ]] ; then | |
423 | new_pr_title="${new_pr_title//\"/\\\"}" | |
424 | fi | |
425 | verbose "New PR title: ${new_pr_title}" | |
426 | fi | |
427 | redmine_url_without_scheme="${redmine_url//http?:\/\//}" | |
428 | verbose "Redmine URL without scheme: $redmine_url_without_scheme" | |
429 | if [[ "$clipped_pr_body" =~ $redmine_url_without_scheme ]] ; then | |
430 | info "Existing backport PR ${backport_pr_number} already mentions $redmine_url" | |
431 | if [ "$FORCE" ] ; then | |
432 | warning "--force was given, so updating the PR body anyway" | |
433 | update_pr_body="yes" | |
434 | fi | |
435 | else | |
436 | warning "Existing backport PR ${backport_pr_number} does NOT mention $redmine_url - adding it" | |
437 | update_pr_body="yes" | |
438 | fi | |
439 | if [ "$update_pr_body" ] ; then | |
440 | new_pr_body="backport tracker: ${redmine_url}" | |
441 | if [ "${original_pr_url}" ] ; then | |
442 | new_pr_body="${new_pr_body} | |
443 | possibly a backport of ${original_pr_url}" | |
444 | fi | |
445 | if [ "${original_issue_url}" ] ; then | |
446 | new_pr_body="${new_pr_body} | |
447 | parent tracker: ${original_issue_url}" | |
448 | fi | |
449 | new_pr_body="${new_pr_body} | |
450 | ||
451 | --- | |
452 | ||
453 | original PR body: | |
454 | ||
455 | $clipped_pr_body | |
456 | ||
457 | --- | |
458 | ||
459 | updated using ceph-backport.sh version ${SCRIPT_VERSION}" | |
460 | fi | |
461 | maybe_update_pr_title_body "${new_pr_title}" "${new_pr_body}" | |
462 | } | |
463 | ||
464 | function failed_mandatory_var_check { | |
465 | local varname="$1" | |
466 | local error="$2" | |
467 | verbose "$varname $error" | |
468 | setup_ok="" | |
469 | } | |
470 | ||
471 | function flag_pr { | |
472 | local pr_num="$1" | |
473 | local pr_url="$2" | |
474 | local flag_reason="$3" | |
475 | warning "flagging PR#${pr_num} because $flag_reason" | |
476 | flagged_pr_hash["${pr_url}"]="$flag_reason" | |
477 | } | |
478 | ||
479 | function from_file { | |
480 | local what="$1" | |
481 | xargs 2>/dev/null < "$HOME/.${what}" || true | |
482 | } | |
483 | ||
484 | function get_user_input { | |
485 | local default_val="$1" | |
486 | local user_input= | |
487 | read -r user_input | |
488 | if [ "$user_input" ] ; then | |
489 | echo "$user_input" | |
490 | else | |
491 | echo "$default_val" | |
492 | fi | |
493 | } | |
494 | ||
495 | # takes a string and a substring - returns position of substring within string, | |
496 | # or -1 if not found | |
497 | # NOTE: position of first character in string is 0 | |
498 | function grep_for_substr { | |
499 | local str="$1" | |
500 | local look_for_in_str="$2" | |
501 | str="${str,,}" | |
502 | munged="${str%%${look_for_in_str}*}" | |
503 | if [ "$munged" = "$str" ] ; then | |
504 | echo "-1" | |
505 | else | |
506 | echo "${#munged}" | |
507 | fi | |
508 | } | |
509 | ||
510 | # takes PR title, attempts to guess component | |
511 | function guess_component { | |
512 | local comp= | |
513 | local pos="0" | |
514 | local pr_title="$1" | |
515 | local winning_comp= | |
516 | local winning_comp_pos="9999" | |
517 | for comp in "${!comp_hash[@]}" ; do | |
518 | pos=$(grep_for_substr "$pr_title" "$comp") | |
519 | # echo "$comp: $pos" | |
520 | [ "$pos" = "-1" ] && continue | |
521 | if [ "$pos" -lt "$winning_comp_pos" ] ; then | |
522 | winning_comp_pos="$pos" | |
523 | winning_comp="$comp" | |
524 | fi | |
525 | done | |
526 | [ "$winning_comp" ] && echo "${comp_hash["$winning_comp"]}" || echo "" | |
527 | } | |
528 | ||
529 | function info { | |
530 | log info "$@" | |
531 | } | |
532 | ||
533 | function init_endpoints { | |
534 | verbose "Initializing remote API endpoints" | |
535 | redmine_endpoint="${redmine_endpoint:-"https://tracker.ceph.com"}" | |
536 | github_endpoint="${github_endpoint:-"https://github.com/ceph/ceph"}" | |
537 | } | |
538 | ||
539 | function init_fork_remote { | |
540 | [ "$github_user" ] || assert_fail "github_user not set" | |
541 | [ "$EXPLICIT_FORK" ] && info "Using explicit fork ->$EXPLICIT_FORK<- instead of personal fork." | |
542 | fork_remote="${fork_remote:-$(maybe_deduce_remote fork)}" | |
543 | } | |
544 | ||
545 | function init_github_token { | |
546 | github_token="$(from_file github_token)" | |
547 | if [ "$github_token" ] ; then | |
548 | true | |
549 | else | |
550 | warning "$github_token_file not populated: initiating interactive setup routine" | |
551 | INTERACTIVE_SETUP_ROUTINE="yes" | |
552 | fi | |
553 | } | |
554 | ||
555 | function init_redmine_key { | |
556 | redmine_key="$(from_file redmine_key)" | |
557 | if [ "$redmine_key" ] ; then | |
558 | true | |
559 | else | |
560 | warning "$redmine_key_file not populated: initiating interactive setup routine" | |
561 | INTERACTIVE_SETUP_ROUTINE="yes" | |
562 | fi | |
563 | } | |
564 | ||
565 | function init_upstream_remote { | |
566 | upstream_remote="${upstream_remote:-$(maybe_deduce_remote upstream)}" | |
567 | } | |
568 | ||
569 | function interactive_setup_routine { | |
570 | local default_val | |
571 | local original_github_token | |
572 | local original_redmine_key | |
573 | local total_steps | |
574 | local yes_or_no_answer | |
575 | original_github_token="$github_token" | |
576 | original_redmine_key="$redmine_key" | |
577 | total_steps="4" | |
578 | if [ -e "$deprecated_backport_common" ] ; then | |
579 | github_token="" | |
580 | redmine_key="" | |
581 | # shellcheck disable=SC1090 | |
582 | source "$deprecated_backport_common" 2>/dev/null || true | |
583 | total_steps="$((total_steps+1))" | |
584 | fi | |
585 | echo | |
586 | echo "Welcome to the ${this_script} interactive setup routine!" | |
587 | echo | |
588 | echo "---------------------------------------------------------------------" | |
589 | echo "Setup step 1 of $total_steps - GitHub token" | |
590 | echo "---------------------------------------------------------------------" | |
591 | echo "For information on how to generate a GitHub personal access token" | |
592 | echo "to use with this script, go to https://github.com/settings/tokens" | |
593 | echo "then click on \"Generate new token\" and make sure the token has" | |
594 | echo "\"Full control of private repositories\" scope." | |
eafe8130 | 595 | echo |
9f95a23c TL |
596 | echo "For more details, see:" |
597 | echo "https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line" | |
eafe8130 | 598 | echo |
9f95a23c TL |
599 | echo -n "What is your GitHub token? " |
600 | default_val="$github_token" | |
601 | [ "$github_token" ] && echo "(default: ${default_val})" | |
602 | github_token="$(get_user_input "$default_val")" | |
603 | if [ "$github_token" ] ; then | |
604 | true | |
605 | else | |
606 | error "You must provide a valid GitHub personal access token" | |
607 | abort_due_to_setup_problem | |
608 | fi | |
609 | [ "$github_token" ] || assert_fail "github_token not set, even after completing Step 1 of interactive setup" | |
eafe8130 | 610 | echo |
9f95a23c TL |
611 | echo "---------------------------------------------------------------------" |
612 | echo "Setup step 2 of $total_steps - GitHub user" | |
613 | echo "---------------------------------------------------------------------" | |
614 | echo "The script will now attempt to determine your GitHub user (login)" | |
615 | echo "from the GitHub token provided in the previous step. If this is" | |
616 | echo "successful, there is a good chance that your GitHub token is OK." | |
617 | echo | |
618 | echo "Communicating with the GitHub API..." | |
619 | set_github_user_from_github_token | |
620 | [ "$github_user" ] || abort_due_to_setup_problem | |
621 | echo | |
622 | echo -n "Is the GitHub username (login) \"$github_user\" correct? " | |
623 | default_val="y" | |
624 | [ "$github_token" ] && echo "(default: ${default_val})" | |
625 | yes_or_no_answer="$(get_user_input "$default_val")" | |
626 | [ "$yes_or_no_answer" ] && yes_or_no_answer="${yes_or_no_answer:0:1}" | |
627 | if [ "$yes_or_no_answer" = "y" ] ; then | |
628 | if [ "$github_token" = "$original_github_token" ] ; then | |
629 | true | |
630 | else | |
631 | debug "GitHub personal access token changed" | |
632 | echo "$github_token" > "$github_token_file" | |
633 | chmod 0600 "$github_token_file" | |
634 | info "Wrote GitHub personal access token to $github_token_file" | |
635 | fi | |
636 | else | |
637 | error "GitHub user does not look right" | |
638 | abort_due_to_setup_problem | |
639 | fi | |
640 | [ "$github_token" ] || assert_fail "github_token not set, even after completing Steps 1 and 2 of interactive setup" | |
641 | [ "$github_user" ] || assert_fail "github_user not set, even after completing Steps 1 and 2 of interactive setup" | |
642 | echo | |
643 | echo "---------------------------------------------------------------------" | |
644 | echo "Setup step 3 of $total_steps - remote repos" | |
645 | echo "---------------------------------------------------------------------" | |
646 | echo "Searching \"git remote -v\" for remote repos" | |
647 | echo | |
648 | init_upstream_remote | |
649 | init_fork_remote | |
650 | vet_remotes | |
651 | echo "Upstream remote is \"$upstream_remote\"" | |
652 | echo "Fork remote is \"$fork_remote\"" | |
653 | [ "$setup_ok" ] || abort_due_to_setup_problem | |
654 | [ "$github_token" ] || assert_fail "github_token not set, even after completing Steps 1-3 of interactive setup" | |
655 | [ "$github_user" ] || assert_fail "github_user not set, even after completing Steps 1-3 of interactive setup" | |
656 | [ "$upstream_remote" ] || assert_fail "upstream_remote not set, even after completing Steps 1-3 of interactive setup" | |
657 | [ "$fork_remote" ] || assert_fail "fork_remote not set, even after completing Steps 1-3 of interactive setup" | |
658 | echo | |
659 | echo "---------------------------------------------------------------------" | |
660 | echo "Setup step 4 of $total_steps - Redmine key" | |
661 | echo "---------------------------------------------------------------------" | |
662 | echo "To generate a Redmine API access key, go to https://tracker.ceph.com" | |
663 | echo "After signing in, click: \"My account\"" | |
664 | echo "Now, find \"API access key\"." | |
665 | echo "Once you know the API access key, enter it below." | |
666 | echo | |
667 | echo -n "What is your Redmine key? " | |
668 | default_val="$redmine_key" | |
669 | [ "$redmine_key" ] && echo "(default: ${default_val})" | |
670 | redmine_key="$(get_user_input "$default_val")" | |
671 | if [ "$redmine_key" ] ; then | |
672 | set_redmine_user_from_redmine_key | |
673 | if [ "$setup_ok" ] ; then | |
674 | true | |
675 | else | |
676 | info "You must provide a valid Redmine API access key" | |
677 | abort_due_to_setup_problem | |
678 | fi | |
679 | if [ "$redmine_key" = "$original_redmine_key" ] ; then | |
680 | true | |
681 | else | |
682 | debug "Redmine API access key changed" | |
683 | echo "$redmine_key" > "$redmine_key_file" | |
684 | chmod 0600 "$redmine_key_file" | |
685 | info "Wrote Redmine API access key to $redmine_key_file" | |
686 | fi | |
687 | else | |
688 | error "You must provide a valid Redmine API access key" | |
689 | abort_due_to_setup_problem | |
690 | fi | |
691 | [ "$github_token" ] || assert_fail "github_token not set, even after completing Steps 1-4 of interactive setup" | |
692 | [ "$github_user" ] || assert_fail "github_user not set, even after completing Steps 1-4 of interactive setup" | |
693 | [ "$upstream_remote" ] || assert_fail "upstream_remote not set, even after completing Steps 1-4 of interactive setup" | |
694 | [ "$fork_remote" ] || assert_fail "fork_remote not set, even after completing Steps 1-4 of interactive setup" | |
695 | [ "$redmine_key" ] || assert_fail "redmine_key not set, even after completing Steps 1-4 of interactive setup" | |
696 | [ "$redmine_user_id" ] || assert_fail "redmine_user_id not set, even after completing Steps 1-4 of interactive setup" | |
697 | [ "$redmine_login" ] || assert_fail "redmine_login not set, even after completing Steps 1-4 of interactive setup" | |
698 | if [ "$total_steps" -gt "4" ] ; then | |
699 | echo | |
700 | echo "---------------------------------------------------------------------" | |
701 | echo "Step 5 of $total_steps - delete deprecated $deprecated_backport_common file" | |
702 | echo "---------------------------------------------------------------------" | |
703 | fi | |
704 | maybe_delete_deprecated_backport_common | |
705 | vet_setup --interactive | |
706 | } | |
707 | ||
708 | function is_active_milestone { | |
709 | local is_active= | |
710 | local milestone_under_test="$1" | |
711 | for m in $active_milestones ; do | |
712 | if [ "$milestone_under_test" = "$m" ] ; then | |
713 | verbose "Milestone $m is active" | |
714 | is_active="yes" | |
715 | break | |
716 | fi | |
717 | done | |
718 | echo "$is_active" | |
719 | } | |
720 | ||
721 | function log { | |
722 | local level="$1" | |
723 | local trailing_newline="yes" | |
724 | local in_hex="" | |
725 | shift | |
726 | local msg="$*" | |
727 | prefix="${this_script}: " | |
728 | verbose_only= | |
729 | case $level in | |
730 | bare) | |
731 | prefix= | |
732 | ;; | |
733 | debug) | |
734 | prefix="${prefix}DEBUG: " | |
735 | verbose_only="yes" | |
736 | ;; | |
737 | err*) | |
738 | prefix="${prefix}ERROR: " | |
739 | ;; | |
740 | hex) | |
741 | in_hex="yes" | |
742 | ;; | |
743 | info) | |
744 | : | |
745 | ;; | |
746 | overwrite) | |
747 | trailing_newline= | |
748 | prefix= | |
749 | ;; | |
750 | verbose) | |
751 | verbose_only="yes" | |
752 | ;; | |
753 | verbose_en) | |
754 | verbose_only="yes" | |
755 | trailing_newline= | |
756 | ;; | |
757 | warn|warning) | |
758 | prefix="${prefix}WARNING: " | |
759 | ;; | |
760 | esac | |
761 | if [ "$in_hex" ] ; then | |
762 | print_in_hex "$msg" | |
763 | elif [ "$verbose_only" ] && [ -z "$VERBOSE" ] ; then | |
764 | true | |
765 | else | |
766 | msg="${prefix}${msg}" | |
767 | if [ "$trailing_newline" ] ; then | |
768 | echo "${msg}" >&2 | |
769 | else | |
770 | echo -en "${msg}" >&2 | |
771 | fi | |
772 | fi | |
773 | } | |
774 | ||
775 | function maybe_deduce_remote { | |
776 | local remote_type="$1" | |
777 | local remote="" | |
778 | local url_component="" | |
779 | if [ "$remote_type" = "upstream" ] ; then | |
780 | url_component="ceph" | |
781 | elif [ "$remote_type" = "fork" ] ; then | |
782 | if [ "$EXPLICIT_FORK" ] ; then | |
783 | url_component="$EXPLICIT_FORK" | |
784 | else | |
785 | url_component="$github_user" | |
786 | fi | |
787 | else | |
788 | assert_fail "bad remote_type ->$remote_type<- in maybe_deduce_remote" | |
789 | fi | |
790 | remote=$(git remote -v | grep --extended-regexp --ignore-case '(://|@)github.com(/|:)'${url_component}'/ceph(\s|\.|\/)' | head -n1 | cut -f 1) | |
791 | echo "$remote" | |
792 | } | |
793 | ||
794 | function maybe_delete_deprecated_backport_common { | |
795 | local default_val | |
796 | local user_inp | |
797 | if [ -e "$deprecated_backport_common" ] ; then | |
798 | echo "You still have a $deprecated_backport_common file," | |
799 | echo "which was used to store configuration parameters in version" | |
800 | echo "15.0.0.6270 and earlier versions of ${this_script}." | |
801 | echo | |
802 | echo "Since $deprecated_backport_common has been deprecated in favor" | |
803 | echo "of the interactive setup routine, which has been completed" | |
804 | echo "successfully, the file should be deleted now." | |
805 | echo | |
806 | echo -n "Delete it now? (default: y) " | |
807 | default_val="y" | |
808 | user_inp="$(get_user_input "$default_val")" | |
809 | user_inp="$(echo "$user_inp" | tr '[:upper:]' '[:lower:]' | xargs)" | |
810 | if [ "$user_inp" ] ; then | |
811 | user_inp="${user_inp:0:1}" | |
812 | if [ "$user_inp" = "y" ] ; then | |
813 | set -x | |
814 | rm -f "$deprecated_backport_common" | |
815 | set +x | |
816 | maybe_restore_set_x | |
817 | fi | |
818 | fi | |
819 | if [ -e "$deprecated_backport_common" ] ; then | |
820 | error "$deprecated_backport_common still exists. Bailing out!" | |
821 | false | |
822 | fi | |
823 | fi | |
824 | } | |
825 | ||
826 | function maybe_restore_set_x { | |
827 | if [ "$DEBUG" ] ; then | |
828 | set -x | |
829 | fi | |
830 | } | |
831 | ||
832 | function maybe_update_pr_milestone_labels { | |
833 | local component | |
834 | local data_binary | |
835 | local data_binary | |
836 | local label | |
837 | local needs_milestone | |
838 | if [ "$EXPLICIT_COMPONENT" ] ; then | |
839 | debug "Component given on command line: using it" | |
840 | component="$EXPLICIT_COMPONENT" | |
841 | else | |
842 | debug "Attempting to guess component" | |
843 | component=$(guess_component "$backport_pr_title") | |
844 | fi | |
845 | data_binary="{" | |
846 | needs_milestone="$(backport_pr_needs_milestone)" | |
847 | if [ "$needs_milestone" ] ; then | |
848 | debug "Attempting to set ${milestone} milestone in ${backport_pr_url}" | |
849 | data_binary="${data_binary}\"milestone\":${milestone_number}" | |
850 | else | |
851 | info "Backport PR ${backport_pr_url} already has ${milestone} milestone" | |
852 | fi | |
853 | if [ "$(backport_pr_needs_label "$component")" ] ; then | |
854 | debug "Attempting to add ${component} label to ${backport_pr_url}" | |
855 | if [ "$needs_milestone" ] ; then | |
856 | data_binary="${data_binary}," | |
857 | fi | |
858 | data_binary="${data_binary}\"labels\":[\"${component}\"" | |
859 | while read -r label ; do | |
860 | if [ "$label" ] ; then | |
861 | data_binary="${data_binary},\"${label}\"" | |
862 | fi | |
863 | done <<< "$backport_pr_labels" | |
864 | data_binary="${data_binary}]}" | |
865 | else | |
866 | info "Backport PR ${backport_pr_url} already has label ${component}" | |
867 | data_binary="${data_binary}}" | |
868 | fi | |
869 | if [ "$data_binary" = "{}" ] ; then | |
870 | true | |
871 | else | |
872 | blindly_set_pr_metadata "$backport_pr_number" "$data_binary" | |
873 | fi | |
874 | } | |
875 | ||
876 | function maybe_update_pr_title_body { | |
877 | local new_title="$1" | |
878 | local new_body="$2" | |
879 | local data_binary | |
880 | if [ "$new_title" ] && [ "$new_body" ] ; then | |
881 | data_binary="{\"title\":\"${new_title}\", \"body\":\"$(munge_body "${new_body}")\"}" | |
882 | elif [ "$new_title" ] ; then | |
883 | data_binary="{\"title\":\"${new_title}\"}" | |
884 | backport_pr_title="${new_title}" | |
885 | elif [ "$new_body" ] ; then | |
886 | data_binary="{\"body\":\"$(munge_body "${new_body}")\"}" | |
887 | #log hex "${data_binary}" | |
888 | #echo -n "${data_binary}" | |
889 | fi | |
890 | if [ "$data_binary" ] ; then | |
891 | blindly_set_pr_metadata "${backport_pr_number}" "$data_binary" | |
892 | fi | |
893 | } | |
894 | ||
895 | function milestone_number_from_remote_api { | |
896 | local mtt="$1" # milestone to try | |
897 | local mn="" # milestone number | |
898 | local milestones | |
9f95a23c TL |
899 | remote_api_output=$(curl -u ${github_user}:${github_token} --silent -X GET "https://api.github.com/repos/ceph/ceph/milestones") |
900 | mn=$(echo "$remote_api_output" | jq --arg milestone "$mtt" '.[] | select(.title==$milestone) | .number') | |
901 | if [ "$mn" -gt "0" ] >/dev/null 2>&1 ; then | |
902 | echo "$mn" | |
903 | else | |
904 | error "Could not determine milestone number of ->$milestone<-" | |
905 | verbose_en "GitHub API said:\n${remote_api_output}\n" | |
906 | remote_api_output=$(curl -u ${github_user}:${github_token} --silent -X GET "https://api.github.com/repos/ceph/ceph/milestones") | |
907 | milestones=$(echo "$remote_api_output" | jq '.[].title') | |
908 | info "Valid values are ${milestones}" | |
909 | info "(This probably means the Release field of ${redmine_url} is populated with" | |
910 | info "an unexpected value - i.e. it does not match any of the GitHub milestones.)" | |
911 | false | |
912 | fi | |
913 | } | |
914 | ||
915 | function munge_body { | |
916 | echo "$new_body" | tr '\r' '\n' | sed 's/$/\\n/' | tr -d '\n' | |
917 | } | |
918 | ||
919 | function number_to_url { | |
920 | local number_type="$1" | |
921 | local number="$2" | |
922 | if [ "$number_type" = "github" ] ; then | |
923 | echo "${github_endpoint}/pull/${number}" | |
924 | elif [ "$number_type" = "redmine" ] ; then | |
925 | echo "${redmine_endpoint}/issues/${number}" | |
926 | else | |
927 | assert_fail "internal error in number_to_url: bad type ->$number_type<-" | |
928 | fi | |
929 | } | |
930 | ||
931 | function populate_original_issue { | |
932 | if [ -z "$original_issue" ] ; then | |
933 | original_issue=$(curl --silent "${redmine_url}.json?include=relations" | | |
934 | jq '.issue.relations[] | select(.relation_type | contains("copied_to")) | .issue_id') | |
935 | original_issue_url="$(number_to_url "redmine" "${original_issue}")" | |
936 | fi | |
937 | } | |
938 | ||
939 | function populate_original_pr { | |
940 | if [ "$original_issue" ] ; then | |
941 | if [ -z "$original_pr" ] ; then | |
942 | original_pr=$(curl --silent "${original_issue_url}.json" | | |
943 | jq -r '.issue.custom_fields[] | select(.id | contains(21)) | .value') | |
944 | original_pr_url="$(number_to_url "github" "${original_pr}")" | |
945 | fi | |
946 | fi | |
947 | } | |
948 | ||
949 | function print_in_hex { | |
950 | local str="$1" | |
951 | local c | |
952 | ||
953 | for (( i=0; i < ${#str}; i++ )) | |
954 | do | |
955 | c=${str:$i:1} | |
956 | if [[ $c == ' ' ]] | |
957 | then | |
958 | printf "[%s] 0x%X\n" " " \'\ \' >&2 | |
959 | else | |
960 | printf "[%s] 0x%X\n" "$c" \'"$c"\' >&2 | |
961 | fi | |
962 | done | |
963 | } | |
964 | ||
965 | function set_github_user_from_github_token { | |
966 | local quiet="$1" | |
967 | local api_error | |
968 | local curl_opts | |
969 | setup_ok="" | |
970 | [ "$github_token" ] || assert_fail "set_github_user_from_github_token: git_token not set" | |
971 | curl_opts="--silent -u :${github_token} https://api.github.com/user" | |
972 | [ "$quiet" ] || set -x | |
973 | remote_api_output="$(curl $curl_opts)" | |
974 | set +x | |
975 | github_user=$(echo "${remote_api_output}" | jq -r .login 2>/dev/null | grep -v null || true) | |
976 | api_error=$(echo "${remote_api_output}" | jq -r .message 2>/dev/null | grep -v null || true) | |
977 | if [ "$api_error" ] ; then | |
978 | info "GitHub API said: ->$api_error<-" | |
979 | info "If you can't figure out what's wrong by examining the curl command and its output, above," | |
980 | info "please also study https://developer.github.com/v3/users/#get-the-authenticated-user" | |
981 | github_user="" | |
982 | else | |
983 | [ "$github_user" ] || assert_fail "set_github_user_from_github_token: failed to set github_user" | |
984 | info "my GitHub username is $github_user" | |
985 | setup_ok="yes" | |
986 | fi | |
987 | } | |
988 | ||
989 | function set_redmine_user_from_redmine_key { | |
990 | [ "$redmine_key" ] || assert_fail "set_redmine_user_from_redmine_key was called, but redmine_key not set" | |
991 | local api_key_from_api | |
992 | remote_api_output="$(curl --silent "https://tracker.ceph.com/users/current.json?key=$redmine_key")" | |
993 | redmine_login="$(echo "$remote_api_output" | jq -r '.user.login')" | |
994 | redmine_user_id="$(echo "$remote_api_output" | jq -r '.user.id')" | |
995 | api_key_from_api="$(echo "$remote_api_output" | jq -r '.user.api_key')" | |
996 | if [ "$redmine_login" ] && [ "$redmine_user_id" ] && [ "$api_key_from_api" = "$redmine_key" ] ; then | |
997 | [ "$redmine_user_id" ] || assert_fail "set_redmine_user_from_redmine_key: failed to set redmine_user_id" | |
998 | [ "$redmine_login" ] || assert_fail "set_redmine_user_from_redmine_key: failed to set redmine_login" | |
999 | info "my Redmine username is $redmine_login (ID $redmine_user_id)" | |
1000 | setup_ok="yes" | |
1001 | else | |
1002 | error "Redmine API access key $redmine_key is invalid" | |
1003 | redmine_login="" | |
1004 | redmine_user_id="" | |
1005 | setup_ok="" | |
1006 | fi | |
eafe8130 TL |
1007 | } |
1008 | ||
9f95a23c TL |
1009 | function tracker_component_is_in_desired_state { |
1010 | local comp="$1" | |
1011 | local val_is="$2" | |
1012 | local val_should_be="$3" | |
1013 | local in_desired_state | |
1014 | if [ "$val_is" = "$val_should_be" ] ; then | |
1015 | debug "Tracker $comp is in the desired state" | |
1016 | in_desired_state="yes" | |
1017 | fi | |
1018 | echo "$in_desired_state" | |
1019 | } | |
1020 | ||
1021 | function tracker_component_was_updated { | |
1022 | local comp="$1" | |
1023 | local val_old="$2" | |
1024 | local val_new="$3" | |
1025 | local was_updated | |
1026 | if [ "$val_old" = "$val_new" ] ; then | |
1027 | true | |
1028 | else | |
1029 | debug "Tracker $comp was updated!" | |
1030 | was_updated="yes" | |
1031 | fi | |
1032 | echo "$was_updated" | |
1033 | } | |
1034 | ||
1035 | function trim_whitespace { | |
1036 | local var="$*" | |
1037 | # remove leading whitespace characters | |
1038 | var="${var#"${var%%[![:space:]]*}"}" | |
1039 | # remove trailing whitespace characters | |
1040 | var="${var%"${var##*[![:space:]]}"}" | |
1041 | echo -n "$var" | |
1042 | } | |
1043 | ||
1044 | function troubleshooting_advice { | |
1045 | cat <<EOM | |
1046 | Troubleshooting notes | |
1047 | --------------------- | |
1048 | ||
1049 | If the script inexplicably fails with: | |
1050 | ||
1051 | error: a cherry-pick or revert is already in progress | |
1052 | hint: try "git cherry-pick (--continue | --quit | --abort)" | |
1053 | fatal: cherry-pick failed | |
1054 | ||
1055 | This is because HEAD is not where git expects it to be: | |
1056 | ||
1057 | $ git cherry-pick --abort | |
1058 | warning: You seem to have moved HEAD. Not rewinding, check your HEAD! | |
1059 | ||
1060 | This can be fixed by issuing the command: | |
1061 | ||
1062 | $ git cherry-pick --quit | |
1063 | ||
1064 | EOM | |
1065 | } | |
1066 | ||
1067 | # to update known milestones, consult: | |
1068 | # curl --verbose -X GET https://api.github.com/repos/ceph/ceph/milestones | |
1069 | function try_known_milestones { | |
1070 | local mtt=$1 # milestone to try | |
1071 | local mn="" # milestone number | |
1072 | case $mtt in | |
1073 | cuttlefish) eol "$mtt" ;; | |
1074 | dumpling) eol "$mtt" ;; | |
1075 | emperor) eol "$mtt" ;; | |
1076 | firefly) eol "$mtt" ;; | |
1077 | giant) eol "$mtt" ;; | |
1078 | hammer) eol "$mtt" ;; | |
1079 | infernalis) eol "$mtt" ;; | |
1080 | jewel) mn="8" ;; | |
1081 | kraken) eol "$mtt" ;; | |
1082 | luminous) mn="10" ;; | |
1083 | mimic) mn="11" ;; | |
1084 | nautilus) mn="12" ;; | |
f67539c2 | 1085 | octopus) mn="13" ;; |
20effc67 | 1086 | pacific) mn="14" ;; |
9f95a23c TL |
1087 | esac |
1088 | echo "$mn" | |
1089 | } | |
1090 | ||
1091 | function update_version_number_and_exit { | |
1092 | set -x | |
1093 | local raw_version | |
1094 | local munge_first_hyphen | |
1095 | # munge_first_hyphen will look like this: 15.0.0.5774-g4c2f2eda969 | |
1096 | local script_version_number | |
1097 | raw_version="$(git describe --long --match 'v*' | sed 's/^v//')" # example: "15.0.0-5774-g4c2f2eda969" | |
1098 | munge_first_hyphen="${raw_version/-/.}" # example: "15.0.0.5774-g4c2f2eda969" | |
1099 | script_version_number="${munge_first_hyphen%-*}" # example: "15.0.0.5774" | |
1100 | sed -i -e "s/^SCRIPT_VERSION=.*/SCRIPT_VERSION=\"${script_version_number}\"/" "$full_path" | |
eafe8130 | 1101 | exit 0 |
9f95a23c TL |
1102 | } |
1103 | ||
1104 | function usage { | |
1105 | cat <<EOM >&2 | |
1106 | Setup: | |
1107 | ||
1108 | ${this_script} --setup | |
1109 | ||
1110 | Documentation: | |
1111 | ||
1112 | ${this_script} --help | |
1113 | ${this_script} --usage | less | |
1114 | ${this_script} --troubleshooting | less | |
eafe8130 | 1115 | |
9f95a23c TL |
1116 | Usage: |
1117 | ${this_script} BACKPORT_TRACKER_ISSUE_NUMBER | |
eafe8130 | 1118 | |
9f95a23c TL |
1119 | Options (not needed in normal operation): |
1120 | --cherry-pick-only (stop after cherry-pick phase) | |
1121 | --component/-c COMPONENT | |
1122 | (explicitly set the component label; if omitted, the | |
1123 | script will try to guess the component) | |
1124 | --debug (turns on "set -x") | |
1125 | --existing-pr BACKPORT_PR_ID | |
1126 | (use this when the backport PR is already open) | |
1127 | --force (exercise caution!) | |
1128 | --fork EXPLICIT_FORK (use EXPLICIT_FORK instead of personal GitHub fork) | |
1129 | --milestones (vet all backport PRs for correct milestone setting) | |
1130 | --setup/-s (run the interactive setup routine - NOTE: this can | |
1131 | be done any number of times) | |
1132 | --setup-report (check the setup and print a report) | |
1133 | --update-version (this option exists as a convenience for the script | |
1134 | maintainer only: not intended for day-to-day usage) | |
1135 | --verbose/-v (produce more output than normal) | |
1136 | --version (display version number and exit) | |
11fdf7f2 | 1137 | |
9f95a23c TL |
1138 | Example: |
1139 | ${this_script} 31459 | |
1140 | (if cherry-pick conflicts are present, finish cherry-picking phase manually | |
1141 | and then run the script again with the same argument) | |
1142 | ||
1143 | CAVEAT: The script must be run from inside a local git clone. | |
1144 | EOM | |
11fdf7f2 TL |
1145 | } |
1146 | ||
9f95a23c TL |
1147 | function usage_advice { |
1148 | cat <<EOM | |
1149 | Usage advice | |
1150 | ------------ | |
11fdf7f2 | 1151 | |
9f95a23c TL |
1152 | Once you have completed --setup, you can run the script with the ID of |
1153 | a Backport tracker issue. For example, to stage the backport | |
1154 | https://tracker.ceph.com/issues/41502, run: | |
1155 | ||
1156 | ${this_script} 41502 | |
1157 | ||
1158 | Provided the commits in the corresponding master PR cherry-pick cleanly, the | |
1159 | script will automatically perform all steps required to stage the backport: | |
1160 | ||
1161 | Cherry-pick phase: | |
1162 | ||
1163 | 1. fetching the latest commits from the upstream remote | |
1164 | 2. creating a wip branch for the backport | |
1165 | 3. figuring out which upstream PR contains the commits to cherry-pick | |
1166 | 4. cherry-picking the commits | |
1167 | ||
1168 | PR phase: | |
1169 | ||
1170 | 5. pushing the wip branch to your fork | |
1171 | 6. opening the backport PR with compliant title and description describing | |
1172 | the backport | |
1173 | 7. (optionally) setting the milestone and label in the PR | |
1174 | 8. updating the Backport tracker issue | |
1175 | ||
1176 | When run with --cherry-pick-only, the script will stop after the cherry-pick | |
1177 | phase. | |
1178 | ||
1179 | If any of the commits do not cherry-pick cleanly, the script will abort in | |
1180 | step 4. In this case, you can either finish the cherry-picking manually | |
1181 | or abort the cherry-pick. In any case, when and if the local wip branch is | |
1182 | ready (all commits cherry-picked), if you run the script again, like so: | |
1183 | ||
1184 | ${this_script} 41502 | |
1185 | ||
1186 | the script will detect that the wip branch already exists and skip over | |
1187 | steps 1-4, starting from step 5 ("PR phase"). In other words, if the wip branch | |
1188 | already exists for any reason, the script will assume that the cherry-pick | |
1189 | phase (steps 1-4) is complete. | |
1190 | ||
1191 | As this implies, you can do steps 1-4 manually. Provided the wip branch name | |
1192 | is in the format wip-\$TRACKER_ID-\$STABLE_RELEASE (e.g. "wip-41502-mimic"), | |
1193 | the script will detect the wip branch and start from step 5. | |
1194 | ||
1195 | For details on all the options the script takes, run: | |
1196 | ||
1197 | ${this_script} --help | |
1198 | ||
1199 | For more information on Ceph backporting, see: | |
1200 | ||
1201 | https://github.com/ceph/ceph/tree/master/SubmittingPatches-backports.rst | |
1202 | ||
1203 | EOM | |
1204 | } | |
1205 | ||
1206 | function verbose { | |
1207 | log verbose "$@" | |
11fdf7f2 TL |
1208 | } |
1209 | ||
9f95a23c TL |
1210 | function verbose_en { |
1211 | log verbose_en "$@" | |
1212 | } | |
1213 | ||
1214 | function vet_pr_milestone { | |
1215 | local pr_number="$1" | |
1216 | local pr_title="$2" | |
1217 | local pr_url="$3" | |
1218 | local milestone_stanza="$4" | |
1219 | local milestone_title_should_be="$5" | |
1220 | local milestone_number_should_be | |
1221 | local milestone_number_is= | |
1222 | local milestone_title_is= | |
1223 | milestone_number_should_be="$(try_known_milestones "$milestone_title_should_be")" | |
1224 | log overwrite "Vetting milestone of PR#${pr_number}\r" | |
1225 | if [ "$milestone_stanza" = "null" ] ; then | |
1226 | blindly_set_pr_metadata "$pr_number" "{\"milestone\": $milestone_number_should_be}" | |
1227 | warning "$pr_url: set milestone to \"$milestone_title_should_be\"" | |
1228 | flag_pr "$pr_number" "$pr_url" "milestone not set" | |
1229 | else | |
1230 | milestone_title_is=$(echo "$milestone_stanza" | jq -r '.title') | |
1231 | milestone_number_is=$(echo "$milestone_stanza" | jq -r '.number') | |
1232 | if [ "$milestone_number_is" -eq "$milestone_number_should_be" ] ; then | |
1233 | true | |
1234 | else | |
1235 | blindly_set_pr_metadata "$pr_number" "{\"milestone\": $milestone_number_should_be}" | |
1236 | warning "$pr_url: changed milestone from \"$milestone_title_is\" to \"$milestone_title_should_be\"" | |
1237 | flag_pr "$pr_number" "$pr_url" "milestone set to wrong value \"$milestone_title_is\"" | |
1238 | fi | |
1239 | fi | |
1240 | } | |
1241 | ||
1242 | function vet_prs_for_milestone { | |
1243 | local milestone_title="$1" | |
1244 | local pages_of_output= | |
1245 | local pr_number= | |
1246 | local pr_title= | |
1247 | local pr_url= | |
1248 | # determine last page (i.e., total number of pages) | |
1249 | remote_api_output="$(curl -u ${github_user}:${github_token} --silent --head "https://api.github.com/repos/ceph/ceph/pulls?base=${milestone_title}" | grep -E '^Link' || true)" | |
1250 | if [ "$remote_api_output" ] ; then | |
1251 | # Link: <https://api.github.com/repositories/2310495/pulls?base=luminous&page=2>; rel="next", <https://api.github.com/repositories/2310495/pulls?base=luminous&page=2>; rel="last" | |
1252 | # shellcheck disable=SC2001 | |
1253 | pages_of_output="$(echo "$remote_api_output" | sed 's/^.*&page\=\([0-9]\+\)>; rel=\"last\".*$/\1/g')" | |
1254 | else | |
1255 | pages_of_output="1" | |
1256 | fi | |
1257 | verbose "GitHub has $pages_of_output pages of pull request data for \"base:${milestone_title}\"" | |
1258 | for ((page=1; page<=pages_of_output; page++)) ; do | |
1259 | verbose "Fetching PRs (page $page of ${pages_of_output})" | |
1260 | remote_api_output="$(curl -u ${github_user}:${github_token} --silent -X GET "https://api.github.com/repos/ceph/ceph/pulls?base=${milestone_title}&page=${page}")" | |
1261 | prs_in_page="$(echo "$remote_api_output" | jq -r '. | length')" | |
1262 | verbose "Page $page of remote API output contains information on $prs_in_page PRs" | |
1263 | for ((i=0; i<prs_in_page; i++)) ; do | |
1264 | pr_number="$(echo "$remote_api_output" | jq -r ".[${i}].number")" | |
1265 | pr_title="$(echo "$remote_api_output" | jq -r ".[${i}].title")" | |
1266 | pr_url="$(number_to_url "github" "${pr_number}")" | |
1267 | milestone_stanza="$(echo "$remote_api_output" | jq -r ".[${i}].milestone")" | |
1268 | vet_pr_milestone "$pr_number" "$pr_title" "$pr_url" "$milestone_stanza" "$milestone_title" | |
1269 | done | |
1270 | clear_line | |
1271 | done | |
1272 | } | |
1273 | ||
1274 | function vet_remotes { | |
1275 | if [ "$upstream_remote" ] ; then | |
1276 | verbose "Upstream remote is $upstream_remote" | |
1277 | else | |
1278 | error "Cannot auto-determine upstream remote" | |
1279 | "(Could not find any upstream remote in \"git remote -v\")" | |
1280 | false | |
1281 | fi | |
1282 | if [ "$fork_remote" ] ; then | |
1283 | verbose "Fork remote is $fork_remote" | |
1284 | else | |
1285 | error "Cannot auto-determine fork remote" | |
1286 | if [ "$EXPLICIT_FORK" ] ; then | |
1287 | info "(Could not find $EXPLICIT_FORK fork of ceph/ceph in \"git remote -v\")" | |
1288 | else | |
1289 | info "(Could not find GitHub user ${github_user}'s fork of ceph/ceph in \"git remote -v\")" | |
1290 | fi | |
1291 | setup_ok="" | |
1292 | fi | |
1293 | } | |
1294 | ||
1295 | function vet_setup { | |
1296 | local argument="$1" | |
1297 | local not_set="!!! NOT SET !!!" | |
1298 | local invalid="!!! INVALID !!!" | |
1299 | local redmine_endpoint_display | |
1300 | local redmine_user_id_display | |
1301 | local github_endpoint_display | |
1302 | local github_user_display | |
1303 | local upstream_remote_display | |
1304 | local fork_remote_display | |
1305 | local redmine_key_display | |
1306 | local github_token_display | |
1307 | debug "Entering vet_setup with argument $argument" | |
1308 | if [ "$argument" = "--report" ] || [ "$argument" = "--normal-operation" ] ; then | |
1309 | [ "$github_token" ] && [ "$setup_ok" ] && set_github_user_from_github_token quiet | |
1310 | init_upstream_remote | |
1311 | [ "$github_token" ] && [ "$setup_ok" ] && init_fork_remote | |
1312 | vet_remotes | |
1313 | [ "$redmine_key" ] && set_redmine_user_from_redmine_key | |
1314 | fi | |
1315 | if [ "$github_token" ] ; then | |
1316 | if [ "$setup_ok" ] ; then | |
1317 | github_token_display="(OK; value not shown)" | |
1318 | else | |
1319 | github_token_display="$invalid" | |
1320 | fi | |
1321 | else | |
1322 | github_token_display="$not_set" | |
1323 | fi | |
1324 | if [ "$redmine_key" ] ; then | |
1325 | if [ "$setup_ok" ] ; then | |
1326 | redmine_key_display="(OK; value not shown)" | |
1327 | else | |
1328 | redmine_key_display="$invalid" | |
1329 | fi | |
1330 | else | |
1331 | redmine_key_display="$not_set" | |
1332 | fi | |
1333 | redmine_endpoint_display="${redmine_endpoint:-$not_set}" | |
1334 | redmine_user_id_display="${redmine_user_id:-$not_set}" | |
1335 | github_endpoint_display="${github_endpoint:-$not_set}" | |
1336 | github_user_display="${github_user:-$not_set}" | |
1337 | upstream_remote_display="${upstream_remote:-$not_set}" | |
1338 | fork_remote_display="${fork_remote:-$not_set}" | |
1339 | test "$redmine_endpoint" || failed_mandatory_var_check redmine_endpoint "not set" | |
1340 | test "$redmine_user_id" || failed_mandatory_var_check redmine_user_id "could not be determined" | |
1341 | test "$redmine_key" || failed_mandatory_var_check redmine_key "not set" | |
1342 | test "$github_endpoint" || failed_mandatory_var_check github_endpoint "not set" | |
1343 | test "$github_user" || failed_mandatory_var_check github_user "could not be determined" | |
1344 | test "$github_token" || failed_mandatory_var_check github_token "not set" | |
1345 | test "$upstream_remote" || failed_mandatory_var_check upstream_remote "could not be determined" | |
1346 | test "$fork_remote" || failed_mandatory_var_check fork_remote "could not be determined" | |
1347 | if [ "$argument" = "--report" ] || [ "$argument" == "--interactive" ] ; then | |
1348 | read -r -d '' setup_summary <<EOM || true > /dev/null 2>&1 | |
1349 | redmine_endpoint $redmine_endpoint | |
1350 | redmine_user_id $redmine_user_id_display | |
1351 | redmine_key $redmine_key_display | |
1352 | github_endpoint $github_endpoint | |
1353 | github_user $github_user_display | |
1354 | github_token $github_token_display | |
1355 | upstream_remote $upstream_remote_display | |
1356 | fork_remote $fork_remote_display | |
1357 | EOM | |
1358 | log bare | |
1359 | log bare "=============================================" | |
1360 | log bare " ${this_script} setup report" | |
1361 | log bare "=============================================" | |
1362 | log bare "variable name value" | |
1363 | log bare "---------------------------------------------" | |
1364 | log bare "$setup_summary" | |
1365 | log bare "---------------------------------------------" | |
1366 | else | |
1367 | verbose "redmine_endpoint $redmine_endpoint_display" | |
1368 | verbose "redmine_user_id $redmine_user_id_display" | |
1369 | verbose "redmine_key $redmine_key_display" | |
1370 | verbose "github_endpoint $github_endpoint_display" | |
1371 | verbose "github_user $github_user_display" | |
1372 | verbose "github_token $github_token_display" | |
1373 | verbose "upstream_remote $upstream_remote_display" | |
1374 | verbose "fork_remote $fork_remote_display" | |
1375 | fi | |
1376 | if [ "$argument" = "--report" ] || [ "$argument" = "--interactive" ] ; then | |
1377 | if [ "$setup_ok" ] ; then | |
1378 | info "setup is OK" | |
1379 | else | |
1380 | info "setup is NOT OK" | |
1381 | fi | |
1382 | log bare "==============================================" | |
1383 | log bare | |
1384 | fi | |
1385 | } | |
1386 | ||
1387 | function warning { | |
1388 | log warning "$@" | |
1389 | } | |
1390 | ||
1391 | ||
1392 | # | |
1393 | # are we in a local git clone? | |
1394 | # | |
1395 | ||
1396 | if git status >/dev/null 2>&1 ; then | |
1397 | debug "In a local git clone. Good." | |
1398 | else | |
1399 | error "This script must be run from inside a local git clone" | |
1400 | abort_due_to_setup_problem | |
1401 | fi | |
1402 | ||
f67539c2 TL |
1403 | # |
1404 | # do we have jq available? | |
1405 | # | |
1406 | ||
1407 | if type jq >/dev/null 2>&1 ; then | |
1408 | debug "jq is available. Good." | |
1409 | else | |
1410 | error "This script uses jq, but it does not seem to be installed" | |
1411 | abort_due_to_setup_problem | |
1412 | fi | |
1413 | ||
1414 | # | |
1415 | # is jq available? | |
1416 | # | |
1417 | ||
1418 | if command -v jq >/dev/null ; then | |
1419 | debug "jq is available. Good." | |
1420 | else | |
1421 | error "This script needs \"jq\" in order to work, and it is not available" | |
1422 | abort_due_to_setup_problem | |
1423 | fi | |
1424 | ||
9f95a23c TL |
1425 | |
1426 | # | |
1427 | # process command-line arguments | |
1428 | # | |
1429 | ||
1430 | munged_options=$(getopt -o c:dhsv --long "cherry-pick-only,component:,debug,existing-pr:,force,fork:,help,milestones,prepare,setup,setup-report,troubleshooting,update-version,usage,verbose,version" -n "$this_script" -- "$@") | |
1431 | eval set -- "$munged_options" | |
11fdf7f2 | 1432 | |
9f95a23c TL |
1433 | ADVICE="" |
1434 | CHECK_MILESTONES="" | |
1435 | CHERRY_PICK_ONLY="" | |
1436 | CHERRY_PICK_PHASE="yes" | |
1437 | DEBUG="" | |
1438 | EXISTING_PR="" | |
1439 | EXPLICIT_COMPONENT="" | |
1440 | EXPLICIT_FORK="" | |
1441 | FORCE="" | |
1442 | HELP="" | |
1443 | INTERACTIVE_SETUP_ROUTINE="" | |
1444 | ISSUE="" | |
1445 | PR_PHASE="yes" | |
1446 | SETUP_OPTION="" | |
1447 | TRACKER_PHASE="yes" | |
1448 | TROUBLESHOOTING_ADVICE="" | |
1449 | USAGE_ADVICE="" | |
1450 | VERBOSE="" | |
1451 | while true ; do | |
1452 | case "$1" in | |
1453 | --cherry-pick-only) CHERRY_PICK_PHASE="yes" ; PR_PHASE="" ; TRACKER_PHASE="" ; shift ;; | |
1454 | --component|-c) shift ; EXPLICIT_COMPONENT="$1" ; shift ;; | |
1455 | --debug|-d) DEBUG="$1" ; shift ;; | |
1456 | --existing-pr) shift ; EXISTING_PR="$1" ; CHERRY_PICK_PHASE="" ; PR_PHASE="" ; shift ;; | |
1457 | --force) FORCE="$1" ; shift ;; | |
1458 | --fork) shift ; EXPLICIT_FORK="$1" ; shift ;; | |
1459 | --help|-h) ADVICE="1" ; HELP="$1" ; shift ;; | |
1460 | --milestones) CHECK_MILESTONES="$1" ; shift ;; | |
1461 | --prepare) CHERRY_PICK_PHASE="yes" ; PR_PHASE="" ; TRACKER_PHASE="" ; shift ;; | |
1462 | --setup*|-s) SETUP_OPTION="$1" ; shift ;; | |
1463 | --troubleshooting) ADVICE="$1" ; TROUBLESHOOTING_ADVICE="$1" ; shift ;; | |
1464 | --update-version) update_version_number_and_exit ;; | |
1465 | --usage) ADVICE="$1" ; USAGE_ADVICE="$1" ; shift ;; | |
1466 | --verbose|-v) VERBOSE="$1" ; shift ;; | |
1467 | --version) display_version_message_and_exit ;; | |
1468 | --) shift ; ISSUE="$1" ; break ;; | |
1469 | *) echo "Internal error" ; false ;; | |
1470 | esac | |
1471 | done | |
1472 | ||
1473 | if [ "$ADVICE" ] ; then | |
1474 | [ "$HELP" ] && usage | |
1475 | [ "$USAGE_ADVICE" ] && usage_advice | |
1476 | [ "$TROUBLESHOOTING_ADVICE" ] && troubleshooting_advice | |
1477 | exit 0 | |
11fdf7f2 | 1478 | fi |
11fdf7f2 | 1479 | |
9f95a23c TL |
1480 | if [ "$SETUP_OPTION" ] || [ "$CHECK_MILESTONES" ] ; then |
1481 | ISSUE="0" | |
1482 | fi | |
11fdf7f2 | 1483 | |
9f95a23c TL |
1484 | if [[ $ISSUE =~ ^[0-9]+$ ]] ; then |
1485 | issue=$ISSUE | |
11fdf7f2 | 1486 | else |
9f95a23c TL |
1487 | error "Invalid or missing argument" |
1488 | usage | |
1489 | false | |
11fdf7f2 | 1490 | fi |
11fdf7f2 | 1491 | |
9f95a23c TL |
1492 | if [ "$DEBUG" ]; then |
1493 | set -x | |
1494 | VERBOSE="--verbose" | |
11fdf7f2 TL |
1495 | fi |
1496 | ||
9f95a23c TL |
1497 | if [ "$VERBOSE" ]; then |
1498 | info "Verbose mode ON" | |
1499 | VERBOSE="--verbose" | |
1500 | fi | |
11fdf7f2 | 1501 | |
11fdf7f2 | 1502 | |
9f95a23c TL |
1503 | # |
1504 | # make sure setup has been completed | |
1505 | # | |
11fdf7f2 | 1506 | |
9f95a23c TL |
1507 | init_endpoints |
1508 | init_github_token | |
1509 | init_redmine_key | |
1510 | setup_ok="OK" | |
1511 | if [ "$SETUP_OPTION" ] ; then | |
1512 | vet_setup --report | |
1513 | maybe_delete_deprecated_backport_common | |
1514 | if [ "$setup_ok" ] ; then | |
1515 | exit 0 | |
1516 | else | |
1517 | default_val="y" | |
1518 | echo -n "Run the interactive setup routine now? (default: ${default_val}) " | |
1519 | yes_or_no_answer="$(get_user_input "$default_val")" | |
1520 | [ "$yes_or_no_answer" ] && yes_or_no_answer="${yes_or_no_answer:0:1}" | |
1521 | if [ "$yes_or_no_answer" = "y" ] ; then | |
1522 | INTERACTIVE_SETUP_ROUTINE="yes" | |
1523 | else | |
1524 | if [ "$FORCE" ] ; then | |
1525 | warning "--force was given; proceeding with broken setup" | |
1526 | else | |
1527 | info "Bailing out!" | |
1528 | exit 1 | |
1529 | fi | |
1530 | fi | |
1531 | fi | |
1532 | fi | |
1533 | if [ "$INTERACTIVE_SETUP_ROUTINE" ] ; then | |
1534 | interactive_setup_routine | |
1535 | else | |
1536 | vet_setup --normal-operation | |
1537 | maybe_delete_deprecated_backport_common | |
1538 | fi | |
1539 | if [ "$INTERACTIVE_SETUP_ROUTINE" ] || [ "$SETUP_OPTION" ] ; then | |
1540 | echo | |
1541 | if [ "$setup_ok" ] ; then | |
1542 | if [ "$ISSUE" ] && [ "$ISSUE" != "0" ] ; then | |
1543 | true | |
1544 | else | |
1545 | exit 0 | |
1546 | fi | |
1547 | else | |
1548 | exit 1 | |
1549 | fi | |
1550 | fi | |
1551 | vet_remotes | |
1552 | [ "$setup_ok" ] || abort_due_to_setup_problem | |
1553 | ||
1554 | # | |
1555 | # query remote GitHub API for active milestones | |
1556 | # | |
1557 | ||
1558 | verbose "Querying GitHub API for active milestones" | |
1559 | remote_api_output="$(curl -u ${github_user}:${github_token} --silent -X GET "https://api.github.com/repos/ceph/ceph/milestones")" | |
1560 | active_milestones="$(echo "$remote_api_output" | jq -r '.[] | .title')" | |
1561 | if [ "$active_milestones" = "null" ] ; then | |
1562 | error "Could not determine the active milestones" | |
1563 | bail_out_github_api "$remote_api_output" | |
1564 | fi | |
1565 | ||
1566 | if [ "$CHECK_MILESTONES" ] ; then | |
1567 | check_milestones "$active_milestones" | |
1568 | exit 0 | |
1569 | fi | |
1570 | ||
1571 | # | |
1572 | # query remote Redmine API for information about the Backport tracker issue | |
1573 | # | |
1574 | ||
1575 | redmine_url="$(number_to_url "redmine" "${issue}")" | |
1576 | debug "Considering Redmine issue: $redmine_url - is it in the Backport tracker?" | |
1577 | ||
1578 | remote_api_output="$(curl --silent "${redmine_url}.json")" | |
1579 | tracker="$(echo "$remote_api_output" | jq -r '.issue.tracker.name')" | |
1580 | if [ "$tracker" = "Backport" ]; then | |
1581 | debug "Yes, $redmine_url is a Backport issue" | |
1582 | else | |
1583 | error "Issue $redmine_url is not a Backport" | |
1584 | info "(This script only works with Backport tracker issues.)" | |
1585 | false | |
1586 | fi | |
1587 | ||
1588 | debug "Looking up release/milestone of $redmine_url" | |
1589 | milestone="$(echo "$remote_api_output" | jq -r '.issue.custom_fields[0].value')" | |
1590 | if [ "$milestone" ] ; then | |
1591 | debug "Release/milestone: $milestone" | |
1592 | else | |
1593 | error "could not obtain release/milestone from ${redmine_url}" | |
1594 | false | |
1595 | fi | |
1596 | ||
1597 | debug "Looking up status of $redmine_url" | |
1598 | tracker_status_id="$(echo "$remote_api_output" | jq -r '.issue.status.id')" | |
1599 | tracker_status_name="$(echo "$remote_api_output" | jq -r '.issue.status.name')" | |
1600 | if [ "$tracker_status_name" ] ; then | |
1601 | debug "Tracker status: $tracker_status_name" | |
1602 | if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then | |
1603 | test "$(check_tracker_status "$tracker_status_name")" || true | |
1604 | else | |
1605 | test "$(check_tracker_status "$tracker_status_name")" | |
1606 | fi | |
1607 | else | |
1608 | error "could not obtain status from ${redmine_url}" | |
1609 | false | |
1610 | fi | |
1611 | ||
1612 | tracker_title="$(echo "$remote_api_output" | jq -r '.issue.subject')" | |
1613 | debug "Title of $redmine_url is ->$tracker_title<-" | |
1614 | ||
1615 | tracker_description="$(echo "$remote_api_output" | jq -r '.issue.description')" | |
1616 | debug "Description of $redmine_url is ->$tracker_description<-" | |
1617 | ||
1618 | tracker_assignee_id="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.id')" | |
1619 | tracker_assignee_name="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.name')" | |
1620 | if [ "$tracker_assignee_id" = "null" ] || [ "$tracker_assignee_id" = "$redmine_user_id" ] ; then | |
1621 | true | |
1622 | else | |
1623 | error_msg_1="$redmine_url is assigned to someone else: $tracker_assignee_name (ID $tracker_assignee_id)" | |
1624 | error_msg_2="(my ID is $redmine_user_id)" | |
1625 | if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then | |
1626 | warning "$error_msg_1" | |
1627 | info "$error_msg_2" | |
1628 | info "--force and/or --existing-pr given: continuing execution" | |
1629 | else | |
1630 | error "$error_msg_1" | |
1631 | info "$error_msg_2" | |
1632 | info "Cowardly refusing to continue" | |
1633 | false | |
1634 | fi | |
1635 | fi | |
1636 | ||
1637 | if [ -z "$(is_active_milestone "$milestone")" ] ; then | |
1638 | error "$redmine_url is a backport to $milestone which is not an active milestone" | |
1639 | info "Cowardly refusing to work on a backport to an inactive release" | |
1640 | false | |
1641 | fi | |
1642 | ||
1643 | milestone_number=$(try_known_milestones "$milestone") | |
1644 | if [ "$milestone_number" -gt "0" ] >/dev/null 2>&1 ; then | |
f67539c2 | 1645 | debug "Milestone ->$milestone<- is known to have number ->$milestone_number<-: skipping remote API call" |
9f95a23c | 1646 | else |
f67539c2 | 1647 | warning "Milestone ->$milestone<- is unknown to the script: falling back to GitHub API" |
9f95a23c TL |
1648 | milestone_number=$(milestone_number_from_remote_api "$milestone") |
1649 | fi | |
f67539c2 | 1650 | target_branch="$milestone" |
9f95a23c TL |
1651 | info "milestone/release is $milestone" |
1652 | debug "milestone number is $milestone_number" | |
1653 | ||
1654 | if [ "$CHERRY_PICK_PHASE" ] ; then | |
1655 | local_branch=wip-${issue}-${target_branch} | |
1656 | if git show-ref --verify --quiet "refs/heads/$local_branch" ; then | |
1657 | if [ "$FORCE" ] ; then | |
1658 | warning "local branch $local_branch already exists" | |
1659 | info "--force was given: will clobber $local_branch and attempt automated cherry-pick" | |
1660 | cherry_pick_phase | |
1661 | elif [ "$CHERRY_PICK_ONLY" ] ; then | |
1662 | error "local branch $local_branch already exists" | |
1663 | info "Cowardly refusing to clobber $local_branch as it might contain valuable data" | |
1664 | info "(hint) run with --force to clobber it and attempt the cherry-pick" | |
1665 | false | |
1666 | fi | |
1667 | if [ "$FORCE" ] || [ "$CHERRY_PICK_ONLY" ] ; then | |
1668 | true | |
1669 | else | |
1670 | info "local branch $local_branch already exists: skipping cherry-pick phase" | |
1671 | fi | |
1672 | else | |
1673 | info "$local_branch does not exist: will create it and attempt automated cherry-pick" | |
1674 | cherry_pick_phase | |
1675 | fi | |
1676 | fi | |
1677 | ||
1678 | if [ "$PR_PHASE" ] ; then | |
1679 | current_branch=$(git rev-parse --abbrev-ref HEAD) | |
1680 | if [ "$current_branch" = "$local_branch" ] ; then | |
1681 | true | |
1682 | else | |
1683 | set -x | |
1684 | git checkout "$local_branch" | |
1685 | set +x | |
1686 | maybe_restore_set_x | |
1687 | fi | |
1688 | ||
1689 | set -x | |
1690 | git push -u "$fork_remote" "$local_branch" | |
1691 | set +x | |
1692 | maybe_restore_set_x | |
1693 | ||
1694 | original_issue="" | |
1695 | original_pr="" | |
1696 | original_pr_url="" | |
1697 | ||
1698 | debug "Generating backport PR description" | |
1699 | populate_original_issue | |
1700 | populate_original_pr | |
1701 | desc="backport tracker: ${redmine_url}" | |
1702 | if [ "$original_pr" ] || [ "$original_issue" ] ; then | |
1703 | desc="${desc}\n\n---\n" | |
1704 | [ "$original_pr" ] && desc="${desc}\nbackport of $(number_to_url "github" "${original_pr}")" | |
1705 | [ "$original_issue" ] && desc="${desc}\nparent tracker: $(number_to_url "redmine" "${original_issue}")" | |
1706 | fi | |
1707 | desc="${desc}\n\nthis backport was staged using ceph-backport.sh version ${SCRIPT_VERSION}\nfind the latest version at ${github_endpoint}/blob/master/src/script/ceph-backport.sh" | |
1708 | ||
1709 | debug "Generating backport PR title" | |
1710 | if [ "$original_pr" ] ; then | |
1711 | backport_pr_title="${milestone}: $(curl --silent https://api.github.com/repos/ceph/ceph/pulls/${original_pr} | jq -r '.title')" | |
1712 | else | |
1713 | if [[ $tracker_title =~ ^${milestone}: ]] ; then | |
1714 | backport_pr_title="${tracker_title}" | |
1715 | else | |
1716 | backport_pr_title="${milestone}: ${tracker_title}" | |
1717 | fi | |
1718 | fi | |
1719 | if [[ "$backport_pr_title" =~ \" ]] ; then | |
1720 | backport_pr_title="${backport_pr_title//\"/\\\"}" | |
1721 | fi | |
1722 | ||
1723 | debug "Opening backport PR" | |
1724 | if [ "$EXPLICIT_FORK" ] ; then | |
1725 | source_repo="$EXPLICIT_FORK" | |
1726 | else | |
1727 | source_repo="$github_user" | |
1728 | fi | |
1729 | remote_api_output=$(curl -u ${github_user}:${github_token} --silent --data-binary "{\"title\":\"${backport_pr_title}\",\"head\":\"${source_repo}:${local_branch}\",\"base\":\"${target_branch}\",\"body\":\"${desc}\"}" "https://api.github.com/repos/ceph/ceph/pulls") | |
1730 | backport_pr_number=$(echo "$remote_api_output" | jq -r .number) | |
1731 | if [ -z "$backport_pr_number" ] || [ "$backport_pr_number" = "null" ] ; then | |
1732 | error "failed to open backport PR" | |
1733 | bail_out_github_api "$remote_api_output" | |
1734 | fi | |
1735 | backport_pr_url="$(number_to_url "github" "$backport_pr_number")" | |
1736 | info "Opened backport PR ${backport_pr_url}" | |
1737 | fi | |
1738 | ||
1739 | if [ "$EXISTING_PR" ] ; then | |
1740 | populate_original_issue | |
1741 | populate_original_pr | |
1742 | backport_pr_number="$EXISTING_PR" | |
1743 | backport_pr_url="$(number_to_url "github" "$backport_pr_number")" | |
1744 | existing_pr_routine | |
1745 | fi | |
1746 | ||
1747 | if [ "$PR_PHASE" ] || [ "$EXISTING_PR" ] ; then | |
1748 | maybe_update_pr_milestone_labels | |
1749 | pgrep firefox >/dev/null && firefox "${backport_pr_url}" | |
1750 | fi | |
1751 | ||
1752 | if [ "$TRACKER_PHASE" ] ; then | |
1753 | debug "Considering Backport tracker issue ${redmine_url}" | |
1754 | status_should_be=2 # In Progress | |
1755 | desc_should_be="${backport_pr_url}" | |
1756 | assignee_should_be="${redmine_user_id}" | |
1757 | if [ "$EXISTING_PR" ] ; then | |
1758 | data_binary="{\"issue\":{\"description\":\"${desc_should_be}\",\"status_id\":${status_should_be}}}" | |
1759 | else | |
1760 | data_binary="{\"issue\":{\"description\":\"${desc_should_be}\",\"status_id\":${status_should_be},\"assigned_to_id\":${assignee_should_be}}}" | |
1761 | fi | |
1762 | remote_api_status_code="$(curl --write-out '%{http_code}' --output /dev/null --silent -X PUT --header "Content-type: application/json" --data-binary "${data_binary}" "${redmine_url}.json?key=$redmine_key")" | |
1763 | if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then | |
1764 | true | |
1765 | else | |
1766 | if [ "${remote_api_status_code:0:1}" = "2" ] ; then | |
1767 | true | |
1768 | elif [ "${remote_api_status_code:0:1}" = "4" ] ; then | |
1769 | warning "remote API ${redmine_endpoint} returned status ${remote_api_status_code}" | |
1770 | info "This merely indicates that you cannot modify issue fields at ${redmine_endpoint}" | |
1771 | info "and does not limit your ability to do backports." | |
1772 | else | |
1773 | error "Remote API ${redmine_endpoint} returned unexpected response code ${remote_api_status_code}" | |
1774 | fi | |
1775 | fi | |
1776 | # check if anything actually changed on the Redmine issue | |
1777 | remote_api_output=$(curl --silent "${redmine_url}.json?include=journals") | |
1778 | status_is="$(echo "$remote_api_output" | jq -r '.issue.status.id')" | |
1779 | desc_is="$(echo "$remote_api_output" | jq -r '.issue.description')" | |
1780 | assignee_is="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.id')" | |
1781 | tracker_was_updated="" | |
1782 | tracker_is_in_desired_state="yes" | |
1783 | [ "$(tracker_component_was_updated "status" "$tracker_status_id" "$status_is")" ] && tracker_was_updated="yes" | |
1784 | [ "$(tracker_component_was_updated "desc" "$tracker_description" "$desc_is")" ] && tracker_was_updated="yes" | |
1785 | if [ "$EXISTING_PR" ] ; then | |
1786 | true | |
1787 | else | |
1788 | [ "$(tracker_component_was_updated "assignee" "$tracker_assignee_id" "$assignee_is")" ] && tracker_was_updated="yes" | |
1789 | fi | |
1790 | [ "$(tracker_component_is_in_desired_state "status" "$status_is" "$status_should_be")" ] || tracker_is_in_desired_state="" | |
1791 | [ "$(tracker_component_is_in_desired_state "desc" "$desc_is" "$desc_should_be")" ] || tracker_is_in_desired_state="" | |
1792 | if [ "$EXISTING_PR" ] ; then | |
1793 | true | |
1794 | else | |
1795 | [ "$(tracker_component_is_in_desired_state "assignee" "$assignee_is" "$assignee_should_be")" ] || tracker_is_in_desired_state="" | |
1796 | fi | |
1797 | if [ "$tracker_is_in_desired_state" ] ; then | |
1798 | [ "$tracker_was_updated" ] && info "Backport tracker ${redmine_url} was updated" | |
1799 | info "Backport tracker ${redmine_url} is in the desired state" | |
1800 | pgrep firefox >/dev/null && firefox "${redmine_url}" | |
1801 | exit 0 | |
1802 | fi | |
1803 | if [ "$tracker_was_updated" ] ; then | |
1804 | warning "backport tracker ${redmine_url} was updated, but is not in the desired state. Please check it." | |
1805 | pgrep firefox >/dev/null && firefox "${redmine_url}" | |
1806 | exit 1 | |
1807 | else | |
1808 | data_binary="{\"issue\":{\"notes\":\"please link this Backport tracker issue with GitHub PR ${desc_should_be}\nceph-backport.sh version ${SCRIPT_VERSION}\"}}" | |
1809 | remote_api_status_code=$(curl --write-out '%{http_code}' --output /dev/null --silent -X PUT --header "Content-type: application/json" --data-binary "${data_binary}" "${redmine_url}.json?key=$redmine_key") | |
1810 | if [ "${remote_api_status_code:0:1}" = "2" ] ; then | |
1811 | info "Comment added to ${redmine_url}" | |
1812 | fi | |
1813 | exit 0 | |
1814 | fi | |
1815 | fi |