Git 1.6.6.3
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Dec 2010 19:32:57 +0000 (11:32 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Dec 2010 19:32:57 +0000 (11:32 -0800)
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1  2 
Documentation/RelNotes/1.6.6.3.txt
GIT-VERSION-GEN
RelNotes
gitweb/gitweb.perl
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..11483acaec5528f828b0d630147187af0c151ed2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,23 @@@
++Git v1.6.6.3 Release Notes
++==========================
++
++Fixes since v1.6.6.2
++--------------------
++
++ * An overlong line after ".gitdir: " in a git file caused out of bounds
++   access to an array on the stack.
++
++ * "git bisect $path" did not correctly diagnose an error when given a
++   non-existent path.
++
++ * "git blame -L $start,$end" segfaulted when too large $start was given.
++
++ * "git imap-send" did not write draft box with CRLF line endings per RFC.
++
++ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
++   when --keep-dashdash was in effect.
++
++ * "gitweb" can sometimes be tricked into parrotting a filename argument
++   given in a request without properly quoting.
++
++Other minor fixes and documentation updates are included.
diff --combined GIT-VERSION-GEN
index 081be51a9e64f3eb788cd31c2a3bc8ad454d2029,1d79f482961d30b91c2ba1ee9ad062e2545a0965..7d16b013824cc6974fe49e9a49c588146b6d7e2d
@@@ -1,7 -1,7 +1,7 @@@
  #!/bin/sh
  
  GVF=GIT-VERSION-FILE
- DEF_VER=v1.6.6.2
 -DEF_VER=v1.6.5.9
++DEF_VER=v1.6.6.3
  
  LF='
  '
diff --combined RelNotes
index 692968f1702044f0474228f2c19f6086568ec9e4,3c23099a3734f8fb9de7c7894308334d5a5b4aec..3dad2389f600053e534a75563049a66b5bdb1ebf
+++ b/RelNotes
@@@ -1,1 -1,1 +1,1 @@@
- Documentation/RelNotes-1.6.6.2.txt
 -Documentation/RelNotes/1.6.5.9.txt
++Documentation/RelNotes/1.6.6.3.txt
diff --combined gitweb/gitweb.perl
index 7e477af9567cff322d18fc9a1fbfe507732418dc,620b5bdbbe238a79f3d9d8fcf852c8d0f3609818..0fe8539321e6eb527aef127a17e437edb4998308
@@@ -18,12 -18,6 +18,12 @@@ use File::Find qw()
  use File::Basename qw(basename);
  binmode STDOUT, ':utf8';
  
 +our $t0;
 +if (eval { require Time::HiRes; 1; }) {
 +      $t0 = [Time::HiRes::gettimeofday()];
 +}
 +our $number_of_git_cmds = 0;
 +
  BEGIN {
        CGI->compile() if $ENV{'MOD_PERL'};
  }
@@@ -96,8 -90,6 +96,8 @@@ our $stylesheet = undef
  our $logo = "++GITWEB_LOGO++";
  # URI of GIT favicon, assumed to be image/png type
  our $favicon = "++GITWEB_FAVICON++";
 +# URI of gitweb.js (JavaScript code for gitweb)
 +our $javascript = "++GITWEB_JS++";
  
  # URI and label (title) of GIT logo link
  #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
@@@ -305,19 -297,6 +305,19 @@@ our %feature = 
                '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
                'sub' => \&feature_avatar,
                'override' => 0,
                'default' => ['']},
 +
 +      # Enable displaying how much time and how many git commands
 +      # it took to generate and display page.  Disabled by default.
 +      # Project specific override is not supported.
 +      'timed' => {
 +              'override' => 0,
 +              'default' => [0]},
 +
 +      # Enable turning some links into links to actions which require
 +      # JavaScript to run (like 'blame_incremental').  Not enabled by
 +      # default.  Project specific override is currently not supported.
 +      'javascript-actions' => {
 +              'override' => 0,
 +              'default' => [0]},
  );
  
  sub gitweb_get_feature {
@@@ -553,7 -518,6 +553,7 @@@ if (-e $GITWEB_CONFIG) 
  
  # version of the core git binary
  our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 +$number_of_git_cmds++;
  
  $projects_list ||= $projectroot;
  
@@@ -591,16 -555,12 +591,16 @@@ our @cgi_param_mapping = 
        snapshot_format => "sf",
        extra_options => "opt",
        search_use_regexp => "sr",
 +      # this must be last entry (for manipulation from JavaScript)
 +      javascript => "js"
  );
  our %cgi_param_mapping = @cgi_param_mapping;
  
  # we will also need to know the possible actions, for validation
  our %actions = (
        "blame" => \&git_blame,
 +      "blame_incremental" => \&git_blame_incremental,
 +      "blame_data" => \&git_blame_data,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@@ -1137,6 -1097,13 +1137,13 @@@ sub esc_url 
        return $str;
  }
  
+ # quote unsafe characters in HTML attributes
+ sub esc_attr {
+       # for XHTML conformance escaping '"' to '&quot;' is not enough
+       return esc_html(@_);
+ }
  # replace invalid utf8 character with SUBSTITUTION sequence
  sub esc_html {
        my $str = shift;
@@@ -1542,7 -1509,7 +1549,7 @@@ sub format_ref_marker 
                                        hash=>$dest
                                )}, $name);
  
-                       $markers .= " <span class=\"$class\" title=\"$ref\">" .
+                       $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
                }
        }
@@@ -1626,7 -1593,7 +1633,7 @@@ sub git_get_avatar 
                return $pre_white .
                       "<img width=\"$size\" " .
                            "class=\"avatar\" " .
-                           "src=\"$url\" " .
+                           "src=\"".esc_url($url)."\" " .
                            "alt=\"\" " .
                       "/>" . $post_white;
        } else {
        }
  }
  
 +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).
@@@ -1665,10 -1609,8 +1672,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) ..."
@@@ -2033,7 -1975,6 +2040,7 @@@ sub get_feed_info 
  
  # returns path to the core git executable and the --git-dir parameter as list
  sub git_cmd {
 +      $number_of_git_cmds++;
        return $GIT, '--git-dir='.$git_dir;
  }
  
@@@ -2048,27 -1989,16 +2055,27 @@@ sub quote_command 
  
  # get HEAD ref of given project as hash
  sub git_get_head_hash {
 -      my $project = shift;
 +      return git_get_full_hash(shift, 'HEAD');
 +}
 +
 +sub git_get_full_hash {
 +      return git_get_hash(@_);
 +}
 +
 +sub git_get_short_hash {
 +      return git_get_hash(@_, '--short=7');
 +}
 +
 +sub git_get_hash {
 +      my ($project, $hash, @options) = @_;
        my $o_git_dir = $git_dir;
        my $retval = undef;
        $git_dir = "$projectroot/$project";
 -      if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
 -              my $head = <$fd>;
 +      if (open my $fd, '-|', git_cmd(), 'rev-parse',
 +          '--verify', '-q', @options, $hash) {
 +              $retval = <$fd>;
 +              chomp $retval if defined $retval;
                close $fd;
 -              if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
 -                      $retval = $1;
 -              }
        }
        if (defined $o_git_dir) {
                $git_dir = $o_git_dir;
@@@ -2335,7 -2265,7 +2342,7 @@@ sub git_show_project_tagcloud 
        } else {
                my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
                return '<p align="center">' . join (', ', map {
-                       "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
+                       $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname})
                } splice(@tags, 0, $count)) . '</p>';
        }
  }
@@@ -2840,31 -2770,16 +2847,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;
        # print out each stylesheet that exist, providing backwards capability
        # for those people who defined $stylesheet in a config file
        if (defined $stylesheet) {
-               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
                        next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
                }
        }
        if (defined $project) {
                        my $type = lc($format);
                        my %link_attr = (
                                '-rel' => 'alternate',
-                               '-title' => "$project - $href_params{'-title'} - $format feed",
+                               '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
                                '-type' => "application/$type+xml"
                        );
  
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8" />'."\n",
-                      $site_name, href(project=>undef, action=>"project_index"));
+                      esc_attr($site_name), href(project=>undef, action=>"project_index"));
                printf('<link rel="alternate" title="%s projects feeds" '.
                       'href="%s" type="text/x-opml" />'."\n",
-                      $site_name, href(project=>undef, action=>"opml"));
+                      esc_attr($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=").esc_url($favicon).qq(" type="image/png" />\n);
        }
  
        print "</head>\n" .
        print "<div class=\"page_header\">\n" .
              $cgi->a({-href => esc_url($logo_url),
                       -title => $logo_label},
-                     qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+                     qq(<img src=").esc_url($logo).qq(" 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));
@@@ -3309,36 -3224,10 +3316,36 @@@ sub git_footer_html 
        }
        print "</div>\n"; # class="page_footer"
  
 +      if (defined $t0 && gitweb_check_feature('timed')) {
 +              print "<div id=\"generating_info\">\n";
 +              print 'This page took '.
 +                    '<span id="generating_time" class="time_span">'.
 +                    Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
 +                    ' seconds </span>'.
 +                    ' and '.
 +                    '<span id="generating_cmd">'.
 +                    $number_of_git_cmds.
 +                    '</span> git commands '.
 +                    " to generate.\n";
 +              print "</div>\n"; # class="page_footer"
 +      }
 +
        if (-f $site_footer) {
                insert_file($site_footer);
        }
  
-       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
++      print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
 +      if ($action eq 'blame_incremental') {
 +              print qq!<script type="text/javascript">\n!.
 +                    qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
 +                    qq!           "!. href() .qq!");\n!.
 +                    qq!</script>\n!;
 +      } elsif (gitweb_check_feature('javascript-actions')) {
 +              print qq!<script type="text/javascript">\n!.
 +                    qq!window.onload = fixLinks;\n!.
 +                    qq!</script>\n!;
 +      }
 +
        print "</body>\n" .
              "</html>";
  }
@@@ -3428,18 -3317,22 +3435,18 @@@ sub git_print_page_nav 
  }
  
  sub format_paging_nav {
 -      my ($action, $hash, $head, $page, $has_next_link) = @_;
 +      my ($action, $page, $has_next_link) = @_;
        my $paging_nav;
  
  
 -      if ($hash ne $head || $page) {
 -              $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
 -      } else {
 -              $paging_nav .= "HEAD";
 -      }
 -
        if ($page > 0) {
 -              $paging_nav .= " &sdot; " .
 +              $paging_nav .=
 +                      $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
 +                      " &sdot; " .
                        $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
 -              $paging_nav .= " &sdot; prev";
 +              $paging_nav .= "first &sdot; prev";
        }
  
        if ($has_next_link) {
@@@ -3486,11 -3379,10 +3493,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)
@@@ -3509,12 -3401,8 +3516,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>" .
@@@ -3682,9 -3570,6 +3689,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'},
        } 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 " | " .
@@@ -4422,46 -4305,6 +4429,46 @@@ sub git_project_list_body 
        print "</table>\n";
  }
  
 +sub git_log_body {
 +      # uses global variable $project
 +      my ($commitlist, $from, $to, $refs, $extra) = @_;
 +
 +      $from = 0 unless defined $from;
 +      $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
 +
 +      for (my $i = 0; $i <= $to; $i++) {
 +              my %co = %{$commitlist->[$i]};
 +              next if !%co;
 +              my $commit = $co{'id'};
 +              my $ref = format_ref_marker($refs, $commit);
 +              my %ad = parse_date($co{'author_epoch'});
 +              git_print_header_div('commit',
 +                             "<span class=\"age\">$co{'age_string'}</span>" .
 +                             esc_html($co{'title'}) . $ref,
 +                             $commit);
 +              print "<div class=\"title_text\">\n" .
 +                    "<div class=\"log_link\">\n" .
 +                    $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") .
 +                    "<br/>\n" .
 +                    "</div>\n";
 +                    git_print_authorship(\%co, -tag => 'span');
 +                    print "<br/>\n</div>\n";
 +
 +              print "<div class=\"log_body\">\n";
 +              git_print_log($co{'comment'}, -final_empty_line=> 1);
 +              print "</div>\n";
 +      }
 +      if ($extra) {
 +              print "<div class=\"page_nav\">\n";
 +              print "$extra\n";
 +              print "</div>\n";
 +      }
 +}
 +
  sub git_shortlog_body {
        # uses global variable $project
        my ($commitlist, $from, $to, $refs, $extra) = @_;
  
  sub git_history_body {
        # Warning: assumes constant type (blob or tree) during history
 -      my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
 +      my ($commitlist, $from, $to, $refs, $extra,
 +          $file_name, $file_hash, $ftype) = @_;
  
        $from = 0 unless defined $from;
        $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
  
                if ($ftype eq 'blob') {
 -                      my $blob_current = git_get_hash_by_path($hash_base, $file_name);
 +                      my $blob_current = $file_hash;
                        my $blob_parent  = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob_current && defined $blob_parent &&
                                        $blob_current ne $blob_parent) {
@@@ -4935,13 -4777,7 +4942,13 @@@ sub git_tag 
        git_footer_html();
  }
  
 -sub git_blame {
 +sub git_blame_common {
 +      my $format = shift || 'porcelain';
 +      if ($format eq 'porcelain' && $cgi->param('js')) {
 +              $format = 'incremental';
 +              $action = 'blame_incremental'; # for page title etc
 +      }
 +
        # permissions
        gitweb_check_feature('blame')
                or die_error(403, "Blame view not allowed");
                }
        }
  
 -      # run git-blame --porcelain
 -      open my $fd, "-|", git_cmd(), "blame", '-p',
 -              $hash_base, '--', $file_name
 -              or die_error(500, "Open git-blame failed");
 +      my $fd;
 +      if ($format eq 'incremental') {
 +              # get file contents (as base)
 +              open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
 +                      or die_error(500, "Open git-cat-file failed");
 +      } elsif ($format eq 'data') {
 +              # run git-blame --incremental
 +              open $fd, "-|", git_cmd(), "blame", "--incremental",
 +                      $hash_base, "--", $file_name
 +                      or die_error(500, "Open git-blame --incremental failed");
 +      } else {
 +              # run git-blame --porcelain
 +              open $fd, "-|", git_cmd(), "blame", '-p',
 +                      $hash_base, '--', $file_name
 +                      or die_error(500, "Open git-blame --porcelain failed");
 +      }
 +
 +      # incremental blame data returns early
 +      if ($format eq 'data') {
 +              print $cgi->header(
 +                      -type=>"text/plain", -charset => "utf-8",
 +                      -status=> "200 OK");
 +              local $| = 1; # output autoflush
 +              print while <$fd>;
 +              close $fd
 +                      or print "ERROR $!\n";
 +
 +              print 'END';
 +              if (defined $t0 && gitweb_check_feature('timed')) {
 +                      print ' '.
 +                            Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
 +                            ' '.$number_of_git_cmds;
 +              }
 +              print "\n";
 +
 +              return;
 +      }
  
        # page header
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
 +              " | ";
 +      if ($format eq 'incremental') {
 +              $formats_nav .=
 +                      $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
 +                              "blame") . " (non-incremental)";
 +      } else {
 +              $formats_nav .=
 +                      $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
 +                              "blame") . " (incremental)";
 +      }
 +      $formats_nav .=
                " | " .
                $cgi->a({-href => href(action=>"history", -replay=>1)},
                        "history") .
                " | " .
 -              $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
 +              $cgi->a({-href => href(action=>$action, 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, $ftype, $hash_base);
  
        # page body
 +      if ($format eq 'incremental') {
 +              print "<noscript>\n<div class=\"error\"><center><b>\n".
 +                    "This page requires JavaScript to run.\n Use ".
 +                    $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
 +                            'this page').
 +                    " instead.\n".
 +                    "</b></center></div>\n</noscript>\n";
 +
 +              print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
 +      }
 +
 +      print qq!<div class="page_body">\n!;
 +      print qq!<div id="progress_info">... / ...</div>\n!
 +              if ($format eq 'incremental');
 +      print qq!<table id="blame_table" class="blame" width="100%">\n!.
 +            #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
 +            qq!<thead>\n!.
 +            qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
 +            qq!</thead>\n!.
 +            qq!<tbody>\n!;
 +
        my @rev_color = qw(light dark);
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
 -      my %metainfo = ();
  
 -      print <<HTML;
 -<div class="page_body">
 -<table class="blame">
 -<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
 -HTML
 - LINE:
 -      while (my $line = <$fd>) {
 -              chomp $line;
 -              # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
 -              # no <lines in group> for subsequent lines in group of lines
 -              my ($full_rev, $orig_lineno, $lineno, $group_size) =
 -                 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
 -              if (!exists $metainfo{$full_rev}) {
 -                      $metainfo{$full_rev} = { 'nprevious' => 0 };
 -              }
 -              my $meta = $metainfo{$full_rev};
 -              my $data;
 -              while ($data = <$fd>) {
 -                      chomp $data;
 -                      last if ($data =~ s/^\t//); # contents of line
 -                      if ($data =~ /^(\S+)(?: (.*))?$/) {
 -                              $meta->{$1} = $2 unless exists $meta->{$1};
 +      if ($format eq 'incremental') {
 +              my $color_class = $rev_color[$current_color];
 +
 +              #contents of a file
 +              my $linenr = 0;
 +      LINE:
 +              while (my $line = <$fd>) {
 +                      chomp $line;
 +                      $linenr++;
 +
 +                      print qq!<tr id="l$linenr" class="$color_class">!.
 +                            qq!<td class="sha1"><a href=""> </a></td>!.
 +                            qq!<td class="linenr">!.
 +                            qq!<a class="linenr" href="">$linenr</a></td>!;
 +                      print qq!<td class="pre">! . esc_html($line) . "</td>\n";
 +                      print qq!</tr>\n!;
 +              }
 +
 +      } else { # porcelain, i.e. ordinary blame
 +              my %metainfo = (); # saves information about commits
 +
 +              # blame data
 +      LINE:
 +              while (my $line = <$fd>) {
 +                      chomp $line;
 +                      # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
 +                      # no <lines in group> for subsequent lines in group of lines
 +                      my ($full_rev, $orig_lineno, $lineno, $group_size) =
 +                         ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
 +                      if (!exists $metainfo{$full_rev}) {
 +                              $metainfo{$full_rev} = { 'nprevious' => 0 };
                        }
 -                      if ($data =~ /^previous /) {
 -                              $meta->{'nprevious'}++;
 +                      my $meta = $metainfo{$full_rev};
 +                      my $data;
 +                      while ($data = <$fd>) {
 +                              chomp $data;
 +                              last if ($data =~ s/^\t//); # contents of line
 +                              if ($data =~ /^(\S+)(?: (.*))?$/) {
 +                                      $meta->{$1} = $2 unless exists $meta->{$1};
 +                              }
 +                              if ($data =~ /^previous /) {
 +                                      $meta->{'nprevious'}++;
 +                              }
                        }
 -              }
 -              my $short_rev = substr($full_rev, 0, 8);
 -              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 + 1) % $num_colors;
 -              }
 -              my $tr_class = $rev_color[$current_color];
 -              $tr_class .= ' boundary' if (exists $meta->{'boundary'});
 -              $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
 -              $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
 -              print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
 -              if ($group_size) {
 -                      print "<td class=\"sha1\"";
 -                      print " title=\"". esc_html($author) . ", $date\"";
 -                      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($short_rev));
 -                      if ($group_size >= 2) {
 -                              my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
 -                              if (@author_initials) {
 -                                      print "<br />" .
 -                                            esc_html(join('', @author_initials));
 -                                      #           or join('.', ...)
 +                      my $short_rev = substr($full_rev, 0, 8);
 +                      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 + 1) % $num_colors;
 +                      }
 +                      my $tr_class = $rev_color[$current_color];
 +                      $tr_class .= ' boundary' if (exists $meta->{'boundary'});
 +                      $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
 +                      $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
 +                      print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
 +                      if ($group_size) {
 +                              print "<td class=\"sha1\"";
 +                              print " title=\"". esc_html($author) . ", $date\"";
 +                              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($short_rev));
 +                              if ($group_size >= 2) {
 +                                      my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
 +                                      if (@author_initials) {
 +                                              print "<br />" .
 +                                                    esc_html(join('', @author_initials));
 +                                              #           or join('.', ...)
 +                                      }
                                }
 +                              print "</td>\n";
                        }
 -                      print "</td>\n";
 -              }
 -              # 'previous' <sha1 of parent commit> <filename at commit>
 -              if (exists $meta->{'previous'} &&
 -                  $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
 -                      $meta->{'parent'} = $1;
 -                      $meta->{'file_parent'} = unquote($2);
 -              }
 -              my $linenr_commit =
 -                      exists($meta->{'parent'}) ?
 -                      $meta->{'parent'} : $full_rev;
 -              my $linenr_filename =
 -                      exists($meta->{'file_parent'}) ?
 -                      $meta->{'file_parent'} : unquote($meta->{'filename'});
 -              my $blamed = href(action => 'blame',
 -                                file_name => $linenr_filename,
 -                                hash_base => $linenr_commit);
 -              print "<td class=\"linenr\">";
 -              print $cgi->a({ -href => "$blamed#l$orig_lineno",
 -                              -class => "linenr" },
 -                            esc_html($lineno));
 -              print "</td>";
 -              print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
 -              print "</tr>\n";
 +                      # 'previous' <sha1 of parent commit> <filename at commit>
 +                      if (exists $meta->{'previous'} &&
 +                          $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
 +                              $meta->{'parent'} = $1;
 +                              $meta->{'file_parent'} = unquote($2);
 +                      }
 +                      my $linenr_commit =
 +                              exists($meta->{'parent'}) ?
 +                              $meta->{'parent'} : $full_rev;
 +                      my $linenr_filename =
 +                              exists($meta->{'file_parent'}) ?
 +                              $meta->{'file_parent'} : unquote($meta->{'filename'});
 +                      my $blamed = href(action => 'blame',
 +                                        file_name => $linenr_filename,
 +                                        hash_base => $linenr_commit);
 +                      print "<td class=\"linenr\">";
 +                      print $cgi->a({ -href => "$blamed#l$orig_lineno",
 +                                      -class => "linenr" },
 +                                    esc_html($lineno));
 +                      print "</td>";
 +                      print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
 +                      print "</tr>\n";
 +              } # end while
 +
        }
 -      print "</table>\n";
 -      print "</div>";
 +
 +      # footer
 +      print "</tbody>\n".
 +            "</table>\n"; # class="blame"
 +      print "</div>\n";   # class="blame_body"
        close $fd
                or print "Reading blob failed\n";
  
 -      # page footer
        git_footer_html();
  }
  
 +sub git_blame {
 +      git_blame_common();
 +}
 +
 +sub git_blame_incremental {
 +      git_blame_common('incremental');
 +}
 +
 +sub git_blame_data {
 +      git_blame_common('data');
 +}
 +
  sub git_tags {
        my $head = git_get_head_hash($project);
        git_header_html();
@@@ -5313,14 -5052,14 +5320,14 @@@ sub git_blob 
        } else {
                print "<div class=\"page_nav\">\n" .
                      "<br/><br/></div>\n" .
-                     "<div class=\"title\">$hash</div>\n";
+                     "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
-               print qq!<img type="$mimetype"!;
+               print qq!<img type="!.esc_attr($mimetype).qq!"!;
                if ($file_name) {
-                       print qq! alt="$file_name" title="$file_name"!;
+                       print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
                print qq! src="! .
                      href(action=>"blob_plain", hash=>$hash,
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
+                       printf "<div class=\"pre\"><a id=\"l%i\" href=\""
+                               . esc_attr(href(-replay => 1))
                                . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
                               $nr, $nr, $nr, esc_html($line, -nbsp=>1);
                }
@@@ -5356,14 -5096,10 +5364,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
        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) {
                        # 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;
                print "<div class=\"page_nav\">\n";
                print "<br/><br/></div>\n";
-               print "<div class=\"title\">$hash</div>\n";
+               print "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
        if (defined $file_name) {
                $basedir = $file_name;
                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";
                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";
        git_footer_html();
  }
  
 +sub snapshot_name {
 +      my ($project, $hash) = @_;
 +
 +      # path/to/project.git  -> project
 +      # path/to/project/.git -> project
 +      my $name = to_utf8($project);
 +      $name =~ s,([^/])/*\.git$,$1,;
 +      $name = basename($name);
 +      # sanitize name
 +      $name =~ s/[[:cntrl:]]/?/g;
 +
 +      my $ver = $hash;
 +      if ($hash =~ /^[0-9a-fA-F]+$/) {
 +              # shorten SHA-1 hash
 +              my $full_hash = git_get_full_hash($project, $hash);
 +              if ($full_hash =~ /^$hash/ && length($hash) > 7) {
 +                      $ver = git_get_short_hash($project, $hash);
 +              }
 +      } elsif ($hash =~ m!^refs/tags/(.*)$!) {
 +              # tags don't need shortened SHA-1 hash
 +              $ver = $1;
 +      } else {
 +              # branches and other need shortened SHA-1 hash
 +              if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
 +                      $ver = $1;
 +              }
 +              $ver .= '-' . git_get_short_hash($project, $hash);
 +      }
 +      # in case of hierarchical branch names
 +      $ver =~ s!/!.!g;
 +
 +      # name = project-version_string
 +      $name = "$name-$ver";
 +
 +      return wantarray ? ($name, $name) : $name;
 +}
 +
  sub git_snapshot {
        my $format = $input_params{'snapshot_format'};
        if (!@snapshot_fmts) {
                die_error(403, "Unsupported snapshot format");
        }
  
 -      if (!defined $hash) {
 -              $hash = git_get_head_hash($project);
 +      my $type = git_get_type("$hash^{}");
 +      if (!$type) {
 +              die_error(404, 'Object does not exist');
 +      }  elsif ($type eq 'blob') {
 +              die_error(400, 'Object is not a tree-ish');
        }
  
 -      my $name = $project;
 -      $name =~ s,([^/])/*\.git$,$1,;
 -      $name = basename($name);
 -      my $filename = to_utf8($name);
 -      $name =~ s/\047/\047\\\047\047/g;
 -      my $cmd;
 -      $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
 -      $cmd = quote_command(
 +      my ($name, $prefix) = snapshot_name($project, $hash);
 +      my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
 +      my $cmd = quote_command(
                git_cmd(), 'archive',
                "--format=$known_snapshot_formats{$format}{'format'}",
 -              "--prefix=$name/", $hash);
 +              "--prefix=$prefix/", $hash);
        if (exists $known_snapshot_formats{$format}{'compressor'}) {
                $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
        }
  
 +      $filename =~ s/(["\\])/\\$1/g;
        print $cgi->header(
                -type => $known_snapshot_formats{$format}{'type'},
 -              -content_disposition => 'inline; filename="' . "$filename" . '"',
 +              -content_disposition => 'inline; filename="' . $filename . '"',
                -status => '200 OK');
  
        open my $fd, "-|", $cmd
        close $fd;
  }
  
 -sub git_log {
 +sub git_log_generic {
 +      my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
 +
        my $head = git_get_head_hash($project);
 -      if (!defined $hash) {
 -              $hash = $head;
 +      if (!defined $base) {
 +              $base = $head;
        }
        if (!defined $page) {
                $page = 0;
        }
        my $refs = git_get_references();
  
 -      my @commitlist = parse_commits($hash, 101, (100 * $page));
 +      my $commit_hash = $base;
 +      if (defined $parent) {
 +              $commit_hash = "$parent..$base";
 +      }
 +      my @commitlist =
 +              parse_commits($commit_hash, 101, (100 * $page),
 +                            defined $file_name ? ($file_name, "--full-history") : ());
  
 -      my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
 +      my $ftype;
 +      if (!defined $file_hash && defined $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++) {
 +                      $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
 +                      last if defined $file_hash;
 +              }
 +      }
 +      if (defined $file_hash) {
 +              $ftype = git_get_type($file_hash);
 +      }
 +      if (defined $file_name && !defined $ftype) {
 +              die_error(500, "Unknown type of object");
 +      }
 +      my %co;
 +      if (defined $file_name) {
 +              %co = parse_commit($base)
 +                      or die_error(404, "Unknown commit object");
 +      }
  
 -      my ($patch_max) = gitweb_get_feature('patches');
 -      if ($patch_max) {
 +
 +      my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
 +      my $next_link = '';
 +      if ($#commitlist >= 100) {
 +              $next_link =
 +                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 +                               -accesskey => "n", -title => "Alt-n"}, "next");
 +      }
 +      my $patch_max = gitweb_get_feature('patches');
 +      if ($patch_max && !defined $file_name) {
                if ($patch_max < 0 || @commitlist <= $patch_max) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"patches", -replay=>1)},
        }
  
        git_header_html();
 -      git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
 -
 -      if (!@commitlist) {
 -              my %co = parse_commit($hash);
 -
 -              git_print_header_div('summary', $project);
 -              print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
 +      git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
 +      if (defined $file_name) {
 +              git_print_header_div('commit', esc_html($co{'title'}), $base);
 +      } else {
 +              git_print_header_div('summary', $project)
        }
 -      my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
 -      for (my $i = 0; $i <= $to; $i++) {
 -              my %co = %{$commitlist[$i]};
 -              next if !%co;
 -              my $commit = $co{'id'};
 -              my $ref = format_ref_marker($refs, $commit);
 -              my %ad = parse_date($co{'author_epoch'});
 -              git_print_header_div('commit',
 -                             "<span class=\"age\">$co{'age_string'}</span>" .
 -                             esc_html($co{'title'}) . $ref,
 -                             $commit);
 -              print "<div class=\"title_text\">\n" .
 -                    "<div class=\"log_link\">\n" .
 -                    $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") .
 -                    "<br/>\n" .
 -                    "</div>\n";
 -                    git_print_authorship(\%co, -tag => 'span');
 -                    print "<br/>\n</div>\n";
 +      git_print_page_path($file_name, $ftype, $hash_base)
 +              if (defined $file_name);
 +
 +      $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
 +                   $file_name, $file_hash, $ftype);
  
 -              print "<div class=\"log_body\">\n";
 -              git_print_log($co{'comment'}, -final_empty_line=> 1);
 -              print "</div>\n";
 -      }
 -      if ($#commitlist >= 100) {
 -              print "<div class=\"page_nav\">\n";
 -              print $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                             -accesskey => "n", -title => "Alt-n"}, "next");
 -              print "</div>\n";
 -      }
        git_footer_html();
  }
  
 +sub git_log {
 +      git_log_generic('log', \&git_log_body,
 +                      $hash, $hash_parent);
 +}
 +
  sub git_commit {
        $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash)
@@@ -5864,7 -5551,7 +5872,7 @@@ sub git_blobdiff 
                        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
                } else {
                        print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
-                       print "<div class=\"title\">$hash vs $hash_parent</div>\n";
+                       print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
                }
                if (defined $file_name) {
                        git_print_page_path($file_name, "blob", $hash_base);
@@@ -6153,9 -5840,70 +6161,9 @@@ sub git_patches 
  }
  
  sub git_history {
 -      if (!defined $hash_base) {
 -              $hash_base = git_get_head_hash($project);
 -      }
 -      if (!defined $page) {
 -              $page = 0;
 -      }
 -      my $ftype;
 -      my %co = parse_commit($hash_base)
 -          or die_error(404, "Unknown commit object");
 -
 -      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")
 -          or die_error(404, "No such file or directory on given branch");
 -
 -      if (!defined $hash && defined $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);
 -      }
 -      if (!defined $ftype) {
 -              die_error(500, "Unknown type of object");
 -      }
 -
 -      my $paging_nav = '';
 -      if ($page > 0) {
 -              $paging_nav .=
 -                      $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
 -                                             file_name=>$file_name)},
 -                              "first");
 -              $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(-replay=>1, page=>$page-1),
 -                               -accesskey => "p", -title => "Alt-p"}, "prev");
 -      } else {
 -              $paging_nav .= "first";
 -              $paging_nav .= " &sdot; prev";
 -      }
 -      my $next_link = '';
 -      if ($#commitlist >= 100) {
 -              $next_link =
 -                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                               -accesskey => "n", -title => "Alt-n"}, "next");
 -              $paging_nav .= " &sdot; $next_link";
 -      } else {
 -              $paging_nav .= " &sdot; next";
 -      }
 -
 -      git_header_html();
 -      git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
 -      git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
 -      git_print_page_path($file_name, $ftype, $hash_base);
 -
 -      git_history_body(\@commitlist, 0, 99,
 -                       $refs, $hash_base, $ftype, $next_link);
 -
 -      git_footer_html();
 +      git_log_generic('history', \&git_history_body,
 +                      $hash_base, $hash_parent_base,
 +                      $file_name, $hash);
  }
  
  sub git_search {
  }
  
  sub git_shortlog {
 -      my $head = git_get_head_hash($project);
 -      if (!defined $hash) {
 -              $hash = $head;
 -      }
 -      if (!defined $page) {
 -              $page = 0;
 -      }
 -      my $refs = git_get_references();
 -
 -      my $commit_hash = $hash;
 -      if (defined $hash_parent) {
 -              $commit_hash = "$hash_parent..$hash";
 -      }
 -      my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
 -
 -      my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
 -      my $next_link = '';
 -      if ($#commitlist >= 100) {
 -              $next_link =
 -                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
 -                               -accesskey => "n", -title => "Alt-n"}, "next");
 -      }
 -      my $patch_max = gitweb_check_feature('patches');
 -      if ($patch_max) {
 -              if ($patch_max < 0 || @commitlist <= $patch_max) {
 -                      $paging_nav .= " &sdot; " .
 -                              $cgi->a({-href => href(action=>"patches", -replay=>1)},
 -                                      "patches");
 -              }
 -      }
 -
 -      git_header_html();
 -      git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
 -      git_print_header_div('summary', $project);
 -
 -      git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
 -
 -      git_footer_html();
 +      git_log_generic('shortlog', \&git_shortlog_body,
 +                      $hash, $hash_parent);
  }
  
  ## ......................................................................