xref: /spdk/scripts/backport.sh (revision 73bc324b0df56dc389f7030d52d3ed920b1f66b9)
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 direcotry 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