Code

GIT 1.5.5.6 v1.5.5.6
authorJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2008 06:08:22 +0000 (22:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2008 06:08:28 +0000 (22:08 -0800)
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1  2 
Documentation/RelNotes-1.5.5.6.txt
RelNotes
gitweb/gitweb.perl

index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d5e85cb70ebe385caee5b3cd908cc0cd9345165f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,10 @@@
++GIT v1.5.5.6 Release Notes
++==========================
++
++Fixes since 1.5.5.5
++-------------------
++
++ * Removed support for an obsolete gitweb request URI, whose
++   implementation ran "git diff" Porcelain, instead of using plumbing,
++   which would have run an external diff command specified in the
++   repository configuration as the gitweb user.
diff --combined RelNotes
index 61a6d5cf29195c3fb753cfa36a19f4eff76fc825,8fbe7db0e2a61ae4952f611b02d5a61bbcaf2b77..d94e74259b6dcd8b4645425b6381b050d24646af
+++ b/RelNotes
@@@ -1,1 -1,1 +1,1 @@@
- Documentation/RelNotes-1.5.5.5.txt
 -Documentation/RelNotes-1.5.4.7.txt
++Documentation/RelNotes-1.5.5.6.txt
diff --combined gitweb/gitweb.perl
index 345acf4520c286ff17a09625ef18c0c0e23621c4,86a6ced11a382ffe7b229b5f796c9cd8ffd4e845..f37b687acb0fedad9386f829f632d340385ab0d4
@@@ -472,15 -472,13 +472,15 @@@ if (defined $searchtype) 
        }
  }
  
 +our $search_use_regexp = $cgi->param('sr');
 +
  our $searchtext = $cgi->param('s');
  our $search_regexp;
  if (defined $searchtext) {
        if (length($searchtext) < 2) {
                die_error(undef, "At least two characters are required for search parameter");
        }
 -      $search_regexp = quotemeta $searchtext;
 +      $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
  }
  
  # now read PATH_INFO and use it as alternative to parameters
@@@ -506,7 -504,7 +506,7 @@@ sub evaluate_path_info 
        }
        # do not change any parameters if an action is given using the query string
        return if $action;
 -      $path_info =~ s,^$project/*,,;
 +      $path_info =~ s,^\Q$project\E/*,,;
        my ($refname, $pathname) = split(/:/, $path_info, 2);
        if (defined $pathname) {
                # we got "project.git/branch:filename" or "project.git/branch:dir/"
@@@ -610,7 -608,6 +610,7 @@@ sub href(%) 
                searchtype => "st",
                snapshot_format => "sf",
                extra_options => "opt",
 +              search_use_regexp => "sr",
        );
        my %mapping = @mapping;
  
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
        if ($use_pathinfo) {
                # use PATH_INFO for project name
 -              $href .= "/$params{'project'}" if defined $params{'project'};
 +              $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
                delete $params{'project'};
  
                # Summary just uses the project path URL
@@@ -756,40 -753,29 +756,40 @@@ sub esc_path 
  # Make control characters "printable", using character escape codes (CEC)
  sub quot_cec {
        my $cntrl = shift;
 +      my %opts = @_;
        my %es = ( # character escape codes, aka escape sequences
 -                 "\t" => '\t',   # tab            (HT)
 -                 "\n" => '\n',   # line feed      (LF)
 -                 "\r" => '\r',   # carrige return (CR)
 -                 "\f" => '\f',   # form feed      (FF)
 -                 "\b" => '\b',   # backspace      (BS)
 -                 "\a" => '\a',   # alarm (bell)   (BEL)
 -                 "\e" => '\e',   # escape         (ESC)
 -                 "\013" => '\v', # vertical tab   (VT)
 -                 "\000" => '\0', # nul character  (NUL)
 -                 );
 +              "\t" => '\t',   # tab            (HT)
 +              "\n" => '\n',   # line feed      (LF)
 +              "\r" => '\r',   # carrige return (CR)
 +              "\f" => '\f',   # form feed      (FF)
 +              "\b" => '\b',   # backspace      (BS)
 +              "\a" => '\a',   # alarm (bell)   (BEL)
 +              "\e" => '\e',   # escape         (ESC)
 +              "\013" => '\v', # vertical tab   (VT)
 +              "\000" => '\0', # nul character  (NUL)
 +      );
        my $chr = ( (exists $es{$cntrl})
                    ? $es{$cntrl}
                    : sprintf('\%03o', ord($cntrl)) );
 -      return "<span class=\"cntrl\">$chr</span>";
 +      if ($opts{-nohtml}) {
 +              return $chr;
 +      } else {
 +              return "<span class=\"cntrl\">$chr</span>";
 +      }
  }
  
  # Alternatively use unicode control pictures codepoints,
  # Unicode "printable representation" (PR)
  sub quot_upr {
        my $cntrl = shift;
 +      my %opts = @_;
 +
        my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
 -      return "<span class=\"cntrl\">$chr</span>";
 +      if ($opts{-nohtml}) {
 +              return $chr;
 +      } else {
 +              return "<span class=\"cntrl\">$chr</span>";
 +      }
  }
  
  # git may return quoted and escaped filenames
@@@ -814,7 -800,7 +814,7 @@@ sub unquote 
                        return chr(oct($seq));
                } elsif (exists $es{$seq}) {
                        # C escape sequence, aka character escape code
 -                      return $es{$seq}
 +                      return $es{$seq};
                }
                # quoted ordinary character
                return $seq;
@@@ -851,78 -837,37 +851,78 @@@ sub project_in_list 
  ## ----------------------------------------------------------------------
  ## HTML aware string manipulation
  
 +# Try to chop given string on a word boundary between position
 +# $len and $len+$add_len. If there is no word boundary there,
 +# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
 +# (marking chopped part) would be longer than given string.
  sub chop_str {
        my $str = shift;
        my $len = shift;
        my $add_len = shift || 10;
 +      my $where = shift || 'right'; # 'left' | 'center' | 'right'
  
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
 -      $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
 -      my $body = $1;
 -      my $tail = $2;
 -      if (length($tail) > 4) {
 -              $tail = " ...";
 -              $body =~ s/&[^;]*$//; # remove chopped character entities
 +      # remove chopped character entities entirely
 +
 +      # when chopping in the middle, distribute $len into left and right part
 +      # return early if chopping wouldn't make string shorter
 +      if ($where eq 'center') {
 +              return $str if ($len + 5 >= length($str)); # filler is length 5
 +              $len = int($len/2);
 +      } else {
 +              return $str if ($len + 4 >= length($str)); # filler is length 4
 +      }
 +
 +      # regexps: ending and beginning with word part up to $add_len
 +      my $endre = qr/.{$len}\w{0,$add_len}/;
 +      my $begre = qr/\w{0,$add_len}.{$len}/;
 +
 +      if ($where eq 'left') {
 +              $str =~ m/^(.*?)($begre)$/;
 +              my ($lead, $body) = ($1, $2);
 +              if (length($lead) > 4) {
 +                      $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
 +                      $lead = " ...";
 +              }
 +              return "$lead$body";
 +
 +      } elsif ($where eq 'center') {
 +              $str =~ m/^($endre)(.*)$/;
 +              my ($left, $str)  = ($1, $2);
 +              $str =~ m/^(.*?)($begre)$/;
 +              my ($mid, $right) = ($1, $2);
 +              if (length($mid) > 5) {
 +                      $left  =~ s/&[^;]*$//;
 +                      $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
 +                      $mid = " ... ";
 +              }
 +              return "$left$mid$right";
 +
 +      } else {
 +              $str =~ m/^($endre)(.*)$/;
 +              my $body = $1;
 +              my $tail = $2;
 +              if (length($tail) > 4) {
 +                      $body =~ s/&[^;]*$//;
 +                      $tail = "... ";
 +              }
 +              return "$body$tail";
        }
 -      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 ($str) = @_;
  
 -      my $chopped = chop_str($str, $len, $add_len);
 +      my $chopped = chop_str(@_);
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
 -              return qq{<span title="} . esc_html($str) . qq{">} .
 -                      esc_html($chopped) . qq{</span>};
 +              $str =~ s/([[:cntrl:]])/?/g;
 +              return $cgi->span({-title=>$str}, esc_html($chopped));
        }
  }
  
@@@ -1675,7 -1620,7 +1675,7 @@@ sub git_get_project_url_list 
        my $path = shift;
  
        $git_dir = "$projectroot/$path";
 -      open my $fd, "$projectroot/$path/cloneurl"
 +      open my $fd, "$git_dir/cloneurl"
                or return wantarray ?
                @{ config_to_multi(git_get_project_config('url')) } :
                   config_to_multi(git_get_project_config('url'));
@@@ -1814,7 -1759,6 +1814,7 @@@ sub git_get_project_owner 
        my $owner;
  
        return undef unless $project;
 +      $git_dir = "$projectroot/$project";
  
        if (!defined $gitweb_project_owner) {
                git_get_project_list_from_file();
        if (exists $gitweb_project_owner->{$project}) {
                $owner = $gitweb_project_owner->{$project};
        }
 +      if (!defined $owner){
 +              $owner = git_get_project_config('owner');
 +      }
        if (!defined $owner) {
 -              $owner = get_file_owner("$projectroot/$project");
 +              $owner = get_file_owner("$git_dir");
        }
  
        return $owner;
@@@ -2082,7 -2023,7 +2082,7 @@@ sub parse_commit 
  }
  
  sub parse_commits {
 -      my ($commit_id, $maxcount, $skip, $arg, $filename) = @_;
 +      my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
        my @cos;
  
        $maxcount ||= 1;
  
        open my $fd, "-|", git_cmd(), "rev-list",
                "--header",
 -              ($arg ? ($arg) : ()),
 +              @args,
                ("--max-count=" . $maxcount),
                ("--skip=" . $skip),
                @extra_options,
@@@ -2164,7 -2105,7 +2164,7 @@@ sub parse_difftree_raw_line 
                $res{'to_mode'} = $2;
                $res{'from_id'} = $3;
                $res{'to_id'} = $4;
 -              $res{'status'} = $res{'status_str'} = $5;
 +              $res{'status'} = $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);
                $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);
        }
@@@ -2570,7 -2512,7 +2570,7 @@@ EO
                my $action = $my_uri;
                my ($use_pathinfo) = gitweb_check_feature('pathinfo');
                if ($use_pathinfo) {
 -                      $action .= "/$project";
 +                      $action .= "/".esc_url($project);
                } else {
                        $cgi->param("p", $project);
                }
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
                      " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
 +                    "<span title=\"Extended regular expression\">" .
 +                    $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
 +                                   -checked => $search_use_regexp) .
 +                    "</span>" .
                      "</div>" .
                      $cgi->end_form() . "\n";
        }
@@@ -2676,7 -2614,7 +2676,7 @@@ sub git_print_page_nav 
  }
  
  sub format_paging_nav {
 -      my ($action, $hash, $head, $page, $nrevs) = @_;
 +      my ($action, $hash, $head, $page, $has_next_link) = @_;
        my $paging_nav;
  
  
                $paging_nav .= " &sdot; prev";
        }
  
 -      if ($nrevs >= (100 * ($page+1)-1)) {
 +      if ($has_next_link) {
                $paging_nav .= " &sdot; " .
                        $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
@@@ -3001,7 -2939,7 +3001,7 @@@ sub fill_from_file_info 
  sub is_deleted {
        my $diffinfo = shift;
  
 -      return $diffinfo->{'status_str'} =~ /D/;
 +      return $diffinfo->{'to_id'} eq ('0' x 40);
  }
  
  # does patch correspond to [previous] difftree raw line
@@@ -3831,24 -3769,18 +3831,24 @@@ sub git_search_grep_body 
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
                      "<td><i>" . $author . "</i></td>\n" .
                      "<td>" .
 -                    $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
 -                             chop_and_escape_str($co{'title'}, 50) . "<br/>");
 +                    $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
 +                             -class => "list subject"},
 +                            chop_and_escape_str($co{'title'}, 50) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
 -                      if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
 -                              my $lead = esc_html($1) || "";
 -                              $lead = chop_str($lead, 30, 10);
 -                              my $match = esc_html($2) || "";
 -                              my $trail = esc_html($3) || "";
 -                              $trail = chop_str($trail, 30, 10);
 -                              my $text = "$lead<span class=\"match\">$match</span>$trail";
 -                              print chop_str($text, 80, 5) . "<br/>\n";
 +                      if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
 +                              my ($lead, $match, $trail) = ($1, $2, $3);
 +                              $match = chop_str($match, 70, 5, 'center');
 +                              my $contextlen = int((80 - length($match))/2);
 +                              $contextlen = 30 if ($contextlen > 30);
 +                              $lead  = chop_str($lead,  $contextlen, 10, 'left');
 +                              $trail = chop_str($trail, $contextlen, 10, 'right');
 +
 +                              $lead  = esc_html($lead);
 +                              $match = esc_html($match);
 +                              $trail = esc_html($trail);
 +
 +                              print "$lead<span class=\"match\">$match</span>$trail<br />";
                        }
                }
                print "</td>\n" .
@@@ -4585,7 -4517,7 +4585,7 @@@ sub git_log 
  
        my @commitlist = parse_commits($hash, 101, (100 * $page));
  
 -      my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
 +      my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
  
        git_header_html();
        git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
@@@ -4877,43 -4809,9 +4877,9 @@@ sub git_blobdiff 
                        or die_error(undef, "Open git-diff-tree failed");
        }
  
-       # old/legacy style URI
-       if (!%diffinfo && # if new style URI failed
-           defined $hash && defined $hash_parent) {
-               # fake git-diff-tree raw output
-               $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob";
-               $diffinfo{'from_id'} = $hash_parent;
-               $diffinfo{'to_id'}   = $hash;
-               if (defined $file_name) {
-                       if (defined $file_parent) {
-                               $diffinfo{'status'} = '2';
-                               $diffinfo{'from_file'} = $file_parent;
-                               $diffinfo{'to_file'}   = $file_name;
-                       } else { # assume not renamed
-                               $diffinfo{'status'} = '1';
-                               $diffinfo{'from_file'} = $file_name;
-                               $diffinfo{'to_file'}   = $file_name;
-                       }
-               } else { # no filename given
-                       $diffinfo{'status'} = '2';
-                       $diffinfo{'from_file'} = $hash_parent;
-                       $diffinfo{'to_file'}   = $hash;
-               }
-               # non-textual hash id's can be cached
-               if ($hash =~ m/^[0-9a-fA-F]{40}$/ &&
-                   $hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
-                       $expires = '+1d';
-               }
-               # open patch output
-               open $fd, "-|", git_cmd(), "diff", @diff_opts,
-                       '-p', ($format eq 'html' ? "--full-index" : ()),
-                       $hash_parent, $hash, "--"
-                       or die_error(undef, "Open git-diff failed");
-       } else  {
+       # old/legacy style URI -- not generated anymore since 1.4.3.
+       if (!%diffinfo) {
                die_error('404 Not Found', "Missing one of the blob diff parameters")
-                       unless %diffinfo;
        }
  
        # header
@@@ -5171,26 -5069,14 +5137,26 @@@ sub git_history 
        my $refs = git_get_references();
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
  
 +      my @commitlist = parse_commits($hash_base, 101, (100 * $page),
 +                                     $file_name, "--full-history");
 +      if (!@commitlist) {
 +              die_error('404 Not Found', "No such file or directory on given branch");
 +      }
 +
        if (!defined $hash && defined $file_name) {
 -              $hash = git_get_hash_by_path($hash_base, $file_name);
 +              # some commits could have deleted file in question,
 +              # and not have it in tree, but one of them has to have it
 +              for (my $i = 0; $i <= @commitlist; $i++) {
 +                      $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
 +                      last if defined $hash;
 +              }
        }
        if (defined $hash) {
                $ftype = git_get_type($hash);
        }
 -
 -      my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name);
 +      if (!defined $ftype) {
 +              die_error(undef, "Unknown type of object");
 +      }
  
        my $paging_nav = '';
        if ($page > 0) {
@@@ -5272,17 -5158,14 +5238,17 @@@ sub git_search 
                } elsif ($searchtype eq 'committer') {
                        $greptype = "--committer=";
                }
 -              $greptype .= $search_regexp;
 -              my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
 +              $greptype .= $searchtext;
 +              my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
 +                                             $greptype, '--regexp-ignore-case',
 +                                             $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
  
                my $paging_nav = '';
                if ($page > 0) {
                        $paging_nav .=
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype)},
 +                                                     searchtext=>$searchtext,
 +                                                     searchtype=>$searchtype)},
                                        "first");
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(-replay=>1, page=>$page-1),
                print "<table class=\"pickaxe search\">\n";
                my $alternate = 1;
                $/ = "\n";
 -              my $git_command = git_cmd_str();
 -              my $searchqtext = $searchtext;
 -              $searchqtext =~ s/'/'\\''/;
 -              open my $fd, "-|", "$git_command rev-list $hash | " .
 -                      "$git_command diff-tree -r --stdin -S\'$searchqtext\'";
 +              open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
 +                      '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
 +                      ($search_use_regexp ? '--pickaxe-regex' : ());
                undef %co;
                my @files;
                while (my $line = <$fd>) {
 -                      if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
 -                              my %set;
 -                              $set{'file'} = $6;
 -                              $set{'from_id'} = $3;
 -                              $set{'to_id'} = $4;
 -                              $set{'id'} = $set{'to_id'};
 -                              if ($set{'id'} =~ m/0{40}/) {
 -                                      $set{'id'} = $set{'from_id'};
 -                              }
 -                              if ($set{'id'} =~ m/0{40}/) {
 -                                      next;
 -                              }
 -                              push @files, \%set;
 -                      } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){
 +                      chomp $line;
 +                      next unless $line;
 +
 +                      my %set = parse_difftree_raw_line($line);
 +                      if (defined $set{'commit'}) {
 +                              # finish previous commit
                                if (%co) {
 -                                      if ($alternate) {
 -                                              print "<tr class=\"dark\">\n";
 -                                      } else {
 -                                              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>" . $author . "</i></td>\n" .
 -                                            "<td>" .
 -                                            $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
 -                                                    -class => "list subject"},
 -                                                    chop_and_escape_str($co{'title'}, 50) . "<br/>");
 -                                      while (my $setref = shift @files) {
 -                                              my %set = %$setref;
 -                                              print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
 -                                                                           hash=>$set{'id'}, file_name=>$set{'file'}),
 -                                                            -class => "list"},
 -                                                            "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
 -                                                    "<br/>\n";
 -                                      }
                                        print "</td>\n" .
                                              "<td class=\"link\">" .
                                              $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
                                        print "</td>\n" .
                                              "</tr>\n";
                                }
 -                              %co = parse_commit($1);
 +
 +                              if ($alternate) {
 +                                      print "<tr class=\"dark\">\n";
 +                              } else {
 +                                      print "<tr class=\"light\">\n";
 +                              }
 +                              $alternate ^= 1;
 +                              %co = parse_commit($set{'commit'});
 +                              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>$author</i></td>\n" .
 +                                    "<td>" .
 +                                    $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
 +                                            -class => "list subject"},
 +                                            chop_and_escape_str($co{'title'}, 50) . "<br/>");
 +                      } elsif (defined $set{'to_id'}) {
 +                              next if ($set{'to_id'} =~ m/^0{40}$/);
 +
 +                              print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
 +                                                           hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
 +                                            -class => "list"},
 +                                            "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
 +                                    "<br/>\n";
                        }
                }
                close $fd;
  
 +              # finish last commit (warning: repetition!)
 +              if (%co) {
 +                      print "</td>\n" .
 +                            "<td class=\"link\">" .
 +                            $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
 +                            " | " .
 +                            $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
 +                      print "</td>\n" .
 +                            "</tr>\n";
 +              }
 +
                print "</table>\n";
        }
  
                my $alternate = 1;
                my $matches = 0;
                $/ = "\n";
 -              open my $fd, "-|", git_cmd(), 'grep', '-n', '-i', '-E', $searchtext, $co{'tree'};
 +              open my $fd, "-|", git_cmd(), 'grep', '-n',
 +                      $search_use_regexp ? ('-E', '-i') : '-F',
 +                      $searchtext, $co{'tree'};
                my $lastfile = '';
                while (my $line = <$fd>) {
                        chomp $line;
                                print "<div class=\"binary\">Binary file</div>\n";
                        } else {
                                $ltext = untabify($ltext);
 -                              if ($ltext =~ m/^(.*)($searchtext)(.*)$/i) {
 +                              if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
                                        $ltext = esc_html($1, -nbsp=>1);
                                        $ltext .= '<span class="match">';
                                        $ltext .= esc_html($2, -nbsp=>1);
@@@ -5453,31 -5332,27 +5419,31 @@@ sub git_search_help 
        git_header_html();
        git_print_page_nav('','', $hash,$hash,$hash);
        print <<EOT;
 +<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
 +regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
 +the pattern entered is recognized as the POSIX extended
 +<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
 +insensitive).</p>
  <dl>
  <dt><b>commit</b></dt>
 -<dd>The commit messages and authorship information will be scanned for the given string.</dd>
 +<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
  EOT
        my ($have_grep) = gitweb_check_feature('grep');
        if ($have_grep) {
                print <<EOT;
  <dt><b>grep</b></dt>
  <dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
 -    a different one) are searched for the given
 -<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a>
 -(POSIX extended) and the matches are listed. On large
 -trees, this search can take a while and put some strain on the server, so please use it with
 -some consideration.</dd>
 +    a different one) are searched for the given pattern. On large trees, this search can take
 +a while and put some strain on the server, so please use it with some consideration. Note that
 +due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
 +case-sensitive.</dd>
  EOT
        }
        print <<EOT;
  <dt><b>author</b></dt>
 -<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
 +<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given pattern.</dd>
  <dt><b>committer</b></dt>
 -<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
 +<dd>Name and e-mail of the committer and date of commit will be scanned for the given pattern.</dd>
  EOT
        my ($have_pickaxe) = gitweb_check_feature('pickaxe');
        if ($have_pickaxe) {
  <dt><b>pickaxe</b></dt>
  <dd>All commits that caused the string to appear or disappear from any file (changes that
  added, removed or "modified" the string) will be listed. This search can take a while and
 -takes a lot of strain on the server, so please use it wisely.</dd>
 +takes a lot of strain on the server, so please use it wisely. Note that since you may be
 +interested even in changes just changing the case as well, this search is case sensitive.</dd>
  EOT
        }
        print "</dl>\n";
@@@ -5505,7 -5379,7 +5471,7 @@@ sub git_shortlog 
  
        my @commitlist = parse_commits($hash, 101, (100 * $page));
  
 -      my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
 +      my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =
@@@ -5537,7 -5411,7 +5503,7 @@@ sub git_feed 
  
        # log/feed of current (HEAD) branch, log of given branch, history of file/directory
        my $head = $hash || 'HEAD';
 -      my @commitlist = parse_commits($head, 150, 0, undef, $file_name);
 +      my @commitlist = parse_commits($head, 150, 0, $file_name);
  
        my %latest_commit;
        my %latest_date;
@@@ -5657,7 -5531,7 +5623,7 @@@ XM
                        or next;
  
                # print element (entry, item)
 -              my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
 +              my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
                if ($format eq 'rss') {
                        print "<item>\n" .
                              "<title>" . esc_html($co{'title'}) . "</title>\n" .