You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
875 lines
19 KiB
875 lines
19 KiB
#!/usr/bin/perl -w |
|
# Maintain "what's cooking" messages |
|
|
|
use strict; |
|
|
|
my %reverts = ('next' => { |
|
map { $_ => 1 } qw( |
|
) }); |
|
|
|
%reverts = (); |
|
|
|
sub phrase_these { |
|
my %uniq = (); |
|
my (@u) = grep { $uniq{$_}++ == 0 } sort @_; |
|
my @d = (); |
|
for (my $i = 0; $i < @u; $i++) { |
|
push @d, $u[$i]; |
|
if ($i == @u - 2) { |
|
push @d, " and "; |
|
} elsif ($i < @u - 2) { |
|
push @d, ", "; |
|
} |
|
} |
|
return join('', @d); |
|
} |
|
|
|
sub describe_relation { |
|
my ($topic_info) = @_; |
|
my @desc; |
|
|
|
if (exists $topic_info->{'used'}) { |
|
push @desc, ("is used by " . |
|
phrase_these(@{$topic_info->{'used'}})); |
|
} |
|
|
|
if (exists $topic_info->{'uses'}) { |
|
push @desc, ("uses " . |
|
phrase_these(@{$topic_info->{'uses'}})); |
|
} |
|
|
|
if (exists $topic_info->{'shares'}) { |
|
push @desc, ("is tangled with " . |
|
phrase_these(@{$topic_info->{'shares'}})); |
|
} |
|
|
|
if (!@desc) { |
|
return ""; |
|
} |
|
|
|
return "(this branch " . join("; ", @desc) . ".)"; |
|
} |
|
|
|
sub forks_from { |
|
my ($topic, $fork, $forkee, @overlap) = @_; |
|
my %ovl = map { $_ => 1 } (@overlap, @{$topic->{$forkee}{'log'}}); |
|
|
|
push @{$topic->{$fork}{'uses'}}, $forkee; |
|
push @{$topic->{$forkee}{'used'}}, $fork; |
|
@{$topic->{$fork}{'log'}} = (grep { !exists $ovl{$_} } |
|
@{$topic->{$fork}{'log'}}); |
|
} |
|
|
|
sub topic_relation { |
|
my ($topic, $one, $two) = @_; |
|
|
|
my $fh; |
|
open($fh, '-|', |
|
qw(git log --abbrev=7), "--format=%m %h", |
|
"$one...$two", "^master") |
|
or die "$!: open log --left-right"; |
|
my (@left, @right); |
|
while (<$fh>) { |
|
my ($sign, $sha1) = /^(.) (.*)/; |
|
if ($sign eq '<') { |
|
push @left, $sha1; |
|
} elsif ($sign eq '>') { |
|
push @right, $sha1; |
|
} |
|
} |
|
close($fh) or die "$!: close log --left-right"; |
|
|
|
if (!@left) { |
|
if (@right) { |
|
forks_from($topic, $two, $one); |
|
} |
|
} elsif (!@right) { |
|
forks_from($topic, $one, $two); |
|
} else { |
|
push @{$topic->{$one}{'shares'}}, $two; |
|
push @{$topic->{$two}{'shares'}}, $one; |
|
} |
|
} |
|
|
|
=head1 |
|
Inspect the current set of topics |
|
|
|
Returns a hash: |
|
|
|
$topic = { |
|
$branchname => { |
|
'tipdate' => date of the tip commit, |
|
'desc' => description string, |
|
'log' => [ $commit,... ], |
|
}, |
|
} |
|
|
|
=cut |
|
|
|
sub get_commit { |
|
my (@base) = qw(master next pu); |
|
my $fh; |
|
open($fh, '-|', |
|
qw(git for-each-ref), |
|
"--format=%(refname:short) %(committerdate:iso8601)", |
|
"refs/heads/??/*") |
|
or die "$!: open for-each-ref"; |
|
my @topic; |
|
my %topic; |
|
|
|
while (<$fh>) { |
|
chomp; |
|
my ($branch, $date) = /^(\S+) (.*)$/; |
|
push @topic, $branch; |
|
$date =~ s/ .*//; |
|
$topic{$branch} = +{ |
|
log => [], |
|
tipdate => $date, |
|
}; |
|
} |
|
close($fh) or die "$!: close for-each-ref"; |
|
|
|
my %base = map { $_ => undef } @base; |
|
my %commit; |
|
my $show_branch_batch = 20; |
|
|
|
while (@topic) { |
|
my @t = (@base, splice(@topic, 0, $show_branch_batch)); |
|
my $header_delim = '-' x scalar(@t); |
|
my $contain_pat = '.' x scalar(@t); |
|
open($fh, '-|', qw(git show-branch --sparse --sha1-name), |
|
map { "refs/heads/$_" } @t) |
|
or die "$!: open show-branch"; |
|
while (<$fh>) { |
|
chomp; |
|
if ($header_delim) { |
|
if (/^$header_delim$/) { |
|
$header_delim = undef; |
|
} |
|
next; |
|
} |
|
my ($contain, $sha1, $log) = |
|
($_ =~ /^($contain_pat) \[([0-9a-f]+)\] (.*)$/); |
|
|
|
for (my $i = 0; $i < @t; $i++) { |
|
my $branch = $t[$i]; |
|
my $sign = substr($contain, $i, 1); |
|
next if ($sign eq ' '); |
|
next if (substr($contain, 0, 1) ne ' '); |
|
|
|
if (!exists $commit{$sha1}) { |
|
$commit{$sha1} = +{ |
|
branch => {}, |
|
log => $log, |
|
}; |
|
} |
|
my $co = $commit{$sha1}; |
|
if (!exists $reverts{$branch}{$sha1}) { |
|
$co->{'branch'}{$branch} = 1; |
|
} |
|
next if (exists $base{$branch}); |
|
push @{$topic{$branch}{'log'}}, $sha1; |
|
} |
|
} |
|
close($fh) or die "$!: close show-branch"; |
|
} |
|
|
|
my %shared; |
|
for my $sha1 (keys %commit) { |
|
my $sign; |
|
my $co = $commit{$sha1}; |
|
if (exists $co->{'branch'}{'next'}) { |
|
$sign = '+'; |
|
} elsif (exists $co->{'branch'}{'pu'}) { |
|
$sign = '-'; |
|
} else { |
|
$sign = '.'; |
|
} |
|
$co->{'log'} = $sign . ' ' . $co->{'log'}; |
|
my @t = (sort grep { !exists $base{$_} } |
|
keys %{$co->{'branch'}}); |
|
next if (@t < 2); |
|
my $t = "@t"; |
|
$shared{$t} = 1; |
|
} |
|
|
|
for my $combo (keys %shared) { |
|
my @combo = split(' ', $combo); |
|
for (my $i = 0; $i < @combo - 1; $i++) { |
|
for (my $j = $i + 1; $j < @combo; $j++) { |
|
topic_relation(\%topic, $combo[$i], $combo[$j]); |
|
} |
|
} |
|
} |
|
|
|
open($fh, '-|', |
|
qw(git log --first-parent --abbrev=7), |
|
"--format=%ci %h %p :%s", "master..next") |
|
or die "$!: open log master..next"; |
|
while (<$fh>) { |
|
my ($date, $commit, $parent, $tips); |
|
unless (($date, $commit, $parent, $tips) = |
|
/^([-0-9]+) ..:..:.. .\d{4} (\S+) (\S+) ([^:]*):/) { |
|
die "Oops: $_"; |
|
} |
|
for my $tip (split(' ', $tips)) { |
|
my $co = $commit{$tip}; |
|
next unless ($co->{'branch'}{'next'}); |
|
$co->{'merged'} = " (merged to 'next' on $date at $commit)"; |
|
} |
|
} |
|
close($fh) or die "$!: close log master..next"; |
|
|
|
for my $branch (keys %topic) { |
|
my @log = (); |
|
my $n = scalar(@{$topic{$branch}{'log'}}); |
|
if (!$n) { |
|
delete $topic{$branch}; |
|
next; |
|
} elsif ($n == 1) { |
|
$n = "1 commit"; |
|
} else { |
|
$n = "$n commits"; |
|
} |
|
my $d = $topic{$branch}{'tipdate'}; |
|
my $head = "* $branch ($d) $n\n"; |
|
my @desc; |
|
for (@{$topic{$branch}{'log'}}) { |
|
my $co = $commit{$_}; |
|
if (exists $co->{'merged'}) { |
|
push @desc, $co->{'merged'}; |
|
} |
|
push @desc, $commit{$_}->{'log'}; |
|
} |
|
|
|
if (80 < @desc) { |
|
@desc = @desc[0..4]; |
|
push @desc, "- ..."; |
|
} |
|
|
|
my $list = join("\n", map { " " . $_ } @desc); |
|
my $relation = describe_relation($topic{$branch}); |
|
$topic{$branch}{'desc'} = $head . $list; |
|
if ($relation) { |
|
$topic{$branch}{'desc'} .= "\n $relation"; |
|
} |
|
} |
|
|
|
return \%topic; |
|
} |
|
|
|
sub blurb_text { |
|
my ($mon, $year, $issue, $dow, $date, |
|
$master_at, $next_at, $text) = @_; |
|
|
|
my $now_string = localtime; |
|
my ($current_dow, $current_mon, $current_date, $current_year) = |
|
($now_string =~ /^(\w+) (\w+) (\d+) [\d:]+ (\d+)$/); |
|
|
|
$mon ||= $current_mon; |
|
$year ||= $current_year; |
|
$issue ||= "01"; |
|
$dow ||= $current_dow; |
|
$date ||= $current_date; |
|
$master_at ||= '0' x 40; |
|
$next_at ||= '0' x 40; |
|
$text ||= <<'EOF'; |
|
Here are the topics that have been cooking. Commits prefixed with '-' are |
|
only in 'pu' (proposed updates) while commits prefixed with '+' are in 'next'. |
|
The ones marked with '.' do not appear in any of the integration branches, |
|
but I am still holding onto them. |
|
|
|
You can find the changes described here in the integration branches of the |
|
repositories listed at |
|
|
|
http://git-blame.blogspot.com/p/git-public-repositories.html |
|
EOF |
|
|
|
$text = <<EOF; |
|
To: git\@vger.kernel.org |
|
Bcc: lwn\@lwn.net |
|
Subject: What's cooking in git.git ($mon $year, #$issue; $dow, $date) |
|
X-master-at: $master_at |
|
X-next-at: $next_at |
|
|
|
What's cooking in git.git ($mon $year, #$issue; $dow, $date) |
|
-------------------------------------------------- |
|
|
|
$text |
|
EOF |
|
$text =~ s/\n+\Z/\n/; |
|
return $text; |
|
} |
|
|
|
my $blurb_match = <<'EOF'; |
|
(?i:\s*[a-z]+: .*\n)*?Subject: What's cooking in \S+ \((\w+) (\d+), #(\d+); (\w+), (\d+)\) |
|
X-master-at: ([0-9a-f]{40}) |
|
X-next-at: ([0-9a-f]{40}) |
|
|
|
What's cooking in \S+ \(\1 \2, #\3; \4, \5\) |
|
-{30,} |
|
\n* |
|
EOF |
|
|
|
my $blurb = "b..l..u..r..b"; |
|
sub read_previous { |
|
my ($fn) = @_; |
|
my $fh; |
|
my $section = undef; |
|
my $serial = 1; |
|
my $branch = $blurb; |
|
my $last_empty = undef; |
|
my (@section, %section, @branch, %branch, %description, @leader); |
|
my $in_unedited_olde = 0; |
|
|
|
if (!-r $fn) { |
|
return +{ |
|
'section_list' => [], |
|
'section_data' => {}, |
|
'topic_description' => { |
|
$blurb => { |
|
desc => undef, |
|
text => blurb_text(), |
|
}, |
|
}, |
|
}; |
|
} |
|
|
|
open ($fh, '<', $fn) or die "$!: open $fn"; |
|
while (<$fh>) { |
|
chomp; |
|
s/\s+$//; |
|
if ($in_unedited_olde) { |
|
if (/^>>$/) { |
|
$in_unedited_olde = 0; |
|
$_ = " | $_"; |
|
} |
|
} elsif (/^<<$/) { |
|
$in_unedited_olde = 1; |
|
} |
|
|
|
if ($in_unedited_olde) { |
|
$_ = " | $_"; |
|
} |
|
|
|
if (defined $section && /^-{20,}$/) { |
|
$_ = ""; |
|
} |
|
if (/^$/) { |
|
$last_empty = 1; |
|
next; |
|
} |
|
if (/^\[(.*)\]\s*$/) { |
|
$section = $1; |
|
$branch = undef; |
|
if (!exists $section{$section}) { |
|
push @section, $section; |
|
$section{$section} = []; |
|
} |
|
next; |
|
} |
|
if (defined $section && /^\* (\S+) /) { |
|
$branch = $1; |
|
$last_empty = 0; |
|
if (!exists $branch{$branch}) { |
|
push @branch, [$branch, $section]; |
|
$branch{$branch} = 1; |
|
} |
|
push @{$section{$section}}, $branch; |
|
} |
|
if (defined $branch) { |
|
my $was_last_empty = $last_empty; |
|
$last_empty = 0; |
|
if (!exists $description{$branch}) { |
|
$description{$branch} = []; |
|
} |
|
if ($was_last_empty) { |
|
push @{$description{$branch}}, ""; |
|
} |
|
push @{$description{$branch}}, $_; |
|
} |
|
} |
|
close($fh); |
|
|
|
my $lead = " "; |
|
for my $branch (keys %description) { |
|
my $ary = $description{$branch}; |
|
if ($branch eq $blurb) { |
|
while (@{$ary} && $ary->[-1] =~ /^-{30,}$/) { |
|
pop @{$ary}; |
|
} |
|
$description{$branch} = +{ |
|
desc => undef, |
|
text => join("\n", @{$ary}), |
|
}; |
|
} else { |
|
my @desc = (); |
|
while (@{$ary}) { |
|
my $elem = shift @{$ary}; |
|
last if ($elem eq ''); |
|
push @desc, $elem; |
|
} |
|
my @txt = map { |
|
s/^\s+//; |
|
$_ = "$lead$_"; |
|
s/\s+$//; |
|
$_; |
|
} @{$ary}; |
|
|
|
$description{$branch} = +{ |
|
desc => join("\n", @desc), |
|
text => join("\n", @txt), |
|
}; |
|
} |
|
} |
|
|
|
return +{ |
|
section_list => \@section, |
|
section_data => \%section, |
|
topic_description => \%description, |
|
}; |
|
} |
|
|
|
sub write_cooking { |
|
my ($fn, $cooking) = @_; |
|
my $fh; |
|
|
|
open($fh, '>', $fn) or die "$!: open $fn"; |
|
print $fh $cooking->{'topic_description'}{$blurb}{'text'}; |
|
|
|
for my $section_name (@{$cooking->{'section_list'}}) { |
|
my $topic_list = $cooking->{'section_data'}{$section_name}; |
|
next if (!@{$topic_list}); |
|
|
|
print $fh "\n"; |
|
print $fh '-' x 50, "\n"; |
|
print $fh "[$section_name]\n"; |
|
my $lead = "\n"; |
|
for my $topic (@{$topic_list}) { |
|
my $d = $cooking->{'topic_description'}{$topic}; |
|
|
|
print $fh $lead, $d->{'desc'}, "\n"; |
|
if ($d->{'text'}) { |
|
print $fh "\n", $d->{'text'}, "\n"; |
|
} |
|
$lead = "\n\n"; |
|
} |
|
} |
|
close($fh); |
|
} |
|
|
|
my $graduated = 'Graduated to "master"'; |
|
my $new_topics = 'New Topics'; |
|
my $discarded = 'Discarded'; |
|
my $old_new_topics = 'Old New Topics'; |
|
|
|
sub update_issue { |
|
my ($cooking) = @_; |
|
my ($fh, $master_at, $next_at, $incremental); |
|
|
|
open($fh, '-|', |
|
qw(git for-each-ref), |
|
"--format=%(refname:short) %(objectname)", |
|
"refs/heads/master", |
|
"refs/heads/next") or die "$!: open for-each-ref"; |
|
while (<$fh>) { |
|
my ($branch, $at) = /^(\S+) (\S+)$/; |
|
if ($branch eq 'master') { $master_at = $at; } |
|
if ($branch eq 'next') { $next_at = $at; } |
|
} |
|
close($fh) or die "$!: close for-each-ref"; |
|
|
|
$incremental = ((-r "Meta/whats-cooking.txt") && |
|
system("cd Meta && " . |
|
"git diff --quiet --no-ext-diff HEAD -- " . |
|
"whats-cooking.txt")); |
|
|
|
my $now_string = localtime; |
|
my ($current_dow, $current_mon, $current_date, $current_year) = |
|
($now_string =~ /^(\w+) (\w+) +(\d+) [\d:]+ (\d+)$/); |
|
|
|
my $btext = $cooking->{'topic_description'}{$blurb}{'text'}; |
|
if ($btext !~ s/\A$blurb_match//) { |
|
die "match pattern broken?"; |
|
} |
|
my ($mon, $year, $issue, $dow, $date) = ($1, $2, $3, $4, $5); |
|
|
|
if ($current_mon ne $mon || $current_year ne $year) { |
|
$issue = "01"; |
|
} elsif (!$incremental) { |
|
$issue =~ s/^0*//; |
|
$issue = sprintf "%02d", ($issue + 1); |
|
} |
|
$mon = $current_mon; |
|
$year = $current_year; |
|
$dow = $current_dow; |
|
$date = $current_date; |
|
|
|
$cooking->{'topic_description'}{$blurb}{'text'} = |
|
blurb_text($mon, $year, $issue, $dow, $date, |
|
$master_at, $next_at, $btext); |
|
|
|
if (!$incremental) { |
|
my $sd = $cooking->{'section_data'}; |
|
my $sl = $cooking->{'section_list'}; |
|
# Rename "New" to "Old New" and insert "New". |
|
# Move "New" to "Old New" |
|
my $i; |
|
my $doneso; |
|
for ($i = 0; $i < @{$sl}; $i++) { |
|
if ($sl->[$i] eq $new_topics) { |
|
$sl->[$i] = $old_new_topics; |
|
unshift @{$sl}, $new_topics; |
|
$doneso = 1; |
|
last; |
|
} |
|
} |
|
if ($doneso) { |
|
$sd->{$old_new_topics} = $sd->{$new_topics}; |
|
} |
|
$sd->{$new_topics} = []; |
|
} |
|
|
|
return $incremental; |
|
} |
|
|
|
sub topic_in_pu { |
|
my ($topic_desc) = @_; |
|
for my $line (split(/\n/, $topic_desc)) { |
|
if ($line =~ /^ [+-] /) { |
|
return 1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
sub merge_cooking { |
|
my ($cooking, $current) = @_; |
|
my $td = $cooking->{'topic_description'}; |
|
my $sd = $cooking->{'section_data'}; |
|
my $sl = $cooking->{'section_list'}; |
|
my (@new_topic, @gone_topic); |
|
|
|
# Make sure "New Topics" and "Graduated" exists |
|
if (!exists $sd->{$new_topics}) { |
|
$sd->{$new_topics} = []; |
|
unshift @{$sl}, $new_topics; |
|
} |
|
|
|
if (!exists $sd->{$graduated}) { |
|
$sd->{$graduated} = []; |
|
unshift @{$sl}, $graduated; |
|
} |
|
|
|
my $incremental = update_issue($cooking); |
|
|
|
for my $topic (sort keys %{$current}) { |
|
if (!exists $td->{$topic}) { |
|
# Ignore new topics without anything merged |
|
if (topic_in_pu($current->{$topic}{'desc'})) { |
|
push @new_topic, $topic; |
|
} |
|
next; |
|
} |
|
# Annotate if the contents of the topic changed |
|
my $n = $current->{$topic}{'desc'}; |
|
my $o = $td->{$topic}{'desc'}; |
|
if ($n ne $o) { |
|
$td->{$topic}{'desc'} = $n . "\n<<\n" . $o ."\n>>"; |
|
} |
|
} |
|
|
|
for my $topic (sort keys %{$td}) { |
|
next if ($topic eq $blurb); |
|
next if (!$incremental && |
|
grep { $topic eq $_ } @{$sd->{$graduated}}); |
|
next if (grep { $topic eq $_ } @{$sd->{$discarded}}); |
|
if (!exists $current->{$topic}) { |
|
push @gone_topic, $topic; |
|
} |
|
} |
|
|
|
for (@new_topic) { |
|
push @{$sd->{$new_topics}}, $_; |
|
$td->{$_}{'desc'} = $current->{$_}{'desc'}; |
|
} |
|
|
|
if (!$incremental) { |
|
$sd->{$graduated} = []; |
|
} |
|
|
|
if (@gone_topic) { |
|
for my $topic (@gone_topic) { |
|
for my $section (@{$sl}) { |
|
my $pre = scalar(@{$sd->{$section}}); |
|
@{$sd->{$section}} = (grep { $_ ne $topic } |
|
@{$sd->{$section}}); |
|
my $post = scalar(@{$sd->{$section}}); |
|
next if ($pre == $post); |
|
} |
|
} |
|
for (@gone_topic) { |
|
push @{$sd->{$graduated}}, $_; |
|
} |
|
} |
|
} |
|
|
|
################################################################ |
|
# WilDo |
|
sub wildo_queue { |
|
my ($what, $action, $topic) = @_; |
|
if (!exists $what->{$action}) { |
|
$what->{$action} = []; |
|
} |
|
push @{$what->{$action}}, $topic; |
|
} |
|
|
|
sub section_action { |
|
my ($section) = @_; |
|
if ($section) { |
|
for ($section) { |
|
return if (/^Graduated to/ || /^Discarded$/); |
|
return $_ if (/^Stalled$/); |
|
} |
|
} |
|
return "Undecided"; |
|
} |
|
|
|
sub wildo_flush_topic { |
|
my ($in_section, $what, $topic) = @_; |
|
if (defined $topic) { |
|
my $action = section_action($in_section); |
|
if ($action) { |
|
wildo_queue($what, $action, $topic); |
|
} |
|
} |
|
} |
|
|
|
sub wildo_match { |
|
if (/^Will (?:\S+ ){0,2}(fast-track|keep|merge|drop|discard|cook|kick|defer|be re-?rolled)[,. ]/ || |
|
/^Not urgent/ || /^Not ready/ || /^Waiting for / || |
|
/^Needs? / || /^Expecting / || /^May want to /) { |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
sub wildo { |
|
my (%what, $topic, $last_merge_to_next, $in_section, $in_desc); |
|
my $too_recent = '9999-99-99'; |
|
while (<>) { |
|
chomp; |
|
|
|
if (/^\[(.*)\]$/) { |
|
my $old_section = $in_section; |
|
$in_section = $1; |
|
wildo_flush_topic($old_section, \%what, $topic); |
|
$topic = $in_desc = undef; |
|
next; |
|
} |
|
|
|
if (/^\* (\S+) \(([-0-9]+)\) (\d+) commits?$/) { |
|
wildo_flush_topic($in_section, \%what, $topic); |
|
|
|
# tip-date, next-date, topic, count, pu-count |
|
$topic = [$2, $too_recent, $1, $3, 0]; |
|
$in_desc = undef; |
|
next; |
|
} |
|
|
|
if (defined $topic && |
|
($topic->[1] eq $too_recent) && |
|
($topic->[4] == 0) && |
|
(/^ \(merged to 'next' on ([-0-9]+)/)) { |
|
$topic->[1] = $1; |
|
} |
|
if (defined $topic && /^ - /) { |
|
$topic->[4]++; |
|
} |
|
|
|
if (defined $topic && /^$/) { |
|
$in_desc = 1; |
|
next; |
|
} |
|
|
|
next unless defined $topic && $in_desc; |
|
|
|
s/^\s+//; |
|
if (wildo_match($_)) { |
|
wildo_queue(\%what, $_, $topic); |
|
$topic = $in_desc = undef; |
|
} |
|
|
|
if (/Originally merged to 'next' on ([-0-9]+)/) { |
|
$topic->[1] = $1; |
|
} |
|
} |
|
wildo_flush_topic($in_section, \%what, $topic); |
|
|
|
my $ipbl = ""; |
|
for my $what (sort keys %what) { |
|
print "$ipbl$what\n"; |
|
for $topic (sort { (($a->[1] cmp $b->[1]) || |
|
($a->[0] cmp $b->[0])) } |
|
@{$what{$what}}) { |
|
my ($tip, $next, $name, $count, $pu) = @$topic; |
|
my ($sign); |
|
$tip =~ s/^\d{4}-//; |
|
if (($next eq $too_recent) || (0 < $pu)) { |
|
$sign = "-"; |
|
$next = " " x 6; |
|
} else { |
|
$sign = "+"; |
|
$next =~ s|^\d{4}-|/|; |
|
} |
|
$count = "#$count"; |
|
printf " %s %-60s %s%s %5s\n", $sign, $name, $tip, $next, $count; |
|
} |
|
$ipbl = "\n"; |
|
} |
|
} |
|
|
|
################################################################ |
|
# HavDone |
|
sub havedone_show { |
|
my $topic = shift; |
|
my $str = shift; |
|
my $prefix = " * "; |
|
$str =~ s/\A\n+//; |
|
$str =~ s/\n+\Z//; |
|
|
|
print "($topic)\n"; |
|
for $str (split(/\n/, $str)) { |
|
print "$prefix$str\n"; |
|
$prefix = " "; |
|
} |
|
} |
|
|
|
sub havedone_count { |
|
my @range = @_; |
|
my $cnt = `git rev-list --count @range`; |
|
chomp $cnt; |
|
return $cnt; |
|
} |
|
|
|
sub havedone { |
|
my $fh; |
|
my %topic = (); |
|
my @topic = (); |
|
my ($topic, $to_maint, %to_maint, %merged, $in_desc); |
|
if (!@ARGV) { |
|
open($fh, '-|', |
|
qw(git rev-list --first-parent -1 master Documentation/RelNotes)) |
|
or die "$!: open rev-list"; |
|
my ($rev) = <$fh>; |
|
close($fh) or die "$!: close rev-list"; |
|
chomp $rev; |
|
@ARGV = ("$rev..master"); |
|
} |
|
open($fh, '-|', |
|
qw(git log --first-parent --oneline --reverse), @ARGV) |
|
or die "$!: open log --first-parent"; |
|
while (<$fh>) { |
|
my ($sha1, $branch) = /^([0-9a-f]+) Merge branch '(.*)'$/; |
|
next unless $branch; |
|
$topic{$branch} = ""; |
|
$merged{$branch} = $sha1; |
|
push @topic, $branch; |
|
} |
|
close($fh) or die "$!: close log --first-parent"; |
|
open($fh, "<", "Meta/whats-cooking.txt") |
|
or die "$!: open whats-cooking"; |
|
while (<$fh>) { |
|
chomp; |
|
if (/^\[(.*)\]$/) { |
|
# section header |
|
$in_desc = $topic = undef; |
|
next; |
|
} |
|
if (/^\* (\S+) \([-0-9]+\) \d+ commits?$/) { |
|
if (exists $topic{$1}) { |
|
$topic = $1; |
|
$to_maint = 0; |
|
} else { |
|
$in_desc = $topic = undef; |
|
} |
|
next; |
|
} |
|
if (defined $topic && /^$/) { |
|
$in_desc = 1; |
|
next; |
|
} |
|
|
|
next unless defined $topic && $in_desc; |
|
|
|
s/^\s+//; |
|
if (wildo_match($_)) { |
|
next; |
|
} |
|
$topic{$topic} .= "$_\n"; |
|
} |
|
close($fh) or die "$!: close whats-cooking"; |
|
|
|
for $topic (@topic) { |
|
my $merged = $merged{$topic}; |
|
my $in_master = havedone_count("$merged^1..$merged^2"); |
|
my $not_in_maint = havedone_count("maint..$merged^2"); |
|
if ($in_master == $not_in_maint) { |
|
$to_maint{$topic} = 1; |
|
} |
|
} |
|
|
|
my $shown = 0; |
|
for $topic (@topic) { |
|
next if (exists $to_maint{$topic}); |
|
havedone_show($topic, $topic{$topic}); |
|
print "\n"; |
|
$shown++; |
|
} |
|
|
|
if ($shown) { |
|
print "-" x 64, "\n"; |
|
} |
|
|
|
for $topic (@topic) { |
|
next unless (exists $to_maint{$topic}); |
|
havedone_show($topic, $topic{$topic}); |
|
my $sha1 = `git rev-parse --short $topic`; |
|
chomp $sha1; |
|
print " (merge $sha1 $topic later to maint).\n"; |
|
print "\n"; |
|
} |
|
} |
|
|
|
################################################################ |
|
# WhatsCooking |
|
|
|
sub doit { |
|
my $topic = get_commit(); |
|
my $cooking = read_previous('Meta/whats-cooking.txt'); |
|
merge_cooking($cooking, $topic); |
|
write_cooking('Meta/whats-cooking.txt', $cooking); |
|
} |
|
|
|
################################################################ |
|
# Main |
|
|
|
use Getopt::Long; |
|
|
|
my $wildo; |
|
my $havedone; |
|
if (!GetOptions("wildo" => \$wildo, "havedone" => \$havedone)) { |
|
print STDERR "$0 [--wildo|--havedone]\n"; |
|
exit 1; |
|
} |
|
|
|
if ($wildo) { |
|
if (!@ARGV) { |
|
push @ARGV, "Meta/whats-cooking.txt"; |
|
} |
|
wildo(); |
|
} elsif ($havedone) { |
|
havedone(); |
|
} else { |
|
doit(); |
|
}
|
|
|