improve error page/httpd logging and file permissions
[gitweb.git] / gitweb / gitweb.perl
index 7fef19fe591c2dd077ec4688f20e3c7bec521f13..49ed13b9dea06618cdc87481f55613c3cd7f6191 100755 (executable)
@@ -5,6 +5,8 @@
 # (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org>
 # (C) 2005, Christian Gierke
 #
+# Modified by Andrew Lorimer, 2019
+#
 # This program is licensed under the GPLv2
 
 use 5.008;
@@ -14,7 +16,8 @@
 use filetest 'access';
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
-use CGI::Carp qw(fatalsToBrowser set_message);
+#use CGI::Carp qw(fatalsToBrowser set_message);
+use CGI::Carp qw(set_message);
 use Encode;
 use Fcntl ':mode';
 use File::Find qw();
@@ -110,6 +113,9 @@ sub evaluate_uri {
 # filename of html text to include at bottom of each page
 our $site_footer = "++GITWEB_SITE_FOOTER++";
 
+# Whether to show table header row on index page
+our $index_header = 1;
+
 # URI of stylesheets
 our @stylesheets = ("++GITWEB_CSS++");
 # URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
@@ -145,6 +151,9 @@ sub evaluate_uri {
 # valid values are none, project, descr, owner, and age
 our $default_projects_order = "project";
 
+# show table header row for projects list on index page
+our $projects_table_headers = 1;
+
 # show repository only if this file exists
 # (only effective if this variable evaluates to true)
 our $export_ok = "++GITWEB_EXPORT_OK++";
@@ -155,6 +164,12 @@ sub evaluate_uri {
 # don't generate information about owners of repositories
 our $omit_owner=0;
 
+# Hide links to each repo's pages in the index
+our $omit_shortcuts = 0;
+
+# Hide .git extension in index table
+our $omit_git_extension = 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"; }
@@ -182,6 +197,18 @@ sub evaluate_uri {
 # could be even 'utf-8' for the old behavior)
 our $fallback_encoding = 'latin1';
 
+# number of characters to truncate commit hashes to
+# Added by Andrew Lorimer, October 2019
+our $hash_char = 7;
+
+# whether to show OPML feed link on homepage
+# Added by Andrew Lorimer, October 2019
+our $opml = 1;
+
+# whether to show TXT link on homepage
+# Added by Andrew Lorimer, October 2019
+our $txt = 1;
+
 # 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).
@@ -290,6 +317,7 @@ sub evaluate_uri {
        (map { $_ => 'pl'  } qw(pl perl pm)), # perhaps also 'cgi'
        (map { $_ => 'make'} qw(make mak mk)),
        (map { $_ => 'xml' } qw(xml xhtml html htm)),
+        (map { $_ => 'markdown' } qw(md markdown))
 );
 
 # You define site-wide feature defaults here; override them with
@@ -324,6 +352,17 @@ sub evaluate_uri {
                'override' => 0,
                'default' => [0]},
 
+        # Regex filter for paths (files and directories) to display in the 
+        # tree view. Currently this only affects the files listed in the tree, 
+        # and files that do not match this regex can still be accessed through 
+        # the full URL. This value can be overriden for individual projects. 
+        # Note that double-escaping is necessary for backslashes (i.e. to match 
+        # a literal full stop, use `\\.`).
+        'tree_filter' => {
+                'sub' => \&feature_tree_filter,
+                'override' => 0,
+                'default' => [".*"]},
+
        # Enable the 'snapshot' link, providing a compressed archive of any
        # tree. This can potentially generate high traffic if you have large
        # project.
@@ -619,6 +658,12 @@ sub feature_bool {
        }
 }
 
+sub feature_tree_filter {
+  my @pattern_global = shift;
+  my @pattern_project = git_get_project_config('tree_filter');
+  return (scalar(@pattern_project) > 0 ? $pattern_project[0] : $pattern_global[0]);
+}
+
 sub feature_snapshot {
        my (@fmts) = @_;
 
@@ -868,8 +913,8 @@ sub oid_nlen_prefix_infix_regex {
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
        "blob_plain" => \&git_blob_plain,
-       "commitdiff" => \&git_commitdiff,
-       "commitdiff_plain" => \&git_commitdiff_plain,
+       "diff" => \&git_diff,
+       "diff_plain" => \&git_diff_plain,
        "commit" => \&git_commit,
        "forks" => \&git_forks,
        "heads" => \&git_heads,
@@ -930,7 +975,9 @@ sub evaluate_path_info {
        while ($project && !check_head_link("$projectroot/$project")) {
                $project =~ s,/*[^/]*$,,;
        }
-       return unless $project;
+        if (!$project) {
+          die_error(404, "Project $path_info not found");
+        }
        $input_params{'project'} = $project;
 
        # do not change any parameters if an action is given using the query string
@@ -1276,11 +1323,11 @@ sub run_request {
 
        # $projectroot and $projects_list might be set in gitweb config file
        $projects_list ||= $projectroot;
+       evaluate_git_dir();
 
        evaluate_query_params();
        evaluate_path_info();
        evaluate_and_validate_params();
-       evaluate_git_dir();
 
        configure_gitweb_features();
 
@@ -2117,25 +2164,34 @@ sub format_ref_marker {
                        $class .= " indirect" if $indirect;
 
                        my $dest_action = "shortlog";
+                       my $dest = "";
+                       $dest .= "refs/" unless $ref =~ m!^refs/!;
+                       $dest .= $ref;
 
                        if ($indirect) {
                                $dest_action = "tag" unless $action eq "tag";
+                                $markers .= $cgi->a({
+                                        -href => href(
+                                                action=>$dest_action,
+                                                hash=>$dest
+                                        )}, '<span class="' . esc_attr($class)
+                                      . '" title="' . esc_attr($ref) . '"><svg width="14" height="14" '
+                                      . 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+                                      . '<use xlink:href="#tagicon" /></svg>' . esc_html($name) . '</span>');
                        } elsif ($action =~ /^(history|(short)?log)$/) {
                                $dest_action = $action;
+                                $markers .= $cgi->a({
+                                        -href => href(
+                                                action=>$dest_action,
+                                                hash=>$dest
+                                        )}, '<span class="' . esc_attr($class)
+                                      . '" title="' . esc_attr($ref) . '"><svg width="14" height="14" '
+                                      . 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+                                      . '<use xlink:href="#reficon" /></svg>' . esc_html($name) . '</span>');
                        }
 
-                       my $dest = "";
-                       $dest .= "refs/" unless $ref =~ m!^refs/!;
-                       $dest .= $ref;
 
-                       my $link = $cgi->a({
-                               -href => href(
-                                       action=>$dest_action,
-                                       hash=>$dest
-                               )}, esc_html($name));
 
-                       $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
-                               $link . "</span>";
                }
        }
 
@@ -2666,6 +2722,7 @@ sub get_feed_info {
 # returns path to the core git executable and the --git-dir parameter as list
 sub git_cmd {
        $number_of_git_cmds++;
+        evaluate_git_dir();
        return $GIT, '--git-dir='.$git_dir;
 }
 
@@ -3529,7 +3586,8 @@ sub parse_commit_text {
        foreach my $title (@commit_lines) {
                $title =~ s/^    //;
                if ($title ne "") {
-                       $co{'title'} = chop_str($title, 80, 5);
+                        #$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/^Automatic //;
@@ -3547,7 +3605,8 @@ sub parse_commit_text {
                                        $title =~ s/\/pub\/scm//;
                                }
                        }
-                       $co{'title_short'} = chop_str($title, 50, 5);
+                        #$co{'title_short'} = chop_str($title, 50, 5);
+                        $co{'title_short'} = $title;
                        last;
                }
        }
@@ -4112,15 +4171,30 @@ sub print_nav_breadcrumbs_path {
 sub print_nav_breadcrumbs {
        my %opts = @_;
 
+        # Modified by Andrew Lorimer, October 2019
+        # Do not show trailing slash in breadcrumb for last item
+        #my @crumbs = (@extra_breadcrumbs, [ $home_link_str => $home_link ]);
+       #for my $i (0 .. (length @crumbs - 1)) {
+       #       print $cgi->a({-href => esc_url($crumbs[$i]->[1])}, $crumbs[$i]->[0]);
+        #        if ($i != $#crumbs) {
+        #          print " / ";
+        #        }
+       #}
+        my @base_links = ();
        for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) {
-               print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / ";
+          if (defined $crumb && length $crumb && $crumb ne "") {
+            push(@base_links, $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]));
+          }
        }
+        print join(" / ", @base_links);
        if (defined $project) {
+                print " / ";
                my @dirname = split '/', $project;
                my $projectbasename = pop @dirname;
                print_nav_breadcrumbs_path(@dirname);
                print $cgi->a({-href => href(action=>"summary")}, esc_html($projectbasename));
                if (defined $action) {
+                        $action =~ s/_/ /;
                        my $action_print = $action ;
                        if (defined $opts{-action_extra}) {
                                $action_print = $cgi->a({-href => href(action=>$action)},
@@ -4154,8 +4228,8 @@ sub print_search_form {
        if ($use_pathinfo) {
                $action .= "/".esc_url($project);
        }
+        print "<div class=\"search\">";
        print $cgi->start_form(-method => "get", -action => $action) .
-             "<div class=\"search\">\n" .
              (!$use_pathinfo &&
              $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
              $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
@@ -4163,14 +4237,14 @@ sub print_search_form {
              $cgi->popup_menu(-name => 'st', -default => 'commit',
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
              " " . $cgi->a({-href => href(action=>"search_help"),
-                            -title => "search help" }, "?") . " search:\n",
-             $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
+                            -title => "search help" }, "?"),
+             $cgi->textfield(-name => "s", -value => $searchtext, -override => 1, -placeholder => "search " . $project) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
                             -checked => $search_use_regexp) .
              "</span>" .
-             "</div>" .
              $cgi->end_form() . "\n";
+        print "</div>";
 }
 
 sub git_header_html {
@@ -4188,12 +4262,14 @@ sub git_header_html {
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- gitweb $version, originally (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- modified by Andrew Lorimer, 2019 (see <https://git.lorimer.id.au>) -->
 <!-- git core binaries version $git_version -->
 <head>
 <meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
 <meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
 <meta name="robots" content="index, nofollow"/>
+<meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>$title</title>
 EOF
        # the stylesheet, favicon etc urls won't work correctly with path_info
@@ -4208,14 +4284,35 @@ sub git_header_html {
        }
 
        print "</head>\n" .
-             "<body>\n";
+             '<body>
+              <svg>
+                <symbol width="14" height="14" viewBox="0 0 384 512" id="reficon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="code-branch" class="svg-inline--fa fa-code-branch fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg">
+                  <path fill="currentColor" d="M384 144c0-44.2-35.8-80-80-80s-80 35.8-80 80c0 36.4 24.3 67.1 57.5 76.8-.6 16.1-4.2 28.5-11 36.9-15.4 19.2-49.3 22.4-85.2 25.7-28.2 2.6-57.4 5.4-81.3 16.9v-144c32.5-10.2 56-40.5 56-76.3 0-44.2-35.8-80-80-80S0 35.8 0 80c0 35.8 23.5 66.1 56 76.3v199.3C23.5 365.9 0 396.2 0 432c0 44.2 35.8 80 80 80s80-35.8 80-80c0-34-21.2-63.1-51.2-74.6 3.1-5.2 7.8-9.8 14.9-13.4 16.2-8.2 40.4-10.4 66.1-12.8 42.2-3.9 90-8.4 118.2-43.4 14-17.4 21.1-39.8 21.6-67.9 31.6-10.8 54.4-40.7 54.4-75.9zM80 64c8.8 0 16 7.2 16 16s-7.2 16-16 16-16-7.2-16-16 7.2-16 16-16zm0 384c-8.8 0-16-7.2-16-16s7.2-16 16-16 16 7.2 16 16-7.2 16-16 16zm224-320c8.8 0 16 7.2 16 16s-7.2 16-16 16-16-7.2-16-16 7.2-16 16-16z"></path>
+                </symbol>
+                <symbol width="14" height="14" viewBox="0 0 448 512" id="copyicon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="copy" class="svg-inline--fa fa-copy fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"></path>
+                </symbol>
+                <symbol width="14" height="14" viewBox="0 0 512 512" id="foldericon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="folder" class="svg-inline--fa fa-folder fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M464 128H272l-64-64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V176c0-26.51-21.49-48-48-48z"></path>
+                </symbol>
+                <symbol width="14" height="14" viewBox="0 0 512 512" id="tagicon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="tag" class="svg-inline--fa fa-tag fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M0 252.118V48C0 21.49 21.49 0 48 0h204.118a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882L293.823 497.941c-18.745 18.745-49.137 18.745-67.882 0L14.059 286.059A48 48 0 0 1 0 252.118zM112 64c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z"></path>
+                </symbol>
+                <symbol width="14" height="14" viewBox="0 0 320 512" id="parenticon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="level-up-alt" class="svg-inline--fa fa-level-up-alt fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M313.553 119.669L209.587 7.666c-9.485-10.214-25.676-10.229-35.174 0L70.438 119.669C56.232 134.969 67.062 160 88.025 160H152v272H68.024a11.996 11.996 0 0 0-8.485 3.515l-56 56C-4.021 499.074 1.333 512 12.024 512H208c13.255 0 24-10.745 24-24V160h63.966c20.878 0 31.851-24.969 17.587-40.331z"></path>
+                </symbol>
+              </svg>
+              ';
 
        if (defined $site_header && -f $site_header) {
                insert_file($site_header);
        }
 
-       print "<div class=\"page_header\">\n";
-       if (defined $logo) {
+       print "<div class=\"breadcrumb\">\n";
+
+        # Modified by Andrew Lorimer, October 2019
+        # Do not print logo if the image source is empty
+       if (defined $logo && $logo ne "") {
                print $cgi->a({-href => esc_url($logo_url),
                               -title => $logo_label},
                              $cgi->img({-src => esc_url($logo),
@@ -4225,23 +4322,13 @@ sub git_header_html {
        }
        print_nav_breadcrumbs(%opts);
        print "</div>\n";
-
-       my $have_search = gitweb_check_feature('search');
-       if (defined $project && $have_search) {
-               print_search_form();
-       }
 }
 
 sub git_footer_html {
        my $feed_class = 'rss_logo';
 
-       print "<div class=\"page_footer\">\n";
+       print "<footer>\n";
        if (defined $project) {
-               my $descr = git_get_project_description($project);
-               if (defined $descr) {
-                       print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
-               }
-
                my %href_params = get_feed_info();
                if (!%href_params) {
                        $feed_class .= ' generic';
@@ -4256,25 +4343,27 @@ sub git_footer_html {
                }
 
        } else {
+            if ($opml) {
                print $cgi->a({-href => href(project=>undef, action=>"opml",
                                             project_filter => $project_filter),
                              -class => $feed_class}, "OPML") . " ";
+            }
+            if ($txt) {
                print $cgi->a({-href => href(project=>undef, action=>"project_index",
                                             project_filter => $project_filter),
                              -class => $feed_class}, "TXT") . "\n";
+            }
        }
-       print "</div>\n"; # class="page_footer"
-
        if (defined $t0 && gitweb_check_feature('timed')) {
                print "<div id=\"generating_info\">\n";
                print 'This page took '.
                      '<span id="generating_time" class="time_span">'.
                      tv_interval($t0, [ gettimeofday() ]).
-                     ' seconds </span>'.
+                     ' s</span>'.
                      ' and '.
                      '<span id="generating_cmd">'.
                      $number_of_git_cmds.
-                     '</span> git commands '.
+                     ' git commands </span>'.
                      " to generate.\n";
                print "</div>\n"; # class="page_footer"
        }
@@ -4282,6 +4371,8 @@ sub git_footer_html {
        if (defined $site_footer && -f $site_footer) {
                insert_file($site_footer);
        }
+       print "</footer>\n";
+
 
        print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
        if (defined $action &&
@@ -4337,21 +4428,21 @@ sub die_error {
                500 => '500 Internal Server Error',
                503 => '503 Service Unavailable',
        );
-       git_header_html($http_responses{$status}, undef, %opts);
-       print <<EOF;
-<div class="page_body">
+        git_header_html($http_responses{$status}, undef, %opts);
+
+        print <<EOF;
 <br /><br />
-$status - $error
-<br />
+<h1 id="error-code" class="$status">$status</h1>
+<p id="error-msg" class="$status">$error</p>
+<p id="error-link" class="$status"><a href="$base_url">go home</a></p>
 EOF
-       if (defined $extra) {
+        if (defined $extra) {
                print "<hr />\n" .
                      "$extra\n";
        }
-       print "</div>\n";
 
        git_footer_html();
-       goto DONE_GITWEB
+        die "$status - $error"
                unless ($opts{'-error_handler'});
 }
 
@@ -4362,17 +4453,17 @@ sub git_print_page_nav {
        my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
        $extra = '' if !defined $extra; # pager or formats
 
-       my @navs = qw(summary shortlog log commit commitdiff tree);
+       my @navs = qw(summary shortlog log commit diff tree);
        if ($suppress) {
                @navs = grep { $_ ne $suppress } @navs;
        }
 
        my %arg = map { $_ => {action=>$_} } @navs;
        if (defined $head) {
-               for (qw(commit commitdiff)) {
+               for (qw(commit diff)) {
                        $arg{$_}{'hash'} = $head;
                }
-               if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
+               if ($current =~ m/^(tree | log | shortlog | commit | diff | search)$/x) {
                        for (qw(shortlog log)) {
                                $arg{$_}{'hash'} = $head;
                        }
@@ -4399,13 +4490,20 @@ sub git_print_page_nav {
                $arg{$label}{'_href'} = $link;
        }
 
-       print "<div class=\"page_nav\">\n" .
+        print "<header>";
+       print "<nav>\n" .
                (join " | ",
                 map { $_ eq $current ?
                       $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
                 } @navs);
-       print "<br/>\n$extra<br/>\n" .
-             "</div>\n";
+       print "<br/>\n$extra<br/>\n" if ($extra);
+        print "</nav>\n";
+
+       my $have_search = gitweb_check_feature('search');
+       if (defined $project && $have_search) {
+               print_search_form();
+       }
+        print "</header>";
 }
 
 # returns a submenu for the navigation of the refs views (tags, heads,
@@ -4451,17 +4549,19 @@ sub format_paging_nav {
 ## functions printing or outputting HTML: div
 
 sub git_print_header_div {
-       my ($action, $title, $hash, $hash_base) = @_;
+       my ($action, $title, $refstr, $hash, $hash_base) = @_;
        my %args = ();
 
        $args{'action'} = $action;
        $args{'hash'} = $hash if $hash;
        $args{'hash_base'} = $hash_base if $hash_base;
 
-       print "<div class=\"header\">\n" .
-             $cgi->a({-href => href(%args), -class => "title"},
-             $title ? $title : $action) .
-             "\n</div>\n";
+        if (defined $title && length $title) {
+          print "<div class=\"header\">\n" .
+                $cgi->a({-href => href(%args), -class => "title"},
+                $title ? $title : $action) .
+                "\n</div>\n";
+        }
 }
 
 sub format_repo_url {
@@ -4555,16 +4655,18 @@ sub git_print_authorship_rows {
        @people = ('author', 'committer') unless @people;
        foreach my $who (@people) {
                my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
-               print "<tr><td>$who</td><td>" .
+               print "<tr><th rowspan=\"2\" class=\"$who\">$who</th><td>" .
                      format_search_author($co->{"${who}_name"}, $who,
                                           esc_html($co->{"${who}_name"})) . " " .
                      format_search_author($co->{"${who}_email"}, $who,
                                           esc_html("<" . $co->{"${who}_email"} . ">")) .
-                     "</td><td rowspan=\"2\">" .
-                     git_get_avatar($co->{"${who}_email"}, -size => 'double') .
-                     "</td></tr>\n" .
-                     "<tr>" .
-                     "<td></td><td>" .
+                     "</td></tr><tr>";
+                if ($git_avatar) {
+                  print "<td rowspan=\"2\">"
+                    . git_get_avatar($co->{"${who}_email"}, -size => 'double')
+                    . "</td>";
+                }
+                print "\n<td>" .
                      format_timestamp_html(\%wd) .
                      "</td>" .
                      "</tr>\n";
@@ -4575,12 +4677,12 @@ sub git_print_page_path {
        my $name = shift;
        my $type = shift;
        my $hb = shift;
+        my $noprint = shift;
 
 
-       print "<div class=\"page_path\">";
-       print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+       my $output = $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
                      -title => 'tree root'}, to_utf8("[$project]"));
-       print " / ";
+       $output .= " / ";
        if (defined $name) {
                my @dirname = split '/', $name;
                my $basename = pop @dirname;
@@ -4588,25 +4690,75 @@ sub git_print_page_path {
 
                foreach my $dir (@dirname) {
                        $fullname .= ($fullname ? '/' : '') . $dir;
-                       print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
+                       $output .= $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
                                                     hash_base=>$hb),
                                      -title => $fullname}, esc_path($dir));
-                       print " / ";
+                       $output .= " / ";
                }
                if (defined $type && $type eq 'blob') {
-                       print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
+                       $output .= $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
                                                     hash_base=>$hb),
                                      -title => $name}, esc_path($basename));
                } elsif (defined $type && $type eq 'tree') {
-                       print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
+                       $output .= $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
                                                     hash_base=>$hb),
                                      -title => $name}, esc_path($basename));
-                       print " / ";
+                       $output .= " / ";
                } else {
-                       print esc_path($basename);
+                       $output .= esc_path($basename);
                }
        }
-       print "<br/></div>\n";
+       $output .= "<br/>\n";
+        unless ($noprint) {
+          print $output;
+        }
+        return $output;
+}
+
+
+sub git_path_header {
+       my $name = shift;
+       my $type = shift;
+       my $hb = shift;
+        my $noprint = shift;
+        my $title = shift;
+        my $ref = shift;
+        my $hash = shift;
+
+        my $output = "";
+       if (defined $name) {
+               my @dirname = split '/', $name;
+               my $basename = pop @dirname;
+               my $fullname = '';
+
+               foreach my $dir (@dirname) {
+                       $fullname .= ($fullname ? '/' : '') . $dir;
+                       $output .= $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
+                                                    hash_base=>$hb),
+                                     -title => $fullname, -class => "title"}, esc_path($dir));
+                       $output .= " / ";
+               }
+               if (defined $type && $type eq 'blob') {
+                       $output .= $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
+                                                    hash_base=>$hb),
+                                     -title => $name, -class => "title"}, esc_path($basename));
+               } elsif (defined $type && $type eq 'tree') {
+                       $output .= $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
+                                                    hash_base=>$hb),
+                                     -title => $name, -class => "title"}, esc_path($basename));
+                       $output .= " / ";
+               } else {
+                       $output .= esc_path($basename);
+               }
+       }
+
+        $output .= "<span class=\"tree_commit_info\">on commit "
+          . $cgi->a({-href => href(action => 'commit', hash => $ref)}, "$title (" . substr($hash, 0, $hash_char) . ")")
+          . "</span>";
+        unless ($noprint) {
+          print $output;
+        }
+        return $output;
 }
 
 sub git_print_log {
@@ -4730,7 +4882,13 @@ sub git_print_tree_entry {
        # the mode of the entry, list is the name of the entry, an href,
        # and link is the action links of the entry.
 
-       print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
+       print "<td class=\"mode\">";
+        if (S_ISDIR(oct $t->{'mode'} & S_IFMT)) {
+          print '<svg width="14" height="14" '
+          . 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+          . '<use xlink:href="#foldericon" /></svg>';
+        }
+        print mode_str($t->{'mode'}) . "</td>\n";
        if (exists $t->{'size'}) {
                print "<td class=\"size\">$t->{'size'}</td>\n";
        }
@@ -4855,18 +5013,18 @@ sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
        my ($parent) = $parents[0];
        my $have_blame = gitweb_check_feature('blame');
-       print "<div class=\"list_head\">\n";
        if ($#{$difftree} > 10) {
+                print "<div class=\"list_head\">\n";
                print(($#{$difftree} + 1) . " files changed:\n");
+                print "</div>\n";
        }
-       print "</div>\n";
 
        print "<table class=\"" .
              (@parents > 1 ? "combined " : "") .
              "diff_tree\">\n";
 
-       # header only for combined diff in 'commitdiff' view
-       my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
+       # header only for combined diff in 'diff' view
+       my $has_header = @$difftree && @parents > 1 && $action eq 'diff';
        if ($has_header) {
                # table header
                print "<thead><tr>\n" .
@@ -4874,9 +5032,9 @@ sub git_difftree_body {
                for (my $i = 0; $i < @parents; $i++) {
                        my $par = $parents[$i];
                        print "<th>" .
-                             $cgi->a({-href => href(action=>"commitdiff",
+                             $cgi->a({-href => href(action=>"diff",
                                                     hash=>$hash, hash_parent=>$par),
-                                      -title => 'commitdiff to parent number ' .
+                                      -title => 'diff to parent number ' .
                                                  ($i+1) . ': ' . substr($par,0,7)},
                                      $i+1) .
                              "&nbsp;</th>\n";
@@ -4915,7 +5073,7 @@ sub git_difftree_body {
                                      "</td>\n";
                        }
 
-                       if ($action eq 'commitdiff') {
+                       if ($action eq 'diff') {
                                # link to patch
                                $patchno++;
                                print "<td class=\"link\">" .
@@ -5014,7 +5172,7 @@ sub git_difftree_body {
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
-                       if ($action eq 'commitdiff') {
+                       if ($action eq 'diff') {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => href(-anchor=>"patch$patchno")},
@@ -5035,7 +5193,7 @@ sub git_difftree_body {
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
-                       if ($action eq 'commitdiff') {
+                       if ($action eq 'diff') {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => href(-anchor=>"patch$patchno")},
@@ -5078,7 +5236,7 @@ sub git_difftree_body {
                        print "</td>\n";
                        print "<td>$mode_chnge</td>\n";
                        print "<td class=\"link\">";
-                       if ($action eq 'commitdiff') {
+                       if ($action eq 'diff') {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => href(-anchor=>"patch$patchno")},
@@ -5124,7 +5282,7 @@ sub git_difftree_body {
                                      -class => "list"}, esc_path($diff->{'from_file'})) .
                              " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
                              "<td class=\"link\">";
-                       if ($action eq 'commitdiff') {
+                       if ($action eq 'diff') {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => href(-anchor=>"patch$patchno")},
@@ -5570,12 +5728,13 @@ sub git_project_search_form {
                if (defined $project_filter);
        print $cgi->textfield(-name => 's', -value => $searchtext,
                              -title => "Search project by name and description$limit",
-                             -size => 60) . "\n" .
+                             -size => 60,
+                              -placeholder => "search repos") . "\n" .
+             $cgi->submit(-name => 'btnS', -value => 'search') .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
                             -checked => $search_use_regexp) .
              "</span>\n" .
-             $cgi->submit(-name => 'btnS', -value => 'Search') .
              $cgi->end_form() . "\n" .
              $cgi->a({-href => href(project => undef, searchtext => undef,
                                     project_filter => $project_filter)},
@@ -5756,9 +5915,16 @@ sub git_project_list_rows {
                        }
                        print "</td>\n";
                }
+
+                # Added by Andrew Lorimer, October 2019
+                # Optionally remove ".git" extension in table
+                my $prname = esc_html_match_hl($pr->{'path'}, $search_regexp);
+                if ($omit_git_extension) {
+                  $prname =~  s/\.git//;
+                }
+
                print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-                                       -class => "list"},
-                                      esc_html_match_hl($pr->{'path'}, $search_regexp)) .
+                                       -class => "list"}, $prname) .
                      "</td>\n" .
                      "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
                                        -class => "list",
@@ -5768,21 +5934,31 @@ sub git_project_list_rows {
                                                                    $pr->{'descr'}, $search_regexp)
                                        : esc_html($pr->{'descr'})) .
                      "</td>\n";
+
                unless ($omit_owner) {
-                       print "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
+                       print "<td>" . chop_and_escape_str($pr->{'owner'}, 15) . "</td>\n";
                }
+
+                # Modified by Andrew Lorimer, October 2019
+                # Link date field to latest commit
                unless ($omit_age_column) {
                        print "<td class=\"". age_class($pr->{'age'}) . "\">" .
-                           (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n";
+                           (defined $pr->{'age_string'} ? $cgi->a({-href => href(project=>$pr->{'path'},
+                                    action=>"commit"), -class => "list"}, $pr->{'age_string'}) : "No commits") . "</td>\n";
                }
-               print"<td class=\"link\">" .
+
+                # Added by Andrew Lorimer, October 2019
+                # Optionally hide repo-specific page shortcuts in project list
+                unless ($omit_shortcuts) {
+                  print"<td class=\"link\">" .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
                      ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
-                     "</td>\n" .
-                     "</tr>\n";
+                     "</td>\n";
+                }
+                print "</tr>\n";
        }
 }
 
@@ -5833,7 +6009,7 @@ sub git_project_list_body {
        }
 
        print "<table class=\"project_list\">\n";
-       unless ($no_header) {
+       unless ($no_header || not $index_header) {
                print "<tr>\n";
                if ($check_forks) {
                        print "<th></th>\n";
@@ -5891,13 +6067,11 @@ sub git_log_body {
                my $ref = format_ref_marker($refs, $commit);
                git_print_header_div('commit',
                               "<span class=\"age\">$co{'age_string'}</span>" .
-                              esc_html($co{'title'}) . $ref,
+                              esc_html($co{'title'}), $ref,
                               $commit);
                print "<div class=\"title_text\">\n" .
                      "<div class=\"log_link\">\n" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
-                     " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
+                     $cgi->a({-href => href(action=>"diff", hash=>$commit)}, "diff") .
                      " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
                      "<br/>\n" .
@@ -5910,9 +6084,9 @@ sub git_log_body {
                print "</div>\n";
        }
        if ($extra) {
-               print "<div class=\"page_nav\">\n";
+               print "<nav>\n";
                print "$extra\n";
-               print "</div>\n";
+               print "</nav>\n";
        }
 }
 
@@ -5935,15 +6109,14 @@ sub git_shortlog_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
-               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+               print "<td class=\"age\" title=\"$co{'age_string_age'}\">$co{'age_string_date'}</td>\n" .
+                      "<td class=\"separator\">&#8226;</td>" .
                      format_author_html('td', \%co, 10) . "<td>";
                print format_subject_html($co{'title'}, $co{'title_short'},
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
+                     $cgi->a({-href => href(action=>"diff", hash=>$commit)}, "diff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
                my $snapshot_links = format_snapshot_links($commit);
                if (defined $snapshot_links) {
@@ -5985,7 +6158,7 @@ sub git_history_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+               print "<td title=\"$co{'age_string_age'}\">$co{'age_string_date'}</td>\n" .
        # shortlog:   format_author_html('td', \%co, 10)
                      format_author_html('td', \%co, 15, 3) . "<td>";
                # originally git_history used chop_str($co{'title'}, 50)
@@ -5994,7 +6167,7 @@ sub git_history_body {
                print "</td>\n" .
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
+                     $cgi->a({-href => href(action=>"diff", hash=>$commit)}, "diff");
 
                if ($ftype eq 'blob') {
                        print " | " .
@@ -6046,7 +6219,7 @@ sub git_tags_body {
                }
                $alternate ^= 1;
                if (defined $tag{'age'}) {
-                       print "<td><i>$tag{'age'}</i></td>\n";
+                       print "<td>$tag{'age'}</td>\n";
                } else {
                        print "<td></td>\n";
                }
@@ -6104,7 +6277,7 @@ sub git_heads_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td><i>$ref{'age'}</i></td>\n" .
+               print "<td>$ref{'age'}</td>\n" .
                      ($curr ? "<td class=\"current_head\">" : "<td>") .
                      $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
                               -class => "list name"},esc_html($ref{'name'})) .
@@ -6116,7 +6289,7 @@ sub git_heads_body {
                      "</td>\n" .
                      "</tr>";
        }
-       if (defined $extra) {
+       if (defined $extra && $extra != "") {
                print "<tr>\n" .
                      "<td colspan=\"3\">$extra</td>\n" .
                      "</tr>\n";
@@ -6151,7 +6324,7 @@ sub git_remote_block {
 
        my $dots;
        if (defined $limit && $limit < @$heads) {
-               $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
+               $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "show all remotes");
        }
 
        print $urls_table;
@@ -6195,7 +6368,7 @@ sub git_remotes_list {
        if ($limited) {
                print "<tr>\n" .
                      "<td colspan=\"3\">" .
-                     $cgi->a({-href => href(action=>"remotes")}, "...") .
+                     $cgi->a({-href => href(action=>"remotes")}, "show all remotes") .
                      "</td>\n" . "</tr>\n";
        }
 
@@ -6315,8 +6488,8 @@ sub git_search_changes {
                        $alternate ^= 1;
                        %co = parse_commit($set{'commit'});
                        my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
-                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                             "<td><i>$author</i></td>\n" .
+                       print "<td title=\"$co{'age_string_age'}\">$co{'age_string_date'}</td>\n" .
+                             "<td>$author</td>\n" .
                              "<td>" .
                              $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
                                      -class => "list subject"},
@@ -6371,10 +6544,10 @@ sub git_search_files {
        my $matches = 0;
        my $lastfile = '';
        my $file_href;
+        my $in_table = 0;
        while (my $line = <$fd>) {
                chomp $line;
                my ($file, $lno, $ltext, $binary);
-               last if ($matches++ > 1000);
                if ($line =~ /^Binary file (.+) matches$/) {
                        $file = $1;
                        $binary = 1;
@@ -6383,21 +6556,27 @@ sub git_search_files {
                        $file =~ s/^$co{'tree'}://;
                }
                if ($file ne $lastfile) {
-                       $lastfile and print "</td></tr>\n";
-                       if ($alternate++) {
-                               print "<tr class=\"dark\">\n";
-                       } else {
-                               print "<tr class=\"light\">\n";
-                       }
+                        if ($in_table) {
+                          print "\t\t</table>\n\t</td>\n</tr>\n";
+                          $in_table = 0;
+                        }
+                        if ($alternate++) {
+                          print "<tr class=\"dark\">\n";
+                        } else {
+                          print "<tr class=\"light\">\n";
+                        }
                        $file_href = href(action=>"blob", hash_base=>$co{'id'},
                                          file_name=>$file);
-                       print "<td class=\"list\">".
+                       print "\t<td class=\"list\">".
                                $cgi->a({-href => $file_href, -class => "list"}, esc_path($file));
-                       print "</td><td>\n";
+                       print "\t</td>\n";
+                        print "\t<td>\n\t\t<table class=\"grep_lines\">\n";
+                        $in_table = 1;
                        $lastfile = $file;
                }
+               last if ($matches++ > 1000);
                if ($binary) {
-                       print "<div class=\"binary\">Binary file</div>\n";
+                       print "\t\t\t<tr>\n\t\t\t\t<td colspan=\"2\" class=\"binary\">Binary file</td>\n\t\t\t</tr>\n";
                } else {
                        $ltext = untabify($ltext);
                        if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
@@ -6409,23 +6588,27 @@ sub git_search_files {
                        } else {
                                $ltext = esc_html($ltext, -nbsp=>1);
                        }
-                       print "<div class=\"pre\">" .
-                               $cgi->a({-href => $file_href.'#l'.$lno,
-                                       -class => "linenr"}, sprintf('%4i', $lno)) .
-                               ' ' .  $ltext . "</div>\n";
+                        print "\t\t\t<tr>\n\t\t\t\t<td>\n\t\t\t\t\t"
+                          . $cgi->a({-href => $file_href.'#l'.$lno,
+                              -class => "linenr"}, sprintf('%4i', $lno)) . "\n\t\t\t\t</td>\n";
+                       print "\t\t\t\t<td>\n\t\t\t\t\t<pre>$ltext</pre>\n\t\t\t\t</td>\n\t\t\t</tr>\n";
                }
        }
        if ($lastfile) {
-               print "</td></tr>\n";
                if ($matches > 1000) {
-                       print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
+                       print "<td colspan=\"3\"><div class=\"diff nodifferences\">Too many matches, listing trimmed</div></td>\n";
                }
+                print "</tr>\n</table>\n";
        } else {
-               print "<div class=\"diff nodifferences\">No matches found</div>\n";
+               print "<tr><td colspan=\"3\"><div class=\"diff nodifferences\">No matches found</div></td>\n";
+                $in_table = 0;
        }
        close $fd;
 
-       print "</table>\n";
+        if ($in_table) {
+          print "\t\t</table>\n\t</td>\n";
+          $in_table = 0;
+        }
 
        git_footer_html();
 }
@@ -6449,7 +6632,7 @@ sub git_search_grep_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+               print "<td title=\"$co{'age_string_age'}\">$co{'age_string_date'}</td>\n" .
                      format_author_html('td', \%co, 15, 5) .
                      "<td>" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
@@ -6474,15 +6657,13 @@ sub git_search_grep_body {
                }
                print "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                     " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
+                     $cgi->a({-href => href(action=>"diff", hash=>$co{'id'})}, "diff") .
                      " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
                print "</td>\n" .
                      "</tr>\n";
        }
-       if (defined $extra) {
+       if (defined $extra && $extra != "") {
                print "<tr>\n" .
                      "<td colspan=\"3\">$extra</td>\n" .
                      "</tr>\n";
@@ -6595,7 +6776,6 @@ sub git_summary {
        git_header_html();
        git_print_page_nav('summary','', $head);
 
-       print "<div class=\"title\">&nbsp;</div>\n";
        print "<table class=\"projects_list\">\n" .
              "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
         if ($owner and not $omit_owner) {
@@ -6649,21 +6829,21 @@ sub git_summary {
                git_print_header_div('shortlog');
                git_shortlog_body(\@commitlist, 0, 15, $refs,
                                  $#commitlist <=  15 ? undef :
-                                 $cgi->a({-href => href(action=>"shortlog")}, "..."));
+                                 $cgi->a({-href => href(action=>"shortlog")}, "show all commits"));
        }
 
        if (@taglist) {
                git_print_header_div('tags');
                git_tags_body(\@taglist, 0, 15,
                              $#taglist <=  15 ? undef :
-                             $cgi->a({-href => href(action=>"tags")}, "..."));
+                             $cgi->a({-href => href(action=>"tags")}, "show all tags"));
        }
 
        if (@headlist) {
                git_print_header_div('heads');
                git_heads_body(\@headlist, $head, 0, 15,
                               $#headlist <= 15 ? undef :
-                              $cgi->a({-href => href(action=>"heads")}, "..."));
+                              $cgi->a({-href => href(action=>"heads")}, "show all branches"));
        }
 
        if (%remotedata) {
@@ -6675,7 +6855,7 @@ sub git_summary {
                git_print_header_div('forks');
                git_project_list_body(\@forklist, 'age', 0, 15,
                                      $#forklist <= 15 ? undef :
-                                     $cgi->a({-href => href(action=>"forks")}, "..."),
+                                     $cgi->a({-href => href(action=>"forks")}, "show all forks"),
                                      'no_header');
        }
 
@@ -6707,13 +6887,13 @@ sub git_tag {
        }
        print "</table>\n\n" .
              "</div>\n";
-       print "<div class=\"page_body\">";
+       print "<main>";
        my $comment = $tag{'comment'};
        foreach my $line (@$comment) {
                chomp $line;
-               print esc_html($line, -nbsp=>1) . "<br/>\n";
+               print esc_html($line, -nbsp=>1) . "\n";
        }
-       print "</div>\n";
+       print "</main>\n";
        git_footer_html();
 }
 
@@ -6824,7 +7004,6 @@ sub git_blame_common {
                print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
        }
 
-       print qq!<div class="page_body">\n!;
        print qq!<div id="progress_info">... / ...</div>\n!
                if ($format eq 'incremental');
        print qq!<table id="blame_table" class="blame" width="100%">\n!.
@@ -7025,18 +7204,24 @@ sub git_blob_plain {
        my $type = shift;
        my $expires;
 
+        my $base = $hash_base || git_get_head_hash($project);
        if (!defined $hash) {
                if (defined $file_name) {
-                       my $base = $hash_base || git_get_head_hash($project);
                        $hash = git_get_hash_by_path($base, $file_name, "blob")
                                or die_error(404, "Cannot find file");
                } else {
                        die_error(400, "No file name defined");
                }
        } elsif ($hash =~ m/^$oid_regex$/) {
+                $file_name = git_get_path_by_hash($base, $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/) {
+          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");
@@ -7086,18 +7271,23 @@ sub git_blob_plain {
 sub git_blob {
        my $expires;
 
+        my $base = $hash_base || git_get_head_hash($project);
        if (!defined $hash) {
                if (defined $file_name) {
-                       my $base = $hash_base || git_get_head_hash($project);
                        $hash = git_get_hash_by_path($base, $file_name, "blob")
                                or die_error(404, "Cannot find file");
                } else {
                        die_error(400, "No file name defined");
                }
        } elsif ($hash =~ m/^$oid_regex$/) {
+                $file_name = git_get_path_by_hash($base, $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/) {
+          die_error(403, "Access denied ($file_name)");
+        }
 
        my $have_blame = gitweb_check_feature('blame');
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
@@ -7140,38 +7330,40 @@ sub git_blob {
                                $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
                                        "raw");
                }
-               git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-               git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+               git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base);
+                print "<div class=\"header\">\n";
+                print git_path_header($file_name, "blob", $hash_base, 1, esc_html($co{'title'}), $co{'id'}, $co{'id'});
+                print "</div>"
        } else {
-               print "<div class=\"page_nav\">\n" .
-                     "<br/><br/></div>\n" .
+               print "<nav>\n" .
+                     "</nav>\n" .
                      "<div class=\"title\">".esc_html($hash)."</div>\n";
        }
-       git_print_page_path($file_name, "blob", $hash_base);
-       print "<div class=\"page_body\">\n";
+        print "<nav class=\"formats_nav\">" . $formats_nav . "</nav>";
+       
        if ($mimetype =~ m!^image/!) {
-               print qq!<img class="blob" type="!.esc_attr($mimetype).qq!"!;
+                my $img_src = href(action=>"blob_plain", hash=>$hash,
+                          hash_base=>$hash_base, file_name=>$file_name);
+               print qq!<a href="$img_src"><img class="blob" type="!.esc_attr($mimetype).qq!"!;
                if ($file_name) {
                        print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
-               print qq! src="! .
-                     href(action=>"blob_plain", hash=>$hash,
-                          hash_base=>$hash_base, file_name=>$file_name) .
-                     qq!" />\n!;
+               print qq! src="! .$img_src . qq!" /></a>\n!;
        } else {
                my $nr;
+                print "<pre class=\"tree_lines\">";
                while (my $line = <$fd>) {
                        chomp $line;
                        $nr++;
-                       $line = untabify($line);
-                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                        #                      $line = untabify($line);
+                       printf qq!<span class="tree_line"><a id="l%i" href="%s#l%i" class="linenr">%4i</a><code>%s</code></span>\n!,
                               $nr, esc_attr(href(-replay => 1)), $nr, $nr,
                               $highlight ? sanitize($line) : esc_html($line, -nbsp=>1);
                }
+                print "</pre>";
        }
        close $fd
                or print "Reading blob failed.\n";
-       print "</div>";
        git_footer_html();
 }
 
@@ -7221,14 +7413,17 @@ sub git_tree {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav, $snapshot_links;
                }
-               git_print_page_nav('tree','', $hash_base, undef, undef,
-                                  join(' | ', @views_nav));
-               git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
+               git_print_page_nav('tree','', $hash_base, undef, undef);
+                if (scalar(@views_nav) > 0) {
+                  print "<nav class=\"formats_nav\">" . join(' | ', @views_nav) . "</nav>";
+                }
+                unless ($hash_base eq "HEAD") {
+                  git_print_header_div('commit', esc_html($co{'title'}), $ref, $hash_base);
+                }
        } else {
                undef $hash_base;
-               print "<div class=\"page_nav\">\n";
-               print "<br/><br/></div>\n";
-               print "<div class=\"title\">".esc_html($hash)."</div>\n";
+               print "<nav>\n";
+               print "</nav>".esc_html($hash)."</div>\n";
        }
        if (defined $file_name) {
                $basedir = $file_name;
@@ -7237,51 +7432,41 @@ sub git_tree {
                }
                git_print_page_path($file_name, 'tree', $hash_base);
        }
-       print "<div class=\"page_body\">\n";
        print "<table class=\"tree\">\n";
-       my $alternate = 1;
        # '..' (top directory) link if possible
        if (defined $hash_base &&
            defined $file_name && $file_name =~ m![^/]+$!) {
-               if ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-
+                print "<tr>\n";
                my $up = $file_name;
                $up =~ s!/?[^/]+$!!;
                undef $up unless $up;
                # based on git_print_tree_entry
-               print '<td class="mode">' . mode_str('040000') . "</td>\n";
+                print '<td class="mode">';
+                print '<svg width="14" height="14" '
+                . 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+                . '<use xlink:href="#parenticon" /></svg>';
+               print mode_str('040000') . "</td>\n";
                print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
                print '<td class="list">';
                print $cgi->a({-href => href(action=>"tree",
                                             hash_base=>$hash_base,
                                             file_name=>$up)},
-                             "..");
+                             "parent");
                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 ($alternate) {
-                       print "<tr class=\"dark\">\n";
-               } else {
-                       print "<tr class=\"light\">\n";
-               }
-               $alternate ^= 1;
-
-               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 ($t->{'name'} =~ /@treefilter_pattern/) {
+                  print "<tr>\n";
+                  git_print_tree_entry($t, $basedir, $hash_base, $have_blame);
+                  print "</tr>\n";
+                }
        }
-       print "</table>\n" .
-             "</div>";
+       print "</table>\n";
        git_footer_html();
 }
 
@@ -7511,7 +7696,7 @@ sub git_commit {
        # we need to prepare $formats_nav before any parameter munging
        my $formats_nav;
        if (!defined $parent) {
-               # --root commitdiff
+               # --root diff
                $formats_nav .= '(initial)';
        } elsif (@$parents == 1) {
                # single parent commit
@@ -7519,7 +7704,7 @@ sub git_commit {
                        '(parent: ' .
                        $cgi->a({-href => href(action=>"commit",
                                               hash=>$parent)},
-                               esc_html(substr($parent, 0, 7))) .
+                               esc_html(substr($parent, 0, $hash_char))) .
                        ')';
        } else {
                # merge commit
@@ -7528,7 +7713,7 @@ sub git_commit {
                        join(' ', map {
                                $cgi->a({-href => href(action=>"commit",
                                                       hash=>$_)},
-                                       esc_html(substr($_, 0, 7)));
+                                       esc_html(substr($_, 0, $hash_char)));
                        } @$parents ) .
                        ')';
        }
@@ -7560,60 +7745,78 @@ sub git_commit {
 
        git_header_html(undef, $expires);
        git_print_page_nav('commit', '',
-                          $hash, $co{'tree'}, $hash,
-                          $formats_nav);
+                          $hash, $co{'tree'}, $hash);
 
        if (defined $co{'parent'}) {
-               git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
+               git_print_header_div('diff', esc_html($co{'title'}), $ref, $hash);
        } else {
-               git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
+               git_print_header_div('tree', esc_html($co{'title'}), $ref, $co{'tree'}, $hash);
        }
        print "<div class=\"title_text\">\n" .
              "<table class=\"object_header\">\n";
        git_print_authorship_rows(\%co);
-       print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
+       print "<tr><th>commit</th><td class=\"sha1\">" . truncate_hash($co{'id'})
+          . "</td></tr>\n";
        print "<tr>" .
-             "<td>tree</td>" .
+             "<th>tree</th>" .
              "<td class=\"sha1\">" .
-             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
-                      class => "list"}, $co{'tree'}) .
-             "</td>" .
-             "<td class=\"link\">" .
-             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
+             truncate_hash($co{'tree'}, "tree_hash", href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)) .
+             " (" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
                      "tree");
        my $snapshot_links = format_snapshot_links($hash);
        if (defined $snapshot_links) {
                print " | " . $snapshot_links;
        }
-       print "</td>" .
+       print ")</td>" .
              "</tr>\n";
 
        foreach my $par (@$parents) {
                print "<tr>" .
-                     "<td>parent</td>" .
+                     "<th>parent</th>" .
                      "<td class=\"sha1\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$par),
-                              class => "list"}, $par) .
-                     "</td>" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
-                     " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
-                     "</td>" .
+                      truncate_hash($par, "parent_hash", href(action=>"commit", hash=>$par)) .
+                     " (" .
+                     $cgi->a({-href => href(action=>"diff", hash=>$hash, hash_parent=>$par)}, "diff") .
+                     ")</td>" .
                      "</tr>\n";
        }
        print "</table>".
              "</div>\n";
-
-       print "<div class=\"page_body\">\n";
-       git_print_log($co{'comment'});
-       print "</div>\n";
+        print "<nav class=\"formats_nav\">$formats_nav</nav>";
 
        git_difftree_body(\@difftree, $hash, @$parents);
 
        git_footer_html();
 }
 
+sub truncate_hash {
+  # Truncate an SHA1 hash into HTML code tags & add link
+  my ($value, $elem_id, $href) = @_;
+  my $truncated = (defined $elem_id ? "<span id=\"$elem_id\">" : "<span>");
+  my $span_content = "<code>" . esc_html(substr($value, 0, $hash_char))
+  . "</code><code class=\"ellipsis\">...</code><code class=\"spoiler\">"
+  . esc_html(substr($value, $hash_char + 1))
+  . "</code>";
+  if (defined $href) {
+    $truncated .= $cgi->a({-href => $href, -class => "list"}, $span_content);
+  } else {
+    $truncated .= $span_content;
+  }
+  $truncated .= (defined $elem_id ? copy_svg($elem_id) : "") . "</span>";
+  return $truncated;
+}
+
+sub copy_svg {
+  # Return an HTML string containing an SVG button to copy the content of 
+  # an element to the clipboard.
+  my ($elem_id) = @_;
+  return "<svg width=\"14\" height=\"14\" xmlns=\"http://www.w3.org/2000/svg\" "
+    . "xmlns:xlink=\"http://www.w3.org/1999/xlink\" class=\"copyicon\" "
+    . "onclick=\"clipboardCopy('$elem_id');\">"
+    . "<title>copy hash to clipboard</title>"
+    . "<use xlink:href=\"#copyicon\" /></svg>";
+}
+
 sub git_object {
        # object is defined by:
        # - hash or hash_base alone
@@ -7745,10 +7948,9 @@ sub git_blobdiff {
                $formats_nav .= diff_style_nav($diff_style);
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
-                       git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+                       git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base);
                        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
                } else {
-                       print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
                        print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
                }
                if (defined $file_name) {
@@ -7772,13 +7974,10 @@ sub git_blobdiff {
 
        # patch
        if ($format eq 'html') {
-               print "<div class=\"page_body\">\n";
-
                git_patchset_body($fd, $diff_style,
                                  [ \%diffinfo ], $hash_base, $hash_parent_base);
                close $fd;
 
-               print "</div>\n"; # class="page_body"
                git_footer_html();
 
        } else {
@@ -7821,7 +8020,7 @@ sub diff_style_nav {
                } @styles;
 }
 
-sub git_commitdiff {
+sub git_diff {
        my %params = @_;
        my $format = $params{-format} || 'html';
        my $diff_style = $input_params{'diff_style'} || 'inline';
@@ -7835,7 +8034,7 @@ sub git_commitdiff {
        my %co = parse_commit($hash)
            or die_error(404, "Unknown commit object");
 
-       # choose format for commitdiff for merge
+       # choose format for diff for merge
        if (! defined $hash_parent && @{$co{'parents'}} > 1) {
                $hash_parent = '--cc';
        }
@@ -7843,7 +8042,7 @@ sub git_commitdiff {
        my $formats_nav;
        if ($format eq 'html') {
                $formats_nav =
-                       $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
+                       $cgi->a({-href => href(action=>"diff_plain", -replay=>1)},
                                "raw");
                if ($patch_max && @{$co{'parents'}} <= 1) {
                        $formats_nav .= " | " .
@@ -7854,7 +8053,7 @@ sub git_commitdiff {
 
                if (defined $hash_parent &&
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
-                       # commitdiff with two commits given
+                       # diff with two commits given
                        my $hash_parent_short = $hash_parent;
                        if ($hash_parent =~ m/^$oid_regex$/) {
                                $hash_parent_short = substr($hash_parent, 0, 7);
@@ -7873,7 +8072,7 @@ sub git_commitdiff {
                                        esc_html($hash_parent_short)) .
                                ')';
                } elsif (!$co{'parent'}) {
-                       # --root commitdiff
+                       # --root diff
                        $formats_nav .= ' (initial)';
                } elsif (scalar @{$co{'parents'}} == 1) {
                        # single parent commit
@@ -7914,7 +8113,7 @@ sub git_commitdiff {
                        @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
        }
 
-       # read commitdiff
+       # read diff
        my $fd;
        my @difftree;
        if ($format eq 'html') {
@@ -7960,7 +8159,7 @@ sub git_commitdiff {
                        '--encoding=utf8', '--stdout', @commit_spec
                        or die_error(500, "Open git-format-patch failed");
        } else {
-               die_error(400, "Unknown commitdiff format");
+               die_error(400, "Unknown diff format");
        }
 
        # non-textual hash id's can be cached
@@ -7975,14 +8174,13 @@ sub git_commitdiff {
                my $ref = format_ref_marker($refs, $co{'id'});
 
                git_header_html(undef, $expires);
-               git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
-               git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
+               git_print_page_nav('diff','', $hash,$co{'tree'},$hash);
+               git_print_header_div('commit', esc_html($co{'title'}), $ref, $hash);
                print "<div class=\"title_text\">\n" .
                      "<table class=\"object_header\">\n";
                git_print_authorship_rows(\%co);
                print "</table>".
                      "</div>\n";
-               print "<div class=\"page_body\">\n";
                if (@{$co{'comment'}} > 1) {
                        print "<div class=\"log\">\n";
                        git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
@@ -8027,13 +8225,12 @@ sub git_commitdiff {
                        $hash_parent eq '-c' || $hash_parent eq '--cc';
                git_difftree_body(\@difftree, $hash,
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
-               print "<br/>\n";
+                print "<nav class=\"formats_nav\">$formats_nav</nav>";
 
                git_patchset_body($fd, $diff_style,
                                  \@difftree, $hash,
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                close $fd;
-               print "</div>\n"; # class="page_body"
                git_footer_html();
 
        } elsif ($format eq 'plain') {
@@ -8049,17 +8246,17 @@ sub git_commitdiff {
        }
 }
 
-sub git_commitdiff_plain {
-       git_commitdiff(-format => 'plain');
+sub git_diff_plain {
+       git_diff(-format => 'plain');
 }
 
 # format-patch-style patches
 sub git_patch {
-       git_commitdiff(-format => 'patch', -single => 1);
+       git_diff(-format => 'patch', -single => 1);
 }
 
 sub git_patches {
-       git_commitdiff(-format => 'patch');
+       git_diff(-format => 'patch');
 }
 
 sub git_history {
@@ -8223,9 +8420,8 @@ sub git_feed {
        if (defined $descr) {
                $descr = esc_html($descr);
        } else {
-               $descr = "$project " .
-                        ($format eq 'rss' ? 'RSS' : 'Atom') .
-                        " feed";
+               $descr = ($format eq 'rss' ? 'RSS' : 'Atom') .
+                        " feed of $project";
        }
        my $owner = git_get_project_owner($project);
        $owner = esc_html($owner);
@@ -8315,7 +8511,7 @@ sub git_feed {
                        or next;
 
                # print element (entry, item)
-               my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
+               my $co_url = href(-full=>1, action=>"diff", hash=>$commit);
                if ($format eq 'rss') {
                        print "<item>\n" .
                              "<title>" . esc_html($co{'title'}) . "</title>\n" .