+
+ my ($to_mode_oct, $to_mode_str, $to_file_type);
+ my ($from_mode_oct, $from_mode_str, $from_file_type);
+ if ($diff{'to_mode'} ne ('0' x 6)) {
+ $to_mode_oct = oct $diff{'to_mode'};
+ if (S_ISREG($to_mode_oct)) { # only for regular file
+ $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
+ }
+ $to_file_type = file_type($diff{'to_mode'});
+ }
+ if ($diff{'from_mode'} ne ('0' x 6)) {
+ $from_mode_oct = oct $diff{'from_mode'};
+ if (S_ISREG($to_mode_oct)) { # only for regular file
+ $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
+ }
+ $from_file_type = file_type($diff{'from_mode'});
+ }
+
+ 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 eq '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 eq '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 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 " | " .
+ $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 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 "</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"