blob: 63340a9db583a70015683d40c1fe8dc15b9ed26e [file] [log] [blame]
#!/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 / ||
/^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 () {
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=
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 )) ;; 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"
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 "$branch" || 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>&1 "Huh?: $msg"
return
;;
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
generate "$0" "$@" | diff -w -u "$update" -
if test $? = 0
then
echo >&2 "No changes."
else
echo >&2 -n "Update [y/N]? "
read yesno
case "$yesno" in
[Yy]*)
generate "$0" "$@" |
sed -e 's/ :rebased?.*//' >"$update" ;;
*)
echo >&2 "No update then." ;;
esac
fi
else
generate "$0" "$@" | diff -w -u "$update" -
fi