+
+ if ($diff{'status'} eq "A") { # created
+ my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
+ $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
+ $mode_chng .= "]</span>";
+ print "<td>" .
+ $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'})) .
+ "</td>\n" .
+ "<td>$mode_chng</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob");
+ if ($action == "commitdiff") {
+ # link to patch
+ $patchno++;
+ print " | " .
+ $cgi->a({-href => "#patch$patchno"}, "patch");
+ }
+ print "</td>\n";
+
+ } elsif ($diff{'status'} eq "D") { # deleted
+ my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
+ print "<td>" .
+ $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'})) .
+ "</td>\n" .
+ "<td>$mode_chng</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'file'})},
+ "blob") .
+ " | ";
+ if ($action == "commitdiff") {
+ # link to patch
+ $patchno++;
+ print " | " .
+ $cgi->a({-href => "#patch$patchno"}, "patch");
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "history") .
+ "</td>\n";
+
+ } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
+ my $mode_chnge = "";
+ if ($diff{'from_mode'} != $diff{'to_mode'}) {
+ $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
+ if ($from_file_type != $to_file_type) {
+ $mode_chnge .= " from $from_file_type to $to_file_type";
+ }
+ if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
+ if ($from_mode_str && $to_mode_str) {
+ $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
+ } elsif ($to_mode_str) {
+ $mode_chnge .= " mode: $to_mode_str";
+ }
+ }
+ $mode_chnge .= "]</span>\n";
+ }
+ print "<td>";
+ if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
+ 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'}),
+ -class => "list"}, esc_html($diff{'file'}));
+ } else { # only mode changed
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'}));
+ }
+ print "</td>\n" .
+ "<td>$mode_chnge</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob");
+ if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
+ if ($action == "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 " | " .
+ $cgi->a({-href => href(action=>"history",
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "history");
+ print "</td>\n";
+
+ } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
+ my %status_name = ('R' => 'moved', 'C' => 'copied');
+ my $nstatus = $status_name{$diff{'status'}};
+ my $mode_chng = "";
+ if ($diff{'from_mode'} != $diff{'to_mode'}) {
+ # mode also for directories, so we cannot use $to_mode_str
+ $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
+ }
+ print "<td>" .
+ $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'})) . "</td>\n" .
+ "<td><span class=\"file_status $nstatus\">[$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'})) .
+ " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})},
+ "blob");
+ if ($diff{'to_id'} ne $diff{'from_id'}) {
+ if ($action == "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 "</td>\n";
+
+ } # we should not encounter Unmerged (U) or Unknown (X) status
+ print "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_patchset_body {
+ my ($fd, $difftree, $hash, $hash_parent) = @_;
+
+ my $patch_idx = 0;
+ my $in_header = 0;
+ my $patch_found = 0;
+ my $diffinfo;
+
+ print "<div class=\"patchset\">\n";
+
+ LINE:
+ while (my $patch_line = <$fd>) {
+ chomp $patch_line;
+
+ if ($patch_line =~ m/^diff /) { # "git diff" header
+ # beginning of patch (in patchset)
+ if ($patch_found) {
+ # close previous patch
+ print "</div>\n"; # class="patch"
+ } else {
+ # first patch in patchset
+ $patch_found = 1;
+ }
+ print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ $diffinfo = $difftree->[$patch_idx];
+ } else {
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ }
+ $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;
+ }
+
+ if ($diffinfo->{'status'} eq "A") { # added
+ print "<div class=\"diff_info\">" . 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)" .
+ "</div>\n"; # class="diff_info"
+
+ } elsif ($diffinfo->{'status'} eq "D") { # deleted
+ print "<div class=\"diff_info\">" . 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)" .
+ "</div>\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 "<div class=\"diff_info\">" .
+ 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 "</div>\n"; # class="diff_info"
+
+ } else { # modified, mode changed, ...
+ print "<div class=\"diff_info\">" .
+ 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 "</div>\n"; # class="diff_info"
+ }
+
+ #print "<div class=\"diff extended_header\">\n";
+ $in_header = 1;
+ next LINE;
+ } # start of patch in patchset
+
+
+ if ($in_header && $patch_line =~ m/^---/) {
+ #print "</div>\n"; # class="diff extended_header"
+ $in_header = 0;
+
+ 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 "<div class=\"diff from_file\">$patch_line</div>\n";
+
+ $patch_line = <$fd>;
+ chomp $patch_line;
+
+ #$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 "<div class=\"diff to_file\">$patch_line</div>\n";
+
+ next LINE;
+ }
+ next LINE if $in_header;
+
+ print format_diff_line($patch_line);
+ }
+ print "</div>\n" if $patch_found; # class="patch"
+
+ print "</div>\n"; # class="patchset"
+}
+
+# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+sub git_shortlog_body {
+ # uses global variable $project
+ my ($revlist, $from, $to, $refs, $extra) = @_;
+
+ my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
+ my $have_snapshot = (defined $ctype && defined $suffix);
+
+ $from = 0 unless defined $from;
+ $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+
+ print "<table class=\"shortlog\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $commit = $revlist->[$i];
+ #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
+ my $ref = format_ref_marker($refs, $commit);
+ my %co = parse_commit($commit);
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
+ "<td>";
+ print format_subject_html($co{'title'}, $co{'title_short'},
+ href(action=>"commit", hash=>$commit), $ref);
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
+ if ($have_snapshot) {
+ print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+ }
+ print "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"4\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_history_body {
+ # Warning: assumes constant type (blob or tree) during history
+ my ($fd, $refs, $hash_base, $ftype, $extra) = @_;
+
+ print "<table class=\"history\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ while (my $line = <$fd>) {
+ if ($line !~ m/^([0-9a-fA-F]{40})/) {
+ next;
+ }
+
+ my $commit = $1;
+ my %co = parse_commit($commit);
+ if (!%co) {
+ next;
+ }
+
+ my $ref = format_ref_marker($refs, $commit);
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ # shortlog uses chop_str($co{'author_name'}, 10)
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
+ "<td>";
+ # originally git_history used chop_str($co{'title'}, 50)
+ print format_subject_html($co{'title'}, $co{'title_short'},
+ href(action=>"commit", hash=>$commit), $ref);
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
+ $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype);
+
+ if ($ftype eq 'blob') {
+ my $blob_current = git_get_hash_by_path($hash_base, $file_name);
+ my $blob_parent = git_get_hash_by_path($commit, $file_name);
+ if (defined $blob_current && defined $blob_parent &&
+ $blob_current ne $blob_parent) {
+ print " | " .
+ $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$blob_current, hash_parent=>$blob_parent,
+ hash_base=>$hash_base, hash_parent_base=>$commit,
+ file_name=>$file_name)},
+ "diff to current");
+ }
+ }
+ print "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"4\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_tags_body {
+ # uses global variable $project
+ my ($taglist, $from, $to, $extra) = @_;
+ $from = 0 unless defined $from;
+ $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+
+ print "<table class=\"tags\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $entry = $taglist->[$i];
+ my %tag = %$entry;
+ my $comment_lines = $tag{'comment'};
+ my $comment = shift @$comment_lines;
+ my $comment_short;
+ if (defined $comment) {
+ $comment_short = chop_str($comment, 30, 5);
+ }
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td><i>$tag{'age'}</i></td>\n" .
+ "<td>" .
+ $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
+ -class => "list name"}, esc_html($tag{'name'})) .
+ "</td>\n" .
+ "<td>";
+ if (defined $comment) {
+ print format_subject_html($comment, $comment_short,
+ href(action=>"tag", hash=>$tag{'id'}));
+ }
+ print "</td>\n" .
+ "<td class=\"selflink\">";
+ if ($tag{'type'} eq "tag") {
+ print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
+ } else {
+ print " ";
+ }
+ print "</td>\n" .