4 # ceph-backport.sh - Ceph backporting script
6 # Credits: This script is based on work done by Loic Dachary
9 # This script automates the process of staging a backport starting from a
10 # Backport tracker issue.
14 # ceph-backport.sh --setup
16 # Usage and troubleshooting:
18 # ceph-backport.sh --help
19 # ceph-backport.sh --usage | less
20 # ceph-backport.sh --troubleshooting | less
25 SCRIPT_VERSION
="16.0.0.6848"
31 deprecated_backport_common
="$HOME/bin/backport_common.sh"
32 existing_pr_milestone_number
=""
34 github_token_file
="$HOME/.github_token"
43 redmine_key_file
="$HOME/.redmine_key"
47 this_script
=$
(basename "$full_path")
49 if [[ $
* == *--debug* ]]; then
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
=(
57 ["bluestore"]="bluestore"
58 ["build/ops"]="build/ops"
59 ["ceph.spec"]="build/ops"
60 ["ceph-volume"]="ceph-volume"
68 ["dashboard"]="dashboard"
70 ["doc"]="documentation"
71 ["grafana"]="monitoring"
76 ["mgr/cephadm"]="cephadm"
77 ["mgr/dashboard"]="dashboard"
78 ["mgr/prometheus"]="monitoring"
80 ["monitoring"]="monitoring"
81 ["orch"]="orchestrator"
83 ["perf"]="performance"
84 ["prometheus"]="monitoring"
96 declare -A flagged_pr_hash
=()
98 function abort_due_to_setup_problem
{
99 error
"problem detected in your setup"
100 info
"Run \"${this_script} --setup\" to fix"
104 function assert_fail
{
106 error
"(internal error) $message"
107 info
"This could be reported as a bug!"
111 function backport_pr_needs_label
{
112 local check_label
="$1"
114 local needs_label
="yes"
115 while read -r label
; do
116 if [ "$label" = "$check_label" ] ; then
119 done <<< "$backport_pr_labels"
123 function backport_pr_needs_milestone
{
124 if [ "$existing_pr_milestone_number" ] ; then
131 function bail_out_github_api
{
134 info
"GitHub API said:"
136 if [ "$hint" ] ; then
139 abort_due_to_setup_problem
142 function blindly_set_pr_metadata
{
145 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
148 function check_milestones {
149 local milestones_to_check
150 milestones_to_check="$
(echo "$1" |
tr '\n' ' ' |
xargs)"
151 info "Active milestones
: $milestones_to_check"
152 for m in $milestones_to_check ; do
153 info "Examining all PRs targeting base branch
\"$m\""
154 vet_prs_for_milestone "$m"
159 function check_tracker_status {
160 local -a ok_statuses=("new
" "need
more info
")
165 for oks in "${ok_statuses[@]}"; do
166 if [ "$tslc" = "$oks" ] ; then
167 debug "Tracker status
$ts is OK
for backport to proceed
"
172 if [ "$tslc_is_ok" ] ; then
175 if [ "$tslc" = "in progress
" ] ; then
176 error_msg="backport
$redmine_url is already
in progress
"
178 error_msg="backport
$redmine_url is closed
(status
: ${ts})"
180 if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then
189 function cherry_pick_phase {
194 local number_of_commits
196 local sha1_to_cherry_pick
197 local singular_or_plural_commit
198 local yes_or_no_answer
199 populate_original_issue
200 if [ -z "$original_issue" ] ; then
201 error "Could not
find original issue
"
202 info "Does
${redmine_url} have a
\"Copied from
\" relation?
"
205 info "Parent issue
: ${original_issue_url}"
208 if [ -z "$original_pr" ]; then
209 error "Could not
find original PR
"
210 info "Is the
\"Pull request ID
\" field of
${original_issue_url} populated?
"
213 info "Parent issue ostensibly fixed by
: ${original_pr_url}"
215 verbose "Examining
${original_pr_url}"
216 remote_api_output=$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${original_pr}")
217 base_branch
=$
(echo "${remote_api_output}" | jq
-r '.base.label')
218 if [ "$base_branch" = "ceph:master" -o "$base_branch" = "ceph:main" ] ; then
221 if [ "$FORCE" ] ; then
222 warning
"base_branch ->$base_branch<- is something other than \"ceph:master\" or \"ceph:main\""
223 info
"--force was given, so continuing anyway"
225 error
"${original_pr_url} is targeting ${base_branch}: cowardly refusing to perform automated cherry-pick"
226 info
"Out of an abundance of caution, the script only automates cherry-picking of commits from PRs targeting \"ceph:master\" or \"ceph:main\"."
227 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."
231 merged
=$
(echo "${remote_api_output}" | jq
-r '.merged')
232 if [ "$merged" = "true" ] ; then
235 error
"${original_pr_url} is not merged yet"
236 info
"Cowardly refusing to perform automated cherry-pick"
239 number_of_commits
=$
(echo "${remote_api_output}" | jq
'.commits')
240 if [ "$number_of_commits" -eq "$number_of_commits" ] 2>/dev
/null
; then
241 # \$number_of_commits is set, and is an integer
242 if [ "$number_of_commits" -eq "1" ] ; then
243 singular_or_plural_commit
="commit"
245 singular_or_plural_commit
="commits"
248 error
"Could not determine the number of commits in ${original_pr_url}"
249 bail_out_github_api
"$remote_api_output"
251 info
"Found $number_of_commits $singular_or_plural_commit in $original_pr_url"
254 git fetch
"$upstream_remote"
256 if git show-ref
--verify --quiet "refs/heads/$local_branch" ; then
257 if [ "$FORCE" ] ; then
258 if [ "$non_interactive" ] ; then
259 git checkout
"$local_branch"
260 git
reset --hard "${upstream_remote}/${milestone}"
263 echo "A local branch $local_branch already exists and the --force option was given."
264 echo "If you continue, any local changes in $local_branch will be lost!"
267 echo -n "Do you really want to overwrite ${local_branch}? (default: ${default_val}) "
268 yes_or_no_answer
="$(get_user_input "$default_val")"
269 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
270 if [ "$yes_or_no_answer" = "y" ] ; then
271 git checkout
"$local_branch"
272 git
reset --hard "${upstream_remote}/${milestone}"
274 info
"OK, bailing out!"
281 error
"Cannot initialize $local_branch - local branch already exists"
285 git checkout
"${upstream_remote}/${milestone}" -b "$local_branch"
288 git fetch
"$upstream_remote" "pull/$original_pr/head:pr-$original_pr"
292 info
"Attempting to cherry pick $number_of_commits commits from ${original_pr_url} into local branch $local_branch"
293 offset
="$((number_of_commits - 1))" || true
294 for ((i
=offset
; i
>=0; i--
)) ; do
295 info
"Running \"git cherry-pick -x\" on $(git log --oneline --max-count=1 --no-decorate "pr-
${original_pr}~
${i}")"
296 sha1_to_cherry_pick
=$
(git rev-parse
--verify "pr-${original_pr}~${i}")
298 if git cherry-pick
-x "$sha1_to_cherry_pick" ; then
304 [ "$VERBOSE" ] && git status
305 error
"Cherry pick failed"
306 info
"Next, manually fix conflicts and complete the current cherry-pick"
307 if [ "$i" -gt "0" ] >/dev
/null
2>&1 ; then
308 info
"Then, cherry-pick the remaining commits from ${original_pr_url}, i.e.:"
309 for ((j
=i-1
; j
>=0; j--
)) ; do
310 info
"-> missing commit: $(git log --oneline --max-count=1 --no-decorate "pr-
${original_pr}~
${j}")"
312 info
"Finally, re-run the script"
314 info
"Then re-run the script"
319 info
"Cherry picking completed without conflicts"
322 function clear_line
{
326 function clip_pr_body
{
329 local last_line_was_blank
=""
331 local pr_json_tempfile
=$
(mktemp
)
332 echo "$pr_body" |
sed -n '/<!--.*/q;p' > "$pr_json_tempfile"
333 while IFS
= read -r line
; do
334 if [ "$(trim_whitespace "$line")" ] ; then
335 last_line_was_blank
=""
336 clipped
="${clipped}${line}\n"
338 if [ "$last_line_was_blank" ] ; then
341 clipped
="${clipped}\n"
344 done < "$pr_json_tempfile"
345 rm "$pr_json_tempfile"
353 function display_version_message_and_exit
{
354 echo "$this_script: Ceph backporting script, version $SCRIPT_VERSION"
358 function dump_flagged_prs
{
361 if [ "${#flagged_pr_hash[@]}" -eq "0" ] ; then
362 info
"All backport PRs appear to have milestone set correctly"
364 warning
"Some backport PRs had problematic milestone settings"
365 log bare
"==========="
366 log bare
"Flagged PRs"
367 log bare
"-----------"
368 for url
in "${!flagged_pr_hash[@]}" ; do
369 log bare
"$url - ${flagged_pr_hash[$url]}"
371 log bare
"==========="
385 function existing_pr_routine
{
387 local clipped_pr_body
391 local pr_json_tempfile
392 local remote_api_output
394 remote_api_output
="$(curl -u ${github_user}:${github_token} --silent "https://api.github.com/repos/ceph/ceph/pulls/${backport_pr_number}")"
395 backport_pr_title="$
(echo "$remote_api_output" | jq
-r '.title')"
396 if [ "$backport_pr_title" = "null
" ] ; then
397 error "could not get PR title of existing PR
${backport_pr_number}"
398 bail_out_github_api "$remote_api_output"
400 existing_pr_milestone_number="$
(echo "$remote_api_output" | jq
-r '.milestone.number')"
401 if [ "$existing_pr_milestone_number" = "null
" ] ; then
402 existing_pr_milestone_number=""
404 backport_pr_labels="$
(echo "$remote_api_output" | jq
-r '.labels[].name')"
405 pr_body="$
(echo "$remote_api_output" | jq
-r '.body')"
406 if [ "$pr_body" = "null
" ] ; then
407 error "could not get PR body of existing PR
${backport_pr_number}"
408 bail_out_github_api "$remote_api_output"
410 base_branch=$(echo "${remote_api_output}" | jq -r '.base.label')
411 base_branch="${base_branch#ceph:}"
412 if [ -z "$
(is_active_milestone
"$base_branch")" ] ; then
413 error "existing PR
$backport_pr_url is targeting
$base_branch which is not an active milestone
"
414 info "Cowardly refusing to work on a backport to
$base_branch"
417 clipped_pr_body="$
(clip_pr_body
"$pr_body")"
418 verbose_en "Clipped body of existing PR
${backport_pr_number}:\n${clipped_pr_body}"
419 if [[ "$backport_pr_title" =~ ^${milestone}: ]] ; then
420 verbose "Existing backport PR
${backport_pr_number} title has
${milestone} prepended
"
422 warning "Existing backport PR
${backport_pr_number} title does NOT have
${milestone} prepended
"
423 new_pr_title="${milestone}: $backport_pr_title"
424 if [[ "$new_pr_title" =~ \" ]] ; then
425 new_pr_title="${new_pr_title//\"/\\\"}"
427 verbose "New PR title
: ${new_pr_title}"
429 redmine_url_without_scheme="${redmine_url//http?:\/\//}"
430 verbose "Redmine URL without scheme
: $redmine_url_without_scheme"
431 if [[ "$clipped_pr_body" =~ $redmine_url_without_scheme ]] ; then
432 info "Existing backport PR
${backport_pr_number} already mentions
$redmine_url"
433 if [ "$FORCE" ] ; then
434 warning "--force was given
, so updating the PR body anyway
"
438 warning "Existing backport PR
${backport_pr_number} does NOT mention
$redmine_url - adding it
"
441 if [ "$update_pr_body" ] ; then
442 new_pr_body="backport tracker
: ${redmine_url}"
443 if [ "${original_pr_url}" ] ; then
444 new_pr_body="${new_pr_body}
445 possibly a backport of
${original_pr_url}"
447 if [ "${original_issue_url}" ] ; then
448 new_pr_body="${new_pr_body}
449 parent tracker
: ${original_issue_url}"
451 new_pr_body="${new_pr_body}
461 updated using ceph-backport.sh version
${SCRIPT_VERSION}"
463 maybe_update_pr_title_body "${new_pr_title}" "${new_pr_body}"
466 function failed_mandatory_var_check {
469 verbose "$varname $error"
476 local flag_reason="$3"
477 warning "flagging PR
#${pr_num} because $flag_reason"
478 flagged_pr_hash
["${pr_url}"]="$flag_reason"
483 xargs 2>/dev
/null
< "$HOME/.${what}" || true
486 function get_user_input
{
487 local default_val
="$1"
490 if [ "$user_input" ] ; then
497 # takes a string and a substring - returns position of substring within string,
499 # NOTE: position of first character in string is 0
500 function grep_for_substr
{
502 local look_for_in_str
="$2"
504 munged
="${str%%${look_for_in_str}*}"
505 if [ "$munged" = "$str" ] ; then
512 # takes PR title, attempts to guess component
513 function guess_component
{
518 local winning_comp_pos
="9999"
519 for comp
in "${!comp_hash[@]}" ; do
520 pos
=$
(grep_for_substr
"$pr_title" "$comp")
522 [ "$pos" = "-1" ] && continue
523 if [ "$pos" -lt "$winning_comp_pos" ] ; then
524 winning_comp_pos
="$pos"
528 [ "$winning_comp" ] && echo "${comp_hash["$winning_comp"]}" ||
echo ""
535 function init_endpoints
{
536 verbose
"Initializing remote API endpoints"
537 redmine_endpoint
="${redmine_endpoint:-"https://tracker.ceph.com"}"
538 github_endpoint
="${github_endpoint:-"https://github.com/ceph/ceph"}"
541 function init_fork_remote
{
542 [ "$github_user" ] || assert_fail
"github_user not set"
543 [ "$EXPLICIT_FORK" ] && info
"Using explicit fork ->$EXPLICIT_FORK<- instead of personal fork."
544 fork_remote
="${fork_remote:-$(maybe_deduce_remote fork)}"
547 function init_github_token
{
548 github_token
="$(from_file github_token)"
549 if [ "$github_token" ] ; then
552 warning
"$github_token_file not populated: initiating interactive setup routine"
553 INTERACTIVE_SETUP_ROUTINE
="yes"
557 function init_redmine_key
{
558 redmine_key
="$(from_file redmine_key)"
559 if [ "$redmine_key" ] ; then
562 warning
"$redmine_key_file not populated: initiating interactive setup routine"
563 INTERACTIVE_SETUP_ROUTINE
="yes"
567 function init_upstream_remote
{
568 upstream_remote
="${upstream_remote:-$(maybe_deduce_remote upstream)}"
571 function interactive_setup_routine
{
573 local original_github_token
574 local original_redmine_key
576 local yes_or_no_answer
577 original_github_token
="$github_token"
578 original_redmine_key
="$redmine_key"
580 if [ -e "$deprecated_backport_common" ] ; then
583 # shellcheck disable=SC1090
584 source "$deprecated_backport_common" 2>/dev
/null || true
585 total_steps
="$((total_steps+1))"
588 echo "Welcome to the ${this_script} interactive setup routine!"
590 echo "---------------------------------------------------------------------"
591 echo "Setup step 1 of $total_steps - GitHub token"
592 echo "---------------------------------------------------------------------"
593 echo "For information on how to generate a GitHub personal access token"
594 echo "to use with this script, go to https://github.com/settings/tokens"
595 echo "then click on \"Generate new token\" and make sure the token has"
596 echo "\"Full control of private repositories\" scope."
598 echo "For more details, see:"
599 echo "https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line"
601 echo -n "What is your GitHub token? "
602 default_val
="$github_token"
603 [ "$github_token" ] && echo "(default: ${default_val})"
604 github_token
="$(get_user_input "$default_val")"
605 if [ "$github_token" ] ; then
608 error
"You must provide a valid GitHub personal access token"
609 abort_due_to_setup_problem
611 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Step 1 of interactive setup"
613 echo "---------------------------------------------------------------------"
614 echo "Setup step 2 of $total_steps - GitHub user"
615 echo "---------------------------------------------------------------------"
616 echo "The script will now attempt to determine your GitHub user (login)"
617 echo "from the GitHub token provided in the previous step. If this is"
618 echo "successful, there is a good chance that your GitHub token is OK."
620 echo "Communicating with the GitHub API..."
621 set_github_user_from_github_token
622 [ "$github_user" ] || abort_due_to_setup_problem
624 echo -n "Is the GitHub username (login) \"$github_user\" correct? "
626 [ "$github_token" ] && echo "(default: ${default_val})"
627 yes_or_no_answer
="$(get_user_input "$default_val")"
628 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
629 if [ "$yes_or_no_answer" = "y" ] ; then
630 if [ "$github_token" = "$original_github_token" ] ; then
633 debug
"GitHub personal access token changed"
634 echo "$github_token" > "$github_token_file"
635 chmod 0600 "$github_token_file"
636 info
"Wrote GitHub personal access token to $github_token_file"
639 error
"GitHub user does not look right"
640 abort_due_to_setup_problem
642 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1 and 2 of interactive setup"
643 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1 and 2 of interactive setup"
645 echo "---------------------------------------------------------------------"
646 echo "Setup step 3 of $total_steps - remote repos"
647 echo "---------------------------------------------------------------------"
648 echo "Searching \"git remote -v\" for remote repos"
653 echo "Upstream remote is \"$upstream_remote\""
654 echo "Fork remote is \"$fork_remote\""
655 [ "$setup_ok" ] || abort_due_to_setup_problem
656 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1-3 of interactive setup"
657 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1-3 of interactive setup"
658 [ "$upstream_remote" ] || assert_fail
"upstream_remote not set, even after completing Steps 1-3 of interactive setup"
659 [ "$fork_remote" ] || assert_fail
"fork_remote not set, even after completing Steps 1-3 of interactive setup"
661 echo "---------------------------------------------------------------------"
662 echo "Setup step 4 of $total_steps - Redmine key"
663 echo "---------------------------------------------------------------------"
664 echo "To generate a Redmine API access key, go to https://tracker.ceph.com"
665 echo "After signing in, click: \"My account\""
666 echo "Now, find \"API access key\"."
667 echo "Once you know the API access key, enter it below."
669 echo -n "What is your Redmine key? "
670 default_val
="$redmine_key"
671 [ "$redmine_key" ] && echo "(default: ${default_val})"
672 redmine_key
="$(get_user_input "$default_val")"
673 if [ "$redmine_key" ] ; then
674 set_redmine_user_from_redmine_key
675 if [ "$setup_ok" ] ; then
678 info
"You must provide a valid Redmine API access key"
679 abort_due_to_setup_problem
681 if [ "$redmine_key" = "$original_redmine_key" ] ; then
684 debug
"Redmine API access key changed"
685 echo "$redmine_key" > "$redmine_key_file"
686 chmod 0600 "$redmine_key_file"
687 info
"Wrote Redmine API access key to $redmine_key_file"
690 error
"You must provide a valid Redmine API access key"
691 abort_due_to_setup_problem
693 [ "$github_token" ] || assert_fail
"github_token not set, even after completing Steps 1-4 of interactive setup"
694 [ "$github_user" ] || assert_fail
"github_user not set, even after completing Steps 1-4 of interactive setup"
695 [ "$upstream_remote" ] || assert_fail
"upstream_remote not set, even after completing Steps 1-4 of interactive setup"
696 [ "$fork_remote" ] || assert_fail
"fork_remote not set, even after completing Steps 1-4 of interactive setup"
697 [ "$redmine_key" ] || assert_fail
"redmine_key not set, even after completing Steps 1-4 of interactive setup"
698 [ "$redmine_user_id" ] || assert_fail
"redmine_user_id not set, even after completing Steps 1-4 of interactive setup"
699 [ "$redmine_login" ] || assert_fail
"redmine_login not set, even after completing Steps 1-4 of interactive setup"
700 if [ "$total_steps" -gt "4" ] ; then
702 echo "---------------------------------------------------------------------"
703 echo "Step 5 of $total_steps - delete deprecated $deprecated_backport_common file"
704 echo "---------------------------------------------------------------------"
706 maybe_delete_deprecated_backport_common
707 vet_setup
--interactive
710 function is_active_milestone
{
712 local milestone_under_test
="$1"
713 for m
in $active_milestones ; do
714 if [ "$milestone_under_test" = "$m" ] ; then
715 verbose
"Milestone $m is active"
725 local trailing_newline
="yes"
729 prefix
="${this_script}: "
736 prefix
="${prefix}DEBUG: "
740 prefix
="${prefix}ERROR: "
760 prefix
="${prefix}WARNING: "
763 if [ "$in_hex" ] ; then
765 elif [ "$verbose_only" ] && [ -z "$VERBOSE" ] ; then
768 msg
="${prefix}${msg}"
769 if [ "$trailing_newline" ] ; then
772 echo -en "${msg}" >&2
777 function maybe_deduce_remote
{
778 local remote_type
="$1"
780 local url_component
=""
781 if [ "$remote_type" = "upstream" ] ; then
783 elif [ "$remote_type" = "fork" ] ; then
784 if [ "$EXPLICIT_FORK" ] ; then
785 url_component
="$EXPLICIT_FORK"
787 url_component
="$github_user"
790 assert_fail
"bad remote_type ->$remote_type<- in maybe_deduce_remote"
792 remote
=$
(git remote
-v |
grep --extended-regexp --ignore-case '(://|@)github.com(/|:|:/)'${url_component}'/ceph(\s|\.|\/)' |
head -n1 | cut
-f 1)
796 function maybe_delete_deprecated_backport_common
{
799 if [ -e "$deprecated_backport_common" ] ; then
800 echo "You still have a $deprecated_backport_common file,"
801 echo "which was used to store configuration parameters in version"
802 echo "15.0.0.6270 and earlier versions of ${this_script}."
804 echo "Since $deprecated_backport_common has been deprecated in favor"
805 echo "of the interactive setup routine, which has been completed"
806 echo "successfully, the file should be deleted now."
808 echo -n "Delete it now? (default: y) "
810 user_inp
="$(get_user_input "$default_val")"
811 user_inp
="$(echo "$user_inp" | tr '[:upper:]' '[:lower:]' | xargs)"
812 if [ "$user_inp" ] ; then
813 user_inp
="${user_inp:0:1}"
814 if [ "$user_inp" = "y" ] ; then
816 rm -f "$deprecated_backport_common"
821 if [ -e "$deprecated_backport_common" ] ; then
822 error
"$deprecated_backport_common still exists. Bailing out!"
828 function maybe_restore_set_x
{
829 if [ "$DEBUG" ] ; then
834 function maybe_update_pr_milestone_labels
{
839 local needs_milestone
840 if [ "$EXPLICIT_COMPONENT" ] ; then
841 debug
"Component given on command line: using it"
842 component
="$EXPLICIT_COMPONENT"
844 debug
"Attempting to guess component"
845 component
=$
(guess_component
"$backport_pr_title")
848 needs_milestone
="$(backport_pr_needs_milestone)"
849 if [ "$needs_milestone" ] ; then
850 debug
"Attempting to set ${milestone} milestone in ${backport_pr_url}"
851 data_binary
="${data_binary}\"milestone\":${milestone_number}"
853 info
"Backport PR ${backport_pr_url} already has ${milestone} milestone"
855 if [ "$(backport_pr_needs_label "$component")" ] ; then
856 debug
"Attempting to add ${component} label to ${backport_pr_url}"
857 if [ "$needs_milestone" ] ; then
858 data_binary
="${data_binary},"
860 data_binary
="${data_binary}\"labels\":[\"${component}\""
861 while read -r label
; do
862 if [ "$label" ] ; then
863 data_binary
="${data_binary},\"${label}\""
865 done <<< "$backport_pr_labels"
866 data_binary
="${data_binary}]}"
868 info
"Backport PR ${backport_pr_url} already has label ${component}"
869 data_binary
="${data_binary}}"
871 if [ "$data_binary" = "{}" ] ; then
874 blindly_set_pr_metadata
"$backport_pr_number" "$data_binary"
878 function maybe_update_pr_title_body
{
882 if [ "$new_title" ] && [ "$new_body" ] ; then
883 data_binary
="{\"title\":\"${new_title}\", \"body\":\"$(munge_body "${new_body}")\"}"
884 elif [ "$new_title" ] ; then
885 data_binary
="{\"title\":\"${new_title}\"}"
886 backport_pr_title
="${new_title}"
887 elif [ "$new_body" ] ; then
888 data_binary
="{\"body\":\"$(munge_body "${new_body}")\"}"
889 #log hex "${data_binary}"
890 #echo -n "${data_binary}"
892 if [ "$data_binary" ] ; then
893 blindly_set_pr_metadata
"${backport_pr_number}" "$data_binary"
897 function milestone_number_from_remote_api
{
898 local mtt
="$1" # milestone to try
899 local mn
="" # milestone number
901 remote_api_output
=$
(curl
-u ${github_user}:${github_token} --silent -X GET
"https://api.github.com/repos/ceph/ceph/milestones")
902 mn
=$
(echo "$remote_api_output" | jq
--arg milestone
"$mtt" '.[] | select(.title==$milestone) | .number')
903 if [ "$mn" -gt "0" ] >/dev
/null
2>&1 ; then
906 error
"Could not determine milestone number of ->$milestone<-"
907 verbose_en
"GitHub API said:\n${remote_api_output}\n"
908 remote_api_output
=$
(curl
-u ${github_user}:${github_token} --silent -X GET
"https://api.github.com/repos/ceph/ceph/milestones")
909 milestones
=$
(echo "$remote_api_output" | jq
'.[].title')
910 info
"Valid values are ${milestones}"
911 info
"(This probably means the Release field of ${redmine_url} is populated with"
912 info
"an unexpected value - i.e. it does not match any of the GitHub milestones.)"
917 function munge_body
{
918 echo "$new_body" |
tr '\r' '\n' |
sed 's/$/\\n/' |
tr -d '\n'
921 function number_to_url
{
922 local number_type
="$1"
924 if [ "$number_type" = "github" ] ; then
925 echo "${github_endpoint}/pull/${number}"
926 elif [ "$number_type" = "redmine" ] ; then
927 echo "${redmine_endpoint}/issues/${number}"
929 assert_fail
"internal error in number_to_url: bad type ->$number_type<-"
933 function populate_original_issue
{
934 if [ -z "$original_issue" ] ; then
935 original_issue
=$
(curl
--silent "${redmine_url}.json?include=relations" |
936 jq
'.issue.relations[] | select(.relation_type | contains("copied_to")) | .issue_id')
937 original_issue_url
="$(number_to_url "redmine
" "${original_issue}")"
941 function populate_original_pr
{
942 if [ "$original_issue" ] ; then
943 if [ -z "$original_pr" ] ; then
944 original_pr
=$
(curl
--silent "${original_issue_url}.json" |
945 jq
-r '.issue.custom_fields[] | select(.id | contains(21)) | .value')
946 original_pr_url
="$(number_to_url "github
" "${original_pr}")"
951 function print_in_hex
{
955 for (( i
=0; i
< ${#str}; i
++ ))
960 printf "[%s] 0x%X\n" " " \'\
\' >&2
962 printf "[%s] 0x%X\n" "$c" \'"$c"\' >&2
967 function set_github_user_from_github_token
{
972 [ "$github_token" ] || assert_fail
"set_github_user_from_github_token: git_token not set"
973 curl_opts
="--silent -u :${github_token} https://api.github.com/user"
974 [ "$quiet" ] ||
set -x
975 remote_api_output
="$(curl $curl_opts)"
977 github_user
=$
(echo "${remote_api_output}" | jq
-r .login
2>/dev
/null |
grep -v null || true
)
978 api_error
=$
(echo "${remote_api_output}" | jq
-r .message
2>/dev
/null |
grep -v null || true
)
979 if [ "$api_error" ] ; then
980 info
"GitHub API said: ->$api_error<-"
981 info
"If you can't figure out what's wrong by examining the curl command and its output, above,"
982 info
"please also study https://developer.github.com/v3/users/#get-the-authenticated-user"
985 [ "$github_user" ] || assert_fail
"set_github_user_from_github_token: failed to set github_user"
986 info
"my GitHub username is $github_user"
991 function set_redmine_user_from_redmine_key
{
992 [ "$redmine_key" ] || assert_fail
"set_redmine_user_from_redmine_key was called, but redmine_key not set"
993 local api_key_from_api
994 remote_api_output
="$(curl --silent "https
://tracker.ceph.com
/users
/current.json?key
=$redmine_key")"
995 redmine_login
="$(echo "$remote_api_output" | jq -r '.user.login')"
996 redmine_user_id
="$(echo "$remote_api_output" | jq -r '.user.id')"
997 api_key_from_api
="$(echo "$remote_api_output" | jq -r '.user.api_key')"
998 if [ "$redmine_login" ] && [ "$redmine_user_id" ] && [ "$api_key_from_api" = "$redmine_key" ] ; then
999 [ "$redmine_user_id" ] || assert_fail
"set_redmine_user_from_redmine_key: failed to set redmine_user_id"
1000 [ "$redmine_login" ] || assert_fail
"set_redmine_user_from_redmine_key: failed to set redmine_login"
1001 info
"my Redmine username is $redmine_login (ID $redmine_user_id)"
1004 error
"Redmine API access key $redmine_key is invalid"
1011 function tracker_component_is_in_desired_state
{
1014 local val_should_be
="$3"
1015 local in_desired_state
1016 if [ "$val_is" = "$val_should_be" ] ; then
1017 debug
"Tracker $comp is in the desired state"
1018 in_desired_state
="yes"
1020 echo "$in_desired_state"
1023 function tracker_component_was_updated
{
1028 if [ "$val_old" = "$val_new" ] ; then
1031 debug
"Tracker $comp was updated!"
1037 function trim_whitespace
{
1039 # remove leading whitespace characters
1040 var
="${var#"${var%%[![:space:]]*}"}"
1041 # remove trailing whitespace characters
1042 var
="${var%"${var##*[![:space:]]}"}"
1046 function troubleshooting_advice
{
1048 Troubleshooting notes
1049 ---------------------
1051 If the script inexplicably fails with:
1053 error: a cherry-pick or revert is already in progress
1054 hint: try "git cherry-pick (--continue | --quit | --abort)"
1055 fatal: cherry-pick failed
1057 This is because HEAD is not where git expects it to be:
1059 $ git cherry-pick --abort
1060 warning: You seem to have moved HEAD. Not rewinding, check your HEAD!
1062 This can be fixed by issuing the command:
1064 $ git cherry-pick --quit
1069 # to update known milestones, consult:
1070 # curl --verbose -X GET https://api.github.com/repos/ceph/ceph/milestones
1071 function try_known_milestones
{
1072 local mtt
=$1 # milestone to try
1073 local mn
="" # milestone number
1075 cuttlefish
) eol
"$mtt" ;;
1076 dumpling
) eol
"$mtt" ;;
1077 emperor
) eol
"$mtt" ;;
1078 firefly
) eol
"$mtt" ;;
1079 giant
) eol
"$mtt" ;;
1080 hammer
) eol
"$mtt" ;;
1081 infernalis
) eol
"$mtt" ;;
1083 kraken
) eol
"$mtt" ;;
1084 luminous
) mn
="10" ;;
1086 nautilus
) mn
="12" ;;
1094 function update_version_number_and_exit
{
1097 local munge_first_hyphen
1098 # munge_first_hyphen will look like this: 15.0.0.5774-g4c2f2eda969
1099 local script_version_number
1100 raw_version
="$(git describe --long --match 'v*' | sed 's/^v//')" # example: "15.0.0-5774-g4c2f2eda969"
1101 munge_first_hyphen
="${raw_version/-/.}" # example: "15.0.0.5774-g4c2f2eda969"
1102 script_version_number
="${munge_first_hyphen%-*}" # example: "15.0.0.5774"
1103 sed -i -e "s/^SCRIPT_VERSION=.*/SCRIPT_VERSION=\"${script_version_number}\"/" "$full_path"
1111 ${this_script} --setup
1115 ${this_script} --help
1116 ${this_script} --usage | less
1117 ${this_script} --troubleshooting | less
1120 ${this_script} BACKPORT_TRACKER_ISSUE_NUMBER
1122 Options (not needed in normal operation):
1123 --cherry-pick-only (stop after cherry-pick phase)
1124 --component/-c COMPONENT
1125 (explicitly set the component label; if omitted, the
1126 script will try to guess the component)
1127 --debug (turns on "set -x")
1128 --existing-pr BACKPORT_PR_ID
1129 (use this when the backport PR is already open)
1130 --force (exercise caution!)
1131 --fork EXPLICIT_FORK (use EXPLICIT_FORK instead of personal GitHub fork)
1132 --milestones (vet all backport PRs for correct milestone setting)
1133 --setup/-s (run the interactive setup routine - NOTE: this can
1134 be done any number of times)
1135 --setup-report (check the setup and print a report)
1136 --update-version (this option exists as a convenience for the script
1137 maintainer only: not intended for day-to-day usage)
1138 --verbose/-v (produce more output than normal)
1139 --version (display version number and exit)
1142 ${this_script} 31459
1143 (if cherry-pick conflicts are present, finish cherry-picking phase manually
1144 and then run the script again with the same argument)
1146 CAVEAT: The script must be run from inside a local git clone.
1150 function usage_advice
{
1155 Once you have completed --setup, you can run the script with the ID of
1156 a Backport tracker issue. For example, to stage the backport
1157 https://tracker.ceph.com/issues/41502, run:
1159 ${this_script} 41502
1161 Provided the commits in the corresponding main PR cherry-pick cleanly, the
1162 script will automatically perform all steps required to stage the backport:
1166 1. fetching the latest commits from the upstream remote
1167 2. creating a wip branch for the backport
1168 3. figuring out which upstream PR contains the commits to cherry-pick
1169 4. cherry-picking the commits
1173 5. pushing the wip branch to your fork
1174 6. opening the backport PR with compliant title and description describing
1176 7. (optionally) setting the milestone and label in the PR
1177 8. updating the Backport tracker issue
1179 When run with --cherry-pick-only, the script will stop after the cherry-pick
1182 If any of the commits do not cherry-pick cleanly, the script will abort in
1183 step 4. In this case, you can either finish the cherry-picking manually
1184 or abort the cherry-pick. In any case, when and if the local wip branch is
1185 ready (all commits cherry-picked), if you run the script again, like so:
1187 ${this_script} 41502
1189 the script will detect that the wip branch already exists and skip over
1190 steps 1-4, starting from step 5 ("PR phase"). In other words, if the wip branch
1191 already exists for any reason, the script will assume that the cherry-pick
1192 phase (steps 1-4) is complete.
1194 As this implies, you can do steps 1-4 manually. Provided the wip branch name
1195 is in the format wip-\$TRACKER_ID-\$STABLE_RELEASE (e.g. "wip-41502-mimic"),
1196 the script will detect the wip branch and start from step 5.
1198 For details on all the options the script takes, run:
1200 ${this_script} --help
1202 For more information on Ceph backporting, see:
1204 https://github.com/ceph/ceph/tree/main/SubmittingPatches-backports.rst
1213 function verbose_en
{
1217 function vet_pr_milestone
{
1218 local pr_number
="$1"
1221 local milestone_stanza
="$4"
1222 local milestone_title_should_be
="$5"
1223 local milestone_number_should_be
1224 local milestone_number_is
=
1225 local milestone_title_is
=
1226 milestone_number_should_be
="$(try_known_milestones "$milestone_title_should_be")"
1227 log overwrite
"Vetting milestone of PR#${pr_number}\r"
1228 if [ "$milestone_stanza" = "null" ] ; then
1229 blindly_set_pr_metadata
"$pr_number" "{\"milestone\": $milestone_number_should_be}"
1230 warning
"$pr_url: set milestone to \"$milestone_title_should_be\""
1231 flag_pr
"$pr_number" "$pr_url" "milestone not set"
1233 milestone_title_is
=$
(echo "$milestone_stanza" | jq
-r '.title')
1234 milestone_number_is
=$
(echo "$milestone_stanza" | jq
-r '.number')
1235 if [ "$milestone_number_is" -eq "$milestone_number_should_be" ] ; then
1238 blindly_set_pr_metadata
"$pr_number" "{\"milestone\": $milestone_number_should_be}"
1239 warning
"$pr_url: changed milestone from \"$milestone_title_is\" to \"$milestone_title_should_be\""
1240 flag_pr
"$pr_number" "$pr_url" "milestone set to wrong value \"$milestone_title_is\""
1245 function vet_prs_for_milestone
{
1246 local milestone_title
="$1"
1247 local pages_of_output
=
1251 # determine last page (i.e., total number of pages)
1252 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
)"
1253 if [ "$remote_api_output" ] ; then
1254 # 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
"
1255 # shellcheck disable=SC2001
1256 pages_of_output="$
(echo "$remote_api_output" |
sed 's/^.*&page\=\([0-9]\+\)>; rel=\"last\".*$/\1/g')"
1260 verbose "GitHub has
$pages_of_output pages of pull request data
for \"base
:${milestone_title}\""
1261 for ((page=1; page<=pages_of_output; page++)) ; do
1262 verbose "Fetching PRs
(page
$page of
${pages_of_output})"
1263 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}")"
1264 prs_in_page
="$(echo "$remote_api_output" | jq -r '. | length')"
1265 verbose
"Page $page of remote API output contains information on $prs_in_page PRs"
1266 for ((i
=0; i
<prs_in_page
; i
++)) ; do
1267 pr_number
="$(echo "$remote_api_output" | jq -r ".
[${i}].number
")"
1268 pr_title
="$(echo "$remote_api_output" | jq -r ".
[${i}].title
")"
1269 pr_url
="$(number_to_url "github
" "${pr_number}")"
1270 milestone_stanza
="$(echo "$remote_api_output" | jq -r ".
[${i}].milestone
")"
1271 vet_pr_milestone
"$pr_number" "$pr_title" "$pr_url" "$milestone_stanza" "$milestone_title"
1277 function vet_remotes
{
1278 if [ "$upstream_remote" ] ; then
1279 verbose
"Upstream remote is $upstream_remote"
1281 error
"Cannot auto-determine upstream remote"
1282 "(Could not find any upstream remote in \"git remote -v\")"
1285 if [ "$fork_remote" ] ; then
1286 verbose
"Fork remote is $fork_remote"
1288 error
"Cannot auto-determine fork remote"
1289 if [ "$EXPLICIT_FORK" ] ; then
1290 info
"(Could not find $EXPLICIT_FORK fork of ceph/ceph in \"git remote -v\")"
1292 info
"(Could not find GitHub user ${github_user}'s fork of ceph/ceph in \"git remote -v\")"
1298 function vet_setup
{
1300 local not_set
="!!! NOT SET !!!"
1301 local invalid
="!!! INVALID !!!"
1302 local redmine_endpoint_display
1303 local redmine_user_id_display
1304 local github_endpoint_display
1305 local github_user_display
1306 local upstream_remote_display
1307 local fork_remote_display
1308 local redmine_key_display
1309 local github_token_display
1310 debug
"Entering vet_setup with argument $argument"
1311 if [ "$argument" = "--report" ] ||
[ "$argument" = "--normal-operation" ] ; then
1312 [ "$github_token" ] && [ "$setup_ok" ] && set_github_user_from_github_token quiet
1313 init_upstream_remote
1314 [ "$github_token" ] && [ "$setup_ok" ] && init_fork_remote
1316 [ "$redmine_key" ] && set_redmine_user_from_redmine_key
1318 if [ "$github_token" ] ; then
1319 if [ "$setup_ok" ] ; then
1320 github_token_display
="(OK; value not shown)"
1322 github_token_display
="$invalid"
1325 github_token_display
="$not_set"
1327 if [ "$redmine_key" ] ; then
1328 if [ "$setup_ok" ] ; then
1329 redmine_key_display
="(OK; value not shown)"
1331 redmine_key_display
="$invalid"
1334 redmine_key_display
="$not_set"
1336 redmine_endpoint_display
="${redmine_endpoint:-$not_set}"
1337 redmine_user_id_display
="${redmine_user_id:-$not_set}"
1338 github_endpoint_display
="${github_endpoint:-$not_set}"
1339 github_user_display
="${github_user:-$not_set}"
1340 upstream_remote_display
="${upstream_remote:-$not_set}"
1341 fork_remote_display
="${fork_remote:-$not_set}"
1342 test "$redmine_endpoint" || failed_mandatory_var_check redmine_endpoint
"not set"
1343 test "$redmine_user_id" || failed_mandatory_var_check redmine_user_id
"could not be determined"
1344 test "$redmine_key" || failed_mandatory_var_check redmine_key
"not set"
1345 test "$github_endpoint" || failed_mandatory_var_check github_endpoint
"not set"
1346 test "$github_user" || failed_mandatory_var_check github_user
"could not be determined"
1347 test "$github_token" || failed_mandatory_var_check github_token
"not set"
1348 test "$upstream_remote" || failed_mandatory_var_check upstream_remote
"could not be determined"
1349 test "$fork_remote" || failed_mandatory_var_check fork_remote
"could not be determined"
1350 if [ "$argument" = "--report" ] ||
[ "$argument" == "--interactive" ] ; then
1351 read -r -d '' setup_summary
<<EOM || true > /dev/null 2>&1
1352 redmine_endpoint $redmine_endpoint
1353 redmine_user_id $redmine_user_id_display
1354 redmine_key $redmine_key_display
1355 github_endpoint $github_endpoint
1356 github_user $github_user_display
1357 github_token $github_token_display
1358 upstream_remote $upstream_remote_display
1359 fork_remote $fork_remote_display
1362 log bare
"============================================="
1363 log bare
" ${this_script} setup report"
1364 log bare
"============================================="
1365 log bare
"variable name value"
1366 log bare
"---------------------------------------------"
1367 log bare
"$setup_summary"
1368 log bare
"---------------------------------------------"
1370 verbose
"redmine_endpoint $redmine_endpoint_display"
1371 verbose
"redmine_user_id $redmine_user_id_display"
1372 verbose
"redmine_key $redmine_key_display"
1373 verbose
"github_endpoint $github_endpoint_display"
1374 verbose
"github_user $github_user_display"
1375 verbose
"github_token $github_token_display"
1376 verbose
"upstream_remote $upstream_remote_display"
1377 verbose
"fork_remote $fork_remote_display"
1379 if [ "$argument" = "--report" ] ||
[ "$argument" = "--interactive" ] ; then
1380 if [ "$setup_ok" ] ; then
1383 info
"setup is NOT OK"
1385 log bare
"=============================================="
1396 # are we in a local git clone?
1399 if git status
>/dev
/null
2>&1 ; then
1400 debug
"In a local git clone. Good."
1402 error
"This script must be run from inside a local git clone"
1403 abort_due_to_setup_problem
1407 # do we have jq available?
1410 if type jq
>/dev
/null
2>&1 ; then
1411 debug
"jq is available. Good."
1413 error
"This script uses jq, but it does not seem to be installed"
1414 abort_due_to_setup_problem
1421 if command -v jq
>/dev
/null
; then
1422 debug
"jq is available. Good."
1424 error
"This script needs \"jq\" in order to work, and it is not available"
1425 abort_due_to_setup_problem
1430 # process command-line arguments
1433 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" -- "$@")
1434 eval set -- "$munged_options"
1439 CHERRY_PICK_PHASE
="yes"
1442 EXPLICIT_COMPONENT
=""
1446 INTERACTIVE_SETUP_ROUTINE
=""
1451 TROUBLESHOOTING_ADVICE
=""
1456 --cherry-pick-only) CHERRY_PICK_PHASE
="yes" ; PR_PHASE
="" ; TRACKER_PHASE
="" ; shift ;;
1457 --component|
-c) shift ; EXPLICIT_COMPONENT
="$1" ; shift ;;
1458 --debug|
-d) DEBUG
="$1" ; shift ;;
1459 --existing-pr) shift ; EXISTING_PR
="$1" ; CHERRY_PICK_PHASE
="" ; PR_PHASE
="" ; shift ;;
1460 --force) FORCE
="$1" ; shift ;;
1461 --fork) shift ; EXPLICIT_FORK
="$1" ; shift ;;
1462 --help|
-h) ADVICE
="1" ; HELP
="$1" ; shift ;;
1463 --milestones) CHECK_MILESTONES
="$1" ; shift ;;
1464 --prepare) CHERRY_PICK_PHASE
="yes" ; PR_PHASE
="" ; TRACKER_PHASE
="" ; shift ;;
1465 --setup*|
-s) SETUP_OPTION
="$1" ; shift ;;
1466 --troubleshooting) ADVICE
="$1" ; TROUBLESHOOTING_ADVICE
="$1" ; shift ;;
1467 --update-version) update_version_number_and_exit
;;
1468 --usage) ADVICE
="$1" ; USAGE_ADVICE
="$1" ; shift ;;
1469 --verbose|
-v) VERBOSE
="$1" ; shift ;;
1470 --version) display_version_message_and_exit
;;
1471 --) shift ; ISSUE
="$1" ; break ;;
1472 *) echo "Internal error" ; false
;;
1476 if [ "$ADVICE" ] ; then
1477 [ "$HELP" ] && usage
1478 [ "$USAGE_ADVICE" ] && usage_advice
1479 [ "$TROUBLESHOOTING_ADVICE" ] && troubleshooting_advice
1483 if [ "$SETUP_OPTION" ] ||
[ "$CHECK_MILESTONES" ] ; then
1487 if [[ $ISSUE =~ ^
[0-9]+$
]] ; then
1490 error
"Invalid or missing argument"
1495 if [ "$DEBUG" ]; then
1500 if [ "$VERBOSE" ]; then
1501 info
"Verbose mode ON"
1507 # make sure setup has been completed
1514 if [ "$SETUP_OPTION" ] ; then
1516 maybe_delete_deprecated_backport_common
1517 if [ "$setup_ok" ] ; then
1521 echo -n "Run the interactive setup routine now? (default: ${default_val}) "
1522 yes_or_no_answer
="$(get_user_input "$default_val")"
1523 [ "$yes_or_no_answer" ] && yes_or_no_answer
="${yes_or_no_answer:0:1}"
1524 if [ "$yes_or_no_answer" = "y" ] ; then
1525 INTERACTIVE_SETUP_ROUTINE
="yes"
1527 if [ "$FORCE" ] ; then
1528 warning
"--force was given; proceeding with broken setup"
1536 if [ "$INTERACTIVE_SETUP_ROUTINE" ] ; then
1537 interactive_setup_routine
1539 vet_setup
--normal-operation
1540 maybe_delete_deprecated_backport_common
1542 if [ "$INTERACTIVE_SETUP_ROUTINE" ] ||
[ "$SETUP_OPTION" ] ; then
1544 if [ "$setup_ok" ] ; then
1545 if [ "$ISSUE" ] && [ "$ISSUE" != "0" ] ; then
1555 [ "$setup_ok" ] || abort_due_to_setup_problem
1558 # query remote GitHub API for active milestones
1561 verbose
"Querying GitHub API for active milestones"
1562 remote_api_output
="$(curl -u ${github_user}:${github_token} --silent -X GET "https
://api.github.com
/repos
/ceph
/ceph
/milestones
")"
1563 active_milestones
="$(echo "$remote_api_output" | jq -r '.[] | .title')"
1564 if [ "$active_milestones" = "null" ] ; then
1565 error
"Could not determine the active milestones"
1566 bail_out_github_api
"$remote_api_output"
1569 if [ "$CHECK_MILESTONES" ] ; then
1570 check_milestones
"$active_milestones"
1575 # query remote Redmine API for information about the Backport tracker issue
1578 redmine_url
="$(number_to_url "redmine
" "${issue}")"
1579 debug
"Considering Redmine issue: $redmine_url - is it in the Backport tracker?"
1581 remote_api_output
="$(curl --silent "${redmine_url}.json
")"
1582 tracker
="$(echo "$remote_api_output" | jq -r '.issue.tracker.name')"
1583 if [ "$tracker" = "Backport" ]; then
1584 debug
"Yes, $redmine_url is a Backport issue"
1586 error
"Issue $redmine_url is not a Backport"
1587 info
"(This script only works with Backport tracker issues.)"
1591 debug
"Looking up release/milestone of $redmine_url"
1592 milestone
="$(echo "$remote_api_output" | jq -r '.issue.custom_fields[0].value')"
1593 if [ "$milestone" ] ; then
1594 debug
"Release/milestone: $milestone"
1596 error
"could not obtain release/milestone from ${redmine_url}"
1600 debug
"Looking up status of $redmine_url"
1601 tracker_status_id
="$(echo "$remote_api_output" | jq -r '.issue.status.id')"
1602 tracker_status_name
="$(echo "$remote_api_output" | jq -r '.issue.status.name')"
1603 if [ "$tracker_status_name" ] ; then
1604 debug
"Tracker status: $tracker_status_name"
1605 if [ "$FORCE" ] ||
[ "$EXISTING_PR" ] ; then
1606 test "$(check_tracker_status "$tracker_status_name")" || true
1608 test "$(check_tracker_status "$tracker_status_name")"
1611 error
"could not obtain status from ${redmine_url}"
1615 tracker_title
="$(echo "$remote_api_output" | jq -r '.issue.subject')"
1616 debug
"Title of $redmine_url is ->$tracker_title<-"
1618 tracker_description
="$(echo "$remote_api_output" | jq -r '.issue.description')"
1619 debug
"Description of $redmine_url is ->$tracker_description<-"
1621 tracker_assignee_id
="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.id')"
1622 tracker_assignee_name
="$(echo "$remote_api_output" | jq -r '.issue.assigned_to.name')"
1623 if [ "$tracker_assignee_id" = "null" ] ||
[ "$tracker_assignee_id" = "$redmine_user_id" ] ; then
1626 error_msg_1
="$redmine_url is assigned to someone else: $tracker_assignee_name (ID $tracker_assignee_id)"
1627 error_msg_2
="(my ID is $redmine_user_id)"
1628 if [ "$FORCE" ] ||
[ "$EXISTING_PR" ] ; then
1629 warning
"$error_msg_1"
1631 info
"--force and/or --existing-pr given: continuing execution"
1633 error
"$error_msg_1"
1635 info
"Cowardly refusing to continue"
1640 if [ -z "$(is_active_milestone "$milestone")" ] ; then
1641 error
"$redmine_url is a backport to $milestone which is not an active milestone"
1642 info
"Cowardly refusing to work on a backport to an inactive release"
1646 milestone_number
=$
(try_known_milestones
"$milestone")
1647 if [ "$milestone_number" -gt "0" ] >/dev
/null
2>&1 ; then
1648 debug
"Milestone ->$milestone<- is known to have number ->$milestone_number<-: skipping remote API call"
1650 warning
"Milestone ->$milestone<- is unknown to the script: falling back to GitHub API"
1651 milestone_number
=$
(milestone_number_from_remote_api
"$milestone")
1653 target_branch
="$milestone"
1654 info
"milestone/release is $milestone"
1655 debug
"milestone number is $milestone_number"
1657 if [ "$CHERRY_PICK_PHASE" ] ; then
1658 local_branch
=wip-
${issue}-${target_branch}
1659 if git show-ref
--verify --quiet "refs/heads/$local_branch" ; then
1660 if [ "$FORCE" ] ; then
1661 warning
"local branch $local_branch already exists"
1662 info
"--force was given: will clobber $local_branch and attempt automated cherry-pick"
1664 elif [ "$CHERRY_PICK_ONLY" ] ; then
1665 error
"local branch $local_branch already exists"
1666 info
"Cowardly refusing to clobber $local_branch as it might contain valuable data"
1667 info
"(hint) run with --force to clobber it and attempt the cherry-pick"
1670 if [ "$FORCE" ] ||
[ "$CHERRY_PICK_ONLY" ] ; then
1673 info
"local branch $local_branch already exists: skipping cherry-pick phase"
1676 info
"$local_branch does not exist: will create it and attempt automated cherry-pick"
1681 if [ "$PR_PHASE" ] ; then
1682 current_branch
=$
(git rev-parse
--abbrev-ref HEAD
)
1683 if [ "$current_branch" = "$local_branch" ] ; then
1687 git checkout
"$local_branch"
1693 git push
-u "$fork_remote" "$local_branch"
1701 debug
"Generating backport PR description"
1702 populate_original_issue
1703 populate_original_pr
1704 desc
="backport tracker: ${redmine_url}"
1705 if [ "$original_pr" ] ||
[ "$original_issue" ] ; then
1706 desc
="${desc}\n\n---\n"
1707 [ "$original_pr" ] && desc
="${desc}\nbackport of $(number_to_url "github
" "${original_pr}")"
1708 [ "$original_issue" ] && desc
="${desc}\nparent tracker: $(number_to_url "redmine
" "${original_issue}")"
1710 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"
1712 debug
"Generating backport PR title"
1713 if [ "$original_pr" ] ; then
1714 backport_pr_title
="${milestone}: $(curl --silent https://api.github.com/repos/ceph/ceph/pulls/${original_pr} | jq -r '.title')"
1716 if [[ $tracker_title =~ ^
${milestone}: ]] ; then
1717 backport_pr_title
="${tracker_title}"
1719 backport_pr_title
="${milestone}: ${tracker_title}"
1722 if [[ "$backport_pr_title" =~
\" ]] ; then
1723 backport_pr_title
="${backport_pr_title//\"/\\\"}"
1726 debug
"Opening backport PR"
1727 if [ "$EXPLICIT_FORK" ] ; then
1728 source_repo
="$EXPLICIT_FORK"
1730 source_repo
="$github_user"
1732 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
")
1733 backport_pr_number=$(echo "$remote_api_output" | jq -r .number)
1734 if [ -z "$backport_pr_number" ] || [ "$backport_pr_number" = "null
" ] ; then
1735 error "failed to open backport PR
"
1736 bail_out_github_api "$remote_api_output"
1738 backport_pr_url="$
(number_to_url
"github" "$backport_pr_number")"
1739 info "Opened backport PR
${backport_pr_url}"
1742 if [ "$EXISTING_PR" ] ; then
1743 populate_original_issue
1744 populate_original_pr
1745 backport_pr_number="$EXISTING_PR"
1746 backport_pr_url="$
(number_to_url
"github" "$backport_pr_number")"
1750 if [ "$PR_PHASE" ] || [ "$EXISTING_PR" ] ; then
1751 maybe_update_pr_milestone_labels
1752 pgrep firefox >/dev/null && firefox "${backport_pr_url}"
1755 if [ "$TRACKER_PHASE" ] ; then
1756 debug "Considering Backport tracker issue
${redmine_url}"
1757 status_should_be=2 # In Progress
1758 desc_should_be="${backport_pr_url}"
1759 assignee_should_be="${redmine_user_id}"
1760 if [ "$EXISTING_PR" ] ; then
1761 data_binary="{\"issue
\":{\"description
\":\"${desc_should_be}\",\"status_id\":${status_should_be}}}"
1763 data_binary="{\"issue
\":{\"description
\":\"${desc_should_be}\",\"status_id\":${status_should_be},\"assigned_to_id\":${assignee_should_be}}}"
1765 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")"
1766 if [ "$FORCE" ] || [ "$EXISTING_PR" ] ; then
1769 if [ "${remote_api_status_code:0:1}" = "2" ] ; then
1771 elif [ "${remote_api_status_code:0:1}" = "4" ] ; then
1772 warning "remote API
${redmine_endpoint} returned status
${remote_api_status_code}"
1773 info "This merely indicates that you cannot modify issue fields
at ${redmine_endpoint}"
1774 info "and does not limit your ability to
do backports.
"
1776 error "Remote API
${redmine_endpoint} returned unexpected response code
${remote_api_status_code}"
1779 # check if anything actually changed on the Redmine issue
1780 remote_api_output=$(curl --silent "${redmine_url}.json?include
=journals
")
1781 status_is="$
(echo "$remote_api_output" | jq
-r '.issue.status.id')"
1782 desc_is="$
(echo "$remote_api_output" | jq
-r '.issue.description')"
1783 assignee_is="$
(echo "$remote_api_output" | jq
-r '.issue.assigned_to.id')"
1784 tracker_was_updated=""
1785 tracker_is_in_desired_state="yes"
1786 [ "$
(tracker_component_was_updated
"status" "$tracker_status_id" "$status_is")" ] && tracker_was_updated="yes"
1787 [ "$
(tracker_component_was_updated
"desc" "$tracker_description" "$desc_is")" ] && tracker_was_updated="yes"
1788 if [ "$EXISTING_PR" ] ; then
1791 [ "$
(tracker_component_was_updated
"assignee" "$tracker_assignee_id" "$assignee_is")" ] && tracker_was_updated="yes"
1793 [ "$
(tracker_component_is_in_desired_state
"status" "$status_is" "$status_should_be")" ] || tracker_is_in_desired_state=""
1794 [ "$
(tracker_component_is_in_desired_state
"desc" "$desc_is" "$desc_should_be")" ] || tracker_is_in_desired_state=""
1795 if [ "$EXISTING_PR" ] ; then
1798 [ "$
(tracker_component_is_in_desired_state
"assignee" "$assignee_is" "$assignee_should_be")" ] || tracker_is_in_desired_state=""
1800 if [ "$tracker_is_in_desired_state" ] ; then
1801 [ "$tracker_was_updated" ] && info "Backport tracker
${redmine_url} was updated
"
1802 info "Backport tracker
${redmine_url} is
in the desired state
"
1803 pgrep firefox >/dev/null && firefox "${redmine_url}"
1806 if [ "$tracker_was_updated" ] ; then
1807 warning "backport tracker
${redmine_url} was updated
, but is not
in the desired state. Please check it.
"
1808 pgrep firefox >/dev/null && firefox "${redmine_url}"
1811 data_binary="{\"issue
\":{\"notes
\":\"please link this Backport tracker issue with GitHub PR
${desc_should_be}\nceph-backport.sh version ${SCRIPT_VERSION}\"}}"
1812 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")
1813 if [ "${remote_api_status_code:0:1}" = "2" ] ; then
1814 info "Comment added to
${redmine_url}"