Raw
1 #!/bin/sh
2
3 accept_rerere="--rerere-autoupdate"
4 generate=no
5 exec=:
6 update= diff= edit= stop_at_cut= skip_cocci= force_cocci= no_cocci=
7 while case "$#,$1" in 0,*) break;; *,-*) ;; esac
8 do
9 case "$1" in
10 -n) accept_rerere= ;;
11 -e) edit=t ;;
12 -c) stop_at_cut=1 ;;
13 -c?*) stop_at_cut=${1#-c} ;;
14 -d) update=${2?"diff with what?"}
15 diff=yes
16 generate=yes
17 shift ;;
18 -u) update=${2?"update what?"}
19 generate=yes
20 shift ;;
21 -x) exec=${2?exec}; shift ;;
22 -x?*) exec=${1#-x} ;;
23 -ss) skip_cocci=t ;;
24 -fs) force_cocci=t ;;
25 -ns) no_cocci=t ;;
26 *) generate=yes
27 break ;;
28 esac
29 shift
30 done
31
32 annotate_merge () {
33 test -f Meta/whats-cooking.txt || return 0
34
35 # NEEDSWORK: unify with cook::wildo_match
36 perl -e '
37 sub wildo_match {
38 s/^\s*//;
39 if (/^Will (?:\S+ ){0,2}(fast-track|hold|keep|merge|drop|discard|cook|kick|defer|eject|be re-?rolled|wait)[,. ]/ ||
40 /^Not urgent/ || /^Not ready/ || /^Waiting for / || /^Under discussion/ ||
41 /^Can wait in / || /^Still / || /^Stuck / || /^On hold/ || /^Breaks / ||
42 /^Inviting / || /^Comments\?/ || /^Retracted/ ||
43 /^Needs? / || /^Expecting / || /^May want to / || /^Under review/) {
44 return 1;
45 }
46 return 0;
47 }
48
49 sub read_message {
50 my ($fh, $branch) = @_;
51 my ($in_section, $in_desc);
52 my @msg = ();
53 while (<$fh>) {
54 chomp;
55 if (/^\* \Q$branch\E /) {
56 $in_section = 1;
57 next;
58 }
59 last if (/^[-*\[]/ && $in_section);
60 next unless $in_section;
61 s/^\s+//;
62 if (/^$/) {
63 $in_desc = 1;
64 }
65 next unless ($in_section && $in_desc);
66 next if (/Originally merged to '\''next'\'' on ([-0-9]+)/);
67 next if (/^source: /);
68 last if (wildo_match($_));
69 push @msg, "$_\n";
70 }
71 return ($in_section, @msg);
72 }
73
74 my ($branch) = $ARGV[0];
75 my ($fh, $in_section, @msg);
76 if (open $fh, "<", "Meta/whats-cooking.txt") {
77 ($in_section, @msg) = read_message($fh, $branch);
78 }
79 if (!@msg) {
80 open my $revs, "-|",
81 qw(git -C Meta rev-list -32 HEAD -- whats-cooking.txt);
82 while (my $rev = <$revs>) {
83 chomp($rev);
84 open $fh, "-|",
85 qw(git -C Meta cat-file blob), "$rev:whats-cooking.txt";
86 ($in_section, @msg) = read_message($fh, $branch);
87 last if (@msg);
88 }
89 }
90 if (@msg) {
91 open(my $fh, "-|", qw(git cat-file commit HEAD));
92 my @original = (<$fh>);
93 close $fh;
94 my @final;
95 $in_section = 0;
96 for (@original) {
97 if (!$in_section) {
98 $in_section = 1 if (/^$/);
99 next;
100 }
101 if (/^Conflicts:$/ && $in_section == 2) {
102 $in_section = 3;
103 }
104
105 if ($in_section == 3) {
106 $_ = "# $_";
107 }
108 push @final, $_;
109 if (/^$/ && $in_section == 1) {
110 push @final, @msg;
111 push @final, "\n";
112 $in_section = 2;
113 }
114 }
115 open($fh, "|-", qw(git commit --amend -F -));
116 print $fh @final;
117 close $fh;
118 }
119 ' "$1"
120 }
121
122 cocci_mark="treewide: apply cocci patch"
123
124 case "$generate" in
125 no)
126 accept_rerere () {
127 git ls-files -u -z |
128 perl -0 -e '
129 my %path_stage = ();
130 my @to_remove = ();
131 while (<>) {
132 my ($mode, $sha1, $stage, $path) =
133 /^([0-7]+) ([0-9a-f]+) ([0-3]) (.*)$/;
134 $path_stage{$path} ||= 0;
135 $path_stage{$path} |= (1 << ($stage - 1));
136 }
137
138 while (my ($path, $bits) = each %path_stage) {
139 if ($bits == 3 || $bits == 5) {
140 push @to_remove, $path;
141 }
142 }
143 if (@to_remove) {
144 system(qw(git rm -f), @to_remove);
145 }
146 '
147
148 if ! git write-tree 2>/dev/null >/dev/null
149 then
150 git rerere remaining
151 return 1
152 else
153 GIT_EDITOR=: git commit --no-verify
154 echo "Accepted previous resolution"
155 return 0
156 fi
157 }
158
159 mark_cut () {
160 test -n "$stop_at_cut" && return
161
162 count_since_last_cut=$(( $count_since_last_cut + 1 ))
163 test -z "$prev_cut" && return
164 git commit --allow-empty -m "$prev_cut"
165 prev_cut=
166 }
167
168 detach () {
169 if original_branch=$(git symbolic-ref HEAD 2>/dev/null)
170 then
171 original_branch=${original_branch#refs/heads/}
172 git checkout --quiet --detach
173 into="--into $original_branch"
174 else
175 original_branch=
176 into=${INTO:+"--into $INTO"}
177 fi
178 }
179
180 leave () {
181 if test -n "$original_branch" && ! git symbolic-ref HEAD 2>/dev/null
182 then
183 git checkout --quiet -B "$original_branch"
184 fi
185 if test -n "$1"
186 then
187 exit "$1"
188 fi
189 }
190
191 detach
192 cut_seen=0 prev_cut= count_since_last_cut=0 cocci_count=0
193
194 while read branch eh
195 do
196 case "$branch" in
197 '###')
198 cut_seen=$(( $cut_seen + 1 ))
199 echo >&2 "$branch $eh"
200 ;;
201 esac
202 if test -n "$stop_at_cut" && test $stop_at_cut -le $cut_seen
203 then
204 continue ;# slurp the remainder and skip
205 fi
206
207 case "$branch" in
208 '###')
209 if test "$count_since_last_cut" = 0
210 then
211 prev_cut=
212 else
213 prev_cut="$branch $eh"
214 count_since_last_cut=0
215 fi
216 continue ;;
217 '#cocci')
218 if test -n "$no_cocci"
219 then
220 continue
221 elif test 0 = "$cocci_count" && test -z "$force_cocci"
222 then
223 continue
224 fi
225
226 if test -n "$skip_cocci" && test -n "$eh"
227 then
228 git cherry-pick --no-commit "$eh"
229 else
230 rm -f contrib/coccinelle/*.patch
231 Meta/Make -j8 coccicheck
232 if grep coccicheck-pending Makefile >/dev/null
233 then
234 Meta/Make -j8 coccicheck-pending
235 fi
236 cat contrib/coccinelle/*.patch >cocci.patch
237 if ! test -s cocci.patch
238 then
239 leave 0
240 fi
241 git apply --index -3 cocci.patch || leave $?
242 rm cocci.patch
243 git diff --quiet HEAD && continue
244 fi
245 git commit -m "$cocci_mark" || leave $?
246
247 mark_cut
248 continue
249 ;;
250 '#'* | '')
251 continue ;;
252 esac
253
254 case "$eh" in
255 "" | "#"* | [0-9][0-9]-[0-9][0-9]*)
256 echo >&2 "* $branch"
257
258 save=$(git rev-parse --verify HEAD) &&
259 tip=$(git rev-parse --verify "$branch^0") &&
260 mb=$(git merge-base "$tip" "$save") ||
261 leave $?
262
263 test "$mb" = "$tip" && continue
264
265 mark_cut
266 cocci_count=$(( $cocci_count + 1 ))
267
268 rebuild=$(git config "branch.$branch.rebuild" || :)
269
270 GIT_EDITOR=: git merge --no-ff $into $rebuild $accept_rerere --edit "$branch" ||
271 accept_rerere ||
272 leave $?
273
274 annotate_merge "$branch" || leave $?
275 test -z "$edit" ||
276 git commit --amend || leave $?
277
278 this=$(git rev-parse --verify HEAD)
279 if test "$this" = "$save"
280 then
281 :
282 elif git show-ref -q --verify "refs/merge-fix/$branch"
283 then
284 echo >&2 "Fixing up the merge"
285 git cherry-pick --no-commit "refs/merge-fix/$branch" &&
286 git diff --stat HEAD &&
287 GIT_EDITOR=: git commit --amend -a || leave $?
288 fi
289 ;;
290 pick" "*)
291 echo >&2 "* $eh"
292
293 mark_cut
294
295 git cherry-pick --allow-empty "$branch" || leave $? ;;
296 *) echo >&2 "Eh? $branch $eh"; leave $? ;;
297 esac
298
299 eval "$exec" || leave $?
300 done
301 leave $?
302 ;;
303 esac
304
305 if test -n "$update" && test $# = 0
306 then
307 set x $(sed -n -e '2s/^# //p' <"$update") &&
308 shift
309 fi
310
311 # Generation (or updating)
312
313 x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
314 x40="$x40$x40$x40$x40$x40$x40$x40$x40"
315 LF='
316 '
317
318 show_merge () {
319 case "$msg" in
320 "Merge branch '"*"'"*)
321 branch=$(expr "$msg" : "Merge branch '\(.*\)'")
322 merge_hier=heads/
323 ;;
324 "Merge remote branch '"*"'"*)
325 branch=$(expr "$msg" : "Merge remote branch '\(.*\)'")
326 merge_hier=
327 ;;
328 *)
329 echo >&2 "unknown merge: $msg"
330 exit 1
331 ;;
332 esac &&
333 tip=$(git rev-parse --verify "refs/$merge_hier$branch" 2>/dev/null) &&
334 merged=$(git name-rev --refs="refs/$merge_hier$branch" "$other" 2>/dev/null) &&
335 merged=$(expr "$merged" : "$x40 \(.*\)") &&
336 test "$merged" != undefined || {
337 other=$(git log -1 --pretty='format:%s' $other) &&
338 merged="$branch :rebased? $other"
339 }
340 }
341
342 show_pick () {
343 case "$msg" in
344 "### "* | "###")
345 merged="$msg$LF"
346 ;;
347 *)
348 merged="$(git rev-parse --verify "$commit") pick $msg"
349 ;;
350 esac
351
352 }
353
354 generate () {
355 PROGRAM=$1
356 shift
357 echo '#!/bin/sh'
358 echo "# $1"
359 echo 'case "$#,$1" in'
360 echo '1,-u|1,-d)'
361 echo " exec $PROGRAM" '"$1" "$0"'
362 echo 'esac'
363 echo "$PROGRAM" '"$@" <<\EOF'
364 git log --no-decorate --pretty=oneline --first-parent "$1" |
365 {
366 series=
367 while read commit msg
368 do
369 if other=$(git rev-parse -q --verify "$commit^2")
370 then
371 show_merge
372 elif test "$msg" = "$cocci_mark"
373 then
374 merged="#cocci "$(git rev-parse "$commit^0")
375 else
376 show_pick
377 fi
378
379 if test -z "$series"
380 then
381 series="$merged"
382 else
383 series="$merged$LF$series"
384 fi
385 done
386 echo "$series"
387 }
388 echo EOF
389 }
390
391 if test -z "$update"
392 then
393 generate "$0" "$@"
394 elif test -z "$diff"
395 then
396 tmp=/tmp/regenerate.$$
397 trap 'rm -f "$tmp"*' 0
398 generate "$0" "$@" >"$tmp"
399 diff -w -u "$update" "$tmp"
400 if test $? = 0
401 then
402 echo >&2 "No changes."
403 else
404 echo >&2 -n "Update [y/N]? "
405 read yesno
406 case "$yesno" in
407 [Yy]*)
408 sed -e 's/ :rebased?.*//' "$tmp" >"$update" ;;
409 *)
410 echo >&2 "No update then." ;;
411 esac
412 fi
413 else
414 generate "$0" "$@" | diff -w -u "$update" -
415 fi