X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/22eb905ce5fd3c18e073b969b33636e56cc46254cde0833370433e440fcaec0a..927087639f1564dd6203150a0c7ad8b59ed4accbcb420b855344a4d276d4d71f:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index d4ff3db..4a8bea0 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -571,21 +571,88 @@ sub esc_url { } # replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { +sub esc_html ($;%) { my $str = shift; + my %opts = @_; + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } + return $str; +} + +# Make control characterss "printable". +sub quot_cec { + my $cntrl = shift; + 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) + ); + my $chr = ( (exists $es{$cntrl}) + ? $es{$cntrl} + : sprintf('\%03o', ord($cntrl)) ); + return "$chr"; +} + +# Alternatively use unicode control pictures codepoints. +sub quot_upr { + my $cntrl = shift; + my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl)); + return "$chr"; +} + +# quote control characters and escape filename to HTML +sub esc_path { + my $str = shift; + + $str = esc_html($str); + $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; return $str; } # git may return quoted and escaped filenames sub unquote { my $str = shift; + + sub unq { + my $seq = shift; + my %es = ( # character escape codes, aka escape sequences + 't' => "\t", # tab (HT, TAB) + 'n' => "\n", # newline (NL) + 'r' => "\r", # return (CR) + 'f' => "\f", # form feed (FF) + 'b' => "\b", # backspace (BS) + 'a' => "\a", # alarm (bell) (BEL) + 'e' => "\e", # escape (ESC) + 'v' => "\013", # vertical tab (VT) + ); + + if ($seq =~ m/^[0-7]{1,3}$/) { + # octal char sequence + return chr(oct($seq)); + } elsif (exists $es{$seq}) { + # C escape sequence, aka character escape code + return $es{$seq} + } + # quoted ordinary character + return $seq; + } + if ($str =~ m/^"(.*)"$/) { + # needs unquoting $str = $1; - $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; + $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg; } return $str; } @@ -719,6 +786,32 @@ sub file_type { } } +# convert file mode in octal to file type description string +sub file_type_long { + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + if ($mode & S_IXUSR) { + return "executable"; + } else { + return "file"; + }; + } else { + return "unknown"; + } +} + + ## ---------------------------------------------------------------------- ## functions returning short HTML fragments, or transforming HTML fragments ## which don't beling to other sections @@ -801,7 +894,7 @@ sub format_diff_line { $diff_class = " incomplete"; } $line = untabify($line); - return "
" . esc_html($line) . "
\n"; + return "
" . esc_html($line, -nbsp=>1) . "
\n"; } ## ---------------------------------------------------------------------- @@ -918,9 +1011,11 @@ sub git_get_projects_list { if (-d $projects_list) { # search in directory my $dir = $projects_list . ($filter ? "/$filter" : ''); + # remove the trailing "/" + $dir =~ s!/+$!!; my $pfxlen = length("$dir"); - my $check_forks = gitweb_check_feature('forks'); + my ($check_forks) = gitweb_check_feature('forks'); File::Find::find({ follow_fast => 1, # follow symbolic links @@ -956,6 +1051,17 @@ sub git_get_projects_list { if (!defined $path) { next; } + if ($filter ne '') { + # looking for forks; + my $pfx = substr($path, 0, length($filter)); + if ($pfx ne $filter) { + next; + } + my $sfx = substr($path, length($filter)); + if ($sfx !~ /^\/.*\.git$/) { + next; + } + } if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, @@ -1503,7 +1609,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - " . esc_html($file_name); + $title .= " - " . esc_path($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -1774,20 +1880,20 @@ sub git_print_page_path { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir)); + -title => $fullname}, esc_path($dir)); print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); + -title => $name}, esc_path($basename)); } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); + -title => $name}, esc_path($basename)); print " / "; } else { - print esc_html($basename); + print esc_path($basename); } } print "
\n"; @@ -1859,7 +1965,7 @@ sub git_print_tree_entry { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_html($t->{'name'})) . "\n"; + -class => "list"}, esc_path($t->{'name'})) . "\n"; print ""; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, @@ -1886,7 +1992,7 @@ sub git_print_tree_entry { print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, - esc_html($t->{'name'})); + esc_path($t->{'name'})); print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, @@ -1907,7 +2013,7 @@ sub git_print_tree_entry { sub git_difftree_body { my ($difftree, $hash, $parent) = @_; - + my ($have_blame) = gitweb_check_feature('blame'); print "
\n"; if ($#{$difftree} > 10) { print(($#{$difftree} + 1) . " files changed:\n"); @@ -1951,7 +2057,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chng\n"; print ""; @@ -1967,7 +2073,7 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chng\n"; print ""; @@ -1980,9 +2086,13 @@ sub git_difftree_body { print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => + href(action=>"blame", + hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, "history"); @@ -2007,30 +2117,33 @@ sub git_difftree_body { print ""; print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'}), - -class => "list"}, esc_html($diff{'file'})); + -class => "list"}, esc_path($diff{'file'})); print "\n"; print "$mode_chnge\n"; print ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not onlu mode changed) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => href(action=>"blame", + hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$diff{'file'})}, "history"); @@ -2047,33 +2160,36 @@ sub git_difftree_body { print "" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), - -class => "list"}, esc_html($diff{'to_file'})) . "\n" . + -class => "list"}, esc_path($diff{'to_file'})) . "\n" . "[$nstatus from " . $cgi->a({-href => href(action=>"blob", hash_base=>$parent, hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), - -class => "list"}, esc_html($diff{'from_file'})) . + -class => "list"}, esc_path($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]\n" . ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not only pure rename or copy) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'from_file'})}, "blob") . " | "; - print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, - file_name=>$diff{'from_file'})}, - "blame") . " | "; + if ($have_blame) { + print $cgi->a({-href => href(action=>"blame", + hash_base=>$hash, + file_name=>$diff{'to_file'})}, + "blame") . " | "; + } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'from_file'})}, "history"); @@ -2092,6 +2208,7 @@ sub git_patchset_body { my $in_header = 0; my $patch_found = 0; my $diffinfo; + my (%from, %to); print "
\n"; @@ -2102,6 +2219,10 @@ sub git_patchset_body { if ($patch_line =~ m/^diff /) { # "git diff" header # beginning of patch (in patchset) if ($patch_found) { + # close extended header for previous empty patch + if ($in_header) { + print "
\n" # class="diff extended_header" + } # close previous patch print "
\n"; # class="patch" } else { @@ -2110,96 +2231,113 @@ sub git_patchset_body { } print "
\n"; + # read and prepare patch information if (ref($difftree->[$patch_idx]) eq "HASH") { + # pre-parsed (or generated by hand) $diffinfo = $difftree->[$patch_idx]; } else { $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); } + $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; + $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'}; + if ($diffinfo->{'status'} ne "A") { # not new (added) file + $from{'href'} = href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, + file_name=>$from{'file'}); + } + if ($diffinfo->{'status'} ne "D") { # not deleted file + $to{'href'} = href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, + file_name=>$to{'file'}); + } $patch_idx++; - # for now, no extended header, hence we skip empty patches - # companion to next LINE if $in_header; - if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change - $in_header = 1; - next LINE; + # print "git diff" header + $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; + if ($from{'href'}) { + $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, + 'a/' . esc_path($from{'file'})); + } else { # file was added + $patch_line .= 'a/' . esc_path($from{'file'}); } - - if ($diffinfo->{'status'} eq "A") { # added - print "
" . file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}) . " (new)" . - "
\n"; # class="diff_info" - - } elsif ($diffinfo->{'status'} eq "D") { # deleted - print "
" . file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . " (deleted)" . - "
\n"; # class="diff_info" - - } elsif ($diffinfo->{'status'} eq "R" || # renamed - $diffinfo->{'status'} eq "C" || # copied - $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff) - print "
" . - file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})}, - $diffinfo->{'from_id'}) . - " -> " . - file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})}, - $diffinfo->{'to_id'}); - print "
\n"; # class="diff_info" - - } else { # modified, mode changed, ... - print "
" . - file_type($diffinfo->{'from_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'from_id'}) . - " -> " . - file_type($diffinfo->{'to_mode'}) . ":" . - $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, - $diffinfo->{'to_id'}); - print "
\n"; # class="diff_info" + $patch_line .= ' '; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + 'b/' . esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= 'b/' . esc_path($to{'file'}); } - #print "
\n"; + print "
$patch_line
\n"; + print "
\n"; $in_header = 1; next LINE; - } # start of patch in patchset + } + if ($in_header) { + if ($patch_line !~ m/^---/) { + # match + if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { + $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { + $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + # match + if ($patch_line =~ m/\s(\d{6})$/) { + $patch_line .= ' (' . + file_type_long($1) . + ')'; + } + # match + if ($patch_line =~ m/^index/) { + my ($from_link, $to_link); + if ($from{'href'}) { + $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, + substr($diffinfo->{'from_id'},0,7)); + } else { + $from_link = '0' x 7; + } + if ($to{'href'}) { + $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, + substr($diffinfo->{'to_id'},0,7)); + } else { + $to_link = '0' x 7; + } + my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); + $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + } + print $patch_line . "
\n"; - if ($in_header && $patch_line =~ m/^---/) { - #print "
\n"; # class="diff extended_header" - $in_header = 0; + } else { + #$in_header && $patch_line =~ m/^---/; + print "
\n"; # class="diff extended_header" + $in_header = 0; + + if ($from{'href'}) { + $patch_line = '--- a/' . + $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + print "
$patch_line
\n"; - my $file = $diffinfo->{'from_file'}; - $file ||= $diffinfo->{'file'}; - $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, file_name=>$file), - -class => "list"}, esc_html($file)); - $patch_line =~ s|a/.*$|a/$file|g; - print "
$patch_line
\n"; + $patch_line = <$fd>; + chomp $patch_line; - $patch_line = <$fd>; - chomp $patch_line; + #$patch_line =~ m/^+++/; + if ($to{'href'}) { + $patch_line = '+++ b/' . + $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + print "
$patch_line
\n"; - #$patch_line =~ m/^+++/; - $file = $diffinfo->{'to_file'}; - $file ||= $diffinfo->{'file'}; - $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, file_name=>$file), - -class => "list"}, esc_html($file)); - $patch_line =~ s|b/.*|b/$file|g; - print "
$patch_line
\n"; + } next LINE; } - next LINE if $in_header; print format_diff_line($patch_line); } @@ -2213,7 +2351,7 @@ sub git_patchset_body { sub git_project_list_body { my ($projlist, $order, $from, $to, $extra, $no_header) = @_; - my $check_forks = gitweb_check_feature('forks'); + my ($check_forks) = gitweb_check_feature('forks'); my @projects; foreach my $pr (@$projlist) { @@ -2231,8 +2369,14 @@ sub git_project_list_body { } if ($check_forks) { my $pname = $pr->{'path'}; - $pname =~ s/\.git$//; - $pr->{'forks'} = -d "$projectroot/$pname"; + if (($pname =~ s/\.git$//) && + ($pname !~ /\/$/) && + (-d "$projectroot/$pname")) { + $pr->{'forks'} = "-d $projectroot/$pname"; + } + else { + $pr->{'forks'} = 0; + } } push @projects, $pr; } @@ -2298,6 +2442,7 @@ sub git_project_list_body { if ($check_forks) { print ""; if ($pr->{'forks'}) { + print "\n"; print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); } print "\n"; @@ -2615,7 +2760,9 @@ sub git_summary { my @taglist = git_get_tags_list(15); my @headlist = git_get_heads_list(15); my @forklist; - if (gitweb_check_feature('forks')) { + my ($check_forks) = gitweb_check_feature('forks'); + + if ($check_forks) { @forklist = git_get_projects_list($project); } @@ -3044,7 +3191,7 @@ sub git_blob { $nr++; $line = untabify($line); printf "
%4i %s
\n", - $nr, $nr, $nr, esc_html($line); + $nr, $nr, $nr, esc_html($line, -nbsp=>1); } close $fd or print "Reading blob failed.\n"; @@ -3502,8 +3649,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; - $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg; print $line; @@ -3860,7 +4007,7 @@ sub git_search { print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, hash=>$set{'id'}, file_name=>$set{'file'}), -class => "list"}, - "" . esc_html($set{'file'}) . "") . + "" . esc_path($set{'file'}) . "") . "
\n"; } print "\n" . @@ -3995,7 +4142,7 @@ XML if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = esc_html(unquote($7)); + my $file = esc_path(unquote($7)); $file = to_utf8($file); print "$file
\n"; }