Skip to content

Commit 6ba1eb5

Browse files
kzysgitster
authored andcommitted
gitweb: Add a feature to show side-by-side diff
This commits adds to support for showing "side-by-side" style diff. Currently you have to hand-craft the URL; navigation for selecting diff style is to be added in the next commit. The diff output in unified format from "git diff-tree" is reorganized to side-by-side style chunk by chunk with format_sidebyside_diff_chunk(). This reorganization requires knowledge about diff line classification, so format_diff_line() was renamed to process_diff_line(), and changed to return tuple (list) consisting of class of diff line and of HTML-formatted (but not wrapped in <div class="diff ...">...</div>) diff line. Wrapping is now done by caller, i.e. git_patchset_body(). Gitweb uses float+margin CSS-based layout for "side by side" diff. You can specify style of diff with "ds" ('diff_style') query parameter. Currently supported values are 'inline' and 'sidebyside'; the default is 'inline'. Another solution would be to use "opt" ('extra_options') for that... though current use of it in gitweb seems to suggest that "opt" is more about passing extra options to underlying git commands, and "git diff" doesn't support '--side-by-side' like GNU diff does, (yet?). Signed-off-by: Kato Kazuyoshi <[email protected]> Signed-off-by: Jakub Narebski <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f1310cf commit 6ba1eb5

File tree

2 files changed

+122
-11
lines changed

2 files changed

+122
-11
lines changed

gitweb/gitweb.perl

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ sub check_loadavg {
759759
extra_options => "opt",
760760
search_use_regexp => "sr",
761761
ctag => "by_tag",
762+
diff_style => "ds",
762763
# this must be last entry (for manipulation from JavaScript)
763764
javascript => "js"
764765
);
@@ -2317,28 +2318,27 @@ sub format_cc_diff_chunk_header {
23172318
return $line;
23182319
}
23192320

2320-
# format patch (diff) line (not to be used for diff headers)
2321-
sub format_diff_line {
2321+
# process patch (diff) line (not to be used for diff headers),
2322+
# returning class and HTML-formatted (but not wrapped) line
2323+
sub process_diff_line {
23222324
my $line = shift;
23232325
my ($from, $to) = @_;
23242326

23252327
my $diff_class = diff_line_class($line, $from, $to);
2326-
my $diff_classes = "diff";
2327-
$diff_classes .= " $diff_class" if ($diff_class);
23282328

23292329
chomp $line;
23302330
$line = untabify($line);
23312331

23322332
if ($from && $to && $line =~ m/^\@{2} /) {
23332333
$line = format_unidiff_chunk_header($line, $from, $to);
2334-
return "<div class=\"$diff_classes\">$line</div>\n";
2334+
return $diff_class, $line;
23352335

23362336
} elsif ($from && $to && $line =~ m/^\@{3}/) {
23372337
$line = format_cc_diff_chunk_header($line, $from, $to);
2338-
return "<div class=\"$diff_classes\">$line</div>\n";
2338+
return $diff_class, $line;
23392339

23402340
}
2341-
return "<div class=\"$diff_classes\">" . esc_html($line, -nbsp=>1) . "</div>\n";
2341+
return $diff_class, esc_html($line, -nbsp=>1);
23422342
}
23432343

23442344
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -4860,8 +4860,78 @@ sub git_difftree_body {
48604860
print "</table>\n";
48614861
}
48624862

4863+
sub print_sidebyside_diff_chunk {
4864+
my @chunk = @_;
4865+
my (@ctx, @rem, @add);
4866+
4867+
return unless @chunk;
4868+
4869+
# incomplete last line might be among removed or added lines,
4870+
# or both, or among context lines: find which
4871+
for (my $i = 1; $i < @chunk; $i++) {
4872+
if ($chunk[$i][0] eq 'incomplete') {
4873+
$chunk[$i][0] = $chunk[$i-1][0];
4874+
}
4875+
}
4876+
4877+
# guardian
4878+
push @chunk, ["", ""];
4879+
4880+
foreach my $line_info (@chunk) {
4881+
my ($class, $line) = @$line_info;
4882+
4883+
# print chunk headers
4884+
if ($class && $class eq 'chunk_header') {
4885+
print $line;
4886+
next;
4887+
}
4888+
4889+
## print from accumulator when type of class of lines change
4890+
# empty contents block on start rem/add block, or end of chunk
4891+
if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
4892+
print join '',
4893+
'<div class="chunk_block">',
4894+
'<div class="old">',
4895+
@ctx,
4896+
'</div>',
4897+
'<div class="new">',
4898+
@ctx,
4899+
'</div>',
4900+
'</div>';
4901+
@ctx = ();
4902+
}
4903+
# empty add/rem block on start context block, or end of chunk
4904+
if ((@rem || @add) && (!$class || $class eq 'ctx')) {
4905+
print join '',
4906+
'<div class="chunk_block">',
4907+
'<div class="old">',
4908+
@rem,
4909+
'</div>',
4910+
'<div class="new">',
4911+
@add,
4912+
'</div>',
4913+
'</div>';
4914+
@rem = @add = ();
4915+
}
4916+
4917+
## adding lines to accumulator
4918+
# guardian value
4919+
last unless $line;
4920+
# rem, add or change
4921+
if ($class eq 'rem') {
4922+
push @rem, $line;
4923+
} elsif ($class eq 'add') {
4924+
push @add, $line;
4925+
}
4926+
# context line
4927+
if ($class eq 'ctx') {
4928+
push @ctx, $line;
4929+
}
4930+
}
4931+
}
4932+
48634933
sub git_patchset_body {
4864-
my ($fd, $difftree, $hash, @hash_parents) = @_;
4934+
my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
48654935
my ($hash_parent) = $hash_parents[0];
48664936

48674937
my $is_combined = (@hash_parents > 1);
@@ -4871,6 +4941,7 @@ sub git_patchset_body {
48714941
my $diffinfo;
48724942
my $to_name;
48734943
my (%from, %to);
4944+
my @chunk; # for side-by-side diff
48744945

48754946
print "<div class=\"patchset\">\n";
48764947

@@ -4977,10 +5048,29 @@ sub git_patchset_body {
49775048

49785049
next PATCH if ($patch_line =~ m/^diff /);
49795050

4980-
print format_diff_line($patch_line, \%from, \%to);
5051+
my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
5052+
my $diff_classes = "diff";
5053+
$diff_classes .= " $class" if ($class);
5054+
$line = "<div class=\"$diff_classes\">$line</div>\n";
5055+
5056+
if ($diff_style eq 'sidebyside' && !$is_combined) {
5057+
if ($class eq 'chunk_header') {
5058+
print_sidebyside_diff_chunk(@chunk);
5059+
@chunk = ( [ $class, $line ] );
5060+
} else {
5061+
push @chunk, [ $class, $line ];
5062+
}
5063+
} else {
5064+
# default 'inline' style and unknown styles
5065+
print $line;
5066+
}
49815067
}
49825068

49835069
} continue {
5070+
if (@chunk) {
5071+
print_sidebyside_diff_chunk(@chunk);
5072+
@chunk = ();
5073+
}
49845074
print "</div>\n"; # class="patch"
49855075
}
49865076

@@ -6976,6 +7066,7 @@ sub git_object {
69767066

69777067
sub git_blobdiff {
69787068
my $format = shift || 'html';
7069+
my $diff_style = $input_params{'diff_style'} || 'inline';
69797070

69807071
my $fd;
69817072
my @difftree;
@@ -7085,7 +7176,8 @@ sub git_blobdiff {
70857176
if ($format eq 'html') {
70867177
print "<div class=\"page_body\">\n";
70877178

7088-
git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
7179+
git_patchset_body($fd, $diff_style,
7180+
[ \%diffinfo ], $hash_base, $hash_parent_base);
70897181
close $fd;
70907182

70917183
print "</div>\n"; # class="page_body"
@@ -7113,6 +7205,7 @@ sub git_blobdiff_plain {
71137205
sub git_commitdiff {
71147206
my %params = @_;
71157207
my $format = $params{-format} || 'html';
7208+
my $diff_style = $input_params{'diff_style'} || 'inline';
71167209

71177210
my ($patch_max) = gitweb_get_feature('patches');
71187211
if ($format eq 'patch') {
@@ -7316,7 +7409,8 @@ sub git_commitdiff {
73167409
$use_parents ? @{$co{'parents'}} : $hash_parent);
73177410
print "<br/>\n";
73187411

7319-
git_patchset_body($fd, \@difftree, $hash,
7412+
git_patchset_body($fd, $diff_style,
7413+
\@difftree, $hash,
73207414
$use_parents ? @{$co{'parents'}} : $hash_parent);
73217415
close $fd;
73227416
print "</div>\n"; # class="page_body"

gitweb/static/gitweb.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,23 @@ div.diff.nodifferences {
475475
color: #600000;
476476
}
477477

478+
/* side-by-side diff */
479+
div.chunk_block {
480+
overflow: hidden;
481+
}
482+
483+
div.chunk_block div.old {
484+
float: left;
485+
width: 50%;
486+
overflow: hidden;
487+
}
488+
489+
div.chunk_block div.new {
490+
margin-left: 50%;
491+
width: 50%;
492+
}
493+
494+
478495
div.index_include {
479496
border: solid #d9d8d1;
480497
border-width: 0px 0px 1px;

0 commit comments

Comments
 (0)