X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/4dbebc2534076e2c5c799c342a22c0525f935f5447f9e751f020c1362c9b20cf..62ca5cd2228519bda52b6911737014c8d0e85f6447dd318cc114569fdd9c0295:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index 9c5dfc5..873f8d0 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -35,6 +35,10 @@ our $GIT = "++GIT_BINDIR++/git"; #our $projectroot = "/pub/scm"; our $projectroot = "++GITWEB_PROJECTROOT++"; +# fs traversing limit for getting project list +# the number is relative to the projectroot +our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; + # target of the home link on top of all pages our $home_link = $my_uri || "/"; @@ -308,10 +312,6 @@ sub feature_snapshot { if ($val) { @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val); - @fmts = grep { defined } map { - exists $known_snapshot_format_aliases{$_} ? - $known_snapshot_format_aliases{$_} : $_ } @fmts; - @fmts = grep(exists $known_snapshot_formats{$_}, @fmts); } return @fmts; @@ -357,6 +357,18 @@ sub check_export_ok { (!$export_ok || -e "$dir/$export_ok")); } +# process alternate names for backward compatibility +# filter out unsupported (unknown) snapshot formats +sub filter_snapshot_fmts { + my @fmts = @_; + + @fmts = map { + exists $known_snapshot_format_aliases{$_} ? + $known_snapshot_format_aliases{$_} : $_} @fmts; + @fmts = grep(exists $known_snapshot_formats{$_}, @fmts); + +} + our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; do $GITWEB_CONFIG if -e $GITWEB_CONFIG; @@ -429,12 +441,11 @@ my %allowed_options = ( our @extra_options = $cgi->param('opt'); if (defined @extra_options) { - foreach(@extra_options) - { - if (not grep(/^$_$/, keys %allowed_options)) { + foreach my $opt (@extra_options) { + if (not exists $allowed_options{$opt}) { die_error(undef, "Invalid option parameter"); } - if (not grep(/^$action$/, @{$allowed_options{$_}})) { + if (not grep(/^$action$/, @{$allowed_options{$opt}})) { die_error(undef, "Invalid option parameter for this action"); } } @@ -465,9 +476,6 @@ if (defined $searchtype) { our $searchtext = $cgi->param('s'); our $search_regexp; if (defined $searchtext) { - if ($searchtype ne 'grep' and $searchtype ne 'pickaxe' and $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"); } @@ -591,7 +599,6 @@ sub href(%) { action => "a", file_name => "f", file_parent => "fp", - extra_options => "opt", hash => "h", hash_parent => "hp", hash_base => "hb", @@ -601,6 +608,7 @@ sub href(%) { searchtext => "s", searchtype => "st", snapshot_format => "sf", + extra_options => "opt", ); my %mapping = @mapping; @@ -623,7 +631,13 @@ sub href(%) { for (my $i = 0; $i < @mapping; $i += 2) { my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); if (defined $params{$name}) { - push @result, $symbol . "=" . esc_param($params{$name}); + if (ref($params{$name}) eq "ARRAY") { + foreach my $par (@{$params{$name}}) { + push @result, $symbol . "=" . esc_param($par); + } + } else { + push @result, $symbol . "=" . esc_param($params{$name}); + } } } $href .= "?" . join(';', @result) if scalar @result; @@ -833,6 +847,23 @@ sub chop_str { return "$body$tail"; } +# takes the same arguments as chop_str, but also wraps a around the +# result with a title attribute if it does get chopped. Additionally, the +# string is HTML-escaped. +sub chop_and_escape_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + my $chopped = chop_str($str, $len, $add_len); + if ($chopped eq $str) { + return esc_html($chopped); + } else { + return qq{} . + esc_html($chopped) . qq{}; + } +} + ## ---------------------------------------------------------------------- ## functions returning short strings @@ -883,11 +914,25 @@ sub age_string { return $age_str; } +use constant { + S_IFINVALID => 0030000, + S_IFGITLINK => 0160000, +}; + +# submodule/subproject, a commit object reference +sub S_ISGITLINK($) { + my $mode = shift; + + return (($mode & S_IFMT) == S_IFGITLINK) +} + # convert file mode in octal to symbolic file mode string sub mode_str { my $mode = oct shift; - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return 'm---------'; + } elsif (S_ISDIR($mode & S_IFMT)) { return 'drwxr-xr-x'; } elsif (S_ISLNK($mode)) { return 'lrwxrwxrwx'; @@ -913,7 +958,9 @@ sub file_type { $mode = oct $mode; } - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return "submodule"; + } elsif (S_ISDIR($mode & S_IFMT)) { return "directory"; } elsif (S_ISLNK($mode)) { return "symlink"; @@ -934,7 +981,9 @@ sub file_type_long { $mode = oct $mode; } - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return "submodule"; + } elsif (S_ISDIR($mode & S_IFMT)) { return "directory"; } elsif (S_ISLNK($mode)) { return "symlink"; @@ -1300,9 +1349,11 @@ sub format_diff_line { sub format_snapshot_links { my ($hash) = @_; my @snapshot_fmts = gitweb_check_feature('snapshot'); + @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); my $num_fmts = @snapshot_fmts; if ($num_fmts > 1) { # A parenthesized list of links bearing format names. + # e.g. "snapshot (_tar.gz_ _zip_)" return "snapshot (" . join(' ', map $cgi->a({ -href => href( @@ -1314,8 +1365,10 @@ sub format_snapshot_links { , @snapshot_fmts) . ")"; } elsif ($num_fmts == 1) { # A single "snapshot" link whose tooltip bears the format name. + # i.e. "_snapshot_" my ($fmt) = @snapshot_fmts; - return $cgi->a({ + return + $cgi->a({ -href => href( action=>"snapshot", hash=>$hash, @@ -1478,15 +1531,22 @@ sub git_get_projects_list { # remove the trailing "/" $dir =~ s!/+$!!; my $pfxlen = length("$dir"); + my $pfxdepth = ($dir =~ tr!/!!); File::Find::find({ follow_fast => 1, # follow symbolic links + follow_skip => 2, # ignore duplicates dangling_symlinks => 0, # ignore dangling symlinks, silently wanted => sub { # skip project-list toplevel, if we get it. return if (m!^[/.]$!); # only directories can be git repositories return unless (-d $_); + # don't traverse too deep (Find is super slow on os x) + if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { + $File::Find::prune = 1; + return; + } my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot @@ -2256,9 +2316,17 @@ EOF printf(''."\n", esc_param($project), href(action=>"rss")); + printf(''."\n", + esc_param($project), href(action=>"rss", + extra_options=>"--no-merges")); printf(''."\n", esc_param($project), href(action=>"atom")); + printf(''."\n", + esc_param($project), href(action=>"atom", + extra_options=>"--no-merges")); } else { printf(''."\n", @@ -2696,6 +2764,20 @@ sub git_print_tree_entry { "history"); } print "\n"; + } else { + # unknown object: we can only present history for it + # (this includes 'commit' object, i.e. submodule support) + print "" . + esc_path($t->{'name'}) . + "\n"; + print ""; + if (defined $hash_base) { + print $cgi->a({-href => href(action=>"history", + hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print "\n"; } } @@ -2755,7 +2837,7 @@ sub git_difftree_body { "diff_tree\">\n"; # header only for combined diff in 'commitdiff' view - my $has_header = @parents > 1 && $action eq 'commitdiff'; + my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff'; if ($has_header) { # table header print "\n" . @@ -3137,44 +3219,10 @@ sub git_patchset_body { } } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || $patch_idx > $#$difftree); + # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); - if ($diffinfo->{'nparents'}) { - # combined diff - $from{'file'} = []; - $from{'href'} = []; - fill_from_file_info($diffinfo, @hash_parents) - unless exists $diffinfo->{'from_file'}; - for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { - $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'}; - if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file - $from{'href'}[$i] = href(action=>"blob", - hash_base=>$hash_parents[$i], - hash=>$diffinfo->{'from_id'}[$i], - file_name=>$from{'file'}[$i]); - } else { - $from{'href'}[$i] = undef; - } - } - } else { - $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; - if ($diffinfo->{'status'} ne "A") { # not new (added) file - $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, - file_name=>$from{'file'}); - } else { - delete $from{'href'}; - } - } - $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; - if (!is_deleted($diffinfo)) { # file exists in result - $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 print "
\n"; @@ -3365,7 +3413,7 @@ sub git_project_list_body { "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list", -title => $pr->{'descr_long'}}, esc_html($pr->{'descr'})) . "\n" . - "" . chop_str($pr->{'owner'}, 15) . "\n"; + "" . esc_html(chop_str($pr->{'owner'}, 15)) . "\n"; print "{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "\n" . "" . @@ -3407,9 +3455,10 @@ sub git_shortlog_body { print "\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 10); # git_summary() used print "$co{'age_string'}\n" . print "$co{'age_string_date'}\n" . - "" . esc_html(chop_str($co{'author_name'}, 10)) . "\n" . + "" . $author . "\n" . ""; print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); @@ -3457,9 +3506,10 @@ sub git_history_body { print "\n"; } $alternate ^= 1; + # shortlog uses chop_str($co{'author_name'}, 10) + my $author = chop_and_escape_str($co{'author_name'}, 15, 3); print "$co{'age_string_date'}\n" . - # shortlog uses chop_str($co{'author_name'}, 10) - "" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "\n" . + "" . $author . "\n" . ""; # originally git_history used chop_str($co{'title'}, 50) print format_subject_html($co{'title'}, $co{'title_short'}, @@ -3613,8 +3663,9 @@ sub git_search_grep_body { print "\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "$co{'age_string_date'}\n" . - "" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "\n" . + "" . $author . "\n" . "" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, esc_html(chop_str($co{'title'}, 50)) . "
"); @@ -3741,7 +3792,7 @@ sub git_summary { print "
 
\n"; print "\n" . "\n" . - "\n"; + "\n"; if (defined $cd{'rfc2822'}) { print "\n"; } @@ -4303,11 +4354,19 @@ sub git_tree { sub git_snapshot { my @supported_fmts = gitweb_check_feature('snapshot'); + @supported_fmts = filter_snapshot_fmts(@supported_fmts); my $format = $cgi->param('sf'); - unless ($format =~ m/[a-z0-9]+/ - && exists($known_snapshot_formats{$format}) - && grep($_ eq $format, @supported_fmts)) { + if (!@supported_fmts) { + die_error('403 Permission denied', "Permission denied"); + } + # default to first supported snapshot format + $format ||= $supported_fmts[0]; + if ($format !~ m/^[a-z0-9]+$/) { + die_error(undef, "Invalid snapshot format parameter"); + } elsif (!exists($known_snapshot_formats{$format})) { + die_error(undef, "Unknown snapshot format"); + } elsif (!grep($_ eq $format, @supported_fmts)) { die_error(undef, "Unsupported snapshot format"); } @@ -4324,7 +4383,7 @@ sub git_snapshot { my $cmd; $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; $cmd = "$git_command archive " . - "--format=$known_snapshot_formats{$format}{'format'}" . + "--format=$known_snapshot_formats{$format}{'format'} " . "--prefix=\'$name\'/ $hash"; if (exists $known_snapshot_formats{$format}{'compressor'}) { $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}}; @@ -5119,8 +5178,9 @@ sub git_search { print "\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "\n" . - "\n" . + "\n" . "
description" . esc_html($descr) . "
owner$owner
owner" . esc_html($owner) . "
last change$cd{'rfc2822'}
$co{'age_string_date'}" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "" . $author . "" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, @@ -5301,7 +5361,7 @@ sub git_feed { # log/feed of current (HEAD) branch, log of given branch, history of file/directory my $head = $hash || 'HEAD'; - my @commitlist = parse_commits($head, 150); + my @commitlist = parse_commits($head, 150, 0, undef, $file_name); my %latest_commit; my %latest_date;