v229
[gitweb.git] / gitweb.cgi
index f096f40d9342a73d5f37347686c35c9d6d6f9c11..0e2a9602c44fb6afe725c99af6a86894cbe0c6d7 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 
-# gitweb.pl - simple web interface to track changes in git repositories
+# gitweb - simple web interface to track changes in git repositories
 #
 # (C) 2005, Kay Sievers <kay.sievers@vrfy.org>
 # (C) 2005, Christian Gierke <ch@gierke.de>
@@ -15,7 +15,7 @@ use CGI::Carp qw(fatalsToBrowser);
 use Fcntl ':mode';
 
 my $cgi = new CGI;
-my $version =          "206";
+my $version =          "229";
 my $my_url =           $cgi->url();
 my $my_uri =           $cgi->url(-absolute => 1);
 my $rss_link = "";
@@ -49,9 +49,10 @@ if (defined $action) {
        if ($action eq "git-logo.png") {
                git_logo();
                exit;
+       } elsif ($action eq "opml") {
+               git_opml();
+               exit;
        }
-} else {
-       $action = "summary";
 }
 
 my $project = $cgi->param('p');
@@ -73,7 +74,7 @@ if (defined $project) {
                die_error(undef, "No such project.");
        }
        $rss_link = "<link rel=\"alternate\" title=\"$project log\" href=\"$my_uri?p=$project;a=rss\" type=\"application/rss+xml\"/>";
-       $ENV{'GIT_OBJECT_DIRECTORY'} = "$projectroot/$project/objects";
+       $ENV{'GIT_DIR'} = "$projectroot/$project";
 } else {
        git_project_list();
        exit;
@@ -92,9 +93,26 @@ if (defined $file_name) {
 }
 
 my $hash = $cgi->param('h');
-if (defined $hash && !($hash =~ m/^[0-9a-fA-F]{40}$/)) {
-       undef $hash;
-       die_error(undef, "Invalid hash parameter.");
+if (defined $hash) {
+       if (!($hash =~ m/^[0-9a-fA-F]{40}$/)) {
+               if ($hash =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+                       undef $hash;
+                       die_error(undef, "Non-canonical hash parameter.");
+               }
+               if ($hash =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~\:\!]/) {
+                       undef $hash;
+                       die_error(undef, "Invalid character in hash parameter.");
+               }
+               # replace branch-name with hash
+               my $branchlist = git_read_refs("refs/heads");
+               foreach my $entry (@$branchlist) {
+                       my %branch = %$entry;
+                       if ($branch{'name'} eq $hash) {
+                               $hash = $branch{'id'};
+                               last;
+                       }
+               }
+       }
 }
 
 my $hash_parent = $cgi->param('hp');
@@ -117,7 +135,6 @@ if (defined $page) {
        }
 }
 
-
 my $searchtext = $cgi->param('s');
 if (defined $searchtext) {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
@@ -127,7 +144,7 @@ if (defined $searchtext) {
        $searchtext = quotemeta $searchtext;
 }
 
-if ($action eq "summary") {
+if (!defined $action || $action eq "summary") {
        git_summary();
        exit;
 } elsif ($action eq "branches") {
@@ -198,6 +215,8 @@ sub git_header_html {
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
 <!-- git web interface v$version, (C) 2005, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke <ch\@gierke.de> -->
 <head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+<meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 $rss_link
 <style type="text/css">
@@ -228,16 +247,21 @@ div.log_link {
 }
 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
 a.list { text-decoration:none; color:#000000; }
-a.list:hover { color:#880000; }
+a.list:hover { text-decoration:underline; color:#880000; }
 table { padding:8px 4px; }
 th { padding:2px 5px; font-size:12px; text-align:left; }
+tr.light:hover { background-color:#edece6; }
+tr.dark { background-color:#f6f6f0; }
+tr.dark:hover { background-color:#edece6; }
 td { padding:2px 5px; font-size:12px; vertical-align:top; }
 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
-input.search { margin:4px 8px; position:absolute; top:56px; right:12px }
-a.rss_logo { float:right; padding:3px 0px; width:35px; line-height:10px;
+div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
+a.linenr { color:#999999; text-decoration:none }
+a.rss_logo {
+       float:right; padding:3px 0px; width:35px; line-height:10px;
        border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
        color:#ffffff; background-color:#ff6600;
        font-weight:bold; font-family:sans-serif; font-size:10px;
@@ -249,7 +273,7 @@ a.rss_logo:hover { background-color:#ee5500; }
 <body>
 EOF
        print "<div class=\"page_header\">\n" .
-             "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\">" .
+             "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
              "<img src=\"$my_uri?a=git-logo.png\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
              "</a>\n";
        print $cgi->a({-href => $home_link}, "projects") . " / ";
@@ -263,13 +287,13 @@ EOF
                        $searchtext = "";
                }
                $cgi->param("a", "search");
-               # post search form, but fake get parameter in browser
-               #print $cgi->startform(-name => "search", -action => "$my_uri",
-               #      -onsubmit => "document.search.action='?p=$project;a=search;s='+document.search.s.value") .
-               #      $cgi->hidden(-name => "p") . "\n" .
-               #      $cgi->hidden(-name => "a") . "\n" .
-               #      $cgi->textfield(-name => "s", -value => $searchtext, -class => "search") .
-               #      $cgi->end_form() . "\n";
+               print $cgi->startform(-method => "get", -action => "$my_uri") .
+                     "<div class=\"search\">\n" .
+                     $cgi->hidden(-name => "p") . "\n" .
+                     $cgi->hidden(-name => "a") . "\n" .
+                     $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+                     "</div>" .
+                     $cgi->end_form() . "\n";
        }
        print "</div>\n";
 }
@@ -282,6 +306,8 @@ sub git_footer_html {
                        print "<div class=\"page_footer_text\">" . escapeHTML($descr) . "</div>\n";
                }
                print $cgi->a({-href => "$my_uri?p=$project;a=rss", -class => "rss_logo"}, "RSS") . "\n";
+       } else {
+               print $cgi->a({-href => "$my_uri?a=opml", -class => "rss_logo"}, "RSS") . "\n";
        }
        print "</div>\n" .
              "</body>\n" .
@@ -452,6 +478,14 @@ sub git_read_commit {
        } else {
                $co{'age_string'} .= " right now";
        }
+       my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
+       if ($age > 60*60*24*7*2) {
+               $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon, $mday;
+               $co{'age_string_age'} = $co{'age_string'};
+       } else {
+               $co{'age_string_date'} = $co{'age_string'};
+               $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon, $mday;
+       }
        return %co;
 }
 
@@ -630,7 +664,7 @@ sub get_file_owner {
        return $owner;
 }
 
-sub git_project_list {
+sub git_read_projects {
        my @list;
 
        if (-d $projects_list) {
@@ -670,12 +704,15 @@ sub git_project_list {
                }
                close $fd;
        }
+       @list = sort {$a->{'path'} cmp $b->{'path'}} @list;
+       return @list;
+}
 
+sub git_project_list {
+       my @list = git_read_projects();
        if (!@list) {
                die_error(undef, "No project found.");
        }
-       @list = sort {$a->{'path'} cmp $b->{'path'}} @list;
-
        git_header_html();
        if (-f $home_text) {
                print "<div class=\"index_include\">\n";
@@ -699,7 +736,7 @@ sub git_project_list {
                if (!defined $head) {
                        next;
                }
-               $ENV{'GIT_OBJECT_DIRECTORY'} = "$projectroot/$proj{'path'}/objects";
+               $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
                my %co = git_read_commit($head);
                if (!%co) {
                        next;
@@ -711,9 +748,9 @@ sub git_project_list {
                        $proj{'owner'} = get_file_owner("$projectroot/$proj{'path'}") || "";
                }
                if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
+                       print "<tr class=\"dark\">\n";
                } else {
-                       print "<tr>\n";
+                       print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
                print "<td>" . $cgi->a({-href => "$my_uri?p=$proj{'path'};a=summary", -class => "list"}, escapeHTML($proj{'path'})) . "</td>\n" .
@@ -743,8 +780,23 @@ sub git_read_refs {
        my $ref_dir = shift;
        my @reflist;
 
+       my @refs;
        opendir my $dh, "$projectroot/$project/$ref_dir";
-       my @refs = grep !m/^\./, readdir $dh;
+       while (my $dir = readdir($dh)) {
+               if ($dir =~ m/^\./) {
+                       next;
+               }
+               if (-d "$projectroot/$project/$ref_dir/$dir") {
+                       opendir my $dh2, "$projectroot/$project/$ref_dir/$dir";
+                       my @subdirs = grep !m/^\./, readdir $dh2;
+                       closedir($dh2);
+                       foreach my $subdir (@subdirs) {
+                               push @refs, "$dir/$subdir"
+                       }
+                       next;
+               }
+               push @refs, $dir;
+       }
        closedir($dh);
        foreach my $ref_file (@refs) {
                my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
@@ -830,9 +882,9 @@ sub git_summary {
                my %co = git_read_commit($commit);
                my %ad = date_str($co{'author_epoch'});
                if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
+                       print "<tr class=\"dark\">\n";
                } else {
-                       print "<tr>\n";
+                       print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
                if ($i-- > 0) {
@@ -866,9 +918,9 @@ sub git_summary {
                foreach my $entry (@$taglist) {
                        my %tag = %$entry;
                        if ($alternate) {
-                               print "<tr style=\"background-color:#f6f5ed\">\n";
+                               print "<tr class=\"dark\">\n";
                        } else {
-                               print "<tr>\n";
+                               print "<tr class=\"light\">\n";
                        }
                        $alternate ^= 1;
                        if ($i-- > 0) {
@@ -905,20 +957,20 @@ sub git_summary {
                foreach my $entry (@$branchlist) {
                        my %tag = %$entry;
                        if ($alternate) {
-                               print "<tr style=\"background-color:#f6f5ed\">\n";
+                               print "<tr class=\"dark\">\n";
                        } else {
-                               print "<tr>\n";
+                               print "<tr class=\"light\">\n";
                        }
                        $alternate ^= 1;
                        if ($i-- > 0) {
                                print "<td><i>$tag{'age'}</i></td>\n" .
                                      "<td>" .
-                                     $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'id'}", -class => "list"},
+                                     $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'name'}", -class => "list"},
                                      "<b>" . escapeHTML($tag{'name'}) . "</b>") .
                                      "</td>\n" .
                                      "<td class=\"link\">" .
-                                     $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'id'}"}, "shortlog") .
-                                     " | " . $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'id'}"}, "log") .
+                                     $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'name'}"}, "shortlog") .
+                                     " | " . $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'name'}"}, "log") .
                                      "</td>\n" .
                                      "</tr>";
                        } else {
@@ -954,20 +1006,20 @@ sub git_tags {
                foreach my $entry (@$taglist) {
                        my %tag = %$entry;
                        if ($alternate) {
-                               print "<tr style=\"background-color:#f6f5ed\">\n";
+                               print "<tr class=\"dark\">\n";
                        } else {
-                               print "<tr>\n";
+                               print "<tr class=\"light\">\n";
                        }
                        $alternate ^= 1;
                        print "<td><i>$tag{'age'}</i></td>\n" .
                              "<td>" .
-                             $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'id'}", -class => "list"},
+                             $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'id'}", -class => "list"},
                              "<b>" . escapeHTML($tag{'name'}) . "</b>") .
                              "</td>\n" .
                              "<td class=\"link\">" .
                              $cgi->a({-href => "$my_uri?p=$project;a=$tag{'type'};h=$tag{'id'}"}, $tag{'type'});
                        if ($tag{'type'} eq "commit") {
-                             print " | " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'id'}"}, "shortlog") .
+                             print " | " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'name'}"}, "shortlog") .
                                    " | " . $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'id'}"}, "log");
                        }
                        print "</td>\n" .
@@ -1000,18 +1052,18 @@ sub git_branches {
                foreach my $entry (@$taglist) {
                        my %tag = %$entry;
                        if ($alternate) {
-                               print "<tr style=\"background-color:#f6f5ed\">\n";
+                               print "<tr class=\"dark\">\n";
                        } else {
-                               print "<tr>\n";
+                               print "<tr class=\"light\">\n";
                        }
                        $alternate ^= 1;
                        print "<td><i>$tag{'age'}</i></td>\n" .
                              "<td>" .
-                             $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'id'}", -class => "list"}, "<b>" . escapeHTML($tag{'name'}) . "</b>") .
+                             $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'name'}", -class => "list"}, "<b>" . escapeHTML($tag{'name'}) . "</b>") .
                              "</td>\n" .
                              "<td class=\"link\">" .
-                             $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'id'}"}, "shortog") .
-                             " | " . $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'id'}"}, "log") .
+                             $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$tag{'name'}"}, "shortlog") .
+                             " | " . $cgi->a({-href => "$my_uri?p=$project;a=log;h=$tag{'name'}"}, "log") .
                              "</td>\n" .
                              "</tr>";
                }
@@ -1090,7 +1142,7 @@ sub git_blob {
                                $line =~ s/\t/$spaces/;
                        }
                }
-               printf "<div class=\"pre\"><span style=\"color:#999999;\">%4i</span> %s</div>\n", $nr, escapeHTML($line);
+               printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, escapeHTML($line);
        }
        close $fd or print "Reading blob failed.\n";
        print "</div>";
@@ -1162,17 +1214,17 @@ sub git_tree {
                my $t_name = $4;
                $file_key = ";f=$base$t_name";
                if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
+                       print "<tr class=\"dark\">\n";
                } else {
-                       print "<tr>\n";
+                       print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
                print "<td style=\"font-family:monospace\">" . mode_str($t_mode) . "</td>\n";
                if ($t_type eq "blob") {
                        print "<td class=\"list\">" .
-                       $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$t_hash" . $base_key . $file_key, -class => "list"}, $t_name) .
-                       "</td>\n";
-                       print "<td class=\"link\">" .
+                             $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$t_hash" . $base_key . $file_key, -class => "list"}, $t_name) .
+                             "</td>\n" .
+                             "<td class=\"link\">" .
                              $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$t_hash" . $base_key . $file_key}, "blob") .
                              " | " . $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash_base" . $file_key}, "history") .
                              "</td>\n";
@@ -1180,7 +1232,9 @@ sub git_tree {
                        print "<td class=\"list\">" .
                              $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$t_hash" . $base_key . $file_key}, $t_name) .
                              "</td>\n" .
-                             "<td></td>\n";
+                             "<td class=\"link\">" .
+                             $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$t_hash" . $base_key . $file_key}, "tree") .
+                             "</td>\n";
                }
                print "</tr>\n";
        }
@@ -1200,7 +1254,7 @@ sub git_rss {
              "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
        print "<channel>\n";
        print "<title>$project</title>\n".
-             "<link>" . escapeHTML("$my_url/$project/log") . "</link>\n".
+             "<link>" . escapeHTML("$my_url?p=$project;a=summary") . "</link>\n".
              "<description>$project log</description>\n".
              "<language>en</language>\n";
 
@@ -1227,6 +1281,40 @@ sub git_rss {
        print "</channel></rss>";
 }
 
+sub git_opml {
+       my @list = git_read_projects();
+
+       print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
+             "<opml version=\"1.0\">\n".
+             "<head>".
+             "  <title>Git OPML Export</title>\n".
+             "</head>\n".
+             "<body>\n".
+             "<outline text=\"git RSS feeds\">\n";
+
+       foreach my $pr (@list) {
+               my %proj = %$pr;
+               my $head = git_read_hash("$proj{'path'}/HEAD");
+               if (!defined $head) {
+                       next;
+               }
+               $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
+               my %co = git_read_commit($head);
+               if (!%co) {
+                       next;
+               }
+
+               my $path = escapeHTML(chop_str($proj{'path'}, 25, 5));
+               my $rss =  "$my_url?p=$proj{'path'};a=rss";
+               my $html =  "$my_url?p=$proj{'path'};a=summary";
+               print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
+       }
+       print "</outline>\n".
+             "</body>\n".
+             "</opml>\n";
+}
+
 sub git_log {
        my $head = git_read_hash("$project/HEAD");
        if (!defined $hash) {
@@ -1250,17 +1338,19 @@ sub git_log {
        close $fd;
 
        if ($hash ne $head || $page) {
-               print $cgi->a({-href => "$my_uri?p=$project;a=shortlog"}, "HEAD");
+               print $cgi->a({-href => "$my_uri?p=$project;a=log"}, "HEAD");
        } else {
                print "HEAD";
        }
        if ($page > 0) {
-               print " &sdot; " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page-1), -accesskey => "p"}, "prev");
+               print " &sdot; " .
+               $cgi->a({-href => "$my_uri?p=$project;a=log;h=$hash;pg=" . ($page-1), -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                print " &sdot; prev";
        }
        if ($#revlist >= (100 * ($page+1)-1)) {
-               print " &sdot; " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page+1), -accesskey => "n"}, "next");
+               print " &sdot; " .
+               $cgi->a({-href => "$my_uri?p=$project;a=log;h=$hash;pg=" . ($page+1), -accesskey => "n", -title => "Alt-n"}, "next");
        } else {
                print " &sdot; next";
        }
@@ -1273,7 +1363,8 @@ sub git_log {
                my %co = git_read_commit($hash);
                print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
        }
-       foreach my $commit (@revlist) {
+       for (my $i = ($page * 100); $i <= $#revlist; $i++) {
+               my $commit = $revlist[$i];
                my %co = git_read_commit($commit);
                next if !%co;
                my %ad = date_str($co{'author_epoch'});
@@ -1429,9 +1520,9 @@ sub git_commit {
                my $file = $7;
                #print "$line ($status)<br/>\n";
                if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
+                       print "<tr class=\"dark\">\n";
                } else {
-                       print "<tr>\n";
+                       print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
                if ($status eq "N") {
@@ -1641,7 +1732,18 @@ sub git_commitdiff_plain {
        my (@difftree) = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading diff-tree failed.");
 
+       my %co = git_read_commit($hash);
+       my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
        print $cgi->header(-type => "text/plain", -charset => 'utf-8');
+       my $comment = $co{'comment'};
+       print "Author: $co{'author'}\n" .
+             "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
+             "Source: $my_url?p=$project;a=commitdiff;h=$hash\n" .
+             "\n";
+       foreach my $line (@$comment) {;
+               print "  $line\n";
+       }
+       print "\n";
        foreach my $line (@difftree) {
                $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
                my $from_id = $3;
@@ -1686,7 +1788,7 @@ sub git_history {
        print "<table cellspacing=\"0\">\n";
        my $alternate = 0;
        while (my $line = <$fd>) {
-               if ($line =~ m/^([0-9a-fA-F]{40}) /){
+               if ($line =~ m/^([0-9a-fA-F]{40})/){
                        $commit = $1;
                        next;
                }
@@ -1696,12 +1798,12 @@ sub git_history {
                                next;
                        }
                        if ($alternate) {
-                               print "<tr style=\"background-color:#f6f5ed\">\n";
+                               print "<tr class=\"dark\">\n";
                        } else {
-                               print "<tr>\n";
+                               print "<tr class=\"light\">\n";
                        }
                        $alternate ^= 1;
-                       print "<td><i>$co{'age_string'}</i></td>\n" .
+                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
                              "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
                              "<td>" . $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"}, "<b>" .
                              escapeHTML(chop_str($co{'title'}, 50)) . "</b>") . "</td>\n" .
@@ -1711,7 +1813,9 @@ sub git_history {
                        my $blob = git_get_hash_by_path($hash, $file_name);
                        my $blob_parent = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob && defined $blob_parent && $blob ne $blob_parent) {
-                               print " | " . $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name"}, "diff to current");
+                               print " | " .
+                               $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name"},
+                               "diff to current");
                        }
                        print "</td>\n" .
                              "</tr>\n";
@@ -1734,6 +1838,20 @@ sub git_search {
        if (!%co) {
                die_error(undef, "Unknown commit object.");
        }
+       # pickaxe may take all resources of your box and run for several minutes
+       # with every query - so decide by yourself how public you make this feature :)
+       my $commit_search = 1;
+       my $author_search = 0;
+       my $committer_search = 0;
+       my $pickaxe_search = 0;
+       if ($searchtext =~ s/^author\\://i) {
+               $author_search = 1;
+       } elsif ($searchtext =~ s/^committer\\://i) {
+               $committer_search = 1;
+       } elsif ($searchtext =~ s/^pickaxe\\://i) {
+               $commit_search = 0;
+               $pickaxe_search = 1;
+       }
        git_header_html();
        print "<div class=\"page_nav\">\n" .
              $cgi->a({-href => "$my_uri?p=$project;a=summary;h=$hash"}, "summary") .
@@ -1749,98 +1867,109 @@ sub git_search {
              $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" .
              "</div>\n";
        print "<table cellspacing=\"0\">\n";
-       $/ = "\0";
-       open my $fd, "-|", "$gitbin/git-rev-list --header $hash";
        my $alternate = 0;
-       while (my $commit_text = <$fd>) {
-               if (!grep m/$searchtext/, $commit_text) {
-                       next;
-               }
-               my @commit_lines = split "\n", $commit_text;
-               my $commit = shift @commit_lines;
-               my %co = git_read_commit($commit, \@commit_lines);
-               if (!%co) {
-                       next;
-               }
-               if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
-               } else {
-                       print "<tr>\n";
-               }
-               $alternate ^= 1;
-               print "<td><i>$co{'age_string'}</i></td>\n" .
-                     "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
-                     "<td>" .
-                     $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"}, "<b>" . escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
-               my $comment = $co{'comment'};
-               foreach my $line (@$comment) {
-                       if ($line =~ m/^(.*)($searchtext)(.*)$/) {
-                               my $lead = escapeHTML($1) || "";
-                               $lead = chop_str($lead, 30, 10);
-                               my $match = escapeHTML($2) || "";
-                               my $trail = escapeHTML($3) || "";
-                               $trail = chop_str($trail, 30, 10);
-                               my $text = "$lead<span style=\"color:#e00000\">$match</span>$trail";
-                               print chop_str($text, 80, 5) . "<br/>\n";
+       if ($commit_search) {
+               $/ = "\0";
+               open my $fd, "-|", "$gitbin/git-rev-list --header $hash";
+               while (my $commit_text = <$fd>) {
+                       if (!grep m/$searchtext/i, $commit_text) {
+                               next;
                        }
-               }
-               print "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") .
-                     " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$commit"}, "tree");
-               print "</td>\n" .
-                     "</tr>\n";
-       }
-       close $fd;
-
-       $/ = "\n";
-       open $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin -S$searchtext";
-       undef %co;
-       my @files;
-       while (my $line = <$fd>) {
-               if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
-                       my %set;
-                       $set{'file'} = $6;
-                       $set{'from_id'} = $3;
-                       $set{'to_id'} = $4;
-                       $set{'id'} = $set{'to_id'};
-                       if ($set{'id'} =~ m/0{40}/) {
-                               $set{'id'} = $set{'from_id'};
+                       if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+                               next;
+                       }
+                       if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+                               next;
                        }
-                       if ($set{'id'} =~ m/0{40}/) {
+                       my @commit_lines = split "\n", $commit_text;
+                       my $commit = shift @commit_lines;
+                       my %co = git_read_commit($commit, \@commit_lines);
+                       if (!%co) {
                                next;
                        }
-                       push @files, \%set;
-               } elsif ($line =~ m/^([0-9a-fA-F]{40}) /){
-                       if (%co) {
-                               if ($alternate) {
-                                       print "<tr style=\"background-color:#f6f5ed\">\n";
-                               } else {
-                                       print "<tr>\n";
+                       if ($alternate) {
+                               print "<tr class=\"dark\">\n";
+                       } else {
+                               print "<tr class=\"light\">\n";
+                       }
+                       $alternate ^= 1;
+                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                             "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                             "<td>" .
+                             $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"}, "<b>" . escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
+                       my $comment = $co{'comment'};
+                       foreach my $line (@$comment) {
+                               if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+                                       my $lead = escapeHTML($1) || "";
+                                       $lead = chop_str($lead, 30, 10);
+                                       my $match = escapeHTML($2) || "";
+                                       my $trail = escapeHTML($3) || "";
+                                       $trail = chop_str($trail, 30, 10);
+                                       my $text = "$lead<span style=\"color:#e00000\">$match</span>$trail";
+                                       print chop_str($text, 80, 5) . "<br/>\n";
                                }
-                               $alternate ^= 1;
-                               print "<td><i>$co{'age_string'}</i></td>\n" .
-                                     "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
-                                     "<td>" .
-                                     $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}", -class => "list"}, "<b>" .
-                                     escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
-                               while (my $setref = shift @files) {
-                                       my %set = %$setref;
-                                       print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}", class => "list"},
-                                       escapeHTML($set{'file'})) . "<br/>\n";
+                       }
+                       print "</td>\n" .
+                             "<td class=\"link\">" .
+                             $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") .
+                             " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$commit"}, "tree");
+                       print "</td>\n" .
+                             "</tr>\n";
+               }
+               close $fd;
+       }
+
+       if ($pickaxe_search) {
+               $/ = "\n";
+               open my $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin -S$searchtext";
+               undef %co;
+               my @files;
+               while (my $line = <$fd>) {
+                       if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
+                               my %set;
+                               $set{'file'} = $6;
+                               $set{'from_id'} = $3;
+                               $set{'to_id'} = $4;
+                               $set{'id'} = $set{'to_id'};
+                               if ($set{'id'} =~ m/0{40}/) {
+                                       $set{'id'} = $set{'from_id'};
                                }
-                               print "</td>\n" .
-                                     "<td class=\"link\">" .
-                                     $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}"}, "commit") .
-                                     " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}"}, "tree");
-                               print "</td>\n" .
-                                     "</tr>\n";
+                               if ($set{'id'} =~ m/0{40}/) {
+                                       next;
+                               }
+                               push @files, \%set;
+                       } elsif ($line =~ m/^([0-9a-fA-F]{40}) /){
+                               if (%co) {
+                                       if ($alternate) {
+                                               print "<tr class=\"dark\">\n";
+                                       } else {
+                                               print "<tr class=\"light\">\n";
+                                       }
+                                       $alternate ^= 1;
+                                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                                             "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                                             "<td>" .
+                                             $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}", -class => "list"}, "<b>" .
+                                             escapeHTML(chop_str($co{'title'}, 50)) . "</b><br/>");
+                                       while (my $setref = shift @files) {
+                                               my %set = %$setref;
+                                               print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}", class => "list"},
+                                                     "<span style=\"color:#e00000\">" . escapeHTML($set{'file'}) . "</span>") .
+                                                     "<br/>\n";
+                                       }
+                                       print "</td>\n" .
+                                             "<td class=\"link\">" .
+                                             $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$co{'id'}"}, "commit") .
+                                             " | " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}"}, "tree");
+                                       print "</td>\n" .
+                                             "</tr>\n";
+                               }
+                               %co = git_read_commit($1);
                        }
-                       %co = git_read_commit($1);
                }
+               close $fd;
        }
        print "</table>\n";
-       close $fd;
        git_footer_html();
 }
 
@@ -1872,12 +2001,14 @@ sub git_shortlog {
                print "HEAD";
        }
        if ($page > 0) {
-               print " &sdot; " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page-1), -accesskey => "p"}, "prev");
+               print " &sdot; " .
+               $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page-1), -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                print " &sdot; prev";
        }
        if ($#revlist >= (100 * ($page+1)-1)) {
-               print " &sdot; " . $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page+1), -accesskey => "n"}, "next");
+               print " &sdot; " .
+               $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page+1), -accesskey => "n", -title => "Alt-n"}, "next");
        } else {
                print " &sdot; next";
        }
@@ -1893,12 +2024,12 @@ sub git_shortlog {
                my %co = git_read_commit($commit);
                my %ad = date_str($co{'author_epoch'});
                if ($alternate) {
-                       print "<tr style=\"background-color:#f6f5ed\">\n";
+                       print "<tr class=\"dark\">\n";
                } else {
-                       print "<tr>\n";
+                       print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td><i>$co{'age_string'}</i></td>\n" .
+               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
                      "<td><i>" . escapeHTML(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
                      "<td>" . $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "list"}, "<b>" .
                      escapeHTML($co{'title_short'}) . "</b>") . "</td>\n" .
@@ -1908,6 +2039,13 @@ sub git_shortlog {
                      "</td>\n" .
                      "</tr>";
        }
+       if ($#revlist >= (100 * ($page+1)-1)) {
+               print "<tr>\n" .
+                     "<td>" .
+                     $cgi->a({-href => "$my_uri?p=$project;a=shortlog;h=$hash;pg=" . ($page+1), -title => "Alt-n"}, "next") .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
        print "</table\n>";
        git_footer_html();
 }