What's cooking (2015/03 #07)
[alt-git.git] / cook
blob5a703f6c08f2ee3aebc5e14643b2e98c606bab97
1 #!/usr/bin/perl -w
2 # Maintain "what's cooking" messages
4 use strict;
6 my %reverts = ('next' => {
7 map { $_ => 1 } qw(
8 ) });
10 %reverts = ();
12 sub phrase_these {
13 my %uniq = ();
14 my (@u) = grep { $uniq{$_}++ == 0 } sort @_;
15 my @d = ();
16 for (my $i = 0; $i < @u; $i++) {
17 push @d, $u[$i];
18 if ($i == @u - 2) {
19 push @d, " and ";
20 } elsif ($i < @u - 2) {
21 push @d, ", ";
24 return join('', @d);
27 sub describe_relation {
28 my ($topic_info) = @_;
29 my @desc;
31 if (exists $topic_info->{'used'}) {
32 push @desc, ("is used by " .
33 phrase_these(@{$topic_info->{'used'}}));
36 if (exists $topic_info->{'uses'}) {
37 push @desc, ("uses " .
38 phrase_these(@{$topic_info->{'uses'}}));
41 if (exists $topic_info->{'shares'}) {
42 push @desc, ("is tangled with " .
43 phrase_these(@{$topic_info->{'shares'}}));
46 if (!@desc) {
47 return "";
50 return "(this branch " . join("; ", @desc) . ".)";
53 sub forks_from {
54 my ($topic, $fork, $forkee, @overlap) = @_;
55 my %ovl = map { $_ => 1 } (@overlap, @{$topic->{$forkee}{'log'}});
57 push @{$topic->{$fork}{'uses'}}, $forkee;
58 push @{$topic->{$forkee}{'used'}}, $fork;
59 @{$topic->{$fork}{'log'}} = (grep { !exists $ovl{$_} }
60 @{$topic->{$fork}{'log'}});
63 sub topic_relation {
64 my ($topic, $one, $two) = @_;
66 my $fh;
67 open($fh, '-|',
68 qw(git log --abbrev=7), "--format=%m %h",
69 "$one...$two", "^master")
70 or die "$!: open log --left-right";
71 my (@left, @right);
72 while (<$fh>) {
73 my ($sign, $sha1) = /^(.) (.*)/;
74 if ($sign eq '<') {
75 push @left, $sha1;
76 } elsif ($sign eq '>') {
77 push @right, $sha1;
80 close($fh) or die "$!: close log --left-right";
82 if (!@left) {
83 if (@right) {
84 forks_from($topic, $two, $one);
86 } elsif (!@right) {
87 forks_from($topic, $one, $two);
88 } else {
89 push @{$topic->{$one}{'shares'}}, $two;
90 push @{$topic->{$two}{'shares'}}, $one;
94 =head1
95 Inspect the current set of topics
97 Returns a hash:
99 $topic = {
100 $branchname => {
101 'tipdate' => date of the tip commit,
102 'desc' => description string,
103 'log' => [ $commit,... ],
107 =cut
109 sub get_commit {
110 my (@base) = qw(master next pu);
111 my $fh;
112 open($fh, '-|',
113 qw(git for-each-ref),
114 "--format=%(refname:short) %(committerdate:iso8601)",
115 "refs/heads/??/*")
116 or die "$!: open for-each-ref";
117 my @topic;
118 my %topic;
120 while (<$fh>) {
121 chomp;
122 my ($branch, $date) = /^(\S+) (.*)$/;
123 push @topic, $branch;
124 $date =~ s/ .*//;
125 $topic{$branch} = +{
126 log => [],
127 tipdate => $date,
130 close($fh) or die "$!: close for-each-ref";
132 my %base = map { $_ => undef } @base;
133 my %commit;
134 my $show_branch_batch = 20;
136 while (@topic) {
137 my @t = (@base, splice(@topic, 0, $show_branch_batch));
138 my $header_delim = '-' x scalar(@t);
139 my $contain_pat = '.' x scalar(@t);
140 open($fh, '-|', qw(git show-branch --sparse --sha1-name),
141 map { "refs/heads/$_" } @t)
142 or die "$!: open show-branch";
143 while (<$fh>) {
144 chomp;
145 if ($header_delim) {
146 if (/^$header_delim$/) {
147 $header_delim = undef;
149 next;
151 my ($contain, $sha1, $log) =
152 ($_ =~ /^($contain_pat) \[([0-9a-f]+)\] (.*)$/);
154 for (my $i = 0; $i < @t; $i++) {
155 my $branch = $t[$i];
156 my $sign = substr($contain, $i, 1);
157 next if ($sign eq ' ');
158 next if (substr($contain, 0, 1) ne ' ');
160 if (!exists $commit{$sha1}) {
161 $commit{$sha1} = +{
162 branch => {},
163 log => $log,
166 my $co = $commit{$sha1};
167 if (!exists $reverts{$branch}{$sha1}) {
168 $co->{'branch'}{$branch} = 1;
170 next if (exists $base{$branch});
171 push @{$topic{$branch}{'log'}}, $sha1;
174 close($fh) or die "$!: close show-branch";
177 my %shared;
178 for my $sha1 (keys %commit) {
179 my $sign;
180 my $co = $commit{$sha1};
181 if (exists $co->{'branch'}{'next'}) {
182 $sign = '+';
183 } elsif (exists $co->{'branch'}{'pu'}) {
184 $sign = '-';
185 } else {
186 $sign = '.';
188 $co->{'log'} = $sign . ' ' . $co->{'log'};
189 my @t = (sort grep { !exists $base{$_} }
190 keys %{$co->{'branch'}});
191 next if (@t < 2);
192 my $t = "@t";
193 $shared{$t} = 1;
196 for my $combo (keys %shared) {
197 my @combo = split(' ', $combo);
198 for (my $i = 0; $i < @combo - 1; $i++) {
199 for (my $j = $i + 1; $j < @combo; $j++) {
200 topic_relation(\%topic, $combo[$i], $combo[$j]);
205 open($fh, '-|',
206 qw(git log --first-parent --abbrev=7),
207 "--format=%ci %h %p :%s", "master..next")
208 or die "$!: open log master..next";
209 while (<$fh>) {
210 my ($date, $commit, $parent, $tips);
211 unless (($date, $commit, $parent, $tips) =
212 /^([-0-9]+) ..:..:.. .\d{4} (\S+) (\S+) ([^:]*):/) {
213 die "Oops: $_";
215 for my $tip (split(' ', $tips)) {
216 my $co = $commit{$tip};
217 next unless ($co->{'branch'}{'next'});
218 $co->{'merged'} = " (merged to 'next' on $date at $commit)";
221 close($fh) or die "$!: close log master..next";
223 for my $branch (keys %topic) {
224 my @log = ();
225 my $n = scalar(@{$topic{$branch}{'log'}});
226 if (!$n) {
227 delete $topic{$branch};
228 next;
229 } elsif ($n == 1) {
230 $n = "1 commit";
231 } else {
232 $n = "$n commits";
234 my $d = $topic{$branch}{'tipdate'};
235 my $head = "* $branch ($d) $n\n";
236 my @desc;
237 for (@{$topic{$branch}{'log'}}) {
238 my $co = $commit{$_};
239 if (exists $co->{'merged'}) {
240 push @desc, $co->{'merged'};
242 push @desc, $commit{$_}->{'log'};
245 if (80 < @desc) {
246 @desc = @desc[0..4];
247 push @desc, "- ...";
250 my $list = join("\n", map { " " . $_ } @desc);
251 my $relation = describe_relation($topic{$branch});
252 $topic{$branch}{'desc'} = $head . $list;
253 if ($relation) {
254 $topic{$branch}{'desc'} .= "\n $relation";
258 return \%topic;
261 sub blurb_text {
262 my ($mon, $year, $issue, $dow, $date,
263 $master_at, $next_at, $text) = @_;
265 my $now_string = localtime;
266 my ($current_dow, $current_mon, $current_date, $current_year) =
267 ($now_string =~ /^(\w+) (\w+) (\d+) [\d:]+ (\d+)$/);
269 $mon ||= $current_mon;
270 $year ||= $current_year;
271 $issue ||= "01";
272 $dow ||= $current_dow;
273 $date ||= $current_date;
274 $master_at ||= '0' x 40;
275 $next_at ||= '0' x 40;
276 $text ||= <<'EOF';
277 Here are the topics that have been cooking. Commits prefixed with '-' are
278 only in 'pu' (proposed updates) while commits prefixed with '+' are in 'next'.
279 The ones marked with '.' do not appear in any of the integration branches,
280 but I am still holding onto them.
282 You can find the changes described here in the integration branches of the
283 repositories listed at
285 http://git-blame.blogspot.com/p/git-public-repositories.html
288 $text = <<EOF;
289 To: git\@vger.kernel.org
290 Bcc: lwn\@lwn.net
291 Subject: What's cooking in git.git ($mon $year, #$issue; $dow, $date)
292 X-master-at: $master_at
293 X-next-at: $next_at
295 What's cooking in git.git ($mon $year, #$issue; $dow, $date)
296 --------------------------------------------------
298 $text
300 $text =~ s/\n+\Z/\n/;
301 return $text;
304 my $blurb_match = <<'EOF';
305 (?:(?i:\s*[a-z]+: .*|\s.*)\n)*?Subject: What's cooking in \S+ \((\w+) (\d+), #(\d+); (\w+), (\d+)\)
306 X-master-at: ([0-9a-f]{40})
307 X-next-at: ([0-9a-f]{40})
309 What's cooking in \S+ \(\1 \2, #\3; \4, \5\)
310 -{30,}
314 my $blurb = "b..l..u..r..b";
315 sub read_previous {
316 my ($fn) = @_;
317 my $fh;
318 my $section = undef;
319 my $serial = 1;
320 my $branch = $blurb;
321 my $last_empty = undef;
322 my (@section, %section, @branch, %branch, %description, @leader);
323 my $in_unedited_olde = 0;
325 if (!-r $fn) {
326 return +{
327 'section_list' => [],
328 'section_data' => {},
329 'topic_description' => {
330 $blurb => {
331 desc => undef,
332 text => blurb_text(),
338 open ($fh, '<', $fn) or die "$!: open $fn";
339 while (<$fh>) {
340 chomp;
341 s/\s+$//;
342 if ($in_unedited_olde) {
343 if (/^>>$/) {
344 $in_unedited_olde = 0;
345 $_ = " | $_";
347 } elsif (/^<<$/) {
348 $in_unedited_olde = 1;
351 if ($in_unedited_olde) {
352 $_ = " | $_";
355 if (defined $section && /^-{20,}$/) {
356 $_ = "";
358 if (/^$/) {
359 $last_empty = 1;
360 next;
362 if (/^\[(.*)\]\s*$/) {
363 $section = $1;
364 $branch = undef;
365 if (!exists $section{$section}) {
366 push @section, $section;
367 $section{$section} = [];
369 next;
371 if (defined $section && /^\* (\S+) /) {
372 $branch = $1;
373 $last_empty = 0;
374 if (!exists $branch{$branch}) {
375 push @branch, [$branch, $section];
376 $branch{$branch} = 1;
378 push @{$section{$section}}, $branch;
380 if (defined $branch) {
381 my $was_last_empty = $last_empty;
382 $last_empty = 0;
383 if (!exists $description{$branch}) {
384 $description{$branch} = [];
386 if ($was_last_empty) {
387 push @{$description{$branch}}, "";
389 push @{$description{$branch}}, $_;
392 close($fh);
394 my $lead = " ";
395 for my $branch (keys %description) {
396 my $ary = $description{$branch};
397 if ($branch eq $blurb) {
398 while (@{$ary} && $ary->[-1] =~ /^-{30,}$/) {
399 pop @{$ary};
401 $description{$branch} = +{
402 desc => undef,
403 text => join("\n", @{$ary}),
405 } else {
406 my @desc = ();
407 while (@{$ary}) {
408 my $elem = shift @{$ary};
409 last if ($elem eq '');
410 push @desc, $elem;
412 my @txt = map {
413 s/^\s+//;
414 $_ = "$lead$_";
415 s/\s+$//;
417 } @{$ary};
419 $description{$branch} = +{
420 desc => join("\n", @desc),
421 text => join("\n", @txt),
426 return +{
427 section_list => \@section,
428 section_data => \%section,
429 topic_description => \%description,
433 sub write_cooking {
434 my ($fn, $cooking) = @_;
435 my $fh;
437 open($fh, '>', $fn) or die "$!: open $fn";
438 print $fh $cooking->{'topic_description'}{$blurb}{'text'};
440 for my $section_name (@{$cooking->{'section_list'}}) {
441 my $topic_list = $cooking->{'section_data'}{$section_name};
442 next if (!@{$topic_list});
444 print $fh "\n";
445 print $fh '-' x 50, "\n";
446 print $fh "[$section_name]\n";
447 my $lead = "\n";
448 for my $topic (@{$topic_list}) {
449 my $d = $cooking->{'topic_description'}{$topic};
451 print $fh $lead, $d->{'desc'}, "\n";
452 if ($d->{'text'}) {
453 print $fh "\n", $d->{'text'}, "\n";
455 $lead = "\n\n";
458 close($fh);
461 my $graduated = 'Graduated to "master"';
462 my $new_topics = 'New Topics';
463 my $discarded = 'Discarded';
464 my $cooking_topics = 'Cooking';
466 sub update_issue {
467 my ($cooking) = @_;
468 my ($fh, $master_at, $next_at, $incremental);
470 open($fh, '-|',
471 qw(git for-each-ref),
472 "--format=%(refname:short) %(objectname)",
473 "refs/heads/master",
474 "refs/heads/next") or die "$!: open for-each-ref";
475 while (<$fh>) {
476 my ($branch, $at) = /^(\S+) (\S+)$/;
477 if ($branch eq 'master') { $master_at = $at; }
478 if ($branch eq 'next') { $next_at = $at; }
480 close($fh) or die "$!: close for-each-ref";
482 $incremental = ((-r "Meta/whats-cooking.txt") &&
483 system("cd Meta && " .
484 "git diff --quiet --no-ext-diff HEAD -- " .
485 "whats-cooking.txt"));
487 my $now_string = localtime;
488 my ($current_dow, $current_mon, $current_date, $current_year) =
489 ($now_string =~ /^(\w+) (\w+) +(\d+) [\d:]+ (\d+)$/);
491 my $btext = $cooking->{'topic_description'}{$blurb}{'text'};
492 if ($btext !~ s/\A$blurb_match//) {
493 die "match pattern broken?";
495 my ($mon, $year, $issue, $dow, $date) = ($1, $2, $3, $4, $5);
497 if ($current_mon ne $mon || $current_year ne $year) {
498 $issue = "01";
499 } elsif (!$incremental) {
500 $issue =~ s/^0*//;
501 $issue = sprintf "%02d", ($issue + 1);
503 $mon = $current_mon;
504 $year = $current_year;
505 $dow = $current_dow;
506 $date = $current_date;
508 $cooking->{'topic_description'}{$blurb}{'text'} =
509 blurb_text($mon, $year, $issue, $dow, $date,
510 $master_at, $next_at, $btext);
512 if (!$incremental) {
513 my $sd = $cooking->{'section_data'};
514 my $sl = $cooking->{'section_list'};
516 if (exists $sd->{$new_topics}) {
517 if (!exists $sd->{$cooking_topics}) {
518 $sd->{$cooking_topics} = [];
519 unshift @{$sl}, $cooking_topics;
521 unshift @{$sd->{$cooking_topics}}, @{$sd->{$new_topics}};
523 $sd->{$new_topics} = [];
526 return $incremental;
529 sub topic_in_pu {
530 my ($topic_desc) = @_;
531 for my $line (split(/\n/, $topic_desc)) {
532 if ($line =~ /^ [+-] /) {
533 return 1;
536 return 0;
539 sub tweak_willdo {
540 my ($td) = @_;
541 my $desc = $td->{'desc'};
542 my $text = $td->{'text'};
543 $desc =~ s/\n<<\n.*//s;
544 if ($desc =~ /^ \(merged to 'next'/m) {
545 $text =~ s/^ Will merge to 'next'\.$/ Will merge to 'master'./m;
547 $td->{'text'} = $text;
550 sub merge_cooking {
551 my ($cooking, $current) = @_;
552 my $td = $cooking->{'topic_description'};
553 my $sd = $cooking->{'section_data'};
554 my $sl = $cooking->{'section_list'};
555 my (@new_topic, @gone_topic);
557 # Make sure "New Topics" and "Graduated" exists
558 if (!exists $sd->{$new_topics}) {
559 $sd->{$new_topics} = [];
560 unshift @{$sl}, $new_topics;
563 if (!exists $sd->{$graduated}) {
564 $sd->{$graduated} = [];
565 unshift @{$sl}, $graduated;
568 my $incremental = update_issue($cooking);
570 for my $topic (sort keys %{$current}) {
571 if (!exists $td->{$topic}) {
572 # Ignore new topics without anything merged
573 if (topic_in_pu($current->{$topic}{'desc'})) {
574 push @new_topic, $topic;
576 next;
578 # Annotate if the contents of the topic changed
579 my $n = $current->{$topic}{'desc'};
580 my $o = $td->{$topic}{'desc'};
581 if ($n ne $o) {
582 $td->{$topic}{'desc'} = $n . "\n<<\n" . $o ."\n>>";
583 tweak_willdo($td->{$topic});
587 for my $topic (sort keys %{$td}) {
588 next if ($topic eq $blurb);
589 next if (!$incremental &&
590 grep { $topic eq $_ } @{$sd->{$graduated}});
591 next if (grep { $topic eq $_ } @{$sd->{$discarded}});
592 if (!exists $current->{$topic}) {
593 push @gone_topic, $topic;
597 for (@new_topic) {
598 push @{$sd->{$new_topics}}, $_;
599 $td->{$_}{'desc'} = $current->{$_}{'desc'};
602 if (!$incremental) {
603 $sd->{$graduated} = [];
606 if (@gone_topic) {
607 for my $topic (@gone_topic) {
608 for my $section (@{$sl}) {
609 my $pre = scalar(@{$sd->{$section}});
610 @{$sd->{$section}} = (grep { $_ ne $topic }
611 @{$sd->{$section}});
612 my $post = scalar(@{$sd->{$section}});
613 next if ($pre == $post);
616 for (@gone_topic) {
617 push @{$sd->{$graduated}}, $_;
622 ################################################################
623 # WilDo
624 sub wildo_queue {
625 my ($what, $action, $topic) = @_;
626 if (!exists $what->{$action}) {
627 $what->{$action} = [];
629 push @{$what->{$action}}, $topic;
632 sub section_action {
633 my ($section) = @_;
634 if ($section) {
635 for ($section) {
636 return if (/^Graduated to/ || /^Discarded$/);
637 return $_ if (/^Stalled$/);
640 return "Undecided";
643 sub wildo_flush_topic {
644 my ($in_section, $what, $topic) = @_;
645 if (defined $topic) {
646 my $action = section_action($in_section);
647 if ($action) {
648 wildo_queue($what, $action, $topic);
653 sub wildo_match {
654 if (/^Will (?:\S+ ){0,2}(fast-track|hold|keep|merge|drop|discard|cook|kick|defer|be re-?rolled)[,. ]/ ||
655 /^Not urgent/ || /^Not ready/ || /^Waiting for / || /^Can wait in / ||
656 /^Needs? / || /^Expecting / || /^May want to /) {
657 return 1;
659 if (/^I think this is ready for /) {
660 return 1;
662 return 0;
665 sub wildo {
666 my $fd = shift;
667 my (%what, $topic, $last_merge_to_next, $in_section, $in_desc);
668 my $too_recent = '9999-99-99';
669 while (<$fd>) {
670 chomp;
672 if (/^\[(.*)\]$/) {
673 my $old_section = $in_section;
674 $in_section = $1;
675 wildo_flush_topic($old_section, \%what, $topic);
676 $topic = $in_desc = undef;
677 next;
680 if (/^\* (\S+) \(([-0-9]+)\) (\d+) commits?$/) {
681 wildo_flush_topic($in_section, \%what, $topic);
683 # tip-date, next-date, topic, count, pu-count
684 $topic = [$2, $too_recent, $1, $3, 0];
685 $in_desc = undef;
686 next;
689 if (defined $topic &&
690 ($topic->[1] eq $too_recent) &&
691 ($topic->[4] == 0) &&
692 (/^ \(merged to 'next' on ([-0-9]+)/)) {
693 $topic->[1] = $1;
695 if (defined $topic && /^ - /) {
696 $topic->[4]++;
699 if (defined $topic && /^$/) {
700 $in_desc = 1;
701 next;
704 next unless defined $topic && $in_desc;
706 s/^\s+//;
707 if (wildo_match($_)) {
708 wildo_queue(\%what, $_, $topic);
709 $topic = $in_desc = undef;
712 if (/Originally merged to 'next' on ([-0-9]+)/) {
713 $topic->[1] = $1;
716 wildo_flush_topic($in_section, \%what, $topic);
718 my $ipbl = "";
719 for my $what (sort keys %what) {
720 print "$ipbl$what\n";
721 for $topic (sort { (($a->[1] cmp $b->[1]) ||
722 ($a->[0] cmp $b->[0])) }
723 @{$what{$what}}) {
724 my ($tip, $next, $name, $count, $pu) = @$topic;
725 my ($sign);
726 $tip =~ s/^\d{4}-//;
727 if (($next eq $too_recent) || (0 < $pu)) {
728 $sign = "-";
729 $next = " " x 6;
730 } else {
731 $sign = "+";
732 $next =~ s|^\d{4}-|/|;
734 $count = "#$count";
735 printf " %s %-60s %s%s %5s\n", $sign, $name, $tip, $next, $count;
737 $ipbl = "\n";
741 ################################################################
742 # HavDone
743 sub havedone_show {
744 my $topic = shift;
745 my $str = shift;
746 my $prefix = " * ";
747 $str =~ s/\A\n+//;
748 $str =~ s/\n+\Z//;
750 print "($topic)\n";
751 for $str (split(/\n/, $str)) {
752 print "$prefix$str\n";
753 $prefix = " ";
757 sub havedone_count {
758 my @range = @_;
759 my $cnt = `git rev-list --count @range`;
760 chomp $cnt;
761 return $cnt;
764 sub havedone {
765 my $fh;
766 my %topic = ();
767 my @topic = ();
768 my ($topic, $to_maint, %to_maint, %merged, $in_desc);
769 if (!@ARGV) {
770 open($fh, '-|',
771 qw(git rev-list --first-parent -1 master Documentation/RelNotes RelNotes))
772 or die "$!: open rev-list";
773 my ($rev) = <$fh>;
774 close($fh) or die "$!: close rev-list";
775 chomp $rev;
776 @ARGV = ("$rev..master");
778 open($fh, '-|',
779 qw(git log --first-parent --oneline --reverse), @ARGV)
780 or die "$!: open log --first-parent";
781 while (<$fh>) {
782 my ($sha1, $branch) = /^([0-9a-f]+) Merge branch '(.*)'$/;
783 next unless $branch;
784 $topic{$branch} = "";
785 $merged{$branch} = $sha1;
786 push @topic, $branch;
788 close($fh) or die "$!: close log --first-parent";
789 open($fh, "<", "Meta/whats-cooking.txt")
790 or die "$!: open whats-cooking";
791 while (<$fh>) {
792 chomp;
793 if (/^\[(.*)\]$/) {
794 # section header
795 $in_desc = $topic = undef;
796 next;
798 if (/^\* (\S+) \([-0-9]+\) \d+ commits?$/) {
799 if (exists $topic{$1}) {
800 $topic = $1;
801 $to_maint = 0;
802 } else {
803 $in_desc = $topic = undef;
805 next;
807 if (defined $topic && /^$/) {
808 $in_desc = 1;
809 next;
812 next unless defined $topic && $in_desc;
814 s/^\s+//;
815 if (wildo_match($_)) {
816 next;
818 $topic{$topic} .= "$_\n";
820 close($fh) or die "$!: close whats-cooking";
822 for $topic (@topic) {
823 my $merged = $merged{$topic};
824 my $in_master = havedone_count("$merged^1..$merged^2");
825 my $not_in_maint = havedone_count("maint..$merged^2");
826 if ($in_master == $not_in_maint) {
827 $to_maint{$topic} = 1;
831 my $shown = 0;
832 for $topic (@topic) {
833 next if (exists $to_maint{$topic});
834 havedone_show($topic, $topic{$topic});
835 print "\n";
836 $shown++;
839 if ($shown) {
840 print "-" x 64, "\n";
843 for $topic (@topic) {
844 next unless (exists $to_maint{$topic});
845 havedone_show($topic, $topic{$topic});
846 my $sha1 = `git rev-parse --short $topic`;
847 chomp $sha1;
848 print " (merge $sha1 $topic later to maint).\n";
849 print "\n";
853 ################################################################
854 # WhatsCooking
856 sub doit {
857 my $topic = get_commit();
858 my $cooking = read_previous('Meta/whats-cooking.txt');
859 merge_cooking($cooking, $topic);
860 write_cooking('Meta/whats-cooking.txt', $cooking);
863 ################################################################
864 # Main
866 use Getopt::Long;
868 my ($wildo, $havedone);
869 if (!GetOptions("wildo" => \$wildo,
870 "havedone" => \$havedone)) {
871 print STDERR "$0 [--wildo|--havedone]\n";
872 exit 1;
875 if ($wildo) {
876 my $fd;
877 if (!@ARGV) {
878 open($fd, "<", "Meta/whats-cooking.txt");
879 } elsif (@ARGV != 1) {
880 print STDERR "$0 --wildo [filename|HEAD|-]\n";
881 exit 1;
882 } elsif ($ARGV[0] eq '-') {
883 $fd = \*STDIN;
884 } elsif ($ARGV[0] =~ /^HEAD/) {
885 open($fd, "-|",
886 qw(git --git-dir=Meta/.git cat-file -p),
887 "$ARGV[0]:whats-cooking.txt");
888 } else {
889 open($fd, "<", $ARGV[0]);
891 wildo($fd);
892 } elsif ($havedone) {
893 havedone();
894 } else {
895 doit();