X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/2929829c600dfe0d3e98756c5cf09caf5f1cdbad2b6f5f47e610c1f065095a40..739df17f552f684fab90d96e66c4ef9470bc2c156b5113fc2f018b4051a473ff:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index 5bf8e44..191be31 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -18,6 +18,10 @@ use File::Find qw(); use File::Basename qw(basename); binmode STDOUT, ':utf8'; +BEGIN { + CGI->compile() if $ENV{MOD_PERL}; +} + our $cgi = new CGI; our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); @@ -129,6 +133,12 @@ our %feature = ( # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + # Enable text search, which will list the commits which match author, + # committer or commit text to a given string. Enabled by default. + 'search' => { + 'override' => 0, + 'default' => [1]}, + # Enable the pickaxe search, which will list the commits that modified # a given string in a file. This can be practical and quite faster # alternative to 'blame', but still potentially CPU-intensive. @@ -352,6 +362,9 @@ if (defined $searchtext) { if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { die_error(undef, "Invalid search parameter"); } + if (length($searchtext) < 2) { + die_error(undef, "At least two characters are required for search parameter"); + } $searchtext = quotemeta $searchtext; } @@ -829,14 +842,12 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{40})/) { + if ($line =~ m/([0-9a-fA-F]{8,40})/) { my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = - $cgi->a({-href => href(action=>"commit", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + my $link = + $cgi->a({-href => href(action=>"object", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; } return $line; } @@ -858,7 +869,8 @@ sub format_ref_marker { $name = $ref; } - $markers .= " " . esc_html($name) . ""; + $markers .= " " . + esc_html($name) . ""; } } @@ -1141,8 +1153,9 @@ sub git_get_last_activity { $git_dir = "$projectroot/$path"; open($fd, "-|", git_cmd(), 'for-each-ref', - '--format=%(refname) %(committer)', + '--format=%(committer)', '--sort=-committerdate', + '--count=1', 'refs/heads') or return; my $most_recent = <$fd>; close $fd or return; @@ -1262,36 +1275,24 @@ sub parse_tag { return %tag } -sub parse_commit { - my $commit_id = shift; - my $commit_text = shift; - - my @commit_lines; +sub parse_commit_text { + my ($commit_text, $withparents) = @_; + my @commit_lines = split '\n', $commit_text; my %co; - if (defined $commit_text) { - @commit_lines = @$commit_text; - } else { - local $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", "--max-count=1", - $commit_id, "--" - or return; - @commit_lines = split '\n', <$fd>; - close $fd or return; - pop @commit_lines; - } + pop @commit_lines; # Remove '\0' + my $header = shift @commit_lines; if (!($header =~ m/^[0-9a-fA-F]{40}/)) { return; } ($co{'id'}, my @parents) = split ' ', $header; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; while (my $line = shift @commit_lines) { last if $line eq "\n"; if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { $co{'tree'} = $1; + } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) { + push @parents, $1; } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { $co{'author'} = $1; $co{'author_epoch'} = $2; @@ -1318,6 +1319,8 @@ sub parse_commit { if (!defined $co{'tree'}) { return; }; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; foreach my $title (@commit_lines) { $title =~ s/^ //; @@ -1367,6 +1370,52 @@ sub parse_commit { return %co; } +sub parse_commit { + my ($commit_id) = @_; + my %co; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--parents", + "--header", + "--max-count=1", + $commit_id, + "--", + or die_error(undef, "Open git-rev-list failed"); + %co = parse_commit_text(<$fd>, 1); + close $fd; + + return %co; +} + +sub parse_commits { + my ($commit_id, $maxcount, $skip, $arg, $filename) = @_; + my @cos; + + $maxcount ||= 1; + $skip ||= 0; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + ($arg ? ($arg) : ()), + ("--max-count=" . $maxcount), + ("--skip=" . $skip), + $commit_id, + "--", + ($filename ? ($filename) : ()) + or die_error(undef, "Open git-rev-list failed"); + while (my $line = <$fd>) { + my %co = parse_commit_text($line); + push @cos, \%co; + } + close $fd; + + return wantarray ? @cos : \@cos; +} + # parse ref from ref_file, given by ref_id, with given type sub parse_ref { my $ref_file = shift; @@ -1667,6 +1716,7 @@ sub git_header_html { } print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print < @@ -1675,7 +1725,7 @@ sub git_header_html { - + $title EOF @@ -1728,6 +1778,9 @@ EOF print " / $action"; } print "\n"; + } + my ($have_search) = gitweb_check_feature('search'); + if ((defined $project) && ($have_search)) { if (!defined $searchtext) { $searchtext = ""; } @@ -2009,6 +2062,48 @@ sub git_get_link_target { return $link_target; } +# given link target, and the directory (basedir) the link is in, +# return target of link relative to top directory (top tree); +# return undef if it is not possible (including absolute links). +sub normalize_link_target { + my ($link_target, $basedir, $hash_base) = @_; + + # we can normalize symlink target only if $hash_base is provided + return unless $hash_base; + + # absolute symlinks (beginning with '/') cannot be normalized + return if (substr($link_target, 0, 1) eq '/'); + + # normalize link target to path from top (root) tree (dir) + my $path; + if ($basedir) { + $path = $basedir . '/' . $link_target; + } else { + # we are in top (root) tree (dir) + $path = $link_target; + } + + # remove //, /./, and /../ + my @path_parts; + foreach my $part (split('/', $path)) { + # discard '.' and '' + next if (!$part || $part eq '.'); + # handle '..' + if ($part eq '..') { + if (@path_parts) { + pop @path_parts; + } else { + # link leads outside repository (outside top dir) + return; + } + } else { + push @path_parts, $part; + } + } + $path = join('/', @path_parts); + + return $path; +} # print tree entry (row of git_tree), but without encompassing element sub git_print_tree_entry { @@ -2030,7 +2125,15 @@ sub git_print_tree_entry { if (S_ISLNK(oct $t->{'mode'})) { my $link_target = git_get_link_target($t->{'hash'}); if ($link_target) { - print " -> " . esc_path($link_target); + my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + if (defined $norm_target) { + print " -> " . + $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, + file_name=>$norm_target), + -title => $norm_target}, esc_path($link_target)); + } else { + print " -> " . esc_path($link_target); + } } } print "\n"; @@ -2172,7 +2275,7 @@ sub git_difftree_body { my $mode_chnge = ""; if ($diff{'from_mode'} != $diff{'to_mode'}) { $mode_chnge = "[changed"; - if ($from_file_type != $to_file_type) { + if ($from_file_type ne $to_file_type) { $mode_chnge .= " from $from_file_type to $to_file_type"; } if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) { @@ -2276,7 +2379,6 @@ sub git_patchset_body { my $patch_line; my $diffinfo; my (%from, %to); - my ($from_id, $to_id); print "
\n"; @@ -2290,6 +2392,7 @@ sub git_patchset_body { PATCH: while ($patch_line) { my @diff_header; + my ($from_id, $to_id); # git diff header #assert($patch_line =~ m/^diff /) if DEBUG; @@ -2301,7 +2404,7 @@ sub git_patchset_body { while ($patch_line = <$fd>) { chomp $patch_line; - last EXTENDED_HEADER if ($patch_line =~ m/^--- /); + 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; @@ -2337,11 +2440,15 @@ sub git_patchset_body { $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, hash=>$diffinfo->{'from_id'}, file_name=>$from{'file'}); + } else { + delete $from{'href'}; } if ($diffinfo->{'status'} ne "D") { # not deleted file $to{'href'} = href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, file_name=>$to{'file'}); + } else { + delete $to{'href'}; } # this is first patch for raw difftree line with $patch_idx index # we index @$difftree array from 0, but number patches from 1 @@ -2373,11 +2480,11 @@ sub git_patchset_body { # match if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, - esc_path($from{'file'})); + esc_path($from{'file'})); } if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { - $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, - esc_path($to{'file'})); + $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); } # match if ($patch_line =~ m/\s(\d{6})$/) { @@ -2416,6 +2523,8 @@ sub git_patchset_body { # from-file/to-file diff header $patch_line = $last_patch_line; + last PATCH unless $patch_line; + next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; if ($from{'href'}) { $patch_line = '--- a/' . @@ -2585,18 +2694,19 @@ sub git_project_list_body { sub git_shortlog_body { # uses global variable $project - my ($revlist, $from, $to, $refs, $extra) = @_; + my ($commitlist, $from, $to, $refs, $extra) = @_; + + my $have_snapshot = gitweb_have_snapshot(); $from = 0 unless defined $from; - $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); print "\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { - my $commit = $revlist->[$i]; - #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my %co = %{$commitlist->[$i]}; + my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); - my %co = parse_commit($commit); if ($alternate) { print "\n"; } else { @@ -2614,7 +2724,7 @@ sub git_shortlog_body { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); - if (gitweb_have_snapshot()) { + if ($have_snapshot) { print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); } print "\n" . @@ -2630,23 +2740,19 @@ sub git_shortlog_body { sub git_history_body { # Warning: assumes constant type (blob or tree) during history - my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; $from = 0 unless defined $from; - $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); + $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist}); print "
\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { - if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { - next; - } - - my $commit = $1; - my %co = parse_commit($commit); + my %co = %{$commitlist->[$i]}; if (!%co) { next; } + my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); @@ -2714,8 +2820,12 @@ sub git_tags_body { print "\n"; } $alternate ^= 1; - print "\n" . - "\n"; + } else { + print "\n"; + } + print "\n" . @@ -2789,6 +2899,58 @@ sub git_heads_body { print "
$tag{'age'}" . + if (defined $tag{'age'}) { + print "$tag{'age'}" . $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}), -class => "list name"}, esc_html($tag{'name'})) . "
\n"; } +sub git_search_grep_body { + my ($commitlist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); + + print "\n"; + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my %co = %{$commitlist->[$i]}; + if (!%co) { + next; + } + my $commit = $co{'id'}; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n" . + "\n" . + "\n"; + } + if (defined $extra) { + print "\n" . + "\n" . + "\n"; + } + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "
"); + my $comment = $co{'comment'}; + foreach my $line (@$comment) { + if ($line =~ m/^(.*)($searchtext)(.*)$/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$match$trail"; + print chop_str($text, 80, 5) . "
\n"; + } + } + print "
" . + $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 "
$extra
\n"; +} + ## ====================================================================== ## ====================================================================== ## actions @@ -2844,7 +3006,7 @@ sub git_project_index { foreach my $pr (@projects) { if (!exists $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$project"); + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}"); } my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'}); @@ -2860,15 +3022,17 @@ sub git_project_index { sub git_summary { my $descr = git_get_project_description($project) || "none"; - my $head = git_get_head_hash($project); - my %co = parse_commit($head); + my %co = parse_commit("HEAD"); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); + my $head = $co{'id'}; my $owner = git_get_project_owner($project); my $refs = git_get_references(); - my @taglist = git_get_tags_list(15); - my @headlist = git_get_heads_list(15); + # These get_*_list functions return one more to allow us to see if + # there are more ... + my @taglist = git_get_tags_list(16); + my @headlist = git_get_heads_list(16); my @forklist; my ($check_forks) = gitweb_check_feature('forks'); @@ -2904,30 +3068,32 @@ sub git_summary { } } - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - git_get_head_hash($project), "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + # we need to request one more than 16 (0..15) to check if + # those 16 are all + my @commitlist = parse_commits($head, 17); git_print_header_div('shortlog'); - git_shortlog_body(\@revlist, 0, 15, $refs, + git_shortlog_body(\@commitlist, 0, 15, $refs, + $#commitlist <= 15 ? undef : $cgi->a({-href => href(action=>"shortlog")}, "...")); if (@taglist) { git_print_header_div('tags'); git_tags_body(\@taglist, 0, 15, + $#taglist <= 15 ? undef : $cgi->a({-href => href(action=>"tags")}, "...")); } if (@headlist) { git_print_header_div('heads'); git_heads_body(\@headlist, $head, 0, 15, + $#headlist <= 15 ? undef : $cgi->a({-href => href(action=>"heads")}, "...")); } if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, undef, 0, 15, + $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), 'noheader'); } @@ -3053,9 +3219,14 @@ HTML esc_html($rev)); print "\n"; } + open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") + or die_error("could not open git-rev-parse"); + my $parent_commit = <$dd>; + close $dd; + chomp($parent_commit); my $blamed = href(action => 'blame', file_name => $meta->{'filename'}, - hash_base => $full_rev); + hash_base => $parent_commit); print ""; print $cgi->a({ -href => "$blamed#l$orig_lineno", -id => "l$lineno", @@ -3469,28 +3640,25 @@ sub git_log { } my $refs = git_get_references(); - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + my @commitlist = parse_commits($hash, 101, (100 * $page)); - my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1))); git_header_html(); git_print_page_nav('log','', $hash,undef,undef, $paging_nav); - if (!@revlist) { + if (!@commitlist) { my %co = parse_commit($hash); git_print_header_div('summary', $project); print "
Last change $co{'age_string'}.

\n"; } - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = format_ref_marker($refs, $commit); - my %co = parse_commit($commit); + my $to = ($#commitlist >= 99) ? (99) : ($#commitlist); + for (my $i = 0; $i <= $to; $i++) { + my %co = %{$commitlist[$i]}; next if !%co; + my $commit = $co{'id'}; + my $ref = format_ref_marker($refs, $commit); my %ad = parse_date($co{'author_epoch'}); git_print_header_div('commit', "$co{'age_string'}" . @@ -3512,6 +3680,12 @@ sub git_log { git_print_log($co{'comment'}, -final_empty_line=> 1); print "
\n"; } + if ($#commitlist >= 100) { + print "
\n"; + print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + print "
\n"; + } git_footer_html(); } @@ -3524,15 +3698,46 @@ sub git_commit { my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $parent = $co{'parent'}; + my $parent = $co{'parent'}; + my $parents = $co{'parents'}; # listref + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if (!defined $parent) { + # --root commitdiff + $formats_nav .= '(initial)'; + } elsif (@$parents == 1) { + # single parent commit + $formats_nav .= + '(parent: ' . + $cgi->a({-href => href(action=>"commit", + hash=>$parent)}, + esc_html(substr($parent, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + '(merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @$parents ) . + ')'; + } + if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + my @difftree; + if (@$parents <= 1) { + # difftree output is not printed for merges + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, $parent, $hash, "--" + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); + } # non-textual hash id's can be cached my $expires; @@ -3544,16 +3749,10 @@ sub git_commit { my $have_snapshot = gitweb_have_snapshot(); - my @views_nav = (); - if (defined $file_name && defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, - "blame"); - } git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, - join (' | ', @views_nav)); + $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -3594,7 +3793,7 @@ sub git_commit { } print "" . "\n"; - my $parents = $co{'parents'}; + foreach my $par (@$parents) { print "" . "parent" . @@ -3616,7 +3815,10 @@ sub git_commit { git_print_log($co{'comment'}); print "\n"; - git_difftree_body(\@difftree, $hash, $parent); + if (@$parents <= 1) { + # do not output difftree/whatchanged for merges + git_difftree_body(\@difftree, $hash, $parent); + } git_footer_html(); } @@ -4012,12 +4214,7 @@ sub git_history { $ftype = git_get_type($hash); } - open my $fd, "-|", - git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name - or die_error(undef, "Open git-rev-list-failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd - or die_error(undef, "Reading git-rev-list failed"); + my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name); my $paging_nav = ''; if ($page > 0) { @@ -4033,7 +4230,7 @@ sub git_history { $paging_nav .= "first"; $paging_nav .= " ⋅ prev"; } - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name, page=>$page+1), @@ -4042,11 +4239,11 @@ sub git_history { $paging_nav .= " ⋅ next"; } my $next_link = ''; - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name, page=>$page+1), - -title => "Alt-n"}, "next"); + -accesskey => "n", -title => "Alt-n"}, "next"); } git_header_html(); @@ -4054,13 +4251,17 @@ sub git_history { git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); - git_history_body(\@revlist, ($page * 100), $#revlist, + git_history_body(\@commitlist, 0, 99, $refs, $hash_base, $ftype, $next_link); git_footer_html(); } sub git_search { + my ($have_search) = gitweb_check_feature('search'); + if (!$have_search) { + die_error('403 Permission denied', "Permission denied"); + } if (!defined $searchtext) { die_error(undef, "Text field empty"); } @@ -4071,6 +4272,9 @@ sub git_search { if (!%co) { die_error(undef, "Unknown commit object"); } + if (!defined $page) { + $page = 0; + } $searchtype ||= 'commit'; if ($searchtype eq 'pickaxe') { @@ -4083,66 +4287,63 @@ sub git_search { } git_header_html(); - git_print_page_nav('','', $hash,$co{'tree'},$hash); - git_print_header_div('commit', esc_html($co{'title'}), $hash); - print "\n"; - my $alternate = 1; if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { - $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", $hash, "--" - or next; - while (my $commit_text = <$fd>) { - if (!grep m/$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) { - next; - } - my @commit_lines = split "\n", $commit_text; - my %co = parse_commit(undef, \@commit_lines); - if (!%co) { - next; - } - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n" . - "\n" . - "\n"; + my $greptype; + if ($searchtype eq 'commit') { + $greptype = "--grep="; + } elsif ($searchtype eq 'author') { + $greptype = "--author="; + } elsif ($searchtype eq 'committer') { + $greptype = "--committer="; } - close $fd; + $greptype .= $searchtext; + my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype); + + my $paging_nav = ''; + if ($page > 0) { + $paging_nav .= + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype)}, + "first"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= "first"; + $paging_nav .= " ⋅ prev"; + } + if ($#commitlist >= 100) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ 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); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + git_search_grep_body(\@commitlist, 0, 99, $next_link); } if ($searchtype eq 'pickaxe') { + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + + print "
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "
"); - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - if ($line =~ m/^(.*)($searchtext)(.*)$/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$match$trail"; - print chop_str($text, 80, 5) . "
\n"; - } - } - print "
" . - $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 "
\n"; + my $alternate = 1; $/ = "\n"; my $git_command = git_cmd_str(); open my $fd, "-|", "$git_command rev-list $hash | " . @@ -4197,8 +4398,9 @@ sub git_search { } } close $fd; + + print "
\n"; } - print "\n"; git_footer_html(); } @@ -4237,26 +4439,21 @@ sub git_shortlog { } my $refs = git_get_references(); - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + my @commitlist = parse_commits($hash, 101, (100 * $page)); - my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1))); my $next_link = ''; - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), - -title => "Alt-n"}, "next"); + -accesskey => "n", -title => "Alt-n"}, "next"); } - git_header_html(); git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); git_print_header_div('summary', $project); - git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link); git_footer_html(); } @@ -4276,11 +4473,7 @@ sub git_feed { # log/feed of current (HEAD) branch, log of given branch, history of file/directory my $head = $hash || 'HEAD'; - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", - $head, "--", (defined $file_name ? $file_name : ()) - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-rev-list failed"); + my @commitlist = parse_commits($head, 150); my %latest_commit; my %latest_date; @@ -4290,8 +4483,8 @@ sub git_feed { # browser (feed reader) prefers text/xml $content_type = 'text/xml'; } - if (defined($revlist[0])) { - %latest_commit = parse_commit($revlist[0]); + if (defined($commitlist[0])) { + %latest_commit = %{$commitlist[0]}; %latest_date = parse_date($latest_commit{'author_epoch'}); print $cgi->header( -type => $content_type, @@ -4381,9 +4574,9 @@ XML } # contents - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = parse_commit($commit); + for (my $i = 0; $i <= $#commitlist; $i++) { + my %co = %{$commitlist[$i]}; + my $commit = $co{'id'}; # we read 150, we always show 30 and the ones more recent than 48 hours if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; @@ -4391,7 +4584,7 @@ XML my %cd = parse_date($co{'author_epoch'}); # get list of changed files - open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ()) or next; my @difftree = map { chomp; $_ } <$fd>;