Repository navigation
Expand file tree
/
Copy pathgit-format-patchsets
More file actions
executable file
·274 lines (214 loc) · 6.36 KB
/
Copy pathgit-format-patchsets
File metadata and controls
executable file
·274 lines (214 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#!/bin/bash
set -e
set -u
scriptname="git format-patchset"
# help text
read -r -d '' cli_help <<EOD || :
Format multi-patches from commits.
Usage:
${scriptname} [OPTIONS] <revision-range> [-- <GIT_ARGS...>]
Description:"
Format multi-patches, i.e. patch files with multiple complete commits
inside, from a given commit range. Commits can be assigned to/picked
for patch-sets interactively (similar to git rebase), or via a
Patchset: <name>
tag in the commit message.
Options:
-h, --help Show this help message.
-t, --from-tags Look for 'Patchset:' tags in commit messages and
assign patch-set based on this. Commits without a
'Patchset:' tag will be formatted in their own file.
-a, --no-confirm Do not open editor for interactive assignment, i.e.
perform assignment automatically without user
interaction. Only valid with -t/--from-tags.
Arguments:
revision-range Range from which to create multi-patches.
GIT_ARGS Arguments forwarded to 'git format-patch'.
EOD
opt_help=n
opt_tags=n
opt_noconfirm=n
opt_gitargs=()
opt_positionals=()
while (( "${#}" )); do
case ${1} in
-h|--help)
opt_help=y
shift
;;
-t|--from-tags)
opt_tags=y
shift
;;
-a|--no-confirm)
opt_noconfirm=y
shift
;;
--)
shift
while (( "${#}" )); do
opt_gitargs+=("${1}")
shift
done
;;
*)
opt_positionals+=("${1}")
shift
;;
esac
done
# handle help
if [[ "${opt_help}" == y ]]; then
echo "${cli_help}"
exit 0
fi
# check positional arguments
if [[ "${#opt_positionals[@]}" -ne 1 ]]; then
echo "error: Invalid arguments"
exit 1
fi
# get revision input
opt_revs="${opt_positionals[0]}"
if [[ ! "${opt_revs}" == *".."* ]]; then
opt_revs="${opt_revs}.."
fi
# instruction file
git_dir="$(git rev-parse --show-toplevel)/.git"
cmdfile="${git_dir}/TMP_MULTIPATCH"
# editor for instruction file
editor=${EDITOR:-vim}
# prepare instruction-file
echo "# Format patchsets in range ${opt_revs}" > "${cmdfile}"
log=$(git log --reverse --oneline --no-merges "${opt_revs}")
if [[ "${opt_tags}" == y ]]; then
prev=""
# try to get tags from log
while IFS= read -r entry; do
read -r -a fields <<< "${entry}"
commit="${fields[0]}"
message=$(git log --format=%B "${commit}" -1)
patchset=""
while IFS= read -r line; do
# split away right-hand-side comment
IFS="#" read -r -a line <<< "${line}"
if [[ "${#line[@]}" -eq 0 ]]
then
continue
fi
text="${line[0]}"
# split line into tokens
IFS=" " read -r -a tokens <<< "${text}"
if [[ "${#tokens[@]}" -eq 0 ]]
then
continue
fi
# if tag, set patchset
if [[ "${tokens[0]}" == "Patchset:" ]]; then
patchset="${tokens[*]:1}"
fi
done <<< "${message}"
# if no tag available, generate set-name from commit title
if [ -z "${patchset}" ]; then
patchset="${fields[*]:1}"
patchset="${patchset//\:/}"
patchset="${patchset//\(/}"
patchset="${patchset//\)/}"
patchset="${patchset//\*/}"
patchset="${patchset//\//-}"
patchset="${patchset// /-}"
patchset=$(echo "${patchset}" | cut -c 1-52)
fi
if [[ "${prev}" != "${patchset}" ]]; then
echo "" >> "${cmdfile}"
echo "set ${patchset}" >> "${cmdfile}"
fi
prev="${patchset}"
echo "pick ${entry}" >> "${cmdfile}"
done <<< "${log}"
else
# don't look at tags, just add all commits without sets
while IFS= read -r entry; do
echo "pick ${entry}" >> "${cmdfile}"
done <<< "${log}"
fi
# add help text to instruction file
cat >> "${cmdfile}" <<End-of-message
# Commands:
# s, set <name> = introduce new patchset
# p, pick <commit> = use commit
#
# These lines can be re-ordered; they are executed from top to bottom.
# However, note that reordering may create conflicts when applying the created
# patches. Prefer creating sets in the original order or rebasing if a
# different order is desired.
#
End-of-message
if [[ "${opt_tags}" != y ]] || [[ "${opt_noconfirm}" != y ]]; then
${editor} "${cmdfile}"
fi
# parse instruction-file into dictionary
declare -A patchsets
set="a"
n_set=1
n_commit=0
while read -r p || [[ -n $p ]]; do
# skip empty lines
if [ -z "$p" ]; then
continue
fi
# skip full-line comments
if [[ "$p" == \#* ]]; then
continue
fi
# split away right-hand-side comment
IFS="#" read -r -a line <<< "${p}"
# split line into tokens
IFS=" " read -r -a line <<< "${line[0]}"
# each line must have at least two tokens (command, argument)
if [ ${#line[@]} -lt 2 ]; then
echo "error: invalid line"
exit 1
fi
# handle instruction
command="${line[0]}"
case "${command}" in
s|set)
set="${line[*]:1}"
if [[ "$n_commit" -gt 0 ]]; then
(( n_set+=1 ))
fi
n_commit=0
;;
p|pick)
key=$(printf '%04d-%s' "${n_set}" "${set}")
commit=${line[1]}
if [[ ${patchsets[${key}]+x} ]]
then
patchsets[${key}]="${patchsets[${key}]} ${commit}"
else
patchsets[${key}]="${commit}"
fi
(( n_commit+=1 ))
;;
*)
echo "invalid command: ${command}"
exit 1
;;
esac
done <"${cmdfile}"
# format patches
sets=("${!patchsets[@]}")
IFS=$'\n' sorted=($(sort <<<"${sets[*]}")); unset IFS
for set in "${sorted[@]}"; do
patch="${set}.patch"
echo ""
echo "${patch}:"
rm -f "${patch}"
for commit in ${patchsets[${set}]}; do
message=$(git log --format=%s -n 1 "${commit}")
echo " ${commit} ${message}"
git format-patch "${opt_gitargs[@]}" "${commit}" -1 --stdout >> "${patch}"
done
done
# final cleanup
rm -f "${cmdfile}"