X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/caf931b11c07a008412bb726da5547e5efb9d3b0835050c0b98bd57cfc817140..a0dfc1488096d018259251282421d36bf9f42d33eaff45df7af5f71cfed831bb:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index b90bc27..67a6b26 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(); @@ -68,6 +72,10 @@ our $logo_label = "git homepage"; # source of projects list our $projects_list = "++GITWEB_LIST++"; +# default order of projects list +# valid values are none, project, descr, owner, and age +our $default_projects_order = "project"; + # show repository only if this file exists # (only effective if this variable evaluates to true) our $export_ok = "++GITWEB_EXPORT_OK++"; @@ -173,8 +181,8 @@ our %feature = ( # projects matching $projname/*.git will not be shown in the main # projects list, instead a '+' mark will be added to $projname # there and a 'forks' view will be enabled for the project, listing - # all the forks. This feature is supported only if project list - # is taken from a directory, not file. + # all the forks. If project list is taken from a file, forks have + # to be listed after the main project. # To enable system wide have in $GITWEB_CONFIG # $feature{'forks'}{'default'} = [1]; @@ -557,12 +565,6 @@ sub validate_refname { return $input; } -# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT); -sub to_utf8 { - my $str = shift; - return decode("utf8", $str, Encode::FB_DEFAULT); -} - # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -587,8 +589,8 @@ sub esc_html ($;%) { my $str = shift; my %opts = @_; - $str = to_utf8($str); - $str = escapeHTML($str); + $str = decode_utf8($str); + $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { $str =~ s/ / /g; } @@ -601,8 +603,8 @@ sub esc_path { my $str = shift; my %opts = @_; - $str = to_utf8($str); - $str = escapeHTML($str); + $str = decode_utf8($str); + $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { $str =~ s/ / /g; } @@ -831,7 +833,7 @@ sub file_type_long { ## ---------------------------------------------------------------------- ## functions returning short HTML fragments, or transforming HTML fragments -## which don't beling to other sections +## which don't belong to other sections # format line of commit message. sub format_log_line_html { @@ -884,7 +886,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => to_utf8($long)}, + -title => decode_utf8($long)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -983,7 +985,7 @@ sub git_get_project_config { $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); - my @x = (git_cmd(), 'repo-config'); + my @x = (git_cmd(), 'config'); if (defined $type) { push @x, $type; } push @x, "--get"; push @x, "gitweb.$key"; @@ -1044,6 +1046,8 @@ sub git_get_projects_list { $filter ||= ''; $filter =~ s/\.git$//; + my ($check_forks) = gitweb_check_feature('forks'); + if (-d $projects_list) { # search in directory my $dir = $projects_list . ($filter ? "/$filter" : ''); @@ -1051,8 +1055,6 @@ sub git_get_projects_list { $dir =~ s!/+$!!; my $pfxlen = length("$dir"); - my ($check_forks) = gitweb_check_feature('forks'); - File::Find::find({ follow_fast => 1, # follow symbolic links dangling_symlinks => 0, # ignore dangling symlinks, silently @@ -1078,7 +1080,9 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + my %paths; open my ($fd), $projects_list or return; + PROJECT: while (my $line = <$fd>) { chomp $line; my ($path, $owner) = split ' ', $line; @@ -1091,24 +1095,41 @@ sub git_get_projects_list { # looking for forks; my $pfx = substr($path, 0, length($filter)); if ($pfx ne $filter) { - next; + next PROJECT; } my $sfx = substr($path, length($filter)); if ($sfx !~ /^\/.*\.git$/) { - next; + next PROJECT; + } + } elsif ($check_forks) { + PATH: + foreach my $filter (keys %paths) { + # looking for forks; + my $pfx = substr($path, 0, length($filter)); + if ($pfx ne $filter) { + next PATH; + } + my $sfx = substr($path, length($filter)); + if ($sfx !~ /^\/.*\.git$/) { + next PATH; + } + # is a fork, don't include it in + # the list + next PROJECT; } } if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, - owner => to_utf8($owner), + owner => decode_utf8($owner), }; - push @list, $pr + push @list, $pr; + (my $forks_path = $path) =~ s/\.git$//; + $paths{$forks_path}++; } } close $fd; } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; return @list; } @@ -1130,7 +1151,7 @@ sub git_get_project_owner { $pr = unescape($pr); $ow = unescape($ow); if ($pr eq $project) { - $owner = to_utf8($ow); + $owner = decode_utf8($ow); last; } } @@ -1271,36 +1292,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; @@ -1327,6 +1336,8 @@ sub parse_commit { if (!defined $co{'tree'}) { return; }; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; foreach my $title (@commit_lines) { $title =~ s/^ //; @@ -1376,6 +1387,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; @@ -1439,6 +1496,17 @@ sub parse_difftree_raw_line { $res{'file'} = unquote($7); } } + # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' + # combined diff (for merge commit) + elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) { + $res{'nparents'} = length($1); + $res{'from_mode'} = [ split(' ', $2) ]; + $res{'to_mode'} = pop @{$res{'from_mode'}}; + $res{'from_id'} = [ split(' ', $3) ]; + $res{'to_id'} = pop @{$res{'from_id'}}; + $res{'status'} = [ split('', $4) ]; + $res{'to_file'} = unquote($5); + } # 'c512b523472485aef4fff9e57b229d9d243c967f' elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { $res{'commit'} = $1; @@ -1568,7 +1636,7 @@ sub get_file_owner { } my $owner = $gcos; $owner =~ s/[,;].*$//; - return to_utf8($owner); + return decode_utf8($owner); } ## ...................................................................... @@ -1651,7 +1719,7 @@ sub git_header_html { my $title = "$site_name"; if (defined $project) { - $title .= " - $project"; + $title .= " - " . decode_utf8($project); if (defined $action) { $title .= "/$action"; if (defined $file_name) { @@ -1676,6 +1744,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 < @@ -1684,7 +1753,7 @@ sub git_header_html { - + $title EOF @@ -1760,7 +1829,7 @@ EOF $cgi->hidden(-name => "a") . "\n" . $cgi->hidden(-name => "h") . "\n" . $cgi->popup_menu(-name => 'st', -default => 'commit', - -values => ['commit', 'author', 'committer', 'pickaxe']) . + -values => ['commit', 'author', 'committer', 'pickaxe']) . $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . " search:\n", $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . @@ -1830,16 +1899,16 @@ sub git_print_page_nav { my %arg = map { $_ => {action=>$_} } @navs; if (defined $head) { for (qw(commit commitdiff)) { - $arg{$_}{hash} = $head; + $arg{$_}{'hash'} = $head; } if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { for (qw(shortlog log)) { - $arg{$_}{hash} = $head; + $arg{$_}{'hash'} = $head; } } } - $arg{tree}{hash} = $treehead if defined $treehead; - $arg{tree}{hash_base} = $treebase if defined $treebase; + $arg{'tree'}{'hash'} = $treehead if defined $treehead; + $arg{'tree'}{'hash_base'} = $treebase if defined $treebase; print "
\n" . (join " | ", @@ -1887,9 +1956,9 @@ sub git_print_header_div { my ($action, $title, $hash, $hash_base) = @_; my %args = (); - $args{action} = $action; - $args{hash} = $hash if $hash; - $args{hash_base} = $hash_base if $hash_base; + $args{'action'} = $action; + $args{'hash'} = $hash if $hash; + $args{'hash_base'} = $hash_base if $hash_base; print "
\n" . $cgi->a({-href => href(%args), -class => "title"}, @@ -1923,7 +1992,7 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); + -title => 'tree root'}, decode_utf8("[$project]")); print " / "; if (defined $name) { my @dirname = split '/', $name; @@ -1934,17 +2003,17 @@ sub git_print_page_path { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => esc_html($fullname)}, esc_path($dir)); + -title => $fullname}, esc_path($dir)); print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, hash_base=>$hb), - -title => esc_html($name)}, esc_path($basename)); + -title => $name}, esc_path($basename)); } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => esc_html($name)}, esc_path($basename)); + -title => $name}, esc_path($basename)); print " / "; } else { print esc_path($basename); @@ -2199,7 +2268,7 @@ sub git_difftree_body { } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, - "blob") . " | "; + "blob"); print "\n"; } elsif ($diff{'status'} eq "D") { # deleted @@ -2234,7 +2303,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)) { @@ -2335,10 +2404,10 @@ sub git_patchset_body { my ($fd, $difftree, $hash, $hash_parent) = @_; my $patch_idx = 0; + my $patch_number = 0; my $patch_line; my $diffinfo; my (%from, %to); - my ($from_id, $to_id); print "
\n"; @@ -2352,10 +2421,12 @@ 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; #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed + $patch_number++; push @diff_header, $patch_line; # extended diff header @@ -2363,7 +2434,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; @@ -2372,7 +2443,6 @@ sub git_patchset_body { push @diff_header, $patch_line; } - #last PATCH unless $patch_line; my $last_patch_line = $patch_line; # check if current patch belong to current raw line @@ -2399,11 +2469,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 @@ -2435,11 +2509,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})$/) { @@ -2478,8 +2552,13 @@ sub git_patchset_body { # from-file/to-file diff header $patch_line = $last_patch_line; + if (! $patch_line) { + print "
\n"; # class="patch" + last PATCH; + } + next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; - if ($from{'href'}) { + if ($from{'href'} && $patch_line =~ m!^--- "?a/!) { $patch_line = '--- a/' . $cgi->a({-href=>$from{'href'}, -class=>"path"}, esc_path($from{'file'})); @@ -2487,11 +2566,10 @@ sub git_patchset_body { print "
$patch_line
\n"; $patch_line = <$fd>; - #last PATCH unless $patch_line; chomp $patch_line; #assert($patch_line =~ m/^+++/) if DEBUG; - if ($to{'href'}) { + if ($to{'href'} && $patch_line =~ m!^\+\+\+ "?b/!) { $patch_line = '+++ b/' . $cgi->a({-href=>$to{'href'}, -class=>"path"}, esc_path($to{'file'})); @@ -2511,6 +2589,7 @@ sub git_patchset_body { } continue { print "
\n"; # class="patch" } + print "
No differences found
\n" if (!$patch_number); print "
\n"; # class="patchset" } @@ -2531,7 +2610,7 @@ sub git_project_list_body { ($pr->{'age'}, $pr->{'age_string'}) = @aa; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; - $pr->{'descr_long'} = to_utf8($descr); + $pr->{'descr_long'} = decode_utf8($descr); $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -2551,7 +2630,7 @@ sub git_project_list_body { push @projects, $pr; } - $order ||= "project"; + $order ||= $default_projects_order; $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); @@ -2647,20 +2726,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 { @@ -2694,23 +2772,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); @@ -2778,8 +2852,12 @@ sub git_tags_body { print "\n"; } $alternate ^= 1; - print "\n" . - "\n"; + } else { + print "\n"; + } + print "\n" . @@ -2853,13 +2931,65 @@ 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 sub git_project_list { my $order = $cgi->param('o'); - if (defined $order && $order !~ m/project|descr|owner|age/) { + if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(undef, "Unknown order parameter"); } @@ -2882,7 +3012,7 @@ sub git_project_list { sub git_forks { my $order = $cgi->param('o'); - if (defined $order && $order !~ m/project|descr|owner|age/) { + if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(undef, "Unknown order parameter"); } @@ -2908,7 +3038,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'}); @@ -2972,14 +3102,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) { @@ -3001,7 +3127,7 @@ sub git_summary { git_project_list_body(\@forklist, undef, 0, 15, $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), - 'noheader'); + 'noheader'); } git_footer_html(); @@ -3060,7 +3186,7 @@ sub git_blame2 { } $ftype = git_get_type($hash); if ($ftype !~ "blob") { - die_error("400 Bad Request", "Object is not a blob"); + die_error('400 Bad Request', "Object is not a blob"); } open ($fd, "-|", git_cmd(), "blame", '-p', '--', $file_name, $hash_base) @@ -3108,7 +3234,7 @@ HTML my $rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; my %date = parse_date($meta->{'author-time'}, - $meta->{'author-tz'}); + $meta->{'author-tz'}); my $date = $date{'iso-tz'}; if ($group_size) { $current_color = ++$current_color % $num_colors; @@ -3120,19 +3246,24 @@ HTML print " rowspan=\"$group_size\"" if ($group_size > 1); print ">"; print $cgi->a({-href => href(action=>"commit", - hash=>$full_rev, - file_name=>$file_name)}, - esc_html($rev)); + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($rev)); print "\n"; } + open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") + or die_error(undef, "Open git-rev-parse failed"); + my $parent_commit = <$dd>; + close $dd; + chomp($parent_commit); my $blamed = href(action => 'blame', - file_name => $meta->{'filename'}, - hash_base => $full_rev); + file_name => $meta->{'filename'}, + hash_base => $parent_commit); print ""; print $cgi->a({ -href => "$blamed#l$orig_lineno", - -id => "l$lineno", - -class => "linenr" }, - esc_html($lineno)); + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); print ""; print "" . esc_html($data) . "\n"; print "\n"; @@ -3511,7 +3642,7 @@ sub git_snapshot { $hash = git_get_head_hash($project); } - my $filename = basename($project) . "-$hash.tar.$suffix"; + my $filename = decode_utf8(basename($project)) . "-$hash.tar.$suffix"; print $cgi->header( -type => "application/$ctype", @@ -3522,8 +3653,8 @@ sub git_snapshot { my $name = $project; $name =~ s/\047/\047\\\047\047/g; open my $fd, "-|", - "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" - or die_error(undef, "Execute git-tar-tree failed."); + "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" + or die_error(undef, "Execute git-tar-tree failed"); binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -3541,28 +3672,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'}" . @@ -3584,6 +3712,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(); } @@ -3617,7 +3751,7 @@ sub git_commit { $formats_nav .= '(merge: ' . join(' ', map { - $cgi->a({-href => href(action=>"commitdiff", + $cgi->a({-href => href(action=>"commit", hash=>$_)}, esc_html(substr($_, 0, 7))); } @$parents ) . @@ -3632,7 +3766,7 @@ sub git_commit { # 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"); + or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); } @@ -3783,7 +3917,7 @@ sub git_blobdiff { # read raw output open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, - "--", $file_name + "--", (defined $file_parent ? $file_parent : ()), $file_name or die_error(undef, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; close $fd @@ -3832,8 +3966,9 @@ sub git_blobdiff { # open patch output open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - '-p', $hash_parent_base, $hash_base, - "--", $file_name + '-p', ($format eq 'html' ? "--full-index" : ()), + $hash_parent_base, $hash_base, + "--", (defined $file_parent ? $file_parent : ()), $file_name or die_error(undef, "Open git-diff-tree failed"); } @@ -3867,7 +4002,8 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, + 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 { @@ -4112,12 +4248,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) { @@ -4133,7 +4264,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), @@ -4142,11 +4273,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(); @@ -4154,7 +4285,7 @@ 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(); @@ -4175,6 +4306,9 @@ sub git_search { if (!%co) { die_error(undef, "Unknown commit object"); } + if (!defined $page) { + $page = 0; + } $searchtype ||= 'commit'; if ($searchtype eq 'pickaxe') { @@ -4187,11 +4321,7 @@ 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') { my $greptype; if ($searchtype eq 'commit') { @@ -4201,52 +4331,53 @@ sub git_search { } elsif ($searchtype eq 'committer') { $greptype = "--committer="; } - $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", ($greptype . $searchtext), - $hash, "--" - or next; - while (my $commit_text = <$fd>) { - 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"; + $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"; } - close $fd; + 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 | " . @@ -4301,8 +4432,9 @@ sub git_search { } } close $fd; + + print "
\n"; } - print "\n"; git_footer_html(); } @@ -4341,26 +4473,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(); } @@ -4380,11 +4507,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; @@ -4394,8 +4517,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, @@ -4485,9 +4608,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; @@ -4495,7 +4618,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>;