X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/9910c8176a1c15391125f22718ea1e9869e79f58b61b957822508e15aec99443..f25fb5055583bf78ce31368b8a0566aa9701a670c81e847452f6f19fc5fe873f:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index 11e849f..820a318 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -129,6 +129,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 +358,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; } @@ -1262,36 +1271,25 @@ 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) = @_; + 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]; + $co{'id'} = $header; + my @parents; while (my $line = shift @commit_lines) { last if $line eq "\n"; if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { $co{'tree'} = $1; + } elsif ($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 +1316,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 +1367,73 @@ sub parse_commit { return %co; } +sub parse_commit { + my ($commit_id) = @_; + my %co; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + "--max-count=1", + $commit_id, + "--", + or die_error(undef, "Open git-rev-list failed"); + %co = parse_commit_text(<$fd>); + close $fd; + + return %co; +} + +sub parse_commits { + my ($commit_id, $maxcount, $skip, $arg, $filename) = @_; + my @cos; + + $maxcount ||= 1; + $skip ||= 0; + + # Delete once rev-list supports the --skip option + if ($skip > 0) { + open my $fd, "-|", git_cmd(), "rev-list", + ($arg ? ($arg) : ()), + ("--max-count=" . ($maxcount + $skip)), + $commit_id, + "--", + ($filename ? ($filename) : ()) + or die_error(undef, "Open git-rev-list failed"); + while (my $line = <$fd>) { + if ($skip-- <= 0) { + chomp $line; + my %co = parse_commit($line); + push @cos, \%co; + } + } + close $fd; + + return wantarray ? @cos : \@cos; + } + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + ($arg ? ($arg) : ()), + ("--max-count=" . $maxcount), + # Add once rev-list supports the --skip option + # ("--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; @@ -1728,6 +1795,9 @@ EOF print " / $action"; } print "\n"; + } + my ($have_search) = gitweb_check_feature('search'); + if ((defined $project) && ($have_search)) { if (!defined $searchtext) { $searchtext = ""; } @@ -2635,20 +2705,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 { @@ -2841,6 +2910,58 @@ sub git_heads_body { print "
\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 @@ -2960,14 +3081,10 @@ sub git_summary { # we need to request one more than 16 (0..15) to check if # those 16 are all - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - $head, "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + my @commitlist = parse_commits($head, 17); git_print_header_div('shortlog'); - git_shortlog_body(\@revlist, 0, 15, $refs, - $#revlist <= 15 ? undef : + git_shortlog_body(\@commitlist, 0, 15, $refs, + $#commitlist <= 15 ? undef : $cgi->a({-href => href(action=>"shortlog")}, "...")); if (@taglist) { @@ -3529,28 +3646,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'}" . @@ -3572,6 +3686,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(); } @@ -4149,6 +4269,10 @@ sub git_history { } 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"); } @@ -4159,6 +4283,9 @@ sub git_search { if (!%co) { die_error(undef, "Unknown commit object"); } + if (!defined $page) { + $page = 0; + } $searchtype ||= 'commit'; if ($searchtype eq 'pickaxe') { @@ -4171,66 +4298,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 | " . @@ -4285,8 +4409,9 @@ sub git_search { } } close $fd; + + print "
\n"; } - print "\n"; git_footer_html(); } @@ -4325,26 +4450,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($head, 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(); }