Code

Merge branch 'maint-1.7.8' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 20 Mar 2012 22:53:30 +0000 (15:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 20 Mar 2012 22:53:30 +0000 (15:53 -0700)
* maint-1.7.8:
  t/Makefile: Use $(sort ...) explicitly where needed
  gitweb: Fix actionless dispatch for non-existent objects
  i18n of multi-line advice messages

1  2 
builtin/revert.c
gitweb/gitweb.perl
t/Makefile
t/t9500-gitweb-standalone-no-errors.sh

diff --combined builtin/revert.c
index 0d8020cf640e1abe6898308a1656446b327c83f4,00df5266d574ea1f8c9ab745ed606bac566392b5..df63794e050b0a27082952f458d93e0b254ce310
@@@ -343,11 -343,10 +343,10 @@@ static void print_advice(int show_hint
                return;
        }
  
-       if (show_hint) {
-               advise("after resolving the conflicts, mark the corrected paths");
-               advise("with 'git add <paths>' or 'git rm <paths>'");
-               advise("and commit the result with 'git commit'");
-       }
+       if (show_hint)
+               advise(_("after resolving the conflicts, mark the corrected paths\n"
+                        "with 'git add <paths>' or 'git rm <paths>'\n"
+                        "and commit the result with 'git commit'"));
  }
  
  static void write_message(struct strbuf *msgbuf, const char *filename)
@@@ -700,47 -699,44 +699,47 @@@ static int format_todo(struct strbuf *b
                struct replay_opts *opts)
  {
        struct commit_list *cur = NULL;
 -      struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
        const char *sha1_abbrev = NULL;
        const char *action_str = opts->action == REVERT ? "revert" : "pick";
 +      const char *subject;
 +      int subject_len;
  
        for (cur = todo_list; cur; cur = cur->next) {
                sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
 -              if (get_message(cur->item, &msg))
 -                      return error(_("Cannot get commit message for %s"), sha1_abbrev);
 -              strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
 +              subject_len = find_commit_subject(cur->item->buffer, &subject);
 +              strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
 +                      subject_len, subject);
        }
        return 0;
  }
  
 -static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
 +static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
  {
        unsigned char commit_sha1[20];
 -      char sha1_abbrev[40];
        enum replay_action action;
 -      int insn_len = 0;
 -      char *p, *q;
 +      char *end_of_object_name;
 +      int saved, status, padding;
  
 -      if (!prefixcmp(start, "pick ")) {
 +      if (!prefixcmp(bol, "pick")) {
                action = CHERRY_PICK;
 -              insn_len = strlen("pick");
 -              p = start + insn_len + 1;
 -      } else if (!prefixcmp(start, "revert ")) {
 +              bol += strlen("pick");
 +      } else if (!prefixcmp(bol, "revert")) {
                action = REVERT;
 -              insn_len = strlen("revert");
 -              p = start + insn_len + 1;
 +              bol += strlen("revert");
        } else
                return NULL;
  
 -      q = strchr(p, ' ');
 -      if (!q)
 +      /* Eat up extra spaces/ tabs before object name */
 +      padding = strspn(bol, " \t");
 +      if (!padding)
                return NULL;
 -      q++;
 +      bol += padding;
  
 -      strlcpy(sha1_abbrev, p, q - p);
 +      end_of_object_name = bol + strcspn(bol, " \t\n");
 +      saved = *end_of_object_name;
 +      *end_of_object_name = '\0';
 +      status = get_sha1(bol, commit_sha1);
 +      *end_of_object_name = saved;
  
        /*
         * Verify that the action matches up with the one in
                return NULL;
        }
  
 -      if (get_sha1(sha1_abbrev, commit_sha1) < 0)
 +      if (status < 0)
                return NULL;
  
        return lookup_commit_reference(commit_sha1);
@@@ -768,12 -764,13 +767,12 @@@ static int parse_insn_buffer(char *buf
        int i;
  
        for (i = 1; *p; i++) {
 -              commit = parse_insn_line(p, opts);
 +              char *eol = strchrnul(p, '\n');
 +              commit = parse_insn_line(p, eol, opts);
                if (!commit)
                        return error(_("Could not parse line %d."), i);
                next = commit_list_append(commit, next);
 -              p = strchrnul(p, '\n');
 -              if (*p)
 -                      p++;
 +              p = *eol ? eol + 1 : eol;
        }
        if (!*todo_list)
                return error(_("No commits parsed."));
@@@ -905,7 -902,7 +904,7 @@@ static int rollback_single_pick(void
        if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
            !file_exists(git_path("REVERT_HEAD")))
                return error(_("no cherry-pick or revert in progress"));
 -      if (!resolve_ref("HEAD", head_sha1, 0, NULL))
 +      if (read_ref_full("HEAD", head_sha1, 0, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_sha1(head_sha1))
                return error(_("cannot abort from a branch yet to be born"));
diff --combined gitweb/gitweb.perl
index b9c33ba31d538e87f69a8f742b1f8ff2134cfd8c,6cf38853b5a1577ae0c7df80c39a98f44b22915e..b67972ec5d9e2ddd78ffccada03361ed0bc63a14
@@@ -52,7 -52,7 +52,7 @@@ sub evaluate_uri 
        # as base URL.
        # Therefore, if we needed to strip PATH_INFO, then we know that we have
        # to build the base URL ourselves:
 -      our $path_info = $ENV{"PATH_INFO"};
 +      our $path_info = decode_utf8($ENV{"PATH_INFO"});
        if ($path_info) {
                if ($my_url =~ s,\Q$path_info\E$,, &&
                    $my_uri =~ s,\Q$path_info\E$,, &&
@@@ -759,7 -759,6 +759,7 @@@ our @cgi_param_mapping = 
        extra_options => "opt",
        search_use_regexp => "sr",
        ctag => "by_tag",
 +      diff_style => "ds",
        # this must be last entry (for manipulation from JavaScript)
        javascript => "js"
  );
@@@ -816,9 -815,9 +816,9 @@@ sub evaluate_query_params 
  
        while (my ($name, $symbol) = each %cgi_param_mapping) {
                if ($symbol eq 'opt') {
 -                      $input_params{$name} = [ $cgi->param($symbol) ];
 +                      $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
                } else {
 -                      $input_params{$name} = $cgi->param($symbol);
 +                      $input_params{$name} = decode_utf8($cgi->param($symbol));
                }
        }
  }
@@@ -1073,16 -1072,7 +1073,16 @@@ sub evaluate_and_validate_params 
                if (length($searchtext) < 2) {
                        die_error(403, "At least two characters are required for search parameter");
                }
 -              $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
 +              if ($search_use_regexp) {
 +                      $search_regexp = $searchtext;
 +                      if (!eval { qr/$search_regexp/; 1; }) {
 +                              (my $error = $@) =~ s/ at \S+ line \d+.*\n?//;
 +                              die_error(400, "Invalid search regexp '$search_regexp'",
 +                                        esc_html($error));
 +                      }
 +              } else {
 +                      $search_regexp = quotemeta $searchtext;
 +              }
        }
  }
  
@@@ -1132,8 -1122,10 +1132,10 @@@ sub dispatch 
        if (!defined $action) {
                if (defined $hash) {
                        $action = git_get_type($hash);
+                       $action or die_error(404, "Object does not exist");
                } elsif (defined $hash_base && defined $file_name) {
                        $action = git_get_type("$hash_base:$file_name");
+                       $action or die_error(404, "File or directory does not exist");
                } elsif (defined $project) {
                        $action = 'summary';
                } else {
@@@ -2236,119 -2228,93 +2238,119 @@@ sub format_diff_cc_simplified 
        return $result;
  }
  
 -# format patch (diff) line (not to be used for diff headers)
 -sub format_diff_line {
 -      my $line = shift;
 -      my ($from, $to) = @_;
 -      my $diff_class = "";
 -
 -      chomp $line;
 +sub diff_line_class {
 +      my ($line, $from, $to) = @_;
  
 +      # ordinary diff
 +      my $num_sign = 1;
 +      # combined diff
        if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
 -              # combined diff
 -              my $prefix = substr($line, 0, scalar @{$from->{'href'}});
 -              if ($line =~ m/^\@{3}/) {
 -                      $diff_class = " chunk_header";
 -              } elsif ($line =~ m/^\\/) {
 -                      $diff_class = " incomplete";
 -              } elsif ($prefix =~ tr/+/+/) {
 -                      $diff_class = " add";
 -              } elsif ($prefix =~ tr/-/-/) {
 -                      $diff_class = " rem";
 -              }
 -      } else {
 -              # assume ordinary diff
 -              my $char = substr($line, 0, 1);
 -              if ($char eq '+') {
 -                      $diff_class = " add";
 -              } elsif ($char eq '-') {
 -                      $diff_class = " rem";
 -              } elsif ($char eq '@') {
 -                      $diff_class = " chunk_header";
 -              } elsif ($char eq "\\") {
 -                      $diff_class = " incomplete";
 -              }
 +              $num_sign = scalar @{$from->{'href'}};
 +      }
 +
 +      my @diff_line_classifier = (
 +              { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
 +              { regexp => qr/^\\/,               class => "incomplete"  },
 +              { regexp => qr/^ {$num_sign}/,     class => "ctx" },
 +              # classifier for context must come before classifier add/rem,
 +              # or we would have to use more complicated regexp, for example
 +              # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
 +              { regexp => qr/^[+ ]{$num_sign}/,   class => "add" },
 +              { regexp => qr/^[- ]{$num_sign}/,   class => "rem" },
 +      );
 +      for my $clsfy (@diff_line_classifier) {
 +              return $clsfy->{'class'}
 +                      if ($line =~ $clsfy->{'regexp'});
        }
 -      $line = untabify($line);
 -      if ($from && $to && $line =~ m/^\@{2} /) {
 -              my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
 -                      $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
  
 -              $from_lines = 0 unless defined $from_lines;
 -              $to_lines   = 0 unless defined $to_lines;
 +      # fallback
 +      return "";
 +}
  
 -              if ($from->{'href'}) {
 -                      $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
 -                                           -class=>"list"}, $from_text);
 -              }
 -              if ($to->{'href'}) {
 -                      $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
 -                                           -class=>"list"}, $to_text);
 -              }
 -              $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
 -                      "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
 -              return "<div class=\"diff$diff_class\">$line</div>\n";
 -      } elsif ($from && $to && $line =~ m/^\@{3}/) {
 -              my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
 -              my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
 +# assumes that $from and $to are defined and correctly filled,
 +# and that $line holds a line of chunk header for unified diff
 +sub format_unidiff_chunk_header {
 +      my ($line, $from, $to) = @_;
  
 -              @from_text = split(' ', $ranges);
 -              for (my $i = 0; $i < @from_text; ++$i) {
 -                      ($from_start[$i], $from_nlines[$i]) =
 -                              (split(',', substr($from_text[$i], 1)), 0);
 -              }
 +      my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
 +              $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
  
 -              $to_text   = pop @from_text;
 -              $to_start  = pop @from_start;
 -              $to_nlines = pop @from_nlines;
 +      $from_lines = 0 unless defined $from_lines;
 +      $to_lines   = 0 unless defined $to_lines;
  
 -              $line = "<span class=\"chunk_info\">$prefix ";
 -              for (my $i = 0; $i < @from_text; ++$i) {
 -                      if ($from->{'href'}[$i]) {
 -                              $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
 -                                                -class=>"list"}, $from_text[$i]);
 -                      } else {
 -                              $line .= $from_text[$i];
 -                      }
 -                      $line .= " ";
 -              }
 -              if ($to->{'href'}) {
 -                      $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
 -                                        -class=>"list"}, $to_text);
 +      if ($from->{'href'}) {
 +              $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
 +                                   -class=>"list"}, $from_text);
 +      }
 +      if ($to->{'href'}) {
 +              $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
 +                                   -class=>"list"}, $to_text);
 +      }
 +      $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
 +              "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
 +      return $line;
 +}
 +
 +# assumes that $from and $to are defined and correctly filled,
 +# and that $line holds a line of chunk header for combined diff
 +sub format_cc_diff_chunk_header {
 +      my ($line, $from, $to) = @_;
 +
 +      my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
 +      my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
 +
 +      @from_text = split(' ', $ranges);
 +      for (my $i = 0; $i < @from_text; ++$i) {
 +              ($from_start[$i], $from_nlines[$i]) =
 +                      (split(',', substr($from_text[$i], 1)), 0);
 +      }
 +
 +      $to_text   = pop @from_text;
 +      $to_start  = pop @from_start;
 +      $to_nlines = pop @from_nlines;
 +
 +      $line = "<span class=\"chunk_info\">$prefix ";
 +      for (my $i = 0; $i < @from_text; ++$i) {
 +              if ($from->{'href'}[$i]) {
 +                      $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
 +                                        -class=>"list"}, $from_text[$i]);
                } else {
 -                      $line .= $to_text;
 +                      $line .= $from_text[$i];
                }
 -              $line .= " $prefix</span>" .
 -                       "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
 -              return "<div class=\"diff$diff_class\">$line</div>\n";
 +              $line .= " ";
 +      }
 +      if ($to->{'href'}) {
 +              $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
 +                                -class=>"list"}, $to_text);
 +      } else {
 +              $line .= $to_text;
 +      }
 +      $line .= " $prefix</span>" .
 +               "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
 +      return $line;
 +}
 +
 +# process patch (diff) line (not to be used for diff headers),
 +# returning class and HTML-formatted (but not wrapped) line
 +sub process_diff_line {
 +      my $line = shift;
 +      my ($from, $to) = @_;
 +
 +      my $diff_class = diff_line_class($line, $from, $to);
 +
 +      chomp $line;
 +      $line = untabify($line);
 +
 +      if ($from && $to && $line =~ m/^\@{2} /) {
 +              $line = format_unidiff_chunk_header($line, $from, $to);
 +              return $diff_class, $line;
 +
 +      } elsif ($from && $to && $line =~ m/^\@{3}/) {
 +              $line = format_cc_diff_chunk_header($line, $from, $to);
 +              return $diff_class, $line;
 +
        }
 -      return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 +      return $diff_class, esc_html($line, -nbsp=>1);
  }
  
  # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@@ -2400,7 -2366,7 +2402,7 @@@ sub get_feed_info 
        return unless (defined $project);
        # some views should link to OPML, or to generic project feed,
        # or don't have specific feed yet (so they should use generic)
-       return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
+       return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
  
        my $branch;
        # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
@@@ -2774,7 -2740,7 +2776,7 @@@ sub git_populate_project_tagcloud 
        }
  
        my $cloud;
 -      my $matched = $cgi->param('by_tag');
 +      my $matched = $input_params{'ctag'};
        if (eval { require HTML::TagCloud; 1; }) {
                $cloud = HTML::TagCloud->new;
                foreach my $ctag (sort keys %ctags_lc) {
@@@ -2845,8 -2811,8 +2847,8 @@@ sub git_get_projects_list 
                my $dir = $projects_list;
                # remove the trailing "/"
                $dir =~ s!/+$!!;
 -              my $pfxlen = length("$projects_list");
 -              my $pfxdepth = ($projects_list =~ tr!/!!);
 +              my $pfxlen = length("$dir");
 +              my $pfxdepth = ($dir =~ tr!/!!);
                # when filtering, search only given subdirectory
                if ($filter) {
                        $dir .= "/$filter";
@@@ -2978,10 -2944,10 +2980,10 @@@ sub filter_forks_from_projects_list 
  sub search_projects_list {
        my ($projlist, %opts) = @_;
        my $tagfilter  = $opts{'tagfilter'};
 -      my $searchtext = $opts{'searchtext'};
 +      my $search_re = $opts{'search_regexp'};
  
        return @$projlist
 -              unless ($tagfilter || $searchtext);
 +              unless ($tagfilter || $search_re);
  
        my @projects;
   PROJECT:
                                grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
                }
  
 -              if ($searchtext) {
 +              if ($search_re) {
                        next unless
 -                              $pr->{'path'} =~ /$searchtext/ ||
 -                              $pr->{'descr_long'} =~ /$searchtext/;
 +                              $pr->{'path'} =~ /$search_re/ ||
 +                              $pr->{'descr_long'} =~ /$search_re/;
                }
  
                push @projects, $pr;
@@@ -3880,7 -3846,7 +3882,7 @@@ sub print_search_form 
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
              $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
              " search:\n",
 -            $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
 +            $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
                             -checked => $search_use_regexp) .
@@@ -4870,97 -4836,8 +4872,97 @@@ sub git_difftree_body 
        print "</table>\n";
  }
  
 +sub print_sidebyside_diff_chunk {
 +      my @chunk = @_;
 +      my (@ctx, @rem, @add);
 +
 +      return unless @chunk;
 +
 +      # incomplete last line might be among removed or added lines,
 +      # or both, or among context lines: find which
 +      for (my $i = 1; $i < @chunk; $i++) {
 +              if ($chunk[$i][0] eq 'incomplete') {
 +                      $chunk[$i][0] = $chunk[$i-1][0];
 +              }
 +      }
 +
 +      # guardian
 +      push @chunk, ["", ""];
 +
 +      foreach my $line_info (@chunk) {
 +              my ($class, $line) = @$line_info;
 +
 +              # print chunk headers
 +              if ($class && $class eq 'chunk_header') {
 +                      print $line;
 +                      next;
 +              }
 +
 +              ## print from accumulator when type of class of lines change
 +              # empty contents block on start rem/add block, or end of chunk
 +              if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
 +                      print join '',
 +                              '<div class="chunk_block ctx">',
 +                                      '<div class="old">',
 +                                      @ctx,
 +                                      '</div>',
 +                                      '<div class="new">',
 +                                      @ctx,
 +                                      '</div>',
 +                              '</div>';
 +                      @ctx = ();
 +              }
 +              # empty add/rem block on start context block, or end of chunk
 +              if ((@rem || @add) && (!$class || $class eq 'ctx')) {
 +                      if (!@add) {
 +                              # pure removal
 +                              print join '',
 +                                      '<div class="chunk_block rem">',
 +                                              '<div class="old">',
 +                                              @rem,
 +                                              '</div>',
 +                                      '</div>';
 +                      } elsif (!@rem) {
 +                              # pure addition
 +                              print join '',
 +                                      '<div class="chunk_block add">',
 +                                              '<div class="new">',
 +                                              @add,
 +                                              '</div>',
 +                                      '</div>';
 +                      } else {
 +                              # assume that it is change
 +                              print join '',
 +                                      '<div class="chunk_block chg">',
 +                                              '<div class="old">',
 +                                              @rem,
 +                                              '</div>',
 +                                              '<div class="new">',
 +                                              @add,
 +                                              '</div>',
 +                                      '</div>';
 +                      }
 +                      @rem = @add = ();
 +              }
 +
 +              ## adding lines to accumulator
 +              # guardian value
 +              last unless $line;
 +              # rem, add or change
 +              if ($class eq 'rem') {
 +                      push @rem, $line;
 +              } elsif ($class eq 'add') {
 +                      push @add, $line;
 +              }
 +              # context line
 +              if ($class eq 'ctx') {
 +                      push @ctx, $line;
 +              }
 +      }
 +}
 +
  sub git_patchset_body {
 -      my ($fd, $difftree, $hash, @hash_parents) = @_;
 +      my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
        my ($hash_parent) = $hash_parents[0];
  
        my $is_combined = (@hash_parents > 1);
        my $diffinfo;
        my $to_name;
        my (%from, %to);
 +      my @chunk; # for side-by-side diff
  
        print "<div class=\"patchset\">\n";
  
  
                        next PATCH if ($patch_line =~ m/^diff /);
  
 -                      print format_diff_line($patch_line, \%from, \%to);
 +                      my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
 +                      my $diff_classes = "diff";
 +                      $diff_classes .= " $class" if ($class);
 +                      $line = "<div class=\"$diff_classes\">$line</div>\n";
 +
 +                      if ($diff_style eq 'sidebyside' && !$is_combined) {
 +                              if ($class eq 'chunk_header') {
 +                                      print_sidebyside_diff_chunk(@chunk);
 +                                      @chunk = ( [ $class, $line ] );
 +                              } else {
 +                                      push @chunk, [ $class, $line ];
 +                              }
 +                      } else {
 +                              # default 'inline' style and unknown styles
 +                              print $line;
 +                      }
                }
  
        } continue {
 +              if (@chunk) {
 +                      print_sidebyside_diff_chunk(@chunk);
 +                      @chunk = ();
 +              }
                print "</div>\n"; # class="patch"
        }
  
@@@ -5289,9 -5146,9 +5291,9 @@@ sub git_project_list_body 
  
        my $check_forks = gitweb_check_feature('forks');
        my $show_ctags  = gitweb_check_feature('ctags');
 -      my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
 +      my $tagfilter = $show_ctags ? $input_params{'ctag'} : undef;
        $check_forks = undef
 -              if ($tagfilter || $searchtext);
 +              if ($tagfilter || $search_regexp);
  
        # filtering out forks before filling info allows to do less work
        @projects = filter_forks_from_projects_list(\@projects)
        @projects = fill_project_list_info(\@projects);
        # searching projects require filling to be run before it
        @projects = search_projects_list(\@projects,
 -                                       'searchtext' => $searchtext,
 +                                       'search_regexp' => $search_regexp,
                                         'tagfilter'  => $tagfilter)
 -              if ($tagfilter || $searchtext);
 +              if ($tagfilter || $search_regexp);
  
        $order ||= $default_projects_order;
        $from = 0 unless defined $from;
@@@ -5577,7 -5434,7 +5579,7 @@@ sub git_tags_body 
  
  sub git_heads_body {
        # uses global variable $project
 -      my ($headlist, $head, $from, $to, $extra) = @_;
 +      my ($headlist, $head_at, $from, $to, $extra) = @_;
        $from = 0 unless defined $from;
        $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
  
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
                my %ref = %$entry;
 -              my $curr = $ref{'id'} eq $head;
 +              my $curr = defined $head_at && $ref{'id'} eq $head_at;
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@@ -5845,7 -5702,7 +5847,7 @@@ sub git_search_files 
        my %co = @_;
  
        local $/ = "\n";
 -      open my $fd, "-|", git_cmd(), 'grep', '-n',
 +      open my $fd, "-|", git_cmd(), 'grep', '-n', '-z',
                $search_use_regexp ? ('-E', '-i') : '-F',
                $searchtext, $co{'tree'}
                        or die_error(500, "Open git-grep failed");
        my $alternate = 1;
        my $matches = 0;
        my $lastfile = '';
 +      my $file_href;
        while (my $line = <$fd>) {
                chomp $line;
                my ($file, $lno, $ltext, $binary);
                        $file = $1;
                        $binary = 1;
                } else {
 -                      (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
 +                      ($file, $lno, $ltext) = split(/\0/, $line, 3);
 +                      $file =~ s/^$co{'tree'}://;
                }
                if ($file ne $lastfile) {
                        $lastfile and print "</td></tr>\n";
                        } else {
                                print "<tr class=\"light\">\n";
                        }
 +                      $file_href = href(action=>"blob", hash_base=>$co{'id'},
 +                                        file_name=>$file);
                        print "<td class=\"list\">".
 -                              $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
 -                                                     file_name=>"$file"),
 -                                      -class => "list"}, esc_path($file));
 +                              $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
                        print "</td><td>\n";
                        $lastfile = $file;
                }
                                $ltext = esc_html($ltext, -nbsp=>1);
                        }
                        print "<div class=\"pre\">" .
 -                              $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
 -                                                     file_name=>"$file").'#l'.$lno,
 -                                      -class => "linenr"}, sprintf('%4i', $lno))
 -                              . ' ' .  $ltext . "</div>\n";
 +                              $cgi->a({-href => $file_href.'#l'.$lno,
 +                                      -class => "linenr"}, sprintf('%4i', $lno)) .
 +                              ' ' .  $ltext . "</div>\n";
                }
        }
        if ($lastfile) {
@@@ -6002,7 -5858,7 +6004,7 @@@ sub git_project_list 
        }
        print $cgi->startform(-method => "get") .
              "<p class=\"projsearch\">Search:\n" .
 -            $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
 +            $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "</p>" .
              $cgi->end_form() . "\n";
        git_project_list_body(\@list, $order);
@@@ -6205,7 -6061,7 +6207,7 @@@ sub git_tag 
  
  sub git_blame_common {
        my $format = shift || 'porcelain';
 -      if ($format eq 'porcelain' && $cgi->param('js')) {
 +      if ($format eq 'porcelain' && $input_params{'javascript'}) {
                $format = 'incremental';
                $action = 'blame_incremental'; # for page title etc
        }
@@@ -7098,7 -6954,6 +7100,7 @@@ sub git_object 
  
  sub git_blobdiff {
        my $format = shift || 'html';
 +      my $diff_style = $input_params{'diff_style'} || 'inline';
  
        my $fd;
        my @difftree;
                my $formats_nav =
                        $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
                                "raw");
 +              $formats_nav .= diff_style_nav($diff_style);
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
        if ($format eq 'html') {
                print "<div class=\"page_body\">\n";
  
 -              git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
 +              git_patchset_body($fd, $diff_style,
 +                                [ \%diffinfo ], $hash_base, $hash_parent_base);
                close $fd;
  
                print "</div>\n"; # class="page_body"
@@@ -7235,31 -7088,9 +7237,31 @@@ sub git_blobdiff_plain 
        git_blobdiff('plain');
  }
  
 +# assumes that it is added as later part of already existing navigation,
 +# so it returns "| foo | bar" rather than just "foo | bar"
 +sub diff_style_nav {
 +      my ($diff_style, $is_combined) = @_;
 +      $diff_style ||= 'inline';
 +
 +      return "" if ($is_combined);
 +
 +      my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
 +      my %styles = @styles;
 +      @styles =
 +              @styles[ map { $_ * 2 } 0..$#styles/2 ];
 +
 +      return join '',
 +              map { " | ".$_ }
 +              map {
 +                      $_ eq $diff_style ? $styles{$_} :
 +                      $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
 +              } @styles;
 +}
 +
  sub git_commitdiff {
        my %params = @_;
        my $format = $params{-format} || 'html';
 +      my $diff_style = $input_params{'diff_style'} || 'inline';
  
        my ($patch_max) = gitweb_get_feature('patches');
        if ($format eq 'patch') {
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
                }
 +              $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
  
                if (defined $hash_parent &&
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
                                }
                        }
                        $formats_nav .= ': ' .
 -                              $cgi->a({-href => href(action=>"commitdiff",
 -                                                     hash=>$hash_parent)},
 +                              $cgi->a({-href => href(-replay=>1,
 +                                                     hash=>$hash_parent, hash_base=>undef)},
                                        esc_html($hash_parent_short)) .
                                ')';
                } elsif (!$co{'parent'}) {
                        # single parent commit
                        $formats_nav .=
                                ' (parent: ' .
 -                              $cgi->a({-href => href(action=>"commitdiff",
 -                                                     hash=>$co{'parent'})},
 +                              $cgi->a({-href => href(-replay=>1,
 +                                                     hash=>$co{'parent'}, hash_base=>undef)},
                                        esc_html(substr($co{'parent'}, 0, 7))) .
                                ')';
                } else {
                        # merge commit
                        if ($hash_parent eq '--cc') {
                                $formats_nav .= ' | ' .
 -                                      $cgi->a({-href => href(action=>"commitdiff",
 +                                      $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'-c')},
                                                'combined');
                        } else { # $hash_parent eq '-c'
                                $formats_nav .= ' | ' .
 -                                      $cgi->a({-href => href(action=>"commitdiff",
 +                                      $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'--cc')},
                                                'compact');
                        }
                        $formats_nav .=
                                ' (merge: ' .
                                join(' ', map {
 -                                      $cgi->a({-href => href(action=>"commitdiff",
 -                                                             hash=>$_)},
 +                                      $cgi->a({-href => href(-replay=>1,
 +                                                             hash=>$_, hash_base=>undef)},
                                                esc_html(substr($_, 0, 7)));
                                } @{$co{'parents'}} ) .
                                ')';
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                print "<br/>\n";
  
 -              git_patchset_body($fd, \@difftree, $hash,
 +              git_patchset_body($fd, $diff_style,
 +                                \@difftree, $hash,
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                close $fd;
                print "</div>\n"; # class="page_body"
diff --combined t/Makefile
index 52a23fffc42384c7b3a0e409e08e5747fb939a2b,66ceefefccac693d29077ef8020fc4f9dbe7a0bc..b5048ab77b9d580c3cc3f97e98f9a83209f2edb9
@@@ -17,9 -17,9 +17,9 @@@ DEFAULT_TEST_TARGET ?= tes
  # Shell quote;
  SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
  
- T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
- TSVN = $(wildcard t91[0-9][0-9]-*.sh)
- TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
+ T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
+ TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
+ TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh))
  
  all: $(DEFAULT_TEST_TARGET)
  
@@@ -73,4 -73,42 +73,4 @@@ gitweb-test
  valgrind:
        $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
  
 -# Smoke testing targets
 --include ../GIT-VERSION-FILE
 -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
 -uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo unknown')
 -
 -test-results:
 -      mkdir -p test-results
 -
 -test-results/git-smoke.tar.gz: test-results
 -      $(PERL_PATH) ./harness \
 -              --archive="test-results/git-smoke.tar.gz" \
 -              $(T)
 -
 -smoke: test-results/git-smoke.tar.gz
 -
 -SMOKE_UPLOAD_FLAGS =
 -ifdef SMOKE_USERNAME
 -      SMOKE_UPLOAD_FLAGS += -F username="$(SMOKE_USERNAME)" -F password="$(SMOKE_PASSWORD)"
 -endif
 -ifdef SMOKE_COMMENT
 -      SMOKE_UPLOAD_FLAGS += -F comments="$(SMOKE_COMMENT)"
 -endif
 -ifdef SMOKE_TAGS
 -      SMOKE_UPLOAD_FLAGS += -F tags="$(SMOKE_TAGS)"
 -endif
 -
 -smoke_report: smoke
 -      curl \
 -              -H "Expect: " \
 -              -F project=Git \
 -              -F architecture="$(uname_M)" \
 -              -F platform="$(uname_S)" \
 -              -F revision="$(GIT_VERSION)" \
 -              -F report_file=@test-results/git-smoke.tar.gz \
 -              $(SMOKE_UPLOAD_FLAGS) \
 -              http://smoke.git.nix.is/app/projects/process_add_report/1 \
 -      | grep -v ^Redirecting
 -
 -.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
 +.PHONY: pre-clean $(T) aggregate-results clean valgrind
index 858a649cb64e40a083a60c16e7ad818fdecb0fa9,94365bb0068661d0a964199f91878decb983b986..90bb6050c13ece02199b6978e43e3c67de563c84
@@@ -273,53 -273,6 +273,53 @@@ test_expect_success 
        'commitdiff(2): directory becomes symlink' \
        'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
  
 +# ----------------------------------------------------------------------
 +# commitdiff testing (incomplete lines)
 +
 +test_expect_success 'setup incomplete lines' '
 +      cat >file<<-\EOF &&
 +      Dominus regit me,
 +      et nihil mihi deerit.
 +      In loco pascuae ibi me collocavit,
 +      super aquam refectionis educavit me;
 +      animam meam convertit,
 +      deduxit me super semitas jusitiae,
 +      propter nomen suum.
 +      CHANGE_ME
 +      EOF
 +      git commit -a -m "Preparing for incomplete lines" &&
 +      echo "incomplete" | tr -d "\\012" >>file &&
 +      git commit -a -m "Add incomplete line" &&
 +      git tag incomplete_lines_add &&
 +      sed -e s/CHANGE_ME/change_me/ <file >file+ &&
 +      mv -f file+ file &&
 +      git commit -a -m "Incomplete context line" &&
 +      git tag incomplete_lines_ctx &&
 +      echo "Dominus regit me," >file &&
 +      echo "incomplete line" | tr -d "\\012" >>file &&
 +      git commit -a -m "Change incomplete line" &&
 +      git tag incomplete_lines_chg
 +      echo "Dominus regit me," >file &&
 +      git commit -a -m "Remove incomplete line" &&
 +      git tag incomplete_lines_rem
 +'
 +
 +test_expect_success 'commitdiff(1): addition of incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add"
 +'
 +
 +test_expect_success 'commitdiff(1): incomplete line as context line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx"
 +'
 +
 +test_expect_success 'commitdiff(1): change incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg"
 +'
 +
 +test_expect_success 'commitdiff(1): removal of incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem"
 +'
 +
  # ----------------------------------------------------------------------
  # commit, commitdiff: merge, large
  test_expect_success \
         git add b &&
         git commit -a -m "On branch" &&
         git checkout master &&
 -       git pull . b'
 +       git pull . b &&
 +       git tag merge_commit'
  
  test_expect_success \
        'commit(0): merge commit' \
@@@ -379,29 -331,6 +379,29 @@@ test_expect_success 
        'commitdiff(1): large commit' \
        'gitweb_run "p=.git;a=commitdiff;h=b"'
  
 +# ----------------------------------------------------------------------
 +# side-by-side diff
 +
 +test_expect_success 'side-by-side: addition of incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add;ds=sidebyside"
 +'
 +
 +test_expect_success 'side-by-side: incomplete line as context line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx;ds=sidebyside"
 +'
 +
 +test_expect_success 'side-by-side: changed incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg;ds=sidebyside"
 +'
 +
 +test_expect_success 'side-by-side: removal of incomplete line' '
 +      gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem;ds=sidebyside"
 +'
 +
 +test_expect_success 'side-by-side: merge commit' '
 +      gitweb_run "p=.git;a=commitdiff;h=merge_commit;ds=sidebyside"
 +'
 +
  # ----------------------------------------------------------------------
  # tags testing
  
@@@ -474,6 -403,14 +474,14 @@@ test_expect_success 
        'path_info: project/branch:dir/' \
        'gitweb_run "" "/.git/master:foo/"'
  
+ test_expect_success \
+       'path_info: project/branch (non-existent)' \
+       'gitweb_run "" "/.git/non-existent"'
+ test_expect_success \
+       'path_info: project/branch:filename (non-existent branch)' \
+       'gitweb_run "" "/.git/non-existent:non-existent"'
  test_expect_success \
        'path_info: project/branch:file (non-existent)' \
        'gitweb_run "" "/.git/master:non-existent"'
@@@ -629,45 -566,6 +637,45 @@@ test_expect_success 
        'config override: tree view, features enabled in repo config (2)' \
        'gitweb_run "p=.git;a=tree"'
  
 +# ----------------------------------------------------------------------
 +# searching
 +
 +cat >>gitweb_config.perl <<\EOF
 +
 +# enable search
 +$feature{'search'}{'default'} = [1];
 +$feature{'grep'}{'default'} = [1];
 +$feature{'pickaxe'}{'default'} = [1];
 +EOF
 +
 +test_expect_success \
 +      'search: preparation' \
 +      'echo "1st MATCH" >>file &&
 +       echo "2nd MATCH" >>file &&
 +       echo "MATCH" >>bar &&
 +       git add file bar &&
 +       git commit -m "Added MATCH word"'
 +
 +test_expect_success \
 +      'search: commit author' \
 +      'gitweb_run "p=.git;a=search;h=HEAD;st=author;s=A+U+Thor"'
 +
 +test_expect_success \
 +      'search: commit message' \
 +      'gitweb_run "p=.git;a=search;h=HEAD;st=commitr;s=MATCH"'
 +
 +test_expect_success \
 +      'search: grep' \
 +      'gitweb_run "p=.git;a=search;h=HEAD;st=grep;s=MATCH"'
 +
 +test_expect_success \
 +      'search: pickaxe' \
 +      'gitweb_run "p=.git;a=search;h=HEAD;st=pickaxe;s=MATCH"'
 +
 +test_expect_success \
 +      'search: projects' \
 +      'gitweb_run "a=project_list;s=.git"'
 +
  # ----------------------------------------------------------------------
  # non-ASCII in README.html
  
@@@ -770,13 -668,4 +778,13 @@@ test_expect_success 
        'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
         gitweb_run'
  
 +# ----------------------------------------------------------------------
 +# unborn branches
 +
 +test_expect_success \
 +      'unborn HEAD: "summary" page (with "heads" subview)' \
 +      'git checkout orphan_branch || git checkout --orphan orphan_branch &&
 +       test_when_finished "git checkout master" &&
 +       gitweb_run "p=.git;a=summary"'
 +
  test_done