X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=gitweb%2Fgitweb.perl;h=b2bae1b250a5316c85ffb221b3b088c9e8cda8c7;hb=b4833a2c62578bdbfd300e296702214cb1b9a601;hp=c7acfade608ce3b6e49c9d061896dcb4bd96c2e0;hpb=91af4ce4ec3f39e1190ab702f51f141e18cdcc1e;p=git.git diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c7acfade6..b2bae1b25 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -71,6 +71,9 @@ our $logo_label = "git homepage"; # source of projects list our $projects_list = "++GITWEB_LIST++"; +# the width (in characters) of the projects list "Description" column +our $projects_list_description_width = 25; + # default order of projects list # valid values are none, project, descr, owner, and age our $default_projects_order = "project"; @@ -101,6 +104,59 @@ our $mimetypes_file = undef; # could be even 'utf-8' for the old behavior) our $fallback_encoding = 'latin1'; +# rename detection options for git-diff and git-diff-tree +# - default is '-M', with the cost proportional to +# (number of removed files) * (number of new files). +# - more costly is '-C' (which implies '-M'), with the cost proportional to +# (number of changed files + number of removed files) * (number of new files) +# - even more costly is '-C', '--find-copies-harder' with cost +# (number of files in the original tree) * (number of new files) +# - one might want to include '-B' option, e.g. '-B', '-M' +our @diff_opts = ('-M'); # taken from git_commit + +# information about snapshot formats that gitweb is capable of serving +our %known_snapshot_formats = ( + # name => { + # 'display' => display name, + # 'type' => mime type, + # 'suffix' => filename suffix, + # 'format' => --format for git-archive, + # 'compressor' => [compressor command and arguments] + # (array reference, optional)} + # + 'tgz' => { + 'display' => 'tar.gz', + 'type' => 'application/x-gzip', + 'suffix' => '.tar.gz', + 'format' => 'tar', + 'compressor' => ['gzip']}, + + 'tbz2' => { + 'display' => 'tar.bz2', + 'type' => 'application/x-bzip2', + 'suffix' => '.tar.bz2', + 'format' => 'tar', + 'compressor' => ['bzip2']}, + + 'zip' => { + 'display' => 'zip', + 'type' => 'application/x-zip', + 'suffix' => '.zip', + 'format' => 'zip'}, +); + +# Aliases so we understand old gitweb.snapshot values in repository +# configuration. +our %known_snapshot_format_aliases = ( + 'gzip' => 'tgz', + 'bzip2' => 'tbz2', + + # backward compatibility: legacy gitweb config support + 'x-gzip' => undef, 'gz' => undef, + 'x-bzip2' => undef, 'bz2' => undef, + 'x-zip' => undef, '' => undef, +); + # You define site-wide feature defaults here; override them with # $GITWEB_CONFIG as necessary. our %feature = ( @@ -131,20 +187,22 @@ our %feature = ( 'override' => 0, 'default' => [0]}, - # Enable the 'snapshot' link, providing a compressed tarball of any + # Enable the 'snapshot' link, providing a compressed archive of any # tree. This can potentially generate high traffic if you have large # project. + # Value is a list of formats defined in %known_snapshot_formats that + # you wish to offer. # To disable system wide have in $GITWEB_CONFIG - # $feature{'snapshot'}{'default'} = [undef]; + # $feature{'snapshot'}{'default'} = []; # To have project specific config enable override in $GITWEB_CONFIG # $feature{'snapshot'}{'override'} = 1; - # and in project config gitweb.snapshot = none|gzip|bzip2|zip; + # and in project config, a comma-separated list of formats or "none" + # to disable. Example: gitweb.snapshot = tbz2,zip; 'snapshot' => { 'sub' => \&feature_snapshot, 'override' => 0, - # => [content-encoding, suffix, program] - 'default' => ['x-gzip', 'gz', 'gzip']}, + 'default' => ['tgz']}, # Enable text search, which will list the commits which match author, # committer or commit text to a given string. Enabled by default. @@ -243,28 +301,15 @@ sub feature_blame { } sub feature_snapshot { - my ($ctype, $suffix, $command) = @_; + my (@fmts) = @_; my ($val) = git_get_project_config('snapshot'); - if ($val eq 'gzip') { - return ('x-gzip', 'gz', 'gzip'); - } elsif ($val eq 'bzip2') { - return ('x-bzip2', 'bz2', 'bzip2'); - } elsif ($val eq 'zip') { - return ('x-zip', 'zip', ''); - } elsif ($val eq 'none') { - return (); + if ($val) { + @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val); } - return ($ctype, $suffix, $command); -} - -sub gitweb_have_snapshot { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); - - return $have_snapshot; + return @fmts; } sub feature_grep { @@ -307,15 +352,17 @@ sub check_export_ok { (!$export_ok || -e "$dir/$export_ok")); } -# rename detection options for git-diff and git-diff-tree -# - default is '-M', with the cost proportional to -# (number of removed files) * (number of new files). -# - more costly is '-C' (or '-C', '-M'), with the cost proportional to -# (number of changed files + number of removed files) * (number of new files) -# - even more costly is '-C', '--find-copies-harder' with cost -# (number of files in the original tree) * (number of new files) -# - one might want to include '-B' option, e.g. '-B', '-M' -our @diff_opts = ('-M'); # taken from git_commit +# process alternate names for backward compatibility +# filter out unsupported (unknown) snapshot formats +sub filter_snapshot_fmts { + my @fmts = @_; + + @fmts = map { + exists $known_snapshot_format_aliases{$_} ? + $known_snapshot_format_aliases{$_} : $_} @fmts; + @fmts = grep(exists $known_snapshot_formats{$_}, @fmts); + +} our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; do $GITWEB_CONFIG if -e $GITWEB_CONFIG; @@ -383,6 +430,22 @@ if (defined $hash_base) { } } +my %allowed_options = ( + "--no-merges" => [ qw(rss atom log shortlog history) ], +); + +our @extra_options = $cgi->param('opt'); +if (defined @extra_options) { + foreach my $opt (@extra_options) { + if (not exists $allowed_options{$opt}) { + die_error(undef, "Invalid option parameter"); + } + if (not grep(/^$action$/, @{$allowed_options{$opt}})) { + die_error(undef, "Invalid option parameter for this action"); + } + } +} + our $hash_parent_base = $cgi->param('hpb'); if (defined $hash_parent_base) { if (!validate_refname($hash_parent_base)) { @@ -408,9 +471,6 @@ if (defined $searchtype) { our $searchtext = $cgi->param('s'); our $search_regexp; if (defined $searchtext) { - if ($searchtype ne 'grep' and $searchtype ne 'pickaxe' and $searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - die_error(undef, "Invalid search parameter"); - } if (length($searchtext) < 2) { die_error(undef, "At least two characters are required for search parameter"); } @@ -542,6 +602,8 @@ sub href(%) { order => "o", searchtext => "s", searchtype => "st", + snapshot_format => "sf", + extra_options => "opt", ); my %mapping = @mapping; @@ -564,7 +626,13 @@ sub href(%) { for (my $i = 0; $i < @mapping; $i += 2) { my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); if (defined $params{$name}) { - push @result, $symbol . "=" . esc_param($params{$name}); + if (ref($params{$name}) eq "ARRAY") { + foreach my $par (@{$params{$name}}) { + push @result, $symbol . "=" . esc_param($par); + } + } else { + push @result, $symbol . "=" . esc_param($params{$name}); + } } } $href .= "?" . join(';', @result) if scalar @result; @@ -824,11 +892,25 @@ sub age_string { return $age_str; } +use constant { + S_IFINVALID => 0030000, + S_IFGITLINK => 0160000, +}; + +# submodule/subproject, a commit object reference +sub S_ISGITLINK($) { + my $mode = shift; + + return (($mode & S_IFMT) == S_IFGITLINK) +} + # convert file mode in octal to symbolic file mode string sub mode_str { my $mode = oct shift; - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return 'm---------'; + } elsif (S_ISDIR($mode & S_IFMT)) { return 'drwxr-xr-x'; } elsif (S_ISLNK($mode)) { return 'lrwxrwxrwx'; @@ -854,7 +936,9 @@ sub file_type { $mode = oct $mode; } - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return "submodule"; + } elsif (S_ISDIR($mode & S_IFMT)) { return "directory"; } elsif (S_ISLNK($mode)) { return "symlink"; @@ -875,7 +959,9 @@ sub file_type_long { $mode = oct $mode; } - if (S_ISDIR($mode & S_IFMT)) { + if (S_ISGITLINK($mode)) { + return "submodule"; + } elsif (S_ISDIR($mode & S_IFMT)) { return "directory"; } elsif (S_ISLNK($mode)) { return "symlink"; @@ -1122,6 +1208,31 @@ sub format_diff_from_to_header { return $result; } +# create note for patch simplified by combined diff +sub format_diff_cc_simplified { + my ($diffinfo, @parents) = @_; + my $result = ''; + + $result .= "
" . + "diff --cc "; + if (!is_deleted($diffinfo)) { + $result .= $cgi->a({-href => href(action=>"blob", + hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, + file_name=>$diffinfo->{'to_file'}), + -class => "path"}, + esc_path($diffinfo->{'to_file'})); + } else { + $result .= esc_path($diffinfo->{'to_file'}); + } + $result .= "
\n" . # class="diff header" + "
" . + "Simple merge" . + "
\n"; # class="diff nodifferences" + + return $result; +} + # format patch (diff) line (not to be used for diff headers) sub format_diff_line { my $line = shift; @@ -1211,6 +1322,43 @@ sub format_diff_line { return "
" . esc_html($line, -nbsp=>1) . "
\n"; } +# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)", +# linked. Pass the hash of the tree/commit to snapshot. +sub format_snapshot_links { + my ($hash) = @_; + my @snapshot_fmts = gitweb_check_feature('snapshot'); + @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); + my $num_fmts = @snapshot_fmts; + if ($num_fmts > 1) { + # A parenthesized list of links bearing format names. + # e.g. "snapshot (_tar.gz_ _zip_)" + return "snapshot (" . join(' ', map + $cgi->a({ + -href => href( + action=>"snapshot", + hash=>$hash, + snapshot_format=>$_ + ) + }, $known_snapshot_formats{$_}{'display'}) + , @snapshot_fmts) . ")"; + } elsif ($num_fmts == 1) { + # A single "snapshot" link whose tooltip bears the format name. + # i.e. "_snapshot_" + my ($fmt) = @snapshot_fmts; + return + $cgi->a({ + -href => href( + action=>"snapshot", + hash=>$hash, + snapshot_format=>$fmt + ), + -title => "in format: $known_snapshot_formats{$fmt}{'display'}" + }, "snapshot"); + } else { # $num_fmts == 0 + return undef; + } +} + ## ---------------------------------------------------------------------- ## git utility subroutines, invoking git commands @@ -1364,6 +1512,7 @@ sub git_get_projects_list { File::Find::find({ follow_fast => 1, # follow symbolic links + follow_skip => 2, # ignore duplicates dangling_symlinks => 0, # ignore dangling symlinks, silently wanted => sub { # skip project-list toplevel, if we get it. @@ -1440,12 +1589,12 @@ sub git_get_projects_list { return @list; } -sub git_get_project_owner { - my $project = shift; - my $owner; +our $gitweb_project_owner = undef; +sub git_get_project_list_from_file { - return undef unless $project; + return if (defined $gitweb_project_owner); + $gitweb_project_owner = {}; # read from file (url-encoded): # 'git%2Fgit.git Linus+Torvalds' # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' @@ -1457,13 +1606,25 @@ sub git_get_project_owner { my ($pr, $ow) = split ' ', $line; $pr = unescape($pr); $ow = unescape($ow); - if ($pr eq $project) { - $owner = to_utf8($ow); - last; - } + $gitweb_project_owner->{$pr} = to_utf8($ow); } close $fd; } +} + +sub git_get_project_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + if (!defined $gitweb_project_owner) { + git_get_project_list_from_file(); + } + + if (exists $gitweb_project_owner->{$project}) { + $owner = $gitweb_project_owner->{$project}; + } if (!defined $owner) { $owner = get_file_owner("$projectroot/$project"); } @@ -1489,6 +1650,7 @@ sub git_get_last_activity { my $age = time - $timestamp; return ($age, age_string($age)); } + return (undef, undef); } sub git_get_references { @@ -1732,6 +1894,7 @@ sub parse_commits { ($arg ? ($arg) : ()), ("--max-count=" . $maxcount), ("--skip=" . $skip), + @extra_options, $commit_id, "--", ($filename ? ($filename) : ()) @@ -2125,9 +2288,17 @@ EOF printf(''."\n", esc_param($project), href(action=>"rss")); + printf(''."\n", + esc_param($project), href(action=>"rss", + extra_options=>"--no-merges")); printf(''."\n", esc_param($project), href(action=>"atom")); + printf(''."\n", + esc_param($project), href(action=>"atom", + extra_options=>"--no-merges")); } else { printf(''."\n", @@ -2176,12 +2347,18 @@ EOF } else { $search_hash = "HEAD"; } + my $action = $my_uri; + my ($use_pathinfo) = gitweb_check_feature('pathinfo'); + if ($use_pathinfo) { + $action .= "/$project"; + } else { + $cgi->param("p", $project); + } $cgi->param("a", "search"); $cgi->param("h", $search_hash); - $cgi->param("p", $project); - print $cgi->startform(-method => "get", -action => $my_uri) . + print $cgi->startform(-method => "get", -action => $action) . "
\n" . - $cgi->hidden(-name => "p") . "\n" . + (!$use_pathinfo && $cgi->hidden(-name => "p") . "\n") . $cgi->hidden(-name => "a") . "\n" . $cgi->hidden(-name => "h") . "\n" . $cgi->popup_menu(-name => 'st', -default => 'commit', @@ -2559,6 +2736,20 @@ sub git_print_tree_entry { "history"); } print "\n"; + } else { + # unknown object: we can only present history for it + # (this includes 'commit' object, i.e. submodule support) + print "" . + esc_path($t->{'name'}) . + "\n"; + print ""; + if (defined $hash_base) { + print $cgi->a({-href => href(action=>"history", + hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print "\n"; } } @@ -2973,13 +3164,33 @@ sub git_patchset_body { # advance raw git-diff output if needed $patch_idx++ if defined $diffinfo; - # 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]); + # compact combined diff output can have some patches skipped + # find which patch (using pathname of result) we are at now + my $to_name; + if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) { + $to_name = $1; } + + do { + # 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]); + } + + # check if current raw line has no patch (it got simplified) + if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) { + print "
\n" . + format_diff_cc_simplified($diffinfo, @hash_parents) . + "
\n"; # class="patch" + + $patch_idx++; + $patch_number++; + } + } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || + $patch_idx > $#$difftree); # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); if ($diffinfo->{'nparents'}) { @@ -3069,6 +3280,27 @@ sub git_patchset_body { print "
\n"; # class="patch" } + # for compact combined (--cc) format, with chunk and patch simpliciaction + # patchset might be empty, but there might be unprocessed raw lines + for ($patch_idx++ if $patch_number > 0; + $patch_idx < @$difftree; + $patch_idx++) { + # 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]); + } + + # generate anchor for "patch" links in difftree / whatchanged part + print "
\n" . + format_diff_cc_simplified($diffinfo, @hash_parents) . + "
\n"; # class="patch" + + $patch_number++; + } + if ($patch_number == 0) { if (@hash_parents > 1) { print "
Trivial merge
\n"; @@ -3097,10 +3329,10 @@ sub git_project_list_body { if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr_long'} = to_utf8($descr); - $pr->{'descr'} = chop_str($descr, 25, 5); + $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5); } if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || ""; } if ($check_forks) { my $pname = $pr->{'path'}; @@ -3187,7 +3419,7 @@ sub git_project_list_body { "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list", -title => $pr->{'descr_long'}}, esc_html($pr->{'descr'})) . "\n" . - "" . chop_str($pr->{'owner'}, 15) . "\n"; + "" . esc_html(chop_str($pr->{'owner'}, 15)) . "\n"; print "{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "\n" . "" . @@ -3214,8 +3446,6 @@ sub git_shortlog_body { # uses global variable $project my ($commitlist, $from, $to, $refs, $extra) = @_; - my $have_snapshot = gitweb_have_snapshot(); - $from = 0 unless defined $from; $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); @@ -3242,8 +3472,9 @@ 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 ($have_snapshot) { - print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + my $snapshot_links = format_snapshot_links($commit); + if (defined $snapshot_links) { + print " | " . $snapshot_links; } print "\n" . "\n"; @@ -3524,7 +3755,7 @@ sub git_project_index { foreach my $pr (@projects) { if (!exists $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}"); + $pr->{'owner'} = git_get_project_owner("$pr->{'path'}"); } my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'}); @@ -3564,7 +3795,7 @@ sub git_summary { print "
 
\n"; print "\n" . "\n" . - "\n"; + "\n"; if (defined $cd{'rfc2822'}) { print "\n"; } @@ -4025,8 +4256,6 @@ sub git_blob { } sub git_tree { - my $have_snapshot = gitweb_have_snapshot(); - if (!defined $hash_base) { $hash_base = "HEAD"; } @@ -4060,11 +4289,10 @@ sub git_tree { hash_base=>"HEAD", file_name=>$file_name)}, "HEAD"), } - if ($have_snapshot) { + my $snapshot_links = format_snapshot_links($hash); + if (defined $snapshot_links) { # FIXME: Should be available when we have no hash base as well. - push @views_nav, - $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, - "snapshot"); + push @views_nav, $snapshot_links; } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); @@ -4128,31 +4356,44 @@ sub git_tree { } sub git_snapshot { - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); - if (!$have_snapshot) { + my @supported_fmts = gitweb_check_feature('snapshot'); + @supported_fmts = filter_snapshot_fmts(@supported_fmts); + + my $format = $cgi->param('sf'); + if (!@supported_fmts) { die_error('403 Permission denied', "Permission denied"); } + # default to first supported snapshot format + $format ||= $supported_fmts[0]; + if ($format !~ m/^[a-z0-9]+$/) { + die_error(undef, "Invalid snapshot format parameter"); + } elsif (!exists($known_snapshot_formats{$format})) { + die_error(undef, "Unknown snapshot format"); + } elsif (!grep($_ eq $format, @supported_fmts)) { + die_error(undef, "Unsupported snapshot format"); + } if (!defined $hash) { $hash = git_get_head_hash($project); } - my $git = git_cmd_str(); + my $git_command = git_cmd_str(); my $name = $project; + $name =~ s,([^/])/*\.git$,$1,; + $name = basename($name); + my $filename = to_utf8($name); $name =~ s/\047/\047\\\047\047/g; - my $filename = to_utf8(basename($project)); my $cmd; - if ($suffix eq 'zip') { - $filename .= "-$hash.$suffix"; - $cmd = "$git archive --format=zip --prefix=\'$name\'/ $hash"; - } else { - $filename .= "-$hash.tar.$suffix"; - $cmd = "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"; + $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; + $cmd = "$git_command archive " . + "--format=$known_snapshot_formats{$format}{'format'} " . + "--prefix=\'$name\'/ $hash"; + if (exists $known_snapshot_formats{$format}{'compressor'}) { + $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}}; } print $cgi->header( - -type => "application/$ctype", + -type => $known_snapshot_formats{$format}{'type'}, -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); @@ -4162,7 +4403,6 @@ sub git_snapshot { print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi close $fd; - } sub git_log { @@ -4281,8 +4521,6 @@ sub git_commit { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my $have_snapshot = gitweb_have_snapshot(); - git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, @@ -4321,9 +4559,9 @@ sub git_commit { "" . "\n"; @@ -4582,7 +4820,11 @@ sub git_commitdiff { die_error(undef, "Unknown commit object"); } - # we need to prepare $formats_nav before any parameter munging + # choose format for commitdiff for merge + if (! defined $hash_parent && @{$co{'parents'}} > 1) { + $hash_parent = '--cc'; + } + # we need to prepare $formats_nav before almost any parameter munging my $formats_nav; if ($format eq 'html') { $formats_nav = @@ -4590,7 +4832,8 @@ sub git_commitdiff { hash=>$hash, hash_parent=>$hash_parent)}, "raw"); - if (defined $hash_parent) { + if (defined $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}$/) { @@ -4622,6 +4865,17 @@ sub git_commitdiff { ')'; } else { # merge commit + if ($hash_parent eq '--cc') { + $formats_nav .= ' | ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$hash, hash_parent=>'-c')}, + 'combined'); + } else { # $hash_parent eq '-c' + $formats_nav .= ' | ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$hash, hash_parent=>'--cc')}, + 'compact'); + } $formats_nav .= ' (merge: ' . join(' ', map { @@ -4634,9 +4888,10 @@ sub git_commitdiff { } my $hash_parent_param = $hash_parent; - if (!defined $hash_parent) { + if (!defined $hash_parent_param) { + # --cc for multiple parents, --root for parentless $hash_parent_param = - @{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root'; + @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root'; } # read commitdiff @@ -4713,10 +4968,14 @@ TEXT # write patch if ($format eq 'html') { - git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}}); + my $use_parents = !defined $hash_parent || + $hash_parent eq '-c' || $hash_parent eq '--cc'; + git_difftree_body(\@difftree, $hash, + $use_parents ? @{$co{'parents'}} : $hash_parent); print "
\n"; - git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}}); + git_patchset_body($fd, \@difftree, $hash, + $use_parents ? @{$co{'parents'}} : $hash_parent); close $fd; print "\n"; # class="page_body" git_footer_html(); @@ -5104,7 +5363,7 @@ sub git_feed { # log/feed of current (HEAD) branch, log of given branch, history of file/directory my $head = $hash || 'HEAD'; - my @commitlist = parse_commits($head, 150); + my @commitlist = parse_commits($head, 150, 0, undef, $file_name); my %latest_commit; my %latest_date;
description" . esc_html($descr) . "
owner$owner
owner" . esc_html($owner) . "
last change$cd{'rfc2822'}
" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree"); - if ($have_snapshot) { - print " | " . - $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); + my $snapshot_links = format_snapshot_links($hash); + if (defined $snapshot_links) { + print " | " . $snapshot_links; } print "