mirror of
https://github.com/git/git.git
synced 2026-08-10 01:49:12 +00:00
While waiting for build and test of the current round of
integration, we should be able to prepare the next round while on
a detached HEAD. By running
$ git co --detach master
$ INTO=jch Meta/redo-jch.sh
we can prepare a sequence of "Merge branch 'foo' into jch" commits
that can be used to replace 'jch' that is currently being tested,
without disrupting the test.
416 lines
8.4 KiB
Bash
Executable File
416 lines
8.4 KiB
Bash
Executable File
#!/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/ ||
|
|
/^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" || 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
|