]> Lady’s Gitweb - Gitweb/blobdiff - gitweb.perl
Fix spelling errors in code comments
[Gitweb] / gitweb.perl
index 31713f8276a9c1490536ea66cd482814c6ad4e97e0f93504889e0a0f52983952..87c8dc000518f3bccda21db160d175e305014f1da82383fe2930e2be3a5042c4 100755 (executable)
@@ -10,6 +10,8 @@
 use 5.008;
 use strict;
 use warnings;
 use 5.008;
 use strict;
 use warnings;
+# handle ACL in file access tests
+use filetest 'access';
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser set_message);
@@ -18,8 +20,14 @@ use Fcntl ':mode';
 use File::Find qw();
 use File::Basename qw(basename);
 use Time::HiRes qw(gettimeofday tv_interval);
 use File::Find qw();
 use File::Basename qw(basename);
 use Time::HiRes qw(gettimeofday tv_interval);
+use Digest::MD5 qw(md5_hex);
+
 binmode STDOUT, ':utf8';
 
 binmode STDOUT, ':utf8';
 
+if (!defined($CGI::VERSION) || $CGI::VERSION < 4.08) {
+       eval 'sub CGI::multi_param { CGI::param(@_) }'
+}
+
 our $t0 = [ gettimeofday() ];
 our $number_of_git_cmds = 0;
 
 our $t0 = [ gettimeofday() ];
 our $number_of_git_cmds = 0;
 
@@ -85,6 +93,9 @@ our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
 # string of the home link on top of all pages
 our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 
 # string of the home link on top of all pages
 our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 
+# extra breadcrumbs preceding the home link
+our @extra_breadcrumbs = ();
+
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
 our $site_name = "++GITWEB_SITENAME++"
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
 our $site_name = "++GITWEB_SITENAME++"
@@ -482,7 +493,6 @@ our %feature = (
        # Currently available providers are gravatar and picon.
        # If an unknown provider is specified, the feature is disabled.
 
        # Currently available providers are gravatar and picon.
        # If an unknown provider is specified, the feature is disabled.
 
-       # Gravatar depends on Digest::MD5.
        # Picon currently relies on the indiana.edu database.
 
        # To enable system wide have in $GITWEB_CONFIG
        # Picon currently relies on the indiana.edu database.
 
        # To enable system wide have in $GITWEB_CONFIG
@@ -546,6 +556,20 @@ our %feature = (
                'sub' => sub { feature_bool('remote_heads', @_) },
                'override' => 0,
                'default' => [0]},
                'sub' => sub { feature_bool('remote_heads', @_) },
                'override' => 0,
                'default' => [0]},
+
+       # Enable showing branches under other refs in addition to heads
+
+       # To set system wide extra branch refs have in $GITWEB_CONFIG
+       # $feature{'extra-branch-refs'}{'default'} = ['dirs', 'of', 'choice'];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'extra-branch-refs'}{'override'} = 1;
+       # and in project config gitweb.extrabranchrefs = dirs of choice
+       # Every directory is separated with whitespace.
+
+       'extra-branch-refs' => {
+               'sub' => \&feature_extra_branch_refs,
+               'override' => 0,
+               'default' => []},
 );
 
 sub gitweb_get_feature {
 );
 
 sub gitweb_get_feature {
@@ -624,6 +648,21 @@ sub feature_avatar {
        return @val ? @val : @_;
 }
 
        return @val ? @val : @_;
 }
 
+sub feature_extra_branch_refs {
+       my (@branch_refs) = @_;
+       my $values = git_get_project_config('extrabranchrefs');
+
+       if ($values) {
+               $values = config_to_multi ($values);
+               @branch_refs = ();
+               foreach my $value (@{$values}) {
+                       push @branch_refs, split /\s+/, $value;
+               }
+       }
+
+       return @branch_refs;
+}
+
 # checking HEAD file with -e is fragile if the repository was
 # initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
 # and then pruned.
 # checking HEAD file with -e is fragile if the repository was
 # initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
 # and then pruned.
@@ -654,6 +693,18 @@ sub filter_snapshot_fmts {
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
+sub filter_and_validate_refs {
+       my @refs = @_;
+       my %unique_refs = ();
+
+       foreach my $ref (@refs) {
+               die_error(500, "Invalid ref '$ref' in 'extra-branch-refs' feature") unless (is_valid_ref_format($ref));
+               # 'heads' are added implicitly in get_branch_refs().
+               $unique_refs{$ref} = 1 if ($ref ne 'heads');
+       }
+       return sort keys %unique_refs;
+}
+
 # If it is set to code reference, it is code that it is to be run once per
 # request, allowing updating configurations that change with each request,
 # while running other code in config file only once.
 # If it is set to code reference, it is code that it is to be run once per
 # request, allowing updating configurations that change with each request,
 # while running other code in config file only once.
@@ -738,6 +789,38 @@ sub check_loadavg {
 # ======================================================================
 # input validation and dispatch
 
 # ======================================================================
 # input validation and dispatch
 
+# Various hash size-related values.
+my $sha1_len = 40;
+my $sha256_extra_len = 24;
+my $sha256_len = $sha1_len + $sha256_extra_len;
+
+# A regex matching $len hex characters. $len may be a range (e.g. 7,64).
+sub oid_nlen_regex {
+       my $len = shift;
+       my $hchr = qr/[0-9a-fA-F]/;
+       return qr/(?:(?:$hchr){$len})/;
+}
+
+# A regex matching two sets of $nlen hex characters, prefixed by the literal
+# string $prefix and with the literal string $infix between them.
+sub oid_nlen_prefix_infix_regex {
+       my $nlen = shift;
+       my $prefix = shift;
+       my $infix = shift;
+
+       my $rx = oid_nlen_regex($nlen);
+
+       return qr/^\Q$prefix\E$rx\Q$infix\E$rx$/;
+}
+
+# A regex matching a valid object ID.
+our $oid_regex;
+{
+       my $x = oid_nlen_regex($sha1_len);
+       my $y = oid_nlen_regex($sha256_extra_len);
+       $oid_regex = qr/(?:$x(?:$y)?)/;
+}
+
 # input parameters can be collected from a variety of sources (presently, CGI
 # and PATH_INFO), so we define an %input_params hash that collects them all
 # together during validation: this allows subsequent uses (e.g. href()) to be
 # input parameters can be collected from a variety of sources (presently, CGI
 # and PATH_INFO), so we define an %input_params hash that collects them all
 # together during validation: this allows subsequent uses (e.g. href()) to be
@@ -828,7 +911,7 @@ sub evaluate_query_params {
 
        while (my ($name, $symbol) = each %cgi_param_mapping) {
                if ($symbol eq 'opt') {
 
        while (my ($name, $symbol) = each %cgi_param_mapping) {
                if ($symbol eq 'opt') {
-                       $input_params{$name} = [ map { decode_utf8($_) } $cgi->param($symbol) ];
+                       $input_params{$name} = [ map { decode_utf8($_) } $cgi->multi_param($symbol) ];
                } else {
                        $input_params{$name} = decode_utf8($cgi->param($symbol));
                }
                } else {
                        $input_params{$name} = decode_utf8($cgi->param($symbol));
                }
@@ -992,7 +1075,7 @@ our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_bas
 sub evaluate_and_validate_params {
        our $action = $input_params{'action'};
        if (defined $action) {
 sub evaluate_and_validate_params {
        our $action = $input_params{'action'};
        if (defined $action) {
-               if (!validate_action($action)) {
+               if (!is_valid_action($action)) {
                        die_error(400, "Invalid action parameter");
                }
        }
                        die_error(400, "Invalid action parameter");
                }
        }
@@ -1000,7 +1083,7 @@ sub evaluate_and_validate_params {
        # parameters which are pathnames
        our $project = $input_params{'project'};
        if (defined $project) {
        # parameters which are pathnames
        our $project = $input_params{'project'};
        if (defined $project) {
-               if (!validate_project($project)) {
+               if (!is_valid_project($project)) {
                        undef $project;
                        die_error(404, "No such project");
                }
                        undef $project;
                        die_error(404, "No such project");
                }
@@ -1008,21 +1091,21 @@ sub evaluate_and_validate_params {
 
        our $project_filter = $input_params{'project_filter'};
        if (defined $project_filter) {
 
        our $project_filter = $input_params{'project_filter'};
        if (defined $project_filter) {
-               if (!validate_pathname($project_filter)) {
+               if (!is_valid_pathname($project_filter)) {
                        die_error(404, "Invalid project_filter parameter");
                }
        }
 
        our $file_name = $input_params{'file_name'};
        if (defined $file_name) {
                        die_error(404, "Invalid project_filter parameter");
                }
        }
 
        our $file_name = $input_params{'file_name'};
        if (defined $file_name) {
-               if (!validate_pathname($file_name)) {
+               if (!is_valid_pathname($file_name)) {
                        die_error(400, "Invalid file parameter");
                }
        }
 
        our $file_parent = $input_params{'file_parent'};
        if (defined $file_parent) {
                        die_error(400, "Invalid file parameter");
                }
        }
 
        our $file_parent = $input_params{'file_parent'};
        if (defined $file_parent) {
-               if (!validate_pathname($file_parent)) {
+               if (!is_valid_pathname($file_parent)) {
                        die_error(400, "Invalid file parent parameter");
                }
        }
                        die_error(400, "Invalid file parent parameter");
                }
        }
@@ -1030,21 +1113,21 @@ sub evaluate_and_validate_params {
        # parameters which are refnames
        our $hash = $input_params{'hash'};
        if (defined $hash) {
        # parameters which are refnames
        our $hash = $input_params{'hash'};
        if (defined $hash) {
-               if (!validate_refname($hash)) {
+               if (!is_valid_refname($hash)) {
                        die_error(400, "Invalid hash parameter");
                }
        }
 
        our $hash_parent = $input_params{'hash_parent'};
        if (defined $hash_parent) {
                        die_error(400, "Invalid hash parameter");
                }
        }
 
        our $hash_parent = $input_params{'hash_parent'};
        if (defined $hash_parent) {
-               if (!validate_refname($hash_parent)) {
+               if (!is_valid_refname($hash_parent)) {
                        die_error(400, "Invalid hash parent parameter");
                }
        }
 
        our $hash_base = $input_params{'hash_base'};
        if (defined $hash_base) {
                        die_error(400, "Invalid hash parent parameter");
                }
        }
 
        our $hash_base = $input_params{'hash_base'};
        if (defined $hash_base) {
-               if (!validate_refname($hash_base)) {
+               if (!is_valid_refname($hash_base)) {
                        die_error(400, "Invalid hash base parameter");
                }
        }
                        die_error(400, "Invalid hash base parameter");
                }
        }
@@ -1064,7 +1147,7 @@ sub evaluate_and_validate_params {
 
        our $hash_parent_base = $input_params{'hash_parent_base'};
        if (defined $hash_parent_base) {
 
        our $hash_parent_base = $input_params{'hash_parent_base'};
        if (defined $hash_parent_base) {
-               if (!validate_refname($hash_parent_base)) {
+               if (!is_valid_refname($hash_parent_base)) {
                        die_error(400, "Invalid hash parent base parameter");
                }
        }
                        die_error(400, "Invalid hash parent base parameter");
                }
        }
@@ -1087,7 +1170,7 @@ sub evaluate_and_validate_params {
        our $search_use_regexp = $input_params{'search_use_regexp'};
 
        our $searchtext = $input_params{'searchtext'};
        our $search_use_regexp = $input_params{'search_use_regexp'};
 
        our $searchtext = $input_params{'searchtext'};
-       our $search_regexp;
+       our $search_regexp = undef;
        if (defined $searchtext) {
                if (length($searchtext) < 2) {
                        die_error(403, "At least two characters are required for search parameter");
        if (defined $searchtext) {
                if (length($searchtext) < 2) {
                        die_error(403, "At least two characters are required for search parameter");
@@ -1111,24 +1194,21 @@ sub evaluate_git_dir {
        our $git_dir = "$projectroot/$project" if $project;
 }
 
        our $git_dir = "$projectroot/$project" if $project;
 }
 
-our (@snapshot_fmts, $git_avatar);
+our (@snapshot_fmts, $git_avatar, @extra_branch_refs);
 sub configure_gitweb_features {
        # list of supported snapshot formats
        our @snapshot_fmts = gitweb_get_feature('snapshot');
        @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
 
 sub configure_gitweb_features {
        # list of supported snapshot formats
        our @snapshot_fmts = gitweb_get_feature('snapshot');
        @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
 
-       # check that the avatar feature is set to a known provider name,
-       # and for each provider check if the dependencies are satisfied.
-       # if the provider name is invalid or the dependencies are not met,
-       # reset $git_avatar to the empty string.
        our ($git_avatar) = gitweb_get_feature('avatar');
        our ($git_avatar) = gitweb_get_feature('avatar');
-       if ($git_avatar eq 'gravatar') {
-               $git_avatar = '' unless (eval { require Digest::MD5; 1; });
-       } elsif ($git_avatar eq 'picon') {
-               # no dependencies
-       } else {
-               $git_avatar = '';
-       }
+       $git_avatar = '' unless $git_avatar =~ /^(?:gravatar|picon)$/s;
+
+       our @extra_branch_refs = gitweb_get_feature('extra-branch-refs');
+       @extra_branch_refs = filter_and_validate_refs (@extra_branch_refs);
+}
+
+sub get_branch_refs {
+       return ('heads', @extra_branch_refs);
 }
 
 # custom error handler: 'die <message>' is Internal Server Error
 }
 
 # custom error handler: 'die <message>' is Internal Server Error
@@ -1416,28 +1496,31 @@ sub href {
 ## ======================================================================
 ## validation, quoting/unquoting and escaping
 
 ## ======================================================================
 ## validation, quoting/unquoting and escaping
 
-sub validate_action {
-       my $input = shift || return undef;
+sub is_valid_action {
+       my $input = shift;
        return undef unless exists $actions{$input};
        return undef unless exists $actions{$input};
-       return $input;
+       return 1;
 }
 
 }
 
-sub validate_project {
-       my $input = shift || return undef;
-       if (!validate_pathname($input) ||
+sub is_valid_project {
+       my $input = shift;
+
+       return unless defined $input;
+       if (!is_valid_pathname($input) ||
                !(-d "$projectroot/$input") ||
                !check_export_ok("$projectroot/$input") ||
                ($strict_export && !project_in_list($input))) {
                return undef;
        } else {
                !(-d "$projectroot/$input") ||
                !check_export_ok("$projectroot/$input") ||
                ($strict_export && !project_in_list($input))) {
                return undef;
        } else {
-               return $input;
+               return 1;
        }
 }
 
        }
 }
 
-sub validate_pathname {
-       my $input = shift || return undef;
+sub is_valid_pathname {
+       my $input = shift;
 
 
-       # no '.' or '..' as elements of path, i.e. no '.' nor '..'
+       return undef unless defined $input;
+       # no '.' or '..' as elements of path, i.e. no '.' or '..'
        # at the beginning, at the end, and between slashes.
        # also this catches doubled slashes
        if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
        # at the beginning, at the end, and between slashes.
        # also this catches doubled slashes
        if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
@@ -1447,24 +1530,33 @@ sub validate_pathname {
        if ($input =~ m!\0!) {
                return undef;
        }
        if ($input =~ m!\0!) {
                return undef;
        }
-       return $input;
+       return 1;
 }
 
 }
 
-sub validate_refname {
-       my $input = shift || return undef;
+sub is_valid_ref_format {
+       my $input = shift;
 
 
-       # textual hashes are O.K.
-       if ($input =~ m/^[0-9a-fA-F]{40}$/) {
-               return $input;
-       }
-       # it must be correct pathname
-       $input = validate_pathname($input)
-               or return undef;
+       return undef unless defined $input;
        # restrictions on ref name according to git-check-ref-format
        if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
                return undef;
        }
        # restrictions on ref name according to git-check-ref-format
        if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
                return undef;
        }
-       return $input;
+       return 1;
+}
+
+sub is_valid_refname {
+       my $input = shift;
+
+       return undef unless defined $input;
+       # textual hashes are O.K.
+       if ($input =~ m/^$oid_regex$/) {
+               return 1;
+       }
+       # it must be correct pathname
+       is_valid_pathname($input) or return undef;
+       # check git-check-ref-format restrictions
+       is_valid_ref_format($input) or return undef;
+       return 1;
 }
 
 # decode sequences of octets in utf8 into Perl's internal form,
 }
 
 # decode sequences of octets in utf8 into Perl's internal form,
@@ -1550,7 +1642,7 @@ sub esc_path {
        return $str;
 }
 
        return $str;
 }
 
-# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0)
+# Sanitize for use in XHTML + application/xml+xhtml (valid XML 1.0)
 sub sanitize {
        my $str = shift;
 
 sub sanitize {
        my $str = shift;
 
@@ -1566,15 +1658,15 @@ sub quot_cec {
        my $cntrl = shift;
        my %opts = @_;
        my %es = ( # character escape codes, aka escape sequences
        my $cntrl = shift;
        my %opts = @_;
        my %es = ( # character escape codes, aka escape sequences
-               "\t" => '\t',   # tab            (HT)
-               "\n" => '\n',   # line feed      (LF)
-               "\r" => '\r',   # carrige return (CR)
-               "\f" => '\f',   # form feed      (FF)
-               "\b" => '\b',   # backspace      (BS)
-               "\a" => '\a',   # alarm (bell)   (BEL)
-               "\e" => '\e',   # escape         (ESC)
-               "\013" => '\v', # vertical tab   (VT)
-               "\000" => '\0', # nul character  (NUL)
+               "\t" => '\t',   # tab             (HT)
+               "\n" => '\n',   # line feed       (LF)
+               "\r" => '\r',   # carriage return (CR)
+               "\f" => '\f',   # form feed       (FF)
+               "\b" => '\b',   # backspace       (BS)
+               "\a" => '\a',   # alarm (bell)    (BEL)
+               "\e" => '\e',   # escape          (ESC)
+               "\013" => '\v', # vertical tab    (VT)
+               "\000" => '\0', # nul character   (NUL)
        );
        my $chr = ( (exists $es{$cntrl})
                    ? $es{$cntrl}
        );
        my $chr = ( (exists $es{$cntrl})
                    ? $es{$cntrl}
@@ -1969,11 +2061,28 @@ sub file_type_long {
 sub format_log_line_html {
        my $line = shift;
 
 sub format_log_line_html {
        my $line = shift;
 
+       # Potentially abbreviated OID.
+       my $regex = oid_nlen_regex("7,64");
+
        $line = esc_html($line, -nbsp=>1);
        $line = esc_html($line, -nbsp=>1);
-       $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+       $line =~ s{
+        \b
+        (
+            # The output of "git describe", e.g. v2.10.0-297-gf6727b0
+            # or hadoop-20160921-113441-20-g094fb7d
+            (?<!-) # see strbuf_check_tag_ref(). Tags can't start with -
+            [A-Za-z0-9.-]+
+            (?!\.) # refs can't end with ".", see check_refname_format()
+            -g$regex
+            |
+            # Just a normal looking Git SHA1
+           $regex
+        )
+        \b
+    }{
                $cgi->a({-href => href(action=>"object", hash=>$1),
                                        -class => "text"}, $1);
                $cgi->a({-href => href(action=>"object", hash=>$1),
                                        -class => "text"}, $1);
-       }eg;
+       }egx;
 
        return $line;
 }
 
        return $line;
 }
@@ -2024,7 +2133,7 @@ sub format_ref_marker {
                                -href => href(
                                        action=>$dest_action,
                                        hash=>$dest
                                -href => href(
                                        action=>$dest_action,
                                        hash=>$dest
-                               )}, $name);
+                               )}, esc_html($name));
 
                        $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
 
                        $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
                                $link . "</span>";
@@ -2085,7 +2194,7 @@ sub gravatar_url {
        my $size = shift;
        $avatar_cache{$email} ||=
                "//www.gravatar.com/avatar/" .
        my $size = shift;
        $avatar_cache{$email} ||=
                "//www.gravatar.com/avatar/" .
-                       Digest::MD5::md5_hex($email) . "?s=";
+                       md5_hex($email) . "?s=";
        return $avatar_cache{$email} . $size;
 }
 
        return $avatar_cache{$email} . $size;
 }
 
@@ -2213,7 +2322,8 @@ sub format_extended_diff_header_line {
                         ')</span>';
        }
        # match <hash>
                         ')</span>';
        }
        # match <hash>
-       if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+       if ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", ",") |
+           $line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", ",")) {
                # can match only for combined diff
                $line = 'index ';
                for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
                # can match only for combined diff
                $line = 'index ';
                for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
@@ -2235,7 +2345,8 @@ sub format_extended_diff_header_line {
                        $line .= '0' x 7;
                }
 
                        $line .= '0' x 7;
                }
 
-       } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+       } elsif ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", "..") |
+                $line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", "..")) {
                # can match only for ordinary diff
                my ($from_link, $to_link);
                if ($from->{'href'}) {
                # can match only for ordinary diff
                my ($from_link, $to_link);
                if ($from->{'href'}) {
@@ -2513,6 +2624,7 @@ sub format_snapshot_links {
 sub get_feed_info {
        my $format = shift || 'Atom';
        my %res = (action => lc($format));
 sub get_feed_info {
        my $format = shift || 'Atom';
        my %res = (action => lc($format));
+       my $matched_ref = 0;
 
        # feed links are possible only for project views
        return unless (defined $project);
 
        # feed links are possible only for project views
        return unless (defined $project);
@@ -2520,12 +2632,17 @@ sub get_feed_info {
        # or don't have specific feed yet (so they should use generic)
        return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
 
        # or don't have specific feed yet (so they should use generic)
        return if (!$action || $action =~ /^(?:tags|heads|forks|tag|search)$/x);
 
-       my $branch;
-       # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
-       # from tag links; this also makes possible to detect branch links
-       if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
-           (defined $hash      && $hash      =~ m!^refs/heads/(.*)$!)) {
-               $branch = $1;
+       my $branch = undef;
+       # branches refs uses 'refs/' + $get_branch_refs()[x] + '/' prefix
+       # (fullname) to differentiate from tag links; this also makes
+       # possible to detect branch links
+       for my $ref (get_branch_refs()) {
+               if ((defined $hash_base && $hash_base =~ m!^refs/\Q$ref\E/(.*)$!) ||
+                   (defined $hash      && $hash      =~ m!^refs/\Q$ref\E/(.*)$!)) {
+                       $branch = $1;
+                       $matched_ref = $ref;
+                       last;
+               }
        }
        # find log type for feed description (title)
        my $type = 'log';
        }
        # find log type for feed description (title)
        my $type = 'log';
@@ -2538,7 +2655,7 @@ sub get_feed_info {
        }
 
        $res{-title} = $type;
        }
 
        $res{-title} = $type;
-       $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
+       $res{'hash'} = (defined $branch ? "refs/$matched_ref/$branch" : undef);
        $res{'file_name'} = $file_name;
 
        return %res;
        $res{'file_name'} = $file_name;
 
        return %res;
@@ -2755,7 +2872,7 @@ sub git_get_hash_by_path {
        }
 
        #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
        }
 
        #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
+       $line =~ m/^([0-9]+) (.+) ($oid_regex)\t/;
        if (defined $type && $type ne $2) {
                # type doesn't match
                return undef;
        if (defined $type && $type ne $2) {
                # type doesn't match
                return undef;
@@ -2985,6 +3102,8 @@ sub git_get_projects_list {
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
                                return unless (-d $_);
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
                                return unless (-d $_);
+                               # need search permission
+                               return unless (-x $_);
                                # 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) {
                                # 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) {
@@ -3039,7 +3158,7 @@ sub git_get_projects_list {
        return @list;
 }
 
        return @list;
 }
 
-# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
+# written with help of Tree::Trie module (Perl Artistic License, GPL compatible)
 # as side effects it sets 'forks' field to list of forks for forked projects
 sub filter_forks_from_projects_list {
        my $projects = shift;
 # as side effects it sets 'forks' field to list of forks for forked projects
 sub filter_forks_from_projects_list {
        my $projects = shift;
@@ -3191,7 +3310,7 @@ sub git_get_last_activity {
             '--format=%(committer)',
             '--sort=-committerdate',
             '--count=1',
             '--format=%(committer)',
             '--sort=-committerdate',
             '--count=1',
-            'refs/heads') or return;
+            map { "refs/$_" } get_branch_refs ()) or return;
        my $most_recent = <$fd>;
        close $fd or return;
        if (defined $most_recent &&
        my $most_recent = <$fd>;
        close $fd or return;
        if (defined $most_recent &&
@@ -3252,7 +3371,7 @@ sub git_get_references {
 
        while (my $line = <$fd>) {
                chomp $line;
 
        while (my $line = <$fd>) {
                chomp $line;
-               if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
+               if ($line =~ m!^($oid_regex)\srefs/($type.*)$!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@ -3326,7 +3445,7 @@ sub parse_tag {
        $tag{'id'} = $tag_id;
        while (my $line = <$fd>) {
                chomp $line;
        $tag{'id'} = $tag_id;
        while (my $line = <$fd>) {
                chomp $line;
-               if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
+               if ($line =~ m/^object ($oid_regex)$/) {
                        $tag{'object'} = $1;
                } elsif ($line =~ m/^type (.+)$/) {
                        $tag{'type'} = $1;
                        $tag{'object'} = $1;
                } elsif ($line =~ m/^type (.+)$/) {
                        $tag{'type'} = $1;
@@ -3370,15 +3489,15 @@ sub parse_commit_text {
        }
 
        my $header = shift @commit_lines;
        }
 
        my $header = shift @commit_lines;
-       if ($header !~ m/^[0-9a-fA-F]{40}/) {
+       if ($header !~ m/^$oid_regex/) {
                return;
        }
        ($co{'id'}, my @parents) = split ' ', $header;
        while (my $line = shift @commit_lines) {
                last if $line eq "\n";
                return;
        }
        ($co{'id'}, my @parents) = split ' ', $header;
        while (my $line = shift @commit_lines) {
                last if $line eq "\n";
-               if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
+               if ($line =~ m/^tree ($oid_regex)$/) {
                        $co{'tree'} = $1;
                        $co{'tree'} = $1;
-               } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
+               } elsif ((!defined $withparents) && ($line =~ m/^parent ($oid_regex)$/)) {
                        push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
                        $co{'author'} = to_utf8($1);
                        push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
                        $co{'author'} = to_utf8($1);
@@ -3510,7 +3629,7 @@ sub parse_difftree_raw_line {
 
        # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M   ls-files.c'
        # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M   rev-tree.c'
 
        # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M   ls-files.c'
        # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M   rev-tree.c'
-       if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
+       if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ($oid_regex) ($oid_regex) (.)([0-9]{0,3})\t(.*)$/) {
                $res{'from_mode'} = $1;
                $res{'to_mode'} = $2;
                $res{'from_id'} = $3;
                $res{'from_mode'} = $1;
                $res{'to_mode'} = $2;
                $res{'from_id'} = $3;
@@ -3525,7 +3644,7 @@ sub parse_difftree_raw_line {
        }
        # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
        # combined diff (for merge commit)
        }
        # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
        # combined diff (for merge commit)
-       elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
+       elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:$oid_regex )+)([a-zA-Z]+)\t(.*)$//) {
                $res{'nparents'}  = length($1);
                $res{'from_mode'} = [ split(' ', $2) ];
                $res{'to_mode'} = pop @{$res{'from_mode'}};
                $res{'nparents'}  = length($1);
                $res{'from_mode'} = [ split(' ', $2) ];
                $res{'to_mode'} = pop @{$res{'from_mode'}};
@@ -3535,7 +3654,7 @@ sub parse_difftree_raw_line {
                $res{'to_file'} = unquote($5);
        }
        # 'c512b523472485aef4fff9e57b229d9d243c967f'
                $res{'to_file'} = unquote($5);
        }
        # 'c512b523472485aef4fff9e57b229d9d243c967f'
-       elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
+       elsif ($line =~ m/^($oid_regex)$/) {
                $res{'commit'} = $1;
        }
 
                $res{'commit'} = $1;
        }
 
@@ -3563,7 +3682,7 @@ sub parse_ls_tree_line {
 
        if ($opts{'-l'}) {
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717  panic.c'
 
        if ($opts{'-l'}) {
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717  panic.c'
-               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
+               $line =~ m/^([0-9]+) (.+) ($oid_regex) +(-|[0-9]+)\t(.+)$/s;
 
                $res{'mode'} = $1;
                $res{'type'} = $2;
 
                $res{'mode'} = $1;
                $res{'type'} = $2;
@@ -3576,7 +3695,7 @@ sub parse_ls_tree_line {
                }
        } else {
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
                }
        } else {
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+               $line =~ m/^([0-9]+) (.+) ($oid_regex)\t(.+)$/s;
 
                $res{'mode'} = $1;
                $res{'type'} = $2;
 
                $res{'mode'} = $1;
                $res{'type'} = $2;
@@ -3642,7 +3761,7 @@ sub parse_from_to_diffinfo {
 
 sub git_get_heads_list {
        my ($limit, @classes) = @_;
 
 sub git_get_heads_list {
        my ($limit, @classes) = @_;
-       @classes = ('heads') unless @classes;
+       @classes = get_branch_refs() unless @classes;
        my @patterns = map { "refs/$_" } @classes;
        my @headslist;
 
        my @patterns = map { "refs/$_" } @classes;
        my @headslist;
 
@@ -3660,9 +3779,16 @@ sub git_get_heads_list {
                my ($committer, $epoch, $tz) =
                        ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
                $ref_item{'fullname'}  = $name;
                my ($committer, $epoch, $tz) =
                        ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
                $ref_item{'fullname'}  = $name;
-               $name =~ s!^refs/(?:head|remote)s/!!;
+               my $strip_refs = join '|', map { quotemeta } get_branch_refs();
+               $name =~ s!^refs/($strip_refs|remotes)/!!;
+               $ref_item{'name'} = $name;
+               # for refs neither in 'heads' nor 'remotes' we want to
+               # show their ref dir
+               my $ref_dir = (defined $1) ? $1 : '';
+               if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') {
+                   $ref_item{'name'} .= ' (' . $ref_dir . ')';
+               }
 
 
-               $ref_item{'name'}  = $name;
                $ref_item{'id'}    = $hash;
                $ref_item{'title'} = $title || '(no commit message)';
                $ref_item{'epoch'} = $epoch;
                $ref_item{'id'}    = $hash;
                $ref_item{'title'} = $title || '(no commit message)';
                $ref_item{'epoch'} = $epoch;
@@ -3834,7 +3960,7 @@ sub blob_contenttype {
 # guess file syntax for syntax highlighting; return undef if no highlighting
 # the name of syntax can (in the future) depend on syntax highlighter used
 sub guess_file_syntax {
 # guess file syntax for syntax highlighting; return undef if no highlighting
 # the name of syntax can (in the future) depend on syntax highlighter used
 sub guess_file_syntax {
-       my ($highlight, $mimetype, $file_name) = @_;
+       my ($highlight, $file_name) = @_;
        return undef unless ($highlight && defined $file_name);
        my $basename = basename($file_name, '.in');
        return $highlight_basename{$basename}
        return undef unless ($highlight && defined $file_name);
        my $basename = basename($file_name, '.in');
        return $highlight_basename{$basename}
@@ -3852,12 +3978,16 @@ sub guess_file_syntax {
 # or return original FD if no highlighting
 sub run_highlighter {
        my ($fd, $highlight, $syntax) = @_;
 # or return original FD if no highlighting
 sub run_highlighter {
        my ($fd, $highlight, $syntax) = @_;
-       return $fd unless ($highlight && defined $syntax);
+       return $fd unless ($highlight);
 
        close $fd;
 
        close $fd;
+       my $syntax_arg = (defined $syntax) ? "--syntax $syntax" : "--force";
        open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
        open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 quote_command($^X, '-CO', '-MEncode=decode,FB_DEFAULT', '-pse',
+                   '$_ = decode($fe, $_, FB_DEFAULT) if !utf8::decode($_);',
+                   '--', "-fe=$fallback_encoding")." | ".
                  quote_command($highlight_bin).
                  quote_command($highlight_bin).
-                 " --replace-tabs=8 --fragment --syntax $syntax |"
+                 " --replace-tabs=8 --fragment $syntax_arg |"
                or die_error(500, "Couldn't open file or run syntax highlighter");
        return $fd;
 }
                or die_error(500, "Couldn't open file or run syntax highlighter");
        return $fd;
 }
@@ -3983,7 +4113,9 @@ sub print_nav_breadcrumbs_path {
 sub print_nav_breadcrumbs {
        my %opts = @_;
 
 sub print_nav_breadcrumbs {
        my %opts = @_;
 
-       print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
+       for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) {
+               print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / ";
+       }
        if (defined $project) {
                my @dirname = split '/', $project;
                my $projectbasename = pop @dirname;
        if (defined $project) {
                my @dirname = split '/', $project;
                my $projectbasename = pop @dirname;
@@ -4023,7 +4155,7 @@ sub print_search_form {
        if ($use_pathinfo) {
                $action .= "/".esc_url($project);
        }
        if ($use_pathinfo) {
                $action .= "/".esc_url($project);
        }
-       print $cgi->startform(-method => "get", -action => $action) .
+       print $cgi->start_form(-method => "get", -action => $action) .
              "<div class=\"search\">\n" .
              (!$use_pathinfo &&
              $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
              "<div class=\"search\">\n" .
              (!$use_pathinfo &&
              $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
@@ -4031,8 +4163,8 @@ sub print_search_form {
              $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
              $cgi->popup_menu(-name => 'st', -default => 'commit',
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
              $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
              $cgi->popup_menu(-name => 'st', -default => 'commit',
                               -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
-             $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
-             " search:\n",
+             " " . $cgi->a({-href => href(action=>"search_help"),
+                            -title => "search help" }, "?") . " search:\n",
              $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
              $cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
              "<span title=\"Extended regular expression\">" .
              $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
@@ -4277,7 +4409,7 @@ sub git_print_page_nav {
              "</div>\n";
 }
 
              "</div>\n";
 }
 
-# returns a submenu for the nagivation of the refs views (tags, heads,
+# returns a submenu for the navigation of the refs views (tags, heads,
 # remotes) with the current view disabled and the remotes view only
 # available if the feature is enabled
 sub format_ref_views {
 # remotes) with the current view disabled and the remotes view only
 # available if the feature is enabled
 sub format_ref_views {
@@ -4705,7 +4837,7 @@ sub fill_from_file_info {
 sub is_deleted {
        my $diffinfo = shift;
 
 sub is_deleted {
        my $diffinfo = shift;
 
-       return $diffinfo->{'to_id'} eq ('0' x 40);
+       return $diffinfo->{'to_id'} eq ('0' x 40) || $diffinfo->{'to_id'} eq ('0' x 64);
 }
 
 # does patch correspond to [previous] difftree raw line
 }
 
 # does patch correspond to [previous] difftree raw line
@@ -5433,7 +5565,7 @@ sub git_project_search_form {
        }
 
        print "<div class=\"projsearch\">\n";
        }
 
        print "<div class=\"projsearch\">\n";
-       print $cgi->startform(-method => 'get', -action => $my_uri) .
+       print $cgi->start_form(-method => 'get', -action => $my_uri) .
              $cgi->hidden(-name => 'a', -value => 'project_list')  . "\n";
        print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
                if (defined $project_filter);
              $cgi->hidden(-name => 'a', -value => 'project_list')  . "\n";
        print $cgi->hidden(-name => 'pf', -value => $project_filter). "\n"
                if (defined $project_filter);
@@ -5866,6 +5998,9 @@ sub git_history_body {
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
 
                if ($ftype eq 'blob') {
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
 
                if ($ftype eq 'blob') {
+                       print " | " .
+                             $cgi->a({-href => href(action=>"blob_plain", hash_base=>$commit, file_name=>$file_name)}, "raw");
+
                        my $blob_current = $file_hash;
                        my $blob_parent  = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob_current && defined $blob_parent &&
                        my $blob_current = $file_hash;
                        my $blob_parent  = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob_current && defined $blob_parent &&
@@ -6188,7 +6323,7 @@ sub git_search_changes {
                                      -class => "list subject"},
                                      chop_and_escape_str($co{'title'}, 50) . "<br/>");
                } elsif (defined $set{'to_id'}) {
                                      -class => "list subject"},
                                      chop_and_escape_str($co{'title'}, 50) . "<br/>");
                } elsif (defined $set{'to_id'}) {
-                       next if ($set{'to_id'} =~ m/^0{40}$/);
+                       next if is_deleted(\%set);
 
                        print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
                                                     hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
 
                        print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
                                                     hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
@@ -6464,7 +6599,7 @@ sub git_summary {
        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";
        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";
-        unless ($omit_owner) {
+        if ($owner and not $omit_owner) {
                print  "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
         }
        if (defined $cd{'rfc2822'}) {
                print  "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
         }
        if (defined $cd{'rfc2822'}) {
@@ -6627,6 +6762,7 @@ sub git_blame_common {
                        $hash_base, '--', $file_name
                        or die_error(500, "Open git-blame --porcelain failed");
        }
                        $hash_base, '--', $file_name
                        or die_error(500, "Open git-blame --porcelain failed");
        }
+       binmode $fd, ':utf8';
 
        # incremental blame data returns early
        if ($format eq 'data') {
 
        # incremental blame data returns early
        if ($format eq 'data') {
@@ -6731,7 +6867,7 @@ sub git_blame_common {
                        # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
                        # no <lines in group> for subsequent lines in group of lines
                        my ($full_rev, $orig_lineno, $lineno, $group_size) =
                        # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
                        # no <lines in group> for subsequent lines in group of lines
                        my ($full_rev, $orig_lineno, $lineno, $group_size) =
-                          ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
+                          ($line =~ /^($oid_regex) (\d+) (\d+)(?: (\d+))?$/);
                        if (!exists $metainfo{$full_rev}) {
                                $metainfo{$full_rev} = { 'nprevious' => 0 };
                        }
                        if (!exists $metainfo{$full_rev}) {
                                $metainfo{$full_rev} = { 'nprevious' => 0 };
                        }
@@ -6781,7 +6917,7 @@ sub git_blame_common {
                        }
                        # 'previous' <sha1 of parent commit> <filename at commit>
                        if (exists $meta->{'previous'} &&
                        }
                        # 'previous' <sha1 of parent commit> <filename at commit>
                        if (exists $meta->{'previous'} &&
-                           $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
+                           $meta->{'previous'} =~ /^($oid_regex) (.*)$/) {
                                $meta->{'parent'} = $1;
                                $meta->{'file_parent'} = unquote($2);
                        }
                                $meta->{'parent'} = $1;
                                $meta->{'file_parent'} = unquote($2);
                        }
@@ -6898,7 +7034,7 @@ sub git_blob_plain {
                } else {
                        die_error(400, "No file name defined");
                }
                } else {
                        die_error(400, "No file name defined");
                }
-       } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+       } elsif ($hash =~ m/^$oid_regex$/) {
                # blobs defined by non-textual hash id's can be cached
                $expires = "+1d";
        }
                # blobs defined by non-textual hash id's can be cached
                $expires = "+1d";
        }
@@ -6959,7 +7095,7 @@ sub git_blob {
                } else {
                        die_error(400, "No file name defined");
                }
                } else {
                        die_error(400, "No file name defined");
                }
-       } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+       } elsif ($hash =~ m/^$oid_regex$/) {
                # blobs defined by non-textual hash id's can be cached
                $expires = "+1d";
        }
                # blobs defined by non-textual hash id's can be cached
                $expires = "+1d";
        }
@@ -6977,9 +7113,8 @@ sub git_blob {
        $have_blame &&= ($mimetype =~ m!^text/!);
 
        my $highlight = gitweb_check_feature('highlight');
        $have_blame &&= ($mimetype =~ m!^text/!);
 
        my $highlight = gitweb_check_feature('highlight');
-       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
-       $fd = run_highlighter($fd, $highlight, $syntax)
-               if $syntax;
+       my $syntax = guess_file_syntax($highlight, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax);
 
        git_header_html(undef, $expires);
        my $formats_nav = '';
 
        git_header_html(undef, $expires);
        my $formats_nav = '';
@@ -7016,7 +7151,7 @@ sub git_blob {
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
        if ($mimetype =~ m!^image/!) {
-               print qq!<img type="!.esc_attr($mimetype).qq!"!;
+               print qq!<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!"!;
                }
                if ($file_name) {
                        print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
                }
@@ -7032,7 +7167,7 @@ sub git_blob {
                        $line = untabify($line);
                        printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
                               $nr, esc_attr(href(-replay => 1)), $nr, $nr,
                        $line = untabify($line);
                        printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
                               $nr, esc_attr(href(-replay => 1)), $nr, $nr,
-                              $syntax ? sanitize($line) : esc_html($line, -nbsp=>1);
+                              $highlight ? sanitize($line) : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
                }
        }
        close $fd
@@ -7151,6 +7286,15 @@ sub git_tree {
        git_footer_html();
 }
 
        git_footer_html();
 }
 
+sub sanitize_for_filename {
+    my $name = shift;
+
+    $name =~ s!/!-!g;
+    $name =~ s/[^[:alnum:]_.-]//g;
+
+    return $name;
+}
+
 sub snapshot_name {
        my ($project, $hash) = @_;
 
 sub snapshot_name {
        my ($project, $hash) = @_;
 
@@ -7158,9 +7302,7 @@ sub snapshot_name {
        # path/to/project/.git -> project
        my $name = to_utf8($project);
        $name =~ s,([^/])/*\.git$,$1,;
        # path/to/project/.git -> project
        my $name = to_utf8($project);
        $name =~ s,([^/])/*\.git$,$1,;
-       $name = basename($name);
-       # sanitize name
-       $name =~ s/[[:cntrl:]]/?/g;
+       $name = sanitize_for_filename(basename($name));
 
        my $ver = $hash;
        if ($hash =~ /^[0-9a-fA-F]+$/) {
 
        my $ver = $hash;
        if ($hash =~ /^[0-9a-fA-F]+$/) {
@@ -7174,13 +7316,25 @@ sub snapshot_name {
                $ver = $1;
        } else {
                # branches and other need shortened SHA-1 hash
                $ver = $1;
        } else {
                # branches and other need shortened SHA-1 hash
-               if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
-                       $ver = $1;
+               my $strip_refs = join '|', map { quotemeta } get_branch_refs();
+               if ($hash =~ m!^refs/($strip_refs|remotes)/(.*)$!) {
+                       my $ref_dir = (defined $1) ? $1 : '';
+                       $ver = $2;
+
+                       $ref_dir = sanitize_for_filename($ref_dir);
+                       # for refs neither in heads nor remotes we want to
+                       # add a ref dir to archive name
+                       if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') {
+                               $ver = $ref_dir . '-' . $ver;
+                       }
                }
                $ver .= '-' . git_get_short_hash($project, $hash);
        }
                }
                $ver .= '-' . git_get_short_hash($project, $hash);
        }
+       # special case of sanitization for filename - we change
+       # slashes to dots instead of dashes
        # in case of hierarchical branch names
        $ver =~ s!/!.!g;
        # in case of hierarchical branch names
        $ver =~ s!/!.!g;
+       $ver =~ s/[^[:alnum:]_.-]//g;
 
        # name = project-version_string
        $name = "$name-$ver";
 
        # name = project-version_string
        $name = "$name-$ver";
@@ -7399,7 +7553,7 @@ sub git_commit {
 
        # non-textual hash id's can be cached
        my $expires;
 
        # non-textual hash id's can be cached
        my $expires;
-       if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+       if ($hash =~ m/^$oid_regex$/) {
                $expires = "+1d";
        }
        my $refs = git_get_references();
                $expires = "+1d";
        }
        my $refs = git_get_references();
@@ -7475,7 +7629,7 @@ sub git_object {
                        git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
                        or die_error(404, "Object does not exist");
                $type = <$fd>;
                        git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
                        or die_error(404, "Object does not exist");
                $type = <$fd>;
-               chomp $type;
+               defined $type && chomp $type;
                close $fd
                        or die_error(404, "Object does not exist");
 
                close $fd
                        or die_error(404, "Object does not exist");
 
@@ -7493,7 +7647,7 @@ sub git_object {
                close $fd;
 
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
                close $fd;
 
                #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-               unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
+               unless ($line && $line =~ m/^([0-9]+) (.+) ($oid_regex)\t/) {
                        die_error(404, "File or directory for given base does not exist");
                }
                $type = $2;
                        die_error(404, "File or directory for given base does not exist");
                }
                $type = $2;
@@ -7533,7 +7687,7 @@ sub git_blobdiff {
                                or die_error(404, "Blob diff not found");
 
                } elsif (defined $hash &&
                                or die_error(404, "Blob diff not found");
 
                } elsif (defined $hash &&
-                        $hash =~ /[0-9a-fA-F]{40}/) {
+                        $hash =~ $oid_regex) {
                        # try to find filename from $hash
 
                        # read filtered raw output
                        # try to find filename from $hash
 
                        # read filtered raw output
@@ -7543,7 +7697,7 @@ sub git_blobdiff {
                        @difftree =
                                # ':100644 100644 03b21826... 3b93d5e7... M     ls-files.c'
                                # $hash == to_id
                        @difftree =
                                # ':100644 100644 03b21826... 3b93d5e7... M     ls-files.c'
                                # $hash == to_id
-                               grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
+                               grep { /^:[0-7]{6} [0-7]{6} $oid_regex $hash/ }
                                map { chomp; $_ } <$fd>;
                        close $fd
                                or die_error(404, "Reading git-diff-tree failed");
                                map { chomp; $_ } <$fd>;
                        close $fd
                                or die_error(404, "Reading git-diff-tree failed");
@@ -7566,8 +7720,8 @@ sub git_blobdiff {
                $hash        ||= $diffinfo{'to_id'};
 
                # non-textual hash id's can be cached
                $hash        ||= $diffinfo{'to_id'};
 
                # non-textual hash id's can be cached
-               if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
-                   $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
+               if ($hash_base =~ m/^$oid_regex$/ &&
+                   $hash_parent_base =~ m/^$oid_regex$/) {
                        $expires = '+1d';
                }
 
                        $expires = '+1d';
                }
 
@@ -7703,7 +7857,7 @@ sub git_commitdiff {
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
                        # commitdiff with two commits given
                        my $hash_parent_short = $hash_parent;
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
                        # commitdiff with two commits given
                        my $hash_parent_short = $hash_parent;
-                       if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
+                       if ($hash_parent =~ m/^$oid_regex$/) {
                                $hash_parent_short = substr($hash_parent, 0, 7);
                        }
                        $formats_nav .=
                                $hash_parent_short = substr($hash_parent, 0, 7);
                        }
                        $formats_nav .=
@@ -7812,7 +7966,7 @@ sub git_commitdiff {
 
        # non-textual hash id's can be cached
        my $expires;
 
        # non-textual hash id's can be cached
        my $expires;
-       if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+       if ($hash =~ m/^$oid_regex$/) {
                $expires = "+1d";
        }
 
                $expires = "+1d";
        }
 
@@ -7967,7 +8121,7 @@ sub git_search_help {
 <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><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
-<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
+<a href="https://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
 insensitive).</p>
 <dl>
 <dt><b>commit</b></dt>
 insensitive).</p>
 <dl>
 <dt><b>commit</b></dt>
This page took 0.740179 seconds and 4 git commands to generate.