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