Merge branch 'js/diff-verbose-submodule'
[gitweb.git] / gitweb / gitweb.perl
index d02b7a3c268045ac0a15855d0f0c06c28b7a88be..e4cbfc35a76f761dd6ad3957956d7624568047d0 100755 (executable)
@@ -297,6 +297,19 @@ BEGIN
                'override' => 0,
                'default' => [1]},
 
+       # Enable showing size of blobs in a 'tree' view, in a separate
+       # column, similar to what 'ls -l' does.  This cost a bit of IO.
+
+       # To disable system wide have in $GITWEB_CONFIG
+       # $feature{'show-sizes'}{'default'} = [0];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'show-sizes'}{'override'} = 1;
+       # and in project config gitweb.showsizes = 0|1;
+       'show-sizes' => {
+               'sub' => sub { feature_bool('showsizes', @_) },
+               '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
@@ -415,7 +428,7 @@ sub gitweb_get_feature {
                @{$feature{$name}{'default'}});
        if (!$override) { return @defaults; }
        if (!defined $sub) {
-               warn "feature $name is not overrideable";
+               warn "feature $name is not overridable";
                return @defaults;
        }
        return $sub->(@defaults);
@@ -1083,8 +1096,7 @@ sub to_utf8 {
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
        my $str = shift;
-       $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
-       $str =~ s/\+/%2B/g;
+       $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
 }
@@ -1595,6 +1607,29 @@ sub git_get_avatar {
        }
 }
 
+sub format_search_author {
+       my ($author, $searchtype, $displaytext) = @_;
+       my $have_search = gitweb_check_feature('search');
+
+       if ($have_search) {
+               my $performed = "";
+               if ($searchtype eq 'author') {
+                       $performed = "authored";
+               } elsif ($searchtype eq 'committer') {
+                       $performed = "committed";
+               }
+
+               return $cgi->a({-href => href(action=>"search", hash=>$hash,
+                               searchtext=>$author,
+                               searchtype=>$searchtype), class=>"list",
+                               title=>"Search for commits $performed by $author"},
+                               $displaytext);
+
+       } else {
+               return $displaytext;
+       }
+}
+
 # format the author name of the given commit with the given tag
 # the author name is chopped and escaped according to the other
 # optional parameters (see chop_str).
@@ -1603,8 +1638,10 @@ sub format_author_html {
        my $co = shift;
        my $author = chop_and_escape_str($co->{'author_name'}, @_);
        return "<$tag class=\"author\">" .
-              git_get_avatar($co->{'author_email'}, -pad_after => 1) .
-              $author . "</$tag>";
+              format_search_author($co->{'author_name'}, "author",
+                      git_get_avatar($co->{'author_email'}, -pad_after => 1) .
+                      $author) .
+              "</$tag>";
 }
 
 # format git diff header line, i.e. "diff --(git|combined|cc) ..."
@@ -2764,16 +2801,31 @@ sub parse_ls_tree_line {
        my %opts = @_;
        my %res;
 
-       #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+       if ($opts{'-l'}) {
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717  panic.c'
+               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
 
-       $res{'mode'} = $1;
-       $res{'type'} = $2;
-       $res{'hash'} = $3;
-       if ($opts{'-z'}) {
-               $res{'name'} = $4;
+               $res{'mode'} = $1;
+               $res{'type'} = $2;
+               $res{'hash'} = $3;
+               $res{'size'} = $4;
+               if ($opts{'-z'}) {
+                       $res{'name'} = $5;
+               } else {
+                       $res{'name'} = unquote($5);
+               }
        } else {
-               $res{'name'} = unquote($4);
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
+               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+
+               $res{'mode'} = $1;
+               $res{'type'} = $2;
+               $res{'hash'} = $3;
+               if ($opts{'-z'}) {
+                       $res{'name'} = $4;
+               } else {
+                       $res{'name'} = unquote($4);
+               }
        }
 
        return wantarray ? %res : \%res;
@@ -3373,10 +3425,11 @@ sub git_print_authorship {
        my $co = shift;
        my %opts = @_;
        my $tag = $opts{-tag} || 'div';
+       my $author = $co->{'author_name'};
 
        my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
        print "<$tag class=\"author_date\">" .
-             esc_html($co->{'author_name'}) .
+             format_search_author($author, "author", esc_html($author)) .
              " [$ad{'rfc2822'}";
        print_local_time(%ad) if ($opts{-localtime});
        print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
@@ -3395,8 +3448,12 @@ sub git_print_authorship_rows {
        @people = ('author', 'committer') unless @people;
        foreach my $who (@people) {
                my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
-               print "<tr><td>$who</td><td>" . esc_html($co->{$who}) . "</td>" .
-                     "<td rowspan=\"2\">" .
+               print "<tr><td>$who</td><td>" .
+                     format_search_author($co->{"${who}_name"}, $who,
+                              esc_html($co->{"${who}_name"})) . " " .
+                     format_search_author($co->{"${who}_email"}, $who,
+                              esc_html("<" . $co->{"${who}_email"} . ">")) .
+                     "</td><td rowspan=\"2\">" .
                      git_get_avatar($co->{"${who}_email"}, -size => 'double') .
                      "</td></tr>\n" .
                      "<tr>" .
@@ -3564,6 +3621,9 @@ sub git_print_tree_entry {
        # and link is the action links of the entry.
 
        print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
+       if (exists $t->{'size'}) {
+               print "<td class=\"size\">$t->{'size'}</td>\n";
+       }
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
@@ -3609,12 +3669,14 @@ sub git_print_tree_entry {
        } elsif ($t->{'type'} eq "tree") {
                print "<td class=\"list\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                                            file_name=>"$basedir$t->{'name'}",
+                                            %base_key)},
                              esc_path($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)},
+                                            file_name=>"$basedir$t->{'name'}",
+                                            %base_key)},
                              "tree");
                if (defined $hash_base) {
                        print " | " .
@@ -5088,10 +5150,14 @@ sub git_tree {
        }
        die_error(404, "No such tree") unless defined($hash);
 
+       my $show_sizes = gitweb_check_feature('show-sizes');
+       my $have_blame = gitweb_check_feature('blame');
+
        my @entries = ();
        {
                local $/ = "\0";
-               open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
+               open my $fd, "-|", git_cmd(), "ls-tree", '-z',
+                       ($show_sizes ? '-l' : ()), @extra_options, $hash
                        or die_error(500, "Open git-ls-tree failed");
                @entries = map { chomp; $_ } <$fd>;
                close $fd
@@ -5102,7 +5168,6 @@ sub git_tree {
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
        my $basedir = '';
-       my $have_blame = gitweb_check_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
                if (defined $file_name) {
@@ -5118,7 +5183,8 @@ sub git_tree {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav, $snapshot_links;
                }
-               git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
+               git_print_page_nav('tree','', $hash_base, undef, undef,
+                                  join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
        } else {
                undef $hash_base;
@@ -5151,8 +5217,10 @@ sub git_tree {
                undef $up unless $up;
                # based on git_print_tree_entry
                print '<td class="mode">' . mode_str('040000') . "</td>\n";
+               print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
                print '<td class="list">';
-               print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+               print $cgi->a({-href => href(action=>"tree",
+                                            hash_base=>$hash_base,
                                             file_name=>$up)},
                              "..");
                print "</td>\n";
@@ -5161,7 +5229,7 @@ sub git_tree {
                print "</tr>\n";
        }
        foreach my $line (@entries) {
-               my %t = parse_ls_tree_line($line, -z => 1);
+               my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
 
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@ -5190,10 +5258,10 @@ sub git_snapshot {
                die_error(400, "Invalid snapshot format parameter");
        } elsif (!exists($known_snapshot_formats{$format})) {
                die_error(400, "Unknown snapshot format");
-       } elsif (!grep($_ eq $format, @snapshot_fmts)) {
-               die_error(403, "Unsupported snapshot format");
        } elsif ($known_snapshot_formats{$format}{'disabled'}) {
                die_error(403, "Snapshot format not allowed");
+       } elsif (!grep($_ eq $format, @snapshot_fmts)) {
+               die_error(403, "Unsupported snapshot format");
        }
 
        if (!defined $hash) {
@@ -5328,7 +5396,7 @@ sub git_commit {
                        } @$parents ) .
                        ')';
        }
-       if (gitweb_check_feature('patches')) {
+       if (gitweb_check_feature('patches') && @$parents <= 1) {
                $formats_nav .= " | " .
                        $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                "patch");
@@ -5616,7 +5684,7 @@ sub git_commitdiff {
                $formats_nav =
                        $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
-               if ($patch_max) {
+               if ($patch_max && @{$co{'parents'}} <= 1) {
                        $formats_nav .= " | " .
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
@@ -5824,7 +5892,7 @@ sub git_commitdiff_plain {
 
 # format-patch-style patches
 sub git_patch {
-       git_commitdiff(-format => 'patch', -single=> 1);
+       git_commitdiff(-format => 'patch', -single => 1);
 }
 
 sub git_patches {