use warnings;
# handle ACL in file access tests
use filetest 'access';
+use HTML::Entities;
+use Pandoc;
use CGI qw(:standard :escapeHTML -nosticky);
use CGI::Util qw(unescape);
-#use CGI::Carp qw(fatalsToBrowser set_message);
-use CGI::Carp qw(set_message);
+#use CGI::Carp qw(fatalsToBrowser set_message); # debugging only
+use CGI::Carp qw(set_message); # production
use Encode;
use Fcntl ':mode';
use File::Find qw();
# this can just be "git" if your webserver has a sensible PATH
our $GIT = "++GIT_BINDIR++/git";
+# executable path to Pandoc executable for rendering readme files etc
+our $PANDOC = "pandoc";
+
# absolute fs-path which will be prepended to the project path
#our $projectroot = "/pub/scm";
our $projectroot = "++GITWEB_PROJECTROOT++";
'sub' => \&feature_extra_branch_refs,
'override' => 0,
'default' => []},
+
+ # Enable or disable RSS feed
+ # Added by Andrew Lorimer, October 2019
+
+ 'rss' => {
+ 'sub' => \&feature_bool,
+ 'override' => 0,
+ 'default' => [1]},
+
+ # Enable or disable Atom feed
+ # Added by Andrew Lorimer, October 2019
+
+ 'atom' => {
+ 'sub' => \&feature_bool,
+ 'override' => 0,
+ 'default' => [1]},
+
+
+ # Enable or disable the shortlog view
+ # Added by Andrew Lorimer, November 2019
+ 'shortlog' => {
+ 'sub' => \&feature_bool,
+ 'override' => 0,
+ 'default' => [1]},
+
+ # Show author name for every commit in main commit list
+ # Added by Andrew Lorimer, January 2020
+ 'summary_omit_author' => {
+ 'sub' => \&feature_bool,
+ 'override' => 0,
+ 'default' => [1]},
);
sub gitweb_get_feature {
}
$href .= esc_path_info($params{'hash_base'});
- if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
+ if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
$href .= ":/".esc_path_info($params{'file_name'});
delete $params{'file_name'};
}
# Potentially abbreviated OID.
my $regex = oid_nlen_regex("7,64");
- $line = esc_html($line, -nbsp=>1);
+ $line = esc_html($line);
$line =~ s{
\b
(
# the author name is chopped and escaped according to the other
# optional parameters (see chop_str).
sub format_author_html {
+ if (not gitweb_check_feature('summary_omit_author')) {
my $tag = shift;
my $co = shift;
my $author = chop_and_escape_str($co->{'author_name'}, @_);
git_get_avatar($co->{'author_email'}, -pad_after => 1) .
$author) .
"</$tag>";
+ } else {
+ return "";
+ }
}
# format git diff header line, i.e. "diff --(git|combined|cc) ..."
foreach my $title (@commit_lines) {
$title =~ s/^ //;
if ($title ne "") {
- #$co{'title'} = chop_str($title, 80, 5);
$co{'title'} = $title;
# remove leading stuff of merges to make the interesting part visible
if (length($title) > 50) {
$title =~ s/\/pub\/scm//;
}
}
- #$co{'title_short'} = chop_str($title, 50, 5);
- $co{'title_short'} = $title;
+ $co{'title_short'} = chop_str($title, 50, 5);
last;
}
}
}
# remove added spaces
foreach my $line (@commit_lines) {
- $line =~ s/^ //;
+ $line =~ s/\s\s+//;
+ $line =~ s/\n+$//;
}
$co{'comment'} = \@commit_lines;
close $fd;
}
+# Check if a file should be displayed
+sub check_tree_filter {
+ my $filename = shift;
+ my @treefilter_pattern = gitweb_get_feature('tree_filter');
+ return $filename =~ /$treefilter_pattern[0]/;
+}
+
## ......................................................................
## mimetype related functions
$href_params{'-title'} = 'log';
}
- foreach my $format (qw(RSS Atom)) {
- my $type = lc($format);
- my %link_attr = (
- '-rel' => 'alternate',
- '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
- '-type' => "application/$type+xml"
- );
-
- $href_params{'extra_options'} = undef;
- $href_params{'action'} = $type;
- $link_attr{'-href'} = href(%href_params);
- print "<link ".
- "rel=\"$link_attr{'-rel'}\" ".
- "title=\"$link_attr{'-title'}\" ".
- "href=\"$link_attr{'-href'}\" ".
- "type=\"$link_attr{'-type'}\" ".
- "/>\n";
-
- $href_params{'extra_options'} = '--no-merges';
- $link_attr{'-href'} = href(%href_params);
- $link_attr{'-title'} .= ' (no merges)';
- print "<link ".
- "rel=\"$link_attr{'-rel'}\" ".
- "title=\"$link_attr{'-title'}\" ".
- "href=\"$link_attr{'-href'}\" ".
- "type=\"$link_attr{'-type'}\" ".
- "/>\n";
+ foreach my $format (qw("RSS Atom")) {
+ if (gitweb_check_feature($format)) {
+ print_feed_links($format, %href_params);
+ }
}
} else {
}
}
+sub print_feed_links {
+
+ my ($format, %href_params) = @_;
+
+ my $type = lc($format);
+ my %link_attr = (
+ '-rel' => 'alternate',
+ '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
+ '-type' => "application/$type+xml"
+ );
+
+ $href_params{'extra_options'} = undef;
+ $href_params{'action'} = $type;
+ $link_attr{'-href'} = href(%href_params);
+ print "<link ".
+ "rel=\"$link_attr{'-rel'}\" ".
+ "title=\"$link_attr{'-title'}\" ".
+ "href=\"$link_attr{'-href'}\" ".
+ "type=\"$link_attr{'-type'}\" ".
+ "/>\n";
+
+ $href_params{'extra_options'} = '--no-merges';
+ $link_attr{'-href'} = href(%href_params);
+ $link_attr{'-title'} .= ' (no merges)';
+ print "<link ".
+ "rel=\"$link_attr{'-rel'}\" ".
+ "title=\"$link_attr{'-title'}\" ".
+ "href=\"$link_attr{'-href'}\" ".
+ "type=\"$link_attr{'-type'}\" ".
+ "/>\n";
+
+}
+
sub print_header_links {
my $status = shift;
my $projectbasename = pop @dirname;
print_nav_breadcrumbs_path(@dirname);
print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
- if (defined $action) {
+ if (defined $action && $action ne "summary") {
$action =~ s/_/ /;
+ if ($action eq "blob") {
+ $action = $file_name;
+ }
my $action_print = $action ;
if (defined $opts{-action_extra}) {
$action_print = $cgi->a({-href => href(action=>$action)},
}
$href_params{'-title'} ||= 'log';
- foreach my $format (qw(RSS Atom)) {
- $href_params{'action'} = lc($format);
+ if (gitweb_check_feature("rss")) {
+ $href_params{'action'} = "rss";
+ print $cgi->a({-href => href(%href_params),
+ -title => "$href_params{'-title'} RSS feed",
+ -class => $feed_class}, "RSS")."\n";
+ }
+ if (gitweb_check_feature("atom")) {
+ $href_params{'action'} = "atom";
print $cgi->a({-href => href(%href_params),
- -title => "$href_params{'-title'} $format feed",
- -class => $feed_class}, $format)."\n";
+ -title => "$href_params{'-title'} Atom feed",
+ -class => $feed_class}, "Atom")."\n";
}
} else {
my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
$extra = '' if !defined $extra; # pager or formats
- my @navs = qw(summary shortlog log commit diff tree);
+ # Check if shortlog should be added to breadcrumb
+ # Added by Andrew Lorimer, November 2019
+ my @navs = qw(summary log commit diff tree);
+ if (gitweb_check_feature("shortlog")) {
+ @navs = qw(summary shortlog log commit diff tree);
+ }
if ($suppress) {
@navs = grep { $_ ne $suppress } @navs;
}
my $author = $co->{'author_name'};
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
- print "<$tag class=\"author_date\">" .
- format_search_author($author, "author", esc_html($author)) .
- " [".format_timestamp_html(\%ad)."]".
- git_get_avatar($co->{'author_email'}, -pad_before => 1) .
- "</$tag>\n";
+ print("<$tag class=\"author\">");
+ print(git_get_avatar($co->{'author_email'}, -pad_before => 1));
+ print(format_search_author($author, "author", esc_html($author)));
+ print("</$tag>\n");
+ print("<$tag class=\"age\">");
+ print(format_timestamp_html(\%ad));
+ print("</$tag>\n");
}
# Outputs table rows containing the full author or committer information,
return $output;
}
+
+# Print the log output for a single commit
sub git_print_log {
my $log = shift;
my %opts = @_;
# uses global variable $project
my ($commitlist, $from, $to, $refs, $extra) = @_;
- $from = 0 unless defined $from;
+ if (not defined $from) {
+ my $from = 0;
+ }
$to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
for (my $i = 0; $i <= $to; $i++) {
next if !%co;
my $commit = $co{'id'};
my $ref = format_ref_marker($refs, $commit);
- git_print_header_div('commit',
- "<span class=\"age\">$co{'age_string'}</span>" .
- esc_html($co{'title'}), $ref,
- $commit);
- print "<div class=\"title_text\">\n" .
- "<div class=\"log_link\">\n" .
+
+ print("<div class=\"log_commit\">\n");
+ print "<div class=\"log_link\">\n" .
$cgi->a({-href => href(action=>"diff", hash=>$commit)}, "diff") .
" | " .
$cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
"<br/>\n" .
"</div>\n";
- git_print_authorship(\%co, -tag => 'span');
- print "<br/>\n</div>\n";
-
- print "<div class=\"log_body\">\n";
- git_print_log($co{'comment'}, -final_empty_line=> 1);
- print "</div>\n";
+ print("<span class=\"commit_title\">");
+ print format_subject_html($co{'title'}, $co{'title_short'},
+ href(action=>"commit", hash=>$commit), $ref);
+ print("</span>");
+ git_print_authorship(\%co, -tag => 'span');
+ print("<p class=\"commit_message\">");
+ git_print_log($co{'comment'}, -final_empty_line=>1);
+ print("</p>");
+ # print "<br/>\n</div>\n";
+
+ #print "<div class=\"log_body\">\n";
+ #git_print_log($co{'comment'}, -final_empty_line=> 1);
+ #print "</div>\n";
+ print("</div>");
}
if ($extra) {
print "<nav>\n";
# uses global variable $project
my ($commitlist, $from, $to, $refs, $extra) = @_;
- $from = 0 unless defined $from;
+ if (not defined $from) {
+ my $from = 0;
+ }
$to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
+ print "<h3>Commits</h3>";
print "<table class=\"shortlog\">\n";
- my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my %co = %{$commitlist->[$i]};
my $commit = $co{'id'};
my $ref = format_ref_marker($refs, $commit);
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
+ print("<tr>");
print "<td class=\"age\" title=\"$co{'age_string_age'}\">$co{'age_string_date'}</td>\n" .
"<td class=\"separator\">•</td>" .
format_author_html('td', \%co, 10) . "<td>";
$from = 0 unless defined $from;
$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+ print "<h3>Tags</h3>";
print "<table class=\"tags\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
$from = 0 unless defined $from;
$to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
+ print "<h3>Branches</h3>";
print "<table class=\"heads\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
# If XSS prevention is on, we don't include README.html.
# TODO: Allow a readme in some safe format.
- if (!$prevent_xss && -s "$projectroot/$project/README.html") {
- print "<div class=\"title\">readme</div>\n" .
- "<div class=\"readme\">\n";
- insert_file("$projectroot/$project/README.html");
- print "\n</div>\n"; # class="readme"
+ if (!$prevent_xss) {
+ open my $fd, "-|", git_cmd(), "ls-tree", "master", "--name-only"
+ or die_error(500, "Error checking ls_tree for readme");
+ my @readme_files = map { chomp; $_ } <$fd>;
+ close $fd
+ or die_error(500, "Error checking ls_tree for readme");
+ if (@readme_files) {
+ $hash_base ||= git_get_head_hash($project);
+ foreach my $readme_file (@readme_files) {
+ if ($readme_file =~ /^readme($|.+)/i && check_tree_filter($readme_file)) {
+ my $hash = git_get_hash_by_path($hash_base, $readme_file)
+ or die_error(500, "Error getting hash for readme file $readme_file");
+ print "<div class=\"title readme-title\">$readme_file<nav class=\"formats_nav\">";
+ print $cgi->a({-href => href(action=>"blob", hash=>$hash)}, "source") . " | ";
+ print $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw");
+ print "</nav>";
+ print "</div>\n" .
+ "<div class=\"readme\">\n";
+ open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
+ or die_error(500, "Cat of readme '$hash' failed");
+ my $filecontents = "";
+ while (my $line = <$fd>) {
+ $filecontents .= $line;
+ }
+
+ # Process markdown if necessary
+ if ($readme_file =~ /\.md$/i && check_pandoc()) {
+ $filecontents = pandoc->parse('markdown' => $filecontents)->to_html;
+ }
+ print($filecontents);
+
+ close $fd
+ or die_error(500, "Error processing readme file $hash");
+ print "\n</div>\n"; # class="readme"
+ last;
+ }
+ }
+ }
}
# we need to request one more than 16 (0..15) to check if
git_footer_html();
}
+
+# Check if Pandoc module is installed, and import it if it is available.
+# This is a bit hacky, but Pandoc rendering is considered an "optional"
+# feature, so we need to make sure no errors are thrown if it's not available.
+# Added by Andrew Lorimer, October 2019
+
+sub check_pandoc {
+ eval "use Pandoc; 1" or return 0;
+ return 1;
+}
+
sub git_tag {
my %tag = parse_tag($hash);
# blobs defined by non-textual hash id's can be cached
$expires = "+1d";
}
- my @treefilter_pattern = gitweb_get_feature('tree_filter');
- if (not $file_name =~ /@treefilter_pattern/) {
+ if (! check_tree_filter($file_name)) {
die_error(403, "Access denied ($file_name)");
}
-
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(500, "Open git-cat-file blob '$hash' failed");
# blobs defined by non-textual hash id's can be cached
$expires = "+1d";
}
- my @treefilter_pattern = gitweb_get_feature('tree_filter');
- if (not $file_name =~ /@treefilter_pattern/) {
+ if (! check_tree_filter($file_name)) {
die_error(403, "Access denied ($file_name)");
}
defined $file_name && $file_name =~ m![^/]+$!) {
print "<tr>\n";
my $up = $file_name;
- $up =~ s!/?[^/]+$!!;
- undef $up unless $up;
+ #$up =~ s!/?[^/]+$!!;
+ $up =~ s/.(?<=(\/|^.))[^\/]*\/?$//;
+ #if ($up eq "") {
+ # $hash_base .= ":";
+ #}
# based on git_print_tree_entry
print '<td class="mode">';
print '<svg width="14" height="14" '
print $cgi->a({-href => href(action=>"tree",
hash_base=>$hash_base,
file_name=>$up)},
- "parent");
+ "parent (dest: $up; base: $hash_base)");
print "</td>\n";
print "<td class=\"link\"></td>\n";
print "</tr>\n";
}
- my @treefilter_pattern = gitweb_get_feature('tree_filter');
foreach my $line (@entries) {
- my $t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
- if ($t->{'name'} =~ /@treefilter_pattern/) {
- print "<tr>\n";
- git_print_tree_entry($t, $basedir, $hash_base, $have_blame);
- print "</tr>\n";
- }
+ my $t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
+ if (check_tree_filter($t->{'name'})) {
+ print "<tr>\n";
+ git_print_tree_entry($t, $basedir, $hash_base, $have_blame);
+ print "</tr>\n";
+ }
}
print "</table>\n";
git_footer_html();
git_header_html();
git_print_page_nav('','', $hash,$hash,$hash);
print <<EOT;
-<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
-regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
-the pattern entered is recognized as the POSIX extended
+<p>By default, the search string is matched exactly (but without regard to case, unless
+<em>pickaxe</em> is selected). However, when you check the <em>re</em> checkbox,
+the pattern is interpreted as a POSIX extended
<a href="https://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
insensitive).</p>
+<h3>Search options</h3>
<dl>
<dt><b>commit</b></dt>
<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
if ($have_grep) {
print <<EOT;
<dt><b>grep</b></dt>
-<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
- a different one) are searched for the given pattern. On large trees, this search can take
+<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing a different one)
+are searched for the given pattern. On large trees, this search can take
a while and put some strain on the server, so please use it with some consideration. Note that
-due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
+due to a git-grep peculiarity, currently if regex mode is turned off, the matches are
case-sensitive.</dd>
EOT
}
print <<EOT;
<dt><b>author</b></dt>
-<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given pattern.</dd>
+<dd>Name and email of the author and date of the patch will be scanned for the given pattern.</dd>
<dt><b>committer</b></dt>
-<dd>Name and e-mail of the committer and date of commit will be scanned for the given pattern.</dd>
+<dd>Name and email of the committer and date of commit will be scanned for the given pattern.</dd>
EOT
my $have_pickaxe = gitweb_check_feature('pickaxe');
if ($have_pickaxe) {
if ($format ne 'rss' && $format ne 'atom') {
die_error(400, "Unknown web feed format");
}
+ if (not gitweb_check_feature($format)) {
+ die_error(403, "Feed disabled");
+ }
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
my $head = $hash || 'HEAD';