X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/a7ab0b416cc5f68d6e9135680eff4fc34327d39baf74967dc50b41ec2b5eb097..307d8d4dcbfefa6cb91eb85a4327da84e35f3e91c86b8c17756854e4145683c6:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index e3c9299..0a499cc 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -54,13 +54,16 @@ our $stylesheet; # default is not to define style sheet, but it can be overwritten later undef $stylesheet; -# URI of GIT logo +# URI of GIT logo (72x27 size) our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type our $favicon = "++GITWEB_FAVICON++"; -our $githelp_url = "http://git.or.cz/"; -our $githelp_label = "git homepage"; +# URI and label (title) of GIT logo link +#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; +#our $logo_label = "git documentation"; +our $logo_url = "http://git.or.cz/"; +our $logo_label = "git homepage"; # source of projects list our $projects_list = "++GITWEB_LIST++"; @@ -98,22 +101,63 @@ our %feature = ( # # use gitweb_check_feature() to check if is enabled + # Enable the 'blame' blob view, showing the last commit that modified + # each line in the file. This can be very CPU-intensive. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'blame'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'blame'}{'override'} = 1; + # and in project config gitweb.blame = 0|1; 'blame' => { 'sub' => \&feature_blame, 'override' => 0, 'default' => [0]}, + # Enable the 'snapshot' link, providing a compressed tarball of any + # tree. This can potentially generate high traffic if you have large + # project. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'snapshot'}{'default'} = [undef]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'blame'}{'override'} = 1; + # and in project config gitweb.snapshot = none|gzip|bzip2; 'snapshot' => { 'sub' => \&feature_snapshot, 'override' => 0, # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + # 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. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'pickaxe'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'pickaxe'}{'override'} = 1; + # and in project config gitweb.pickaxe = 0|1; 'pickaxe' => { 'sub' => \&feature_pickaxe, 'override' => 0, 'default' => [1]}, + # Make gitweb use an alternative format of the URLs which can be + # more readable and natural-looking: project name is embedded + # directly in the path and the query string contains other + # auxiliary information. All gitweb installations recognize + # URL in either format; this configures in which formats gitweb + # generates links. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'pathinfo'}{'default'} = [1]; + # Project specific override is not supported. + + # Note that you will need to change the default location of CSS, + # favicon, logo and possibly other files to an absolute URL. Also, + # if gitweb.cgi serves as your indexfile, you will need to force + # $my_uri to contain the script name in your $GITWEB_CONFIG. 'pathinfo' => { 'override' => 0, 'default' => [0]}, @@ -134,12 +178,6 @@ sub gitweb_check_feature { return $sub->(@defaults); } -# To enable system wide have in $GITWEB_CONFIG -# $feature{'blame'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; -# and in project config gitweb.blame = 0|1; - sub feature_blame { my ($val) = git_get_project_config('blame', '--bool'); @@ -152,12 +190,6 @@ sub feature_blame { return $_[0]; } -# To disable system wide have in $GITWEB_CONFIG -# $feature{'snapshot'}{'default'} = [undef]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'blame'}{'override'} = 1; -# and in project config gitweb.snapshot = none|gzip|bzip2 - sub feature_snapshot { my ($ctype, $suffix, $command) = @_; @@ -181,12 +213,6 @@ sub gitweb_have_snapshot { return $have_snapshot; } -# To enable system wide have in $GITWEB_CONFIG -# $feature{'pickaxe'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'pickaxe'}{'override'} = 1; -# and in project config gitweb.pickaxe = 0|1; - sub feature_pickaxe { my ($val) = git_get_project_config('pickaxe', '--bool'); @@ -412,6 +438,9 @@ sub href(%) { my %params = @_; my $href = $my_uri; + # XXX: Warning: If you touch this, check the search form for updating, + # too. + my @mapping = ( project => "p", action => "a", @@ -491,6 +520,12 @@ 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 { @@ -513,7 +548,7 @@ sub esc_url { # replace invalid utf8 character with SUBSTITUTION sequence sub esc_html { my $str = shift; - $str = decode("utf8", $str, Encode::FB_DEFAULT); + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) @@ -716,7 +751,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => decode("utf8", $long, Encode::FB_DEFAULT)}, + -title => to_utf8($long)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -891,7 +926,7 @@ sub git_get_projects_list { if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), + owner => to_utf8($owner), }; push @list, $pr } @@ -920,7 +955,7 @@ sub git_get_project_owner { $pr = unescape($pr); $ow = unescape($ow); if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + $owner = to_utf8($ow); last; } } @@ -998,6 +1033,9 @@ sub parse_date { $date{'hour_local'} = $hour; $date{'minute_local'} = $min; $date{'tz_local'} = $tz; + $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s", + 1900+$year, $mon+1, $mday, + $hour, $min, $sec, $tz); return %date; } @@ -1036,6 +1074,24 @@ sub parse_tag { return %tag } +sub git_get_last_activity { + my ($path) = @_; + my $fd; + + $git_dir = "$projectroot/$path"; + open($fd, "-|", git_cmd(), 'for-each-ref', + '--format=%(refname) %(committer)', + '--sort=-committerdate', + 'refs/heads') or return; + my $most_recent = <$fd>; + close $fd or return; + if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) { + my $timestamp = $1; + my $age = time - $timestamp; + return ($age, age_string($age)); + } +} + sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -1111,6 +1167,9 @@ sub parse_commit { last; } } + if ($co{'title'} eq "") { + $co{'title'} = $co{'title_short'} = '(no commit message)'; + } # remove added spaces foreach my $line (@commit_lines) { $line =~ s/^ //; @@ -1282,7 +1341,7 @@ sub get_file_owner { } my $owner = $gcos; $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); + return to_utf8($owner); } ## ...................................................................... @@ -1438,11 +1497,9 @@ EOF } print "
\n" . - "" . - "\"git\"" . - "\n"; + $cgi->a({-href => esc_url($logo_url), + -title => $logo_label}, + qq()); print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); @@ -1463,6 +1520,7 @@ EOF } $cgi->param("a", "search"); $cgi->param("h", $search_hash); + $cgi->param("p", $project); print $cgi->startform(-method => "get", -action => $my_uri) . "
\n" . $cgi->hidden(-name => "p") . "\n" . @@ -1730,13 +1788,13 @@ sub git_print_tree_entry { if ($t->{'type'} eq "blob") { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_html($t->{'name'})) . "\n"; + file_name=>"$basedir$t->{'name'}", %base_key), + -class => "list"}, esc_html($t->{'name'})) . "\n"; print ""; if ($have_blame) { print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { if ($have_blame) { @@ -1748,8 +1806,8 @@ sub git_print_tree_entry { } print " | " . $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base, - file_name=>"$basedir$t->{'name'}")}, - "raw"); + file_name=>"$basedir$t->{'name'}")}, + "raw"); print "\n"; } elsif ($t->{'type'} eq "tree") { @@ -1817,7 +1875,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_html($diff{'file'})); print "\n"; print "$mode_chng\n"; print ""; @@ -1844,11 +1902,11 @@ sub git_difftree_body { print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})}, - "history"); + file_name=>$diff{'file'})}, + "history"); print "\n"; } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed @@ -1869,8 +1927,8 @@ sub git_difftree_body { } print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); print "\n"; print "$mode_chnge\n"; print ""; @@ -1881,19 +1939,19 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff"); } print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$hash, - file_name=>$diff{'file'})}, - "history"); + file_name=>$diff{'file'})}, + "history"); print "\n"; } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied @@ -1921,19 +1979,19 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); } else { print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); } print " | "; } print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "blame") . " | "; + file_name=>$diff{'from_file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "history"); + file_name=>$diff{'from_file'})}, + "history"); print "\n"; } # we should not encounter Unmerged (U) or Unknown (X) status @@ -1985,14 +2043,14 @@ sub git_patchset_body { print "
" . file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}) . "(new)" . + $diffinfo->{'to_id'}) . " (new)" . "
\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "D") { # deleted print "
" . file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . "(deleted)" . + $diffinfo->{'from_id'}) . " (deleted)" . "
\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "R" || # renamed @@ -2096,8 +2154,10 @@ sub git_shortlog_body { print "\n" . "" . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " . - $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); + if (gitweb_have_snapshot()) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } print "\n" . "\n"; } @@ -2287,16 +2347,11 @@ sub git_project_list { die_error(undef, "No projects found"); } foreach my $pr (@list) { - my $head = git_get_head_hash($pr->{'path'}); - if (!defined $head) { + my (@aa) = git_get_last_activity($pr->{'path'}); + unless (@aa) { next; } - $git_dir = "$projectroot/$pr->{'path'}"; - my %co = parse_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; + ($pr->{'age'}, $pr->{'age_string'}) = @aa; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr'} = chop_str($descr, 25, 5); @@ -2346,7 +2401,7 @@ sub git_project_list { "\n"; } if ($order eq "age") { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; print "Last Change\n"; } else { print "" . @@ -2368,8 +2423,8 @@ sub git_project_list { -class => "list"}, esc_html($pr->{'path'})) . "\n" . "" . esc_html($pr->{'descr'}) . "\n" . "" . chop_str($pr->{'owner'}, 15) . "\n"; - print "{'commit'}{'age'}) . "\">" . - $pr->{'commit'}{'age_string'} . "\n" . + print "{'age'}) . "\">" . + $pr->{'age_string'} . "\n" . "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . @@ -2525,7 +2580,8 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-p', '--', + $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2549,33 +2605,52 @@ sub git_blame2 { HTML - while (<$fd>) { - my ($full_rev, $author, $date, $lineno, $data) = - /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; + my %metainfo = (); + while (1) { + $_ = <$fd>; + last unless defined $_; + my ($full_rev, $orig_lineno, $lineno, $group_size) = + /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = {}; + } + my $meta = $metainfo{$full_rev}; + while (<$fd>) { + last if (s/^\t//); + if (/^(\S+) (.*)$/) { + $meta->{$1} = $2; + } + } + my $data = $_; my $rev = substr($full_rev, 0, 8); - my $print_c8 = 0; - - if (!defined $last_rev) { - $last_rev = $full_rev; - $print_c8 = 1; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; + my $author = $meta->{'author'}; + my %date = parse_date($meta->{'author-time'}, + $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { $current_color = ++$current_color % $num_colors; - $print_c8 = 1; } print "\n"; - print "\n"; } - print "\n"; - print "\n"; + my $blamed = href(action => 'blame', + file_name => $meta->{'filename'}, + hash_base => $full_rev); + print ""; print "\n"; print "\n"; } @@ -2865,7 +2940,7 @@ sub git_tree { # FIXME: Should be available when we have no hash base as well. push @views_nav, $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, - "snapshot"); + "snapshot"); } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); @@ -3023,7 +3098,7 @@ sub git_commit { "blame"); } git_header_html(undef, $expires); - git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, join (' | ', @views_nav)); @@ -3666,7 +3741,7 @@ XML "\n"; } print "
\n"; @@ -3675,7 +3750,7 @@ XML next; } my $file = esc_html(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); + $file = to_utf8($file); print "$file
\n"; } print "]]>\n" .
CommitLineData
"; - if ($print_c8 == 1) { - print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + 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)); + print "" . - esc_html($lineno) . ""; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "