X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/95ffa50f893aed551b698a71db6cd403019dba0f1c49d2e9699d98e2995a0fea..add99f03690b38bbc54ed03d13c18bb3d2396bc3a5ecf0dcb642ebe6ff909dcd:/gitweb.perl
diff --git a/gitweb.perl b/gitweb.perl
index cf34c5b..09a9951 100755
--- a/gitweb.perl
+++ b/gitweb.perl
@@ -85,6 +85,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
our $site_name = "++GITWEB_SITENAME++"
|| ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
+# html snippet to include in the
section of each page
+our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
# filename of html text to include at top of each page
our $site_header = "++GITWEB_SITE_HEADER++";
# html text to include at home page
@@ -322,6 +324,10 @@ our %feature = (
# Enable text search, which will list the commits which match author,
# committer or commit text to a given string. Enabled by default.
# Project specific override is not supported.
+ #
+ # Note that this controls all search features, which means that if
+ # it is disabled, then 'grep' and 'pickaxe' search would also be
+ # disabled.
'search' => {
'override' => 0,
'default' => [1]},
@@ -329,6 +335,7 @@ our %feature = (
# Enable grep search, which will list the files in currently selected
# tree containing the given string. Enabled by default. This can be
# potentially CPU-intensive, of course.
+ # Note that you need to have 'search' feature enabled too.
# To enable system wide have in $GITWEB_CONFIG
# $feature{'grep'}{'default'} = [1];
@@ -343,6 +350,7 @@ our %feature = (
# 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.
+ # Note that you need to have 'search' feature enabled too.
# To enable system wide have in $GITWEB_CONFIG
# $feature{'pickaxe'}{'default'} = [1];
@@ -492,6 +500,18 @@ our %feature = (
'override' => 0,
'default' => [0]},
+ # Enable and configure ability to change common timezone for dates
+ # in gitweb output via JavaScript. Enabled by default.
+ # Project specific override is not supported.
+ 'javascript-timezone' => {
+ 'override' => 0,
+ 'default' => [
+ 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
+ # or undef to turn off this feature
+ 'gitweb_tz', # name of cookie where to store selected timezone
+ 'datetime', # CSS class used to mark up dates for manipulation
+ ]},
+
# Syntax highlighting support. This is based on Daniel Svensson's
# and Sham Chukoury's work in gitweb-xmms2.git.
# It requires the 'highlight' program present in $PATH,
@@ -632,18 +652,42 @@ sub filter_snapshot_fmts {
# if it is true then gitweb config would be run for each request.
our $per_request_config = 1;
-our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
-sub evaluate_gitweb_config {
- our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
- our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
+# read and parse gitweb config file given by its parameter.
+# returns true on success, false on recoverable error, allowing
+# to chain this subroutine, using first file that exists.
+# dies on errors during parsing config file, as it is unrecoverable.
+sub read_config_file {
+ my $filename = shift;
+ return unless defined $filename;
# die if there are errors parsing config file
- if (-e $GITWEB_CONFIG) {
- do $GITWEB_CONFIG;
- die $@ if $@;
- } elsif (-e $GITWEB_CONFIG_SYSTEM) {
- do $GITWEB_CONFIG_SYSTEM;
+ if (-e $filename) {
+ do $filename;
die $@ if $@;
+ return 1;
}
+ return;
+}
+
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON);
+sub evaluate_gitweb_config {
+ our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++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.
+ # 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);
+ $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON);
+
+ # Common system-wide settings for convenience.
+ # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM.
+ read_config_file($GITWEB_CONFIG_COMMON);
+
+ # Use first config file that exists. This means use the per-instance
+ # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG.
+ read_config_file($GITWEB_CONFIG) and return;
+ read_config_file($GITWEB_CONFIG_SYSTEM);
}
# Get loadavg of system, to compare against $maxload.
@@ -1476,6 +1520,17 @@ sub esc_path {
return $str;
}
+# Sanitize for use in XHTML + application/xml+xhtm (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;
+ return $str;
+}
+
# Make control characters "printable", using character escape codes (CEC)
sub quot_cec {
my $cntrl = shift;
@@ -2485,6 +2540,13 @@ sub git_get_project_config {
# key sanity check
return unless ($key);
+ # only subsection, if exists, is case sensitive,
+ # and not lowercased by 'git config -z -l'
+ if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) {
+ $key = join(".", lc($hi), $mi, lc($lo));
+ } else {
+ $key = lc($key);
+ }
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
@@ -2621,7 +2683,7 @@ sub git_get_project_ctags {
close $ct;
(my $ctag = $tagfile) =~ s#.*/##;
- if ($val =~ /\d+/) {
+ if ($val =~ /^\d+$/) {
$ctags->{$ctag} = $val;
} else {
$ctags->{$ctag} = 1;
@@ -2737,21 +2799,23 @@ sub git_get_project_url_list {
}
sub git_get_projects_list {
- my ($filter) = @_;
+ my $filter = shift || '';
my @list;
- $filter ||= '';
$filter =~ s/\.git$//;
- my $check_forks = gitweb_check_feature('forks');
-
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list . ($filter ? "/$filter" : '');
+ my $dir = $projects_list;
# remove the trailing "/"
$dir =~ s!/+$!!;
- my $pfxlen = length("$dir");
- my $pfxdepth = ($dir =~ tr!/!!);
+ my $pfxlen = length("$projects_list");
+ my $pfxdepth = ($projects_list =~ tr!/!!);
+ # when filtering, search only given subdirectory
+ if ($filter) {
+ $dir .= "/$filter";
+ $dir =~ s!/+$!!;
+ }
File::Find::find({
follow_fast => 1, # follow symbolic links
@@ -2766,14 +2830,14 @@ sub git_get_projects_list {
# only directories can be git repositories
return unless (-d $_);
# don't traverse too deep (Find is super slow on os x)
+ # $project_maxdepth excludes depth of $projectroot
if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
$File::Find::prune = 1;
return;
}
- my $subdir = substr($File::Find::name, $pfxlen + 1);
+ my $path = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- my $path = ($filter ? "$filter/" : '') . $subdir;
if (check_export_ok("$projectroot/$path")) {
push @list, { path => $path };
$File::Find::prune = 1;
@@ -2786,7 +2850,6 @@ sub git_get_projects_list {
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
- my %paths;
open my $fd, '<', $projects_list or return;
PROJECT:
while (my $line = <$fd>) {
@@ -2797,32 +2860,9 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if ($filter ne '') {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PROJECT;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PROJECT;
- }
- } elsif ($check_forks) {
- PATH:
- foreach my $filter (keys %paths) {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PATH;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PATH;
- }
- # is a fork, don't include it in
- # the list
- next PROJECT;
- }
+ # if $filter is rpovided, check if $path begins with $filter
+ if ($filter && $path !~ m!^\Q$filter\E/!) {
+ next;
}
if (check_export_ok("$projectroot/$path")) {
my $pr = {
@@ -2830,8 +2870,6 @@ sub git_get_projects_list {
owner => to_utf8($owner),
};
push @list, $pr;
- (my $forks_path = $path) =~ s/\.git$//;
- $paths{$forks_path}++;
}
}
close $fd;
@@ -2839,6 +2877,98 @@ sub git_get_projects_list {
return @list;
}
+# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
+# as side effects it sets 'forks' field to list of forks for forked projects
+sub filter_forks_from_projects_list {
+ my $projects = shift;
+
+ my %trie; # prefix tree of directories (path components)
+ # generate trie out of those directories that might contain forks
+ foreach my $pr (@$projects) {
+ my $path = $pr->{'path'};
+ $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
+ next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
+ next unless ($path); # skip '.git' repository: tests, git-instaweb
+ next unless (-d "$projectroot/$path"); # containing directory exists
+ $pr->{'forks'} = []; # there can be 0 or more forks of project
+
+ # add to trie
+ my @dirs = split('/', $path);
+ # walk the trie, until either runs out of components or out of trie
+ my $ref = \%trie;
+ while (scalar @dirs &&
+ exists($ref->{$dirs[0]})) {
+ $ref = $ref->{shift @dirs};
+ }
+ # create rest of trie structure from rest of components
+ foreach my $dir (@dirs) {
+ $ref = $ref->{$dir} = {};
+ }
+ # create end marker, store $pr as a data
+ $ref->{''} = $pr if (!exists $ref->{''});
+ }
+
+ # filter out forks, by finding shortest prefix match for paths
+ my @filtered;
+ PROJECT:
+ foreach my $pr (@$projects) {
+ # trie lookup
+ my $ref = \%trie;
+ DIR:
+ foreach my $dir (split('/', $pr->{'path'})) {
+ if (exists $ref->{''}) {
+ # found [shortest] prefix, is a fork - skip it
+ push @{$ref->{''}{'forks'}}, $pr;
+ next PROJECT;
+ }
+ if (!exists $ref->{$dir}) {
+ # not in trie, cannot have prefix, not a fork
+ push @filtered, $pr;
+ next PROJECT;
+ }
+ # If the dir is there, we just walk one step down the trie.
+ $ref = $ref->{$dir};
+ }
+ # we ran out of trie
+ # (shouldn't happen: it's either no match, or end marker)
+ push @filtered, $pr;
+ }
+
+ return @filtered;
+}
+
+# note: fill_project_list_info must be run first,
+# for 'descr_long' and 'ctags' to be filled
+sub search_projects_list {
+ my ($projlist, %opts) = @_;
+ my $tagfilter = $opts{'tagfilter'};
+ my $searchtext = $opts{'searchtext'};
+
+ return @$projlist
+ unless ($tagfilter || $searchtext);
+
+ my @projects;
+ PROJECT:
+ foreach my $pr (@$projlist) {
+
+ if ($tagfilter) {
+ next unless ref($pr->{'ctags'}) eq 'HASH';
+ next unless
+ grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
+ }
+
+ if ($searchtext) {
+ next unless
+ $pr->{'path'} =~ /$searchtext/ ||
+ $pr->{'descr_long'} =~ /$searchtext/;
+ }
+
+ push @projects, $pr;
+ }
+
+ return @projects;
+}
+
our $gitweb_project_owner = undef;
sub git_get_project_list_from_file {
@@ -3469,12 +3599,9 @@ sub mimetype_guess_file {
open(my $mh, '<', $mimemap) or return undef;
while (<$mh>) {
next if m/^#/; # skip comments
- my ($mimetype, $exts) = split(/\t+/);
- if (defined $exts) {
- my @exts = split(/\s+/, $exts);
- foreach my $ext (@exts) {
- $mimemap{$ext} = $mimetype;
- }
+ my ($mimetype, @exts) = split(/\s+/);
+ foreach my $ext (@exts) {
+ $mimemap{$ext} = $mimetype;
}
}
close($mh);
@@ -3590,6 +3717,20 @@ sub get_page_title {
return $title;
}
+sub get_content_type_html {
+ # require explicit support from the UA if we are to send the page as
+ # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+ # we have to do this because MSIE sometimes globs '*/*', pretending to
+ # support xhtml+xml but choking when it gets what it asked for.
+ if (defined $cgi->http('HTTP_ACCEPT') &&
+ $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+ $cgi->Accept('application/xhtml+xml') != 0) {
+ return 'application/xhtml+xml';
+ } else {
+ return 'text/html';
+ }
+}
+
sub print_feed_meta {
if (defined $project) {
my %href_params = get_feed_info();
@@ -3635,24 +3776,90 @@ sub print_feed_meta {
}
}
+sub print_header_links {
+ my $status = shift;
+
+ # print out each stylesheet that exist, providing backwards capability
+ # for those people who defined $stylesheet in a config file
+ if (defined $stylesheet) {
+ print ''."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print ''."\n";
+ }
+ }
+ print_feed_meta()
+ if ($status eq '200 OK');
+ if (defined $favicon) {
+ print qq(\n);
+ }
+}
+
+sub print_nav_breadcrumbs {
+ my %opts = @_;
+
+ 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) {
+ my $action_print = $action ;
+ if (defined $opts{-action_extra}) {
+ $action_print = $cgi->a({-href => href(action=>$action)},
+ $action);
+ }
+ print " / $action_print";
+ }
+ if (defined $opts{-action_extra}) {
+ print " / $opts{-action_extra}";
+ }
+ print "\n";
+ }
+}
+
+sub print_search_form {
+ if (!defined $searchtext) {
+ $searchtext = "";
+ }
+ my $search_hash;
+ if (defined $hash_base) {
+ $search_hash = $hash_base;
+ } elsif (defined $hash) {
+ $search_hash = $hash;
+ } else {
+ $search_hash = "HEAD";
+ }
+ my $action = $my_uri;
+ my $use_pathinfo = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ $action .= "/".esc_url($project);
+ }
+ print $cgi->startform(-method => "get", -action => $action) .
+ "