#!/bin/sh
-accept_rerere=t generate=no update= diff=
+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
-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 / ||
+ /^Can wait in / || /^Still / || /^Stuck / || /^On hold/ || /^Breaks / ||
+ /^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 () {
- if test -z "$accept_rerere"
- then
- return 1
- fi
- if git diff |
- grep -e "^.+" -e "^+." |
- grep -e "^..<<<<<<<" -e "^..=======" -e "^..>>>>>>>" >/dev/null
+ 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
- EDITOR=: git commit -a --no-verify
+ 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=
+ 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 "$eh" in
- "")
- save=$(git rev-parse --verify HEAD)
+ case "$branch" in '###') cut_seen=$(( $cut_seen + 1 )) ;; 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
+ echo >&2 "$branch $eh"
+ 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"
- git merge "$branch" || accept_rerere || exit
+
+ 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"
:
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" &&
- EDITOR=: git commit --amend -a
+ git diff --stat HEAD &&
+ GIT_EDITOR=: git commit --amend -a || leave $?
fi
;;
pick" "*)
echo >&2 "* $eh"
- git cherry-pick "$branch" || exit ;;
- *) echo >&2 "Eh? $branch $eh"; exit ;;
+
+ mark_cut
+
+ git cherry-pick "$branch" || leave $? ;;
+ *) echo >&2 "Eh? $branch $eh"; leave $? ;;
esac
+
+ eval "$exec" || leave $?
done
- exit
+ leave $?
+ ;;
esac
if test -n "$update" && test $# = 0
show_merge () {
case "$msg" in
- "Merge branch '"*"'"*" into "*)
+ "Merge branch '"*"'"*)
branch=$(expr "$msg" : "Merge branch '\(.*\)'")
merge_hier=heads/
;;
- "Merge remote branch '"*"'"*" into "*)
+ "Merge remote branch '"*"'"*)
branch=$(expr "$msg" : "Merge remote branch '\(.*\)'")
merge_hier=
;;
*)
echo 2>&1 "Huh?: $msg"
- exit 1
+ return
;;
esac &&
tip=$(git rev-parse --verify "refs/$merge_hier$branch" 2>/dev/null) &&
}
show_pick () {
- merged="$(git rev-parse --verify "$commit") pick $msg"
+ case "$msg" in
+ "### "* | "###")
+ merged="$msg$LF"
+ ;;
+ *)
+ merged="$(git rev-parse --verify "$commit") pick $msg"
+ ;;
+ esac
+
}
generate () {
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 --pretty=oneline --first-parent "$1" |
+ git log --no-decorate --pretty=oneline --first-parent "$1" |
{
series=
while read commit msg
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
generate "$0" "$@"
elif test -z "$diff"
then
- generate "$0" "$@" | diff -u "$update" -
+ generate "$0" "$@" | diff -w -u "$update" -
if test $? = 0
then
echo >&2 "No changes."
else
- echo >&2 -n "Update [Y/n]? "
+ echo >&2 -n "Update [y/N]? "
read yesno
case "$yesno" in
[Yy]*)
esac
fi
else
- generate "$0" "$@" | diff -u "$update" -
+ generate "$0" "$@" | diff -w -u "$update" -
fi