| #!/bin/sh | |
| accept_rerere="--rerere-autoupdate" | |
| generate=no | |
| exec=: | |
| update= diff= edit= stop_at_cut= skip_cocci= force_cocci= no_cocci= | |
| while case "$#,$1" in 0,*) break;; *,-*) ;; esac | |
| do | |
| case "$1" in | |
| -n) accept_rerere= ;; | |
| -e) edit=t ;; | |
| -c) stop_at_cut=1 ;; | |
| -c?*) stop_at_cut=${1#-c} ;; | |
| -d) update=${2?"diff with what?"} | |
| diff=yes | |
| generate=yes | |
| shift ;; | |
| -u) update=${2?"update what?"} | |
| generate=yes | |
| shift ;; | |
| -x) exec=${2?exec}; shift ;; | |
| -x?*) exec=${1#-x} ;; | |
| -ss) skip_cocci=t ;; | |
| -fs) force_cocci=t ;; | |
| -ns) no_cocci=t ;; | |
| *) generate=yes | |
| break ;; | |
| esac | |
| shift | |
| done | |
| annotate_merge () { | |
| test -f Meta/whats-cooking.txt || return 0 | |
| # NEEDSWORK: unify with cook::wildo_match | |
| perl -e ' | |
| sub wildo_match { | |
| s/^\s*//; | |
| if (/^Will (?:\S+ ){0,2}(fast-track|hold|keep|merge|drop|discard|cook|kick|defer|eject|be re-?rolled|wait)[,. ]/ || | |
| /^Not urgent/ || /^Not ready/ || /^Waiting for / || /^Under discussion/ || | |
| /^Can wait in / || /^Still / || /^Stuck / || /^On hold/ || /^Breaks / || | |
| /^Inviting / || /^Comments\?/ || /^Retracted/ || /^Looking good/ || | |
| /^Needs? / || /^Expecting / || /^May want to / || /^Under review/) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| sub read_message { | |
| my ($fh, $branch) = @_; | |
| my ($in_section, $in_desc); | |
| my @msg = (); | |
| while (<$fh>) { | |
| chomp; | |
| if (/^\* \Q$branch\E /) { | |
| $in_section = 1; | |
| next; | |
| } | |
| last if (/^[-*\[]/ && $in_section); | |
| next unless $in_section; | |
| s/^\s+//; | |
| if (/^$/) { | |
| $in_desc = 1; | |
| } | |
| next unless ($in_section && $in_desc); | |
| next if (/Originally merged to '\''next'\'' on ([-0-9]+)/); | |
| next if (/^source: /); | |
| last if (wildo_match($_)); | |
| push @msg, "$_\n"; | |
| } | |
| return ($in_section, @msg); | |
| } | |
| my ($branch) = $ARGV[0]; | |
| my ($fh, $in_section, @msg); | |
| if (open $fh, "<", "Meta/whats-cooking.txt") { | |
| ($in_section, @msg) = read_message($fh, $branch); | |
| } | |
| if (!@msg) { | |
| open my $revs, "-|", | |
| qw(git -C Meta rev-list -32 HEAD -- whats-cooking.txt); | |
| while (my $rev = <$revs>) { | |
| chomp($rev); | |
| open $fh, "-|", | |
| qw(git -C Meta cat-file blob), "$rev:whats-cooking.txt"; | |
| ($in_section, @msg) = read_message($fh, $branch); | |
| last if (@msg); | |
| } | |
| } | |
| if (@msg) { | |
| open(my $fh, "-|", qw(git cat-file commit HEAD)); | |
| my @original = (<$fh>); | |
| close $fh; | |
| my @final; | |
| $in_section = 0; | |
| for (@original) { | |
| if (!$in_section) { | |
| $in_section = 1 if (/^$/); | |
| next; | |
| } | |
| if (/^Conflicts:$/ && $in_section == 2) { | |
| $in_section = 3; | |
| } | |
| if ($in_section == 3) { | |
| $_ = "# $_"; | |
| } | |
| push @final, $_; | |
| if (/^$/ && $in_section == 1) { | |
| push @final, @msg; | |
| push @final, "\n"; | |
| $in_section = 2; | |
| } | |
| } | |
| open($fh, "|-", qw(git commit --amend -F -)); | |
| print $fh @final; | |
| close $fh; | |
| } | |
| ' "$1" | |
| } | |
| cocci_mark="treewide: apply cocci patch" | |
| case "$generate" in | |
| no) | |
| accept_rerere () { | |
| git ls-files -u -z | | |
| perl -0 -e ' | |
| my %path_stage = (); | |
| my @to_remove = (); | |
| while (<>) { | |
| my ($mode, $sha1, $stage, $path) = | |
| /^([0-7]+) ([0-9a-f]+) ([0-3]) (.*)$/; | |
| $path_stage{$path} ||= 0; | |
| $path_stage{$path} |= (1 << ($stage - 1)); | |
| } | |
| while (my ($path, $bits) = each %path_stage) { | |
| if ($bits == 3 || $bits == 5) { | |
| push @to_remove, $path; | |
| } | |
| } | |
| if (@to_remove) { | |
| system(qw(git rm -f), @to_remove); | |
| } | |
| ' | |
| if ! git write-tree 2>/dev/null >/dev/null | |
| then | |
| git rerere remaining | |
| return 1 | |
| else | |
| GIT_EDITOR=: git commit --no-verify | |
| echo "Accepted previous resolution" | |
| return 0 | |
| fi | |
| } | |
| mark_cut () { | |
| test -n "$stop_at_cut" && return | |
| count_since_last_cut=$(( $count_since_last_cut + 1 )) | |
| test -z "$prev_cut" && return | |
| git commit --allow-empty -m "$prev_cut" | |
| prev_cut= | |
| } | |
| detach () { | |
| if original_branch=$(git symbolic-ref HEAD 2>/dev/null) | |
| then | |
| original_branch=${original_branch#refs/heads/} | |
| git checkout --quiet --detach | |
| into="--into $original_branch" | |
| else | |
| original_branch= | |
| into=${INTO:+"--into $INTO"} | |
| fi | |
| } | |
| leave () { | |
| if test -n "$original_branch" && ! git symbolic-ref HEAD 2>/dev/null | |
| then | |
| git checkout --quiet -B "$original_branch" | |
| fi | |
| if test -n "$1" | |
| then | |
| exit "$1" | |
| fi | |
| } | |
| detach | |
| cut_seen=0 prev_cut= count_since_last_cut=0 cocci_count=0 | |
| while read branch eh | |
| do | |
| case "$branch" in | |
| '###') | |
| cut_seen=$(( $cut_seen + 1 )) | |
| echo >&2 "$branch $eh" | |
| ;; | |
| esac | |
| if test -n "$stop_at_cut" && test $stop_at_cut -le $cut_seen | |
| then | |
| continue ;# slurp the remainder and skip | |
| fi | |
| case "$branch" in | |
| '###') | |
| if test "$count_since_last_cut" = 0 | |
| then | |
| prev_cut= | |
| else | |
| prev_cut="$branch $eh" | |
| count_since_last_cut=0 | |
| fi | |
| continue ;; | |
| '#cocci') | |
| if test -n "$no_cocci" | |
| then | |
| continue | |
| elif test 0 = "$cocci_count" && test -z "$force_cocci" | |
| then | |
| continue | |
| fi | |
| if test -n "$skip_cocci" && test -n "$eh" | |
| then | |
| git cherry-pick --no-commit "$eh" | |
| else | |
| rm -f contrib/coccinelle/*.patch | |
| Meta/Make -j8 coccicheck | |
| if grep coccicheck-pending Makefile >/dev/null | |
| then | |
| Meta/Make -j8 coccicheck-pending | |
| fi | |
| cat contrib/coccinelle/*.patch >cocci.patch | |
| if ! test -s cocci.patch | |
| then | |
| leave 0 | |
| fi | |
| git apply --index -3 cocci.patch || leave $? | |
| rm cocci.patch | |
| git diff --quiet HEAD && continue | |
| fi | |
| git commit -m "$cocci_mark" || leave $? | |
| mark_cut | |
| continue | |
| ;; | |
| '#'* | '') | |
| continue ;; | |
| esac | |
| case "$eh" in | |
| "" | "#"* | [0-9][0-9]-[0-9][0-9]*) | |
| echo >&2 "* $branch" | |
| save=$(git rev-parse --verify HEAD) && | |
| tip=$(git rev-parse --verify "$branch^0") && | |
| mb=$(git merge-base "$tip" "$save") || | |
| leave $? | |
| test "$mb" = "$tip" && continue | |
| mark_cut | |
| cocci_count=$(( $cocci_count + 1 )) | |
| rebuild=$(git config "branch.$branch.rebuild" || :) | |
| GIT_EDITOR=: git merge --no-ff $into $rebuild $accept_rerere --edit "$branch" || | |
| accept_rerere || | |
| leave $? | |
| annotate_merge "$branch" || leave $? | |
| test -z "$edit" || | |
| git commit --amend || leave $? | |
| this=$(git rev-parse --verify HEAD) | |
| if test "$this" = "$save" | |
| then | |
| : | |
| elif git show-ref -q --verify "refs/merge-fix/$branch" | |
| then | |
| echo >&2 "Fixing up the merge" | |
| git cherry-pick --no-commit "refs/merge-fix/$branch" && | |
| git diff --stat HEAD && | |
| GIT_EDITOR=: git commit --amend -a || leave $? | |
| fi | |
| ;; | |
| pick" "*) | |
| echo >&2 "* $eh" | |
| mark_cut | |
| git cherry-pick --allow-empty "$branch" && | |
| GIT_EDITOR=: git commit --amend --reset-author || leave $? ;; | |
| *) echo >&2 "Eh? $branch $eh"; leave $? ;; | |
| esac | |
| eval "$exec" || leave $? | |
| done | |
| leave $? | |
| ;; | |
| esac | |
| if test -n "$update" && test $# = 0 | |
| then | |
| set x $(sed -n -e '2s/^# //p' <"$update") && | |
| shift | |
| fi | |
| # Generation (or updating) | |
| x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' | |
| x40="$x40$x40$x40$x40$x40$x40$x40$x40" | |
| LF=' | |
| ' | |
| show_merge () { | |
| case "$msg" in | |
| "Merge branch '"*"'"*) | |
| branch=$(expr "$msg" : "Merge branch '\(.*\)'") | |
| merge_hier=heads/ | |
| ;; | |
| "Merge remote branch '"*"'"*) | |
| branch=$(expr "$msg" : "Merge remote branch '\(.*\)'") | |
| merge_hier= | |
| ;; | |
| *) | |
| echo >&2 "unknown merge: $msg" | |
| exit 1 | |
| ;; | |
| esac && | |
| tip=$(git rev-parse --verify "refs/$merge_hier$branch" 2>/dev/null) && | |
| merged=$(git name-rev --refs="refs/$merge_hier$branch" "$other" 2>/dev/null) && | |
| merged=$(expr "$merged" : "$x40 \(.*\)") && | |
| test "$merged" != undefined || { | |
| other=$(git log -1 --pretty='format:%s' $other) && | |
| merged="$branch :rebased? $other" | |
| } | |
| } | |
| show_pick () { | |
| case "$msg" in | |
| "### "* | "###") | |
| merged="$msg$LF" | |
| ;; | |
| *) | |
| merged="$(git rev-parse --verify "$commit") pick $msg" | |
| ;; | |
| esac | |
| } | |
| generate () { | |
| PROGRAM=$1 | |
| shift | |
| echo '#!/bin/sh' | |
| echo "# $1" | |
| echo 'case "$#,$1" in' | |
| echo '1,-u|1,-d)' | |
| echo " exec $PROGRAM" '"$1" "$0"' | |
| echo 'esac' | |
| echo "$PROGRAM" '"$@" <<\EOF' | |
| git log --no-decorate --pretty=oneline --first-parent "$1" | | |
| { | |
| series= | |
| while read commit msg | |
| do | |
| if other=$(git rev-parse -q --verify "$commit^2") | |
| then | |
| show_merge | |
| elif test "$msg" = "$cocci_mark" | |
| then | |
| merged="#cocci "$(git rev-parse "$commit^0") | |
| else | |
| show_pick | |
| fi | |
| if test -z "$series" | |
| then | |
| series="$merged" | |
| else | |
| series="$merged$LF$series" | |
| fi | |
| done | |
| echo "$series" | |
| } | |
| echo EOF | |
| } | |
| if test -z "$update" | |
| then | |
| generate "$0" "$@" | |
| elif test -z "$diff" | |
| then | |
| tmp=/tmp/regenerate.$$ | |
| trap 'rm -f "$tmp"*' 0 | |
| generate "$0" "$@" >"$tmp" | |
| diff -w -u "$update" "$tmp" | |
| if test $? = 0 | |
| then | |
| echo >&2 "No changes." | |
| else | |
| echo >&2 -n "Update [y/N]? " | |
| read yesno | |
| case "$yesno" in | |
| [Yy]*) | |
| sed -e 's/ :rebased?.*//' "$tmp" >"$update" ;; | |
| *) | |
| echo >&2 "No update then." ;; | |
| esac | |
| fi | |
| else | |
| generate "$0" "$@" | diff -w -u "$update" - | |
| fi |