git/cook
Junio C Hamano 7f71885f30 Meta/cook: more auto-transition of states
Just like "Will merge to 'next'" is automatically promoted to "Will
merge to 'master'" once the topic is merged to 'next', transition
two more states automatically when a topic is merged to 'next'.
"Will merge to and cook in 'next'" will become "Will cook in 'next'"
"Will merge to 'next' and then to 'master'" will become "Will merge
to 'master'".
2017-02-10 14:18:20 -08:00

931 lines
20 KiB
Perl
Executable File

#!/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), "--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),
"--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 (100 < @desc) {
@desc = @desc[0..99];
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]+: .*|\s.*)\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'}) {
# Final clean-up. No leading or trailing
# blank lines, no multi-line gaps.
for ($d->{'text'}) {
s/^\n+//s;
s/\n{3,}/\n\n/s;
s/\n+$//s;
}
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 $cooking_topics = 'Cooking';
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 starting a new issue, move what used to be in
# new topics to cooking topics.
if (!$incremental) {
my $sd = $cooking->{'section_data'};
my $sl = $cooking->{'section_list'};
if (exists $sd->{$new_topics}) {
if (!exists $sd->{$cooking_topics}) {
$sd->{$cooking_topics} = [];
unshift @{$sl}, $cooking_topics;
}
unshift @{$sd->{$cooking_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 tweak_willdo {
my ($td) = @_;
my $desc = $td->{'desc'};
my $text = $td->{'text'};
# If updated description (i.e. the list of patches with
# merge trail to 'next') has 'merged to next', then
# tweak the topic to be slated to 'master'.
# NEEDSWORK: does this work correctly for a half-merged topic?
$desc =~ s/\n<<\n.*//s;
if ($desc =~ /^ \(merged to 'next'/m) {
$text =~ s/^ Will merge to 'next'\.$/ Will merge to 'master'./m;
$text =~ s/^ Will merge to and then cook in 'next'\.$/ Will cook in 'next'./m;
$text =~ s/^ Will merge to 'next' and then to 'master'\.$/ Will merge to 'master'./m;
}
$td->{'text'} = $text;
}
sub tweak_graduated {
my ($td) = @_;
# Remove the "Will merge" marker from topics that have graduated.
for ($td->{'text'}) {
s/\n Will merge to 'master'\.(\n|$)//s;
}
}
sub merge_cooking {
my ($cooking, $current) = @_;
# A hash to find <desc, text> with a branch name or $blurb
my $td = $cooking->{'topic_description'};
# A hash to find a list of $td element given a section name
my $sd = $cooking->{'section_data'};
# A list of section names
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>>";
tweak_willdo($td->{$topic});
}
}
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}}, $_;
tweak_graduated($td->{$_});
}
}
}
################################################################
# 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|hold|keep|merge|drop|discard|cook|kick|defer|eject|be re-?rolled)[,. ]/ ||
/^Not urgent/ || /^Not ready/ || /^Waiting for / ||
/^Can wait in / || /^Still / ||
/^Needs? / || /^Expecting / || /^May want to /) {
return 1;
}
if (/^I think this is ready for /) {
return 1;
}
return 0;
}
sub wildo {
my $fd = shift;
my (%what, $topic, $last_merge_to_next, $in_section, $in_desc);
my $too_recent = '9999-99-99';
while (<$fd>) {
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 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, $havedone);
if (!GetOptions("wildo" => \$wildo,
"havedone" => \$havedone)) {
print STDERR "$0 [--wildo|--havedone]\n";
exit 1;
}
if ($wildo) {
my $fd;
if (!@ARGV) {
open($fd, "<", "Meta/whats-cooking.txt");
} elsif (@ARGV != 1) {
print STDERR "$0 --wildo [filename|HEAD|-]\n";
exit 1;
} elsif ($ARGV[0] eq '-') {
$fd = \*STDIN;
} elsif ($ARGV[0] =~ /^HEAD/) {
open($fd, "-|",
qw(git --git-dir=Meta/.git cat-file -p),
"$ARGV[0]:whats-cooking.txt");
} else {
open($fd, "<", $ARGV[0]);
}
wildo($fd);
} elsif ($havedone) {
havedone();
} else {
doit();
}