X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/a2458eb841ef2a1f2208dc20896c7e5b6d11308f20a25d1e26466bcee7aeb711..24b953b17157d1334f2cfc254ebfb11ea55b76624938ec7cab50ed7b7484d9d9:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index 9025db6..f651db1 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -41,16 +41,27 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; +# filename of html text to include at top of each page +our $site_header = "++GITWEB_SITE_HEADER++"; # html text to include at home page our $home_text = "++GITWEB_HOMETEXT++"; +# filename of html text to include at bottom of each page +our $site_footer = "++GITWEB_SITE_FOOTER++"; + +# URI of stylesheets +our @stylesheets = ("++GITWEB_CSS++"); +our $stylesheet; +# default is not to define style sheet, but it can be overwritten later +undef $stylesheet; -# URI of default stylesheet -our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo 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"; + # source of projects list our $projects_list = "++GITWEB_LIST++"; @@ -87,40 +98,83 @@ 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]}, ); sub gitweb_check_feature { my ($name) = @_; - return undef unless exists $feature{$name}; + return unless exists $feature{$name}; my ($sub, $override, @defaults) = ( $feature{$name}{'sub'}, $feature{$name}{'override'}, @{$feature{$name}{'default'}}); if (!$override) { return @defaults; } + if (!defined $sub) { + warn "feature $name is not overrideable"; + return @defaults; + } 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'); @@ -133,12 +187,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) = @_; @@ -155,11 +203,12 @@ sub feature_snapshot { return ($ctype, $suffix, $command); } -# 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 gitweb_have_snapshot { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + + return $have_snapshot; +} sub feature_pickaxe { my ($val) = git_get_project_config('pickaxe', '--bool'); @@ -173,6 +222,22 @@ sub feature_pickaxe { return ($_[0]); } +# checking HEAD file with -e is fragile if the repository was +# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed +# and then pruned. +sub check_head_link { + my ($dir) = @_; + my $headfile = "$dir/HEAD"; + return ((-e $headfile) || + (-l $headfile && readlink($headfile) =~ /^refs\/heads\//)); +} + +sub check_export_ok { + my ($dir) = @_; + return (check_head_link($dir) && + (!$export_ok || -e "$dir/$export_ok")); +} + # rename detection options for git-diff and git-diff-tree # - default is '-M', with the cost proportional to # (number of removed files) * (number of new files). @@ -200,11 +265,12 @@ if (defined $action) { } } +# parameters which are pathnames our $project = $cgi->param('p'); if (defined $project) { - if (!validate_input($project) || + if (!validate_pathname($project) || !(-d "$projectroot/$project") || - !(-e "$projectroot/$project/HEAD") || + !check_head_link("$projectroot/$project") || ($export_ok && !(-e "$projectroot/$project/$export_ok")) || ($strict_export && !project_in_list($project))) { undef $project; @@ -214,46 +280,48 @@ if (defined $project) { our $file_name = $cgi->param('f'); if (defined $file_name) { - if (!validate_input($file_name)) { + if (!validate_pathname($file_name)) { die_error(undef, "Invalid file parameter"); } } our $file_parent = $cgi->param('fp'); if (defined $file_parent) { - if (!validate_input($file_parent)) { + if (!validate_pathname($file_parent)) { die_error(undef, "Invalid file parent parameter"); } } +# parameters which are refnames our $hash = $cgi->param('h'); if (defined $hash) { - if (!validate_input($hash)) { + if (!validate_refname($hash)) { die_error(undef, "Invalid hash parameter"); } } our $hash_parent = $cgi->param('hp'); if (defined $hash_parent) { - if (!validate_input($hash_parent)) { + if (!validate_refname($hash_parent)) { die_error(undef, "Invalid hash parent parameter"); } } our $hash_base = $cgi->param('hb'); if (defined $hash_base) { - if (!validate_input($hash_base)) { + if (!validate_refname($hash_base)) { die_error(undef, "Invalid hash base parameter"); } } our $hash_parent_base = $cgi->param('hpb'); if (defined $hash_parent_base) { - if (!validate_input($hash_parent_base)) { + if (!validate_refname($hash_parent_base)) { die_error(undef, "Invalid hash parent base parameter"); } } +# other parameters our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]/) { @@ -279,11 +347,11 @@ sub evaluate_path_info { # find which part of PATH_INFO is project $project = $path_info; $project =~ s,/+$,,; - while ($project && !-e "$projectroot/$project/HEAD") { + while ($project && !check_head_link("$projectroot/$project")) { $project =~ s,/*[^/]*$,,; } # validate project - $project = validate_input($project); + $project = validate_pathname($project); if (!$project || ($export_ok && !-e "$projectroot/$project/$export_ok") || ($strict_export && !project_in_list($project))) { @@ -304,12 +372,12 @@ sub evaluate_path_info { } else { $action ||= "blob_plain"; } - $hash_base ||= validate_input($refname); - $file_name ||= validate_input($pathname); + $hash_base ||= validate_refname($refname); + $file_name ||= validate_pathname($pathname); } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; - $hash ||= validate_input($refname); + $hash ||= validate_refname($refname); } } evaluate_path_info(); @@ -365,6 +433,7 @@ exit; sub href(%) { my %params = @_; + my $href = $my_uri; my @mapping = ( project => "p", @@ -383,6 +452,19 @@ sub href(%) { $params{'project'} = $project unless exists $params{'project'}; + my ($use_pathinfo) = gitweb_check_feature('pathinfo'); + if ($use_pathinfo) { + # use PATH_INFO for project name + $href .= "/$params{'project'}" if defined $params{'project'}; + delete $params{'project'}; + + # Summary just uses the project path URL + if (defined $params{'action'} && $params{'action'} eq 'summary') { + delete $params{'action'}; + } + } + + # now encode the parameters explicitly my @result = (); for (my $i = 0; $i < @mapping; $i += 2) { my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); @@ -390,31 +472,66 @@ sub href(%) { push @result, $symbol . "=" . esc_param($params{$name}); } } - return "$my_uri?" . join(';', @result); + $href .= "?" . join(';', @result) if scalar @result; + + return $href; } ## ====================================================================== ## validation, quoting/unquoting and escaping -sub validate_input { - my $input = shift; +sub validate_pathname { + my $input = shift || return undef; - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; + # no '.' or '..' as elements of path, i.e. no '.' nor '..' + # at the beginning, at the end, and between slashes. + # also this catches doubled slashes + if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) { + return undef; } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { + # no null characters + if ($input =~ m!\0!) { return undef; } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { + return $input; +} + +sub validate_refname { + my $input = shift || return undef; + + # textual hashes are O.K. + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + # it must be correct pathname + $input = validate_pathname($input) + or return undef; + # restrictions on ref name according to git-check-ref-format + if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) { return undef; } 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 { + my $str = shift; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/\+/%2B/g; + $str =~ s/ /\+/g; + return $str; +} + +# quote unsafe chars in whole URL, so some charactrs cannot be quoted +sub esc_url { my $str = shift; $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; @@ -425,9 +542,10 @@ sub esc_param { # 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) return $str; } @@ -627,7 +745,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => $long}, + -title => to_utf8($long)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -720,7 +838,7 @@ sub git_get_hash_by_path { my $path = shift || return undef; my $type = shift; - my $tree = $base; + $path =~ s,/+$,,; open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path or die_error(undef, "Open git-ls-tree failed"); @@ -752,7 +870,7 @@ sub git_get_project_description { sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/cloneurl" or return undef; + open my $fd, "$projectroot/$path/cloneurl" or return; my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; @@ -778,8 +896,7 @@ sub git_get_projects_list { my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if (-e "$projectroot/$subdir/HEAD" && (!$export_ok || - -e "$projectroot/$subdir/$export_ok")) { + if (check_export_ok("$projectroot/$subdir")) { push @list, { path => $subdir }; $File::Find::prune = 1; } @@ -791,7 +908,7 @@ sub git_get_projects_list { # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; + open my ($fd), $projects_list or return; while (my $line = <$fd>) { chomp $line; my ($path, $owner) = split ' ', $line; @@ -800,11 +917,10 @@ sub git_get_projects_list { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD" && (!$export_ok || - -e "$projectroot/$path/$export_ok")) { + if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), + owner => to_utf8($owner), }; push @list, $pr } @@ -833,7 +949,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; } } @@ -911,6 +1027,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; } @@ -1024,6 +1143,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/^ //; @@ -1195,7 +1317,7 @@ sub get_file_owner { } my $owner = $gcos; $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); + return to_utf8($owner); } ## ...................................................................... @@ -1282,7 +1404,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - $file_name"; + $title .= " - " . esc_html($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -1314,8 +1436,17 @@ sub git_header_html { $title - EOF +# print out each stylesheet that exist + if (defined $stylesheet) { +#provides backwards capability for those people who define style sheet in a config file + print ''."\n"; + } else { + foreach my $stylesheet (@stylesheets) { + next unless $stylesheet; + print ''."\n"; + } + } if (defined $project) { printf(''."\n", @@ -1333,12 +1464,21 @@ EOF } print "\n" . - "\n" . - "
\n" . - "" . + "\n"; + + if (-f $site_header) { + open (my $fd, $site_header); + print <$fd>; + close $fd; + } + + print "
\n" . + "" . "\"git\"" . "\n"; - print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / "; + print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { @@ -1385,8 +1525,15 @@ sub git_footer_html { print $cgi->a({-href => href(project=>undef, action=>"project_index"), -class => "rss_logo"}, "TXT") . "\n"; } - print "
\n" . - "\n" . + print "
\n" ; + + if (-f $site_footer) { + open (my $fd, $site_footer); + print <$fd>; + close $fd; + } + + print "\n" . ""; } @@ -1520,14 +1667,14 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => '/'}, '/'); - print " "; + -title => 'tree root'}, "[$project]"); + print " / "; foreach my $dir (@dirname) { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir . '/')); - print " "; + -title => $fullname}, esc_html($dir)); + print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1536,7 +1683,7 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename . '/')); + -title => $name}, esc_html($basename)); } else { print esc_html($basename); } @@ -1610,48 +1757,45 @@ sub git_print_tree_entry { my %base_key = (); $base_key{hash_base} = $hash_base if defined $hash_base; + # The format of a table row is: mode list link. Where mode is + # the mode of the entry, list is the name of the entry, an href, + # and link is the action links of the entry. + print "" . mode_str($t->{'mode'}) . "\n"; 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" . - "" . - $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + 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"); + print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { - print " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + if ($have_blame) { + print " | "; + } + print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, "history"); } print " | " . - $cgi->a({-href => href(action=>"blob_plain", - hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, - "raw") . - "\n"; + $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "raw"); + print "\n"; } elsif ($t->{'type'} eq "tree") { - print "" . - $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + print ""; + print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, - esc_html($t->{'name'})) . - "\n" . - "" . - $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + esc_html($t->{'name'})); + print "\n"; + print ""; if (defined $hash_base) { - print " | " . - $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$basedir$t->{'name'}")}, "history"); } @@ -1672,7 +1816,7 @@ sub git_difftree_body { print "
\n"; print "\n"; - my $alternate = 0; + my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { my %diff = parse_difftree_raw_line($line); @@ -1705,47 +1849,42 @@ sub git_difftree_body { my $mode_chng = "[new $to_file_type"; $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; $mode_chng .= "]"; - print "\n" . - "\n" . - "\n"; + print "\n"; + print "\n"; } elsif ($diff{'status'} eq "D") { # deleted my $mode_chng = "[deleted $from_file_type]"; - print "\n" . - "\n" . - "\n"; + print "\n"; + print "\n"; + file_name=>$diff{'file'})}, + "history"); + print "\n"; } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed my $mode_chnge = ""; @@ -1764,42 +1903,32 @@ sub git_difftree_body { $mode_chnge .= "]\n"; } print "\n" . - "\n" . - "\n"; + print "\n"; + print "\n"; } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied @@ -1819,25 +1948,27 @@ sub git_difftree_body { hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), -class => "list"}, esc_html($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]\n" . - "\n"; } # we should not encounter Unmerged (U) or Unknown (X) status @@ -1889,14 +2020,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 @@ -1979,7 +2110,7 @@ sub git_shortlog_body { $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); print "
" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + 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'})) . - "$mode_chng" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob"); + -class => "list"}, esc_html($diff{'file'})); + print "$mode_chng"; if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); } print "" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + print ""; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})) . - "$mode_chng" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'file'})}, - "blob") . - " | "; + -class => "list"}, esc_html($diff{'file'})); + print "$mode_chng"; if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + print $cgi->a({-href => "#patch$patchno"}, "patch"); + print " | "; } + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'file'})}, - "history") . - ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - 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'}), - -class => "list"}, esc_html($diff{'file'})); - } else { # only mode changed - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); - } - print "$mode_chnge" . - $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob"); + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + print "$mode_chnge"; if ($diff{'to_id'} ne $diff{'from_id'}) { # modified if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + 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"); + 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"); } + print " | "; } - print " | " . - $cgi->a({-href => href(action=>"history", - hash_base=>$hash, file_name=>$diff{'file'})}, - "history"); + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; + print $cgi->a({-href => href(action=>"history", hash_base=>$hash, + file_name=>$diff{'file'})}, + "history"); print "" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, - "blob"); + ""; if ($diff{'to_id'} ne $diff{'from_id'}) { if ($action eq 'commitdiff') { # link to patch $patchno++; - print " | " . - $cgi->a({-href => "#patch$patchno"}, "patch"); + 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"); + 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"); } + print " | "; } + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'from_file'})}, + "blame") . " | "; + print $cgi->a({-href => href(action=>"history", hash_base=>$parent, + file_name=>$diff{'from_file'})}, + "history"); print "
\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $commit = $revlist->[$i]; #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; @@ -1999,9 +2130,11 @@ sub git_shortlog_body { href(action=>"commit", hash=>$commit), $ref); print "\n" . "\n" . "\n"; } @@ -2021,7 +2154,7 @@ sub git_history_body { $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); print "
" . - $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()) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } print "
\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { next; @@ -2050,9 +2183,8 @@ sub git_history_body { href(action=>"commit", hash=>$commit), $ref); print "\n" . "
" . - $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype); + $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); if ($ftype eq 'blob') { my $blob_current = git_get_hash_by_path($hash_base, $file_name); @@ -2085,7 +2217,7 @@ sub git_tags_body { $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); print "\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; my %tag = %$entry; @@ -2145,7 +2277,7 @@ sub git_heads_body { $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); print "
\n"; - my $alternate = 0; + my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; my %tag = %$entry; @@ -2261,7 +2393,7 @@ sub git_project_list { } print "\n" . "\n"; - my $alternate = 0; + my $alternate = 1; foreach my $pr (@projects) { if ($alternate) { print "\n"; @@ -2293,7 +2425,7 @@ sub git_project_index { print $cgi->header( -type => 'text/plain', -charset => 'utf-8', - -content_disposition => qq(inline; filename="index.aux")); + -content_disposition => 'inline; filename="index.aux"'); foreach my $pr (@projects) { if (!exists $pr->{'owner'}) { @@ -2430,7 +2562,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", '--porcelain', '--', + $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2454,25 +2587,52 @@ sub git_blame2 {
HTML - while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; + my %metainfo = (); + while (1) { + $_ = <$fd>; + last unless defined $_; + my ($full_rev, $lineno, $orig_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 $lineno = $2; - my $data = $3; - - if (!defined $last_rev) { - $last_rev = $full_rev; - } 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 "\n"; - print "\n"; - print "\n"; + if ($group_size) { + print "\n"; + } + my $blamed = href(action => 'blame', + file_name => $meta->{'filename'}, + hash_base => $full_rev); + print ""; print "\n"; print "\n"; } @@ -2639,7 +2799,7 @@ sub git_blob_plain { print $cgi->header( -type => "$type", -expires=>$expires, - -content_disposition => "inline; filename=\"$save_as\""); + -content_disposition => 'inline; filename="' . "$save_as" . '"'); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -2723,17 +2883,16 @@ sub git_blob { } sub git_tree { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); + my $have_snapshot = gitweb_have_snapshot(); + if (!defined $hash_base) { + $hash_base = "HEAD"; + } if (!defined $hash) { - $hash = git_get_head_hash($project); if (defined $file_name) { - my $base = $hash_base || $hash; - $hash = git_get_hash_by_path($base, $file_name, "tree"); - } - if (!defined $hash_base) { - $hash_base = $hash; + $hash = git_get_hash_by_path($hash_base, $file_name, "tree"); + } else { + $hash = $hash_base; } } $/ = "\0"; @@ -2762,7 +2921,7 @@ sub git_tree { if ($have_snapshot) { # FIXME: Should be available when we have no hash base as well. push @views_nav, - $cgi->a({-href => href(action=>"snapshot")}, + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); @@ -2779,7 +2938,7 @@ sub git_tree { git_print_page_path($file_name, 'tree', $hash_base); print "
\n"; print "
CommitLineData
" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "" . - esc_html($lineno) . " 1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($rev)); + print ""; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "
\n"; - my $alternate = 0; + my $alternate = 1; foreach my $line (@entries) { my %t = parse_ls_tree_line($line, -z => 1); @@ -2800,7 +2959,6 @@ sub git_tree { } sub git_snapshot { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); my $have_snapshot = (defined $ctype && defined $suffix); if (!$have_snapshot) { @@ -2813,14 +2971,18 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; - print $cgi->header(-type => 'application/x-tar', - -content_encoding => $ctype, - -content_disposition => "inline; filename=\"$filename\"", - -status => '200 OK'); - - my $git_command = git_cmd_str(); - open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or - die_error(undef, "Execute git-tar-tree failed."); + print $cgi->header( + -type => 'application/x-tar', + -content_encoding => $ctype, + -content_disposition => 'inline; filename="' . "$filename" . '"', + -status => '200 OK'); + + my $git = git_cmd_str(); + 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."); binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi @@ -2871,7 +3033,7 @@ sub git_log { " | " . $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=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . "
\n" . "\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . @@ -2909,23 +3071,16 @@ sub git_commit { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); + my $have_snapshot = gitweb_have_snapshot(); my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { - my $parent = $co{'parent'}; push @views_nav, $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } - if (defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"), - $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log"); - } 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)); @@ -3126,7 +3281,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="${file_name}.patch")); + -content_disposition => 'inline; filename="' . "$file_name" . '.patch"'); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3146,8 +3301,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; - $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; print $line; @@ -3229,7 +3384,7 @@ sub git_commitdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="$filename")); + -content_disposition => 'inline; filename="' . "$filename" . '"'); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); print <\n"; - my $alternate = 0; + my $alternate = 1; if ($commit_search) { $/ = "\0"; open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; @@ -3568,7 +3723,7 @@ XML "\n"; } print "
\n"; @@ -3576,8 +3731,8 @@ XML if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); + my $file = esc_html(unquote($7)); + $file = to_utf8($file); print "$file
\n"; } print "]]>\n" .