Skip to content

Commit f4fc36a

Browse files
Merge branch 'next'
2 parents d7dcc17 + c9df4a5 commit f4fc36a

17 files changed

+557
-1623
lines changed

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
Vanilla `git diff` vs `git` and `diff-so-fancy`
88

9-
![diff-highlight vs diff-so-fancy](https://user-images.githubusercontent.com/3429760/32387617-44c873da-c082-11e7-829c-6160b853adcb.png)
9+
![diff-highlight vs diff-so-fancy](diff-so-fancy.png)
1010

1111
## Install
1212

1313
Installation is as simple as cloning this repo and then putting the `diff-so-fancy` script in to your `$PATH`. The `lib/` directory will need to be kept relative to the core script.
1414

15-
`diff-so-fancy` is also available from the [NPM registry](https://www.npmjs.com/package/diff-so-fancy), [brew](https://formulae.brew.sh/formula/diff-so-fancy), and as a package on [Nix](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/diff-so-fancy/default.nix), [Fedora](https://packages.fedoraproject.org/pkgs/diff-so-fancy/diff-so-fancy/), in the [Arch community repo](https://archlinux.org/packages/community/any/diff-so-fancy/), [Gentoo Guru](https://github.com/gentoo/guru/tree/master/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian).
15+
`diff-so-fancy` is also available from the [NPM registry](https://www.npmjs.com/package/diff-so-fancy), [brew](https://formulae.brew.sh/formula/diff-so-fancy), as a package on [Nix](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/git-and-tools/diff-so-fancy/default.nix), in the [Arch community repo](https://archlinux.org/packages/community/any/diff-so-fancy/), as [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian), and as [Fedora COPR repository](https://copr.fedorainfracloud.org/coprs/kopfkrieg/diff-so-fancy/).
1616

1717
Issues relating to packaging ('installation does not work', 'version is out of date', etc.) should be directed to those packages' own repositories/issue trackers where applicable.
1818

@@ -58,6 +58,15 @@ Use `-u` with `diff` for unified output, and pipe the output to `diff-so-fancy`:
5858
diff -u file_a file_b | diff-so-fancy
5959
```
6060

61+
It also supports the recursive mode of diff with `-r` or `--recursive` as **first argument**
62+
63+
```shell
64+
diff -r -u folder_a folder_b | diff-so-fancy
65+
```
66+
67+
```shell
68+
diff --recursive -u folder_a folder_b | diff-so-fancy
69+
```
6170
## Options
6271

6372
### markEmptyLines

diff-so-fancy

Lines changed: 108 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#!/usr/bin/env perl
22

3-
my $VERSION = "1.4.2";
3+
my $VERSION = "1.4.4";
44

55
#################################################################################
66

7-
use v5.010; # Require Perl 5.10 for 'state' variables
7+
use v5.014; # Require Perl 5.14 for 'state' variables and /u in regexes
88
use warnings FATAL => 'all';
99
use strict;
1010

@@ -26,6 +26,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul
2626
my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef);
2727
my $git_strip_prefix = git_config_boolean("diff.noprefix","false");
2828
my $has_stdin = has_stdin();
29+
my $CONTEXT_LINES = undef; # Number of lines of context diff used
2930

3031
my $ansi_regex = qr/\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK]/;
3132
my $ansi_color_regex = qr/(${ansi_regex})?/;
@@ -139,6 +140,11 @@ do_dsf_stuff(\@lines);
139140
sub do_dsf_stuff {
140141
my $input = shift();
141142

143+
# Calculate the context lines the first time
144+
if (!defined $CONTEXT_LINES) {
145+
$CONTEXT_LINES = calculate_context_lines(@lines);
146+
}
147+
142148
#print STDERR "START -------------------------------------------------\n";
143149
#print STDERR join("",@$input);
144150
#print STDERR "END ---------------------------------------------------\n";
@@ -192,11 +198,11 @@ sub do_dsf_stuff {
192198
#########################
193199
# Look for the filename #
194200
#########################
195-
# $4 $5
196-
} elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) {
201+
# $4 $5
202+
} elsif ($line =~ /^${ansi_color_regex}diff (-r|--recursive|--git|--cc) (.*?)(\e| b\/|$)/) {
197203

198204
# Mercurial looks like: diff -r 82e55d328c8c hello.c
199-
if ($4 eq "-r") {
205+
if ($4 eq "-r" || $4 eq "--recursive") {
200206
$is_mercurial = 1;
201207
$meta_color = get_config_color("meta");
202208
# Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy
@@ -300,11 +306,11 @@ sub do_dsf_stuff {
300306
print $frag_color;
301307
}
302308

303-
print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n";
309+
print "@ $last_file_seen:$start_line \@${reset_color}${last_function_color}${remain}${reset_color}\n";
304310
###################################
305311
# Remove any new file permissions #
306312
###################################
307-
} elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) {
313+
} elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}new file mode [0-7]{6}/) {
308314
# Don't print the line (i.e. remove it from the output);
309315
$last_file_mode = "add";
310316
if ($patch_mode) {
@@ -313,7 +319,7 @@ sub do_dsf_stuff {
313319
######################################
314320
# Remove any delete file permissions #
315321
######################################
316-
} elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) {
322+
} elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode [0-7]{6}/) {
317323
# Don't print the line (i.e. remove it from the output);
318324
$last_file_mode = "delete";
319325
if ($patch_mode) {
@@ -448,7 +454,7 @@ sub boolean {
448454

449455
# Get the git config
450456
sub git_config_raw {
451-
my $cmd = "git config --list";
457+
my $cmd = "git config --list 2>&1";
452458
my @out = `$cmd`;
453459

454460
return \@out;
@@ -491,26 +497,12 @@ sub git_config_boolean {
491497
my $search_key = lc($_[0] || "");
492498
my $default_value = lc($_[1] || 0); # Default to false
493499

494-
# If we're in a unit test, use the default (don't read the users config)
495-
if (in_unit_test()) {
496-
return boolean($default_value);
497-
}
498-
499500
my $result = git_config($search_key,$default_value);
500501
my $ret = boolean($result);
501502

502503
return $ret;
503504
}
504505

505-
# Check if we're inside of BATS
506-
sub in_unit_test {
507-
if ($ENV{BATS_CWD}) {
508-
return 1;
509-
} else {
510-
return 0;
511-
}
512-
}
513-
514506
sub get_less_charset {
515507
my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG");
516508
foreach my $key (@less_char_vars) {
@@ -548,16 +540,14 @@ sub start_line_calc {
548540
return 1;
549541
}
550542

551-
# Git defaults to three lines of context
552-
my $default_context_lines = 3;
553543
# Three lines on either side, and the line itself = 7
554-
my $expected_context = ($default_context_lines * 2 + 1);
544+
my $expected_context = ($CONTEXT_LINES * 2 + 1);
555545

556546
# The first three lines
557547
if ($line_num == 1 && $diff_context < $expected_context) {
558-
$ret = $diff_context - $default_context_lines;
548+
$ret = $diff_context - $CONTEXT_LINES;
559549
} else {
560-
$ret = $line_num + $default_context_lines;
550+
$ret = $line_num + $CONTEXT_LINES;
561551
}
562552

563553
if ($ret < 1) {
@@ -904,6 +894,7 @@ sub git_ansi_color {
904894
if (!@parts) {
905895
return '';
906896
}
897+
907898
my $colors = {
908899
'black' => 0,
909900
'red' => 1,
@@ -913,71 +904,74 @@ sub git_ansi_color {
913904
'magenta' => 5,
914905
'cyan' => 6,
915906
'white' => 7,
907+
'default' => 9, # pseudo color (39/49 = set default)
908+
'normal' => -1, # placeholder color to be ignored
916909
};
917910

911+
# Bright colors are just offsets from the "regular" color
912+
for my $k (keys %{ $colors }) {
913+
$colors->{"bright" . $k} = $colors->{$k} + 60;
914+
}
915+
918916
my @ansi_part = ();
919917

920-
if (grep { /bold/ } @parts) {
918+
if (grep { /^bold$/ } @parts) {
921919
push(@ansi_part, "1");
922-
@parts = grep { !/bold/ } @parts; # Remove from array
923920
}
924921

925-
if (grep { /reverse/ } @parts) {
922+
if (grep { /^dim$/ } @parts) {
923+
push(@ansi_part, "2");
924+
}
925+
926+
if (grep { /^ul$/ } @parts) {
927+
push(@ansi_part, "4");
928+
}
929+
930+
if (grep { /^reverse$/ } @parts) {
926931
push(@ansi_part, "7");
927-
@parts = grep { !/reverse/ } @parts; # Remove from array
928932
}
929933

934+
# Remove parts that aren't colors
935+
@parts = grep { exists $colors->{$_} || is_numeric($_) || /^\#/ } @parts;
936+
930937
my $fg = $parts[0] // "";
931938
my $bg = $parts[1] // "";
932939

940+
set_ansi_color($fg, 0 , \@ansi_part, $colors) if $fg;
941+
set_ansi_color($bg, 10, \@ansi_part, $colors) if $bg;
942+
933943
#############################################
934944

935-
# It's an numeric value, so it's an 8 bit color
936-
if (is_numeric($fg)) {
937-
if ($fg < 8) {
938-
push(@ansi_part, $fg + 30);
939-
} elsif ($fg < 16) {
940-
push(@ansi_part, $fg + 82);
941-
} else {
942-
push(@ansi_part, "38;5;$fg");
943-
}
944-
# It's a simple 16 color OG ansi
945-
} elsif ($fg) {
946-
my $bright = $fg =~ s/bright//;
947-
my $color_num = $colors->{$fg} + 30;
945+
my $ansi_str = join(";", @ansi_part);
946+
my $ret = "\e[" . $ansi_str . "m";
948947

949-
if ($bright) { $color_num += 60; } # Set bold
948+
return $ret;
949+
}
950950

951-
push(@ansi_part, $color_num);
952-
}
951+
sub set_ansi_color {
952+
my ($color, $increment, $ansi_part, $colors) = @_;
953953

954-
#############################################
954+
my $base_code = 30 + $increment;
955+
my $base8_code = 38 + $increment;
956+
my $ext_code = 82 + $increment;
955957

956-
# It's an numeric value, so it's an 8 bit color
957-
if (is_numeric($bg)) {
958-
if ($bg < 8) {
959-
push(@ansi_part, $bg + 40);
960-
} elsif ($bg < 16) {
961-
push(@ansi_part, $bg + 92);
958+
if (is_numeric($color)) {
959+
if ($color < 8) {
960+
push(@$ansi_part, $color + $base_code);
961+
} elsif ($color < 16) {
962+
push(@$ansi_part, $color + $ext_code);
962963
} else {
963-
push(@ansi_part, "48;5;$bg");
964+
push(@$ansi_part, "$base8_code;5;$color");
964965
}
966+
# It's a full rgb code
967+
} elsif ($color =~ /^#/) {
968+
my ($rgbr, $rgbg, $rgbb) = $color =~ /.(..)(..)(..)/;
969+
push(@$ansi_part, "$base8_code;2;" . hex($rgbr) . ";" . hex($rgbg) . ";" . hex($rgbb));
965970
# It's a simple 16 color OG ansi
966-
} elsif ($bg) {
967-
my $bright = $bg =~ s/bright//;
968-
my $color_num = $colors->{$bg} + 40;
969-
970-
if ($bright) { $color_num += 60; } # Set bold
971-
972-
push(@ansi_part, $color_num);
971+
} elsif ($color ne "normal") {
972+
my $color_num = $colors->{$color} + $base_code;
973+
push(@$ansi_part, $color_num);
973974
}
974-
975-
#############################################
976-
977-
my $ansi_str = join(";", @ansi_part);
978-
my $ret = "\e[" . $ansi_str . "m";
979-
980-
return $ret;
981975
}
982976

983977
sub is_numeric {
@@ -1033,11 +1027,17 @@ sub get_terminal_width {
10331027

10341028
sub show_debug_info {
10351029
my @less = get_less_charset();
1036-
my $git_ver = trim(`git --version`);
1030+
my $git_ver = trim(`git --version 2>&1`);
10371031
$git_ver =~ s/[^\d.]//g;
10381032

1033+
if ($git_ver !~ /git/) {
1034+
$git_ver = "Unknown";
1035+
} else {
1036+
$git_ver = "v" . $git_ver;
1037+
}
1038+
10391039
print "Diff-so-fancy : v$VERSION\n";
1040-
print "Git : v$git_ver\n";
1040+
print "Git : $git_ver\n";
10411041
print "Perl : $^V\n";
10421042
print "\n";
10431043

@@ -1088,20 +1088,47 @@ sub debug_log {
10881088
return 1;
10891089
}
10901090

1091-
# Enable k() and kd() if there is a DSF_DEBUG environment variable
1092-
BEGIN {
1093-
if ($ENV{"DSF_DEBUG"}) {
1094-
require Data::Dump::Color;
1095-
*k = sub { Data::Dump::Color::dd(@_) };
1096-
*kd = sub {
1091+
sub calculate_context_lines {
1092+
my @lines = @_;
1093+
my $count = 0;
1094+
my $hunk_line = 0;
1095+
1096+
foreach my $line (@lines) {
1097+
if ($line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) {
1098+
$hunk_line = $count;
1099+
} elsif ($hunk_line && $line =~ /^${ansi_color_regex}[+-]/) {
1100+
my $diff = $count - $hunk_line - 1;
1101+
#print "Hunk: $hunk_line, ChangedLine: $count ($diff context)\n";
1102+
return $diff;
1103+
}
1104+
1105+
$count++;
1106+
};
1107+
1108+
# If for some reason we can't figure it out, assume 3
1109+
return 3;
1110+
}
1111+
1112+
# Borrowed from: https://www.perturb.org/display/1097_Perl_detect_if_a_module_is_installed_before_using_it.html
1113+
sub AUTOLOAD {
1114+
our $AUTOLOAD; # keep 'use strict' happy
1115+
1116+
if ($AUTOLOAD eq 'main::k' || $AUTOLOAD eq 'main::kd') {
1117+
if (eval { require Data::Dump::Color }) {
1118+
*k = sub { Data::Dump::Color::dd(@_) };
1119+
} else {
1120+
require Data::Dumper;
1121+
*k = sub { print Data::Dumper::Dumper(@_) };
1122+
}
1123+
1124+
sub kd {
10971125
k(@_);
10981126

10991127
printf("Died at %2\$s line #%3\$s\n",caller());
11001128
exit(15);
11011129
}
1102-
} else {
1103-
*k = sub {};
1104-
*kd = sub {};
1130+
1131+
eval($AUTOLOAD . '(@_)');
11051132
}
11061133
}
11071134

diff-so-fancy.png

148 KB
Loading

0 commit comments

Comments
 (0)