+ my $git = try_git_pm($args->{git_dir});
+ each_recent_blob($args, sub {
+ my ($add) = @_;
+ add_to_feed($feed_opts, $feed, $add, $top, $git);
+ });
+ $feed->as_string;
+}
+
+sub generate_html_index {
+ my ($class, $args) = @_;
+ require Mail::Thread;
+
+ my $max = $args->{max} || MAX_PER_PAGE;
+ my $top = $args->{top}; # bool
+ local $ENV{GIT_DIR} = $args->{git_dir};
+ my $feed_opts = get_feedopts($args);
+ my $title = xs_html($feed_opts->{description} || "");
+ my @messages;
+ my $git = try_git_pm($args->{git_dir});
+ my ($first, $last) = each_recent_blob($args, sub {
+ my $simple = do_cat_mail($git, 'Email::Simple', $_[0])
+ or return 0;
+ if ($top && ($simple->header("In-Reply-To") ||
+ $simple->header("References"))) {
+ return 0;
+ }
+ $simple->body_set(""); # save some memory
+
+ my $t = eval { str2time($simple->header('Date')) };
+ defined($t) or $t = 0;
+ $simple->header_set('X-PI-Date', $t);
+ push @messages, $simple;
+ 1;
+ });
+
+ my $th = Mail::Thread->new(@messages);
+ $th->thread;
+ my @out = (
+ "<html><head><title>$title</title>" .
+ '<link rel=alternate title=Atom.feed href="' .
+ $feed_opts->{atomurl} . '" type="application/atom+xml"/>' .
+ '</head><body><pre>');
+ push @out, $feed_opts->{midurl};
+
+ # sort by date, most recent at top
+ $th->order(sub {
+ sort {
+ $b->topmost->message->header('X-PI-Date') <=>
+ $a->topmost->message->header('X-PI-Date')
+ } @_;
+ });
+ dump_html_line($_, 0, \@out) for $th->rootset;
+
+ my $footer = nav_footer($args->{cgi}, $first, $last);
+ $footer = "<hr /><pre>$footer</pre>" if $footer;
+ $out[0] . "</pre>$footer</html>";
+}
+
+# private subs
+
+sub nav_footer {
+ my ($cgi, $first, $last) = @_;
+ $cgi or return '';
+ my $old_r = $cgi->param('r');
+ my $prev = ' ';
+ my $next = ' ';
+ my %opts = (-path => 1, -query => 1, -relative => 1);
+
+ if ($last) {
+ $cgi->param('r', $last);
+ $next = $cgi->url(%opts);
+ $next = qq!<a href="$next">next</a>!;
+ }
+ if ($first && $old_r) {
+ $cgi->param('r', "$first..");
+ $prev = $cgi->url(%opts);
+ $prev = qq!<a href="$prev">prev</a>!;
+ }
+ "$prev $next";
+}
+
+sub each_recent_blob {
+ my ($args, $cb) = @_;
+ my $max = $args->{max} || MAX_PER_PAGE;
+ my $refhex = qr/[a-f0-9]{4,40}(?:~\d+)?/;
+ my $cgi = $args->{cgi};
+
+ # revision ranges may be specified
+ my $reverse;
+ my $range = 'HEAD';
+ my $r = $cgi->param('r') if $cgi;
+ if ($r) {
+ if ($r =~ /\A(?:$refhex\.\.)?$refhex\z/o) {
+ $range = $r;
+ } elsif ($r =~ /\A(?:$refhex\.\.)\z/o) {
+ $reverse = 1;
+ $range = $r;
+ }
+ }