Merge branch 'maint'
[gitweb.git] / gitweb / gitweb.perl
index 491a3f41d20deafa6513e7f574108022b762507b..e8226b16f4096673180b24d4362aa45ffe1e2e9a 100755 (executable)
@@ -611,6 +611,8 @@ (%)
        );
        my %mapping = @mapping;
 
+       $params{'project'} = $project unless exists $params{'project'};
+
        if ($params{-replay}) {
                while (my ($name, $symbol) = each %mapping) {
                        if (!exists $params{$name}) {
@@ -620,8 +622,6 @@ (%)
                }
        }
 
-       $params{'project'} = $project unless exists $params{'project'};
-
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
        if ($use_pathinfo) {
                # use PATH_INFO for project name
@@ -695,10 +695,9 @@ sub validate_refname {
 # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
 sub to_utf8 {
        my $str = shift;
-       my $res;
-       eval { $res = decode_utf8($str, Encode::FB_CROAK); };
-       if (defined $res) {
-               return $res;
+       if (utf8::valid($str)) {
+               utf8::decode($str);
+               return $str;
        } else {
                return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
        }
@@ -754,29 +753,40 @@ sub esc_path {
 # Make control characters "printable", using character escape codes (CEC)
 sub quot_cec {
        my $cntrl = shift;
+       my %opts = @_;
        my %es = ( # character escape codes, aka escape sequences
-                  "\t" => '\t',   # tab            (HT)
-                  "\n" => '\n',   # line feed      (LF)
-                  "\r" => '\r',   # carrige return (CR)
-                  "\f" => '\f',   # form feed      (FF)
-                  "\b" => '\b',   # backspace      (BS)
-                  "\a" => '\a',   # alarm (bell)   (BEL)
-                  "\e" => '\e',   # escape         (ESC)
-                  "\013" => '\v', # vertical tab   (VT)
-                  "\000" => '\0', # nul character  (NUL)
-                  );
+               "\t" => '\t',   # tab            (HT)
+               "\n" => '\n',   # line feed      (LF)
+               "\r" => '\r',   # carrige return (CR)
+               "\f" => '\f',   # form feed      (FF)
+               "\b" => '\b',   # backspace      (BS)
+               "\a" => '\a',   # alarm (bell)   (BEL)
+               "\e" => '\e',   # escape         (ESC)
+               "\013" => '\v', # vertical tab   (VT)
+               "\000" => '\0', # nul character  (NUL)
+       );
        my $chr = ( (exists $es{$cntrl})
                    ? $es{$cntrl}
                    : sprintf('\%03o', ord($cntrl)) );
-       return "<span class=\"cntrl\">$chr</span>";
+       if ($opts{-nohtml}) {
+               return $chr;
+       } else {
+               return "<span class=\"cntrl\">$chr</span>";
+       }
 }
 
 # Alternatively use unicode control pictures codepoints,
 # Unicode "printable representation" (PR)
 sub quot_upr {
        my $cntrl = shift;
+       my %opts = @_;
+
        my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
-       return "<span class=\"cntrl\">$chr</span>";
+       if ($opts{-nohtml}) {
+               return $chr;
+       } else {
+               return "<span class=\"cntrl\">$chr</span>";
+       }
 }
 
 # git may return quoted and escaped filenames
@@ -801,7 +811,7 @@ sub unquote {
                        return chr(oct($seq));
                } elsif (exists $es{$seq}) {
                        # C escape sequence, aka character escape code
-                       return $es{$seq}
+                       return $es{$seq};
                }
                # quoted ordinary character
                return $seq;
@@ -867,8 +877,8 @@ sub chop_and_escape_str {
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
-               return qq{<span title="} . esc_html($str) . qq{">} .
-                       esc_html($chopped) . qq{</span>};
+               $str =~ s/([[:cntrl:]])/?/g;
+               return $cgi->span({-title=>$str}, esc_html($chopped));
        }
 }
 
@@ -1512,7 +1522,7 @@ sub config_to_int {
 sub config_to_multi {
        my $val = shift;
 
-       return ref($val) ? $val : [ $val ];
+       return ref($val) ? $val : (defined($val) ? [ $val ] : []);
 }
 
 sub git_get_project_config {
@@ -1607,7 +1617,7 @@ sub git_get_project_description {
        my $path = shift;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, "$projectroot/$path/description"
+       open my $fd, "$git_dir/description"
                or return git_get_project_config('description');
        my $descr = <$fd>;
        close $fd;
@@ -1621,7 +1631,7 @@ sub git_get_project_url_list {
        my $path = shift;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, "$projectroot/$path/cloneurl"
+       open my $fd, "$git_dir/cloneurl"
                or return wantarray ?
                @{ config_to_multi(git_get_project_config('url')) } :
                   config_to_multi(git_get_project_config('url'));
@@ -1760,6 +1770,7 @@ sub git_get_project_owner {
        my $owner;
 
        return undef unless $project;
+       $git_dir = "$projectroot/$project";
 
        if (!defined $gitweb_project_owner) {
                git_get_project_list_from_file();
@@ -1768,8 +1779,11 @@ sub git_get_project_owner {
        if (exists $gitweb_project_owner->{$project}) {
                $owner = $gitweb_project_owner->{$project};
        }
+       if (!defined $owner){
+               $owner = git_get_project_config('owner');
+       }
        if (!defined $owner) {
-               $owner = get_file_owner("$projectroot/$project");
+               $owner = get_file_owner("$git_dir");
        }
 
        return $owner;
@@ -2234,6 +2248,7 @@ sub git_get_heads_list {
                my ($hash, $name, $title) = split(' ', $refinfo, 3);
                my ($committer, $epoch, $tz) =
                        ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $ref_item{'fullname'}  = $name;
                $name =~ s!^refs/heads/!!;
 
                $ref_item{'name'}  = $name;
@@ -2271,6 +2286,7 @@ sub git_get_tags_list {
                my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
                my ($creator, $epoch, $tz) =
                        ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+               $ref_item{'fullname'} = $name;
                $name =~ s!^refs/tags/!!;
 
                $ref_item{'type'} = $type;
@@ -3691,8 +3707,8 @@ sub git_tags_body {
                      "<td class=\"link\">" . " | " .
                      $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
                if ($tag{'reftype'} eq "commit") {
-                       print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
-                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
+                       print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
+                             " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
                } elsif ($tag{'reftype'} eq "blob") {
                        print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
                }
@@ -3727,13 +3743,13 @@ sub git_heads_body {
                $alternate ^= 1;
                print "<td><i>$ref{'age'}</i></td>\n" .
                      ($curr ? "<td class=\"current_head\">" : "<td>") .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
                               -class => "list name"},esc_html($ref{'name'})) .
                      "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
-                     $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
+                     $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
+                     $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
                      "</td>\n" .
                      "</tr>";
        }
@@ -3768,24 +3784,32 @@ sub git_search_grep_body {
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
                      "<td><i>" . $author . "</i></td>\n" .
                      "<td>" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                              chop_and_escape_str($co{'title'}, 50) . "<br/>");
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+                              -class => "list subject"},
+                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
                        if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
-                               my $lead = esc_html($1) || "";
-                               $lead = chop_str($lead, 30, 10);
-                               my $match = esc_html($2) || "";
-                               my $trail = esc_html($3) || "";
-                               $trail = chop_str($trail, 30, 10);
-                               my $text = "$lead<span class=\"match\">$match</span>$trail";
-                               print chop_str($text, 80, 5) . "<br/>\n";
+                               my ($lead, $match, $trail) = ($1, $2, $3);
+                               $match = chop_str($match, 70, 5);       # in case match is very long
+                               my $contextlen = int((80 - length($match))/2); # for the remainder
+                               $contextlen = 30 if ($contextlen > 30); # but not too much
+                               $lead  = chop_str($lead,  $contextlen, 10);
+                               $trail = chop_str($trail, $contextlen, 10);
+
+                               $lead  = esc_html($lead);
+                               $match = esc_html($match);
+                               $trail = esc_html($trail);
+
+                               print "$lead<span class=\"match\">$match</span>$trail<br />";
                        }
                }
                print "</td>\n" .
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
                      " | " .
+                     $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
+                     " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
                print "</td>\n" .
                      "</tr>\n";
@@ -4287,7 +4311,7 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(undef, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
-       if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) {
+       if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
        }
@@ -4328,16 +4352,7 @@ sub git_blob {
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
-       if ($mimetype =~ m!^text/!) {
-               my $nr;
-               while (my $line = <$fd>) {
-                       chomp $line;
-                       $nr++;
-                       $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
-               }
-       } elsif ($mimetype =~ m!^image/!) {
+       if ($mimetype =~ m!^image/!) {
                print qq!<img type="$mimetype"!;
                if ($file_name) {
                        print qq! alt="$file_name" title="$file_name"!;
@@ -4346,6 +4361,15 @@ sub git_blob {
                      href(action=>"blob_plain", hash=>$hash,
                           hash_base=>$hash_base, file_name=>$file_name) .
                      qq!" />\n!;
+       } else {
+               my $nr;
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       $nr++;
+                       $line = untabify($line);
+                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+               }
        }
        close $fd
                or print "Reading blob failed.\n";
@@ -5045,16 +5069,15 @@ sub git_commitdiff {
                        -expires => $expires,
                        -content_disposition => 'inline; filename="' . "$filename" . '"');
                my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
-               print <<TEXT;
-From: $co{'author'}
-Date: $ad{'rfc2822'} ($ad{'tz_local'})
-Subject: $co{'title'}
-TEXT
+               print "From: " . to_utf8($co{'author'}) . "\n";
+               print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
+               print "Subject: " . to_utf8($co{'title'}) . "\n";
+
                print "X-Git-Tag: $tagname\n" if $tagname;
                print "X-Git-Url: " . $cgi->self_url() . "\n\n";
 
                foreach my $line (@{$co{'comment'}}) {
-                       print "$line\n";
+                       print to_utf8($line) . "\n";
                }
                print "---\n\n";
        }
@@ -5563,7 +5586,7 @@ sub git_feed {
                        or next;
 
                # print element (entry, item)
-               my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+               my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
                if ($format eq 'rss') {
                        print "<item>\n" .
                              "<title>" . esc_html($co{'title'}) . "</title>\n" .