X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/435037d8cf5dce5ee0f25d4ba5866925489f7045b310f29dc43c6f32523d672f..aa850baeea643106c0fc945a1ff1429cd7d408b779af22a186e13d1e522563bf:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index 6231f15..99ed3e8 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -20,6 +20,10 @@ use File::Basename qw(basename); use Time::HiRes qw(gettimeofday tv_interval); binmode STDOUT, ':utf8'; +if (!defined($CGI::VERSION) || $CGI::VERSION < 4.08) { + eval 'sub CGI::multi_param { CGI::param(@_) }' +} + our $t0 = [ gettimeofday() ]; our $number_of_git_cmds = 0; @@ -54,6 +58,11 @@ sub evaluate_uri { # to build the base URL ourselves: our $path_info = decode_utf8($ENV{"PATH_INFO"}); if ($path_info) { + # $path_info has already been URL-decoded by the web server, but + # $my_url and $my_uri have not. URL-decode them so we can properly + # strip $path_info. + $my_url = unescape($my_url); + $my_uri = unescape($my_uri); if ($my_url =~ s,\Q$path_info\E$,, && $my_uri =~ s,\Q$path_info\E$,, && defined $ENV{'SCRIPT_NAME'}) { @@ -80,6 +89,9 @@ our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; # string of the home link on top of all pages our $home_link_str = "++GITWEB_HOME_LINK_STR++"; +# extra breadcrumbs preceding the home link +our @extra_breadcrumbs = (); + # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" @@ -137,6 +149,9 @@ our $export_ok = "++GITWEB_EXPORT_OK++"; # don't generate age column on the projects list page our $omit_age_column = 0; +# don't generate information about owners of repositories +our $omit_owner=0; + # show repository only if this subroutine returns true # when given the path to the project, for example: # sub { return -e "$_[0]/git-daemon-export-ok"; } @@ -263,16 +278,15 @@ our %highlight_basename = ( our %highlight_ext = ( # main extensions, defining name of syntax; # see files in /usr/share/highlight/langDefs/ directory - map { $_ => $_ } - qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make), + (map { $_ => $_ } qw(py rb java css js tex bib xml awk bat ini spec tcl sql)), # alternate extensions, see /etc/highlight/filetypes.conf - 'h' => 'c', - map { $_ => 'sh' } qw(bash zsh ksh), - map { $_ => 'cpp' } qw(cxx c++ cc), - map { $_ => 'php' } qw(php3 php4 php5 phps), - map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' - map { $_ => 'make'} qw(mak mk), - map { $_ => 'xml' } qw(xhtml html htm), + (map { $_ => 'c' } qw(c h)), + (map { $_ => 'sh' } qw(sh bash zsh ksh)), + (map { $_ => 'cpp' } qw(cpp cxx c++ cc)), + (map { $_ => 'php' } qw(php php3 php4 php5 phps)), + (map { $_ => 'pl' } qw(pl perl pm)), # perhaps also 'cgi' + (map { $_ => 'make'} qw(make mak mk)), + (map { $_ => 'xml' } qw(xml xhtml html htm)), ); # You define site-wide feature defaults here; override them with @@ -534,11 +548,25 @@ our %feature = ( # $feature{'remote_heads'}{'default'} = [1]; # To have project specific config enable override in $GITWEB_CONFIG # $feature{'remote_heads'}{'override'} = 1; - # and in project config gitweb.remote_heads = 0|1; + # and in project config gitweb.remoteheads = 0|1; 'remote_heads' => { 'sub' => sub { feature_bool('remote_heads', @_) }, 'override' => 0, 'default' => [0]}, + + # Enable showing branches under other refs in addition to heads + + # To set system wide extra branch refs have in $GITWEB_CONFIG + # $feature{'extra-branch-refs'}{'default'} = ['dirs', 'of', 'choice']; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'extra-branch-refs'}{'override'} = 1; + # and in project config gitweb.extrabranchrefs = dirs of choice + # Every directory is separated with whitespace. + + 'extra-branch-refs' => { + 'sub' => \&feature_extra_branch_refs, + 'override' => 0, + 'default' => []}, ); sub gitweb_get_feature { @@ -617,6 +645,21 @@ sub feature_avatar { return @val ? @val : @_; } +sub feature_extra_branch_refs { + my (@branch_refs) = @_; + my $values = git_get_project_config('extrabranchrefs'); + + if ($values) { + $values = config_to_multi ($values); + @branch_refs = (); + foreach my $value (@{$values}) { + push @branch_refs, split /\s+/, $value; + } + } + + return @branch_refs; +} + # 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. @@ -647,6 +690,18 @@ sub filter_snapshot_fmts { !$known_snapshot_formats{$_}{'disabled'}} @fmts; } +sub filter_and_validate_refs { + my @refs = @_; + my %unique_refs = (); + + foreach my $ref (@refs) { + die_error(500, "Invalid ref '$ref' in 'extra-branch-refs' feature") unless (is_valid_ref_format($ref)); + # 'heads' are added implicitly in get_branch_refs(). + $unique_refs{$ref} = 1 if ($ref ne 'heads'); + } + return sort keys %unique_refs; +} + # If it is set to code reference, it is code that it is to be run once per # request, allowing updating configurations that change with each request, # while running other code in config file only once. @@ -677,7 +732,7 @@ sub evaluate_gitweb_config { our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++"; - # Protect agains duplications of file names, to not read config twice. + # Protect against duplications of file names, to not read config twice. # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so # there possibility of duplication of filename there doesn't matter. $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON); @@ -821,7 +876,7 @@ sub evaluate_query_params { while (my ($name, $symbol) = each %cgi_param_mapping) { if ($symbol eq 'opt') { - $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ]; + $input_params{$name} = [ map { decode_utf8($_) } $cgi->multi_param($symbol) ]; } else { $input_params{$name} = decode_utf8($cgi->param($symbol)); } @@ -985,7 +1040,7 @@ our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_bas sub evaluate_and_validate_params { our $action = $input_params{'action'}; if (defined $action) { - if (!validate_action($action)) { + if (!is_valid_action($action)) { die_error(400, "Invalid action parameter"); } } @@ -993,7 +1048,7 @@ sub evaluate_and_validate_params { # parameters which are pathnames our $project = $input_params{'project'}; if (defined $project) { - if (!validate_project($project)) { + if (!is_valid_project($project)) { undef $project; die_error(404, "No such project"); } @@ -1001,21 +1056,21 @@ sub evaluate_and_validate_params { our $project_filter = $input_params{'project_filter'}; if (defined $project_filter) { - if (!validate_pathname($project_filter)) { + if (!is_valid_pathname($project_filter)) { die_error(404, "Invalid project_filter parameter"); } } our $file_name = $input_params{'file_name'}; if (defined $file_name) { - if (!validate_pathname($file_name)) { + if (!is_valid_pathname($file_name)) { die_error(400, "Invalid file parameter"); } } our $file_parent = $input_params{'file_parent'}; if (defined $file_parent) { - if (!validate_pathname($file_parent)) { + if (!is_valid_pathname($file_parent)) { die_error(400, "Invalid file parent parameter"); } } @@ -1023,21 +1078,21 @@ sub evaluate_and_validate_params { # parameters which are refnames our $hash = $input_params{'hash'}; if (defined $hash) { - if (!validate_refname($hash)) { + if (!is_valid_refname($hash)) { die_error(400, "Invalid hash parameter"); } } our $hash_parent = $input_params{'hash_parent'}; if (defined $hash_parent) { - if (!validate_refname($hash_parent)) { + if (!is_valid_refname($hash_parent)) { die_error(400, "Invalid hash parent parameter"); } } our $hash_base = $input_params{'hash_base'}; if (defined $hash_base) { - if (!validate_refname($hash_base)) { + if (!is_valid_refname($hash_base)) { die_error(400, "Invalid hash base parameter"); } } @@ -1057,7 +1112,7 @@ sub evaluate_and_validate_params { our $hash_parent_base = $input_params{'hash_parent_base'}; if (defined $hash_parent_base) { - if (!validate_refname($hash_parent_base)) { + if (!is_valid_refname($hash_parent_base)) { die_error(400, "Invalid hash parent base parameter"); } } @@ -1080,7 +1135,7 @@ sub evaluate_and_validate_params { our $search_use_regexp = $input_params{'search_use_regexp'}; our $searchtext = $input_params{'searchtext'}; - our $search_regexp; + our $search_regexp = undef; if (defined $searchtext) { if (length($searchtext) < 2) { die_error(403, "At least two characters are required for search parameter"); @@ -1104,7 +1159,7 @@ sub evaluate_git_dir { our $git_dir = "$projectroot/$project" if $project; } -our (@snapshot_fmts, $git_avatar); +our (@snapshot_fmts, $git_avatar, @extra_branch_refs); sub configure_gitweb_features { # list of supported snapshot formats our @snapshot_fmts = gitweb_get_feature('snapshot'); @@ -1122,6 +1177,13 @@ sub configure_gitweb_features { } else { $git_avatar = ''; } + + our @extra_branch_refs = gitweb_get_feature('extra-branch-refs'); + @extra_branch_refs = filter_and_validate_refs (@extra_branch_refs); +} + +sub get_branch_refs { + return ('heads', @extra_branch_refs); } # custom error handler: 'die ' is Internal Server Error @@ -1130,7 +1192,7 @@ sub handle_errors_html { # to avoid infinite loop where error occurs in die_error, # change handler to default handler, disabling handle_errors_html - set_message("Error occured when inside die_error:\n$msg"); + set_message("Error occurred when inside die_error:\n$msg"); # you cannot jump out of die_error when called as error handler; # the subroutine set via CGI::Carp::set_message is called _after_ @@ -1409,28 +1471,31 @@ sub href { ## ====================================================================== ## validation, quoting/unquoting and escaping -sub validate_action { - my $input = shift || return undef; +sub is_valid_action { + my $input = shift; return undef unless exists $actions{$input}; - return $input; + return 1; } -sub validate_project { - my $input = shift || return undef; - if (!validate_pathname($input) || +sub is_valid_project { + my $input = shift; + + return unless defined $input; + if (!is_valid_pathname($input) || !(-d "$projectroot/$input") || !check_export_ok("$projectroot/$input") || ($strict_export && !project_in_list($input))) { return undef; } else { - return $input; + return 1; } } -sub validate_pathname { - my $input = shift || return undef; +sub is_valid_pathname { + my $input = shift; - # no '.' or '..' as elements of path, i.e. no '.' nor '..' + return undef unless defined $input; + # no '.' or '..' as elements of path, i.e. no '.' or '..' # at the beginning, at the end, and between slashes. # also this catches doubled slashes if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) { @@ -1440,24 +1505,33 @@ sub validate_pathname { if ($input =~ m!\0!) { return undef; } - return $input; + return 1; } -sub validate_refname { - my $input = shift || return undef; +sub is_valid_ref_format { + my $input = shift; - # 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; + return undef unless defined $input; # restrictions on ref name according to git-check-ref-format if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) { return undef; } - return $input; + return 1; +} + +sub is_valid_refname { + my $input = shift; + + return undef unless defined $input; + # textual hashes are O.K. + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return 1; + } + # it must be correct pathname + is_valid_pathname($input) or return undef; + # check git-check-ref-format restrictions + is_valid_ref_format($input) or return undef; + return 1; } # decode sequences of octets in utf8 into Perl's internal form, @@ -1543,14 +1617,14 @@ sub esc_path { return $str; } -# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0) +# Sanitize for use in XHTML + application/xml+xhtml (valid XML 1.0) sub sanitize { my $str = shift; return undef unless defined $str; $str = to_utf8($str); - $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg; + $str =~ s|([[:cntrl:]])|(index("\t\n\r", $1) != -1 ? $1 : quot_cec($1))|eg; return $str; } @@ -1963,7 +2037,7 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - $line =~ s{\b([0-9a-fA-F]{8,40})\b}{ + $line =~ s{\b([0-9a-fA-F]{7,40})\b}{ $cgi->a({-href => href(action=>"object", hash=>$1), -class => "text"}, $1); }eg; @@ -2017,7 +2091,7 @@ sub format_ref_marker { -href => href( action=>$dest_action, hash=>$dest - )}, $name); + )}, esc_html($name)); $markers .= " " . $link . ""; @@ -2062,7 +2136,7 @@ sub picon_url { if (!$avatar_cache{$email}) { my ($user, $domain) = split('@', $email); $avatar_cache{$email} = - "http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" . + "//www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" . "$domain/$user/" . "users+domains+unknown/up/single"; } @@ -2077,7 +2151,7 @@ sub gravatar_url { my $email = lc shift; my $size = shift; $avatar_cache{$email} ||= - "http://www.gravatar.com/avatar/" . + "//www.gravatar.com/avatar/" . Digest::MD5::md5_hex($email) . "?s="; return $avatar_cache{$email} . $size; } @@ -2506,6 +2580,7 @@ sub format_snapshot_links { sub get_feed_info { my $format = shift || 'Atom'; my %res = (action => lc($format)); + my $matched_ref = 0; # feed links are possible only for project views return unless (defined $project); @@ -2513,12 +2588,17 @@ sub get_feed_info { # or don't have specific feed yet (so they should use generic) return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x); - my $branch; - # branches refs uses 'refs/heads/' prefix (fullname) to differentiate - # from tag links; this also makes possible to detect branch links - if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) || - (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) { - $branch = $1; + my $branch = undef; + # branches refs uses 'refs/' + $get_branch_refs()[x] + '/' prefix + # (fullname) to differentiate from tag links; this also makes + # possible to detect branch links + for my $ref (get_branch_refs()) { + if ((defined $hash_base && $hash_base =~ m!^refs/\Q$ref\E/(.*)$!) || + (defined $hash && $hash =~ m!^refs/\Q$ref\E/(.*)$!)) { + $branch = $1; + $matched_ref = $ref; + last; + } } # find log type for feed description (title) my $type = 'log'; @@ -2531,7 +2611,7 @@ sub get_feed_info { } $res{-title} = $type; - $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef); + $res{'hash'} = (defined $branch ? "refs/$matched_ref/$branch" : undef); $res{'file_name'} = $file_name; return %res; @@ -2690,12 +2770,15 @@ sub git_get_project_config { # only subsection, if exists, is case sensitive, # and not lowercased by 'git config -z -l' if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) { + $lo =~ s/_//g; $key = join(".", lc($hi), $mi, lc($lo)); + return if ($lo =~ /\W/ || $hi =~ /\W/); } else { $key = lc($key); + $key =~ s/_//g; + return if ($key =~ /\W/); } $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); # type sanity check if (defined $type) { @@ -3181,7 +3264,7 @@ sub git_get_last_activity { '--format=%(committer)', '--sort=-committerdate', '--count=1', - 'refs/heads') or return; + map { "refs/$_" } get_branch_refs ()) or return; my $most_recent = <$fd>; close $fd or return; if (defined $most_recent && @@ -3632,7 +3715,7 @@ sub parse_from_to_diffinfo { sub git_get_heads_list { my ($limit, @classes) = @_; - @classes = ('heads') unless @classes; + @classes = get_branch_refs() unless @classes; my @patterns = map { "refs/$_" } @classes; my @headslist; @@ -3650,9 +3733,16 @@ sub git_get_heads_list { my ($committer, $epoch, $tz) = ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); $ref_item{'fullname'} = $name; - $name =~ s!^refs/(?:head|remote)s/!!; + my $strip_refs = join '|', map { quotemeta } get_branch_refs(); + $name =~ s!^refs/($strip_refs|remotes)/!!; + $ref_item{'name'} = $name; + # for refs neither in 'heads' nor 'remotes' we want to + # show their ref dir + my $ref_dir = (defined $1) ? $1 : ''; + if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') { + $ref_item{'name'} .= ' (' . $ref_dir . ')'; + } - $ref_item{'name'} = $name; $ref_item{'id'} = $hash; $ref_item{'title'} = $title || '(no commit message)'; $ref_item{'epoch'} = $epoch; @@ -3824,7 +3914,7 @@ sub blob_contenttype { # guess file syntax for syntax highlighting; return undef if no highlighting # the name of syntax can (in the future) depend on syntax highlighter used sub guess_file_syntax { - my ($highlight, $mimetype, $file_name) = @_; + my ($highlight, $file_name) = @_; return undef unless ($highlight && defined $file_name); my $basename = basename($file_name, '.in'); return $highlight_basename{$basename} @@ -3842,12 +3932,16 @@ sub guess_file_syntax { # or return original FD if no highlighting sub run_highlighter { my ($fd, $highlight, $syntax) = @_; - return $fd unless ($highlight && defined $syntax); + return $fd unless ($highlight); close $fd; + my $syntax_arg = (defined $syntax) ? "--syntax $syntax" : "--force"; open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ". + quote_command($^X, '-CO', '-MEncode=decode,FB_DEFAULT', '-pse', + '$_ = decode($fe, $_, FB_DEFAULT) if !utf8::decode($_);', + '--', "-fe=$fallback_encoding")." | ". quote_command($highlight_bin). - " --replace-tabs=8 --fragment --syntax $syntax |" + " --replace-tabs=8 --fragment $syntax_arg |" or die_error(500, "Couldn't open file or run syntax highlighter"); return $fd; } @@ -3973,7 +4067,9 @@ sub print_nav_breadcrumbs_path { sub print_nav_breadcrumbs { my %opts = @_; - print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; + for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) { + print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / "; + } if (defined $project) { my @dirname = split '/', $project; my $projectbasename = pop @dirname; @@ -4013,7 +4109,7 @@ sub print_search_form { if ($use_pathinfo) { $action .= "/".esc_url($project); } - print $cgi->startform(-method => "get", -action => $action) . + print $cgi->start_form(-method => "get", -action => $action) . "
\n" . (!$use_pathinfo && $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") . @@ -4021,8 +4117,8 @@ sub print_search_form { $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" . $cgi->popup_menu(-name => 'st', -default => 'commit', -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) . - $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . - " search:\n", + " " . $cgi->a({-href => href(action=>"search_help"), + -title => "search help" }, "?") . " search:\n", $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" . "" . $cgi->checkbox(-name => 'sr', -value => 1, -label => 're', @@ -4482,30 +4578,33 @@ sub git_print_log { } # print log - my $signoff = 0; - my $empty = 0; + my $skip_blank_line = 0; foreach my $line (@$log) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - $signoff = 1; - $empty = 0; + if ($line =~ m/^\s*([A-Z][-A-Za-z]*-[Bb]y|C[Cc]): /) { if (! $opts{'-remove_signoff'}) { print "" . esc_html($line) . "
\n"; - next; - } else { - # remove signoff lines - next; + $skip_blank_line = 1; } - } else { - $signoff = 0; + next; + } + + if ($line =~ m,\s*([a-z]*link): (https?://\S+),i) { + if (! $opts{'-remove_signoff'}) { + print "" . esc_html($1) . ": " . + "" . esc_html($2) . "" . + "
\n"; + $skip_blank_line = 1; + } + next; } # print only one empty line # do not print empty line after signoff if ($line eq "") { - next if ($empty || $signoff); - $empty = 1; + next if ($skip_blank_line); + $skip_blank_line = 1; } else { - $empty = 0; + $skip_blank_line = 0; } print format_log_line_html($line) . "
\n"; @@ -4513,7 +4612,7 @@ sub git_print_log { if ($opts{'-final_empty_line'}) { # end with single empty line - print "
\n" unless $empty; + print "
\n" unless $skip_blank_line; } } @@ -5420,7 +5519,7 @@ sub git_project_search_form { } print "
\n"; - print $cgi->startform(-method => 'get', -action => $my_uri) . + print $cgi->start_form(-method => 'get', -action => $my_uri) . $cgi->hidden(-name => 'a', -value => 'project_list') . "\n"; print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n" if (defined $project_filter); @@ -5516,23 +5615,30 @@ sub fill_project_list_info { sub sort_projects_list { my ($projlist, $order) = @_; - my @projects; - my %order_info = ( - project => { key => 'path', type => 'str' }, - descr => { key => 'descr_long', type => 'str' }, - owner => { key => 'owner', type => 'str' }, - age => { key => 'age', type => 'num' } - ); - my $oi = $order_info{$order}; - return @$projlist unless defined $oi; - if ($oi->{'type'} eq 'str') { - @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist; - } else { - @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist; + sub order_str { + my $key = shift; + return sub { $a->{$key} cmp $b->{$key} }; } - return @projects; + sub order_num_then_undef { + my $key = shift; + return sub { + defined $a->{$key} ? + (defined $b->{$key} ? $a->{$key} <=> $b->{$key} : -1) : + (defined $b->{$key} ? 1 : 0) + }; + } + + my %orderings = ( + project => order_str('path'), + descr => order_str('descr_long'), + owner => order_str('owner'), + age => order_num_then_undef('age'), + ); + + my $ordering = $orderings{$order}; + return defined $ordering ? sort $ordering @$projlist : @$projlist; } # returns a hash of categories, containing the list of project @@ -5616,8 +5722,10 @@ sub git_project_list_rows { ? esc_html_match_hl_chopped($pr->{'descr_long'}, $pr->{'descr'}, $search_regexp) : esc_html($pr->{'descr'})) . - "\n" . - "" . chop_and_escape_str($pr->{'owner'}, 15) . "\n"; + "\n"; + unless ($omit_owner) { + print "" . chop_and_escape_str($pr->{'owner'}, 15) . "\n"; + } unless ($omit_age_column) { print "{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "\n"; @@ -5653,7 +5761,9 @@ sub git_project_list_body { 'tagfilter' => $tagfilter) if ($tagfilter || $search_regexp); # fill the rest - my @all_fields = $omit_age_column ? ('descr', 'descr_long', 'owner', 'ctags', 'category') : (); + my @all_fields = ('descr', 'descr_long', 'ctags', 'category'); + push @all_fields, ('age', 'age_string') unless($omit_age_column); + push @all_fields, 'owner' unless($omit_owner); @projects = fill_project_list_info(\@projects, @all_fields); $order ||= $default_projects_order; @@ -5685,7 +5795,7 @@ sub git_project_list_body { } print_sort_th('project', $order, 'Project'); print_sort_th('descr', $order, 'Description'); - print_sort_th('owner', $order, 'Owner'); + print_sort_th('owner', $order, 'Owner') unless $omit_owner; print_sort_th('age', $order, 'Last Change') unless $omit_age_column; print "\n" . # for links "\n"; @@ -6439,8 +6549,10 @@ sub git_summary { print "
 
\n"; print "\n" . - "\n" . - "\n"; + "\n"; + if ($owner and not $omit_owner) { + print "\n"; + } if (defined $cd{'rfc2822'}) { print "" . "\n"; @@ -6601,6 +6713,7 @@ sub git_blame_common { $hash_base, '--', $file_name or die_error(500, "Open git-blame --porcelain failed"); } + binmode $fd, ':utf8'; # incremental blame data returns early if ($format eq 'data') { @@ -6951,9 +7064,8 @@ sub git_blob { $have_blame &&= ($mimetype =~ m!^text/!); my $highlight = gitweb_check_feature('highlight'); - my $syntax = guess_file_syntax($highlight, $mimetype, $file_name); - $fd = run_highlighter($fd, $highlight, $syntax) - if $syntax; + my $syntax = guess_file_syntax($highlight, $file_name); + $fd = run_highlighter($fd, $highlight, $syntax); git_header_html(undef, $expires); my $formats_nav = ''; @@ -6990,7 +7102,7 @@ sub git_blob { git_print_page_path($file_name, "blob", $hash_base); print "
\n"; if ($mimetype =~ m!^image/!) { - print qq!!.esc_attr($file_name).qq!%4i %s
\n!, $nr, esc_attr(href(-replay => 1)), $nr, $nr, - $syntax ? sanitize($line) : esc_html($line, -nbsp=>1); + $highlight ? sanitize($line) : esc_html($line, -nbsp=>1); } } close $fd @@ -7125,6 +7237,15 @@ sub git_tree { git_footer_html(); } +sub sanitize_for_filename { + my $name = shift; + + $name =~ s!/!-!g; + $name =~ s/[^[:alnum:]_.-]//g; + + return $name; +} + sub snapshot_name { my ($project, $hash) = @_; @@ -7132,9 +7253,7 @@ sub snapshot_name { # path/to/project/.git -> project my $name = to_utf8($project); $name =~ s,([^/])/*\.git$,$1,; - $name = basename($name); - # sanitize name - $name =~ s/[[:cntrl:]]/?/g; + $name = sanitize_for_filename(basename($name)); my $ver = $hash; if ($hash =~ /^[0-9a-fA-F]+$/) { @@ -7148,13 +7267,25 @@ sub snapshot_name { $ver = $1; } else { # branches and other need shortened SHA-1 hash - if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) { - $ver = $1; + my $strip_refs = join '|', map { quotemeta } get_branch_refs(); + if ($hash =~ m!^refs/($strip_refs|remotes)/(.*)$!) { + my $ref_dir = (defined $1) ? $1 : ''; + $ver = $2; + + $ref_dir = sanitize_for_filename($ref_dir); + # for refs neither in heads nor remotes we want to + # add a ref dir to archive name + if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') { + $ver = $ref_dir . '-' . $ver; + } } $ver .= '-' . git_get_short_hash($project, $hash); } + # special case of sanitization for filename - we change + # slashes to dots instead of dashes # in case of hierarchical branch names $ver =~ s!/!.!g; + $ver =~ s/[^[:alnum:]_.-]//g; # name = project-version_string $name = "$name-$ver"; @@ -7449,7 +7580,7 @@ sub git_object { git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null' or die_error(404, "Object does not exist"); $type = <$fd>; - chomp $type; + defined $type && chomp $type; close $fd or die_error(404, "Object does not exist"); @@ -7460,7 +7591,7 @@ sub git_object { system(git_cmd(), "cat-file", '-e', $hash_base) == 0 or die_error(404, "Base object does not exist"); - # here errors should not hapen + # here errors should not happen open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name or die_error(500, "Open git-ls-tree failed"); my $line = <$fd>; @@ -8012,7 +8143,7 @@ sub git_feed { %latest_commit = %{$commitlist[0]}; my $latest_epoch = $latest_commit{'committer_epoch'}; exit_if_unmodified_since($latest_epoch); - %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'}); + %latest_date = parse_date($latest_epoch, $latest_commit{'committer_tz'}); } print $cgi->header( -type => $content_type, @@ -8039,6 +8170,7 @@ sub git_feed { $feed_type = 'history'; } $title .= " $feed_type"; + $title = esc_html($title); my $descr = git_get_project_description($project); if (defined $descr) { $descr = esc_html($descr);
description" . esc_html($descr) . "
owner" . esc_html($owner) . "
description" . esc_html($descr) . "
owner" . esc_html($owner) . "
last change".format_timestamp_html(\%cd)."