Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Sun, 11 Nov 2007 23:00:05 +0000 (15:00 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 11 Nov 2007 23:00:05 +0000 (15:00 -0800)
* maint:
fix index-pack with packs >4GB containing deltas on 32-bit machines
git-hash-object should honor config variables
gitweb: correct month in date display for atom feeds

1  2 
gitweb/gitweb.perl
index-pack.c
diff --combined gitweb/gitweb.perl
index 759dff1cceafb3d67f662d3876405ffcf9ebf96c,9deefce0a61a3581b78047d6b38c4f506c4a3cfa..e788ef90c981e93f59400332da8bb7de1c2b3952
@@@ -35,10 -35,6 +35,10 @@@ our $GIT = "++GIT_BINDIR++/git"
  #our $projectroot = "/pub/scm";
  our $projectroot = "++GITWEB_PROJECTROOT++";
  
 +# fs traversing limit for getting project list
 +# the number is relative to the projectroot
 +our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
 +
  # target of the home link on top of all pages
  our $home_link = $my_uri || "/";
  
@@@ -611,15 -607,6 +611,15 @@@ sub href(%) 
        );
        my %mapping = @mapping;
  
 +      if ($params{-replay}) {
 +              while (my ($name, $symbol) = each %mapping) {
 +                      if (!exists $params{$name}) {
 +                              # to allow for multivalued params we use arrayref form
 +                              $params{$name} = [ $cgi->param($symbol) ];
 +                      }
 +              }
 +      }
 +
        $params{'project'} = $project unless exists $params{'project'};
  
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
@@@ -855,23 -842,6 +855,23 @@@ sub chop_str 
        return "$body$tail";
  }
  
 +# takes the same arguments as chop_str, but also wraps a <span> around the
 +# result with a title attribute if it does get chopped. Additionally, the
 +# string is HTML-escaped.
 +sub chop_and_escape_str {
 +      my $str = shift;
 +      my $len = shift;
 +      my $add_len = shift || 10;
 +
 +      my $chopped = chop_str($str, $len, $add_len);
 +      if ($chopped eq $str) {
 +              return esc_html($chopped);
 +      } else {
 +              return qq{<span title="} . esc_html($str) . qq{">} .
 +                      esc_html($chopped) . qq{</span>};
 +      }
 +}
 +
  ## ----------------------------------------------------------------------
  ## functions returning short strings
  
@@@ -1432,121 -1402,20 +1432,121 @@@ sub git_get_type 
        return $type;
  }
  
 +# repository configuration
 +our $config_file = '';
 +our %config;
 +
 +# store multiple values for single key as anonymous array reference
 +# single values stored directly in the hash, not as [ <value> ]
 +sub hash_set_multi {
 +      my ($hash, $key, $value) = @_;
 +
 +      if (!exists $hash->{$key}) {
 +              $hash->{$key} = $value;
 +      } elsif (!ref $hash->{$key}) {
 +              $hash->{$key} = [ $hash->{$key}, $value ];
 +      } else {
 +              push @{$hash->{$key}}, $value;
 +      }
 +}
 +
 +# return hash of git project configuration
 +# optionally limited to some section, e.g. 'gitweb'
 +sub git_parse_project_config {
 +      my $section_regexp = shift;
 +      my %config;
 +
 +      local $/ = "\0";
 +
 +      open my $fh, "-|", git_cmd(), "config", '-z', '-l',
 +              or return;
 +
 +      while (my $keyval = <$fh>) {
 +              chomp $keyval;
 +              my ($key, $value) = split(/\n/, $keyval, 2);
 +
 +              hash_set_multi(\%config, $key, $value)
 +                      if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
 +      }
 +      close $fh;
 +
 +      return %config;
 +}
 +
 +# convert config value to boolean, 'true' or 'false'
 +# no value, number > 0, 'true' and 'yes' values are true
 +# rest of values are treated as false (never as error)
 +sub config_to_bool {
 +      my $val = shift;
 +
 +      # strip leading and trailing whitespace
 +      $val =~ s/^\s+//;
 +      $val =~ s/\s+$//;
 +
 +      return (!defined $val ||               # section.key
 +              ($val =~ /^\d+$/ && $val) ||   # section.key = 1
 +              ($val =~ /^(?:true|yes)$/i));  # section.key = true
 +}
 +
 +# convert config value to simple decimal number
 +# an optional value suffix of 'k', 'm', or 'g' will cause the value
 +# to be multiplied by 1024, 1048576, or 1073741824
 +sub config_to_int {
 +      my $val = shift;
 +
 +      # strip leading and trailing whitespace
 +      $val =~ s/^\s+//;
 +      $val =~ s/\s+$//;
 +
 +      if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
 +              $unit = lc($unit);
 +              # unknown unit is treated as 1
 +              return $num * ($unit eq 'g' ? 1073741824 :
 +                             $unit eq 'm' ?    1048576 :
 +                             $unit eq 'k' ?       1024 : 1);
 +      }
 +      return $val;
 +}
 +
 +# convert config value to array reference, if needed
 +sub config_to_multi {
 +      my $val = shift;
 +
 +      return ref($val) ? $val : [ $val ];
 +}
 +
  sub git_get_project_config {
        my ($key, $type) = @_;
  
 +      # key sanity check
        return unless ($key);
        $key =~ s/^gitweb\.//;
        return if ($key =~ m/\W/);
  
 -      my @x = (git_cmd(), 'config');
 -      if (defined $type) { push @x, $type; }
 -      push @x, "--get";
 -      push @x, "gitweb.$key";
 -      my $val = qx(@x);
 -      chomp $val;
 -      return ($val);
 +      # type sanity check
 +      if (defined $type) {
 +              $type =~ s/^--//;
 +              $type = undef
 +                      unless ($type eq 'bool' || $type eq 'int');
 +      }
 +
 +      # get config
 +      if (!defined $config_file ||
 +          $config_file ne "$git_dir/config") {
 +              %config = git_parse_project_config('gitweb');
 +              $config_file = "$git_dir/config";
 +      }
 +
 +      # ensure given type
 +      if (!defined $type) {
 +              return $config{"gitweb.$key"};
 +      } elsif ($type eq 'bool') {
 +              # backward compatibility: 'git config --bool' returns true/false
 +              return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
 +      } elsif ($type eq 'int') {
 +              return config_to_int($config{"gitweb.$key"});
 +      }
 +      return $config{"gitweb.$key"};
  }
  
  # get hash of given path at given ref
@@@ -1606,9 -1475,7 +1606,9 @@@ sub git_get_path_by_hash 
  sub git_get_project_description {
        my $path = shift;
  
 -      open my $fd, "$projectroot/$path/description" or return undef;
 +      $git_dir = "$projectroot/$path";
 +      open my $fd, "$projectroot/$path/description"
 +              or return git_get_project_config('description');
        my $descr = <$fd>;
        close $fd;
        if (defined $descr) {
  sub git_get_project_url_list {
        my $path = shift;
  
 -      open my $fd, "$projectroot/$path/cloneurl" or return;
 +      $git_dir = "$projectroot/$path";
 +      open my $fd, "$projectroot/$path/cloneurl"
 +              or return wantarray ?
 +              @{ config_to_multi(git_get_project_config('url')) } :
 +                 config_to_multi(git_get_project_config('url'));
        my @git_project_url_list = map { chomp; $_ } <$fd>;
        close $fd;
  
@@@ -1646,7 -1509,6 +1646,7 @@@ sub git_get_projects_list 
                # remove the trailing "/"
                $dir =~ s!/+$!!;
                my $pfxlen = length("$dir");
 +              my $pfxdepth = ($dir =~ tr!/!!);
  
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
                                return unless (-d $_);
 +                              # don't traverse too deep (Find is super slow on os x)
 +                              if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
 +                                      $File::Find::prune = 1;
 +                                      return;
 +                              }
  
                                my $subdir = substr($File::Find::name, $pfxlen + 1);
                                # we check related file in $projectroot
@@@ -1856,7 -1713,7 +1856,7 @@@ sub parse_date 
        $date{'mday-time'} = sprintf "%d %s %02d:%02d",
                             $mday, $months[$mon], $hour ,$min;
        $date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
-                            1900+$year, $mon, $mday, $hour ,$min, $sec;
+                            1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
  
        $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
        my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@@ -2106,12 -1963,12 +2106,12 @@@ sub parse_difftree_raw_line 
                $res{'to_mode'} = $2;
                $res{'from_id'} = $3;
                $res{'to_id'} = $4;
 -              $res{'status'} = $5;
 +              $res{'status'} = $res{'status_str'} = $5;
                $res{'similarity'} = $6;
                if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
                        ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
                } else {
 -                      $res{'file'} = unquote($7);
 +                      $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
                }
        }
        # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
                $res{'to_mode'} = pop @{$res{'from_mode'}};
                $res{'from_id'} = [ split(' ', $3) ];
                $res{'to_id'} = pop @{$res{'from_id'}};
 +              $res{'status_str'} = $4;
                $res{'status'} = [ split('', $4) ];
                $res{'to_file'} = unquote($5);
        }
        return wantarray ? %res : \%res;
  }
  
 +# wrapper: return parsed line of git-diff-tree "raw" output
 +# (the argument might be raw line, or parsed info)
 +sub parsed_difftree_line {
 +      my $line_or_ref = shift;
 +
 +      if (ref($line_or_ref) eq "HASH") {
 +              # pre-parsed (or generated by hand)
 +              return $line_or_ref;
 +      } else {
 +              return parse_difftree_raw_line($line_or_ref);
 +      }
 +}
 +
  # parse line of git-ls-tree output
  sub parse_ls_tree_line ($;%) {
        my $line = shift;
@@@ -2179,10 -2022,7 +2179,10 @@@ sub parse_from_to_diffinfo 
                fill_from_file_info($diffinfo, @parents)
                        unless exists $diffinfo->{'from_file'};
                for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
 -                      $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
 +                      $from->{'file'}[$i] =
 +                              defined $diffinfo->{'from_file'}[$i] ?
 +                                      $diffinfo->{'from_file'}[$i] :
 +                                      $diffinfo->{'to_file'};
                        if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
                                $from->{'href'}[$i] = href(action=>"blob",
                                                           hash_base=>$parents[$i],
                        }
                }
        } else {
 -              $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
 +              # ordinary (not combined) diff
 +              $from->{'file'} = $diffinfo->{'from_file'};
                if ($diffinfo->{'status'} ne "A") { # not new (added) file
                        $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
                                               hash=>$diffinfo->{'from_id'},
                }
        }
  
 -      $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
 +      $to->{'file'} = $diffinfo->{'to_file'};
        if (!is_deleted($diffinfo)) { # file exists in result
                $to->{'href'} = href(action=>"blob", hash_base=>$hash,
                                     hash=>$diffinfo->{'to_id'},
@@@ -2625,7 -2464,7 +2625,7 @@@ sub format_paging_nav 
  
        if ($page > 0) {
                $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1),
 +                      $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                $paging_nav .= " &sdot; prev";
  
        if ($nrevs >= (100 * ($page+1)-1)) {
                $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1),
 +                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
        } else {
                $paging_nav .= " &sdot; next";
@@@ -2917,7 -2756,6 +2917,7 @@@ sub git_print_tree_entry 
  ## ......................................................................
  ## functions printing large fragments of HTML
  
 +# get pre-image filenames for merge (combined) diff
  sub fill_from_file_info {
        my ($diff, @parents) = @_;
  
        return $diff;
  }
  
 -# parameters can be strings, or references to arrays of strings
 -sub from_ids_eq {
 -      my ($a, $b) = @_;
 +# is current raw difftree line of file deletion
 +sub is_deleted {
 +      my $diffinfo = shift;
  
 -      if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) {
 -              for (my $i = 0; $i < @$a; ++$i) {
 -                      return 0 unless ($a->[$i] eq $b->[$i]);
 -              }
 -              return 1;
 -      } elsif (!ref($a) && !ref($b)) {
 -              return $a eq $b;
 -      } else {
 -              return 0;
 -      }
 +      return $diffinfo->{'status_str'} =~ /D/;
  }
  
 -sub is_deleted {
 -      my $diffinfo = shift;
 +# does patch correspond to [previous] difftree raw line
 +# $diffinfo  - hashref of parsed raw diff format
 +# $patchinfo - hashref of parsed patch diff format
 +#              (the same keys as in $diffinfo)
 +sub is_patch_split {
 +      my ($diffinfo, $patchinfo) = @_;
  
 -      return $diffinfo->{'to_id'} eq ('0' x 40);
 +      return defined $diffinfo && defined $patchinfo
 +              && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
  }
  
 +
  sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
        my ($parent) = $parents[0];
        my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
 -              my $diff;
 -              if (ref($line) eq "HASH") {
 -                      # pre-parsed (or generated by hand)
 -                      $diff = $line;
 -              } else {
 -                      $diff = parse_difftree_raw_line($line);
 -              }
 +              my $diff = parsed_difftree_line($line);
  
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@@ -3260,12 -3107,10 +3260,12 @@@ sub git_patchset_body 
        my ($fd, $difftree, $hash, @hash_parents) = @_;
        my ($hash_parent) = $hash_parents[0];
  
 +      my $is_combined = (@hash_parents > 1);
        my $patch_idx = 0;
        my $patch_number = 0;
        my $patch_line;
        my $diffinfo;
 +      my $to_name;
        my (%from, %to);
  
        print "<div class=\"patchset\">\n";
  
   PATCH:
        while ($patch_line) {
 -              my @diff_header;
 -              my ($from_id, $to_id);
 -
 -              # git diff header
 -              #assert($patch_line =~ m/^diff /) if DEBUG;
 -              #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
 -              $patch_number++;
 -              push @diff_header, $patch_line;
 -
 -              # extended diff header
 -      EXTENDED_HEADER:
 -              while ($patch_line = <$fd>) {
 -                      chomp $patch_line;
 -
 -                      last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
 -
 -                      if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
 -                              $from_id = $1;
 -                              $to_id   = $2;
 -                      } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
 -                              $from_id = [ split(',', $1) ];
 -                              $to_id   = $2;
 -                      }
  
 -                      push @diff_header, $patch_line;
 +              # parse "git diff" header line
 +              if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
 +                      # $1 is from_name, which we do not use
 +                      $to_name = unquote($2);
 +                      $to_name =~ s!^b/!!;
 +              } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
 +                      # $1 is 'cc' or 'combined', which we do not use
 +                      $to_name = unquote($2);
 +              } else {
 +                      $to_name = undef;
                }
 -              my $last_patch_line = $patch_line;
  
                # check if current patch belong to current raw line
                # and parse raw git-diff line if needed
 -              if (defined $diffinfo &&
 -                  defined $from_id && defined $to_id &&
 -                  from_ids_eq($diffinfo->{'from_id'}, $from_id) &&
 -                  $diffinfo->{'to_id'} eq $to_id) {
 +              if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
                        # this is continuation of a split patch
                        print "<div class=\"patch cont\">\n";
                } else {
                        # advance raw git-diff output if needed
                        $patch_idx++ if defined $diffinfo;
  
 -                      # compact combined diff output can have some patches skipped
 -                      # find which patch (using pathname of result) we are at now
 -                      my $to_name;
 -                      if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
 -                              $to_name = $1;
 -                      }
 -
 -                      do {
 -                              # read and prepare patch information
 -                              if (ref($difftree->[$patch_idx]) eq "HASH") {
 -                                      # pre-parsed (or generated by hand)
 -                                      $diffinfo = $difftree->[$patch_idx];
 -                              } else {
 -                                      $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
 -                              }
 +                      # read and prepare patch information
 +                      $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
  
 -                              # check if current raw line has no patch (it got simplified)
 -                              if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
 +                      # compact combined diff output can have some patches skipped
 +                      # find which patch (using pathname of result) we are at now;
 +                      if ($is_combined) {
 +                              while ($to_name ne $diffinfo->{'to_file'}) {
                                        print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
                                              format_diff_cc_simplified($diffinfo, @hash_parents) .
                                              "</div>\n";  # class="patch"
  
                                        $patch_idx++;
                                        $patch_number++;
 +
 +                                      last if $patch_idx > $#$difftree;
 +                                      $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
                                }
 -                      } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
 -                               $patch_idx > $#$difftree);
 +                      }
  
                        # modifies %from, %to hashes
                        parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
                        print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
                }
  
 +              # git diff header
 +              #assert($patch_line =~ m/^diff /) if DEBUG;
 +              #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
 +              $patch_number++;
                # print "git diff" header
 -              $patch_line = shift @diff_header;
                print format_git_diff_header_line($patch_line, $diffinfo,
                                                  \%from, \%to);
  
                # print extended diff header
 -              print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
 +              print "<div class=\"diff extended_header\">\n";
        EXTENDED_HEADER:
 -              foreach $patch_line (@diff_header) {
 +              while ($patch_line = <$fd>) {
 +                      chomp $patch_line;
 +
 +                      last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
 +
                        print format_extended_diff_header_line($patch_line, $diffinfo,
                                                               \%from, \%to);
                }
 -              print "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
 +              print "</div>\n"; # class="diff extended_header"
  
                # from-file/to-file diff header
 -              $patch_line = $last_patch_line;
                if (! $patch_line) {
                        print "</div>\n"; # class="patch"
                        last PATCH;
                }
                next PATCH if ($patch_line =~ m/^diff /);
                #assert($patch_line =~ m/^---/) if DEBUG;
 -              #assert($patch_line eq $last_patch_line) if DEBUG;
  
 +              my $last_patch_line = $patch_line;
                $patch_line = <$fd>;
                chomp $patch_line;
                #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
  
        # for compact combined (--cc) format, with chunk and patch simpliciaction
        # patchset might be empty, but there might be unprocessed raw lines
 -      for ($patch_idx++ if $patch_number > 0;
 +      for (++$patch_idx if $patch_number > 0;
             $patch_idx < @$difftree;
 -           $patch_idx++) {
 +           ++$patch_idx) {
                # read and prepare patch information
 -              if (ref($difftree->[$patch_idx]) eq "HASH") {
 -                      # pre-parsed (or generated by hand)
 -                      $diffinfo = $difftree->[$patch_idx];
 -              } else {
 -                      $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
 -              }
 +              $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
  
                # generate anchor for "patch" links in difftree / whatchanged part
                print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
@@@ -3514,7 -3385,7 +3514,7 @@@ sub git_project_list_body 
                      "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
                                        -class => "list", -title => $pr->{'descr_long'}},
                                        esc_html($pr->{'descr'})) . "</td>\n" .
 -                    "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n";
 +                    "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
                print "<td class=\"". age_class($pr->{'age'}) . "\">" .
                      (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
                      "<td class=\"link\">" .
@@@ -3556,10 -3427,9 +3556,10 @@@ sub git_shortlog_body 
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
 +              my $author = chop_and_escape_str($co{'author_name'}, 10);
                # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 -                    "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
 +                    "<td><i>" . $author . "</i></td>\n" .
                      "<td>";
                print format_subject_html($co{'title'}, $co{'title_short'},
                                          href(action=>"commit", hash=>$commit), $ref);
@@@ -3607,10 -3477,9 +3607,10 @@@ sub git_history_body 
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
 +      # shortlog uses      chop_str($co{'author_name'}, 10)
 +              my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 -                    # shortlog uses      chop_str($co{'author_name'}, 10)
 -                    "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
 +                    "<td><i>" . $author . "</i></td>\n" .
                      "<td>";
                # originally git_history used chop_str($co{'title'}, 50)
                print format_subject_html($co{'title'}, $co{'title_short'},
@@@ -3764,12 -3633,11 +3764,12 @@@ sub git_search_grep_body 
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
 +              my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 -                    "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
 +                    "<td><i>" . $author . "</i></td>\n" .
                      "<td>" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
 -                             esc_html(chop_str($co{'title'}, 50)) . "<br/>");
 +                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
                        if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
@@@ -4018,11 -3886,11 +4018,11 @@@ sub git_blame2 
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
 -              $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
 +              $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
                " | " .
 -              $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
 -                      "history") .
 +              $cgi->a({-href => href(action=>"history", -replay=>1)},
 +                      "history") .
                " | " .
                $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
                        "HEAD");
@@@ -4298,15 -4166,18 +4298,15 @@@ sub git_blob 
                if (defined $file_name) {
                        if ($have_blame) {
                                $formats_nav .=
 -                                      $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base,
 -                                                             hash=>$hash, file_name=>$file_name)},
 +                                      $cgi->a({-href => href(action=>"blame", -replay=>1)},
                                                "blame") .
                                        " | ";
                        }
                        $formats_nav .=
 -                              $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
 -                                                     hash=>$hash, file_name=>$file_name)},
 +                              $cgi->a({-href => href(action=>"history", -replay=>1)},
                                        "history") .
                                " | " .
 -                              $cgi->a({-href => href(action=>"blob_plain",
 -                                                     hash=>$hash, file_name=>$file_name)},
 +                              $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
                                        "raw") .
                                " | " .
                                $cgi->a({-href => href(action=>"blob",
                                        "HEAD");
                } else {
                        $formats_nav .=
 -                              $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw");
 +                              $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
 +                                      "raw");
                }
                git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
                git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
@@@ -4378,7 -4248,8 +4378,7 @@@ sub git_tree 
                my @views_nav = ();
                if (defined $file_name) {
                        push @views_nav,
 -                              $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
 -                                                     hash=>$hash, file_name=>$file_name)},
 +                              $cgi->a({-href => href(action=>"history", -replay=>1)},
                                        "history"),
                                $cgi->a({-href => href(action=>"tree",
                                                       hash_base=>"HEAD", file_name=>$file_name)},
@@@ -4552,7 -4423,7 +4552,7 @@@ sub git_log 
        }
        if ($#commitlist >= 100) {
                print "<div class=\"page_nav\">\n";
 -              print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
 +              print $cgi->a({-href => href(-replay=>1, page=>$page+1),
                               -accesskey => "n", -title => "Alt-n"}, "next");
                print "</div>\n";
        }
@@@ -4784,8 -4655,8 +4784,8 @@@ sub git_blobdiff 
                }
  
                %diffinfo = parse_difftree_raw_line($difftree[0]);
 -              $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'};
 -              $file_name   ||= $diffinfo{'to_file'}   || $diffinfo{'file'};
 +              $file_parent ||= $diffinfo{'from_file'} || $file_name;
 +              $file_name   ||= $diffinfo{'to_file'};
  
                $hash_parent ||= $diffinfo{'from_id'};
                $hash        ||= $diffinfo{'to_id'};
        # header
        if ($format eq 'html') {
                my $formats_nav =
 -                      $cgi->a({-href => href(action=>"blobdiff_plain",
 -                                             hash=>$hash, hash_parent=>$hash_parent,
 -                                             hash_base=>$hash_base, hash_parent_base=>$hash_parent_base,
 -                                             file_name=>$file_name, file_parent=>$file_parent)},
 +                      $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
                                "raw");
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@@ -4920,7 -4794,8 +4920,7 @@@ sub git_commitdiff 
        my $formats_nav;
        if ($format eq 'html') {
                $formats_nav =
 -                      $cgi->a({-href => href(action=>"commitdiff_plain",
 -                                             hash=>$hash, hash_parent=>$hash_parent)},
 +                      $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
  
                if (defined $hash_parent &&
@@@ -5115,20 -4990,27 +5115,20 @@@ sub git_history 
                                               file_name=>$file_name)},
                                "first");
                $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
 -                                             file_name=>$file_name, page=>$page-1),
 +                      $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                $paging_nav .= "first";
                $paging_nav .= " &sdot; prev";
        }
 -      if ($#commitlist >= 100) {
 -              $paging_nav .= " &sdot; " .
 -                      $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
 -                                             file_name=>$file_name, page=>$page+1),
 -                               -accesskey => "n", -title => "Alt-n"}, "next");
 -      } else {
 -              $paging_nav .= " &sdot; next";
 -      }
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =
 -                      $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
 -                                             file_name=>$file_name, page=>$page+1),
 +                      $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();
@@@ -5198,23 -5080,30 +5198,23 @@@ sub git_search 
                                                       searchtext=>$searchtext, searchtype=>$searchtype)},
                                        "first");
                        $paging_nav .= " &sdot; " .
 -                              $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page-1),
 +                              $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) {
 -                      $paging_nav .= " &sdot; " .
 -                              $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page+1),
 +                      $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";
                }
 -              my $next_link = '';
 +
                if ($#commitlist >= 100) {
 -                      $next_link =
 -                              $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page+1),
 -                                       -accesskey => "n", -title => "Alt-n"}, "next");
                }
  
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
                                                print "<tr class=\"light\">\n";
                                        }
                                        $alternate ^= 1;
 +                                      my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
                                        print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 -                                            "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
 +                                            "<td><i>" . $author . "</i></td>\n" .
                                              "<td>" .
                                              $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
                                                      -class => "list subject"},
 -                                                    esc_html(chop_str($co{'title'}, 50)) . "<br/>");
 +                                                    chop_and_escape_str($co{'title'}, 50) . "<br/>");
                                        while (my $setref = shift @files) {
                                                my %set = %$setref;
                                                print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
@@@ -5413,7 -5301,7 +5413,7 @@@ sub git_shortlog 
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =
 -                      $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
 +                      $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
        }
  
diff --combined index-pack.c
index 715a5bb7a6e42694b3e35de703e44e792a5442fe,c232e3fc78a93a7080893976f33b1c88db2edadf..3c99a1fce97c387ec1dfc76649699a8095e76d81
@@@ -46,7 -46,7 +46,7 @@@ static int nr_resolved_deltas
  static int from_stdin;
  static int verbose;
  
 -static struct progress progress;
 +static struct progress *progress;
  
  /* We always read in 4kB chunks. */
  static unsigned char input_buffer[4096];
@@@ -87,8 -87,6 +87,8 @@@ static void *fill(int min
                                die("early EOF");
                        die("read error on input: %s", strerror(errno));
                }
 +              if (from_stdin)
 +                      display_throughput(progress, ret);
                input_len += ret;
        } while (input_len < min);
        return input_buffer;
@@@ -108,7 -106,7 +108,7 @@@ static void use(int bytes
        consumed_bytes += bytes;
  }
  
 -static const char *open_pack_file(const char *pack_name)
 +static char *open_pack_file(char *pack_name)
  {
        if (from_stdin) {
                input_fd = 0;
@@@ -256,7 -254,7 +256,7 @@@ static void *unpack_raw_entry(struct ob
  
  static void *get_data_from_pack(struct object_entry *obj)
  {
-       unsigned long from = obj[0].idx.offset + obj[0].hdr_size;
+       off_t from = obj[0].idx.offset + obj[0].hdr_size;
        unsigned long len = obj[1].idx.offset - from;
        unsigned long rdy = 0;
        unsigned char *src, *data;
@@@ -408,9 -406,7 +408,9 @@@ static void parse_pack_objects(unsigne
         * - remember base (SHA1 or offset) for all deltas.
         */
        if (verbose)
 -              start_progress(&progress, "Indexing %u objects...", "", nr_objects);
 +              progress = start_progress(
 +                              from_stdin ? "Receiving objects" : "Indexing objects",
 +                              nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                data = unpack_raw_entry(obj, &delta->base);
                } else
                        sha1_object(data, obj->size, obj->type, obj->idx.sha1);
                free(data);
 -              if (verbose)
 -                      display_progress(&progress, i+1);
 +              display_progress(progress, i+1);
        }
        objects[i].idx.offset = consumed_bytes;
 -      if (verbose)
 -              stop_progress(&progress);
 +      stop_progress(&progress);
  
        /* Check pack integrity */
        flush();
         *   for some more deltas.
         */
        if (verbose)
 -              start_progress(&progress, "Resolving %u deltas...", "", nr_deltas);
 +              progress = start_progress("Resolving deltas", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                union delta_base base;
                                                      obj->size, obj->type);
                        }
                free(data);
 -              if (verbose)
 -                      display_progress(&progress, nr_resolved_deltas);
 +              display_progress(progress, nr_resolved_deltas);
        }
  }
  
@@@ -595,7 -594,8 +595,7 @@@ static void fix_unresolved_deltas(int n
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
                append_obj_to_pack(d->base.sha1, data, size, type);
                free(data);
 -              if (verbose)
 -                      display_progress(&progress, nr_resolved_deltas);
 +              display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
  }
@@@ -683,31 -683,18 +683,31 @@@ static void final(const char *final_pac
        }
  }
  
 +static int git_index_pack_config(const char *k, const char *v)
 +{
 +      if (!strcmp(k, "pack.indexversion")) {
 +              pack_idx_default_version = git_config_int(k, v);
 +              if (pack_idx_default_version > 2)
 +                      die("bad pack.indexversion=%d", pack_idx_default_version);
 +              return 0;
 +      }
 +      return git_default_config(k, v);
 +}
 +
  int main(int argc, char **argv)
  {
        int i, fix_thin_pack = 0;
 -      const char *curr_pack, *pack_name = NULL;
 -      const char *curr_index, *index_name = NULL;
 +      char *curr_pack, *pack_name = NULL;
 +      char *curr_index, *index_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
        struct pack_idx_entry **idx_objects;
        unsigned char sha1[20];
  
 +      git_config(git_index_pack_config);
 +
        for (i = 1; i < argc; i++) {
 -              const char *arg = argv[i];
 +              char *arg = argv[i];
  
                if (*arg == '-') {
                        if (!strcmp(arg, "--stdin")) {
        deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
        parse_pack_objects(sha1);
        if (nr_deltas == nr_resolved_deltas) {
 -              if (verbose)
 -                      stop_progress(&progress);
 +              stop_progress(&progress);
                /* Flush remaining pack final 20-byte SHA1. */
                flush();
        } else {
                                           (nr_objects + nr_unresolved + 1)
                                           * sizeof(*objects));
                        fix_unresolved_deltas(nr_unresolved);
 -                      if (verbose) {
 -                              stop_progress(&progress);
 +                      stop_progress(&progress);
 +                      if (verbose)
                                fprintf(stderr, "%d objects were added to complete this thin pack.\n",
                                        nr_objects - nr_objects_initial);
 -                      }
                        fixup_pack_header_footer(output_fd, sha1,
                                curr_pack, nr_objects);
                }
        free(objects);
        free(index_name_buf);
        free(keep_name_buf);
 +      if (pack_name == NULL)
 +              free(curr_pack);
 +      if (index_name == NULL)
 +              free(curr_index);
  
        return 0;
  }