1#!/usr/bin/env bash 2 3# SPDX-License-Identifier: BSD-3-Clause 4# Copyright (C) 2023 Intel Corporation 5# All rights reserved. 6 7GERRIT_USER="" 8HASHTAG="" 9BACKPORT_DIR="" 10SPDK_DIR="" 11CHERRY_PICK_TEMP="" 12FROM_BRANCH="" 13 14function cleanup() { 15 rm -rf "$BACKPORT_DIR"/cherry* 16 rm -rf "$TMP_DIR" 17} 18 19function display_help() { 20 echo " Usage: ${0##*/} [-u <gerrit_user>] [-t <hashtag>] [-d <spdk_dir>] 21 22 Script automating Git operations to submit patches to different branches. 23 24 -d Path for SPDK repository and temporary files used for backporting. 25 -t Hashtag on patches to be backported and destination SPDK version (e.g. 23.05). 26 -u Gerrit username. 27 -h Show this help message. 28 29 SHH authentication for provided Gerrit user is required. 30 Merged commits from 'master' branch with provided hashtag (eg. 23.05) are cherry-picked 31 to release branch (v23.05.x) in order they were merged. Skipping commits already present 32 on the release branch. 33 34 If merge conflict occurs during cherry-pick, it needs to be resolved manually. 35 Then calling this script again will resume further cherry-picks. 36 37 Example: 38 ./scripts/backport.sh -u gerrit_user -t 23.05 -d ./backports" 39} 40 41function reorder_commits() { 42 local unordered_list=$1 43 local from="v${HASHTAG}-rc1" 44 45 git -C "$SPDK_DIR" fetch origin 46 # Reorder commits to how they were merged to master 47 git -C "$SPDK_DIR" show -s --format="%H" "$from^..HEAD" | tac > "$TMP_DIR/ordered_commits" 48 49 mapfile -t u < "$unordered_list" 50 mapfile -t o < "$TMP_DIR/ordered_commits" 51 cp=() 52 53 for ((i = 0; i < ${#u[@]}; i++)); do 54 for ((j = 0; j < ${#o[@]}; j++)); do 55 if [[ "${u[i]}" == "${o[j]}" ]]; then 56 cp[j]=${u[i]} 57 fi 58 done 59 done 60 printf '%s\n' "${cp[@]}" 61} 62 63function fix_up_commit_msg() { 64 local hash=$1 65 66 git show -s --format="%B" "$hash" > $SPDK_DIR/msg 67 # Add '(master)' info to link for original review 68 sed -i '/Reviewed-on/ s/$/ (master)/' $SPDK_DIR/msg 69 70 # Remove all tags added on original patches merged regarding reviewers and testers 71 sed -i '/^Reviewed-by/d' "$SPDK_DIR/msg" 72 sed -i '/^Community-CI/d' "$SPDK_DIR/msg" 73 sed -i '/^Tested-by/d' "$SPDK_DIR/msg" 74 echo "(cherry picked from commit $hash)" >> "$SPDK_DIR/msg" 75 grep "^Change-Id" "$SPDK_DIR/msg" > "$SPDK_DIR/id" 76 sed -i '/^Change-Id/d' "$SPDK_DIR/msg" 77 cat "$SPDK_DIR/id" >> "$SPDK_DIR/msg" 78 git commit -s --amend -m "$(cat $SPDK_DIR/msg)" 79 rm -f "$SPDK_DIR/msg" 80 rm -f "$SPDK_DIR/id" 81} 82 83while getopts "d:ht:u:" opt; do 84 case "${opt}" in 85 d) 86 BACKPORT_DIR="$OPTARG" 87 SPDK_DIR="$BACKPORT_DIR/spdk" 88 CHERRY_PICK_TEMP="$BACKPORT_DIR/cherry-temp" 89 ;; 90 h) 91 display_help >&2 92 exit 0 93 ;; 94 t) 95 HASHTAG="$OPTARG" 96 FROM_BRANCH="v${HASHTAG}.x" 97 ;; 98 u) 99 GERRIT_USER="$OPTARG" 100 ;; 101 *) 102 display_help >&2 103 exit 1 104 ;; 105 esac 106done 107 108if [[ -z $HASHTAG || -z $GERRIT_USER || -z $BACKPORT_DIR ]]; then 109 echo "Gerrit user, hashtag and directory are required." 110 display_help 111 exit 1 112fi 113 114# Gerrit interactive shells are disabled and attempts to connect with below 115# command will result in error code 127, so parse the response instead. 116if ! ssh -p 29418 $GERRIT_USER@review.spdk.io 2>&1 | grep -q "Welcome to Gerrit Code Review"; then 117 echo "Could not connect to Gerrit" 118 exit 1 119fi 120 121if [[ -d $SPDK_DIR && -e $SPDK_DIR/.git ]]; then 122 echo "Updating SPDK repository." 123 git -C "$SPDK_DIR" pull 124else 125 rm -rf "$SPDK_DIR" 126 echo "Cloning SPDK repo" 127 git clone "https://review.spdk.io/spdk/spdk" "$SPDK_DIR" --recurse-submodules 128fi 129 130cd "$SPDK_DIR" 131# Enable git rerere to remember conflict resolution when creating commits to resubmit multiple times 132git config --local rerere.enabled true 133git config --local rerere.autoupdate true 134 135if [[ ! -f $CHERRY_PICK_TEMP ]]; then 136 echo "Creating commit list to cherry-pick" 137 TMP_DIR="$BACKPORT_DIR/temp_files" 138 139 if [[ ! -d $TMP_DIR ]]; then 140 # If this is not the first run, then the directory might already exist 141 mkdir "$TMP_DIR" 142 fi 143 144 # Get list of hash tagged patches on master branch 145 ssh -p 29418 $GERRIT_USER@review.spdk.io gerrit query --format json \ 146 --current-patch-set project:spdk/spdk status:merged branch:"master" hashtag:"$HASHTAG" > "$TMP_DIR/master_${HASHTAG}_list" 147 148 jq -r 'select(.id != null)|.id' "$TMP_DIR/master_${HASHTAG}_list" | sort > "$TMP_DIR/hash_id" 149 150 # List of patches already submitted to the backporting branch 151 ssh -p 29418 $GERRIT_USER@review.spdk.io gerrit query --format json \ 152 --current-patch-set project:spdk/spdk branch:"$FROM_BRANCH" > "$TMP_DIR/branch_list" 153 jq -r 'select(.id != null)|.id' "$TMP_DIR/branch_list" | sort > "$TMP_DIR/branch_id" 154 155 comm -13 "$TMP_DIR/branch_id" "$TMP_DIR/hash_id" > "$TMP_DIR/id_to_port" 156 157 while read -r line; do 158 jq -r ". | select(.id == "\"$line\"")" "$TMP_DIR/master_${HASHTAG}_list" \ 159 | jq -r '.currentPatchSet.revision' 160 done < "$TMP_DIR/id_to_port" > "$TMP_DIR/hashtagged" 161 162 reorder_commits "$TMP_DIR/hashtagged" > "$CHERRY_PICK_TEMP" 163 164 git fetch origin 165 git checkout $FROM_BRANCH 166 167 git submodule update --init 168fi 169 170TOTAL=$(wc -l < "$CHERRY_PICK_TEMP") 171echo "Total number of patches to backport: $TOTAL" 172 173cat "$CHERRY_PICK_TEMP" > "$CHERRY_PICK_TEMP.cpy" 174 175while read -r line; do 176 cd "$SPDK_DIR" 177 178 if [[ -n $(git status -suno) ]]; then 179 if [[ -f "$BACKPORT_DIR/merge_conflict" ]]; then 180 # Patch had conflict on previous script call, need to commit and fixup message 181 id="$(cat $BACKPORT_DIR/merge_conflict)" 182 git commit -C "$id" 183 fix_up_commit_msg "$id" 184 rm -f "$BACKPORT_DIR/merge_conflict" 185 else 186 echo "Changes pending, please resolve and commit them" 187 exit 1 188 fi 189 fi 190 191 # Removing new commit to cherry-pick from the list 192 tail -n +2 "$CHERRY_PICK_TEMP" > "$CHERRY_PICK_TEMP.tmp" && mv "$CHERRY_PICK_TEMP.tmp" "$CHERRY_PICK_TEMP" 193 194 REMAINING=$(wc -l < "$CHERRY_PICK_TEMP") 195 NR=$((TOTAL - REMAINING)) 196 echo "Currently at $NR, remaining $REMAINING" 197 198 if [ ! "$(git cherry-pick --rerere-autoupdate $line)" ]; then 199 if [[ -z $(git status -suno) ]]; then 200 echo "No changes to commit, most likely patch was already merged." 201 git cherry-pick --abort 202 continue 203 fi 204 205 # Git rerere might have already resolved the conflict 206 if ! GIT_EDITOR=true git cherry-pick --continue; then 207 echo "Merge conflict on patch:" 208 echo "$line" 209 echo "Use: git mergetool" 210 echo "and then call this script again" 211 echo "$line" > "$BACKPORT_DIR/merge_conflict" 212 exit 1 213 fi 214 fi 215 216 fix_up_commit_msg "$line" 217 echo "Merged" 218done < "$CHERRY_PICK_TEMP.cpy" 219cd "$SPDK_DIR" 220if [[ -f "$BACKPORT_DIR/merge_conflict" ]]; then 221 # Patch had conflict on previous script call, need to commit and fixup message 222 id="$(cat $BACKPORT_DIR/merge_conflict)" 223 git commit -C "$id" 224 fix_up_commit_msg "$id" 225 rm -f "$BACKPORT_DIR/merge_conflict" 226fi 227 228cleanup 229 230echo 231echo "The script finished." 232echo "Enter $SPDK_DIR, verify the commits at the top of $FROM_BRANCH" 233echo "and then call 'git push origin HEAD:refs/for/$FROM_BRANCH' to submit them." 234