Meta/Announce: catch tested-by trailer, too
[git] / Reintegrate
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 / ||
41                             /^Can wait in / || /^Still / || /^Stuck / || /^On hold/ ||
42                             /^Needs? / || /^Expecting / || /^May want to /) {
43                                 return 1;
44                         }
45                         return 0;
46                 }
47
48                 sub read_message {
49                         my ($fh, $branch) = @_;
50                         my ($in_section, $in_desc);
51                         my @msg = ();
52                         while (<$fh>) {
53                                 chomp;
54                                 if (/^\* \Q$branch\E /) {
55                                         $in_section = 1;
56                                         next;
57                                 }
58                                 last if (/^[-*\[]/ && $in_section);
59                                 next unless $in_section;
60                                 s/^\s+//;
61                                 if (/^$/) {
62                                         $in_desc = 1;
63                                 }
64                                 next unless ($in_section && $in_desc);
65                                 next if (/Originally merged to '\''next'\'' on ([-0-9]+)/);
66                                 last if (wildo_match($_));
67                                 push @msg, "$_\n";
68                         }
69                         return ($in_section, @msg);
70                 }
71
72                 my ($branch) = $ARGV[0];
73                 my ($fh, $in_section, @msg);
74                 if (open $fh, "<", "Meta/whats-cooking.txt") {
75                         ($in_section, @msg) = read_message($fh, $branch);
76                 }
77                 if (!@msg) {
78                         open my $revs, "-|",
79                                 qw(git -C Meta rev-list -32 HEAD -- whats-cooking.txt);
80                         while (my $rev = <$revs>) {
81                                 chomp($rev);
82                                 open $fh, "-|",
83                                 qw(git -C Meta cat-file blob), "$rev:whats-cooking.txt";
84                                 ($in_section, @msg) = read_message($fh, $branch);
85                                 last if (@msg);
86                         }
87                 }
88                 if (@msg) {
89                         open(my $fh, "-|", qw(git cat-file commit HEAD));
90                         my @original = (<$fh>);
91                         close $fh;
92                         my @final;
93                         $in_section = 0;
94                         for (@original) {
95                                 if (!$in_section) {
96                                         $in_section = 1 if (/^$/);
97                                         next;
98                                 }
99                                 if (/^Conflicts:$/ && $in_section == 2) {
100                                         $in_section = 3;
101                                 }
102
103                                 if ($in_section == 3) {
104                                         $_ = "# $_";
105                                 }
106                                 push @final, $_;
107                                 if (/^$/ && $in_section == 1) {
108                                         push @final, @msg;
109                                         push @final, "\n";
110                                         $in_section = 2;
111                                 }
112                         }
113                         open($fh, "|-", qw(git commit --amend -F -));
114                         print $fh @final;
115                         close $fh;
116                 }
117         ' "$1"
118 }
119
120 cocci_mark="treewide: apply cocci patch"
121
122 case "$generate" in
123 no)
124         accept_rerere () {
125                 if ! git write-tree 2>/dev/null >/dev/null
126                 then
127                         git rerere remaining
128                         return 1
129                 else
130                         GIT_EDITOR=: git commit --no-verify
131                         echo "Accepted previous resolution"
132                         return 0
133                 fi
134         }
135
136         mark_cut () {
137                 test -n "$stop_at_cut" && return
138
139                 count_since_last_cut=$(( $count_since_last_cut + 1 ))
140                 test -z "$prev_cut" && return
141                 git commit --allow-empty -m "$prev_cut"
142                 prev_cut=
143         }
144
145         cut_seen=0 prev_cut= count_since_last_cut=0 cocci_count=0
146         while read branch eh
147         do
148                 case "$branch" in '###') cut_seen=$(( $cut_seen + 1 )) ;; esac
149                 if test -n "$stop_at_cut" && test $stop_at_cut -le $cut_seen
150                 then
151                         continue ;# slurp the remainder and skip
152                 fi
153
154                 case "$branch" in
155                 '###')
156                         if test "$count_since_last_cut" = 0
157                         then
158                                 prev_cut=
159                         else
160                                 echo >&2 "$branch $eh"
161                                 prev_cut="$branch $eh"
162                                 count_since_last_cut=0
163                         fi
164                         continue ;;
165                 '#cocci')
166                         if test -n "$no_cocci"
167                         then
168                                 continue
169                         elif test 0 = "$cocci_count" && test -z "$force_cocci"
170                         then
171                                 continue
172                         fi
173
174                         if test -n "$skip_cocci" && test -n "$eh"
175                         then
176                                 git cherry-pick --no-commit "$eh"
177                         else
178                                 rm -f contrib/coccinelle/*.patch
179                                 Meta/Make -j8 coccicheck
180                                 if grep coccicheck-pending Makefile >/dev/null
181                                 then
182                                         Meta/Make -j8 coccicheck-pending
183                                 fi
184                                 cat contrib/coccinelle/*.patch >cocci.patch
185                                 if ! test -s cocci.patch
186                                 then
187                                         exit 0
188                                 fi
189                                 git apply --index -3 cocci.patch || exit
190                                 rm cocci.patch
191                                 git diff --quiet HEAD && continue
192                         fi
193                         git commit -m "$cocci_mark" || exit
194
195                         mark_cut
196                         continue
197                         ;;
198                 '#'* | '')
199                         continue ;;
200                 esac
201
202                 case "$eh" in
203                 "" | "#"* | [0-9][0-9]-[0-9][0-9]*)
204                         echo >&2 "* $branch"
205
206                         save=$(git rev-parse --verify HEAD) &&
207                         tip=$(git rev-parse --verify "$branch^0") &&
208                         mb=$(git merge-base "$tip" "$save") ||
209                         exit
210
211                         test "$mb" = "$tip" && continue
212
213                         mark_cut
214                         cocci_count=$(( $cocci_count + 1 ))
215
216                         rebuild=$(git config "branch.$branch.rebuild" || :)
217
218                         GIT_EDITOR=: git merge --no-ff $rebuild $accept_rerere --edit "$branch" ||
219                         accept_rerere ||
220                         exit
221
222                         annotate_merge "$branch" || exit
223                         test -z "$edit" ||
224                         git commit --amend || exit
225
226                         this=$(git rev-parse --verify HEAD)
227                         if test "$this" = "$save"
228                         then
229                                 :
230                         elif git show-ref -q --verify "refs/merge-fix/$branch"
231                         then
232                                 echo >&2 "Fixing up the merge"
233                                 git cherry-pick --no-commit "refs/merge-fix/$branch" &&
234                                 git diff --stat HEAD &&
235                                 GIT_EDITOR=: git commit --amend -a || exit
236                         fi
237                         ;;
238                 pick" "*)
239                         echo >&2 "* $eh"
240
241                         mark_cut
242
243                         git cherry-pick "$branch" || exit ;;
244                 *) echo >&2 "Eh? $branch $eh"; exit ;;
245                 esac
246
247                 eval "$exec" || exit
248         done
249         exit
250 esac
251
252 if test -n "$update" && test $# = 0
253 then
254         set x $(sed -n -e '2s/^# //p' <"$update") &&
255         shift
256 fi
257
258 # Generation (or updating)
259
260 x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
261 x40="$x40$x40$x40$x40$x40$x40$x40$x40"
262 LF='
263 '
264
265 show_merge () {
266         case "$msg" in
267         "Merge branch '"*"'"*)
268                 branch=$(expr "$msg" : "Merge branch '\(.*\)'")
269                 merge_hier=heads/
270                 ;;
271         "Merge remote branch '"*"'"*)
272                 branch=$(expr "$msg" : "Merge remote branch '\(.*\)'")
273                 merge_hier=
274                 ;;
275         *)
276                 echo 2>&1 "Huh?: $msg"
277                 return
278                 ;;
279         esac &&
280         tip=$(git rev-parse --verify "refs/$merge_hier$branch" 2>/dev/null) &&
281         merged=$(git name-rev --refs="refs/$merge_hier$branch" "$other" 2>/dev/null) &&
282         merged=$(expr "$merged" : "$x40 \(.*\)") &&
283         test "$merged" != undefined || {
284                 other=$(git log -1 --pretty='format:%s' $other) &&
285                 merged="$branch :rebased? $other"
286         }
287 }
288
289 show_pick () {
290         case "$msg" in
291         "### "* | "###")
292                 merged="$msg$LF"
293                 ;;
294         *)
295                 merged="$(git rev-parse --verify "$commit") pick $msg"
296                 ;;
297         esac
298
299 }
300
301 generate () {
302         PROGRAM=$1
303         shift
304         echo '#!/bin/sh'
305         echo "# $1"
306         echo 'case "$#,$1" in'
307         echo '1,-u|1,-d)'
308         echo "  exec $PROGRAM" '"$1" "$0"'
309         echo 'esac'
310         echo "$PROGRAM" '"$@" <<\EOF'
311         git log --no-decorate --pretty=oneline --first-parent "$1" |
312         {
313                 series=
314                 while read commit msg
315                 do
316                         if other=$(git rev-parse -q --verify "$commit^2")
317                         then
318                                 show_merge
319                         elif test "$msg" = "$cocci_mark"
320                         then
321                                 merged="#cocci "$(git rev-parse "$commit^0")
322                         else
323                                 show_pick
324                         fi
325
326                         if test -z "$series"
327                         then
328                                 series="$merged"
329                         else
330                                 series="$merged$LF$series"
331                         fi
332                 done
333                 echo "$series"
334         }
335         echo EOF
336 }
337
338 if test -z "$update"
339 then
340         generate "$0" "$@"
341 elif test -z "$diff"
342 then
343         generate "$0" "$@" | diff -w -u "$update" -
344         if test $? = 0
345         then
346                 echo >&2 "No changes."
347         else
348                 echo >&2 -n "Update [y/N]? "
349                 read yesno
350                 case "$yesno" in
351                 [Yy]*)
352                         generate "$0" "$@" |
353                         sed -e 's/ :rebased?.*//' >"$update" ;;
354                 *)
355                         echo >&2 "No update then." ;;
356                 esac
357         fi
358 else
359         generate "$0" "$@" | diff -w -u "$update" -
360 fi