| 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 |