X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/5389af3745819e46fd9318799dd52e7c988ec75593810dc5e24f3cf0d73d4cf7..57f13c04a633df6d0783aabf7fc3e49281d26f66704e143c29b08563ea942d5d:/gitweb.perl diff --git a/gitweb.perl b/gitweb.perl index eb04ebb..84a3b1a 100755 --- a/gitweb.perl +++ b/gitweb.perl @@ -435,6 +435,7 @@ my %actions = ( "tags" => \&git_tags, "tree" => \&git_tree, "snapshot" => \&git_snapshot, + "object" => \&git_object, # those below don't need $project "opml" => \&git_opml, "project_list" => \&git_project_list, @@ -828,14 +829,12 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{40})/) { + if ($line =~ m/([0-9a-fA-F]{8,40})/) { my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = - $cgi->a({-href => href(action=>"commit", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + my $link = + $cgi->a({-href => href(action=>"object", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; } return $line; } @@ -857,7 +856,8 @@ sub format_ref_marker { $name = $ref; } - $markers .= " " . esc_html($name) . ""; + $markers .= " " . + esc_html($name) . ""; } } @@ -1140,8 +1140,9 @@ sub git_get_last_activity { $git_dir = "$projectroot/$path"; open($fd, "-|", git_cmd(), 'for-each-ref', - '--format=%(refname) %(committer)', + '--format=%(committer)', '--sort=-committerdate', + '--count=1', 'refs/heads') or return; my $most_recent = <$fd>; close $fd or return; @@ -1990,12 +1991,73 @@ sub git_print_log ($;%) { } } +# return link target (what link points to) +sub git_get_link_target { + my $hash = shift; + my $link_target; + + # read link + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash + or return; + { + local $/; + $link_target = <$fd>; + } + close $fd + or return; + + return $link_target; +} + +# given link target, and the directory (basedir) the link is in, +# return target of link relative to top directory (top tree); +# return undef if it is not possible (including absolute links). +sub normalize_link_target { + my ($link_target, $basedir, $hash_base) = @_; + + # we can normalize symlink target only if $hash_base is provided + return unless $hash_base; + + # absolute symlinks (beginning with '/') cannot be normalized + return if (substr($link_target, 0, 1) eq '/'); + + # normalize link target to path from top (root) tree (dir) + my $path; + if ($basedir) { + $path = $basedir . '/' . $link_target; + } else { + # we are in top (root) tree (dir) + $path = $link_target; + } + + # remove //, /./, and /../ + my @path_parts; + foreach my $part (split('/', $path)) { + # discard '.' and '' + next if (!$part || $part eq '.'); + # handle '..' + if ($part eq '..') { + if (@path_parts) { + pop @path_parts; + } else { + # link leads outside repository (outside top dir) + return; + } + } else { + push @path_parts, $part; + } + } + $path = join('/', @path_parts); + + return $path; +} + # print tree entry (row of git_tree), but without encompassing element sub git_print_tree_entry { my ($t, $basedir, $hash_base, $have_blame) = @_; my %base_key = (); - $base_key{hash_base} = $hash_base if defined $hash_base; + $base_key{'hash_base'} = $hash_base if defined $hash_base; # The format of a table row is: mode list link. Where mode is # the mode of the entry, list is the name of the entry, an href, @@ -2006,16 +2068,31 @@ 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_path($t->{'name'})) . "\n"; + -class => "list"}, esc_path($t->{'name'})); + if (S_ISLNK(oct $t->{'mode'})) { + my $link_target = git_get_link_target($t->{'hash'}); + if ($link_target) { + my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + if (defined $norm_target) { + print " -> " . + $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, + file_name=>$norm_target), + -title => $norm_target}, esc_path($link_target)); + } else { + print " -> " . esc_path($link_target); + } + } + } + print "\n"; print ""; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); if ($have_blame) { print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { print " | " . @@ -2037,8 +2114,8 @@ sub git_print_tree_entry { print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); if (defined $hash_base) { print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, @@ -2560,6 +2637,8 @@ sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; + my $have_snapshot = gitweb_have_snapshot(); + $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -2587,7 +2666,7 @@ sub git_shortlog_body { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); - if (gitweb_have_snapshot()) { + if ($have_snapshot) { print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); } print "\n" . @@ -2833,15 +2912,17 @@ sub git_project_index { sub git_summary { my $descr = git_get_project_description($project) || "none"; - my $head = git_get_head_hash($project); - my %co = parse_commit($head); + my %co = parse_commit("HEAD"); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); + my $head = $co{'id'}; my $owner = git_get_project_owner($project); my $refs = git_get_references(); - my @taglist = git_get_tags_list(15); - my @headlist = git_get_heads_list(15); + # These get_*_list functions return one more to allow us to see if + # there are more ... + my @taglist = git_get_tags_list(16); + my @headlist = git_get_heads_list(16); my @forklist; my ($check_forks) = gitweb_check_feature('forks'); @@ -2877,30 +2958,36 @@ sub git_summary { } } + # we need to request one more than 16 (0..15) to check if + # those 16 are all open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - git_get_head_hash($project), "--" + $head, "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; git_print_header_div('shortlog'); git_shortlog_body(\@revlist, 0, 15, $refs, + $#revlist <= 15 ? undef : $cgi->a({-href => href(action=>"shortlog")}, "...")); if (@taglist) { git_print_header_div('tags'); git_tags_body(\@taglist, 0, 15, + $#taglist <= 15 ? undef : $cgi->a({-href => href(action=>"tags")}, "...")); } if (@headlist) { git_print_header_div('heads'); git_heads_body(\@headlist, $head, 0, 15, + $#headlist <= 15 ? undef : $cgi->a({-href => href(action=>"heads")}, "...")); } if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, undef, 0, 15, + $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), 'noheader'); } @@ -3497,15 +3584,46 @@ sub git_commit { my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $parent = $co{'parent'}; + my $parent = $co{'parent'}; + my $parents = $co{'parents'}; # listref + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if (!defined $parent) { + # --root commitdiff + $formats_nav .= '(initial)'; + } elsif (@$parents == 1) { + # single parent commit + $formats_nav .= + '(parent: ' . + $cgi->a({-href => href(action=>"commit", + hash=>$parent)}, + esc_html(substr($parent, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + '(merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @$parents ) . + ')'; + } + if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + my @difftree; + if (@$parents <= 1) { + # difftree output is not printed for merges + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, $parent, $hash, "--" + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); + } # non-textual hash id's can be cached my $expires; @@ -3517,16 +3635,10 @@ sub git_commit { my $have_snapshot = gitweb_have_snapshot(); - my @views_nav = (); - if (defined $file_name && defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, - "blame"); - } git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, - join (' | ', @views_nav)); + $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -3567,7 +3679,7 @@ sub git_commit { } print "" . "\n"; - my $parents = $co{'parents'}; + foreach my $par (@$parents) { print "" . "parent" . @@ -3589,11 +3701,61 @@ sub git_commit { git_print_log($co{'comment'}); print "\n"; - git_difftree_body(\@difftree, $hash, $parent); + if (@$parents <= 1) { + # do not output difftree/whatchanged for merges + git_difftree_body(\@difftree, $hash, $parent); + } git_footer_html(); } +sub git_object { + # object is defined by: + # - hash or hash_base alone + # - hash_base and file_name + my $type; + + # - hash or hash_base alone + if ($hash || ($hash_base && !defined $file_name)) { + my $object_id = $hash || $hash_base; + + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null" + or die_error('404 Not Found', "Object does not exist"); + $type = <$fd>; + chomp $type; + close $fd + or die_error('404 Not Found', "Object does not exist"); + + # - hash_base and file_name + } elsif ($hash_base && defined $file_name) { + $file_name =~ s,/+$,,; + + system(git_cmd(), "cat-file", '-e', $hash_base) == 0 + or die_error('404 Not Found', "Base object does not exist"); + + # here errors should not hapen + open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name + or die_error(undef, "Open git-ls-tree failed"); + my $line = <$fd>; + close $fd; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { + die_error('404 Not Found', "File or directory for given base does not exist"); + } + $type = $2; + $hash = $3; + } else { + die_error('404 Not Found', "Not enough information to find object"); + } + + print $cgi->redirect(-uri => href(action=>$type, -full=>1, + hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name), + -status => '302 Found'); +} + sub git_blobdiff { my $format = shift || 'html'; @@ -4015,20 +4177,20 @@ sub git_search { print "\n"; my $alternate = 1; if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { + my $greptype; + if ($searchtype eq 'commit') { + $greptype = "--grep="; + } elsif ($searchtype eq 'author') { + $greptype = "--author="; + } elsif ($searchtype eq 'committer') { + $greptype = "--committer="; + } $/ = "\0"; open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", $hash, "--" + "--header", "--parents", ($greptype . $searchtext), + $hash, "--" or next; while (my $commit_text = <$fd>) { - if (!grep m/$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) { - next; - } my @commit_lines = split "\n", $commit_text; my %co = parse_commit(undef, \@commit_lines); if (!%co) {