]> Lady’s Gitweb - Gitweb/blobdiff - gitweb.perl
gitweb: Support for 'forks'
[Gitweb] / gitweb.perl
index de893cccb5dc6b253941237eab46a63b448dcd0dd44731bb8c2ba33745fc6926..4c1e54d893bd71ebe93a3672744283fe138384d126444504552332079d48ecde 100755 (executable)
@@ -39,7 +39,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
-our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+our $site_name = "++GITWEB_SITENAME++"
+                 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
 
 # filename of html text to include at top of each page
 our $site_header = "++GITWEB_SITE_HEADER++";
@@ -54,11 +55,17 @@ our $stylesheet;
 # default is not to define style sheet, but it can be overwritten later
 undef $stylesheet;
 
-# URI of GIT logo
+# URI of GIT logo (72x27 size)
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
 our $favicon = "++GITWEB_FAVICON++";
 
+# URI and label (title) of GIT logo link
+#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
+#our $logo_label = "git documentation";
+our $logo_url = "http://git.or.cz/";
+our $logo_label = "git homepage";
+
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
@@ -95,25 +102,81 @@ our %feature = (
        #
        # use gitweb_check_feature(<feature>) to check if <feature> is enabled
 
+       # Enable the 'blame' blob view, showing the last commit that modified
+       # each line in the file. This can be very CPU-intensive.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'blame'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'blame'}{'override'} = 1;
+       # and in project config gitweb.blame = 0|1;
        'blame' => {
                'sub' => \&feature_blame,
                'override' => 0,
                'default' => [0]},
 
+       # Enable the 'snapshot' link, providing a compressed tarball of any
+       # tree. This can potentially generate high traffic if you have large
+       # project.
+
+       # To disable system wide have in $GITWEB_CONFIG
+       # $feature{'snapshot'}{'default'} = [undef];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'blame'}{'override'} = 1;
+       # and in project config gitweb.snapshot = none|gzip|bzip2;
        'snapshot' => {
                'sub' => \&feature_snapshot,
                'override' => 0,
                #         => [content-encoding, suffix, program]
                'default' => ['x-gzip', 'gz', 'gzip']},
 
+       # Enable the pickaxe search, which will list the commits that modified
+       # a given string in a file. This can be practical and quite faster
+       # alternative to 'blame', but still potentially CPU-intensive.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'pickaxe'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'pickaxe'}{'override'} = 1;
+       # and in project config gitweb.pickaxe = 0|1;
        'pickaxe' => {
                'sub' => \&feature_pickaxe,
                'override' => 0,
                'default' => [1]},
 
+       # Make gitweb use an alternative format of the URLs which can be
+       # more readable and natural-looking: project name is embedded
+       # directly in the path and the query string contains other
+       # auxiliary information. All gitweb installations recognize
+       # URL in either format; this configures in which formats gitweb
+       # generates links.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'pathinfo'}{'default'} = [1];
+       # Project specific override is not supported.
+
+       # Note that you will need to change the default location of CSS,
+       # favicon, logo and possibly other files to an absolute URL. Also,
+       # if gitweb.cgi serves as your indexfile, you will need to force
+       # $my_uri to contain the script name in your $GITWEB_CONFIG.
        'pathinfo' => {
                'override' => 0,
                'default' => [0]},
+
+       # Make gitweb consider projects in project root subdirectories
+       # to be forks of existing projects. Given project $projname.git,
+       # projects matching $projname/*.git will not be shown in the main
+       # projects list, instead a '+' mark will be added to $projname
+       # there and a 'forks' view will be enabled for the project, listing
+       # all the forks. This feature is supported only if project list
+       # is taken from a directory, not file.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'forks'}{'default'} = [1];
+       # Project specific override is not supported.
+       'forks' => {
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_check_feature {
@@ -131,12 +194,6 @@ sub gitweb_check_feature {
        return $sub->(@defaults);
 }
 
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'blame'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config gitweb.blame = 0|1;
-
 sub feature_blame {
        my ($val) = git_get_project_config('blame', '--bool');
 
@@ -149,12 +206,6 @@ sub feature_blame {
        return $_[0];
 }
 
-# To disable system wide have in $GITWEB_CONFIG
-# $feature{'snapshot'}{'default'} = [undef];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config  gitweb.snapshot = none|gzip|bzip2
-
 sub feature_snapshot {
        my ($ctype, $suffix, $command) = @_;
 
@@ -178,12 +229,6 @@ sub gitweb_have_snapshot {
        return $have_snapshot;
 }
 
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'override'} = 1;
-# and in project config gitweb.pickaxe = 0|1;
-
 sub feature_pickaxe {
        my ($val) = git_get_project_config('pickaxe', '--bool');
 
@@ -311,6 +356,13 @@ if (defined $searchtext) {
        $searchtext = quotemeta $searchtext;
 }
 
+our $searchtype = $cgi->param('st');
+if (defined $searchtype) {
+       if ($searchtype =~ m/[^a-z]/) {
+               die_error(undef, "Invalid searchtype parameter");
+       }
+}
+
 # now read PATH_INFO and use it as alternative to parameters
 sub evaluate_path_info {
        return if defined $project;
@@ -370,11 +422,13 @@ my %actions = (
        "commitdiff" => \&git_commitdiff,
        "commitdiff_plain" => \&git_commitdiff_plain,
        "commit" => \&git_commit,
+       "forks" => \&git_forks,
        "heads" => \&git_heads,
        "history" => \&git_history,
        "log" => \&git_log,
        "rss" => \&git_rss,
        "search" => \&git_search,
+       "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
        "summary" => \&git_summary,
        "tag" => \&git_tag,
@@ -409,6 +463,9 @@ sub href(%) {
        my %params = @_;
        my $href = $my_uri;
 
+       # XXX: Warning: If you touch this, check the search form for updating,
+       # too.
+
        my @mapping = (
                project => "p",
                action => "a",
@@ -421,6 +478,7 @@ sub href(%) {
                page => "pg",
                order => "o",
                searchtext => "s",
+               searchtype => "st",
        );
        my %mapping = @mapping;
 
@@ -488,6 +546,12 @@ sub validate_refname {
        return $input;
 }
 
+# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT);
+sub to_utf8 {
+       my $str = shift;
+       return decode("utf8", $str, Encode::FB_DEFAULT);
+}
+
 # quote unsafe chars, but keep the slash, even when it's not
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
@@ -510,7 +574,7 @@ sub esc_url {
 # replace invalid utf8 character with SUBSTITUTION sequence
 sub esc_html {
        my $str = shift;
-       $str = decode("utf8", $str, Encode::FB_DEFAULT);
+       $str = to_utf8($str);
        $str = escapeHTML($str);
        $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
        $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
@@ -713,7 +777,7 @@ sub format_subject_html {
 
        if (length($short) < length($long)) {
                return $cgi->a({-href => $href, -class => "list subject",
-                               -title => decode("utf8", $long, Encode::FB_DEFAULT)},
+                               -title => to_utf8($long)},
                       esc_html($short) . $extra);
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
@@ -846,13 +910,19 @@ sub git_get_project_url_list {
 }
 
 sub git_get_projects_list {
+       my ($filter) = @_;
        my @list;
 
+       $filter ||= '';
+       $filter =~ s/\.git$//;
+
        if (-d $projects_list) {
                # search in directory
-               my $dir = $projects_list;
+               my $dir = $projects_list . ($filter ? "/$filter" : '');
                my $pfxlen = length("$dir");
 
+               my $check_forks = gitweb_check_feature('forks');
+
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
@@ -864,8 +934,10 @@ sub git_get_projects_list {
 
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
-                               if (check_export_ok("$projectroot/$subdir")) {
-                                       push @list, { path => $subdir };
+                               if ($check_forks and $subdir =~ m#/.#) {
+                                       $File::Find::prune = 1;
+                               } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+                                       push @list, { path => ($filter ? "$filter/" : '') . $subdir };
                                        $File::Find::prune = 1;
                                }
                        },
@@ -888,7 +960,7 @@ sub git_get_projects_list {
                        if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
                                        path => $path,
-                                       owner => decode("utf8", $owner, Encode::FB_DEFAULT),
+                                       owner => to_utf8($owner),
                                };
                                push @list, $pr
                        }
@@ -917,7 +989,7 @@ sub git_get_project_owner {
                        $pr = unescape($pr);
                        $ow = unescape($ow);
                        if ($pr eq $project) {
-                               $owner = decode("utf8", $ow, Encode::FB_DEFAULT);
+                               $owner = to_utf8($ow);
                                last;
                        }
                }
@@ -995,6 +1067,9 @@ sub parse_date {
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
+       $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+                                  1900+$year, $mon+1, $mday,
+                                  $hour, $min, $sec, $tz);
        return %date;
 }
 
@@ -1033,6 +1108,24 @@ sub parse_tag {
        return %tag
 }
 
+sub git_get_last_activity {
+       my ($path) = @_;
+       my $fd;
+
+       $git_dir = "$projectroot/$path";
+       open($fd, "-|", git_cmd(), 'for-each-ref',
+            '--format=%(refname) %(committer)',
+            '--sort=-committerdate',
+            'refs/heads') or return;
+       my $most_recent = <$fd>;
+       close $fd or return;
+       if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+               my $timestamp = $1;
+               my $age = time - $timestamp;
+               return ($age, age_string($age));
+       }
+}
+
 sub parse_commit {
        my $commit_id = shift;
        my $commit_text = shift;
@@ -1043,12 +1136,11 @@ sub parse_commit {
        if (defined $commit_text) {
                @commit_lines = @$commit_text;
        } else {
-               $/ = "\0";
+               local $/ = "\0";
                open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id
                        or return;
                @commit_lines = split '\n', <$fd>;
                close $fd or return;
-               $/ = "\n";
                pop @commit_lines;
        }
        my $header = shift @commit_lines;
@@ -1108,6 +1200,9 @@ sub parse_commit {
                        last;
                }
        }
+       if ($co{'title'} eq "") {
+               $co{'title'} = $co{'title_short'} = '(no commit message)';
+       }
        # remove added spaces
        foreach my $line (@commit_lines) {
                $line =~ s/^    //;
@@ -1279,7 +1374,7 @@ sub get_file_owner {
        }
        my $owner = $gcos;
        $owner =~ s/[,;].*$//;
-       return decode("utf8", $owner, Encode::FB_DEFAULT);
+       return to_utf8($owner);
 }
 
 ## ......................................................................
@@ -1360,7 +1455,7 @@ sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
 
-       my $title = "$site_name git";
+       my $title = "$site_name";
        if (defined $project) {
                $title .= " - $project";
                if (defined $action) {
@@ -1435,9 +1530,9 @@ EOF
        }
 
        print "<div class=\"page_header\">\n" .
-             "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
-             "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
-             "</a>\n";
+             $cgi->a({-href => esc_url($logo_url),
+                      -title => $logo_label},
+                     qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        if (defined $project) {
                print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
@@ -1458,11 +1553,16 @@ EOF
                }
                $cgi->param("a", "search");
                $cgi->param("h", $search_hash);
+               $cgi->param("p", $project);
                print $cgi->startform(-method => "get", -action => $my_uri) .
                      "<div class=\"search\">\n" .
                      $cgi->hidden(-name => "p") . "\n" .
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
+                     $cgi->popup_menu(-name => 'st', -default => 'commit',
+                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
+                     $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+                     " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
                      "</div>" .
                      $cgi->end_form() . "\n";
@@ -1618,17 +1718,16 @@ sub git_print_page_path {
        my $type = shift;
        my $hb = shift;
 
-       if (!defined $name) {
-               print "<div class=\"page_path\">/</div>\n";
-       } else {
+
+       print "<div class=\"page_path\">";
+       print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+                     -title => 'tree root'}, "[$project]");
+       print " / ";
+       if (defined $name) {
                my @dirname = split '/', $name;
                my $basename = pop @dirname;
                my $fullname = '';
 
-               print "<div class=\"page_path\">";
-               print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                             -title => 'tree root'}, "[$project]");
-               print " / ";
                foreach my $dir (@dirname) {
                        $fullname .= ($fullname ? '/' : '') . $dir;
                        print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
@@ -1644,11 +1743,12 @@ sub git_print_page_path {
                        print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
                                                     hash_base=>$hb),
                                      -title => $name}, esc_html($basename));
+                       print " / ";
                } else {
                        print esc_html($basename);
                }
-               print "<br/></div>\n";
        }
+       print "<br/></div>\n";
 }
 
 # sub git_print_log (\@;%) {
@@ -1701,15 +1801,6 @@ sub git_print_log ($;%) {
        }
 }
 
-sub git_print_simplified_log {
-       my $log = shift;
-       my $remove_title = shift;
-
-       git_print_log($log,
-               -final_empty_line=> 1,
-               -remove_title => $remove_title);
-}
-
 # print tree entry (row of git_tree), but without encompassing <tr> element
 sub git_print_tree_entry {
        my ($t, $basedir, $hash_base, $have_blame) = @_;
@@ -1725,26 +1816,28 @@ sub git_print_tree_entry {
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                              file_name=>"$basedir$t->{'name'}", %base_key),
-                                -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+                                              file_name=>"$basedir$t->{'name'}", %base_key),
+                               -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "blob");
                if ($have_blame) {
-                       print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
-                                                    file_name=>"$basedir$t->{'name'}", %base_key)},
-                                     "blame");
+                       print " | " .
+                             $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+                                                          file_name=>"$basedir$t->{'name'}", %base_key)},
+                                           "blame");
                }
                if (defined $hash_base) {
-                       if ($have_blame) {
-                               print " | ";
-                       }
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
                print " | " .
                        $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
-                                              file_name=>"$basedir$t->{'name'}")},
-                               "raw");
+                                              file_name=>"$basedir$t->{'name'}")},
+                               "raw");
                print "</td>\n";
 
        } elsif ($t->{'type'} eq "tree") {
@@ -1754,8 +1847,12 @@ sub git_print_tree_entry {
                              esc_html($t->{'name'}));
                print "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "tree");
                if (defined $hash_base) {
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
@@ -1812,7 +1909,7 @@ sub git_difftree_body {
                        print "<td>";
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
                                                     hash_base=>$hash, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_html($diff{'file'}));
+                                     -class => "list"}, esc_html($diff{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
@@ -1838,12 +1935,15 @@ sub git_difftree_body {
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
-                                                    file_name=>$diff{'file'})},
-                                     "blame") . " | ";
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'file'})},
-                                     "history");
+                                                    file_name=>$diff{'file'})},
+                                     "history");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
@@ -1864,8 +1964,8 @@ sub git_difftree_body {
                        }
                        print "<td>";
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_html($diff{'file'}));
+                                                    hash_base=>$hash, file_name=>$diff{'file'}),
+                                     -class => "list"}, esc_html($diff{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chnge</td>\n";
                        print "<td class=\"link\">";
@@ -1876,19 +1976,22 @@ sub git_difftree_body {
                                        print $cgi->a({-href => "#patch$patchno"}, "patch");
                                } else {
                                        print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'file'})},
-                                                     "diff");
+                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                                    hash_base=>$hash, hash_parent_base=>$parent,
+                                                                    file_name=>$diff{'file'})},
+                                                     "diff");
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
-                                                    file_name=>$diff{'file'})},
-                                     "blame") . " | ";
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
-                                                    file_name=>$diff{'file'})},
-                                     "history");
+                                                    file_name=>$diff{'file'})},
+                                     "history");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
@@ -1916,19 +2019,22 @@ sub git_difftree_body {
                                        print $cgi->a({-href => "#patch$patchno"}, "patch");
                                } else {
                                        print $cgi->a({-href => href(action=>"blobdiff",
-                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                    hash_base=>$hash, hash_parent_base=>$parent,
-                                                                    file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
-                                                     "diff");
+                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                                    hash_base=>$hash, hash_parent_base=>$parent,
+                                                                    file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+                                                     "diff");
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'from_file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
-                                                    file_name=>$diff{'from_file'})},
-                                     "blame") . " | ";
+                                                    file_name=>$diff{'from_file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'from_file'})},
-                                     "history");
+                                                   file_name=>$diff{'from_file'})},
+                                     "history");
                        print "</td>\n";
 
                } # we should not encounter Unmerged (U) or Unknown (X) status
@@ -1980,14 +2086,14 @@ sub git_patchset_body {
                                print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
                                      $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
                                                             hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
-                                             $diffinfo->{'to_id'}) . "(new)" .
+                                             $diffinfo->{'to_id'}) . " (new)" .
                                      "</div>\n"; # class="diff_info"
 
                        } elsif ($diffinfo->{'status'} eq "D") { # deleted
                                print "<div class=\"diff_info\">" . file_type($diffinfo->{'from_mode'}) . ":" .
                                      $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
                                                             hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
-                                             $diffinfo->{'from_id'}) . "(deleted)" .
+                                             $diffinfo->{'from_id'}) . " (deleted)" .
                                      "</div>\n"; # class="diff_info"
 
                        } elsif ($diffinfo->{'status'} eq "R" || # renamed
@@ -2062,6 +2168,124 @@ sub git_patchset_body {
 
 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
+sub git_project_list_body {
+       my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+       my $check_forks = gitweb_check_feature('forks');
+
+       my @projects;
+       foreach my $pr (@$projlist) {
+               my (@aa) = git_get_last_activity($pr->{'path'});
+               unless (@aa) {
+                       next;
+               }
+               ($pr->{'age'}, $pr->{'age_string'}) = @aa;
+               if (!defined $pr->{'descr'}) {
+                       my $descr = git_get_project_description($pr->{'path'}) || "";
+                       $pr->{'descr'} = chop_str($descr, 25, 5);
+               }
+               if (!defined $pr->{'owner'}) {
+                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+               }
+               if ($check_forks) {
+                       my $pname = $pr->{'path'};
+                       $pname =~ s/\.git$//;
+                       $pr->{'forks'} = -d "$projectroot/$pname";
+               }
+               push @projects, $pr;
+       }
+
+       $order ||= "project";
+       $from = 0 unless defined $from;
+       $to = $#projects if (!defined $to || $#projects < $to);
+
+       print "<table class=\"project_list\">\n";
+       unless ($no_header) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<th></th>\n";
+               }
+               if ($order eq "project") {
+                       @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+                       print "<th>Project</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'project'),
+                                      -class => "header"}, "Project") .
+                             "</th>\n";
+               }
+               if ($order eq "descr") {
+                       @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+                       print "<th>Description</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'descr'),
+                                      -class => "header"}, "Description") .
+                             "</th>\n";
+               }
+               if ($order eq "owner") {
+                       @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+                       print "<th>Owner</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'owner'),
+                                      -class => "header"}, "Owner") .
+                             "</th>\n";
+               }
+               if ($order eq "age") {
+                       @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+                       print "<th>Last Change</th>\n";
+               } else {
+                       print "<th>" .
+                             $cgi->a({-href => href(project=>undef, order=>'age'),
+                                      -class => "header"}, "Last Change") .
+                             "</th>\n";
+               }
+               print "<th></th>\n" .
+                     "</tr>\n";
+       }
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my $pr = $projects[$i];
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               if ($check_forks) {
+                       print "<td>";
+                       if ($pr->{'forks'}) {
+                               print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+                       }
+                       print "</td>\n";
+               }
+               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+                     "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                     $pr->{'age_string'} . "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
+                     $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+                     ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+                     "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n";
+               if ($check_forks) {
+                       print "<td></td>\n";
+               }
+               print "<td colspan=\"5\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 sub git_shortlog_body {
        # uses global variable $project
        my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -2090,9 +2314,12 @@ sub git_shortlog_body {
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " .
-                     $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
+               if (gitweb_have_snapshot()) {
+                       print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+               }
                print "</td>\n" .
                      "</tr>\n";
        }
@@ -2277,30 +2504,9 @@ sub git_project_list {
        }
 
        my @list = git_get_projects_list();
-       my @projects;
        if (!@list) {
                die_error(undef, "No projects found");
        }
-       foreach my $pr (@list) {
-               my $head = git_get_head_hash($pr->{'path'});
-               if (!defined $head) {
-                       next;
-               }
-               $git_dir = "$projectroot/$pr->{'path'}";
-               my %co = parse_commit($head);
-               if (!%co) {
-                       next;
-               }
-               $pr->{'commit'} = \%co;
-               if (!defined $pr->{'descr'}) {
-                       my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr'} = chop_str($descr, 25, 5);
-               }
-               if (!defined $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
-               }
-               push @projects, $pr;
-       }
 
        git_header_html();
        if (-f $home_text) {
@@ -2310,75 +2516,30 @@ sub git_project_list {
                close $fd;
                print "</div>\n";
        }
-       print "<table class=\"project_list\">\n" .
-             "<tr>\n";
-       $order ||= "project";
-       if ($order eq "project") {
-               @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
-               print "<th>Project</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'project'),
-                              -class => "header"}, "Project") .
-                     "</th>\n";
-       }
-       if ($order eq "descr") {
-               @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
-               print "<th>Description</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'descr'),
-                              -class => "header"}, "Description") .
-                     "</th>\n";
-       }
-       if ($order eq "owner") {
-               @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
-               print "<th>Owner</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'owner'),
-                              -class => "header"}, "Owner") .
-                     "</th>\n";
-       }
-       if ($order eq "age") {
-               @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
-               print "<th>Last Change</th>\n";
-       } else {
-               print "<th>" .
-                     $cgi->a({-href => href(project=>undef, order=>'age'),
-                              -class => "header"}, "Last Change") .
-                     "</th>\n";
+       git_project_list_body(\@list, $order);
+       git_footer_html();
+}
+
+sub git_forks {
+       my $order = $cgi->param('o');
+       if (defined $order && $order !~ m/project|descr|owner|age/) {
+               die_error(undef, "Unknown order parameter");
        }
-       print "<th></th>\n" .
-             "</tr>\n";
-       my $alternate = 1;
-       foreach my $pr (@projects) {
-               if ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-               print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
-                     "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
-                     $pr->{'commit'}{'age_string'} . "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
-                     $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
-                     "</td>\n" .
-                     "</tr>\n";
+
+       my @list = git_get_projects_list($project);
+       if (!@list) {
+               die_error(undef, "No forks found");
        }
-       print "</table>\n";
+
+       git_header_html();
+       git_print_page_nav('','');
+       git_print_header_div('summary', "$project forks");
+       git_project_list_body(\@list, $order);
        git_footer_html();
 }
 
 sub git_project_index {
-       my @projects = git_get_projects_list();
+       my @projects = git_get_projects_list($project);
 
        print $cgi->header(
                -type => 'text/plain',
@@ -2421,6 +2582,10 @@ sub git_summary {
                        push @taglist, $ref;
                }
        }
+       my @forklist;
+       if (gitweb_check_feature('forks')) {
+               @forklist = git_get_projects_list($project);
+       }
 
        git_header_html();
        git_print_page_nav('summary','', $head);
@@ -2442,6 +2607,14 @@ sub git_summary {
        }
        print "</table>\n";
 
+       if (-s "$projectroot/$project/README.html") {
+               if (open my $fd, "$projectroot/$project/README.html") {
+                       print "<div class=\"title\">readme</div>\n";
+                       print $_ while (<$fd>);
+                       close $fd;
+               }
+       }
+
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
                git_get_head_hash($project)
                or die_error(undef, "Open git-rev-list failed");
@@ -2463,6 +2636,13 @@ sub git_summary {
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
+       if (@forklist) {
+               git_print_header_div('forks');
+               git_project_list_body(\@forklist, undef, 0, 15,
+                                     $cgi->a({-href => href(action=>"forks")}, "..."),
+                                     'noheader');
+       }
+
        git_footer_html();
 }
 
@@ -2520,7 +2700,8 @@ sub git_blame2 {
        if ($ftype !~ "blob") {
                die_error("400 Bad Request", "Object is not a blob");
        }
-       open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
+       open ($fd, "-|", git_cmd(), "blame", '-p', '--',
+             $file_name, $hash_base)
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
@@ -2544,33 +2725,52 @@ sub git_blame2 {
 <table class="blame">
 <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
 HTML
-       while (<$fd>) {
-               my ($full_rev, $author, $date, $lineno, $data) =
-                       /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/;
+       my %metainfo = ();
+       while (1) {
+               $_ = <$fd>;
+               last unless defined $_;
+               my ($full_rev, $orig_lineno, $lineno, $group_size) =
+                   /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+               if (!exists $metainfo{$full_rev}) {
+                       $metainfo{$full_rev} = {};
+               }
+               my $meta = $metainfo{$full_rev};
+               while (<$fd>) {
+                       last if (s/^\t//);
+                       if (/^(\S+) (.*)$/) {
+                               $meta->{$1} = $2;
+                       }
+               }
+               my $data = $_;
                my $rev = substr($full_rev, 0, 8);
-               my $print_c8 = 0;
-
-               if (!defined $last_rev) {
-                       $last_rev = $full_rev;
-                       $print_c8 = 1;
-               } elsif ($last_rev ne $full_rev) {
-                       $last_rev = $full_rev;
+               my $author = $meta->{'author'};
+               my %date = parse_date($meta->{'author-time'},
+                                     $meta->{'author-tz'});
+               my $date = $date{'iso-tz'};
+               if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
-                       $print_c8 = 1;
                }
                print "<tr class=\"$rev_color[$current_color]\">\n";
-               print "<td class=\"sha1\"";
-               if ($print_c8 == 1) {
+               if ($group_size) {
+                       print "<td class=\"sha1\"";
                        print " title=\"$author, $date\"";
-               }
-               print ">";
-               if ($print_c8 == 1) {
-                       print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)},
+                       print " rowspan=\"$group_size\"" if ($group_size > 1);
+                       print ">";
+                       print $cgi->a({-href => href(action=>"commit",
+                                                    hash=>$full_rev,
+                                                    file_name=>$file_name)},
                                      esc_html($rev));
+                       print "</td>\n";
                }
-               print "</td>\n";
-               print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
-                     esc_html($lineno) . "</a></td>\n";
+               my $blamed = href(action => 'blame',
+                                 file_name => $meta->{'filename'},
+                                 hash_base => $full_rev);
+               print "<td class=\"linenr\">";
+               print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                               -id => "l$lineno",
+                               -class => "linenr" },
+                             esc_html($lineno));
+               print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
        }
@@ -2843,7 +3043,7 @@ sub git_tree {
        my $refs = git_get_references();
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
-       my $base = "";
+       my $basedir = '';
        my ($have_blame) = gitweb_check_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
@@ -2860,7 +3060,7 @@ sub git_tree {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav,
                                $cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
-                                       "snapshot");
+                                       "snapshot");
                }
                git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
@@ -2871,12 +3071,39 @@ sub git_tree {
                print "<div class=\"title\">$hash</div>\n";
        }
        if (defined $file_name) {
-               $base = esc_html("$file_name/");
+               $basedir = $file_name;
+               if ($basedir ne '' && substr($basedir, -1) ne '/') {
+                       $basedir .= '/';
+               }
        }
        git_print_page_path($file_name, 'tree', $hash_base);
        print "<div class=\"page_body\">\n";
        print "<table cellspacing=\"0\">\n";
        my $alternate = 1;
+       # '..' (top directory) link if possible
+       if (defined $hash_base &&
+           defined $file_name && $file_name =~ m![^/]+$!) {
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+
+               my $up = $file_name;
+               $up =~ s!/?[^/]+$!!;
+               undef $up unless $up;
+               # based on git_print_tree_entry
+               print '<td class="mode">' . mode_str('040000') . "</td>\n";
+               print '<td class="list">';
+               print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+                                            file_name=>$up)},
+                             "..");
+               print "</td>\n";
+               print "<td class=\"link\"></td>\n";
+
+               print "</tr>\n";
+       }
        foreach my $line (@entries) {
                my %t = parse_ls_tree_line($line, -z => 1);
 
@@ -2887,7 +3114,7 @@ sub git_tree {
                }
                $alternate ^= 1;
 
-               git_print_tree_entry(\%t, $base, $hash_base, $have_blame);
+               git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
 
                print "</tr>\n";
        }
@@ -2915,9 +3142,12 @@ sub git_snapshot {
                -content_disposition => 'inline; filename="' . "$filename" . '"',
                -status => '200 OK');
 
-       my $git_command = git_cmd_str();
-       open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or
-               die_error(undef, "Execute git-tar-tree failed.");
+       my $git = git_cmd_str();
+       my $name = $project;
+       $name =~ s/\047/\047\\\047\047/g;
+       open my $fd, "-|",
+       "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+               or die_error(undef, "Execute git-tar-tree failed.");
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -2975,7 +3205,7 @@ sub git_log {
                      "</div>\n";
 
                print "<div class=\"log_body\">\n";
-               git_print_simplified_log($co{'comment'});
+               git_print_log($co{'comment'}, -final_empty_line=> 1);
                print "</div>\n";
        }
        git_footer_html();
@@ -2998,6 +3228,9 @@ sub git_commit {
        my @difftree = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading git-diff-tree failed");
 
+       # filter out commit ID output
+       @difftree = grep(!/^[0-9a-fA-F]{40}$/, @difftree);
+
        # non-textual hash id's can be cached
        my $expires;
        if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
@@ -3014,13 +3247,8 @@ sub git_commit {
                        $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
                                "blame");
        }
-       if (defined $co{'parent'}) {
-               push @views_nav,
-                       $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"),
-                       $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log");
-       }
        git_header_html(undef, $expires);
-       git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
+       git_print_page_nav('commit', '',
                           $hash, $co{'tree'}, $hash,
                           join (' | ', @views_nav));
 
@@ -3279,7 +3507,9 @@ sub git_commitdiff {
                while (chomp(my $line = <$fd>)) {
                        # empty line ends raw part of diff-tree output
                        last unless $line;
-                       push @difftree, $line;
+                       # filter out commit ID output
+                       push @difftree, $line
+                               unless $line =~ m/^[0-9a-fA-F]{40}$/;
                }
 
        } elsif ($format eq 'plain') {
@@ -3311,9 +3541,11 @@ sub git_commitdiff {
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
                git_print_authorship(\%co);
                print "<div class=\"page_body\">\n";
-               print "<div class=\"log\">\n";
-               git_print_simplified_log($co{'comment'}, 1); # skip title
-               print "</div>\n"; # class="log"
+               if (@{$co{'comment'}} > 1) {
+                       print "<div class=\"log\">\n";
+                       git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
+                       print "</div>\n"; # class="log"
+               }
 
        } elsif ($format eq 'plain') {
                my $refs = git_get_references("tags");
@@ -3445,18 +3677,8 @@ sub git_search {
                die_error(undef, "Unknown commit object");
        }
 
-       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;
-
+       $searchtype ||= 'commit';
+       if ($searchtype eq 'pickaxe') {
                # 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 ($have_pickaxe) = gitweb_check_feature('pickaxe');
@@ -3464,23 +3686,24 @@ sub git_search {
                        die_error('403 Permission denied', "Permission denied");
                }
        }
+
        git_header_html();
        git_print_page_nav('','', $hash,$co{'tree'},$hash);
        git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
        print "<table cellspacing=\"0\">\n";
        my $alternate = 1;
-       if ($commit_search) {
+       if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
                $/ = "\0";
                open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
                while (my $commit_text = <$fd>) {
                        if (!grep m/$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
                                next;
                        }
                        my @commit_lines = split "\n", $commit_text;
@@ -3522,7 +3745,7 @@ sub git_search {
                close $fd;
        }
 
-       if ($pickaxe_search) {
+       if ($searchtype eq 'pickaxe') {
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -3582,6 +3805,31 @@ sub git_search {
        git_footer_html();
 }
 
+sub git_search_help {
+       git_header_html();
+       git_print_page_nav('','', $hash,$hash,$hash);
+       print <<EOT;
+<dl>
+<dt><b>commit</b></dt>
+<dd>The commit messages and authorship information will be scanned for the given string.</dd>
+<dt><b>author</b></dt>
+<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+<dt><b>committer</b></dt>
+<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+EOT
+       my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+       if ($have_pickaxe) {
+               print <<EOT;
+<dt><b>pickaxe</b></dt>
+<dd>All commits that caused the string to appear or disappear from any file (changes that
+added, removed or "modified" the string) will be listed. This search can take a while and
+takes a lot of strain on the server, so please use it wisely.</dd>
+EOT
+       }
+       print "</dl>\n";
+       git_footer_html();
+}
+
 sub git_shortlog {
        my $head = git_get_head_hash($project);
        if (!defined $hash) {
@@ -3663,7 +3911,7 @@ XML
                      "<![CDATA[\n";
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
-                       $line = decode("utf8", $line, Encode::FB_DEFAULT);
+                       $line = to_utf8($line);
                        print "$line<br/>\n";
                }
                print "<br/>\n";
@@ -3672,7 +3920,7 @@ XML
                                next;
                        }
                        my $file = esc_html(unquote($7));
-                       $file = decode("utf8", $file, Encode::FB_DEFAULT);
+                       $file = to_utf8($file);
                        print "$file<br/>\n";
                }
                print "]]>\n" .
@@ -3690,7 +3938,7 @@ sub git_opml {
 <?xml version="1.0" encoding="utf-8"?>
 <opml version="1.0">
 <head>
-  <title>$site_name Git OPML Export</title>
+  <title>$site_name OPML Export</title>
 </head>
 <body>
 <outline text="git RSS feeds">
This page took 0.480988 seconds and 4 git commands to generate.