Code

gitweb: remove git_blame and rename git_blame2 to git_blame
[git.git] / gitweb / gitweb.perl
index e69d7fd07b74d2e1c06f26e98eb72c83183c19f8..8d1f3e0547dd675d7d0fe3788f76e49efc701222 100755 (executable)
@@ -511,7 +511,7 @@ sub evaluate_path_info {
        }
        # do not change any parameters if an action is given using the query string
        return if $action;
-       $path_info =~ s,^$project/*,,;
+       $path_info =~ s,^\Q$project\E/*,,;
        my ($refname, $pathname) = split(/:/, $path_info, 2);
        if (defined $pathname) {
                # we got "project.git/branch:filename" or "project.git/branch:dir/"
@@ -539,7 +539,7 @@ $git_dir = "$projectroot/$project" if $project;
 
 # dispatch
 my %actions = (
-       "blame" => \&git_blame2,
+       "blame" => \&git_blame,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@ -592,7 +592,7 @@ exit;
 ## ======================================================================
 ## action links
 
-sub href(%) {
+sub href (%) {
        my %params = @_;
        # default is to use -absolute url() i.e. $my_uri
        my $href = $params{-full} ? $my_url : $my_uri;
@@ -633,7 +633,7 @@ sub href(%) {
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
        if ($use_pathinfo) {
                # use PATH_INFO for project name
-               $href .= "/$params{'project'}" if defined $params{'project'};
+               $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
                delete $params{'project'};
 
                # Summary just uses the project path URL
@@ -866,6 +866,10 @@ sub chop_str {
        my $add_len = shift || 10;
        my $where = shift || 'right'; # 'left' | 'center' | 'right'
 
+       # Make sure perl knows it is utf8 encoded so we don't
+       # cut in the middle of a utf8 multibyte char.
+       $str = to_utf8($str);
+
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
        # remove chopped character entities entirely
@@ -1448,6 +1452,46 @@ sub format_snapshot_links {
        }
 }
 
+## ......................................................................
+## functions returning values to be passed, perhaps after some
+## transformation, to other functions; e.g. returning arguments to href()
+
+# returns hash to be passed to href to generate gitweb URL
+# in -title key it returns description of link
+sub get_feed_info {
+       my $format = shift || 'Atom';
+       my %res = (action => lc($format));
+
+       # feed links are possible only for project views
+       return unless (defined $project);
+       # some views should link to OPML, or to generic project feed,
+       # or don't have specific feed yet (so they should use generic)
+       return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+
+       my $branch;
+       # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
+       # from tag links; this also makes possible to detect branch links
+       if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
+           (defined $hash      && $hash      =~ m!^refs/heads/(.*)$!)) {
+               $branch = $1;
+       }
+       # find log type for feed description (title)
+       my $type = 'log';
+       if (defined $file_name) {
+               $type  = "history of $file_name";
+               $type .= "/" if ($action eq 'tree');
+               $type .= " on '$branch'" if (defined $branch);
+       } else {
+               $type = "log of $branch" if (defined $branch);
+       }
+
+       $res{-title} = $type;
+       $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
+       $res{'file_name'} = $file_name;
+
+       return %res;
+}
+
 ## ----------------------------------------------------------------------
 ## git utility subroutines, invoking git commands
 
@@ -2437,8 +2481,7 @@ sub blob_mimetype {
        return $default_blob_plain_mimetype unless $fd;
 
        if (-T $fd) {
-               return 'text/plain' .
-                      ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
+               return 'text/plain';
        } elsif (! $filename) {
                return 'application/octet-stream';
        } elsif ($filename =~ m/\.png$/i) {
@@ -2452,6 +2495,17 @@ sub blob_mimetype {
        }
 }
 
+sub blob_contenttype {
+       my ($fd, $file_name, $type) = @_;
+
+       $type ||= blob_mimetype($fd, $file_name);
+       if ($type eq 'text/plain' && defined $default_text_plain_charset) {
+               $type .= "; charset=$default_text_plain_charset";
+       }
+
+       return $type;
+}
+
 ## ======================================================================
 ## functions printing HTML: header, footer, error page
 
@@ -2510,30 +2564,49 @@ EOF
                }
        }
        if (defined $project) {
-               printf('<link rel="alternate" title="%s log RSS feed" '.
-                      'href="%s" type="application/rss+xml" />'."\n",
-                      esc_param($project), href(action=>"rss"));
-               printf('<link rel="alternate" title="%s log RSS feed (no merges)" '.
-                      'href="%s" type="application/rss+xml" />'."\n",
-                      esc_param($project), href(action=>"rss",
-                                                extra_options=>"--no-merges"));
-               printf('<link rel="alternate" title="%s log Atom feed" '.
-                      'href="%s" type="application/atom+xml" />'."\n",
-                      esc_param($project), href(action=>"atom"));
-               printf('<link rel="alternate" title="%s log Atom feed (no merges)" '.
-                      'href="%s" type="application/atom+xml" />'."\n",
-                      esc_param($project), href(action=>"atom",
-                                                extra_options=>"--no-merges"));
+               my %href_params = get_feed_info();
+               if (!exists $href_params{'-title'}) {
+                       $href_params{'-title'} = 'log';
+               }
+
+               foreach my $format qw(RSS Atom) {
+                       my $type = lc($format);
+                       my %link_attr = (
+                               '-rel' => 'alternate',
+                               '-title' => "$project - $href_params{'-title'} - $format feed",
+                               '-type' => "application/$type+xml"
+                       );
+
+                       $href_params{'action'} = $type;
+                       $link_attr{'-href'} = href(%href_params);
+                       print "<link ".
+                             "rel=\"$link_attr{'-rel'}\" ".
+                             "title=\"$link_attr{'-title'}\" ".
+                             "href=\"$link_attr{'-href'}\" ".
+                             "type=\"$link_attr{'-type'}\" ".
+                             "/>\n";
+
+                       $href_params{'extra_options'} = '--no-merges';
+                       $link_attr{'-href'} = href(%href_params);
+                       $link_attr{'-title'} .= ' (no merges)';
+                       print "<link ".
+                             "rel=\"$link_attr{'-rel'}\" ".
+                             "title=\"$link_attr{'-title'}\" ".
+                             "href=\"$link_attr{'-href'}\" ".
+                             "type=\"$link_attr{'-type'}\" ".
+                             "/>\n";
+               }
+
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
-                      'href="%s" type="text/plain; charset=utf-8"/>'."\n",
+                      'href="%s" type="text/plain; charset=utf-8" />'."\n",
                       $site_name, href(project=>undef, action=>"project_index"));
                printf('<link rel="alternate" title="%s projects feeds" '.
-                      'href="%s" type="text/x-opml"/>'."\n",
+                      'href="%s" type="text/x-opml" />'."\n",
                       $site_name, href(project=>undef, action=>"opml"));
        }
        if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n);
+               print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
        }
 
        print "</head>\n" .
@@ -2560,7 +2633,7 @@ EOF
        print "</div>\n";
 
        my ($have_search) = gitweb_check_feature('search');
-       if ((defined $project) && ($have_search)) {
+       if (defined $project && $have_search) {
                if (!defined $searchtext) {
                        $searchtext = "";
                }
@@ -2575,17 +2648,14 @@ EOF
                my $action = $my_uri;
                my ($use_pathinfo) = gitweb_check_feature('pathinfo');
                if ($use_pathinfo) {
-                       $action .= "/$project";
-               } else {
-                       $cgi->param("p", $project);
+                       $action .= "/".esc_url($project);
                }
-               $cgi->param("a", "search");
-               $cgi->param("h", $search_hash);
                print $cgi->startform(-method => "get", -action => $action) .
                      "<div class=\"search\">\n" .
-                     (!$use_pathinfo && $cgi->hidden(-name => "p") . "\n") .
-                     $cgi->hidden(-name => "a") . "\n" .
-                     $cgi->hidden(-name => "h") . "\n" .
+                     (!$use_pathinfo &&
+                     $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+                     $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+                     $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
                      $cgi->popup_menu(-name => 'st', -default => 'commit',
                                       -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
@@ -2601,23 +2671,35 @@ EOF
 }
 
 sub git_footer_html {
+       my $feed_class = 'rss_logo';
+
        print "<div class=\"page_footer\">\n";
        if (defined $project) {
                my $descr = git_get_project_description($project);
                if (defined $descr) {
                        print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
                }
-               print $cgi->a({-href => href(action=>"rss"),
-                             -class => "rss_logo"}, "RSS") . " ";
-               print $cgi->a({-href => href(action=>"atom"),
-                             -class => "rss_logo"}, "Atom") . "\n";
+
+               my %href_params = get_feed_info();
+               if (!%href_params) {
+                       $feed_class .= ' generic';
+               }
+               $href_params{'-title'} ||= 'log';
+
+               foreach my $format qw(RSS Atom) {
+                       $href_params{'action'} = lc($format);
+                       print $cgi->a({-href => href(%href_params),
+                                     -title => "$href_params{'-title'} $format feed",
+                                     -class => $feed_class}, $format)."\n";
+               }
+
        } else {
                print $cgi->a({-href => href(project=>undef, action=>"opml"),
-                             -class => "rss_logo"}, "OPML") . " ";
+                             -class => $feed_class}, "OPML") . " ";
                print $cgi->a({-href => href(project=>undef, action=>"project_index"),
-                             -class => "rss_logo"}, "TXT") . "\n";
+                             -class => $feed_class}, "TXT") . "\n";
        }
-       print "</div>\n" ;
+       print "</div>\n"; # class="page_footer"
 
        if (-f $site_footer) {
                open (my $fd, $site_footer);
@@ -2681,7 +2763,7 @@ sub git_print_page_nav {
 }
 
 sub format_paging_nav {
-       my ($action, $hash, $head, $page, $nrevs) = @_;
+       my ($action, $hash, $head, $page, $has_next_link) = @_;
        my $paging_nav;
 
 
@@ -2699,7 +2781,7 @@ sub format_paging_nav {
                $paging_nav .= " &sdot; prev";
        }
 
-       if ($nrevs >= (100 * ($page+1)-1)) {
+       if ($has_next_link) {
                $paging_nav .= " &sdot; " .
                        $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
@@ -4070,7 +4152,7 @@ sub git_tag {
        git_footer_html();
 }
 
-sub git_blame2 {
+sub git_blame {
        my $fd;
        my $ftype;
 
@@ -4178,103 +4260,6 @@ HTML
        git_footer_html();
 }
 
-sub git_blame {
-       my $fd;
-
-       my ($have_blame) = gitweb_check_feature('blame');
-       if (!$have_blame) {
-               die_error('403 Permission denied', "Permission denied");
-       }
-       die_error('404 Not Found', "File name not defined") if (!$file_name);
-       $hash_base ||= git_get_head_hash($project);
-       die_error(undef, "Couldn't find base commit") unless ($hash_base);
-       my %co = parse_commit($hash_base)
-               or die_error(undef, "Reading commit failed");
-       if (!defined $hash) {
-               $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
-                       or die_error(undef, "Error lookup file");
-       }
-       open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base)
-               or die_error(undef, "Open git-annotate failed");
-       git_header_html();
-       my $formats_nav =
-               $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
-                       "blob") .
-               " | " .
-               $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
-                       "history") .
-               " | " .
-               $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
-                       "HEAD");
-       git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-       git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
-       git_print_page_path($file_name, 'blob', $hash_base);
-       print "<div class=\"page_body\">\n";
-       print <<HTML;
-<table class="blame">
-  <tr>
-    <th>Commit</th>
-    <th>Age</th>
-    <th>Author</th>
-    <th>Line</th>
-    <th>Data</th>
-  </tr>
-HTML
-       my @line_class = (qw(light dark));
-       my $line_class_len = scalar (@line_class);
-       my $line_class_num = $#line_class;
-       while (my $line = <$fd>) {
-               my $long_rev;
-               my $short_rev;
-               my $author;
-               my $time;
-               my $lineno;
-               my $data;
-               my $age;
-               my $age_str;
-               my $age_class;
-
-               chomp $line;
-               $line_class_num = ($line_class_num + 1) % $line_class_len;
-
-               if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) {
-                       $long_rev = $1;
-                       $author   = $2;
-                       $time     = $3;
-                       $lineno   = $4;
-                       $data     = $5;
-               } else {
-                       print qq(  <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
-                       next;
-               }
-               $short_rev  = substr ($long_rev, 0, 8);
-               $age        = time () - $time;
-               $age_str    = age_string ($age);
-               $age_str    =~ s/ /&nbsp;/g;
-               $age_class  = age_class($age);
-               $author     = esc_html ($author);
-               $author     =~ s/ /&nbsp;/g;
-
-               $data = untabify($data);
-               $data = esc_html ($data);
-
-               print <<HTML;
-  <tr class="$line_class[$line_class_num]">
-    <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td>
-    <td class="$age_class">$age_str</td>
-    <td>$author</td>
-    <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
-    <td class="pre">$data</td>
-  </tr>
-HTML
-       } # while (my $line = <$fd>)
-       print "</table>\n\n";
-       close $fd
-               or print "Reading blob failed.\n";
-       print "</div>";
-       git_footer_html();
-}
-
 sub git_tags {
        my $head = git_get_head_hash($project);
        git_header_html();
@@ -4302,6 +4287,7 @@ sub git_heads {
 }
 
 sub git_blob_plain {
+       my $type = shift;
        my $expires;
 
        if (!defined $hash) {
@@ -4317,13 +4303,13 @@ sub git_blob_plain {
                $expires = "+1d";
        }
 
-       my $type = shift;
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
-               or die_error(undef, "Couldn't cat $file_name, $hash");
+               or die_error(undef, "Open git-cat-file blob '$hash' failed");
 
-       $type ||= blob_mimetype($fd, $file_name);
+       # content-type (can include charset)
+       $type = blob_contenttype($fd, $file_name, $type);
 
-       # save as filename, even when no $file_name is given
+       # "save as" filename, even when no $file_name is given
        my $save_as = "$hash";
        if (defined $file_name) {
                $save_as = $file_name;
@@ -4332,9 +4318,9 @@ sub git_blob_plain {
        }
 
        print $cgi->header(
-               -type => "$type",
-               -expires=>$expires,
-               -content_disposition => 'inline; filename="' . "$save_as" . '"');
+               -type => $type,
+               -expires => $expires,
+               -content_disposition => 'inline; filename="' . $save_as . '"');
        undef $/;
        binmode STDOUT, ':raw';
        print <$fd>;
@@ -4590,7 +4576,7 @@ sub git_log {
 
        my @commitlist = parse_commits($hash, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
+       my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
 
        git_header_html();
        git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
@@ -5176,14 +5162,26 @@ sub git_history {
        my $refs = git_get_references();
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 
+       my @commitlist = parse_commits($hash_base, 101, (100 * $page),
+                                      $file_name, "--full-history");
+       if (!@commitlist) {
+               die_error('404 Not Found', "No such file or directory on given branch");
+       }
+
        if (!defined $hash && defined $file_name) {
-               $hash = git_get_hash_by_path($hash_base, $file_name);
+               # some commits could have deleted file in question,
+               # and not have it in tree, but one of them has to have it
+               for (my $i = 0; $i <= @commitlist; $i++) {
+                       $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
+                       last if defined $hash;
+               }
        }
        if (defined $hash) {
                $ftype = git_get_type($hash);
        }
-
-       my @commitlist = parse_commits($hash_base, 101, (100 * $page), $file_name, "--full-history");
+       if (!defined $ftype) {
+               die_error(undef, "Unknown type of object");
+       }
 
        my $paging_nav = '';
        if ($page > 0) {
@@ -5498,7 +5496,7 @@ sub git_shortlog {
 
        my @commitlist = parse_commits($hash, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
+       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =