3 # ceph-backport.sh - Ceph backporting script
5 # Credits: This script is based on work done by Loic Dachary
8 # This script automates the process of staging a backport starting from a
9 # Backport tracker issue.
13 # ceph-backport.sh --setup
15 # Usage and troubleshooting:
17 # ceph-backport.sh --help
18 # ceph-backport.sh --usage | less
19 # ceph-backport.sh --troubleshooting | less
24 SCRIPT_VERSION
="15.1.0.1009"
30 deprecated_backport_common
="$HOME/bin/backport_common.sh"
31 existing_pr_milestone_number
=""
33 github_token_file
="$HOME/.github_token"
42 redmine_key_file
="$HOME/.redmine_key"
46 this_script
=$
(basename "$full_path")
48 if [[ $
* == *--debug* ]]; then
52 # associative array keyed on "component" strings from PR titles, mapping them to
53 # GitHub PR labels that make sense in backports
54 declare -A comp_hash
=(
56 ["bluestore"]="bluestore"
57 ["build/ops"]="build/ops"
58 ["ceph.spec"]="build/ops"
59 ["ceph-volume"]="ceph-volume"
66 ["dashboard"]="dashboard"
68 ["doc"]="documentation"
69 ["grafana"]="monitoring"
74 ["mgr/dashboard"]="dashboard"
75 ["mgr/prometheus"]="monitoring"
77 ["monitoring"]="monitoring"
78 ["orch"]="orchestrator"
80 ["perf"]="performance"
81 ["prometheus"]="monitoring"
93 declare -A flagged_pr_hash
=()
95 function abort_due_to_setup_problem
{
96 error
"problem detected in your setup"
97 info
"Run \"${this_script} --setup\" to fix"
101 function assert_fail
{
103 error
"(internal error) $message"
104 info
"This could be reported as a bug!"
108 function backport_pr_needs_label
{
109 local check_label
="$1"
111 local needs_label
="yes"
112 while read -r label
; do
113 if [ "$label" = "$check_label" ] ; then
116 done <<< "$backport_pr_labels"
120 function backport_pr_needs_milestone
{
121 if [ "$existing_pr_milestone_number" ] ; then
128 function bail_out_github_api
{
131 info
"GitHub API said:"
133 if [ "$hint" ] ; then
136 abort_due_to_setup_problem
139 function blindly_set_pr_metadata
{
142 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
145 function check_milestones {
146 local milestones_to_check
147 milestones_to_check="$
(echo "$1" |
tr '\n' ' ' |
xargs)"
148 info "Active milestones
: $milestones_to_check"
149 for m in $milestones_to_check ; do
150 info "Examining all PRs targeting base branch
\"$m\""
151 vet_prs_for_milestone "$m"
156 function check_tracker_status {
157 local -a ok_statuses=("new
" "need
more info
")
162 for oks in "${ok_statuses[@]}"; do
163 if [ "$tslc" = "$oks" ] ; then
164 debug "Tracker status
$ts is OK
for backport to proceed
"
169 if [ "$tslc_is_ok" ] ; then
172 if [ "$tslc" = "in progress
" ] ; then
173 error_msg="backport
$redmine_url is already
in progress
"
175 error_msg="backport
$redmine_url is closed
(status
: ${ts})"
177 if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then
186 function cherry_pick_phase {
191 local number_of_commits
193 local sha1_to_cherry_pick
194 local singular_or_plural_commit
195 local yes_or_no_answer
196 populate_original_issue
197 if [ -z "$original_issue" ] ; then
198 error "Could not
find original issue
"
199 info "Does
${redmine_url} have a
\"Copied from
\" relation?
"
202 info "Parent issue
: ${original_issue_url}"
205 if [ -z "$original_pr" ]; then
206 error "Could not
find original PR
"
207 info "Is the
\"Pull request ID
\" field of
${original_issue_url} populated?
"
210 info "Parent issue ostensibly fixed by
: ${original_pr_url}"
212 verbose "Examining
${original_pr_url}"
213 remote_api_output=$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${original_pr}")
214 base_branch
=$
(echo "${remote_api_output}" | jq
-r '.base.label')
215 if [ "$base_branch" = "ceph:master" ] ; then
218 if [ "$FORCE" ] ; then
219 warning
"base_branch ->$base_branch<- is something other than \"ceph:master\""
220 info
"--force was given, so continuing anyway"
222 error
"${original_pr_url} is targeting ${base_branch}: cowardly refusing to perform automated cherry-pick"
223 info
"Out of an abundance of caution, the script only automates cherry-picking of commits from PRs targeting \"ceph:master\"."
224 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."
228 merged
=$
(echo "${remote_api_output}" | jq
-r '.merged')
229 if [ "$merged" = "true" ] ; then
232 error
"${original_pr_url} is not merged yet"
233 info
"Cowardly refusing to perform automated cherry-pick"
236 number_of_commits
=$
(echo "${remote_api_output}" | jq
'.commits')
237 if [ "$number_of_commits" -eq "$number_of_commits" ] 2>/dev
/null
; then
238 # \$number_of_commits is set, and is an integer
239 if [ "$number_of_commits" -eq "1" ] ; then
240 singular_or_plural_commit
="commit"
242 singular_or_plural_commit
="commits"
245 error
"Could not determine the number of commits in ${original_pr_url}"
246 bail_out_github_api
"$remote_api_output"
248 info
"Found $number_of_commits $singular_or_plural_commit in $original_pr_url"
251 git fetch
"$upstream_remote"
253 if git show-ref
--verify --quiet "refs/heads/$local_branch" ; then
254 if [ "$FORCE" ] ; then
255 if [ "$non_interactive" ] ; then
256 git checkout
"$local_branch"
257 git
reset --hard "${upstream_remote}/${milestone}"
260 echo "A local branch $local_branch already exists and the --force option was given."
261 echo "If you continue, any local changes in $local_branch will be lost!"
264 echo -n "Do you really want to overwrite ${local_branch}? (default: ${default_val}) "
265 yes_or_no_answer
="$(get_user_input "$default_val")"
266 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
267 if [ "$yes_or_no_answer" = "y" ] ; then
268 git checkout
"$local_branch"
269 git
reset --hard "${upstream_remote}/${milestone}"
271 info
"OK, bailing out!"
278 error
"Cannot initialize $local_branch - local branch already exists"
282 git checkout
"${upstream_remote}/${milestone}" -b "$local_branch"
285 git fetch
"$upstream_remote" "pull/$original_pr/head:pr-$original_pr"
289 info
"Attempting to cherry pick $number_of_commits commits from ${original_pr_url} into local branch $local_branch"
290 offset
="$((number_of_commits - 1))" || true
291 for ((i
=offset
; i
>=0; i--
)) ; do
292 info
"Running \"git cherry-pick -x\" on $(git log --oneline --max-count=1 --no-decorate "pr-
${original_pr}~
${i}")"
293 sha1_to_cherry_pick
=$
(git rev-parse
--verify "pr-${original_pr}~${i}")
295 if git cherry-pick
-x "$sha1_to_cherry_pick" ; then
301 [ "$VERBOSE" ] && git status
302 error
"Cherry pick failed"
303 info
"Next, manually fix conflicts and complete the current cherry-pick"
304 if [ "$i" -gt "0" ] >/dev
/null
2>&1 ; then
305 info
"Then, cherry-pick the remaining commits from ${original_pr_url}, i.e.:"
306 for ((j
=i-1
; j
>=0; j--
)) ; do
307 info
"-> missing commit: $(git log --oneline --max-count=1 --no-decorate "pr-
${original_pr}~
${j}")"
309 info
"Finally, re-run the script"
311 info
"Then re-run the script"
316 info
"Cherry picking completed without conflicts"
319 function clear_line
{
323 function clip_pr_body
{
326 local last_line_was_blank
=""
328 local pr_json_tempfile
=$
(mktemp
)
329 echo "$pr_body" |
sed -n '/<!--.*/q;p' > "$pr_json_tempfile"
330 while IFS
= read -r line
; do
331 if [ "$(trim_whitespace "$line")" ] ; then
332 last_line_was_blank
=""
333 clipped
="${clipped}${line}\n"
335 if [ "$last_line_was_blank" ] ; then
338 clipped
="${clipped}\n"
341 done < "$pr_json_tempfile"
342 rm "$pr_json_tempfile"
350 function deprecation_warning
{
351 echo "*******************"
352 echo "DEPRECATION WARNING"
353 echo "*******************"
355 echo "This is an outdated, unmaintained version of ceph-backport.sh. Using this"
356 echo "version can have unpredictable results. It is recommended to use the"
357 echo "version from the \"master\" branch, instead. In other words, use this:"
359 echo "https://github.com/ceph/ceph/blob/master/src/script/ceph-backport.sh"
363 function display_version_message_and_exit
{
365 echo "$this_script: Ceph backporting script, version $SCRIPT_VERSION (DEPRECATED - DO NOT USE)"
369 function dump_flagged_prs
{
372 if [ "${#flagged_pr_hash[@]}" -eq "0" ] ; then
373 info
"All backport PRs appear to have milestone set correctly"
375 warning
"Some backport PRs had problematic milestone settings"
376 log bare
"==========="
377 log bare
"Flagged PRs"
378 log bare
"-----------"
379 for url
in "${!flagged_pr_hash[@]}" ; do
380 log bare
"$url - ${flagged_pr_hash[$url]}"
382 log bare
"==========="
396 function existing_pr_routine
{
398 local clipped_pr_body
402 local pr_json_tempfile
403 local remote_api_output
405 remote_api_output
="$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${backport_pr_number}")"
406 backport_pr_title="$
(echo "$remote_api_output" | jq
-r '.title')"
407 if [ "$backport_pr_title" = "null
" ] ; then
408 error "could not get PR title of existing PR
${backport_pr_number}"
409 bail_out_github_api "$remote_api_output"
411 existing_pr_milestone_number="$
(echo "$remote_api_output" | jq
-r '.milestone.number')"
412 if [ "$existing_pr_milestone_number" = "null
" ] ; then
413 existing_pr_milestone_number=""
415 backport_pr_labels="$
(echo "$remote_api_output" | jq
-r '.labels[].name')"
416 pr_body="$
(echo "$remote_api_output" | jq
-r '.body')"
417 if [ "$pr_body" = "null
" ] ; then
418 error "could not get PR body of existing PR
${backport_pr_number}"
419 bail_out_github_api "$remote_api_output"
421 base_branch=$(echo "${remote_api_output}" | jq -r '.base.label')
422 base_branch="${base_branch#ceph:}"
423 if [ -z "$
(is_active_milestone
"$base_branch")" ] ; then
424 error "existing PR
$backport_pr_url is targeting
$base_branch which is not an active milestone
"
425 info "Cowardly refusing to work on a backport to
$base_branch"
428 clipped_pr_body="$
(clip_pr_body
"$pr_body")"
429 verbose_en "Clipped body of existing PR
${backport_pr_number}:\n${clipped_pr_body}"
430 if [[ "$backport_pr_title" =~ ^${milestone}: ]] ; then
431 verbose "Existing backport PR
${backport_pr_number} title has
${milestone} prepended
"
433 warning "Existing backport PR
${backport_pr_number} title does NOT have
${milestone} prepended
"
434 new_pr_title="${milestone}: $backport_pr_title"
435 if [[ "$new_pr_title" =~ \" ]] ; then
436 new_pr_title="${new_pr_title//\"/\\\"}"
438 verbose "New PR title
: ${new_pr_title}"
440 redmine_url_without_scheme="${redmine_url//http?:\/\//}"
441 verbose "Redmine URL without scheme
: $redmine_url_without_scheme"
442 if [[ "$clipped_pr_body" =~ $redmine_url_without_scheme ]] ; then
443 info "Existing backport PR
${backport_pr_number} already mentions
$redmine_url"
444 if [ "$FORCE" ] ; then
445 warning "--force was given
, so updating the PR body anyway
"
449 warning "Existing backport PR
${backport_pr_number} does NOT mention
$redmine_url - adding it
"
452 if [ "$update_pr_body" ] ; then
453 new_pr_body="backport tracker
: ${redmine_url}"
454 if [ "${original_pr_url}" ] ; then
455 new_pr_body="${new_pr_body}
456 possibly a backport of
${original_pr_url}"
458 if [ "${original_issue_url}" ] ; then
459 new_pr_body="${new_pr_body}
460 parent tracker
: ${original_issue_url}"
462 new_pr_body="${new_pr_body}
472 updated using ceph-backport.sh version
${SCRIPT_VERSION}"
474 maybe_update_pr_title_body "${new_pr_title}" "${new_pr_body}"
477 function failed_mandatory_var_check {
480 verbose "$varname $error"
487 local flag_reason="$3"
488 warning "flagging PR
#${pr_num} because $flag_reason"
489 flagged_pr_hash
["${pr_url}"]="$flag_reason"
494 xargs 2>/dev
/null
< "$HOME/.${what}" || true
497 function get_user_input
{
498 local default_val
="$1"
501 if [ "$user_input" ] ; then
508 # takes a string and a substring - returns position of substring within string,
510 # NOTE: position of first character in string is 0
511 function grep_for_substr
{
513 local look_for_in_str
="$2"
515 munged
="${str%%${look_for_in_str}*}"
516 if [ "$munged" = "$str" ] ; then
523 # takes PR title, attempts to guess component
524 function guess_component
{
529 local winning_comp_pos
="9999"
530 for comp
in "${!comp_hash[@]}" ; do
531 pos
=$
(grep_for_substr
"$pr_title" "$comp")
533 [ "$pos" = "-1" ] && continue
534 if [ "$pos" -lt "$winning_comp_pos" ] ; then
535 winning_comp_pos
="$pos"
539 [ "$winning_comp" ] && echo "${comp_hash["$winning_comp"]}" ||
echo ""
546 function init_endpoints
{
547 verbose
"Initializing remote API endpoints"
548 redmine_endpoint
="${redmine_endpoint:-"https://tracker.ceph.com"}"
549 github_endpoint
="${github_endpoint:-"https://github.com/ceph/ceph"}"
552 function init_fork_remote
{
553 [ "$github_user" ] || assert_fail
"github_user not set"
554 [ "$EXPLICIT_FORK" ] && info
"Using explicit fork ->$EXPLICIT_FORK<- instead of personal fork."
555 fork_remote
="${fork_remote:-$(maybe_deduce_remote fork)}"
558 function init_github_token
{
559 github_token
="$(from_file github_token)"
560 if [ "$github_token" ] ; then
563 warning
"$github_token_file not populated: initiating interactive setup routine"
564 INTERACTIVE_SETUP_ROUTINE
="yes"
568 function init_redmine_key
{
569 redmine_key
="$(from_file redmine_key)"
570 if [ "$redmine_key" ] ; then
573 warning
"$redmine_key_file not populated: initiating interactive setup routine"
574 INTERACTIVE_SETUP_ROUTINE
="yes"
578 function init_upstream_remote
{
579 upstream_remote
="${upstream_remote:-$(maybe_deduce_remote upstream)}"
582 function interactive_setup_routine
{
584 local original_github_token
585 local original_redmine_key
587 local yes_or_no_answer
588 original_github_token
="$github_token"
589 original_redmine_key
="$redmine_key"
591 if [ -e "$deprecated_backport_common" ] ; then
594 # shellcheck disable=SC1090
595 source "$deprecated_backport_common" 2>/dev
/null || true
596 total_steps
="$((total_steps+1))"
599 echo "Welcome to the ${this_script} interactive setup routine!"
601 echo "---------------------------------------------------------------------"
602 echo "Setup step 1 of $total_steps - GitHub token"
603 echo "---------------------------------------------------------------------"
604 echo "For information on how to generate a GitHub personal access token"
605 echo "to use with this script, go to https://github.com/settings/tokens"
606 echo "then click on \"Generate new token\" and make sure the token has"
607 echo "\"Full control of private repositories\" scope."
609 echo "For more details, see:"
610 echo "https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line"
612 echo -n "What is your GitHub token? "
613 default_val
="$github_token"
614 [ "$github_token" ] && echo "(default: ${default_val})"
615 github_token
="$(get_user_input "$default_val")"
616 if [ "$github_token" ] ; then
619 error
"You must provide a valid GitHub personal access token"
620 abort_due_to_setup_problem
622 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Step 1 of interactive setup"
624 echo "---------------------------------------------------------------------"
625 echo "Setup step 2 of $total_steps - GitHub user"
626 echo "---------------------------------------------------------------------"
627 echo "The script will now attempt to determine your GitHub user (login)"
628 echo "from the GitHub token provided in the previous step. If this is"
629 echo "successful, there is a good chance that your GitHub token is OK."
631 echo "Communicating with the GitHub API..."
632 set_github_user_from_github_token
633 [ "$github_user" ] || abort_due_to_setup_problem
635 echo -n "Is the GitHub username (login) \"$github_user\" correct? "
637 [ "$github_token" ] && echo "(default: ${default_val})"
638 yes_or_no_answer
="$(get_user_input "$default_val")"
639 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
640 if [ "$yes_or_no_answer" = "y" ] ; then
641 if [ "$github_token" = "$original_github_token" ] ; then
644 debug
"GitHub personal access token changed"
645 echo "$github_token" > "$github_token_file"
646 chmod 0600 "$github_token_file"
647 info
"Wrote GitHub personal access token to $github_token_file"
650 error
"GitHub user does not look right"
651 abort_due_to_setup_problem
653 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1 and 2 of interactive setup"
654 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1 and 2 of interactive setup"
656 echo "---------------------------------------------------------------------"
657 echo "Setup step 3 of $total_steps - remote repos"
658 echo "---------------------------------------------------------------------"
659 echo "Searching \"git remote -v\" for remote repos"
664 echo "Upstream remote is \"$upstream_remote\""
665 echo "Fork remote is \"$fork_remote\""
666 [ "$setup_ok" ] || abort_due_to_setup_problem
667 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1-3 of interactive setup"
668 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1-3 of interactive setup"
669 [ "$upstream_remote" ] || assert_fail
"upstream_remote not set, even after completing Steps 1-3 of interactive setup"
670 [ "$fork_remote" ] || assert_fail
"fork_remote not set, even after completing Steps 1-3 of interactive setup"
672 echo "---------------------------------------------------------------------"
673 echo "Setup step 4 of $total_steps - Redmine key"
674 echo "---------------------------------------------------------------------"
675 echo "To generate a Redmine API access key, go to https://tracker.ceph.com"
676 echo "After signing in, click: \"My account\""
677 echo "Now, find \"API access key\"."
678 echo "Once you know the API access key, enter it below."
680 echo -n "What is your Redmine key? "
681 default_val
="$redmine_key"
682 [ "$redmine_key" ] && echo "(default: ${default_val})"
683 redmine_key
="$(get_user_input "$default_val")"
684 if [ "$redmine_key" ] ; then
685 set_redmine_user_from_redmine_key
686 if [ "$setup_ok" ] ; then
689 info
"You must provide a valid Redmine API access key"
690 abort_due_to_setup_problem
692 if [ "$redmine_key" = "$original_redmine_key" ] ; then
695 debug
"Redmine API access key changed"
696 echo "$redmine_key" > "$redmine_key_file"
697 chmod 0600 "$redmine_key_file"
698 info
"Wrote Redmine API access key to $redmine_key_file"
701 error
"You must provide a valid Redmine API access key"
702 abort_due_to_setup_problem
704 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1-4 of interactive setup"
705 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1-4 of interactive setup"
706 [ "$upstream_remote" ] || assert_fail
"upstream_remote not set, even after completing Steps 1-4 of interactive setup"
707 [ "$fork_remote" ] || assert_fail
"fork_remote not set, even after completing Steps 1-4 of interactive setup"
708 [ "$redmine_key" ] || assert_fail
"redmine_key not set, even after completing Steps 1-4 of interactive setup"
709 [ "$redmine_user_id" ] || assert_fail
"redmine_user_id not set, even after completing Steps 1-4 of interactive setup"
710 [ "$redmine_login" ] || assert_fail
"redmine_login not set, even after completing Steps 1-4 of interactive setup"
711 if [ "$total_steps" -gt "4" ] ; then
713 echo "---------------------------------------------------------------------"
714 echo "Step 5 of $total_steps - delete deprecated $deprecated_backport_common file"
715 echo "---------------------------------------------------------------------"
717 maybe_delete_deprecated_backport_common
718 vet_setup
--interactive
721 function is_active_milestone
{
723 local milestone_under_test
="$1"
724 for m
in $active_milestones ; do
725 if [ "$milestone_under_test" = "$m" ] ; then
726 verbose
"Milestone $m is active"
736 local trailing_newline
="yes"
740 prefix
="${this_script}: "
747 prefix
="${prefix}DEBUG: "
751 prefix
="${prefix}ERROR: "
771 prefix
="${prefix}WARNING: "
774 if [ "$in_hex" ] ; then
776 elif [ "$verbose_only" ] && [ -z "$VERBOSE" ] ; then
779 msg
="${prefix}${msg}"
780 if [ "$trailing_newline" ] ; then
783 echo -en "${msg}" >&2
788 function maybe_deduce_remote
{
789 local remote_type
="$1"
791 local url_component
=""
792 if [ "$remote_type" = "upstream" ] ; then
794 elif [ "$remote_type" = "fork" ] ; then
795 if [ "$EXPLICIT_FORK" ] ; then
796 url_component
="$EXPLICIT_FORK"
798 url_component
="$github_user"
801 assert_fail
"bad remote_type ->$remote_type<- in maybe_deduce_remote"
803 remote
=$
(git remote
-v |
grep --extended-regexp --ignore-case '(://|@)github.com(/|:)'${url_component}'/ceph(\s|\.|\/)' |
head -n1 | cut
-f 1)
807 function maybe_delete_deprecated_backport_common
{
810 if [ -e "$deprecated_backport_common" ] ; then
811 echo "You still have a $deprecated_backport_common file,"
812 echo "which was used to store configuration parameters in version"
813 echo "15.0.0.6270 and earlier versions of ${this_script}."
815 echo "Since $deprecated_backport_common has been deprecated in favor"
816 echo "of the interactive setup routine, which has been completed"
817 echo "successfully, the file should be deleted now."
819 echo -n "Delete it now? (default: y) "
821 user_inp
="$(get_user_input "$default_val")"
822 user_inp
="$(echo "$user_inp" | tr '[:upper:]' '[:lower:]' | xargs)"
823 if [ "$user_inp" ] ; then
824 user_inp
="${user_inp:0:1}"
825 if [ "$user_inp" = "y" ] ; then
827 rm -f "$deprecated_backport_common"
832 if [ -e "$deprecated_backport_common" ] ; then
833 error
"$deprecated_backport_common still exists. Bailing out!"
839 function maybe_restore_set_x
{
840 if [ "$DEBUG" ] ; then
845 function maybe_update_pr_milestone_labels
{
850 local needs_milestone
851 if [ "$EXPLICIT_COMPONENT" ] ; then
852 debug
"Component given on command line: using it"
853 component
="$EXPLICIT_COMPONENT"
855 debug
"Attempting to guess component"
856 component
=$
(guess_component
"$backport_pr_title")
859 needs_milestone
="$(backport_pr_needs_milestone)"
860 if [ "$needs_milestone" ] ; then
861 debug
"Attempting to set ${milestone} milestone in ${backport_pr_url}"
862 data_binary
="${data_binary}\"milestone\":${milestone_number}"
864 info
"Backport PR ${backport_pr_url} already has ${milestone} milestone"
866 if [ "$(backport_pr_needs_label "$component")" ] ; then
867 debug
"Attempting to add ${component} label to ${backport_pr_url}"
868 if [ "$needs_milestone" ] ; then
869 data_binary
="${data_binary},"
871 data_binary
="${data_binary}\"labels\":[\"${component}\""
872 while read -r label
; do
873 if [ "$label" ] ; then
874 data_binary
="${data_binary},\"${label}\""
876 done <<< "$backport_pr_labels"
877 data_binary
="${data_binary}]}"
879 info
"Backport PR ${backport_pr_url} already has label ${component}"
880 data_binary
="${data_binary}}"
882 if [ "$data_binary" = "{}" ] ; then
885 blindly_set_pr_metadata
"$backport_pr_number" "$data_binary"
889 function maybe_update_pr_title_body
{
893 if [ "$new_title" ] && [ "$new_body" ] ; then
894 data_binary
="{\"title\":\"${new_title}\", \"body\":\"$(munge_body "${new_body}")\"}"
895 elif [ "$new_title" ] ; then
896 data_binary
="{\"title\":\"${new_title}\"}"
897 backport_pr_title
="${new_title}"
898 elif [ "$new_body" ] ; then
899 data_binary
="{\"body\":\"$(munge_body "${new_body}")\"}"
900 #log hex "${data_binary}"
901 #echo -n "${data_binary}"
903 if [ "$data_binary" ] ; then
904 blindly_set_pr_metadata
"${backport_pr_number}" "$data_binary"
908 function milestone_number_from_remote_api
{
909 local mtt
="$1" # milestone to try
910 local mn
="" # milestone number
912 warning
"Milestone ->$mtt<- unknown to script - falling back to GitHub API"
913 remote_api_output
=$
(curl
-u ${github_user}:${github_token} --silent -X GET
"https://api.github.com/repos/ceph/ceph/milestones")
914 mn
=$
(echo "$remote_api_output" | jq
--arg milestone
"$mtt" '.[] | select(.title==$milestone) | .number')
915 if [ "$mn" -gt "0" ] >/dev
/null
2>&1 ; then
918 error
"Could not determine milestone number of ->$milestone<-"
919 verbose_en
"GitHub API said:\n${remote_api_output}\n"
920 remote_api_output
=$
(curl
-u ${github_user}:${github_token} --silent -X GET
"https://api.github.com/repos/ceph/ceph/milestones")
921 milestones
=$
(echo "$remote_api_output" | jq
'.[].title')
922 info
"Valid values are ${milestones}"
923 info
"(This probably means the Release field of ${redmine_url} is populated with"
924 info
"an unexpected value - i.e. it does not match any of the GitHub milestones.)"
929 function munge_body
{
930 echo "$new_body" |
tr '\r' '\n' |
sed 's/$/\\n/' |
tr -d '\n'
933 function number_to_url
{
934 local number_type
="$1"
936 if [ "$number_type" = "github" ] ; then
937 echo "${github_endpoint}/pull/${number}"
938 elif [ "$number_type" = "redmine" ] ; then
939 echo "${redmine_endpoint}/issues/${number}"
941 assert_fail
"internal error in number_to_url: bad type ->$number_type<-"
945 function populate_original_issue
{
946 if [ -z "$original_issue" ] ; then
947 original_issue
=$
(curl
--silent "${redmine_url}.json?include=relations" |
948 jq
'.issue.relations[] | select(.relation_type | contains("copied_to")) | .issue_id')
949 original_issue_url
="$(number_to_url "redmine
" "${original_issue}")"
953 function populate_original_pr
{
954 if [ "$original_issue" ] ; then
955 if [ -z "$original_pr" ] ; then
956 original_pr
=$
(curl
--silent "${original_issue_url}.json" |
957 jq
-r '.issue.custom_fields[] | select(.id | contains(21)) | .value')
958 original_pr_url
="$(number_to_url "github
" "${original_pr}")"
963 function print_in_hex
{
967 for (( i
=0; i
< ${#str}; i
++ ))
972 printf "[%s] 0x%X\n" " " \'\
\' >&2
974 printf "[%s] 0x%X\n" "$c" \'"$c"\' >&2
979 function set_github_user_from_github_token
{
984 [ "$github_token" ] || assert_fail
"set_github_user_from_github_token: git_token not set"
985 curl_opts
="--silent -u :${github_token} https://api.github.com/user"
986 [ "$quiet" ] ||
set -x
987 remote_api_output
="$(curl $curl_opts)"
989 github_user
=$
(echo "${remote_api_output}" | jq
-r .login
2>/dev
/null |
grep -v null || true
)
990 api_error
=$
(echo "${remote_api_output}" | jq
-r .message
2>/dev
/null |
grep -v null || true
)
991 if [ "$api_error" ] ; then
992 info
"GitHub API said: ->$api_error<-"
993 info
"If you can't figure out what's wrong by examining the curl command and its output, above,"
994 info
"please also study https://developer.github.com/v3/users/#get-the-authenticated-user"
997 [ "$github_user" ] || assert_fail
"set_github_user_from_github_token: failed to set github_user"
998 info
"my GitHub username is $github_user"
1003 function set_redmine_user_from_redmine_key
{
1004 [ "$redmine_key" ] || assert_fail
"set_redmine_user_from_redmine_key was called, but redmine_key not set"
1005 local api_key_from_api
1006 remote_api_output
="$(curl --silent "https
://tracker.ceph.com
/users
/current.json?key
=$redmine_key")"
1007 redmine_login
="$(echo "$remote_api_output" | jq -r '.user.login')"
1008 redmine_user_id
="$(echo "$remote_api_output" | jq -r '.user.id')"
1009 api_key_from_api
="$(echo "$remote_api_output" | jq -r '.user.api_key')"
1010 if [ "$redmine_login" ] && [ "$redmine_user_id" ] && [ "$api_key_from_api" = "$redmine_key" ] ; then
1011 [ "$redmine_user_id" ] || assert_fail
"set_redmine_user_from_redmine_key: failed to set redmine_user_id"
1012 [ "$redmine_login" ] || assert_fail
"set_redmine_user_from_redmine_key: failed to set redmine_login"
1013 info
"my Redmine username is $redmine_login (ID $redmine_user_id)"
1016 error
"Redmine API access key $redmine_key is invalid"
1023 function tracker_component_is_in_desired_state
{
1026 local val_should_be
="$3"
1027 local in_desired_state
1028 if [ "$val_is" = "$val_should_be" ] ; then
1029 debug
"Tracker $comp is in the desired state"
1030 in_desired_state
="yes"
1032 echo "$in_desired_state"
1035 function tracker_component_was_updated
{
1040 if [ "$val_old" = "$val_new" ] ; then
1043 debug
"Tracker $comp was updated!"
1049 function trim_whitespace
{
1051 # remove leading whitespace characters
1052 var
="${var#"${var%%[![:space:]]*}"}"
1053 # remove trailing whitespace characters
1054 var
="${var%"${var##*[![:space:]]}"}"
1058 function troubleshooting_advice
{
1060 Troubleshooting notes
1061 ---------------------
1063 If the script inexplicably fails with:
1065 error: a cherry-pick or revert is already in progress
1066 hint: try "git cherry-pick (--continue | --quit | --abort)"
1067 fatal: cherry-pick failed
1069 This is because HEAD is not where git expects it to be:
1071 $ git cherry-pick --abort
1072 warning: You seem to have moved HEAD. Not rewinding, check your HEAD!
1074 This can be fixed by issuing the command:
1076 $ git cherry-pick --quit
1081 # to update known milestones, consult:
1082 # curl --verbose -X GET https://api.github.com/repos/ceph/ceph/milestones
1083 function try_known_milestones
{
1084 local mtt
=$1 # milestone to try
1085 local mn
="" # milestone number
1087 cuttlefish
) eol
"$mtt" ;;
1088 dumpling
) eol
"$mtt" ;;
1089 emperor
) eol
"$mtt" ;;
1090 firefly
) eol
"$mtt" ;;
1091 giant
) eol
"$mtt" ;;
1092 hammer
) eol
"$mtt" ;;
1093 infernalis
) eol
"$mtt" ;;
1095 kraken
) eol
"$mtt" ;;
1096 luminous
) mn
="10" ;;
1098 nautilus
) mn
="12" ;;
1099 octopus
) echo "Octopus milestone number is unknown! Update the script now." ; exit 1 ;;
1104 function update_version_number_and_exit
{
1107 local munge_first_hyphen
1108 # munge_first_hyphen will look like this: 15.0.0.5774-g4c2f2eda969
1109 local script_version_number
1110 raw_version
="$(git describe --long --match 'v*' | sed 's/^v//')" # example: "15.0.0-5774-g4c2f2eda969"
1111 munge_first_hyphen
="${raw_version/-/.}" # example: "15.0.0.5774-g4c2f2eda969"
1112 script_version_number
="${munge_first_hyphen%-*}" # example: "15.0.0.5774"
1113 sed -i -e "s/^SCRIPT_VERSION=.*/SCRIPT_VERSION=\"${script_version_number}\"/" "$full_path"
1121 ${this_script} --setup
1125 ${this_script} --help
1126 ${this_script} --usage | less
1127 ${this_script} --troubleshooting | less
1130 ${this_script} BACKPORT_TRACKER_ISSUE_NUMBER
1132 Options (not needed in normal operation):
1133 --cherry-pick-only (stop after cherry-pick phase)
1134 --component/-c COMPONENT
1135 (explicitly set the component label; if omitted, the
1136 script will try to guess the component)
1137 --debug (turns on "set -x")
1138 --existing-pr BACKPORT_PR_ID
1139 (use this when the backport PR is already open)
1140 --force (exercise caution!)
1141 --fork EXPLICIT_FORK (use EXPLICIT_FORK instead of personal GitHub fork)
1142 --milestones (vet all backport PRs for correct milestone setting)
1143 --setup/-s (run the interactive setup routine - NOTE: this can
1144 be done any number of times)
1145 --setup-report (check the setup and print a report)
1146 --update-version (this option exists as a convenience for the script
1147 maintainer only: not intended for day-to-day usage)
1148 --verbose/-v (produce more output than normal)
1149 --version (display version number and exit)
1152 ${this_script} 31459
1153 (if cherry-pick conflicts are present, finish cherry-picking phase manually
1154 and then run the script again with the same argument)
1156 CAVEAT: The script must be run from inside a local git clone.
1160 function usage_advice
{
1165 Once you have completed --setup, you can run the script with the ID of
1166 a Backport tracker issue. For example, to stage the backport
1167 https://tracker.ceph.com/issues/41502, run:
1169 ${this_script} 41502
1171 Provided the commits in the corresponding master PR cherry-pick cleanly, the
1172 script will automatically perform all steps required to stage the backport:
1176 1. fetching the latest commits from the upstream remote
1177 2. creating a wip branch for the backport
1178 3. figuring out which upstream PR contains the commits to cherry-pick
1179 4. cherry-picking the commits
1183 5. pushing the wip branch to your fork
1184 6. opening the backport PR with compliant title and description describing
1186 7. (optionally) setting the milestone and label in the PR
1187 8. updating the Backport tracker issue
1189 When run with --cherry-pick-only, the script will stop after the cherry-pick
1192 If any of the commits do not cherry-pick cleanly, the script will abort in
1193 step 4. In this case, you can either finish the cherry-picking manually
1194 or abort the cherry-pick. In any case, when and if the local wip branch is
1195 ready (all commits cherry-picked), if you run the script again, like so:
1197 ${this_script} 41502
1199 the script will detect that the wip branch already exists and skip over
1200 steps 1-4, starting from step 5 ("PR phase"). In other words, if the wip branch
1201 already exists for any reason, the script will assume that the cherry-pick
1202 phase (steps 1-4) is complete.
1204 As this implies, you can do steps 1-4 manually. Provided the wip branch name
1205 is in the format wip-\$TRACKER_ID-\$STABLE_RELEASE (e.g. "wip-41502-mimic"),
1206 the script will detect the wip branch and start from step 5.
1208 For details on all the options the script takes, run:
1210 ${this_script} --help
1212 For more information on Ceph backporting, see:
1214 https://github.com/ceph/ceph/tree/master/SubmittingPatches-backports.rst
1223 function verbose_en
{
1227 function vet_pr_milestone
{
1228 local pr_number
="$1"
1231 local milestone_stanza
="$4"
1232 local milestone_title_should_be
="$5"
1233 local milestone_number_should_be
1234 local milestone_number_is
=
1235 local milestone_title_is
=
1236 milestone_number_should_be
="$(try_known_milestones "$milestone_title_should_be")"
1237 log overwrite
"Vetting milestone of PR#${pr_number}\r"
1238 if [ "$milestone_stanza" = "null" ] ; then
1239 blindly_set_pr_metadata
"$pr_number" "{\"milestone\": $milestone_number_should_be}"
1240 warning
"$pr_url: set milestone to \"$milestone_title_should_be\""
1241 flag_pr
"$pr_number" "$pr_url" "milestone not set"
1243 milestone_title_is
=$
(echo "$milestone_stanza" | jq
-r '.title')
1244 milestone_number_is
=$
(echo "$milestone_stanza" | jq
-r '.number')
1245 if [ "$milestone_number_is" -eq "$milestone_number_should_be" ] ; then
1248 blindly_set_pr_metadata
"$pr_number" "{\"milestone\": $milestone_number_should_be}"
1249 warning
"$pr_url: changed milestone from \"$milestone_title_is\" to \"$milestone_title_should_be\""
1250 flag_pr
"$pr_number" "$pr_url" "milestone set to wrong value \"$milestone_title_is\""
1255 function vet_prs_for_milestone
{
1256 local milestone_title
="$1"
1257 local pages_of_output
=
1261 # determine last page (i.e., total number of pages)
1262 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
)"
1263 if [ "$remote_api_output" ] ; then
1264 # 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
"
1265 # shellcheck disable=SC2001
1266 pages_of_output="$
(echo "$remote_api_output" |
sed 's/^.*&page\=\([0-9]\+\)>; rel=\"last\".*$/\1/g')"
1270 verbose "GitHub has
$pages_of_output pages of pull request data
for \"base
:${milestone_title}\""
1271 for ((page=1; page<=pages_of_output; page++)) ; do
1272 verbose "Fetching PRs
(page
$page of
${pages_of_output})"
1273 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}")"
1274 prs_in_page
="$(echo "$remote_api_output" | jq -r '. | length')"
1275 verbose
"Page $page of remote API output contains information on $prs_in_page PRs"
1276 for ((i
=0; i
<prs_in_page
; i
++)) ; do
1277 pr_number
="$(echo "$remote_api_output" | jq -r ".
[${i}].number
")"
1278 pr_title
="$(echo "$remote_api_output" | jq -r ".
[${i}].title
")"
1279 pr_url
="$(number_to_url "github
" "${pr_number}")"
1280 milestone_stanza
="$(echo "$remote_api_output" | jq -r ".
[${i}].milestone
")"
1281 vet_pr_milestone
"$pr_number" "$pr_title" "$pr_url" "$milestone_stanza" "$milestone_title"
1287 function vet_remotes
{
1288 if [ "$upstream_remote" ] ; then
1289 verbose
"Upstream remote is $upstream_remote"
1291 error
"Cannot auto-determine upstream remote"
1292 "(Could not find any upstream remote in \"git remote -v\")"
1295 if [ "$fork_remote" ] ; then
1296 verbose
"Fork remote is $fork_remote"
1298 error
"Cannot auto-determine fork remote"
1299 if [ "$EXPLICIT_FORK" ] ; then
1300 info
"(Could not find $EXPLICIT_FORK fork of ceph/ceph in \"git remote -v\")"
1302 info
"(Could not find GitHub user ${github_user}'s fork of ceph/ceph in \"git remote -v\")"
1308 function vet_setup
{
1310 local not_set
="!!! NOT SET !!!"
1311 local invalid
="!!! INVALID !!!"
1312 local redmine_endpoint_display
1313 local redmine_user_id_display
1314 local github_endpoint_display
1315 local github_user_display
1316 local upstream_remote_display
1317 local fork_remote_display
1318 local redmine_key_display
1319 local github_token_display
1320 debug
"Entering vet_setup with argument $argument"
1321 if [ "$argument" = "--report" ] ||
[ "$argument" = "--normal-operation" ] ; then
1322 [ "$github_token" ] && [ "$setup_ok" ] && set_github_user_from_github_token quiet
1323 init_upstream_remote
1324 [ "$github_token" ] && [ "$setup_ok" ] && init_fork_remote
1326 [ "$redmine_key" ] && set_redmine_user_from_redmine_key
1328 if [ "$github_token" ] ; then
1329 if [ "$setup_ok" ] ; then
1330 github_token_display
="(OK; value not shown)"
1332 github_token_display
="$invalid"
1335 github_token_display
="$not_set"
1337 if [ "$redmine_key" ] ; then
1338 if [ "$setup_ok" ] ; then
1339 redmine_key_display
="(OK; value not shown)"
1341 redmine_key_display
="$invalid"
1344 redmine_key_display
="$not_set"
1346 redmine_endpoint_display
="${redmine_endpoint:-$not_set}"
1347 redmine_user_id_display
="${redmine_user_id:-$not_set}"
1348 github_endpoint_display
="${github_endpoint:-$not_set}"
1349 github_user_display
="${github_user:-$not_set}"
1350 upstream_remote_display
="${upstream_remote:-$not_set}"
1351 fork_remote_display
="${fork_remote:-$not_set}"
1352 test "$redmine_endpoint" || failed_mandatory_var_check redmine_endpoint
"not set"
1353 test "$redmine_user_id" || failed_mandatory_var_check redmine_user_id
"could not be determined"
1354 test "$redmine_key" || failed_mandatory_var_check redmine_key
"not set"
1355 test "$github_endpoint" || failed_mandatory_var_check github_endpoint
"not set"
1356 test "$github_user" || failed_mandatory_var_check github_user
"could not be determined"
1357 test "$github_token" || failed_mandatory_var_check github_token
"not set"
1358 test "$upstream_remote" || failed_mandatory_var_check upstream_remote
"could not be determined"
1359 test "$fork_remote" || failed_mandatory_var_check fork_remote
"could not be determined"
1360 if [ "$argument" = "--report" ] ||
[ "$argument" == "--interactive" ] ; then
1361 read -r -d '' setup_summary
<<EOM || true > /dev/null 2>&1
1362 redmine_endpoint $redmine_endpoint
1363 redmine_user_id $redmine_user_id_display
1364 redmine_key $redmine_key_display
1365 github_endpoint $github_endpoint
1366 github_user $github_user_display
1367 github_token $github_token_display
1368 upstream_remote $upstream_remote_display
1369 fork_remote $fork_remote_display
1372 log bare
"============================================="
1373 log bare
" ${this_script} setup report"
1374 log bare
"============================================="
1375 log bare
"variable name value"
1376 log bare
"---------------------------------------------"
1377 log bare
"$setup_summary"
1378 log bare
"---------------------------------------------"
1380 verbose
"redmine_endpoint $redmine_endpoint_display"
1381 verbose
"redmine_user_id $redmine_user_id_display"
1382 verbose
"redmine_key $redmine_key_display"
1383 verbose
"github_endpoint $github_endpoint_display"
1384 verbose
"github_user $github_user_display"
1385 verbose
"github_token $github_token_display"
1386 verbose
"upstream_remote $upstream_remote_display"
1387 verbose
"fork_remote $fork_remote_display"
1389 if [ "$argument" = "--report" ] ||
[ "$argument" = "--interactive" ] ; then
1390 if [ "$setup_ok" ] ; then
1393 info
"setup is NOT OK"
1395 log bare
"=============================================="
1406 # are we in a local git clone?
1409 if git status
>/dev
/null
2>&1 ; then
1410 debug
"In a local git clone. Good."
1412 error
"This script must be run from inside a local git clone"
1413 abort_due_to_setup_problem
1418 # process command-line arguments
1421 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" -- "$@")
1422 eval set -- "$munged_options"
1427 CHERRY_PICK_PHASE
="yes"
1430 EXPLICIT_COMPONENT
=""
1434 INTERACTIVE_SETUP_ROUTINE
=""
1439 TROUBLESHOOTING_ADVICE
=""
1444 --cherry-pick-only) CHERRY_PICK_PHASE
="yes" ; PR_PHASE
="" ; TRACKER_PHASE
="" ; shift ;;
1445 --component|
-c) shift ; EXPLICIT_COMPONENT
="$1" ; shift ;;
1446 --debug|
-d) DEBUG
="$1" ; shift ;;
1447 --existing-pr) shift ; EXISTING_PR
="$1" ; CHERRY_PICK_PHASE
="" ; PR_PHASE
="" ; shift ;;
1448 --force) FORCE
="$1" ; shift ;;
1449 --fork) shift ; EXPLICIT_FORK
="$1" ; shift ;;
1450 --help|
-h) ADVICE
="1" ; HELP
="$1" ; shift ;;
1451 --milestones) CHECK_MILESTONES
="$1" ; shift ;;
1452 --prepare) CHERRY_PICK_PHASE
="yes" ; PR_PHASE
="" ; TRACKER_PHASE
="" ; shift ;;
1453 --setup*|
-s) SETUP_OPTION
="$1" ; shift ;;
1454 --troubleshooting) ADVICE
="$1" ; TROUBLESHOOTING_ADVICE
="$1" ; shift ;;
1455 --update-version) update_version_number_and_exit
;;
1456 --usage) ADVICE
="$1" ; USAGE_ADVICE
="$1" ; shift ;;
1457 --verbose|
-v) VERBOSE
="$1" ; shift ;;
1458 --version) display_version_message_and_exit
;;
1459 --) shift ; ISSUE
="$1" ; break ;;
1460 *) echo "Internal error" ; false
;;
1465 echo "Sleeping for 5 seconds to give you time to hit CTRL-C..."
1468 if [ "$ADVICE" ] ; then
1469 [ "$HELP" ] && usage
1470 [ "$USAGE_ADVICE" ] && usage_advice
1471 [ "$TROUBLESHOOTING_ADVICE" ] && troubleshooting_advice
1475 if [ "$SETUP_OPTION" ] ||
[ "$CHECK_MILESTONES" ] ; then
1479 if [[ $ISSUE =~ ^
[0-9]+$
]] ; then
1482 error
"Invalid or missing argument"
1487 if [ "$DEBUG" ]; then
1492 if [ "$VERBOSE" ]; then
1493 info
"Verbose mode ON"
1499 # make sure setup has been completed
1506 if [ "$SETUP_OPTION" ] ; then
1508 maybe_delete_deprecated_backport_common
1509 if [ "$setup_ok" ] ; then
1513 echo -n "Run the interactive setup routine now? (default: ${default_val}) "
1514 yes_or_no_answer
="$(get_user_input "$default_val")"
1515 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
1516 if [ "$yes_or_no_answer" = "y" ] ; then
1517 INTERACTIVE_SETUP_ROUTINE
="yes"
1519 if [ "$FORCE" ] ; then
1520 warning
"--force was given; proceeding with broken setup"
1528 if [ "$INTERACTIVE_SETUP_ROUTINE" ] ; then
1529 interactive_setup_routine
1531 vet_setup
--normal-operation
1532 maybe_delete_deprecated_backport_common
1534 if [ "$INTERACTIVE_SETUP_ROUTINE" ] ||
[ "$SETUP_OPTION" ] ; then
1536 if [ "$setup_ok" ] ; then
1537 if [ "$ISSUE" ] && [ "$ISSUE" != "0" ] ; then
1547 [ "$setup_ok" ] || abort_due_to_setup_problem
1550 # query remote GitHub API for active milestones
1553 verbose
"Querying GitHub API for active milestones"
1554 remote_api_output
="$(curl -u ${github_user}:${github_token} --silent -X GET "https
://api.github.com
/repos
/ceph
/ceph
/milestones
")"
1555 active_milestones
="$(echo "$remote_api_output" | jq -r '.[] | .title')"
1556 if [ "$active_milestones" = "null" ] ; then
1557 error
"Could not determine the active milestones"
1558 bail_out_github_api
"$remote_api_output"
1561 if [ "$CHECK_MILESTONES" ] ; then
1562 check_milestones
"$active_milestones"
1567 # query remote Redmine API for information about the Backport tracker issue
1570 redmine_url
="$(number_to_url "redmine
" "${issue}")"
1571 debug
"Considering Redmine issue: $redmine_url - is it in the Backport tracker?"
1573 remote_api_output
="$(curl --silent "${redmine_url}.json
")"
1574 tracker
="$(echo "$remote_api_output" | jq -r '.issue.tracker.name')"
1575 if [ "$tracker" = "Backport" ]; then
1576 debug
"Yes, $redmine_url is a Backport issue"
1578 error
"Issue $redmine_url is not a Backport"
1579 info
"(This script only works with Backport tracker issues.)"
1583 debug
"Looking up release/milestone of $redmine_url"
1584 milestone
="$(echo "$remote_api_output" | jq -r '.issue.custom_fields[0].value')"
1585 if [ "$milestone" ] ; then
1586 debug
"Release/milestone: $milestone"
1588 error
"could not obtain release/milestone from ${redmine_url}"
1592 debug
"Looking up status of $redmine_url"
1593 tracker_status_id
="$(echo "$remote_api_output" | jq -r '.issue.status.id')"
1594 tracker_status_name
="$(echo "$remote_api_output" | jq -r '.issue.status.name')"
1595 if [ "$tracker_status_name" ] ; then
1596 debug
"Tracker status: $tracker_status_name"
1597 if [ "$FORCE" ] ||
[ "$EXISTING_PR" ] ; then
1598 test "$(check_tracker_status "$tracker_status_name")" || true
1600 test "$(check_tracker_status "$tracker_status_name")"
1603 error
"could not obtain status from ${redmine_url}"
1607 tracker_title
="$(echo "$remote_api_output" | jq -r '.issue.subject')"
1608 debug
"Title of $redmine_url is ->$tracker_title<-"
1610 tracker_description
="$(echo "$remote_api_output" | jq -r '.issue.description')"
1611 debug
"Description of $redmine_url is ->$tracker_description<-"
1613 tracker_assignee_id
="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.id')"
1614 tracker_assignee_name
="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.name')"
1615 if [ "$tracker_assignee_id" = "null" ] ||
[ "$tracker_assignee_id" = "$redmine_user_id" ] ; then
1618 error_msg_1
="$redmine_url is assigned to someone else: $tracker_assignee_name (ID $tracker_assignee_id)"
1619 error_msg_2
="(my ID is $redmine_user_id)"
1620 if [ "$FORCE" ] ||
[ "$EXISTING_PR" ] ; then
1621 warning
"$error_msg_1"
1623 info
"--force and/or --existing-pr given: continuing execution"
1625 error
"$error_msg_1"
1627 info
"Cowardly refusing to continue"
1632 if [ -z "$(is_active_milestone "$milestone")" ] ; then
1633 error
"$redmine_url is a backport to $milestone which is not an active milestone"
1634 info
"Cowardly refusing to work on a backport to an inactive release"
1638 milestone_number
=$
(try_known_milestones
"$milestone")
1639 if [ "$milestone_number" -gt "0" ] >/dev
/null
2>&1 ; then
1640 target_branch
="$milestone"
1642 milestone_number
=$
(milestone_number_from_remote_api
"$milestone")
1644 info
"milestone/release is $milestone"
1645 debug
"milestone number is $milestone_number"
1647 if [ "$CHERRY_PICK_PHASE" ] ; then
1648 local_branch
=wip-
${issue}-${target_branch}
1649 if git show-ref
--verify --quiet "refs/heads/$local_branch" ; then
1650 if [ "$FORCE" ] ; then
1651 warning
"local branch $local_branch already exists"
1652 info
"--force was given: will clobber $local_branch and attempt automated cherry-pick"
1654 elif [ "$CHERRY_PICK_ONLY" ] ; then
1655 error
"local branch $local_branch already exists"
1656 info
"Cowardly refusing to clobber $local_branch as it might contain valuable data"
1657 info
"(hint) run with --force to clobber it and attempt the cherry-pick"
1660 if [ "$FORCE" ] ||
[ "$CHERRY_PICK_ONLY" ] ; then
1663 info
"local branch $local_branch already exists: skipping cherry-pick phase"
1666 info
"$local_branch does not exist: will create it and attempt automated cherry-pick"
1671 if [ "$PR_PHASE" ] ; then
1672 current_branch
=$
(git rev-parse
--abbrev-ref HEAD
)
1673 if [ "$current_branch" = "$local_branch" ] ; then
1677 git checkout
"$local_branch"
1683 git push
-u "$fork_remote" "$local_branch"
1691 debug
"Generating backport PR description"
1692 populate_original_issue
1693 populate_original_pr
1694 desc
="backport tracker: ${redmine_url}"
1695 if [ "$original_pr" ] ||
[ "$original_issue" ] ; then
1696 desc
="${desc}\n\n---\n"
1697 [ "$original_pr" ] && desc
="${desc}\nbackport of $(number_to_url "github
" "${original_pr}")"
1698 [ "$original_issue" ] && desc
="${desc}\nparent tracker: $(number_to_url "redmine
" "${original_issue}")"
1700 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"
1702 debug
"Generating backport PR title"
1703 if [ "$original_pr" ] ; then
1704 backport_pr_title
="${milestone}: $(curl --silent https://api.github.com/repos/ceph/ceph/pulls/${original_pr} | jq -r '.title')"
1706 if [[ $tracker_title =~ ^
${milestone}: ]] ; then
1707 backport_pr_title
="${tracker_title}"
1709 backport_pr_title
="${milestone}: ${tracker_title}"
1712 if [[ "$backport_pr_title" =~
\" ]] ; then
1713 backport_pr_title
="${backport_pr_title//\"/\\\"}"
1716 debug
"Opening backport PR"
1717 if [ "$EXPLICIT_FORK" ] ; then
1718 source_repo
="$EXPLICIT_FORK"
1720 source_repo
="$github_user"
1722 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
")
1723 backport_pr_number=$(echo "$remote_api_output" | jq -r .number)
1724 if [ -z "$backport_pr_number" ] || [ "$backport_pr_number" = "null
" ] ; then
1725 error "failed to open backport PR
"
1726 bail_out_github_api "$remote_api_output"
1728 backport_pr_url="$
(number_to_url
"github" "$backport_pr_number")"
1729 info "Opened backport PR
${backport_pr_url}"
1732 if [ "$EXISTING_PR" ] ; then
1733 populate_original_issue
1734 populate_original_pr
1735 backport_pr_number="$EXISTING_PR"
1736 backport_pr_url="$
(number_to_url
"github" "$backport_pr_number")"
1740 if [ "$PR_PHASE" ] || [ "$EXISTING_PR" ] ; then
1741 maybe_update_pr_milestone_labels
1742 pgrep firefox >/dev/null && firefox "${backport_pr_url}"
1745 if [ "$TRACKER_PHASE" ] ; then
1746 debug "Considering Backport tracker issue
${redmine_url}"
1747 status_should_be=2 # In Progress
1748 desc_should_be="${backport_pr_url}"
1749 assignee_should_be="${redmine_user_id}"
1750 if [ "$EXISTING_PR" ] ; then
1751 data_binary="{\"issue
\":{\"description
\":\"${desc_should_be}\",\"status_id\":${status_should_be}}}"
1753 data_binary="{\"issue
\":{\"description
\":\"${desc_should_be}\",\"status_id\":${status_should_be},\"assigned_to_id\":${assignee_should_be}}}"
1755 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")"
1756 if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then
1759 if [ "${remote_api_status_code:0:1}" = "2" ] ; then
1761 elif [ "${remote_api_status_code:0:1}" = "4" ] ; then
1762 warning "remote API
${redmine_endpoint} returned status
${remote_api_status_code}"
1763 info "This merely indicates that you cannot modify issue fields
at ${redmine_endpoint}"
1764 info "and does not limit your ability to
do backports.
"
1766 error "Remote API
${redmine_endpoint} returned unexpected response code
${remote_api_status_code}"
1769 # check if anything actually changed on the Redmine issue
1770 remote_api_output=$(curl --silent "${redmine_url}.json?include
=journals
")
1771 status_is="$
(echo "$remote_api_output" | jq
-r '.issue.status.id')"
1772 desc_is="$
(echo "$remote_api_output" | jq
-r '.issue.description')"
1773 assignee_is="$
(echo "$remote_api_output" | jq
-r '.issue.assigned_to.id')"
1774 tracker_was_updated=""
1775 tracker_is_in_desired_state="yes"
1776 [ "$
(tracker_component_was_updated
"status" "$tracker_status_id" "$status_is")" ] && tracker_was_updated="yes"
1777 [ "$
(tracker_component_was_updated
"desc" "$tracker_description" "$desc_is")" ] && tracker_was_updated="yes"
1778 if [ "$EXISTING_PR" ] ; then
1781 [ "$
(tracker_component_was_updated
"assignee" "$tracker_assignee_id" "$assignee_is")" ] && tracker_was_updated="yes"
1783 [ "$
(tracker_component_is_in_desired_state
"status" "$status_is" "$status_should_be")" ] || tracker_is_in_desired_state=""
1784 [ "$
(tracker_component_is_in_desired_state
"desc" "$desc_is" "$desc_should_be")" ] || tracker_is_in_desired_state=""
1785 if [ "$EXISTING_PR" ] ; then
1788 [ "$
(tracker_component_is_in_desired_state
"assignee" "$assignee_is" "$assignee_should_be")" ] || tracker_is_in_desired_state=""
1790 if [ "$tracker_is_in_desired_state" ] ; then
1791 [ "$tracker_was_updated" ] && info "Backport tracker
${redmine_url} was updated
"
1792 info "Backport tracker
${redmine_url} is
in the desired state
"
1793 pgrep firefox >/dev/null && firefox "${redmine_url}"
1796 if [ "$tracker_was_updated" ] ; then
1797 warning "backport tracker
${redmine_url} was updated
, but is not
in the desired state. Please check it.
"
1798 pgrep firefox >/dev/null && firefox "${redmine_url}"
1801 data_binary="{\"issue
\":{\"notes
\":\"please link this Backport tracker issue with GitHub PR
${desc_should_be}\nceph-backport.sh version ${SCRIPT_VERSION}\"}}"
1802 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")
1803 if [ "${remote_api_status_code:0:1}" = "2" ] ; then
1804 info "Comment added to
${redmine_url}"