From f5108f37be2e4fedabae9c40d7606a3da8b88bc1 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Sun, 14 Feb 2016 13:10:16 +0200 Subject: [PATCH 001/443] Strip first column of spaces --- diff-so-fancy | 9 +++++++-- readme.md | 4 ++-- test/diff-so-fancy.bats | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index d749e5e..a5e253d 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -55,7 +55,11 @@ mark_empty_lines () { strip_leading_symbols () { # strip the + and - - $SED -E "s/^$color_code_regex[\+\-]/\1 /g" + $SED -E "s/^($color_code_regex)[\+\-]/\1 /g" +} + +strip_first_column () { + $SED -E "s/^($color_code_regex)[[:space:]]/\1/g" } print_horizontal_rule () { @@ -69,4 +73,5 @@ cat $input \ | format_diff_header \ | colorize_context_line \ | mark_empty_lines \ - | strip_leading_symbols + | strip_leading_symbols \ + | strip_first_column diff --git a/readme.md b/readme.md index fdd0748..da17b00 100644 --- a/readme.md +++ b/readme.md @@ -21,8 +21,8 @@ git diff --color | diff-so-fancy **But**, you'll probably want to fancify all your diffs. Run this so `git diff` (and `git show`) will use it: ```shell -git config --global pager.diff "diff-so-fancy | less --tabs=1,5 -RFX" -git config --global pager.show "diff-so-fancy | less --tabs=1,5 -RFX" +git config --global pager.diff "diff-so-fancy | less --tabs=4 -RFX" +git config --global pager.show "diff-so-fancy | less --tabs=4 -RFX" ``` However, if you'd prefer to do the fanciness on-demand with `git dsf`, drop an alias in your `~/.gitconfig`: diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 77ff252..847a7f6 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -15,7 +15,7 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "original source is indented by a single space" { assert_output --partial " - if begin" +if begin" } @test "index line is removed entirely" { @@ -31,9 +31,9 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "empty lines added/removed are marked" { assert_output --partial "  -  set -x CLICOLOR_FORCE 1" + set -x CLICOLOR_FORCE 1" assert_output --partial "  - if not set -q LS_COLORS" + if not set -q LS_COLORS" } @test "diff-highlight is highlighting changes within lines" { From 80cd9cf397a616eaf5da94a767bd076bcbae579c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 24 Feb 2016 11:36:32 -0800 Subject: [PATCH 002/443] Delete whitespace above header line (instead of replacing it with "") --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index a5e253d..534a753 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -38,7 +38,7 @@ git_index_line_pattern="^($color_code_regex)index .*" format_diff_header () { # simplify the unified patch diff header - $SED -E "s/^($color_code_regex)diff --git .*$//g" \ + $SED -E "/^($color_code_regex)diff --git .*$/d" \ | $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1\-\-\-\-\5/g" \ | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1\+\+\+\+\5\\${NL}\1$(print_horizontal_rule)/g" From 16125d0296833b4c62b7000331cfa024036072fa Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 25 Feb 2016 18:35:20 +1100 Subject: [PATCH 003/443] 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c2da412..c7a1ab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.4.1", + "version": "0.5.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From f547196887bdbb58ef0e91eeb317f9a552d92280 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 25 Feb 2016 02:33:19 -0800 Subject: [PATCH 004/443] readme: update alias to handle non-root uses fixes #86. fixes #87 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index da17b00..5b07e64 100644 --- a/readme.md +++ b/readme.md @@ -27,7 +27,7 @@ git config --global pager.show "diff-so-fancy | less --tabs=4 -RFX" However, if you'd prefer to do the fanciness on-demand with `git dsf`, drop an alias in your `~/.gitconfig`: ```shell -dsf = "!git diff --color $@ | diff-so-fancy" +dsf = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd "$GIT_PREFIX"; git diff --color $@ | diff-so-fancy | less --tabs=4 -RFX; }; f" ``` ## Install From 652032a9d8ef1f3734f8c7ccba88e1cdfe538a1d Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 27 Feb 2016 09:06:17 +1100 Subject: [PATCH 005/443] 0.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7a1ab2..316a815 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.5.0", + "version": "0.5.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From dd26e6b89b82c1c1455f8b7e32248cb96cb646bb Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Fri, 26 Feb 2016 14:55:23 -0800 Subject: [PATCH 006/443] add some newlines between files for spacing and readability --- diff-so-fancy | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 534a753..055b61f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -38,7 +38,7 @@ git_index_line_pattern="^($color_code_regex)index .*" format_diff_header () { # simplify the unified patch diff header - $SED -E "/^($color_code_regex)diff --git .*$/d" \ + $SED -E "s/^($color_code_regex)diff --git .*$//g" \ | $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1\-\-\-\-\5/g" \ | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1\+\+\+\+\5\\${NL}\1$(print_horizontal_rule)/g" @@ -53,6 +53,10 @@ mark_empty_lines () { $SED -E "s/^$color_code_regex[\+\-]$reset_color\s*$/$invert_color\1 $reset_escape/g" } +remove_first_empty_line () { + $SED -E '1 D' +} + strip_leading_symbols () { # strip the + and - $SED -E "s/^($color_code_regex)[\+\-]/\1 /g" @@ -73,5 +77,6 @@ cat $input \ | format_diff_header \ | colorize_context_line \ | mark_empty_lines \ + | remove_first_empty_line \ | strip_leading_symbols \ | strip_first_column From 68de5d24a35ab4cb2fb37fe2e4d7776156a48a18 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 29 Feb 2016 09:58:15 -0800 Subject: [PATCH 007/443] Clean up the headers to indicate a file was added/removed/modified Also makes the file name "clickable" by removing the leading a/ and /b --- diff-so-fancy | 7 ++++- libs/header_clean/header_clean.pl | 43 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 libs/header_clean/header_clean.pl diff --git a/diff-so-fancy b/diff-so-fancy index 055b61f..116b654 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -70,6 +70,10 @@ print_horizontal_rule () { printf "%$(tput cols)s\n"| $SED 's/ /─/g' } +print_header_clean () { + "$(get_script_dir)/libs/header_clean/header_clean.pl" +} + # run it. # shellcheck disable=SC2002 cat $input \ @@ -79,4 +83,5 @@ cat $input \ | mark_empty_lines \ | remove_first_empty_line \ | strip_leading_symbols \ - | strip_first_column + | strip_first_column \ + | print_header_clean diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl new file mode 100755 index 0000000..313da02 --- /dev/null +++ b/libs/header_clean/header_clean.pl @@ -0,0 +1,43 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; + +my ($f1,$f2); +while (my $l = <>) { + # Find the first file: --- a/README.md + if ($l =~ /^$ansi_sequence_regex--- (a\/)?(.+?)(\e|$)/) { + $f1 = $5; + # Find the second file: +++ b/README.md + } elsif ($l =~ /^$ansi_sequence_regex\+\+\+ (b\/)?(.+?)(\e|$)/) { + if ($1) { + print $1; # Print out whatever color we're using + } + $f2 = $5; + + # If they're the same it's a modify + if ($f1 eq $f2) { + print "modified: $f1\n"; + # If the first is /dev/null it's a new file + } elsif ($f1 eq "/dev/null") { + print "added: $f2\n"; + # If the second is /dev/null it's a deletion + } elsif ($f2 eq "/dev/null") { + print "deleted: $f1\n"; + # If the files aren't the same it's a rename + } elsif ($f1 ne $f2) { + print "renamed: $f1 to $f2\n"; + # Something we haven't thought of yet + } else { + print "$f1 -> $f2\n"; + } + + # Reset the vars + my ($f1,$f2); + # Just a regular line, print it out + } else { + print $l; + } +} From d293ed7ebd780cfda3457e51277716bf33e3f0e5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 29 Feb 2016 10:00:01 -0800 Subject: [PATCH 008/443] Add tests for header_clean --- test/fixtures/file-moves.diff | 112 ++++++++++++++++++++++++++++++++++ test/header_clean.bats | 21 +++++++ 2 files changed, 133 insertions(+) create mode 100644 test/fixtures/file-moves.diff create mode 100644 test/header_clean.bats diff --git a/test/fixtures/file-moves.diff b/test/fixtures/file-moves.diff new file mode 100644 index 0000000..fdec611 --- /dev/null +++ b/test/fixtures/file-moves.diff @@ -0,0 +1,112 @@ +commit a12f64cfa2388b1d07659149db3a77314c9836c8 +Author: Scott Baker +Date: Thu Feb 25 08:31:54 2016 -0800 + + Moves + +diff --git a/appveyor.yml b/appveyor.yml +index a6fb95e..43d61c3 100644 +--- a/appveyor.yml ++++ b/appveyor.yml +@@ -1,4 +1,4 @@ +-install: ++FOOinstall: + - git clone --depth 1 https://github.com/sstephenson/bats.git +  + build: false +diff --git a/circle.yml b/circle.yml +deleted file mode 100644 +index 18a04a3..0000000 +--- a/circle.yml ++++ /dev/null +@@ -1,21 +0,0 @@ +-machine: +- environment: +- +- +-dependencies: +- pre: +- - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; +- - sudo apt-get -y update +- - sudo apt-get -y install shellcheck; +- +- post: +- - git clone --depth 1 https://github.com/sstephenson/bats.git +- +-checkout: +- post: +- - git submodule update --init +- +-test: +- override: +- - bats/bin/bats test/*.bats +- - shellcheck diff-so-fancy update-deps.sh +diff --git a/hello.txt b/hello.txt +new file mode 100644 +index 0000000..0c767bc +--- /dev/null ++++ b/hello.txt +@@ -0,0 +1 @@ ++HI THERE +diff --git a/package.json b/package.json +deleted file mode 100644 +index c7a1ab2..0000000 +--- a/package.json ++++ /dev/null +@@ -1,29 +0,0 @@ +-{ +- "name": "diff-so-fancy", +- "version": "0.5.0", +- "description": "Good-lookin' diffs with diff-highlight and more", +- "bin": { +- "diff-so-fancy": "diff-so-fancy", +- "diff-highlight": "third_party/diff-highlight/diff-highlight" +- }, +- "repository": { +- "type": "git", +- "url": "git+https://github.com/so-fancy/diff-so-fancy.git" +- }, +- "keywords": [ +- "git", +- "diff", +- "fancy", +- "good-lookin'", +- "diff-highlight", +- "color", +- "readable", +- "highlight" +- ], +- "author": "Paul Irish", +- "license": "MIT", +- "bugs": { +- "url": "https://github.com/so-fancy/diff-so-fancy/issues" +- }, +- "homepage": "https://github.com/so-fancy/diff-so-fancy#readme" +-} +diff --git a/square.yml b/square.yml +new file mode 100644 +index 0000000..18a04a3 +--- /dev/null ++++ b/square.yml +@@ -0,0 +1,21 @@ ++machine: ++ environment: ++ ++ ++dependencies: ++ pre: ++ - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; ++ - sudo apt-get -y update ++ - sudo apt-get -y install shellcheck; ++ ++ post: ++ - git clone --depth 1 https://github.com/sstephenson/bats.git ++ ++checkout: ++ post: ++ - git submodule update --init ++ ++test: ++ override: ++ - bats/bin/bats test/*.bats ++ - shellcheck diff-so-fancy update-deps.sh diff --git a/test/header_clean.bats b/test/header_clean.bats new file mode 100644 index 0000000..3d50271 --- /dev/null +++ b/test/header_clean.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load 'test_helper/bats-core/load' +load 'test_helper/bats-assert/load' +load 'test_helper/util' + + +# bats fails to handle our multiline result, so we save to $output ourselves +output=$( load_fixture "file-moves" | $diff_so_fancy ) + +@test "header_clean 'added:'" { + assert_output --partial 'added: hello.txt' +} + +@test "header_clean 'modified:'" { + assert_output --partial 'modified: appveyor.yml' +} + +@test "header_clean 'deleted:'" { + assert_output --partial 'deleted: circle.yml' +} From 671103b43e7d94490762e2aaf83c0fe27375837f Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 1 Mar 2016 22:44:19 +1100 Subject: [PATCH 009/443] 0.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 316a815..a17430e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.5.1", + "version": "0.5.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From c4c13a1ceab420ce3479e6f9238a8d07d5778054 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 2 Mar 2016 15:06:22 -0800 Subject: [PATCH 010/443] readme: testing notes --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5b07e64..c612a17 100644 --- a/readme.md +++ b/readme.md @@ -113,8 +113,10 @@ bats test brew install entr ls --color=never diff-so-fancy test/*.bats | entr bats test ``` +When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` -Be sure to lint your scripts via shellcheck + +You can lint your scripts via shellcheck, our CI bots will also check. ```sh brew install shellcheck From 70b91f3cd81b979ca60aef46efd8d8d73caa77de Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 2 Mar 2016 16:33:30 -0800 Subject: [PATCH 011/443] readme: notes on less movement. fixes #96 --- readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c612a17..745b983 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,9 @@ If you want, you can choose to install manually: Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). -## Opting-out +## Pro-tips + +#### Opting-out Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: @@ -76,6 +78,9 @@ Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: git --no-pager diff ``` +#### Moving around in the diff + +You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`p` keys. Add `--pattern='^(added|deleted|modified): '` to the end of the less flags for your git pager config. ## History From 1113a59486747d036fdf0852d24444f973a8bff8 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 2 Mar 2016 20:02:33 -0800 Subject: [PATCH 012/443] Leave the first line in, we're cleaning it in Perl now --- diff-so-fancy | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 116b654..8dab59c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -38,8 +38,7 @@ git_index_line_pattern="^($color_code_regex)index .*" format_diff_header () { # simplify the unified patch diff header - $SED -E "s/^($color_code_regex)diff --git .*$//g" \ - | $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ + $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1\-\-\-\-\5/g" \ | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1\+\+\+\+\5\\${NL}\1$(print_horizontal_rule)/g" } @@ -53,10 +52,6 @@ mark_empty_lines () { $SED -E "s/^$color_code_regex[\+\-]$reset_color\s*$/$invert_color\1 $reset_escape/g" } -remove_first_empty_line () { - $SED -E '1 D' -} - strip_leading_symbols () { # strip the + and - $SED -E "s/^($color_code_regex)[\+\-]/\1 /g" @@ -81,7 +76,6 @@ cat $input \ | format_diff_header \ | colorize_context_line \ | mark_empty_lines \ - | remove_first_empty_line \ | strip_leading_symbols \ | strip_first_column \ | print_header_clean From e10a8606c2524145a1fadf154b5d57f1198f7c62 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 2 Mar 2016 20:04:15 -0800 Subject: [PATCH 013/443] Update perl script to remove add/deleted file perms Also cleanup modified file perms --- libs/header_clean/header_clean.pl | 94 ++++++++++++++++++++++++------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 313da02..0ee9c42 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -3,41 +3,93 @@ use strict; use warnings; +my $remove_file_add_header = 1; +my $remove_file_delete_header = 1; +my $clean_permission_changes = 1; + +################################################################################# + my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my ($f1,$f2); -while (my $l = <>) { - # Find the first file: --- a/README.md - if ($l =~ /^$ansi_sequence_regex--- (a\/)?(.+?)(\e|$)/) { - $f1 = $5; - # Find the second file: +++ b/README.md - } elsif ($l =~ /^$ansi_sequence_regex\+\+\+ (b\/)?(.+?)(\e|$)/) { +my ($file_1,$file_2,$last_file_seen); +my @input = <>; +strip_empty_first_line(\@input); + +for (my $i = 0; $i <= $#input; $i++) { + my $line = $input[$i]; + + #print "RAW: $line"; + + ######################### + # Look for the filename # + ######################### + if ($line =~ /^${ansi_sequence_regex}diff --git a\/(.+?) b\/(.+)/) { + $last_file_seen = $4; + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif ($line =~ /^$ansi_sequence_regex--- (a\/)?(.+?)(\e|$)/) { + $file_1 = $5; + + # Find the second file on the next line: +++ b/README.md + my $next = $input[++$i]; + $next =~ /^$ansi_sequence_regex\+\+\+ (b\/)?(.+?)(\e|$)/; if ($1) { print $1; # Print out whatever color we're using } - $f2 = $5; + $file_2 = $5; # If they're the same it's a modify - if ($f1 eq $f2) { - print "modified: $f1\n"; + if ($file_1 eq $file_2) { + print "modified: $file_1\n"; # If the first is /dev/null it's a new file - } elsif ($f1 eq "/dev/null") { - print "added: $f2\n"; + } elsif ($file_1 eq "/dev/null") { + print "added: $file_2\n"; # If the second is /dev/null it's a deletion - } elsif ($f2 eq "/dev/null") { - print "deleted: $f1\n"; + } elsif ($file_2 eq "/dev/null") { + print "deleted: $file_1\n"; # If the files aren't the same it's a rename - } elsif ($f1 ne $f2) { - print "renamed: $f1 to $f2\n"; + } elsif ($file_1 ne $file_2) { + print "renamed: $file_1 to $file_2\n"; # Something we haven't thought of yet } else { - print "$f1 -> $f2\n"; + print "$file_1 -> $file_2\n"; + } + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_sequence_regex}new file mode/) { + # Don't print the line (i.e. remove it from the output); + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_sequence_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_sequence_regex}old mode (\d+)/) { + my $next = $input[++$i]; + + if ($1) { + print $1; # Print out whatever color we're using } - # Reset the vars - my ($f1,$f2); - # Just a regular line, print it out + my ($new_mode) = $next =~ m/new mode (\d+)/; + print "$last_file_seen changed file mode to $new_mode\n"; + ##################################### + # Just a regular line, print it out # + ##################################### } else { - print $l; + print $line; + } +} + +sub strip_empty_first_line { + my $foo = shift(); # Array passed in by reference + + # If the first line is just whitespace remove it + if ($foo->[0] =~ /^\s*$/) { + shift($foo); } } From 286ce59840acc3fef3744c29d5b9a321fffa9642 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 2 Mar 2016 20:04:49 -0800 Subject: [PATCH 014/443] Tests for file permission changes --- test/fixtures/file-perms.diff | 73 +++++++++++++++++++++++++++++++++++ test/header_clean.bats | 16 +++++++- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/file-perms.diff diff --git a/test/fixtures/file-perms.diff b/test/fixtures/file-perms.diff new file mode 100644 index 0000000..1332646 --- /dev/null +++ b/test/fixtures/file-perms.diff @@ -0,0 +1,73 @@ +diff --git a/circle.yml b/circle.yml +old mode 100644 +new mode 100755 +diff --git a/foo.json b/foo.json +new file mode 100644 +index 0000000..316a815 +--- /dev/null ++++ b/foo.json +@@ -0,0 +1,29 @@ ++{ ++ "name": "diff-so-fancy", ++ "version": "0.5.1", ++ "description": "Good-lookin' diffs with diff-highlight and more", ++ "bin": { ++ "diff-so-fancy": "diff-so-fancy", ++ "diff-highlight": "third_party/diff-highlight/diff-highlight" ++ }, ++ "repository": { ++ "type": "git", ++ "url": "git+https://github.com/so-fancy/diff-so-fancy.git" ++ }, ++ "keywords": [ ++ "git", ++ "diff", ++ "fancy", ++ "good-lookin'", ++ "diff-highlight", ++ "color", ++ "readable", ++ "highlight" ++ ], ++ "author": "Paul Irish", ++ "license": "MIT", ++ "bugs": { ++ "url": "https://github.com/so-fancy/diff-so-fancy/issues" ++ }, ++ "homepage": "https://github.com/so-fancy/diff-so-fancy#readme" ++} +diff --git a/package.json b/package.json +deleted file mode 100644 +index 316a815..0000000 +--- a/package.json ++++ /dev/null +@@ -1,29 +0,0 @@ +-{ +- "name": "diff-so-fancy", +- "version": "0.5.1", +- "description": "Good-lookin' diffs with diff-highlight and more", +- "bin": { +- "diff-so-fancy": "diff-so-fancy", +- "diff-highlight": "third_party/diff-highlight/diff-highlight" +- }, +- "repository": { +- "type": "git", +- "url": "git+https://github.com/so-fancy/diff-so-fancy.git" +- }, +- "keywords": [ +- "git", +- "diff", +- "fancy", +- "good-lookin'", +- "diff-highlight", +- "color", +- "readable", +- "highlight" +- ], +- "author": "Paul Irish", +- "license": "MIT", +- "bugs": { +- "url": "https://github.com/so-fancy/diff-so-fancy/issues" +- }, +- "homepage": "https://github.com/so-fancy/diff-so-fancy#readme" +-} diff --git a/test/header_clean.bats b/test/header_clean.bats index 3d50271..a057a78 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -4,7 +4,6 @@ load 'test_helper/bats-core/load' load 'test_helper/bats-assert/load' load 'test_helper/util' - # bats fails to handle our multiline result, so we save to $output ourselves output=$( load_fixture "file-moves" | $diff_so_fancy ) @@ -19,3 +18,18 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "header_clean 'deleted:'" { assert_output --partial 'deleted: circle.yml' } + +@test "header_clean permission changes" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + assert_output --partial 'circle.yml changed file mode to 100755' +} + +@test "header_clean new file mode is removed" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'new file mode' +} + +@test "header_clean deleted file mode is removed" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'deleted file mode' +} From 29a88893d2a23484b745b099c6c139b471dae6b0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 3 Mar 2016 08:06:58 -0800 Subject: [PATCH 015/443] Handle both filename.txt and a/filename.txt --- libs/header_clean/header_clean.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 0ee9c42..2ae6513 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -23,8 +23,9 @@ ######################### # Look for the filename # ######################### - if ($line =~ /^${ansi_sequence_regex}diff --git a\/(.+?) b\/(.+)/) { + if ($line =~ /^${ansi_sequence_regex}diff --git (.+?) /) { $last_file_seen = $4; + $last_file_seen =~ s|a/||; # Remove a/ ######################################## # Find the first file: --- a/README.md # ######################################## From 72b5627f5a3e11d083c5aa70642438d11ffe17d1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 3 Mar 2016 11:17:28 -0800 Subject: [PATCH 016/443] Add a test to make sure we remove the "git --diff" header line --- test/header_clean.bats | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/header_clean.bats b/test/header_clean.bats index a057a78..e3b83f0 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -24,12 +24,17 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial 'circle.yml changed file mode to 100755' } -@test "header_clean new file mode is removed" { +@test "header_clean 'new file mode' is removed" { output=$( load_fixture "file-perms" | $diff_so_fancy ) refute_output --partial 'new file mode' } -@test "header_clean deleted file mode is removed" { +@test "header_clean 'deleted file mode' is removed" { output=$( load_fixture "file-perms" | $diff_so_fancy ) refute_output --partial 'deleted file mode' } + +@test "header_clean remove 'git --diff' header" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'diff --git' +} From b7afd52350ca43ad08b1f4fde99219565ebbc99c Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 3 Mar 2016 15:45:47 -0800 Subject: [PATCH 017/443] update bats-support submodule --- .gitmodules | 2 +- test/test_helper/bats-assert | 2 +- test/test_helper/bats-core | 1 - test/test_helper/bats-support | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 160000 test/test_helper/bats-core create mode 160000 test/test_helper/bats-support diff --git a/.gitmodules b/.gitmodules index 83e2bc5..b4374b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ [submodule "test/test_helper/bats-core"] - path = test/test_helper/bats-core + path = test/test_helper/bats-support url = https://github.com/ztombol/bats-core [submodule "test/test_helper/bats-assert"] path = test/test_helper/bats-assert diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert index 9d6f9a2..3c30cb6 160000 --- a/test/test_helper/bats-assert +++ b/test/test_helper/bats-assert @@ -1 +1 @@ -Subproject commit 9d6f9a219026789af4097db7d208fbda873002ee +Subproject commit 3c30cb6424b9760884f8f8b7378a9367b76692b7 diff --git a/test/test_helper/bats-core b/test/test_helper/bats-core deleted file mode 160000 index 2c97639..0000000 --- a/test/test_helper/bats-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c97639223e65e54bec2bccda788ba3db338fc22 diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support new file mode 160000 index 0000000..88f342e --- /dev/null +++ b/test/test_helper/bats-support @@ -0,0 +1 @@ +Subproject commit 88f342e29681cbcb3c183bd47dfbb45965f42eb7 From 57a2abf7dfa5da2c66494e5a36c9bb931f2f0ba7 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 3 Mar 2016 15:47:29 -0800 Subject: [PATCH 018/443] tweak path. --- test/bugs.bats | 2 +- test/diff-highlight.bats | 2 +- test/diff-so-fancy.bats | 2 +- test/header_clean.bats | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index ad16e2f..e2e8fd1 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -1,6 +1,6 @@ #!/usr/bin/env bats -load 'test_helper/bats-core/load' +load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' diff --git a/test/diff-highlight.bats b/test/diff-highlight.bats index 5c3ea06..487dde7 100644 --- a/test/diff-highlight.bats +++ b/test/diff-highlight.bats @@ -1,6 +1,6 @@ #!/usr/bin/env bats -load 'test_helper/bats-core/load' +load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 847a7f6..4975866 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -1,6 +1,6 @@ #!/usr/bin/env bats -load 'test_helper/bats-core/load' +load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' diff --git a/test/header_clean.bats b/test/header_clean.bats index 3d50271..8660895 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -1,6 +1,6 @@ #!/usr/bin/env bats -load 'test_helper/bats-core/load' +load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' From daa480d8425c7f97cab9f0c608343a77b26375b1 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 3 Mar 2016 16:22:15 -0800 Subject: [PATCH 019/443] test: add fixture for diff=noprefix --- test/diff-so-fancy.bats | 6 +++--- test/fixtures/noprefix.diff | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/noprefix.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 847a7f6..779e6a3 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -36,7 +36,7 @@ if begin" if not set -q LS_COLORS" } -@test "diff-highlight is highlighting changes within lines" { - assert_output --partial 'eval "env CLICOLOR_FORCE=1 command $ls $param $argv"' - assert_output --partial 'eval $ls $param "$argv"' +@test "diff --git line is removed entirely" { + output=$( load_fixture "noprefix" | $diff_so_fancy ) + refute_output --partial "diff --git a/fish/functions/ls.fish" } diff --git a/test/fixtures/noprefix.diff b/test/fixtures/noprefix.diff new file mode 100644 index 0000000..9c5b181 --- /dev/null +++ b/test/fixtures/noprefix.diff @@ -0,0 +1,20 @@ +diff --git setup-a-new-machine.sh setup-a-new-machine.sh +index 7b5996c..67eec2a 100755 +--- setup-a-new-machine.sh ++++ setup-a-new-machine.sh +@@ -30,6 +30,7 @@ cp -R ~/.gnupg ~/migration/home + cp /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist ~/migration # wifi +  + cp ~/Library/Preferences/net.limechat.LimeChat.plist ~/migration ++cp ~/Library/Preferences/com.tinyspeck.slackmacgap.plist ~/migration +  + cp -R ~/Library/Services ~/migration # automator stuff +  +@@ -215,5 +216,7 @@ sh .osx + # symlink it up! + ./symlink-setup.sh +  ++# add manual symlink for .ssh/config and probably .config/fish ++ + ### + ############################################################################################################## From 433a782696945e2d971995ff2cd08a7f3021cf71 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 3 Mar 2016 16:26:54 -0800 Subject: [PATCH 020/443] noprefix: check two difference fixtures --- test/diff-so-fancy.bats | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 779e6a3..783ad20 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -37,6 +37,9 @@ if begin" } @test "diff --git line is removed entirely" { - output=$( load_fixture "noprefix" | $diff_so_fancy ) + # test against ls-function refute_output --partial "diff --git a/fish/functions/ls.fish" + # test with git config diff.noprefix true + output=$( load_fixture "noprefix" | $diff_so_fancy ) + refute_output --partial "diff --git setup-a-new-machine.sh" } From d166cf1d210c8c295fc0e64a12b53cea03eba304 Mon Sep 17 00:00:00 2001 From: Lam Chau Date: Fri, 4 Mar 2016 01:05:04 -0500 Subject: [PATCH 021/443] update testing readme prevents `fatal: no submodule mapping found in .gitmodules for path 'test/test_helper/bats-core'` --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 745b983..8f46c62 100644 --- a/readme.md +++ b/readme.md @@ -109,6 +109,7 @@ cd ~/projects/catfabulator && git diff You'll need to install [bats](https://github.com/sstephenson/bats#installing-bats-from-source), the Bash automated testing system. It's also available as `brew install bats` ```sh +git submodule sync git submodule update --init # pull in the assertion library, bats-assert # Run the test suite once: From 4e5800fb65692cf052fdbf3a7918e7d2a81400c1 Mon Sep 17 00:00:00 2001 From: Lam Chau Date: Fri, 4 Mar 2016 04:46:21 -0500 Subject: [PATCH 022/443] use native terminal-drawing character --- diff-so-fancy | 6 +++++- test/diff-so-fancy.bats | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 8dab59c..e92d45d 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -62,7 +62,11 @@ strip_first_column () { } print_horizontal_rule () { - printf "%$(tput cols)s\n"| $SED 's/ /─/g' + let width="$(tput cols)" + + # echo -n '─' | hexdump -C + local -r dash=$( printf "%b" "\xe2\x94\x80" ) + printf "%*s\n" "$width" | $SED "s/ /${dash}/g" } print_header_clean () { diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index e4bfb5f..8778849 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -43,3 +43,11 @@ if begin" output=$( load_fixture "noprefix" | $diff_so_fancy ) refute_output --partial "diff --git setup-a-new-machine.sh" } + +@test "header format uses a native line-drawing character" { + header=$( printf "%s" "$output" | head -n3 ) + run printf "%s" "$header" + assert_line --index 0 --partial "─────" + assert_line --index 1 --partial "modified: fish/functions/ls.fish" + assert_line --index 2 --partial "─────" +} From cb0c0f14da59bf9b1bfc6a0b0bee77b2957453cf Mon Sep 17 00:00:00 2001 From: Lam Chau Date: Fri, 4 Mar 2016 04:46:31 -0500 Subject: [PATCH 023/443] fix header wrapping for powershell --- diff-so-fancy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index e92d45d..2323d9b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -64,6 +64,10 @@ strip_first_column () { print_horizontal_rule () { let width="$(tput cols)" + if [[ $(uname -s) =~ (MINGW32*|MSYS*) ]]; then + width=$(( width - 1 )) + fi + # echo -n '─' | hexdump -C local -r dash=$( printf "%b" "\xe2\x94\x80" ) printf "%*s\n" "$width" | $SED "s/ /${dash}/g" From e8bda85e7e9ee0c80d6453e890d5e88d557611b3 Mon Sep 17 00:00:00 2001 From: Lam Chau Date: Fri, 4 Mar 2016 04:46:44 -0500 Subject: [PATCH 024/443] correct test typo --- test/diff-so-fancy.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 8778849..e341eb3 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -22,7 +22,7 @@ if begin" refute_output --partial "index 33c3d8b..fd54db2 100644" } -@test "+/- line stars are stripped" { +@test "+/- line symbols are stripped" { refute_output --partial " -" refute_output --partial " From 31cbe1e88ecd3efbb004d8b35608ad4e1ff272b4 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 4 Mar 2016 08:45:49 -0800 Subject: [PATCH 025/443] Rework how diff hunks are displayed --- libs/header_clean/header_clean.pl | 29 ++++++++++++++++++++++++----- test/header_clean.bats | 6 ++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 2ae6513..30e3019 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -2,10 +2,12 @@ use strict; use warnings; +use File::Basename; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; +my $change_hunk_indicators = 1; ################################################################################# @@ -18,13 +20,11 @@ for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; - #print "RAW: $line"; - ######################### # Look for the filename # ######################### - if ($line =~ /^${ansi_sequence_regex}diff --git (.+?) /) { - $last_file_seen = $4; + if ($line =~ /^${ansi_sequence_regex}diff --(git|cc) (.*?)(\e|$)/) { + $last_file_seen = $5; $last_file_seen =~ s|a/||; # Remove a/ ######################################## # Find the first file: --- a/README.md # @@ -56,10 +56,29 @@ } else { print "$file_1 -> $file_2\n"; } + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif ($change_hunk_indicators && $line =~ /^${ansi_sequence_regex}@@@* (.+?) @@@*(.*)/) { + my $file_str = $4; + my $remain = $5; + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($start_line) = $file_str =~ m/(.+?),/; + $start_line = abs($start_line + 0); + + $last_file_seen = basename($last_file_seen); + + # Plus three line for context + print "@ $last_file_seen:" . ($start_line + 3) . " \@${remain}\n"; + #print $line; ################################### # Remove any new file permissions # ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_sequence_regex}new file mode/) { + } elsif ($remove_file_add_header && $line =~ /^${ansi_sequence_regex}.*new file mode/) { # Don't print the line (i.e. remove it from the output); ###################################### # Remove any delete file permissions # diff --git a/test/header_clean.bats b/test/header_clean.bats index b7565af..a46555d 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -38,3 +38,9 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) output=$( load_fixture "file-perms" | $diff_so_fancy ) refute_output --partial 'diff --git' } + +@test "Reworked hunks" { + output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --partial 'square.yml:3' + assert_output --partial 'package.json:4' +} From a92e9cf5425f32dddc869e766afca3836c04beec Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 4 Mar 2016 08:47:53 -0800 Subject: [PATCH 026/443] Don't emit a warning if there is NO diff at all --- libs/header_clean/header_clean.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 30e3019..a05c169 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -109,7 +109,7 @@ sub strip_empty_first_line { my $foo = shift(); # Array passed in by reference # If the first line is just whitespace remove it - if ($foo->[0] =~ /^\s*$/) { + if (defined($foo->[0]) && $foo->[0] =~ /^\s*$/) { shift($foo); } } From 7fa6a877e5236f58629bc7b56dff15f2c397140c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 4 Mar 2016 11:51:13 -0800 Subject: [PATCH 027/443] Properly handle diff chunks with prefixes disabled Also update tests to catch this in the future --- libs/header_clean/header_clean.pl | 2 +- test/header_clean.bats | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index a05c169..0c247ec 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -23,7 +23,7 @@ ######################### # Look for the filename # ######################### - if ($line =~ /^${ansi_sequence_regex}diff --(git|cc) (.*?)(\e|$)/) { + if ($line =~ /^${ansi_sequence_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { $last_file_seen = $5; $last_file_seen =~ s|a/||; # Remove a/ ######################################## diff --git a/test/header_clean.bats b/test/header_clean.bats index a46555d..16968c2 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -41,6 +41,6 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "Reworked hunks" { output=$( load_fixture "file-moves" | $diff_so_fancy ) - assert_output --partial 'square.yml:3' - assert_output --partial 'package.json:4' + assert_output --partial '@ square.yml:3 @' + assert_output --partial '@ package.json:4 @' } From b2fe4a682959382fb0c6b333dd73c28113c4184f Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 4 Mar 2016 11:57:10 -0800 Subject: [PATCH 028/443] Test for reworked hunks on 'noprefix' --- test/header_clean.bats | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/header_clean.bats b/test/header_clean.bats index 16968c2..4a3e7ee 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -44,3 +44,9 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ square.yml:3 @' assert_output --partial '@ package.json:4 @' } + +@test "Reworked hunks (noprefix)" { + output=$( load_fixture "noprefix" | $diff_so_fancy ) + assert_output --partial '@ setup-a-new-machine.sh:33 @' + assert_output --partial '@ setup-a-new-machine.sh:218 @' +} From 47545cd59e0edf7ba4a7aa7b08e099c9a0283274 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sun, 6 Mar 2016 08:56:15 +1100 Subject: [PATCH 029/443] 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a17430e..444c17a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.5.2", + "version": "0.6.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 9d0fe396e4c4f484eff2803fbde9f1c8f54b25cb Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Sat, 5 Mar 2016 21:02:17 -0800 Subject: [PATCH 030/443] use starting line of second hunk. fixes #109 --- libs/header_clean/header_clean.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 0c247ec..90a9358 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -67,7 +67,7 @@ print $1; # Print out whatever color we're using } - my ($start_line) = $file_str =~ m/(.+?),/; + my ($start_line) = $file_str =~ m/\+(\d+)/; $start_line = abs($start_line + 0); $last_file_seen = basename($last_file_seen); From 2a89ca842e2078da7de88501dc35e2380ae61730 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Sat, 5 Mar 2016 21:16:21 -0800 Subject: [PATCH 031/443] rebaseline tests for startline change --- test/header_clean.bats | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/header_clean.bats b/test/header_clean.bats index 4a3e7ee..1a6509c 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -41,12 +41,12 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "Reworked hunks" { output=$( load_fixture "file-moves" | $diff_so_fancy ) - assert_output --partial '@ square.yml:3 @' - assert_output --partial '@ package.json:4 @' + assert_output --partial '@ square.yml:4 @' + assert_output --partial '@ package.json:3 @' } @test "Reworked hunks (noprefix)" { output=$( load_fixture "noprefix" | $diff_so_fancy ) assert_output --partial '@ setup-a-new-machine.sh:33 @' - assert_output --partial '@ setup-a-new-machine.sh:218 @' + assert_output --partial '@ setup-a-new-machine.sh:219 @' } From 48bc5e271b9f6a01da0fe2cc66e2c2ce8b566ad6 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Sat, 5 Mar 2016 21:16:56 -0800 Subject: [PATCH 032/443] add test for deleted file hunks. #109 --- test/header_clean.bats | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/header_clean.bats b/test/header_clean.bats index 1a6509c..659ecc1 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -50,3 +50,8 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ setup-a-new-machine.sh:33 @' assert_output --partial '@ setup-a-new-machine.sh:219 @' } + +@test "Reworked hunks (deleted files)" { + output=$( load_fixture "dotfiles" | $diff_so_fancy ) + assert_output --partial '@ diff-so-fancy:3 @' +} From 72c5bca2db1de8e991e0002c35dad2b80dbe20f2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2016 12:05:55 -0800 Subject: [PATCH 033/443] Fix #114 (array shift error) --- libs/header_clean/header_clean.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 0c247ec..a621dd7 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -106,10 +106,10 @@ } sub strip_empty_first_line { - my $foo = shift(); # Array passed in by reference + my $array = shift(); # Array passed in by reference # If the first line is just whitespace remove it - if (defined($foo->[0]) && $foo->[0] =~ /^\s*$/) { - shift($foo); + if (defined($array->[0]) && $array->[0] =~ /^\s*$/) { + shift(@$array); # Throw away the first line } } From 8e513c56d1dd6d4acd14c84590284549338394c2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2016 14:09:01 -0800 Subject: [PATCH 034/443] Perl critic complained this sub didn't return. Sub operates pass-by-reference so it doesn't matter, but I'll fix it anyway --- libs/header_clean/header_clean.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index a621dd7..23df5e7 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -112,4 +112,6 @@ sub strip_empty_first_line { if (defined($array->[0]) && $array->[0] =~ /^\s*$/) { shift(@$array); # Throw away the first line } + + return 1; } From a3092aca66897e48e342341bd961d41382b7bef9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2016 14:42:36 -0800 Subject: [PATCH 035/443] Properly handle mnemonic prefixes --- libs/header_clean/header_clean.pl | 4 +- test/fixtures/mnemonicprefix.diff | 64 +++++++++++++++++++++++++++++++ test/header_clean.bats | 5 +++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/mnemonicprefix.diff diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index a621dd7..3a0726d 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -29,12 +29,12 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_sequence_regex--- (a\/)?(.+?)(\e|$)/) { + } elsif ($line =~ /^$ansi_sequence_regex--- ([abiwco]\/)?(.+?)(\e|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = $input[++$i]; - $next =~ /^$ansi_sequence_regex\+\+\+ (b\/)?(.+?)(\e|$)/; + $next =~ /^$ansi_sequence_regex\+\+\+ ([abiwco]\/)?(.+?)(\e|$)/; if ($1) { print $1; # Print out whatever color we're using } diff --git a/test/fixtures/mnemonicprefix.diff b/test/fixtures/mnemonicprefix.diff new file mode 100644 index 0000000..dbe985e --- /dev/null +++ b/test/fixtures/mnemonicprefix.diff @@ -0,0 +1,64 @@ +diff --git i/diff-so-fancy w/diff-so-fancy +index 2323d9b..a280985 100755 +--- i/diff-so-fancy ++++ w/diff-so-fancy +@@ -48,10 +48,6 @@ colorize_context_line () { + $SED -E "s/@@$reset_color $reset_color(.*$)/@@ $dim_magenta\1/g" + } +  +-mark_empty_lines () { +- $SED -E "s/^$color_code_regex[\+\-]$reset_color\s*$/$invert_color\1 $reset_escape/g" +-} +- + strip_leading_symbols () { + # strip the + and - + $SED -E "s/^($color_code_regex)[\+\-]/\1 /g" +@@ -83,7 +79,6 @@ cat $input \ + | $diff_highlight \ + | format_diff_header \ + | colorize_context_line \ +- | mark_empty_lines \ + | strip_leading_symbols \ + | strip_first_column \ + | print_header_clean +diff --git i/libs/header_clean/header_clean.pl w/libs/header_clean/header_clean.pl +index 23df5e7..54b3da1 100755 +--- i/libs/header_clean/header_clean.pl ++++ w/libs/header_clean/header_clean.pl +@@ -9,6 +9,8 @@ my $remove_file_delete_header = 1; + my $clean_permission_changes = 1; + my $change_hunk_indicators = 1; +  ++use Data::Dump::Color; ++ + ################################################################################# +  + my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +@@ -29,12 +31,12 @@ for (my $i = 0; $i <= $#input; $i++) { + ######################################## + # Find the first file: --- a/README.md # + ######################################## +- } elsif ($line =~ /^$ansi_sequence_regex--- (a\/)?(.+?)(\e|$)/) { ++ } elsif ($line =~ /^$ansi_sequence_regex--- ([abiwco]\/)?(.+?)(\e|$)/) { + $file_1 = $5; +  + # Find the second file on the next line: +++ b/README.md + my $next = $input[++$i]; +- $next =~ /^$ansi_sequence_regex\+\+\+ (b\/)?(.+?)(\e|$)/; ++ $next =~ /^$ansi_sequence_regex\+\+\+ ([abiwco]\/)?(.+?)(\e|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } +diff --git i/test/header_clean.bats w/test/header_clean.bats +index 4a3e7ee..a8385a5 100644 +--- i/test/header_clean.bats ++++ w/test/header_clean.bats +@@ -50,3 +50,8 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --partial '@ setup-a-new-machine.sh:33 @' + assert_output --partial '@ setup-a-new-machine.sh:218 @' + } ++ ++@test "mnemonicprefix" { ++ output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) ++ assert_output --partial '@ setup-a-new-machine.sh:33 @' ++} diff --git a/test/header_clean.bats b/test/header_clean.bats index 4a3e7ee..789c451 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -50,3 +50,8 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ setup-a-new-machine.sh:33 @' assert_output --partial '@ setup-a-new-machine.sh:218 @' } + +@test "mnemonicprefix handling" { + output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) + assert_output --partial 'modified: test/header_clean.bats' +} From 33502aae47356448effb46723a3f7643e91e3068 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2016 14:53:47 -0800 Subject: [PATCH 036/443] We'll match a word char instead to be a little more lenient --- libs/header_clean/header_clean.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 3a0726d..7c544c9 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -29,12 +29,12 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_sequence_regex--- ([abiwco]\/)?(.+?)(\e|$)/) { + } elsif ($line =~ /^$ansi_sequence_regex--- (\w\/)?(.+?)(\e|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = $input[++$i]; - $next =~ /^$ansi_sequence_regex\+\+\+ ([abiwco]\/)?(.+?)(\e|$)/; + $next =~ /^$ansi_sequence_regex\+\+\+ (\w\/)?(.+?)(\e|$)/; if ($1) { print $1; # Print out whatever color we're using } From 59aebbd37907701814e56304b207c3317350d804 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 8 Mar 2016 15:44:51 -0800 Subject: [PATCH 037/443] use parse_hunk_header from git --- libs/header_clean/header_clean.pl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 90a9358..5970580 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -59,22 +59,21 @@ ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_sequence_regex}@@@* (.+?) @@@*(.*)/) { - my $file_str = $4; + } elsif ($change_hunk_indicators && $line =~ /^${ansi_sequence_regex}(@@@* .+? @@@*)(.*)/) { + + my $hunk_header = $4; my $remain = $5; if ($1) { print $1; # Print out whatever color we're using } - my ($start_line) = $file_str =~ m/\+(\d+)/; - $start_line = abs($start_line + 0); - + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); $last_file_seen = basename($last_file_seen); # Plus three line for context - print "@ $last_file_seen:" . ($start_line + 3) . " \@${remain}\n"; - #print $line; + print "@ $last_file_seen:" . ($new_offset + 3) . " \@${remain}\n"; + ################################### # Remove any new file permissions # ################################### @@ -105,6 +104,16 @@ } } +# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 +sub parse_hunk_header { + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = + $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; + $o_cnt = 1 unless defined $o_cnt; + $n_cnt = 1 unless defined $n_cnt; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); +} + sub strip_empty_first_line { my $foo = shift(); # Array passed in by reference From 91bc769c020c7bd7f82dabce2d9f43063746c849 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 8 Mar 2016 16:41:24 -0800 Subject: [PATCH 038/443] readme: upgrade via npm too --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8f46c62..21db9f5 100644 --- a/readme.md +++ b/readme.md @@ -36,7 +36,7 @@ For convenience, the recommended installation is via NPM. If you'd prefer, you m ```shell npm install -g diff-so-fancy ``` -This will install and link the `diff-so-fancy` and `diff-highlight` scripts. +This will install and link the `diff-so-fancy` and `diff-highlight` scripts. You can also upgrade to the latest version with this command. On Mac, you can install via Homebrew: ```shell From 9e10738d2ef13b76b47ec6529ab8d13ec1806702 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 8 Mar 2016 16:44:27 -0800 Subject: [PATCH 039/443] readme: add trick for using no-pager to grab original diff output --- readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/readme.md b/readme.md index 21db9f5..51d3642 100644 --- a/readme.md +++ b/readme.md @@ -90,6 +90,14 @@ You can pre-seed your `less` pager with a search pattern, so you can move betwee Pull requests quite welcome, along with any feedback or ideas. +### Reporting bugs + +Providing the original diff text inline in the issue is quite helpful. + +```sh +git --no-pager diff > output.diff +``` + ### Hacking ```sh From 9103572a1ff97d2d1c25a8d8655f42b84c68c473 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 9 Mar 2016 19:16:00 +1100 Subject: [PATCH 040/443] 0.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 444c17a..7379c98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.6.0", + "version": "0.6.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From e664750b1175d822b4e2e5479fa112993da1c245 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 9 Mar 2016 19:19:50 +1100 Subject: [PATCH 041/443] update readme screenshot fixes #98 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 51d3642..e905ae3 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ your diffs' appearances. *`git diff` vs `git diff --color | diff-so-fancy`* -![diff-highlight vs diff-so-fancy](https://cloud.githubusercontent.com/assets/39191/10000682/8e849130-6052-11e5-9bd9-bd4505cd24d6.png) +![diff-highlight vs diff-so-fancy](https://cloud.githubusercontent.com/assets/39191/13622719/7cc7c54c-e555-11e5-86c4-7045d91af041.png) ## Usage From fbf440dd9c32a60f9bc97693a6bd7b5ca87ec9fc Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 9 Mar 2016 19:21:27 +1100 Subject: [PATCH 042/443] 0.6.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7379c98..3cba6ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.6.1", + "version": "0.6.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 8c34dae60a1bc9735292010a87fd6dd736f830a6 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 9 Mar 2016 04:15:05 -0800 Subject: [PATCH 043/443] Adjust hunk header regex for reality. Now works on hunks like: @@@ -48,10 -48,10 +48,15 @@@ --- libs/header_clean/header_clean.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index ae279d0..e3fecb9 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -108,7 +108,7 @@ sub parse_hunk_header { my ($line) = @_; my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = - $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; + $line =~ /^@@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? @@+/; $o_cnt = 1 unless defined $o_cnt; $n_cnt = 1 unless defined $n_cnt; return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); From efc22bbab6dbb4c81098e0940821f8d36222af31 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 09:42:20 -0800 Subject: [PATCH 044/443] Move strip_leading_symbols() and strip_first_column() to Perl --- diff-so-fancy | 2 - libs/header_clean/header_clean.pl | 63 ++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 2323d9b..68a791f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -84,6 +84,4 @@ cat $input \ | format_diff_header \ | colorize_context_line \ | mark_empty_lines \ - | strip_leading_symbols \ - | strip_first_column \ | print_header_clean diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index e3fecb9..c8f1b58 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -8,15 +8,15 @@ my $remove_file_delete_header = 1; my $clean_permission_changes = 1; my $change_hunk_indicators = 1; +my $strip_leading_indicators = 1; ################################################################################# -my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; - -my ($file_1,$file_2,$last_file_seen); my @input = <>; -strip_empty_first_line(\@input); +clean_up_input(\@input); +my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my ($file_1,$file_2,$last_file_seen); for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; @@ -106,12 +106,11 @@ # Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 sub parse_hunk_header { - my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = - $line =~ /^@@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? @@+/; - $o_cnt = 1 unless defined $o_cnt; - $n_cnt = 1 unless defined $n_cnt; - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^@@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? @@+/; + $o_cnt = 1 unless defined $o_cnt; + $n_cnt = 1 unless defined $n_cnt; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); } sub strip_empty_first_line { @@ -122,5 +121,49 @@ sub strip_empty_first_line { shift(@$array); # Throw away the first line } + return 1; +} + +# Remove + or - at the beginning of the lines +sub strip_leading_indicators { + my $array = shift(); # Array passed in by reference + my $line_count = scalar(@$array); + my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + + for (my $i = 0; $i < $line_count; $i++) { + $array->[$i] =~ s/^(${ansi_color_regex})[+-]/$1 /; + } + + return 1; +} + +# Remove the first space so everything aligns left +sub strip_first_column { + my $array = shift(); # Array passed in by reference + my $line_count = scalar(@$array); + my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + + for (my $i = 0; $i < $line_count; $i++) { + $array->[$i] =~ s/^(${ansi_color_regex})[[:space:]]/$1/; + } + + return 1; +} + +sub clean_up_input { + my $input_array_ref = shift(); + + # Usually the first line of a diff is whitespace so we remove that + strip_empty_first_line($input_array_ref); + + # Remove + or - at the beginning of the lines + if ($strip_leading_indicators) { + strip_leading_indicators($input_array_ref); + + # Remove the first space so everything aligns left + strip_first_column($input_array_ref); + } + + return 1; } From c5570c03866211bb732426ea10be8023e6eb6feb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 09:47:21 -0800 Subject: [PATCH 045/443] Add a function to get the git config as a hash (in Perl) --- libs/header_clean/header_clean.pl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index c8f1b58..e7ca3fc 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -167,3 +167,23 @@ sub clean_up_input { return 1; } + +# Return git config as a hash +sub get_git_config { + my $cmd = "git config --list"; + my @out = `$cmd`; + + my %hash; + foreach my $line (@out) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + my @path = split(/\./,$key); + + my $last = pop @path; + my $p = \%hash; + $p = $p->{$_} //= {} for @path; + $p->{$last} = $value; + } + + return \%hash; +} From 0ea95e5f6f1edcb166a0bef4757f6ae7db23d994 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 09:49:17 -0800 Subject: [PATCH 046/443] Remove the unused functions from bash They're in Perl now --- diff-so-fancy | 9 --------- 1 file changed, 9 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 68a791f..909cbc8 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -52,15 +52,6 @@ mark_empty_lines () { $SED -E "s/^$color_code_regex[\+\-]$reset_color\s*$/$invert_color\1 $reset_escape/g" } -strip_leading_symbols () { - # strip the + and - - $SED -E "s/^($color_code_regex)[\+\-]/\1 /g" -} - -strip_first_column () { - $SED -E "s/^($color_code_regex)[[:space:]]/\1/g" -} - print_horizontal_rule () { let width="$(tput cols)" From cbd916ae44cde8cdecbe22d8e883556851d78893 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 11:17:20 -0800 Subject: [PATCH 047/443] Move mark_empty_lines() to Perl We're down to five calls to sed --- diff-so-fancy | 5 ----- libs/header_clean/header_clean.pl | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 909cbc8..da7d587 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -48,10 +48,6 @@ colorize_context_line () { $SED -E "s/@@$reset_color $reset_color(.*$)/@@ $dim_magenta\1/g" } -mark_empty_lines () { - $SED -E "s/^$color_code_regex[\+\-]$reset_color\s*$/$invert_color\1 $reset_escape/g" -} - print_horizontal_rule () { let width="$(tput cols)" @@ -74,5 +70,4 @@ cat $input \ | $diff_highlight \ | format_diff_header \ | colorize_context_line \ - | mark_empty_lines \ | print_header_clean diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index e7ca3fc..75508b7 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -9,6 +9,7 @@ my $clean_permission_changes = 1; my $change_hunk_indicators = 1; my $strip_leading_indicators = 1; +my $mark_empty_lines = 1; ################################################################################# @@ -150,12 +151,32 @@ sub strip_first_column { return 1; } +sub mark_empty_lines { + my $array = shift(); # Array passed in by reference + my $line_count = scalar(@$array); + my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + + my $reset_color = "\e\\[0?m"; + my $reset_escape = "\e\[m"; + my $invert_color = "\e\[7m"; + + foreach my $line (@$array) { + $line =~ s/^(${ansi_color_regex})[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + } + + return 1; +} + sub clean_up_input { my $input_array_ref = shift(); # Usually the first line of a diff is whitespace so we remove that strip_empty_first_line($input_array_ref); + if ($mark_empty_lines) { + mark_empty_lines($input_array_ref); + } + # Remove + or - at the beginning of the lines if ($strip_leading_indicators) { strip_leading_indicators($input_array_ref); From 6b44c1993ae55cfd9a508cb105b136195879b273 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 11:21:28 -0800 Subject: [PATCH 048/443] Remove some bash definitions so the tests won't fail --- diff-so-fancy | 2 -- 1 file changed, 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index da7d587..c163500 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -30,9 +30,7 @@ NL=" " color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" reset_color="${CSI}0?m" -reset_escape="${CSI}m" dim_magenta="${CSI}38;05;146m" -invert_color="${CSI}7m" git_index_line_pattern="^($color_code_regex)index .*" From 1ac25e4ec570ec26dd4fb333c014576df96c645e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 11:24:22 -0800 Subject: [PATCH 049/443] Change two for loops to foreach loops for consistency --- libs/header_clean/header_clean.pl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 75508b7..a182304 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -128,11 +128,10 @@ sub strip_empty_first_line { # Remove + or - at the beginning of the lines sub strip_leading_indicators { my $array = shift(); # Array passed in by reference - my $line_count = scalar(@$array); my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; - for (my $i = 0; $i < $line_count; $i++) { - $array->[$i] =~ s/^(${ansi_color_regex})[+-]/$1 /; + foreach my $line (@$array) { + $line =~ s/^(${ansi_color_regex})[+-]/$1 /; } return 1; @@ -141,11 +140,10 @@ sub strip_leading_indicators { # Remove the first space so everything aligns left sub strip_first_column { my $array = shift(); # Array passed in by reference - my $line_count = scalar(@$array); my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; - for (my $i = 0; $i < $line_count; $i++) { - $array->[$i] =~ s/^(${ansi_color_regex})[[:space:]]/$1/; + foreach my $line (@$array) { + $line =~ s/^(${ansi_color_regex})[[:space:]]/$1/; } return 1; @@ -153,7 +151,6 @@ sub strip_first_column { sub mark_empty_lines { my $array = shift(); # Array passed in by reference - my $line_count = scalar(@$array); my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; my $reset_color = "\e\\[0?m"; From 3b279ed81fb9ab816f3c74bf19de74dc8c1f9454 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 9 Mar 2016 11:31:37 -0800 Subject: [PATCH 050/443] test for complex hunk headers --- test/fixtures/complex-hunks.diff | 32 ++++++++++++++++++++++++++++++++ test/header_clean.bats | 7 +++++++ 2 files changed, 39 insertions(+) create mode 100644 test/fixtures/complex-hunks.diff diff --git a/test/fixtures/complex-hunks.diff b/test/fixtures/complex-hunks.diff new file mode 100644 index 0000000..f02e035 --- /dev/null +++ b/test/fixtures/complex-hunks.diff @@ -0,0 +1,32 @@ +commit 74804e377d4a54d1173d4393827d4e4b27e4d5d0 +diff --cc libs/header_clean/header_clean.pl +index e8bcd92,5970580..ae279d0 +--- a/libs/header_clean/header_clean.pl ++++ b/libs/header_clean/header_clean.pl +@@@ -105,13 -104,21 +104,23 @@@ for (my $i = 0; $i <= $#input; $i++)  + } + } +  ++ # Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 ++ sub parse_hunk_header { ++ my ($line) = @_; ++ my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = ++ $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; ++ $o_cnt = 1 unless defined $o_cnt; ++ $n_cnt = 1 unless defined $n_cnt; ++ return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); ++ } ++  + sub strip_empty_first_line { + - my $foo = shift(); # Array passed in by reference + + my $array = shift(); # Array passed in by reference +  + # If the first line is just whitespace remove it + - if (defined($foo->[0]) && $foo->[0] =~ /^\s*$/) { + - shift($foo); + + if (defined($array->[0]) && $array->[0] =~ /^\s*$/) { + + shift(@$array); # Throw away the first line + } + + + + return 1; + } diff --git a/test/header_clean.bats b/test/header_clean.bats index a00d35d..04573ac 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -56,6 +56,13 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ diff-so-fancy:3 @' } +@test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { + # stderr forced into output + output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 ) + assert_output --partial '@ header_clean.pl:107 @' + refute_output --partial 'Use of uninitialized value' +} + @test "mnemonicprefix handling" { output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) assert_output --partial 'modified: test/header_clean.bats' From eca2546d7476511c8eb37a19fddec86dc7cb877a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 9 Mar 2016 13:31:58 -0800 Subject: [PATCH 051/443] Declare $ansi_color_regex only once --- libs/header_clean/header_clean.pl | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index a182304..256dfb4 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -13,10 +13,10 @@ ################################################################################# +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; + my @input = <>; clean_up_input(\@input); - -my $ansi_sequence_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; my ($file_1,$file_2,$last_file_seen); for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; @@ -24,18 +24,18 @@ ######################### # Look for the filename # ######################### - if ($line =~ /^${ansi_sequence_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + if ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { $last_file_seen = $5; $last_file_seen =~ s|a/||; # Remove a/ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_sequence_regex--- (\w\/)?(.+?)(\e|$)/) { + } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = $input[++$i]; - $next =~ /^$ansi_sequence_regex\+\+\+ (\w\/)?(.+?)(\e|$)/; + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|$)/; if ($1) { print $1; # Print out whatever color we're using } @@ -60,7 +60,7 @@ ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_sequence_regex}(@@@* .+? @@@*)(.*)/) { + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { my $hunk_header = $4; my $remain = $5; @@ -78,17 +78,17 @@ ################################### # Remove any new file permissions # ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_sequence_regex}.*new file mode/) { + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { # Don't print the line (i.e. remove it from the output); ###################################### # Remove any delete file permissions # ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_sequence_regex}deleted file mode/) { + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { # Don't print the line (i.e. remove it from the output); ##################################################### # Check if we're changing the permissions of a file # ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_sequence_regex}old mode (\d+)/) { + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { my $next = $input[++$i]; if ($1) { @@ -127,8 +127,7 @@ sub strip_empty_first_line { # Remove + or - at the beginning of the lines sub strip_leading_indicators { - my $array = shift(); # Array passed in by reference - my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + my $array = shift(); # Array passed in by reference foreach my $line (@$array) { $line =~ s/^(${ansi_color_regex})[+-]/$1 /; @@ -139,8 +138,7 @@ sub strip_leading_indicators { # Remove the first space so everything aligns left sub strip_first_column { - my $array = shift(); # Array passed in by reference - my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + my $array = shift(); # Array passed in by reference foreach my $line (@$array) { $line =~ s/^(${ansi_color_regex})[[:space:]]/$1/; @@ -150,15 +148,14 @@ sub strip_first_column { } sub mark_empty_lines { - my $array = shift(); # Array passed in by reference - my $ansi_color_regex = qr/(\e\[[0-9]{1,3}(?:;[0-9]{1,3}){0,3}[mK])?/; + my $array = shift(); # Array passed in by reference my $reset_color = "\e\\[0?m"; my $reset_escape = "\e\[m"; my $invert_color = "\e\[7m"; foreach my $line (@$array) { - $line =~ s/^(${ansi_color_regex})[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; } return 1; From 661b18e92c393eda917c533bbb2eae084c24e0ca Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 10 Mar 2016 20:50:07 +1100 Subject: [PATCH 052/443] 0.6.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3cba6ee..984028e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.6.2", + "version": "0.6.3", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 51dde07db44d83dc2b018def5e0a5c62d86fd51d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 10 Mar 2016 09:50:44 -0800 Subject: [PATCH 053/443] Error out (die) if there are any syntax errors --- libs/header_clean/header_clean.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/header_clean/header_clean.pl b/libs/header_clean/header_clean.pl index 256dfb4..929aee4 100755 --- a/libs/header_clean/header_clean.pl +++ b/libs/header_clean/header_clean.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl use strict; -use warnings; +use warnings FATAL => 'all'; use File::Basename; my $remove_file_add_header = 1; @@ -17,6 +17,7 @@ my @input = <>; clean_up_input(\@input); + my ($file_1,$file_2,$last_file_seen); for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; From 08908d73a71b522dcfd7b1823ee0ca352e9cae2d Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 15 Mar 2016 10:12:33 -0700 Subject: [PATCH 054/443] rename header_clean.pl to diff-so-fancy.pl --- diff-so-fancy | 2 +- libs/header_clean/header_clean.pl => lib/diff-so-fancy.pl | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename libs/header_clean/header_clean.pl => lib/diff-so-fancy.pl (100%) diff --git a/diff-so-fancy b/diff-so-fancy index c163500..627a774 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -59,7 +59,7 @@ print_horizontal_rule () { } print_header_clean () { - "$(get_script_dir)/libs/header_clean/header_clean.pl" + "$(get_script_dir)/lib/diff-so-fancy.pl" } # run it. diff --git a/libs/header_clean/header_clean.pl b/lib/diff-so-fancy.pl similarity index 100% rename from libs/header_clean/header_clean.pl rename to lib/diff-so-fancy.pl From d1a4c91c191b1df22fddcfc591e79cbee8cb0a63 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 15 Mar 2016 11:16:06 -0700 Subject: [PATCH 055/443] Try and be smart about what line the diff starts on 1: @@ -1,4 +1,4 @@ 2: @@ -1,5 +1,5 @@ 3: @@ -1,6 +1,6 @@ 4: @@ -1,7 +1,7 @@ 5: @@ -2,6 +2,7 @@ 6: @@ -3,7 +3,7 @@ Starting from the end (last line (10), second to last line (9))... -1: @@ -7,4 +7,4 @@ -2: @@ -6,5 +6,5 @@ -3: @@ -5,6 +5,6 @@ -4: @@ -4,7 +4,7 @@ --- lib/diff-so-fancy.pl | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 929aee4..e611162 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -73,8 +73,9 @@ my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); $last_file_seen = basename($last_file_seen); - # Plus three line for context - print "@ $last_file_seen:" . ($new_offset + 3) . " \@${remain}\n"; + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + print "@ $last_file_seen:$start_line \@${remain}\n"; ################################### # Remove any new file permissions # @@ -203,3 +204,27 @@ sub get_git_config { return \%hash; } + +# Try and be smart about what line the diff hunk starts on +sub start_line_calc { + my ($line_num,$diff_context) = @_; + my $ret; + + # Git defaults to three lines of context + my $default_context_lines = 3; + # Three lines on either side, and the line itself = 7 + my $expected_context = ($default_context_lines * 2 + 1); + + # The first three lines + if ($line_num == 1 && $diff_context < $expected_context) { + $ret = $diff_context - $default_context_lines; + } else { + $ret = $line_num + $default_context_lines; + } + + if ($ret < 1) { + $ret = 1; + } + + return $ret; +} From 9c3000d3b55f68b6d9fa3a4925ffff2553607e9f Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 15 Mar 2016 23:04:48 -0700 Subject: [PATCH 056/443] show previous file mode. --- lib/diff-so-fancy.pl | 3 ++- test/header_clean.bats | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 929aee4..37ac3ce 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -90,6 +90,7 @@ # Check if we're changing the permissions of a file # ##################################################### } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; my $next = $input[++$i]; if ($1) { @@ -97,7 +98,7 @@ } my ($new_mode) = $next =~ m/new mode (\d+)/; - print "$last_file_seen changed file mode to $new_mode\n"; + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; ##################################### # Just a regular line, print it out # ##################################### diff --git a/test/header_clean.bats b/test/header_clean.bats index 04573ac..31625e2 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -21,7 +21,7 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "header_clean permission changes" { output=$( load_fixture "file-perms" | $diff_so_fancy ) - assert_output --partial 'circle.yml changed file mode to 100755' + assert_output --partial 'circle.yml changed file mode from 100644 to 100755' } @test "header_clean 'new file mode' is removed" { From 1c417ac837bbd1885453ca88946f7399c2ce4289 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 17 Mar 2016 14:04:02 -0700 Subject: [PATCH 057/443] Add test for three way merge --- test/header_clean.bats | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/header_clean.bats b/test/header_clean.bats index 31625e2..18f46fc 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -63,6 +63,15 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) refute_output --partial 'Use of uninitialized value' } +@test "Three way merge" { + # stderr forced into output + output=$( load_fixture "complex-hunks" | $diff_so_fancy ) + # Lines should not have + or - in at the start + refute_output --partial '- my $foo = shift(); # Array passed in by reference' + refute_output --partial '+ my $array = shift(); # Array passed in by reference' + refute_output --partial ' sub parse_hunk_header {' +} + @test "mnemonicprefix handling" { output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) assert_output --partial 'modified: test/header_clean.bats' From cf8c229e75ebde525fb4bf1e24bc01172d525d24 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 17 Mar 2016 14:22:29 -0700 Subject: [PATCH 058/443] Properly remove the right number of leading columns in a three way merge --- lib/diff-so-fancy.pl | 63 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 430400a..18c2735 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -128,28 +128,6 @@ sub strip_empty_first_line { return 1; } -# Remove + or - at the beginning of the lines -sub strip_leading_indicators { - my $array = shift(); # Array passed in by reference - - foreach my $line (@$array) { - $line =~ s/^(${ansi_color_regex})[+-]/$1 /; - } - - return 1; -} - -# Remove the first space so everything aligns left -sub strip_first_column { - my $array = shift(); # Array passed in by reference - - foreach my $line (@$array) { - $line =~ s/^(${ansi_color_regex})[[:space:]]/$1/; - } - - return 1; -} - sub mark_empty_lines { my $array = shift(); # Array passed in by reference @@ -177,9 +155,6 @@ sub clean_up_input { # Remove + or - at the beginning of the lines if ($strip_leading_indicators) { strip_leading_indicators($input_array_ref); - - # Remove the first space so everything aligns left - strip_first_column($input_array_ref); } @@ -229,3 +204,41 @@ sub start_line_calc { return $ret; } + +# Remove + or - at the beginning of the lines +sub strip_leading_indicators { + my $array = shift(); # Array passed in by reference + my $columns_to_remove = 1; # Default to 1 (two-way merge) + + foreach my $line (@$array) { + # If the line is a hunk line, check for two-way vs three-way merge + # Two-way = @@ -132,6 +132,9 @@ + # Three-way = @@@ -48,10 -48,10 +48,15 @@@ + if ($line =~ /^${ansi_color_regex}@@@* (.+?) @@@*/) { + $columns_to_remove = (char_count(",",$4)) - 1; + last; + } + } + + foreach my $line (@$array) { + # Remove a number of "+", "-", or spaces equal to the indent level + $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; + } + + return 1; +} + +# Count the number of a given char in a string +sub char_count { + my ($needle,$str) = @_; + my $len = length($str); + my $ret = 0; + + for (my $i = 0; $i < $len; $i++) { + my $found = substr($str,$i,1); + + if ($needle eq $found) { $ret++; } + } + + return $ret; +} From 506d30121e264c5dfb56210a2830ca838e0f654a Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Mon, 21 Mar 2016 21:54:34 +1100 Subject: [PATCH 059/443] add npm-test --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 984028e..215c200 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "diff-so-fancy": "diff-so-fancy", "diff-highlight": "third_party/diff-highlight/diff-highlight" }, + "scripts": { + "test": "bats test" + }, "repository": { "type": "git", "url": "git+https://github.com/so-fancy/diff-so-fancy.git" From e90dd5bbbf16affc76d85ed1acb310bf7fbdc878 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Mon, 21 Mar 2016 22:05:09 +1100 Subject: [PATCH 060/443] add test for #131 When diff starts in the first three lines Closes #120 --- test/fixtures/first-three-line.diff | 18 ++++++++++++++++++ test/header_clean.bats | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 test/fixtures/first-three-line.diff diff --git a/test/fixtures/first-three-line.diff b/test/fixtures/first-three-line.diff new file mode 100644 index 0000000..f51e921 --- /dev/null +++ b/test/fixtures/first-three-line.diff @@ -0,0 +1,18 @@ +commit fbf440dd9c32a60f9bc97693a6bd7b5ca87ec9fc +Author: Steve Mao +Date: Wed Mar 9 19:21:27 2016 +1100 + + 0.6.2 + +diff --git package.json package.json +index 7379c98..3cba6ee 100644 +--- package.json ++++ package.json +@@ -1,6 +1,6 @@ + { + "name": "diff-so-fancy", +- "version": "0.6.1", ++ "version": "0.6.2", + "description": "Good-lookin' diffs with diff-highlight and more", + "bin": { + "diff-so-fancy": "diff-so-fancy", diff --git a/test/header_clean.bats b/test/header_clean.bats index 31625e2..b807637 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -63,6 +63,12 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) refute_output --partial 'Use of uninitialized value' } +@test "Hunk formatting: @@ -1,6 +1,6 @@" { + # stderr forced into output + output=$( load_fixture "first-three-line" | $diff_so_fancy ) + assert_output --partial '@ package.json:3 @' +} + @test "mnemonicprefix handling" { output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) assert_output --partial 'modified: test/header_clean.bats' From 79d2d514bc01bb70aed300b010243bee05bdb134 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 9 Mar 2016 20:38:10 +1100 Subject: [PATCH 061/443] improve reporting bugs Closes #121 --- readme.md | 16 ++++++++++++-- report-bug.sh | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100755 report-bug.sh diff --git a/readme.md b/readme.md index e905ae3..99ab613 100644 --- a/readme.md +++ b/readme.md @@ -92,12 +92,24 @@ Pull requests quite welcome, along with any feedback or ideas. ### Reporting bugs -Providing the original diff text inline in the issue is quite helpful. +If you find a bug using the following command ```sh -git --no-pager diff > output.diff +git diff HEAD..HEAD^ ``` +You can use [report-bug.sh](./report-bug.sh) we provide + +```sh +./report-bug.sh 'git diff HEAD..HEAD^' + +# or + +curl https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/report-bug.sh | bash -s 'git diff HEAD..HEAD^' 'diff.txt' +``` + +Grab the output file and attach to the GitHub issue you create. A base64 version is also copied to your clipboard so you can paste to the issue. + ### Hacking ```sh diff --git a/report-bug.sh b/report-bug.sh new file mode 100755 index 0000000..8acb479 --- /dev/null +++ b/report-bug.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +clipboard() { + local copy_cmd + if [ -n "$PBCOPY_SERVER" ]; then + local body="" buffer + body=$(cat) + # while IFS= read -r buffer; do + # body="$body$buffer\n"; + # done + curl $PBCOPY_SERVER --data-urlencode body="$body" >/dev/null 2>&1 + return $? + fi + if type putclip >/dev/null 2>&1; then + copy_cmd="putclip" + elif [ -e /dev/clipboard ];then + cat > /dev/clipboard + return 0 + elif type clip >/dev/null 2>&1; then + if [[ $LANG = UTF-8 ]]; then + copy_cmd="iconv -f utf-8 -t shift_jis | clip" + else + copy_cmd=clip + fi + # copy_cmd=clip + elif which pbcopy >/dev/null 2>&1; then + copy_cmd="pbcopy" + elif which xclip >/dev/null 2>&1; then + # copy_cmd="xclip -i -selection clipboard" + copy_cmd="xclip" + elif which xsel >/dev/null 2>&1 ; then + local copy_cmd="xsel -b" + fi + if [ -n "$copy_cmd" ] ;then + eval $copy_cmd + else + echo "clipboard is unavailable" 1>&2 + fi +} + +file=${2:-diff.txt} + +echo $1 > $file +eval $1 >> $file + +echo " + +" >> $file + +echo "$1 --color" >> $file +eval "$1 --color" >> $file + +echo "git config pager.diff" >> $file +eval "git config pager.diff" >> $file + +echo "git config pager.show" >> $file +eval "git config pager.show" >> $file + +cat $file | base64 | clipboard From 108f54bf783b6e40765876729972b6d266e0c489 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 22 Mar 2016 21:48:50 +1100 Subject: [PATCH 062/443] 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 215c200..735f0e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.6.3", + "version": "0.7.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 0ef08b5019047ae6bb15d0b91647702ca44bf1f1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 28 Mar 2016 09:10:44 -0700 Subject: [PATCH 063/443] Move "highlight context line" to Perl (one less sed call) --- diff-so-fancy | 7 ------- lib/diff-so-fancy.pl | 14 ++++++++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 627a774..e9c1b2f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -30,7 +30,6 @@ NL=" " color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" reset_color="${CSI}0?m" -dim_magenta="${CSI}38;05;146m" git_index_line_pattern="^($color_code_regex)index .*" @@ -41,11 +40,6 @@ format_diff_header () { | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1\+\+\+\+\5\\${NL}\1$(print_horizontal_rule)/g" } -colorize_context_line () { - # extra color for @@ context line - $SED -E "s/@@$reset_color $reset_color(.*$)/@@ $dim_magenta\1/g" -} - print_horizontal_rule () { let width="$(tput cols)" @@ -67,5 +61,4 @@ print_header_clean () { cat $input \ | $diff_highlight \ | format_diff_header \ - | colorize_context_line \ | print_header_clean diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 18c2735..321d3ab 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -14,6 +14,7 @@ ################################################################################# my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $dim_magenta = "\e[38;5;146m"; my @input = <>; clean_up_input(\@input); @@ -62,9 +63,8 @@ # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - my $hunk_header = $4; - my $remain = $5; + my $remain = bleach_text($5); if ($1) { print $1; # Print out whatever color we're using @@ -75,8 +75,7 @@ # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${remain}\n"; - + print "@ $last_file_seen:$start_line \@${dim_magenta}${remain}\n"; ################################### # Remove any new file permissions # ################################### @@ -242,3 +241,10 @@ sub char_count { return $ret; } + +sub bleach_text { + my $str = shift(); + $str =~ s/\e\[\d*(;\d+)*m//mg; + + return $str; +} From 33e83ed84528836a274165f4613da7f9f24bd370 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 28 Mar 2016 09:15:49 -0700 Subject: [PATCH 064/443] Remove a bash variable so the tests won't fail --- diff-so-fancy | 1 - 1 file changed, 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index e9c1b2f..4e35bef 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -29,7 +29,6 @@ CSI=$'\x1b\[' NL=" " color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" -reset_color="${CSI}0?m" git_index_line_pattern="^($color_code_regex)index .*" From b90ff629e7ff54a65c0a04f1e0fa38d59bd9758d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 28 Mar 2016 11:59:49 -0700 Subject: [PATCH 065/443] Specifically bold the context line --- lib/diff-so-fancy.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 321d3ab..574a539 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -15,6 +15,8 @@ my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; my $dim_magenta = "\e[38;5;146m"; +my $reset_color = "\e[0m"; +my $bold = "\e[1m"; my @input = <>; clean_up_input(\@input); @@ -75,7 +77,7 @@ # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${dim_magenta}${remain}\n"; + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}\n"; ################################### # Remove any new file permissions # ################################### From dc3c8b67889c34701ff0a192727dc60a7b67acd5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 30 Mar 2016 08:08:26 -0700 Subject: [PATCH 066/443] Don't error out if we don't have a filename --- lib/diff-so-fancy.pl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 574a539..63178a2 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -21,7 +21,8 @@ my @input = <>; clean_up_input(\@input); -my ($file_1,$file_2,$last_file_seen); +my ($file_1,$file_2); +my $last_file_seen = ""; for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; @@ -34,16 +35,19 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|$)/) { + } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = $input[++$i]; - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|$)/; + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; if ($1) { print $1; # Print out whatever color we're using } $file_2 = $5; + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } # If they're the same it's a modify if ($file_1 eq $file_2) { @@ -77,7 +81,7 @@ # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}\n"; + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### From 52a25d04d79aa73b77eee6e5996cf39a89c01e8e Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Wed, 30 Mar 2016 11:08:55 -0700 Subject: [PATCH 067/443] update readme to mention the lib dependency --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 99ab613..adb8163 100644 --- a/readme.md +++ b/readme.md @@ -63,6 +63,7 @@ If you want, you can choose to install manually: 1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. 1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. 1. Place them in a location that is in your `PATH` directly or with symlinks. +1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. 1. Set up the git `pager.diff` and `pager.show` configs, as described above. Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). From 6f8cdc5cd9673a54623f95a79a2a9c91b41fbc9a Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Wed, 30 Mar 2016 11:14:07 -0700 Subject: [PATCH 068/443] add example install listing to readme manual install is getting confusing, hopefully this helps. --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index adb8163..25bedd1 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,10 @@ If you want, you can choose to install manually: 1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. 1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. 1. Place them in a location that is in your `PATH` directly or with symlinks. -1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. +1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: + * `~/bin/diff-highlight` + * `~/bin/diff-so-fancy` + * `~/bin/lib/diff-so-fancy.pl` 1. Set up the git `pager.diff` and `pager.show` configs, as described above. Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). From 65c6d87e2d59d40c86a1fe06d11e9fb14d3eb068 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 30 Mar 2016 16:09:08 -0700 Subject: [PATCH 069/443] Add a test for non-git diff parsing --- test/fixtures/weird.diff | 12 ++++++++++++ test/header_clean.bats | 6 ++++++ 2 files changed, 18 insertions(+) create mode 100644 test/fixtures/weird.diff diff --git a/test/fixtures/weird.diff b/test/fixtures/weird.diff new file mode 100644 index 0000000..21f5de9 --- /dev/null +++ b/test/fixtures/weird.diff @@ -0,0 +1,12 @@ +diff -r 62e478a3f1c8 -r 47aeb87ce9cd doc/manual.xml.head +--- a/doc/manual.xml.head ++++ b/doc/manual.xml.head +@@ -8352,7 +8352,7 @@ + -aattach a file to a message + -bspecify a blind carbon-copy (BCC) address + -cspecify a carbon-copy (Cc) address +--dlog debugging output to ~/.muttdebug0 if mutt was complied with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended) ++-dlog debugging output to ~/.muttdebug0 if mutt was compiled with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended) + -Dprint the value of all Mutt variables to stdout + -Eedit the draft (-H) or include (-i) file + -especify a config command to be run after initialization files are read diff --git a/test/header_clean.bats b/test/header_clean.bats index cad777b..9e23681 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -82,3 +82,9 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) assert_output --partial 'modified: test/header_clean.bats' } + +@test "non-git diff parsing" { + output=$( load_fixture "weird" | $diff_so_fancy ) + assert_output --partial 'modified: doc/manual.xml.head' + assert_output --partial '@ manual.xml.head:8355 @' +} From 5c47ebec849a689af7e49ea84ccb9473820e7502 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 30 Mar 2016 08:08:26 -0700 Subject: [PATCH 070/443] Don't error out if we don't have a filename --- lib/diff-so-fancy.pl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 574a539..63178a2 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -21,7 +21,8 @@ my @input = <>; clean_up_input(\@input); -my ($file_1,$file_2,$last_file_seen); +my ($file_1,$file_2); +my $last_file_seen = ""; for (my $i = 0; $i <= $#input; $i++) { my $line = $input[$i]; @@ -34,16 +35,19 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|$)/) { + } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = $input[++$i]; - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|$)/; + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; if ($1) { print $1; # Print out whatever color we're using } $file_2 = $5; + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } # If they're the same it's a modify if ($file_1 eq $file_2) { @@ -77,7 +81,7 @@ # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}\n"; + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### From 16f0e256efd86a8eea677dd7b5366753ee4db2eb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 30 Mar 2016 16:09:08 -0700 Subject: [PATCH 071/443] Add a test for non-git diff parsing --- test/fixtures/weird.diff | 12 ++++++++++++ test/header_clean.bats | 6 ++++++ 2 files changed, 18 insertions(+) create mode 100644 test/fixtures/weird.diff diff --git a/test/fixtures/weird.diff b/test/fixtures/weird.diff new file mode 100644 index 0000000..21f5de9 --- /dev/null +++ b/test/fixtures/weird.diff @@ -0,0 +1,12 @@ +diff -r 62e478a3f1c8 -r 47aeb87ce9cd doc/manual.xml.head +--- a/doc/manual.xml.head ++++ b/doc/manual.xml.head +@@ -8352,7 +8352,7 @@ + -aattach a file to a message + -bspecify a blind carbon-copy (BCC) address + -cspecify a carbon-copy (Cc) address +--dlog debugging output to ~/.muttdebug0 if mutt was complied with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended) ++-dlog debugging output to ~/.muttdebug0 if mutt was compiled with +DEBUG; it can range from 1-5 and affects verbosity (a value of 2 is recommended) + -Dprint the value of all Mutt variables to stdout + -Eedit the draft (-H) or include (-i) file + -especify a config command to be run after initialization files are read diff --git a/test/header_clean.bats b/test/header_clean.bats index cad777b..9e23681 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -82,3 +82,9 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) assert_output --partial 'modified: test/header_clean.bats' } + +@test "non-git diff parsing" { + output=$( load_fixture "weird" | $diff_so_fancy ) + assert_output --partial 'modified: doc/manual.xml.head' + assert_output --partial '@ manual.xml.head:8355 @' +} From 9df75973613885d9520c3718768e054846be8dfb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 31 Mar 2016 14:36:54 -0700 Subject: [PATCH 072/443] By default don't remove any lines (i.e. don't fail on non-diff files) --- lib/diff-so-fancy.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 63178a2..2215991 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -213,7 +213,7 @@ sub start_line_calc { # Remove + or - at the beginning of the lines sub strip_leading_indicators { my $array = shift(); # Array passed in by reference - my $columns_to_remove = 1; # Default to 1 (two-way merge) + my $columns_to_remove = 0; # Don't remove any lines by default foreach my $line (@$array) { # If the line is a hunk line, check for two-way vs three-way merge @@ -225,6 +225,10 @@ sub strip_leading_indicators { } } + if ($columns_to_remove == 0) { + return 1; # Nothing to do + } + foreach my $line (@$array) { # Remove a number of "+", "-", or spaces equal to the indent level $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; From 22b045fbfa871e6a86ef4e72164e009754ea7d56 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 31 Mar 2016 15:23:03 -0700 Subject: [PATCH 073/443] Streamline the input by reading one line at a time Before we'd load the entire input in to memory, but this should: 1) Use less memory 2) Output things to the screen a lot faster --- lib/diff-so-fancy.pl | 115 +++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 2215991..e5e9346 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -18,13 +18,38 @@ my $reset_color = "\e[0m"; my $bold = "\e[1m"; -my @input = <>; -clean_up_input(\@input); +my $columns_to_remove = 0; my ($file_1,$file_2); my $last_file_seen = ""; -for (my $i = 0; $i <= $#input; $i++) { - my $line = $input[$i]; +my $i = 0; + +while (my $line = <>) { + + ###################################################### + # Pre-process the line before we do any other markup + ###################################################### + + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; + } + + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + + ###################### + # End pre-processing + ###################### + + ####################################################################### ######################### # Look for the filename # @@ -35,12 +60,12 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + } elsif ($line =~ /^$ansi_color_regex---* (\w\/)?(.+?)(\e|\t|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md - my $next = $input[++$i]; - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + my $next = <>; + $next =~ /^$ansi_color_regex\+\+\+* (\w\/)?(.+?)(\e|\t|$)/; if ($1) { print $1; # Print out whatever color we're using } @@ -69,8 +94,9 @@ # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - my $hunk_header = $4; - my $remain = bleach_text($5); + my $hunk_header = $4; + my $remain = bleach_text($5); + $columns_to_remove = (char_count(",",$hunk_header)) - 1; if ($1) { print $1; # Print out whatever color we're using @@ -97,7 +123,7 @@ ##################################################### } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { my ($old_mode) = $4; - my $next = $input[++$i]; + my $next = <>; if ($1) { print $1; # Print out whatever color we're using @@ -111,8 +137,14 @@ } else { print $line; } + + $i++; } +###################################################################################################### +# End regular code, begin functions +###################################################################################################### + # Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 sub parse_hunk_header { my ($line) = @_; @@ -122,48 +154,16 @@ sub parse_hunk_header { return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); } -sub strip_empty_first_line { - my $array = shift(); # Array passed in by reference - - # If the first line is just whitespace remove it - if (defined($array->[0]) && $array->[0] =~ /^\s*$/) { - shift(@$array); # Throw away the first line - } - - return 1; -} - -sub mark_empty_lines { - my $array = shift(); # Array passed in by reference +sub mark_empty_line { + my $line = shift(); my $reset_color = "\e\\[0?m"; my $reset_escape = "\e\[m"; my $invert_color = "\e\[7m"; - foreach my $line (@$array) { - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; - } + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; - return 1; -} - -sub clean_up_input { - my $input_array_ref = shift(); - - # Usually the first line of a diff is whitespace so we remove that - strip_empty_first_line($input_array_ref); - - if ($mark_empty_lines) { - mark_empty_lines($input_array_ref); - } - - # Remove + or - at the beginning of the lines - if ($strip_leading_indicators) { - strip_leading_indicators($input_array_ref); - } - - - return 1; + return $line; } # Return git config as a hash @@ -212,29 +212,16 @@ sub start_line_calc { # Remove + or - at the beginning of the lines sub strip_leading_indicators { - my $array = shift(); # Array passed in by reference - my $columns_to_remove = 0; # Don't remove any lines by default - - foreach my $line (@$array) { - # If the line is a hunk line, check for two-way vs three-way merge - # Two-way = @@ -132,6 +132,9 @@ - # Three-way = @@@ -48,10 -48,10 +48,15 @@@ - if ($line =~ /^${ansi_color_regex}@@@* (.+?) @@@*/) { - $columns_to_remove = (char_count(",",$4)) - 1; - last; - } - } + my $line = shift(); # Array passed in by reference + my $columns_to_remove = shift(); # Don't remove any lines by default if ($columns_to_remove == 0) { - return 1; # Nothing to do + return $line; # Nothing to do } - foreach my $line (@$array) { - # Remove a number of "+", "-", or spaces equal to the indent level - $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; - } + $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; - return 1; + return $line; } # Count the number of a given char in a string From a79fda14826efcdbec1387b7fd58678d3ca001f1 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 2 Apr 2016 13:26:53 +1100 Subject: [PATCH 074/443] 0.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 735f0e6..6cfc9f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.7.0", + "version": "0.7.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 1ba35037a0f2388f72e99910413a2514752bf025 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 4 Apr 2016 17:14:53 +0200 Subject: [PATCH 075/443] Do not sneak in another dash/plus in format_diff_header And especially do not match with `*` for this, which might trigger false positives. This moves the removal of leading +/- down to where those lines are printed, instead of applying it globally to the input. Ref: https://github.com/so-fancy/diff-so-fancy/commit/2f743f07941e744b2f24218dc422f26512823549#commitcomment-16952151 Closes https://github.com/so-fancy/diff-so-fancy/pull/143. --- diff-so-fancy | 4 ++-- lib/diff-so-fancy.pl | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 4e35bef..0f2911c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -35,8 +35,8 @@ git_index_line_pattern="^($color_code_regex)index .*" format_diff_header () { # simplify the unified patch diff header $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ - | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1\-\-\-\-\5/g" \ - | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1\+\+\+\+\5\\${NL}\1$(print_horizontal_rule)/g" + | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1---\5/g" \ + | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1+++\5\\${NL}\1$(print_horizontal_rule)/g" } print_horizontal_rule () { diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index e5e9346..39b4d18 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -40,11 +40,6 @@ $line = mark_empty_line($line); } - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); - } - ###################### # End pre-processing ###################### @@ -60,12 +55,12 @@ ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_color_regex---* (\w\/)?(.+?)(\e|\t|$)/) { + } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md my $next = <>; - $next =~ /^$ansi_color_regex\+\+\+* (\w\/)?(.+?)(\e|\t|$)/; + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; if ($1) { print $1; # Print out whatever color we're using } @@ -135,6 +130,10 @@ # Just a regular line, print it out # ##################################### } else { + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } print $line; } From 0e647fb59c2a1e70558133a88a7891c90eda18ea Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 4 Apr 2016 18:27:40 +0200 Subject: [PATCH 076/443] Also move mark_empty_line down Ref: https://github.com/so-fancy/diff-so-fancy/pull/143#discussion_r58394936 --- lib/diff-so-fancy.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 39b4d18..4d9a5c6 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -35,11 +35,6 @@ next; } - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } - ###################### # End pre-processing ###################### @@ -130,6 +125,11 @@ # Just a regular line, print it out # ##################################### } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + # Remove the correct number of leading " " or "+" or "-" if ($strip_leading_indicators) { $line = strip_leading_indicators($line,$columns_to_remove); From 9b283058e127a7368df4e0c5228398eee35c7849 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 4 Apr 2016 18:21:31 +0200 Subject: [PATCH 077/443] Handle mnemonicprefix with last_file_seen This is not really required currently, because `basename` is used on it, but it is good to have it fixed nonetheless. Closes https://github.com/so-fancy/diff-so-fancy/pull/146. --- lib/diff-so-fancy.pl | 2 +- test/fixtures/mnemonicprefix.diff | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 4d9a5c6..1008cde 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -46,7 +46,7 @@ ######################### if ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { $last_file_seen = $5; - $last_file_seen =~ s|a/||; # Remove a/ + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). ######################################## # Find the first file: --- a/README.md # ######################################## diff --git a/test/fixtures/mnemonicprefix.diff b/test/fixtures/mnemonicprefix.diff index dbe985e..1bb42a7 100644 --- a/test/fixtures/mnemonicprefix.diff +++ b/test/fixtures/mnemonicprefix.diff @@ -62,3 +62,10 @@ + output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) + assert_output --partial '@ setup-a-new-machine.sh:33 @' +} +diff --git c/hello.txt i/hello.txt +deleted file mode 100644 +index 0c767bc..0000000 +--- c/hello.txt ++++ /dev/null +@@ -1 +0,0 @@ +-HI THERE From c3a141b7e60c597a38426fa709b6a0e3bd7fe766 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 4 Apr 2016 17:38:41 +0200 Subject: [PATCH 078/443] Fix handling lines starting with dashes as file header Use a global state ($in_hunk) to only look for `---`-lines before (and not in) the actual hunk. Closes https://github.com/so-fancy/diff-so-fancy/pull/144. --- lib/diff-so-fancy.pl | 5 ++++- test/diff-so-fancy.bats | 5 +++++ test/fixtures/leading-dashes.diff | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/leading-dashes.diff diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 1008cde..ba48641 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -23,6 +23,7 @@ my ($file_1,$file_2); my $last_file_seen = ""; my $i = 0; +my $in_hunk = 0; while (my $line = <>) { @@ -47,10 +48,11 @@ if ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { $last_file_seen = $5; $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; ######################################## # Find the first file: --- a/README.md # ######################################## - } elsif ($line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { $file_1 = $5; # Find the second file on the next line: +++ b/README.md @@ -84,6 +86,7 @@ # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; my $hunk_header = $4; my $remain = bleach_text($5); $columns_to_remove = (char_count(",",$hunk_header)) - 1; diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index e341eb3..af50203 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -51,3 +51,8 @@ if begin" assert_line --index 1 --partial "modified: fish/functions/ls.fish" assert_line --index 2 --partial "─────" } + +@test "Leading dashes are not handled as modified" { + output=$( load_fixture "leading-dashes" | $diff_so_fancy ) + refute_output --partial "modified: Callback" +} diff --git a/test/fixtures/leading-dashes.diff b/test/fixtures/leading-dashes.diff new file mode 100644 index 0000000..7845fb0 --- /dev/null +++ b/test/fixtures/leading-dashes.diff @@ -0,0 +1,13 @@ +diff --git i/lib/awful/widget/keyboardlayout.lua w/lib/awful/widget/keyboardlayout.lua +index f9142b8..b6e8ce0 100644 +--- i/lib/awful/widget/keyboardlayout.lua ++++ w/lib/awful/widget/keyboardlayout.lua +@@ -113,7 +113,7 @@ keyboardlayout.xkeyboard_country_code = { + ["za"] = true, -- South Africa + } + +--- Callback for updaing current layout ++-- Callback for updating current layout. + local function update_status (self) + self._current = awesome.xkb_get_layout_group(); + local text = "" From 4b32fbf78e9c3eeb12a019163f025a76fb4a7ed6 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 5 Apr 2016 10:12:49 +1000 Subject: [PATCH 079/443] add redirect to update-deps.sh Add -L and point the URLs (which will redirect; githubusercontent.com is the implementation detail at this point). --- update-deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update-deps.sh b/update-deps.sh index e9180c5..9eda47b 100755 --- a/update-deps.sh +++ b/update-deps.sh @@ -1,4 +1,4 @@ #!/bin/bash -curl -o "third_party/diff-highlight/diff-highlight" "https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight/diff-highlight" -curl -o "third_party/diff-highlight/README" "https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight/README" +curl -oL "third_party/diff-highlight/diff-highlight" "https://github.com/git/git/raw/master/contrib/diff-highlight/diff-highlight" +curl -oL "third_party/diff-highlight/README" "https://github.com/git/git/raw/master/contrib/diff-highlight/README" From 391fb536f7e2957687225e790291c7400ab73caf Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 31 Mar 2016 09:05:04 -0700 Subject: [PATCH 080/443] Version 0.1 of reading items from ~/.gitconfig --- lib/diff-so-fancy.pl | 104 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 7 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index ba48641..ee3b068 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -7,9 +7,9 @@ my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; -my $change_hunk_indicators = 1; -my $strip_leading_indicators = 1; -my $mark_empty_lines = 1; +my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); +my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); +my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); ################################################################################# @@ -168,13 +168,95 @@ sub mark_empty_line { return $line; } +sub boolean { + my $str = shift(); + $str = trim($str); + + if ($str eq "" || $str =~ /^(no|false|0)$/i) { + return 0; + } else { + return 1; + } +} + +# Memoize getting the git config +{ + my $static_config; + + sub git_config_raw { + if ($static_config) { + # If we already have the config return that + return $static_config; + } + + my $cmd = "git config --list"; + my @out = `$cmd`; + + $static_config = \@out; + + return \@out; + } +} + +# Fetch a textual item from the git config +sub git_config { + my $search_key = lc($_[0] // ""); + my $default_value = lc($_[1] // ""); + + my $out = git_config_raw(); + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return $default_value; + } + + my $raw = {}; + foreach my $line (@$out) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + + $raw->{$key} = $value; + } + + # If we're given a search key return that, else return the hash + if ($search_key) { + return $raw->{$search_key} // $default_value; + } else { + return $raw; + } +} + +# Fetch a boolean item from the git config +sub git_config_boolean { + my $search_key = lc($_[0] // ""); + my $default_value = lc($_[1] // 0); # Default to false + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return $default_value; + } + + my $result = git_config($search_key,$default_value); + my $ret = boolean($result); + + return $ret; +} + +# Check if we're inside of BATS +sub in_unit_test { + if ($ENV{BATS_CWD}) { + return 1; + } else { + return 0; + } +} + # Return git config as a hash -sub get_git_config { - my $cmd = "git config --list"; - my @out = `$cmd`; +sub get_git_config_hash { + my $out = git_config_raw(); my %hash; - foreach my $line (@out) { + foreach my $line (@$out) { my ($key,$value) = split("=",$line,2); $value =~ s/\s+$//; my @path = split(/\./,$key); @@ -247,3 +329,11 @@ sub bleach_text { return $str; } + +sub trim { + my $s = shift(); + if (!$s) { return ""; } + $s =~ s/^\s*|\s*$//g; + + return $s; +} From 88480ceed707d2be269fc0a48d8efc20390216a9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 31 Mar 2016 09:05:16 -0700 Subject: [PATCH 081/443] Add third_party/ansi_reveal for troubleshooting ANSI issues --- third_party/ansi-reveal/ansi-reveal.pl | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 third_party/ansi-reveal/ansi-reveal.pl diff --git a/third_party/ansi-reveal/ansi-reveal.pl b/third_party/ansi-reveal/ansi-reveal.pl new file mode 100755 index 0000000..8937739 --- /dev/null +++ b/third_party/ansi-reveal/ansi-reveal.pl @@ -0,0 +1,59 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Getopt::Long; + +my $raw = 0; +GetOptions( + 'raw' => \$raw, +); + +if ($raw) { + output_raw(); +} else { + output_human(); +} + +############################################### + +sub output_human { + my $reset = "\e[0m"; + + while (my $l = <>) { + # Basic reset + $l =~ s/(\e\[0?m)/${reset}[RESET]/g; + + # Bold text + $l =~ s/(\e\[1m)/${reset}[BOLD]/g; + + # Inverted text + $l =~ s/(\e\[7m)/${reset}. "[INVRT]$1"/eg; + $l =~ s/(\e\[7;(\d+)m)/${reset}. "[INVRT" . sprintf("%03d",($2-30)) . "]$1"/eg; + + # Basic 16 color ANSI + $l =~ s/(\e\[(3[0-7])m)/${reset}. "[BASIC" . sprintf("%03d",($2-30)) . "]$1"/eg; + $l =~ s/(\e\[1;(3[0-7])m)/${reset}. "[BRIGH" . sprintf("%03d",($2-30)) . "]$1"/eg; + + # 256 Color/Background + $l =~ s/(\e\[38;0?5;(\d+)m)/${reset}. "[COLOR" . sprintf("%03d",$2) . "]$1"/eg; + $l =~ s/(\e\[48;0?5;(\d+)m)/${reset}. "[BACKG" . sprintf("%03d",$2) . "]$1"/eg; + + $l =~ s/(\e\[1;(3[0-7]);48;5;(\d+)m)/${reset}. "[FGBG" . sprintf("%02d:%02d",($2-30),($3)) . "]$1"/eg; + + # 24bit Color/Background + $l =~ s/(\e\[38;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGB#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; + $l =~ s/(\e\[48;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGBBG#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; + + print $l; + } +} + +############################################### + +sub output_raw { + while (<>) { + s/\e/\\e/g; + print; + } +} From 3f77b19f1623d45583e9f6ab4ce56cc40a87f13c Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sun, 17 Apr 2016 21:27:35 +1000 Subject: [PATCH 082/443] 0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6cfc9f4..af6e24c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.7.1", + "version": "0.8.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 813df794586c11cc489261d663f50e62316a4dfe Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 11 May 2016 11:05:36 +1000 Subject: [PATCH 083/443] add docs for git config options (#152) * add docs for git config options fixes #151 * correct some mistakes of git config docs --- readme.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/readme.md b/readme.md index 25bedd1..12d7378 100644 --- a/readme.md +++ b/readme.md @@ -71,6 +71,35 @@ If you want, you can choose to install manually: Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). +## options + +### markEmptyLines + +Should the first block of an empty line be colored. + +### changeHunkIndicators + +Simplify git header chunks to a more human readable format. + +### stripLeadingSymbols + +Should the pesky `+` or `-` at line-start be removed. + +By default all the configs are true. You can turn any off by running: + +``` +git config --bool --global diff-so-fancy.markEmptyLines false +git config --bool --global diff-so-fancy.changeHunkIndicators false +git config --bool --global diff-so-fancy.stripLeadingSymbols false +``` + +To reset them to default (`true`): + +``` +git config --unset --global diff-so-fancy.markEmptyLines +git config --unset --global diff-so-fancy.changeHunkIndicators +git config --unset --global diff-so-fancy.stripLeadingSymbols +``` ## Pro-tips From 60079d604d8e1f4c63a6d9f5a6c897b08ffb22b8 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 12 May 2016 21:09:05 +1000 Subject: [PATCH 084/443] 0.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af6e24c..74c554c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.8.0", + "version": "0.9.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 796a5a5390dead81762969b5222baf15a6ac02b6 Mon Sep 17 00:00:00 2001 From: Jonas Hermsmeier Date: Thu, 12 May 2016 14:14:12 +0200 Subject: [PATCH 085/443] Fix Use of uninitialized $value in substitution (s///) --- lib/diff-so-fancy.pl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index ee3b068..b26d240 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -212,10 +212,11 @@ sub git_config { my $raw = {}; foreach my $line (@$out) { - my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - - $raw->{$key} = $value; + if ($line =~ /=/) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + $raw->{$key} = $value; + } } # If we're given a search key return that, else return the hash From 4dbf1e6bea0bbc36ba70d7b40414bb405917eccf Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Fri, 13 May 2016 18:47:30 +1000 Subject: [PATCH 086/443] 0.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74c554c..158c8e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.9.0", + "version": "0.9.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From e8c9800fdbb863f1327c27c2a5431a749665dcbf Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Mon, 16 May 2016 10:41:33 +1000 Subject: [PATCH 087/443] :lipstick: minor whitespace fixes --- readme.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 12d7378..50a7f5d 100644 --- a/readme.md +++ b/readme.md @@ -55,7 +55,6 @@ git config --global color.diff-highlight.newHighlight "green bold 22" ``` You may also want to configure [general diff colors](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/.gitconfig#L23-L36). - ## Manual install If you want, you can choose to install manually: @@ -63,7 +62,7 @@ If you want, you can choose to install manually: 1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. 1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. 1. Place them in a location that is in your `PATH` directly or with symlinks. -1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: +1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: * `~/bin/diff-highlight` * `~/bin/diff-so-fancy` * `~/bin/lib/diff-so-fancy.pl` @@ -150,7 +149,7 @@ Grab the output file and attach to the GitHub issue you create. A base64 version git clone https://github.com/so-fancy/diff-so-fancy/ && cd diff-so-fancy # test a saved diff against your local version -cat test/fixtures/ls-function.diff | ./diff-so-fancy +cat test/fixtures/ls-function.diff | ./diff-so-fancy # setup symlinks to use local copy npm link @@ -163,7 +162,7 @@ You'll need to install [bats](https://github.com/sstephenson/bats#installing-bat ```sh git submodule sync -git submodule update --init # pull in the assertion library, bats-assert +git submodule update --init # pull in the assertion library, bats-assert # Run the test suite once: bats test @@ -174,7 +173,6 @@ ls --color=never diff-so-fancy test/*.bats | entr bats test ``` When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` - You can lint your scripts via shellcheck, our CI bots will also check. ```sh From 353058f543b047282e47ad14a3ac94f92dc90b03 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 May 2016 02:08:18 -0400 Subject: [PATCH 088/443] Suggest configuring for git log as well (#154) * Suggest configuring for git log as well * Typo revert * Make this basically a revert of #44 --- readme.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 50a7f5d..e190a0f 100644 --- a/readme.md +++ b/readme.md @@ -19,10 +19,9 @@ You can do one-off fanciness: git diff --color | diff-so-fancy ``` -**But**, you'll probably want to fancify all your diffs. Run this so `git diff` (and `git show`) will use it: +**But**, you'll probably want to fancify all your diffs. Run this so `git diff` will use it: ```shell -git config --global pager.diff "diff-so-fancy | less --tabs=4 -RFX" -git config --global pager.show "diff-so-fancy | less --tabs=4 -RFX" +git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` However, if you'd prefer to do the fanciness on-demand with `git dsf`, drop an alias in your `~/.gitconfig`: @@ -66,7 +65,7 @@ If you want, you can choose to install manually: * `~/bin/diff-highlight` * `~/bin/diff-so-fancy` * `~/bin/lib/diff-so-fancy.pl` -1. Set up the git `pager.diff` and `pager.show` configs, as described above. +1. Set up the git `core.pager` config, as described above. Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). From 8e2c6acd5679ec5a8937949f3021ba1a84f75cba Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 16 May 2016 09:07:02 -0700 Subject: [PATCH 089/443] Move horizontal header to Perl --- diff-so-fancy | 8 -------- lib/diff-so-fancy.pl | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0f2911c..5811717 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -32,13 +32,6 @@ color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" git_index_line_pattern="^($color_code_regex)index .*" -format_diff_header () { - # simplify the unified patch diff header - $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ - | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1---\5/g" \ - | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1+++\5\\${NL}\1$(print_horizontal_rule)/g" -} - print_horizontal_rule () { let width="$(tput cols)" @@ -59,5 +52,4 @@ print_header_clean () { # shellcheck disable=SC2002 cat $input \ | $diff_highlight \ - | format_diff_header \ | print_header_clean diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index b26d240..3fe5c34 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -4,6 +4,9 @@ use warnings FATAL => 'all'; use File::Basename; +use utf8; +binmode STDOUT,':utf8'; + my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; @@ -17,6 +20,7 @@ my $dim_magenta = "\e[38;5;146m"; my $reset_color = "\e[0m"; my $bold = "\e[1m"; +my $horizontal_color = ""; my $columns_to_remove = 0; @@ -42,10 +46,14 @@ ####################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $horizontal_color = $1; + print horizontal_rule($horizontal_color); ######################### # Look for the filename # ######################### - if ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { $last_file_seen = $5; $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). $in_hunk = 0; @@ -82,6 +90,8 @@ } else { print "$file_1 -> $file_2\n"; } + + print horizontal_rule($horizontal_color); ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## @@ -338,3 +348,24 @@ sub trim { return $s; } + + +sub horizontal_rule { + my $color = $_[0] || ""; + my $width = `tput cols`; + my $uname = `uname -s`; + + if ($uname =~ /MINGW32|MSYS/) { + $width--; + } + + # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm + #my $dash = "\x{2014}"; + # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm + my $dash = "\x{2500}"; + + # Draw the line + my $ret = $color . ($dash x $width) . "\n"; + + return $ret; +} From 42553291ed303255cdfbe3852ec88b98699a51a6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 16 May 2016 09:22:32 -0700 Subject: [PATCH 090/443] Handle binary file modifications like we do text ones --- lib/diff-so-fancy.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 3fe5c34..3f53704 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -121,6 +121,12 @@ ###################################### } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { # Don't print the line (i.e. remove it from the output); + ###################################### + # Look for binary file changes + ###################################### + } elsif ($line =~ /Binary files \w\/(.+?) and \w\/(.+?) differ/) { + print "${horizontal_color}modified: $2 (binary)\n"; + print horizontal_rule($horizontal_color); ##################################################### # Check if we're changing the permissions of a file # ##################################################### From ea0d21e37bfce6cba5a1574ad874869efdd8b934 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 16 May 2016 09:22:52 -0700 Subject: [PATCH 091/443] Add tests for binary file modifications --- test/diff-so-fancy.bats | 6 ++++++ test/fixtures/binary-modified.diff | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 test/fixtures/binary-modified.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index af50203..04fc774 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -56,3 +56,9 @@ if begin" output=$( load_fixture "leading-dashes" | $diff_so_fancy ) refute_output --partial "modified: Callback" } + +@test "Handle binary modifications" { + output=$( load_fixture "binary-modified" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 1 --partial "modified: cancel.png (binary)"; +} diff --git a/test/fixtures/binary-modified.diff b/test/fixtures/binary-modified.diff new file mode 100644 index 0000000..b5d7887 --- /dev/null +++ b/test/fixtures/binary-modified.diff @@ -0,0 +1,21 @@ +diff --git a/cancel.png b/cancel.png +index 667e10c..06fc49c 100644 +Binary files a/cancel.png and b/cancel.png differ +diff --git a/diff-so-fancy b/diff-so-fancy +index 0f2911c..5811717 100755 +--- a/diff-so-fancy ++++ b/diff-so-fancy +@@ -32,13 +32,6 @@ color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" +  + git_index_line_pattern="^($color_code_regex)index .*" +  +-format_diff_header () { +- # simplify the unified patch diff header +- $SED -E "/$git_index_line_pattern/{N;s/$git_index_line_pattern\n//;}" \ +- | $SED -E "s/^($color_code_regex)\-\-\-(.*)$/\1$(print_horizontal_rule)\\${NL}\1---\5/g" \ +- | $SED -E "s/^($color_code_regex)\+\+\+(.*)$/\1+++\5\\${NL}\1$(print_horizontal_rule)/g" +-} +- + print_horizontal_rule () { + let width="$(tput cols)" +  From 0f712b314618fca0ecc2d8eec037ed426035c878 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 16 May 2016 14:12:53 -0700 Subject: [PATCH 092/443] Remove some old functions and variables from bash as they're not used anymore --- diff-so-fancy | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 5811717..946d56f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -15,9 +15,6 @@ get_script_dir () { echo "$dir" } -# find my sed -hash gsed 2> /dev/null && SED=gsed || SED=sed - # find my diff-highlight if hash diff-highlight 2> /dev/null; then diff_highlight=diff-highlight @@ -25,25 +22,6 @@ else diff_highlight="$(get_script_dir)/third_party/diff-highlight/diff-highlight" fi -CSI=$'\x1b\[' -NL=" -" -color_code_regex="(${CSI}([0-9]{1,3}(;[0-9]{1,3}){0,3})[m|K])?" - -git_index_line_pattern="^($color_code_regex)index .*" - -print_horizontal_rule () { - let width="$(tput cols)" - - if [[ $(uname -s) =~ (MINGW32*|MSYS*) ]]; then - width=$(( width - 1 )) - fi - - # echo -n '─' | hexdump -C - local -r dash=$( printf "%b" "\xe2\x94\x80" ) - printf "%*s\n" "$width" | $SED "s/ /${dash}/g" -} - print_header_clean () { "$(get_script_dir)/lib/diff-so-fancy.pl" } From ed8200e2c754daca25a7c3c8aa9056891787c2eb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 20 May 2016 08:20:34 -0700 Subject: [PATCH 093/443] Add a bunch of comments --- lib/diff-so-fancy.pl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 3f53704..4f3cc3c 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -32,7 +32,7 @@ while (my $line = <>) { ###################################################### - # Pre-process the line before we do any other markup + # Pre-process the line before we do any other markup # ###################################################### # If the first line of the input is a blank line, skip that @@ -41,11 +41,14 @@ } ###################### - # End pre-processing + # End pre-processing # ###################### ####################################################################### + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line $horizontal_color = $1; @@ -91,6 +94,7 @@ print "$file_1 -> $file_2\n"; } + # Print out the bottom horizontal line of the header print horizontal_rule($horizontal_color); ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # @@ -172,6 +176,7 @@ sub parse_hunk_header { return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); } +# Mark the first char of an empty line sub mark_empty_line { my $line = shift(); @@ -184,6 +189,7 @@ sub mark_empty_line { return $line; } +# String to boolean sub boolean { my $str = shift(); $str = trim($str); @@ -340,6 +346,7 @@ sub char_count { return $ret; } +# Remove all ANSI codes from a string sub bleach_text { my $str = shift(); $str =~ s/\e\[\d*(;\d+)*m//mg; @@ -347,6 +354,7 @@ sub bleach_text { return $str; } +# Remove all trailing and leading spaces sub trim { my $s = shift(); if (!$s) { return ""; } @@ -355,7 +363,7 @@ sub trim { return $s; } - +# Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; my $width = `tput cols`; From 328839ae136a1f53d5f7a1ef1de78b84a62a75bd Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 24 May 2016 22:50:22 +1000 Subject: [PATCH 094/443] 0.9.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 158c8e1..96333dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.9.1", + "version": "0.9.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From d50eded750804839248c56dd5abefa7b039ea48f Mon Sep 17 00:00:00 2001 From: William Boman Date: Tue, 24 May 2016 22:52:25 +0200 Subject: [PATCH 095/443] fix unicode output --- lib/diff-so-fancy.pl | 1 + test/diff-so-fancy.bats | 6 ++++++ test/fixtures/unicode.diff | 7 +++++++ 3 files changed, 14 insertions(+) create mode 100644 test/fixtures/unicode.diff diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 4f3cc3c..657e055 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -5,6 +5,7 @@ use File::Basename; use utf8; +use open qw(:std :utf8); # http://stackoverflow.com/a/519359 binmode STDOUT,':utf8'; my $remove_file_add_header = 1; diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 04fc774..b2133c2 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -62,3 +62,9 @@ if begin" run printf "%s" "$output" assert_line --index 1 --partial "modified: cancel.png (binary)"; } + +@test "Handle unicode characters in diff output" { + output=$( load_fixture "unicode" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 5 --partial "åäöç" +} diff --git a/test/fixtures/unicode.diff b/test/fixtures/unicode.diff new file mode 100644 index 0000000..2d5a510 --- /dev/null +++ b/test/fixtures/unicode.diff @@ -0,0 +1,7 @@ +diff --git a/unicodes b/unicodes +index 223f57d..1c2066d 100644 +--- a/unicodes ++++ b/unicodes +@@ -1 +1 @@ +-aao ++åäöç From 23f265c640228a54baab7c3b3b3e86ca7ffb6386 Mon Sep 17 00:00:00 2001 From: William Boman Date: Tue, 17 May 2016 02:54:35 +0200 Subject: [PATCH 096/443] don't print characters that we know will be incompatible with less(1) --- lib/diff-so-fancy.pl | 23 ++++++++++++++++++++++- test/diff-so-fancy.bats | 13 +++++++++++++ test/test_helper/util.bash | 4 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 657e055..aed64c5 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -275,6 +275,22 @@ sub in_unit_test { } } +sub get_less_charset { + my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); + foreach (@less_char_vars) { + return $ENV{$_} if defined $ENV{$_}; + } +} + +sub should_print_unicode { + if (-t STDOUT) { + # Always print unicode chars if we're not piping stuff, e.g. to less(1) + return 1; + } + # Otherwise, assume we're piping to less(1) + return get_less_charset() =~ /utf-?8/i; +} + # Return git config as a hash sub get_git_config_hash { my $out = git_config_raw(); @@ -377,7 +393,12 @@ sub horizontal_rule { # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm #my $dash = "\x{2014}"; # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm - my $dash = "\x{2500}"; + my $dash; + if (should_print_unicode()) { + $dash = "\x{2500}"; + } else { + $dash = "-"; + } # Draw the line my $ret = $color . ($dash x $width) . "\n"; diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index b2133c2..62ac302 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -4,6 +4,7 @@ load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' +set_env # bats fails to handle our multiline result, so we save to $output ourselves output=$( load_fixture "ls-function" | $diff_so_fancy ) @@ -52,6 +53,18 @@ if begin" assert_line --index 2 --partial "─────" } +# see https://git.io/vrOF4 +@test "Should not show unicode bytes in hex if missing LC_*/LANG _and_ piping the output" { + unset LESSCHARSET LESSCHARDEF LC_ALL LC_CTYPE LANG + # pipe to cat(1) so we don't open stdout + header=$( printf "%s" "$(load_fixture "ls-function" | $diff_so_fancy | cat)" | head -n3 ) + run printf "%s" "$header" + assert_line --index 0 --partial "-----" + assert_line --index 1 --partial "modified: fish/functions/ls.fish" + assert_line --index 2 --partial "-----" + set_env # reset env +} + @test "Leading dashes are not handled as modified" { output=$( load_fixture "leading-dashes" | $diff_so_fancy ) refute_output --partial "modified: Callback" diff --git a/test/test_helper/util.bash b/test/test_helper/util.bash index e37afec..26a1a03 100644 --- a/test/test_helper/util.bash +++ b/test/test_helper/util.bash @@ -6,6 +6,10 @@ load_fixture() { cat "$BATS_TEST_DIRNAME/fixtures/${name}.diff" } +set_env() { + export LC_CTYPE="en_US.UTF-8" +} + # applying colors so ANSI color values will match # FIXME: not everyone will have these set, so we need to test for that. From 7ad89c613b494a9d08844e8482d11d78a0603182 Mon Sep 17 00:00:00 2001 From: William Boman Date: Wed, 25 May 2016 02:09:53 +0200 Subject: [PATCH 097/443] test/diff-highlight: avoid word splitting --- test/diff-highlight.bats | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/diff-highlight.bats b/test/diff-highlight.bats index 487dde7..8253141 100644 --- a/test/diff-highlight.bats +++ b/test/diff-highlight.bats @@ -9,9 +9,9 @@ load 'test_helper/util' output=$( load_fixture "ls-function" | $diff_so_fancy ) -setup(){ +setup() { users_path=$PATH - mv $BATS_TEST_DIRNAME/../third_party/diff-highlight/diff-highlight $BATS_TEST_DIRNAME + mv "$BATS_TEST_DIRNAME/../third_party/diff-highlight/diff-highlight" "$BATS_TEST_DIRNAME" export PATH=$BATS_TEST_DIRNAME:$PATH } @@ -20,7 +20,7 @@ setup(){ assert_output --partial 'eval $ls $param "$argv"' } -teardown(){ +teardown() { export PATH=$users_path - mv $BATS_TEST_DIRNAME/diff-highlight $BATS_TEST_DIRNAME/../third_party/diff-highlight + mv "$BATS_TEST_DIRNAME/diff-highlight" "$BATS_TEST_DIRNAME/../third_party/diff-highlight" } From 9f1b95159543217b93ba9cc0462861b15e71aea3 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 25 May 2016 21:43:00 +1000 Subject: [PATCH 098/443] 0.9.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96333dd..1b28628 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.9.2", + "version": "0.9.3", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From f037d3c3fc878811d3cfa0dd0861cc6ab0b298da Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Wed, 25 May 2016 13:30:17 -0700 Subject: [PATCH 099/443] Fix up 'dsf' git alias example in readme - Fix quoting around GIT_PREFIX - Use && to fail early if the cd does - Use `git config --global alias.dsf`, which git recommends over hand-editing .gitconfig (and makes it easy to adapt the command to per-repository configs by dropping --global) --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e190a0f..7836a39 100644 --- a/readme.md +++ b/readme.md @@ -24,9 +24,9 @@ git diff --color | diff-so-fancy git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` -However, if you'd prefer to do the fanciness on-demand with `git dsf`, drop an alias in your `~/.gitconfig`: +However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your git config: ```shell -dsf = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd "$GIT_PREFIX"; git diff --color $@ | diff-so-fancy | less --tabs=4 -RFX; }; f" +git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" && git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' ``` ## Install From 5e97da744ee1cb70eaf94f44217badaba45db188 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 14 Jun 2016 08:23:40 -0700 Subject: [PATCH 100/443] Add a test to see if we properly handle latin1 text --- test/diff-so-fancy.bats | 6 ++++++ test/fixtures/latin1.diff | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 test/fixtures/latin1.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 62ac302..7c1b675 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -81,3 +81,9 @@ if begin" run printf "%s" "$output" assert_line --index 5 --partial "åäöç" } + +@test "Handle latin1 encoding sanely" { + output=$( load_fixture "latin1" | $diff_so_fancy ) + # Make sure the output contains SOME of the english text (i.e. it doesn't barf on the whole line) + assert_output --partial "saw he conqu" +} diff --git a/test/fixtures/latin1.diff b/test/fixtures/latin1.diff new file mode 100644 index 0000000..689a0fb --- /dev/null +++ b/test/fixtures/latin1.diff @@ -0,0 +1,8 @@ +diff --git a/test.txt b/test.txt +index 5836369..51ccf71 100644 +--- a/test.txt ++++ b/test.txt +@@ -1,6 +1 @@ +-é +- ++H� cam� h� saw he conqu�r�d From e74fdbbb5ce59a809af16d2c3d608f1334240b29 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 14 Jun 2016 08:28:06 -0700 Subject: [PATCH 101/443] Properly handle latin1 encoded diffs aka: fail gracefully --- lib/diff-so-fancy.pl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index aed64c5..4be4907 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -4,8 +4,11 @@ use warnings FATAL => 'all'; use File::Basename; -use utf8; +# Set the input (STDIN) as UTF8, but don't give warnings about unknown chars use open qw(:std :utf8); # http://stackoverflow.com/a/519359 +no warnings 'utf8'; + +# Set the output to always be UTF8 binmode STDOUT,':utf8'; my $remove_file_add_header = 1; From 04c071550f3777ee7996b15f6a01013d4cab8235 Mon Sep 17 00:00:00 2001 From: Tuomas Siipola Date: Tue, 14 Jun 2016 19:57:45 +0300 Subject: [PATCH 102/443] Handle all binary file modifications similiar to text files Format correctly modify, add, delete and rename operation titles on binary files. --- lib/diff-so-fancy.pl | 49 +++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index aed64c5..640eec8 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -78,22 +78,7 @@ $last_file_seen = $file_2; } - # If they're the same it's a modify - if ($file_1 eq $file_2) { - print "modified: $file_1\n"; - # If the first is /dev/null it's a new file - } elsif ($file_1 eq "/dev/null") { - print "added: $file_2\n"; - # If the second is /dev/null it's a deletion - } elsif ($file_2 eq "/dev/null") { - print "deleted: $file_1\n"; - # If the files aren't the same it's a rename - } elsif ($file_1 ne $file_2) { - print "renamed: $file_1 to $file_2\n"; - # Something we haven't thought of yet - } else { - print "$file_1 -> $file_2\n"; - } + print "${\file_change_string($file_1,$file_2)}\n"; # Print out the bottom horizontal line of the header print horizontal_rule($horizontal_color); @@ -126,11 +111,11 @@ ###################################### } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { # Don't print the line (i.e. remove it from the output); - ###################################### - # Look for binary file changes - ###################################### - } elsif ($line =~ /Binary files \w\/(.+?) and \w\/(.+?) differ/) { - print "${horizontal_color}modified: $2 (binary)\n"; + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + print "${horizontal_color}${\file_change_string($2,$4)} (binary)\n"; print horizontal_rule($horizontal_color); ##################################################### # Check if we're changing the permissions of a file # @@ -405,3 +390,25 @@ sub horizontal_rule { return $ret; } + +sub file_change_string { + my $file_1 = shift(); + my $file_2 = shift(); + + # If they're the same it's a modify + if ($file_1 eq $file_2) { + return "modified: $file_1"; + # If the first is /dev/null it's a new file + } elsif ($file_1 eq "/dev/null") { + return "added: $file_2"; + # If the second is /dev/null it's a deletion + } elsif ($file_2 eq "/dev/null") { + return "deleted: $file_1"; + # If the files aren't the same it's a rename + } elsif ($file_1 ne $file_2) { + return "renamed: $file_1 to $file_2"; + # Something we haven't thought of yet + } else { + return "$file_1 -> $file_2"; + } +} From 7298fddf7ac2da16b2d5a2c7e0cdeac423d3c83a Mon Sep 17 00:00:00 2001 From: Tuomas Siipola Date: Tue, 14 Jun 2016 20:34:10 +0300 Subject: [PATCH 103/443] Fix code style --- lib/diff-so-fancy.pl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 640eec8..f3837ef 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -78,7 +78,7 @@ $last_file_seen = $file_2; } - print "${\file_change_string($file_1,$file_2)}\n"; + print file_change_string($file_1,$file_2) . "\n"; # Print out the bottom horizontal line of the header print horizontal_rule($horizontal_color); @@ -115,7 +115,8 @@ # Look for binary file changes # ################################ } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - print "${horizontal_color}${\file_change_string($2,$4)} (binary)\n"; + my $change = file_change_string($2,$4); + print "$horizontal_color$change (binary)\n"; print horizontal_rule($horizontal_color); ##################################################### # Check if we're changing the permissions of a file # From 6734bcc94efaad6b2ab5aaf33ecead633e42609c Mon Sep 17 00:00:00 2001 From: Max Nordlund Date: Tue, 14 Jun 2016 22:13:11 +0200 Subject: [PATCH 104/443] Add perlcritic to Travis To make it work it needs to use `language: perl` and override the default install command, and presto perlcritic. --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b43a5fb..d196ea0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -language: bash +language: perl before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then @@ -10,9 +10,12 @@ before_install: sudo apt-get -y update; sudo apt-get -y install shellcheck; fi +install: + - "cpanm --quiet --notest Test::Perl::Critic" script: - ./bats/bin/bats test/*.bats - shellcheck diff-so-fancy update-deps.sh + - perlcritic -1 -q lib/diff-so-fancy.pl os: - linux - osx From f5c8abc7022c98dd2981f3abf8cc2c9e0d1fad3e Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 15 Jun 2016 01:28:10 +0400 Subject: [PATCH 105/443] Do not use hardcoded "/usr/bin/perl" path --- lib/diff-so-fancy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index f3837ef..7f11ca1 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl use strict; use warnings FATAL => 'all'; From 5d487f41cd79f7b3907a90cd7d3c826732c95777 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 15 Jun 2016 08:12:12 -0700 Subject: [PATCH 106/443] Fix a Perl Critic error I/O layer ":utf8" used at line 12, column 1. Use ":encoding(UTF-8)" to get strict validation. --- lib/diff-so-fancy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index a5a3b84..40c79bf 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -9,7 +9,7 @@ no warnings 'utf8'; # Set the output to always be UTF8 -binmode STDOUT,':utf8'; +binmode STDOUT,':encoding(UTF-8)'; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; From 954bc7f9389d1be770bc59748926e4b0470e3144 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 15 Jun 2016 08:12:36 -0700 Subject: [PATCH 107/443] Fix a Perl Critic error and make some code more readable Subroutine "get_less_charset" does not end with "return" at line 267, column 1. See page 197 of PBP. --- lib/diff-so-fancy.pl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 40c79bf..2c944ae 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -269,6 +269,8 @@ sub get_less_charset { foreach (@less_char_vars) { return $ENV{$_} if defined $ENV{$_}; } + + return ""; } sub should_print_unicode { @@ -276,8 +278,14 @@ sub should_print_unicode { # Always print unicode chars if we're not piping stuff, e.g. to less(1) return 1; } + # Otherwise, assume we're piping to less(1) - return get_less_charset() =~ /utf-?8/i; + my $less_charset = get_less_charset(); + if ($less_charset =~ /utf-?8/i) { + return 1; + } + + return 0; } # Return git config as a hash From c5384d72abf07a241d1383d6859c97dc674e45ea Mon Sep 17 00:00:00 2001 From: Max Nordlund gmail Date: Wed, 15 Jun 2016 19:39:27 +0200 Subject: [PATCH 108/443] Use Perl::Critic::Freenode --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d196ea0..bed5120 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,16 @@ before_install: sudo apt-get -y install shellcheck; fi install: - - "cpanm --quiet --notest Test::Perl::Critic" + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib); + cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode + fi script: - ./bats/bin/bats test/*.bats - shellcheck diff-so-fancy update-deps.sh - - perlcritic -1 -q lib/diff-so-fancy.pl + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + perlcritic -1 -q --theme freenode lib/diff-so-fancy.pl; + fi os: - linux - osx From e9650e345a7b19bc7a63175470a5a520de950c7b Mon Sep 17 00:00:00 2001 From: Max Nordlund gmail Date: Wed, 15 Jun 2016 21:14:11 +0200 Subject: [PATCH 109/443] Use newer OSX This make sure to use El Capitan and to make perlcritic work on OSX some tricks are involed. First we need to install both a newer version of perl and install cpanm ourselves. Then we need to manually create the ~/perl5 directory or the `cpanm --local-lib` part will fail. Finally in order to _not_ run Linux twice we need to avoid using `perl: 5.18` toplevel and instead use a tweaked build matrix. Now we're all good. --- .travis.yml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index bed5120..182bc10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,19 @@ -language: perl +language: generic + +matrix: + include: + - os: osx + osx_image: xcode7.3 + - os: linux + language: perl + perl: 5.18 + before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; - brew install shellcheck; + brew install shellcheck perl cpanminus; + mkdir -p ~/perl5; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; @@ -11,16 +21,10 @@ before_install: sudo apt-get -y install shellcheck; fi install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib); - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode - fi + - cpanm --quiet --notest --local-lib=~/perl5 local::lib + - eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) + - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode script: - ./bats/bin/bats test/*.bats - shellcheck diff-so-fancy update-deps.sh - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - perlcritic -1 -q --theme freenode lib/diff-so-fancy.pl; - fi -os: - - linux - - osx + - perlcritic -1 -q --theme freenode lib/diff-so-fancy.pl From 545a5ed1303f7b1323e3e7b6c4ff18dea8c93461 Mon Sep 17 00:00:00 2001 From: Max Nordlund gmail Date: Wed, 15 Jun 2016 23:10:14 +0200 Subject: [PATCH 110/443] Try to use toplevel perl again --- .travis.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 182bc10..40c4e3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,15 @@ -language: generic +os: linux +language: perl +perl: + - 5.18 + - 5.14 matrix: include: - os: osx osx_image: xcode7.3 - - os: linux - language: perl - perl: 5.18 + language: generic + perl: 5.24 before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git From 7d10db10ea62cb78a431fb5e2b9a9452ed1f11ad Mon Sep 17 00:00:00 2001 From: Max Nordlund gmail Date: Wed, 15 Jun 2016 23:37:10 +0200 Subject: [PATCH 111/443] Add more versions --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 40c4e3d..ba69d93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ os: linux language: perl perl: + - 5.24 + - 5.22 + - 5.20 - 5.18 - 5.14 From 9d8b7bc02b85e44db74d90665fab9b5d4e7eedb8 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Fri, 17 Jun 2016 09:45:46 +0200 Subject: [PATCH 112/443] ~/.gitconfig reference. fixes #163 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7836a39..548fa5d 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ git diff --color | diff-so-fancy git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` -However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your git config: +However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your `~/.gitconfig` ```shell git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" && git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' ``` From 735c9d6dc04e8641b8e3c5ae64cb1251fcc508ea Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 21 Jun 2016 21:11:34 +1000 Subject: [PATCH 113/443] 0.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b28628..0b50234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.9.3", + "version": "0.10.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 280b97f31463444d409b05e3d63e3774c35aa0e6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 21 Jun 2016 07:55:34 -0700 Subject: [PATCH 114/443] Fix for #175, Perl's prior to 5.10 did not know the // operator --- lib/diff-so-fancy.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 2c944ae..4aa0e8f 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -241,8 +241,8 @@ sub git_config { # Fetch a boolean item from the git config sub git_config_boolean { - my $search_key = lc($_[0] // ""); - my $default_value = lc($_[1] // 0); # Default to false + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || 0); # Default to false # If we're in a unit test, use the default (don't read the users config) if (in_unit_test()) { From f9a35fa4cd5c76131288b7d2aeada4b8c361c8fc Mon Sep 17 00:00:00 2001 From: delphinus Date: Thu, 23 Jun 2016 14:53:35 +0900 Subject: [PATCH 115/443] Fix for old Perl. additional to 280b97f --- lib/diff-so-fancy.pl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 4aa0e8f..b760156 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -212,8 +212,8 @@ sub boolean { # Fetch a textual item from the git config sub git_config { - my $search_key = lc($_[0] // ""); - my $default_value = lc($_[1] // ""); + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || ""); my $out = git_config_raw(); @@ -233,7 +233,7 @@ sub git_config { # If we're given a search key return that, else return the hash if ($search_key) { - return $raw->{$search_key} // $default_value; + return $raw->{$search_key} || $default_value; } else { return $raw; } @@ -300,7 +300,7 @@ sub get_git_config_hash { my $last = pop @path; my $p = \%hash; - $p = $p->{$_} //= {} for @path; + $p = $p->{$_} ||= {} for @path; $p->{$last} = $value; } From b1bfb11dcbada3bdd788a69e3948819f77a08e3b Mon Sep 17 00:00:00 2001 From: delphinus Date: Thu, 23 Jun 2016 14:54:24 +0900 Subject: [PATCH 116/443] Fix to avoid warnings for old Perl --- lib/diff-so-fancy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index b760156..5207bbe 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -160,7 +160,7 @@ # Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 sub parse_hunk_header { my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^@@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? @@+/; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; $o_cnt = 1 unless defined $o_cnt; $n_cnt = 1 unless defined $n_cnt; return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); From 81540e8c15df87acc3d6e2b1283cacd964bb4d23 Mon Sep 17 00:00:00 2001 From: Woodrow Barlow Date: Wed, 6 Jul 2016 10:33:36 -0400 Subject: [PATCH 117/443] Update recommended pre-seeded search pattern The old recommended pre-seeded search pattern for less would cause "git show" to jump past the commit message, going straight to the file. This change would prevent that (mis)behavior, without hindering the pattern's usefulness. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 548fa5d..e4cd2f0 100644 --- a/readme.md +++ b/readme.md @@ -111,7 +111,7 @@ git --no-pager diff #### Moving around in the diff -You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`p` keys. Add `--pattern='^(added|deleted|modified): '` to the end of the less flags for your git pager config. +You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`p` keys. Add `--pattern='^(Date|added|deleted|modified): '` to the end of the less flags for your git pager config. ## History From ea9be2fd32eca1fdfe9e3a127015a5b5a349d59c Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 14 Jul 2016 06:21:31 +1000 Subject: [PATCH 118/443] clarify how to add an alias in readme (#179) fixes #178 --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 548fa5d..08d44f9 100644 --- a/readme.md +++ b/readme.md @@ -24,9 +24,10 @@ git diff --color | diff-so-fancy git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` -However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your `~/.gitconfig` +However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your `~/.gitconfig` by running: ```shell -git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" && git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' +git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ +'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' ``` ## Install From 2fa9897e5de76cd4dc739e57a063768ff36121fd Mon Sep 17 00:00:00 2001 From: Frank Schumacher Date: Fri, 15 Jul 2016 09:57:07 +0200 Subject: [PATCH 119/443] Add setting to allow disabling of unicode ruler This commit adds a single setting to allow users to disable the use of the unicode em-dash that is being used as a horizonal ruler character for marking the changed filename. It defaults to the current bahaviour of using em-dash. Setting `diff-so-fancy.useUnicodeRuler` to `false` will use a simple dash (minus sign) instead. --- lib/diff-so-fancy.pl | 15 ++++++++------- readme.md | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/diff-so-fancy.pl b/lib/diff-so-fancy.pl index 5207bbe..b772e81 100755 --- a/lib/diff-so-fancy.pl +++ b/lib/diff-so-fancy.pl @@ -11,12 +11,13 @@ # Set the output to always be UTF8 binmode STDOUT,':encoding(UTF-8)'; -my $remove_file_add_header = 1; -my $remove_file_delete_header = 1; -my $clean_permission_changes = 1; -my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); -my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); -my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); +my $remove_file_add_header = 1; +my $remove_file_delete_header = 1; +my $clean_permission_changes = 1; +my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); +my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); +my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); +my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); ################################################################################# @@ -391,7 +392,7 @@ sub horizontal_rule { #my $dash = "\x{2014}"; # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm my $dash; - if (should_print_unicode()) { + if ($use_unicode_dash_for_ruler && should_print_unicode()) { $dash = "\x{2500}"; } else { $dash = "-"; diff --git a/readme.md b/readme.md index 05cd53a..8dbb9b0 100644 --- a/readme.md +++ b/readme.md @@ -84,12 +84,18 @@ Simplify git header chunks to a more human readable format. Should the pesky `+` or `-` at line-start be removed. +### useUnicodeRuler + +Should the horizonal line above and below the name of the changed file be displayed using unicode. +Try setting this to `false` if those lines are displayed to long to fit your screen. + By default all the configs are true. You can turn any off by running: ``` git config --bool --global diff-so-fancy.markEmptyLines false git config --bool --global diff-so-fancy.changeHunkIndicators false git config --bool --global diff-so-fancy.stripLeadingSymbols false +git config --bool --global diff-so-fancy.useUnicodeRuler false ``` To reset them to default (`true`): @@ -98,6 +104,7 @@ To reset them to default (`true`): git config --unset --global diff-so-fancy.markEmptyLines git config --unset --global diff-so-fancy.changeHunkIndicators git config --unset --global diff-so-fancy.stripLeadingSymbols +git config --unset --global diff-so-fancy.useUnicodeRuler ``` ## Pro-tips From 6c25d3b5e55f50920dc155d234dc8721875bcd0d Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 16 Jul 2016 11:30:46 +1000 Subject: [PATCH 120/443] 0.10.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b50234..f1260df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.10.0", + "version": "0.10.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 0ca8cfb1631ec8d28d4362f6bacad83120a9a3d7 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 18 Jul 2016 09:46:57 +0200 Subject: [PATCH 121/443] change readme as adviced --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 8dbb9b0..374d72d 100644 --- a/readme.md +++ b/readme.md @@ -86,8 +86,8 @@ Should the pesky `+` or `-` at line-start be removed. ### useUnicodeRuler -Should the horizonal line above and below the name of the changed file be displayed using unicode. -Try setting this to `false` if those lines are displayed to long to fit your screen. +By default the separator for the file header uses unicode line drawing characters. +If this is causing output errors on your terminal set this to `false` to use ASCII characters instead. By default all the configs are true. You can turn any off by running: From 3c8b3df2ea3504fef3c365ae1d5de66b1db49159 Mon Sep 17 00:00:00 2001 From: Ollie Ford Date: Fri, 29 Jul 2016 19:51:45 +0100 Subject: [PATCH 122/443] documents pattern pre-load tip only for diffs Closes #173. The pattern match errors if no match, which almost certainly happen if it's a log being paged; so we only want to do this if it really is a diff. @rocketraman points out that this misses `git show` diffs, but that case cannot be covered without introducing the same error when used for blobs, trees, and tags - which `show` may also show. --- readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 374d72d..864ab90 100644 --- a/readme.md +++ b/readme.md @@ -119,7 +119,12 @@ git --no-pager diff #### Moving around in the diff -You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`p` keys. Add `--pattern='^(Date|added|deleted|modified): '` to the end of the less flags for your git pager config. +You can pre-seed your `less` pager with a search pattern, so you can move +between files with `n`/`p` keys: +```ini +[pager] + diff = diff-so-fancy | less --tabs=4 -RFX --pattern'^(Date|added|deleted|modified): ' +``` ## History From c46450f6a4a06f4d2f2c72fc260467be0ab93655 Mon Sep 17 00:00:00 2001 From: Ollie Ford Date: Fri, 29 Jul 2016 19:59:46 +0100 Subject: [PATCH 123/443] documents DSF-less patch alias suggestion Discussed in #181. --- readme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/readme.md b/readme.md index 864ab90..d00832d 100644 --- a/readme.md +++ b/readme.md @@ -117,6 +117,16 @@ Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: git --no-pager diff ``` +#### Raw patches + +As a shortcut for a 'normal' diff to save as a patch for emailing or later +application, it may be helpful to configure an alias: +```ini +[alias] + patch = --no-pager diff --no-color +``` +which can then be used as `git patch > changes.patch`. + #### Moving around in the diff You can pre-seed your `less` pager with a search pattern, so you can move From 406c71283c5ab7399eb2ed8674a88662f9f36ae6 Mon Sep 17 00:00:00 2001 From: Ollie Ford Date: Tue, 2 Aug 2016 20:15:28 +0100 Subject: [PATCH 124/443] changes `less` options to be mere suggestion Closes #174. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d00832d..b6e9eb9 100644 --- a/readme.md +++ b/readme.md @@ -19,7 +19,7 @@ You can do one-off fanciness: git diff --color | diff-so-fancy ``` -**But**, you'll probably want to fancify all your diffs. Run this so `git diff` will use it: +**But**, you'll probably want to fancify all your diffs. Set your `core.pager` to run `diff-so-fancy`, and pipe the output through your existing pager, or if unset we recommend `less --tabs=4 -RFX`: ```shell git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` From fafa8ea5e90b9698bb1c5f19d6c62760c3ae0428 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 6 Aug 2016 15:16:03 +1000 Subject: [PATCH 125/443] 0.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1260df..93111a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.10.1", + "version": "0.11.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From fb8eba11ace458b7733a8ead1917c644f8cc25f9 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Mon, 8 Aug 2016 21:39:25 +1000 Subject: [PATCH 126/443] change lib/ to libexec/ fixes https://github.com/Homebrew/homebrew-core/pull/3663#issuecomment-238010069 --- .travis.yml | 2 +- diff-so-fancy | 2 +- {lib => libexec}/diff-so-fancy.pl | 0 readme.md | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename {lib => libexec}/diff-so-fancy.pl (100%) diff --git a/.travis.yml b/.travis.yml index ba69d93..d97717e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,4 +33,4 @@ install: script: - ./bats/bin/bats test/*.bats - shellcheck diff-so-fancy update-deps.sh - - perlcritic -1 -q --theme freenode lib/diff-so-fancy.pl + - perlcritic -1 -q --theme freenode libexec/diff-so-fancy.pl diff --git a/diff-so-fancy b/diff-so-fancy index 946d56f..93a5a62 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -23,7 +23,7 @@ else fi print_header_clean () { - "$(get_script_dir)/lib/diff-so-fancy.pl" + "$(get_script_dir)/libexec/diff-so-fancy.pl" } # run it. diff --git a/lib/diff-so-fancy.pl b/libexec/diff-so-fancy.pl similarity index 100% rename from lib/diff-so-fancy.pl rename to libexec/diff-so-fancy.pl diff --git a/readme.md b/readme.md index b6e9eb9..5d9ff58 100644 --- a/readme.md +++ b/readme.md @@ -62,10 +62,10 @@ If you want, you can choose to install manually: 1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. 1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. 1. Place them in a location that is in your `PATH` directly or with symlinks. -1. Place `lib/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: +1. Place `libexec/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: * `~/bin/diff-highlight` * `~/bin/diff-so-fancy` - * `~/bin/lib/diff-so-fancy.pl` + * `~/bin/libexec/diff-so-fancy.pl` 1. Set up the git `core.pager` config, as described above. Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). From 2d7330797a73182545c8ffa7244a8c10c073e56b Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 11 Aug 2016 20:39:18 +1000 Subject: [PATCH 127/443] 0.11.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93111a2..aa701bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.11.0", + "version": "0.11.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From ba919ec021f1f1aa42c48002382823c28f06f1e2 Mon Sep 17 00:00:00 2001 From: Hexchain Tong Date: Fri, 12 Aug 2016 14:42:14 +0800 Subject: [PATCH 128/443] Fix pager pattern argument --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5d9ff58..504acaa 100644 --- a/readme.md +++ b/readme.md @@ -133,7 +133,7 @@ You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`p` keys: ```ini [pager] - diff = diff-so-fancy | less --tabs=4 -RFX --pattern'^(Date|added|deleted|modified): ' + diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' ``` ## History From 224db6aae69f3174bf0d883a748c716e1704442d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 23 Aug 2016 09:55:59 -0700 Subject: [PATCH 129/443] Add a LICENSE file to make the project official --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..242132d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From 5571f0986a065660020642eb06d95f10470861a0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 23 Aug 2016 11:28:56 -0700 Subject: [PATCH 130/443] Clarify the year/owner in the license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 242132d..eb26539 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) +Copyright (c) 2016 So Fancy team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 3dd258043054375b06ca098d00ac962e39a6ee54 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 25 Aug 2016 09:14:24 -0700 Subject: [PATCH 131/443] Update ANSI reveal to be more reflective of the real world --- third_party/ansi-reveal/ansi-reveal.pl | 113 +++++++++++++++++++++---- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/third_party/ansi-reveal/ansi-reveal.pl b/third_party/ansi-reveal/ansi-reveal.pl index 8937739..b4b7fc8 100755 --- a/third_party/ansi-reveal/ansi-reveal.pl +++ b/third_party/ansi-reveal/ansi-reveal.pl @@ -21,29 +21,34 @@ sub output_human { my $reset = "\e[0m"; while (my $l = <>) { - # Basic reset - $l =~ s/(\e\[0?m)/${reset}[RESET]/g; + $l =~ s/(\e\[.*?m)/dump_ansi($1)/eg; - # Bold text - $l =~ s/(\e\[1m)/${reset}[BOLD]/g; + ## Basic reset + #$l =~ s/(\e\[0?m)/${reset}[RESET]/g; - # Inverted text - $l =~ s/(\e\[7m)/${reset}. "[INVRT]$1"/eg; - $l =~ s/(\e\[7;(\d+)m)/${reset}. "[INVRT" . sprintf("%03d",($2-30)) . "]$1"/eg; + ## Bold text + #$l =~ s/(\e\[1m)/${reset}[BOLD]/g; - # Basic 16 color ANSI - $l =~ s/(\e\[(3[0-7])m)/${reset}. "[BASIC" . sprintf("%03d",($2-30)) . "]$1"/eg; - $l =~ s/(\e\[1;(3[0-7])m)/${reset}. "[BRIGH" . sprintf("%03d",($2-30)) . "]$1"/eg; + ## Inverted text + #$l =~ s/(\e\[7m)/${reset}. "[INVRT]$1"/eg; + #$l =~ s/(\e\[7;(\d+)m)/${reset}. "[INVRT" . sprintf("%03d",($2-30)) . "]$1"/eg; - # 256 Color/Background - $l =~ s/(\e\[38;0?5;(\d+)m)/${reset}. "[COLOR" . sprintf("%03d",$2) . "]$1"/eg; - $l =~ s/(\e\[48;0?5;(\d+)m)/${reset}. "[BACKG" . sprintf("%03d",$2) . "]$1"/eg; + ## Basic 16 color ANSI + ##$l =~ s/(\e\[(3[0-7])m)/${reset}. "[BASIC" . sprintf("%03d",($2-30)) . "]$1"/eg; + #$l =~ s/(\e\[(3[0-7])m)/dump_ansi($1)/eg; + ##$l =~ s/(\e\[1;(3[0-7])m)/${reset}. "[BRIGH" . sprintf("%03d",($2-30)) . "]$1"/eg; + #$l =~ s/(\e\[1;(3[0-7])m)/dump_ansi($1)/eg; - $l =~ s/(\e\[1;(3[0-7]);48;5;(\d+)m)/${reset}. "[FGBG" . sprintf("%02d:%02d",($2-30),($3)) . "]$1"/eg; + ## 256 Color/Background + #$l =~ s/(\e\[38;0?5;(\d+)m)/${reset}. "[COLOR" . sprintf("%03d",$2) . "]$1"/eg; + #$l =~ s/(\e\[48;0?5;(\d+)m)/${reset}. "[BACKG" . sprintf("%03d",$2) . "]$1"/eg; - # 24bit Color/Background - $l =~ s/(\e\[38;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGB#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; - $l =~ s/(\e\[48;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGBBG#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; + #$l =~ s/(\e\[1;(3[0-7]);48;5;(\d+)m)/${reset}. "[FGBG" . sprintf("%02d:%02d",($2-30),($3)) . "]$1"/eg; + #$l =~ s/(\e\[1;38;5;(.*?)m)/${reset} . dump_ansi($1) . "$1"/eg; + + ## 24bit Color/Background + #$l =~ s/(\e\[38;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGB#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; + #$l =~ s/(\e\[48;2;(\d+);(\d+);(\d+)m)/${reset} . sprintf("[RGBBG#%02X%02X%02X",$2,$3,$4) . "]$1"/eg; print $l; } @@ -57,3 +62,77 @@ sub output_raw { print; } } + +sub dump_ansi { + my $str = shift(); + if ($str !~ /^\e/) { + return ""; + } + + my $raw = $str; + my $human = $str =~ s/\e/ESC/rg; + + # Remove the ANSI control chars, we just want the payload + $str =~ s/^\e\[//g; + $str =~ s/m$//g; + + # Make the [HUMAN] text reset and white to make it easier to see + my $ret = "\e[0m"; + $ret .= "\e[38;5;15m"; + + my @parts = split(";",$str); + + #k(\@parts); + + my @basic_mapping = qw(BLACK RED GREEN YELLW BLUE MAGNT CYAN WHITE); + + if (!@parts) { + $ret .= "[RESET]"; + } + + for (my $count = 0; $count < @parts; $count++) { + my $p = $parts[$count]; + + #print "[$count = '$p']\n"; + if ($p eq "1") { + $ret .= "[BOLD]"; + } elsif ($p eq "0" || $p eq "") { + $ret .= "[RESET]"; + } elsif ($p eq "38") { + my $next = $parts[$count + 1]; + my $color = $parts[$count + 2]; + + $count += 2; + + $ret .= sprintf("[COLOR%03d]",$color); + } elsif ($p eq "48") { + my $next = $parts[++$count]; + my $color = $parts[++$count]; + + $count += 2; + + $ret .= sprintf("[BACKG%03d]",$color); + } elsif ($p >= 30 and $p <= 37) { + my $color = $p - 30; + $color = $basic_mapping[$color]; + $ret .= "[$color]"; + #$ret .= sprintf("[BASIC%03d]",$color); + } else { + $ret .= "[UKN: $p]"; + } + } + + # Append the ANSI color string to end of the human readable one + $ret .= $raw; + + return $ret; +} + +BEGIN { + if (eval { require Data::Dump::Color }) { + *k = sub { Data::Dump::Color::dd($_[0]) }; + } else { + require Data::Dumper; + *k = sub { print Data::Dumper::Dumper($_[0]) }; + } +} From 9e99f91f9bfb02dddce6437d5143df135d2742fb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 14 Sep 2016 16:11:09 -0700 Subject: [PATCH 132/443] Update diff-highlight from upstream Git to address #196 --- third_party/diff-highlight/diff-highlight | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/third_party/diff-highlight/diff-highlight b/third_party/diff-highlight/diff-highlight index ffefc31..81bd804 100755 --- a/third_party/diff-highlight/diff-highlight +++ b/third_party/diff-highlight/diff-highlight @@ -21,6 +21,10 @@ my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; my $BORING = qr/$COLOR|\s/; +# The patch portion of git log -p --graph should only ever have preceding | and +# not / or \ as merge history only shows up on the commit line. +my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; + my @removed; my @added; my $in_hunk; @@ -32,12 +36,12 @@ $SIG{PIPE} = 'DEFAULT'; while (<>) { if (!$in_hunk) { print; - $in_hunk = /^$COLOR*\@/; + $in_hunk = /^$GRAPH*$COLOR*\@\@ /; } - elsif (/^$COLOR*-/) { + elsif (/^$GRAPH*$COLOR*-/) { push @removed, $_; } - elsif (/^$COLOR*\+/) { + elsif (/^$GRAPH*$COLOR*\+/) { push @added, $_; } else { @@ -46,7 +50,7 @@ while (<>) { @added = (); print; - $in_hunk = /^$COLOR*[\@ ]/; + $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; } # Most of the time there is enough output to keep things streaming, @@ -163,6 +167,9 @@ sub highlight_pair { } } +# we split either by $COLOR or by character. This has the side effect of +# leaving in graph cruft. It works because the graph cruft does not contain "-" +# or "+" sub split_line { local $_ = shift; return utf8::decode($_) ? @@ -211,8 +218,8 @@ sub is_pair_interesting { my $suffix_a = join('', @$a[($sa+1)..$#$a]); my $suffix_b = join('', @$b[($sb+1)..$#$b]); - return $prefix_a !~ /^$COLOR*-$BORING*$/ || - $prefix_b !~ /^$COLOR*\+$BORING*$/ || + return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || + $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || $suffix_a !~ /^$BORING*$/ || $suffix_b !~ /^$BORING*$/; } From afa3bf1ce802d9232184ab47e2d1a4a12c8c15df Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 20 Sep 2016 21:41:51 +1000 Subject: [PATCH 133/443] 0.11.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa701bd..d6b4ed8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.11.1", + "version": "0.11.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From fd82538f4cde279ee16ee895ba6929e1abfc177a Mon Sep 17 00:00:00 2001 From: Chris Stryczynski Date: Sat, 19 Nov 2016 17:15:10 +0000 Subject: [PATCH 134/443] Addition of Arch linux package availability --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 504acaa..7a5af96 100644 --- a/readme.md +++ b/readme.md @@ -44,6 +44,11 @@ brew update brew install diff-so-fancy ``` +A package is available in Arch linux: +``` +pacman -S diff-so-fancy +``` + ### Improved colors for the highlighted bits `diff-highlight` has default colors that are arguably a little nasty. They'll work fine, but you can try some fancier colors: From 4a1b3f3ac0ce33b58a2143034a8324effa474a1f Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sun, 20 Nov 2016 20:22:56 +0100 Subject: [PATCH 135/443] moving around in the diff is done using n/N the `p` moves to the top instead --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 504acaa..599fe65 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ which can then be used as `git patch > changes.patch`. #### Moving around in the diff You can pre-seed your `less` pager with a search pattern, so you can move -between files with `n`/`p` keys: +between files with `n`/`N` keys: ```ini [pager] diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' From 6a35a4a8450cb4e014d8c1a60560ef9dc804ca91 Mon Sep 17 00:00:00 2001 From: Piper Chester Date: Sun, 8 Jan 2017 09:30:56 -0800 Subject: [PATCH 136/443] Fix capitalization typo in readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2e46a5a..bdae1bd 100644 --- a/readme.md +++ b/readme.md @@ -91,7 +91,7 @@ Should the pesky `+` or `-` at line-start be removed. ### useUnicodeRuler -By default the separator for the file header uses unicode line drawing characters. +By default the separator for the file header uses Unicode line drawing characters. If this is causing output errors on your terminal set this to `false` to use ASCII characters instead. By default all the configs are true. You can turn any off by running: From 6701b79cf0e07cb3c29fe27541097e0ee38259f3 Mon Sep 17 00:00:00 2001 From: Kmaschta Date: Fri, 13 Jan 2017 14:23:54 +0100 Subject: [PATCH 137/443] Fix README patch alias When I set `patch = --no-pager diff --no-color` in my gitconfig and type `git patch`, I have this error: ``` fatal: alias 'patch' changes environment variables You can use '!git' in the alias to do this. ``` Then, here is a fix. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index bdae1bd..7ccd13f 100644 --- a/readme.md +++ b/readme.md @@ -128,7 +128,7 @@ As a shortcut for a 'normal' diff to save as a patch for emailing or later application, it may be helpful to configure an alias: ```ini [alias] - patch = --no-pager diff --no-color + patch = !git --no-pager diff --no-color ``` which can then be used as `git patch > changes.patch`. From 8b73bfe9fe355e75f17ba45e13e8d5de96d61b30 Mon Sep 17 00:00:00 2001 From: Yota Toyama Date: Fri, 20 Jan 2017 12:40:14 +0900 Subject: [PATCH 138/443] Use /usr/bin/env for perl --- third_party/ansi-reveal/ansi-reveal.pl | 2 +- third_party/diff-highlight/diff-highlight | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/ansi-reveal/ansi-reveal.pl b/third_party/ansi-reveal/ansi-reveal.pl index b4b7fc8..692591a 100755 --- a/third_party/ansi-reveal/ansi-reveal.pl +++ b/third_party/ansi-reveal/ansi-reveal.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl use strict; use warnings; diff --git a/third_party/diff-highlight/diff-highlight b/third_party/diff-highlight/diff-highlight index 81bd804..ecf4195 100755 --- a/third_party/diff-highlight/diff-highlight +++ b/third_party/diff-highlight/diff-highlight @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl use 5.008; use warnings FATAL => 'all'; From c4a6b6b6afa4735fa9b8b47c64a5d687197f2019 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sun, 26 Feb 2017 18:22:22 +1100 Subject: [PATCH 139/443] 0.11.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6b4ed8..a2d8aac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.11.2", + "version": "0.11.3", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 14c08a54aa64a202bc9cedcd737e12c4d7974538 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 27 Feb 2017 13:59:38 -0800 Subject: [PATCH 140/443] Properly count the starting line on delete diffs (i.e. start 1) --- libexec/diff-so-fancy.pl | 4 ++++ test/header_clean.bats | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index b772e81..218454b 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -313,6 +313,10 @@ sub start_line_calc { my ($line_num,$diff_context) = @_; my $ret; + if ($line_num == 0 && $diff_context == 0) { + return 1; + } + # Git defaults to three lines of context my $default_context_lines = 3; # Three lines on either side, and the line itself = 7 diff --git a/test/header_clean.bats b/test/header_clean.bats index 9e23681..3f5a5e3 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -42,7 +42,7 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "Reworked hunks" { output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ square.yml:4 @' - assert_output --partial '@ package.json:3 @' + assert_output --partial '@ package.json:1 @' } @test "Reworked hunks (noprefix)" { @@ -53,7 +53,7 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) @test "Reworked hunks (deleted files)" { output=$( load_fixture "dotfiles" | $diff_so_fancy ) - assert_output --partial '@ diff-so-fancy:3 @' + assert_output --partial '@ diff-so-fancy:1 @' } @test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { From f131c709822fcd30837215423bad7fdb8b090281 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 27 Feb 2017 14:00:26 -0800 Subject: [PATCH 141/443] Fix Hunk formatting like '@@ -1 0,0 @@' (fixes #201) --- libexec/diff-so-fancy.pl | 12 ++++++++---- test/fixtures/single-line-remove.diff | 7 +++++++ test/header_clean.bats | 7 +++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/single-line-remove.diff diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index 218454b..69abee3 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -90,10 +90,14 @@ # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - my $hunk_header = $4; - my $remain = bleach_text($5); - $columns_to_remove = (char_count(",",$hunk_header)) - 1; + $in_hunk = 1; + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + $columns_to_remove ||= 1; if ($1) { print $1; # Print out whatever color we're using diff --git a/test/fixtures/single-line-remove.diff b/test/fixtures/single-line-remove.diff new file mode 100644 index 0000000..074da8e --- /dev/null +++ b/test/fixtures/single-line-remove.diff @@ -0,0 +1,7 @@ +diff --git test/data/readywaitasset.js test/data/readywaitasset.js +deleted file mode 100644 +index 2308965..0000000 +--- test/data/readywaitasset.js ++++ /dev/null +@@ -1 +0,0 @@ +-var delayedMessage = "It worked"; diff --git a/test/header_clean.bats b/test/header_clean.bats index 3f5a5e3..37c6833 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -69,6 +69,13 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) assert_output --partial '@ package.json:3 @' } +@test "Hunk formatting: @@ -1 0,0 @@" { + # stderr forced into output + output=$( load_fixture "single-line-remove" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 4 'var delayedMessage = "It worked";' +} + @test "Three way merge" { # stderr forced into output output=$( load_fixture "complex-hunks" | $diff_so_fancy ) From 47c23af9d1814bf3941a1630c13bce1e3ec36dd7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 27 Feb 2017 16:34:02 -0800 Subject: [PATCH 142/443] Make sure "false" returns 0 in unit tests --- libexec/diff-so-fancy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index b772e81..5e5536b 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -247,7 +247,7 @@ sub git_config_boolean { # If we're in a unit test, use the default (don't read the users config) if (in_unit_test()) { - return $default_value; + return boolean($default_value); } my $result = git_config($search_key,$default_value); From e9b95fa8e55248b50de80afe086a3be77197b822 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 27 Feb 2017 16:34:18 -0800 Subject: [PATCH 143/443] Respect if a user had diff.noprefix set in their config (fixes #205) --- libexec/diff-so-fancy.pl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index 5e5536b..f6d6d81 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -18,6 +18,7 @@ my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $diff_no_prefix = git_config_boolean("diff.noprefix","false"); ################################################################################# @@ -69,7 +70,12 @@ # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - $file_1 = $5; + if ($diff_no_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } # Find the second file on the next line: +++ b/README.md my $next = <>; @@ -77,7 +83,13 @@ if ($1) { print $1; # Print out whatever color we're using } - $file_2 = $5; + if ($diff_no_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } + if ($file_2 ne "/dev/null") { $last_file_seen = $file_2; } From 5c710b3bacb5882383676d11985dc7477a06814c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 1 Mar 2017 18:43:52 -0800 Subject: [PATCH 144/443] Change a variable so it's more english readable --- libexec/diff-so-fancy.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index f6d6d81..16c0f72 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -18,7 +18,7 @@ my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); -my $diff_no_prefix = git_config_boolean("diff.noprefix","false"); +my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); ################################################################################# @@ -70,7 +70,7 @@ # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - if ($diff_no_prefix) { + if ($git_strip_prefix) { my $file_dir = $4 || ""; $file_1 = $file_dir . $5; } else { @@ -83,7 +83,7 @@ if ($1) { print $1; # Print out whatever color we're using } - if ($diff_no_prefix) { + if ($git_strip_prefix) { my $file_dir = $4 || ""; $file_2 = $file_dir . $5; } else { From adb7b9d932416e2d38c6f4b479de21835255cc46 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 4 Mar 2017 10:07:45 +1100 Subject: [PATCH 145/443] 0.11.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2d8aac..ef2633a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.11.3", + "version": "0.11.4", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 6fd038baedb9b10ab50c29535db661d3b636103d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 6 Mar 2017 10:25:22 -0800 Subject: [PATCH 146/443] Initial checkin for --version and CLI usage --- libexec/diff-so-fancy.pl | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index 2c20642..a5e3cfe 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -19,6 +19,21 @@ my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); +my $has_stdin = has_stdin(); + +# We only process ARGV if we don't have STDIN +if (!$has_stdin) { + my $args = argv(); + + if ($args->{v} || $args->{version}) { + die(version()); + } + + if (!%$args) { + print usage(); exit; + die(usage()); + } +} ################################################################################# @@ -445,3 +460,60 @@ sub file_change_string { return "$file_1 -> $file_2"; } } + +# Check to see if STDIN is connected to an interactive terminal +sub has_stdin { + my $i = -t STDIN; + my $ret = int(!$i); + + return $ret; +} + +# We use this instead of Getopt::Long because it's faster and we're not parsing any +# crazy arguments +# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html +sub argv { + my $ret = {}; + + for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + # If the next item does not start with "--" it's the value for this item + if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { + $ret->{$key} = $ARGV[$i + 1]; + # Bareword like --verbose with no options + } else { + $ret->{$key}++; + } + } + } + + # We're looking for a certain item + if ($_[0]) { return $ret->{$_[0]}; } + + return $ret; +} + +# Output the command line usage for d-s-f +sub usage { + my $out = "Usage: + + # One off fanciness + git diff --color | diff-so-fancy + + # Configure git to use d-s-f for all diff operations + git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" + + # Create a git alias to run d-s-f on demand + git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n\n"; + + return $out; +} + +# Output the current version string +sub version { + my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; + $ret .= "Version : 0.12.0\n\n"; + + return $ret; +} From 8c2fb5390e34e8a3e2763d1f4ff8c54da1d750d3 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 6 Mar 2017 10:44:19 -0800 Subject: [PATCH 147/443] Store the version in a $VERSION variable instead --- libexec/diff-so-fancy.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl index a5e3cfe..b3b540d 100755 --- a/libexec/diff-so-fancy.pl +++ b/libexec/diff-so-fancy.pl @@ -1,5 +1,9 @@ #!/usr/bin/env perl +my $VERSION = "0.12.0"; + +################################################################################# + use strict; use warnings FATAL => 'all'; use File::Basename; @@ -513,7 +517,7 @@ sub usage { # Output the current version string sub version { my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; - $ret .= "Version : 0.12.0\n\n"; + $ret .= "Version : $VERSION\n\n"; return $ret; } From efb307ae27db6bed378f9ba96ecaa5805f79365e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 6 Mar 2017 15:46:01 -0800 Subject: [PATCH 148/443] Clean up the bash -> perl calling script This diff looks a lot worse than it is because I did a bunch of whitespace fixing --- diff-so-fancy | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 93a5a62..a5fa8b1 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,33 +1,38 @@ #!/usr/bin/env bash -[[ $# -ge 1 ]] && [[ -f "$1" ]] && input="$1" || input="-" - get_script_dir () { - src="https://github.com/$%7BBASH_SOURCE[0]%7D" + src="https://github.com/$%7BBASH_SOURCE[0]%7D" - while [[ -h "$src" ]]; do - dir="$(cd -P "$( dirname "$src" )" && pwd)" - src="https://github.com/$(readlink"$src")" - [[ $src != /* ]] && src="https://github.com/$dir/$src" - done + while [[ -h "$src" ]]; do + dir="$(cd -P "$( dirname "$src" )" && pwd)" + src="https://github.com/$(readlink"$src")" + [[ $src != /* ]] && src="https://github.com/$dir/$src" + done - dir="$(cd -P "$( dirname "$src" )" && pwd)" - echo "$dir" + dir="$(cd -P "$( dirname "$src" )" && pwd)" + echo "$dir" } -# find my diff-highlight -if hash diff-highlight 2> /dev/null; then - diff_highlight=diff-highlight -else - diff_highlight="$(get_script_dir)/third_party/diff-highlight/diff-highlight" -fi +call_dsf () { + "$(get_script_dir)/libexec/diff-so-fancy.pl" "$@" +} -print_header_clean () { - "$(get_script_dir)/libexec/diff-so-fancy.pl" +call_dh () { + # find my diff-highlight + if hash diff-highlight 2> /dev/null; then + "diff-highlight" + else + "$(get_script_dir)/third_party/diff-highlight/diff-highlight" + fi } -# run it. -# shellcheck disable=SC2002 -cat $input \ - | $diff_highlight \ - | print_header_clean +################################################################################ + +# Check to see if there is an STDIN +if [[ ! -t 0 ]]; then + call_dh | call_dsf +# We've been called stand-alone, pass options directly through +else + call_dsf "$@" + exit +fi From f1ae25111016f6a4a213b5214eb0a12f40413955 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 6 Mar 2017 19:22:38 -0800 Subject: [PATCH 149/443] d-s-f is now 100% perl, no need to call it from Bash anymore --- .travis.yml | 3 +- diff-so-fancy | 582 +++++++++++++++++++++++++++++++++++++-- libexec/diff-so-fancy.pl | 523 ----------------------------------- 3 files changed, 555 insertions(+), 553 deletions(-) delete mode 100755 libexec/diff-so-fancy.pl diff --git a/.travis.yml b/.travis.yml index d97717e..cca456d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,5 +32,4 @@ install: - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode script: - ./bats/bin/bats test/*.bats - - shellcheck diff-so-fancy update-deps.sh - - perlcritic -1 -q --theme freenode libexec/diff-so-fancy.pl + - perlcritic -1 -q --theme freenode diff-so-fancy diff --git a/diff-so-fancy b/diff-so-fancy index a5fa8b1..5f55266 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,38 +1,564 @@ -#!/usr/bin/env bash +#!/usr/bin/env perl -get_script_dir () { - src="https://github.com/$%7BBASH_SOURCE[0]%7D" +my $VERSION = "0.12.0"; - while [[ -h "$src" ]]; do - dir="$(cd -P "$( dirname "$src" )" && pwd)" - src="https://github.com/$(readlink"$src")" - [[ $src != /* ]] && src="https://github.com/$dir/$src" - done +################################################################################# - dir="$(cd -P "$( dirname "$src" )" && pwd)" - echo "$dir" +use strict; +use warnings FATAL => 'all'; +use File::Basename; +use IPC::Open2; + +# Set the input (STDIN) as UTF8, but don't give warnings about unknown chars +use open qw(:std :utf8); # http://stackoverflow.com/a/519359 +no warnings 'utf8'; + +# Set the output to always be UTF8 +binmode STDOUT,':encoding(UTF-8)'; + +my $remove_file_add_header = 1; +my $remove_file_delete_header = 1; +my $clean_permission_changes = 1; +my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); +my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); +my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); +my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); +my $has_stdin = has_stdin(); + +# We only process ARGV if we don't have STDIN +my $input; +if (!$has_stdin) { + my $args = argv(); + + if ($args->{v} || $args->{version}) { + die(version()); + } elsif (!%$args) { + print usage(); exit; + die(usage()); + } else { + die("Missing input on STDIN\n"); + } +} else { + $input = filter_stdin_through_diff_highlight(); +} + +################################################################################# + +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $dim_magenta = "\e[38;5;146m"; +my $reset_color = "\e[0m"; +my $bold = "\e[1m"; +my $horizontal_color = ""; + +my $columns_to_remove = 0; + +my ($file_1,$file_2); +my $last_file_seen = ""; +my $i = 0; +my $in_hunk = 0; + +while (my $line = <$input>) { + ###################################################### + # Pre-process the line before we do any other markup # + ###################################################### + + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; + } + + ###################### + # End pre-processing # + ###################### + + ####################################################################### + + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $horizontal_color = $1; + print horizontal_rule($horizontal_color); + ######################### + # Look for the filename # + ######################### + } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + $last_file_seen = $5; + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } + + # Find the second file on the next line: +++ b/README.md + my $next = <$input>; + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } + + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } + + print file_change_string($file_1,$file_2) . "\n"; + + # Print out the bottom horizontal line of the header + print horizontal_rule($horizontal_color); + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + $columns_to_remove ||= 1; + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); + $last_file_seen = basename($last_file_seen); + + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + # Don't print the line (i.e. remove it from the output); + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + my $change = file_change_string($2,$4); + print "$horizontal_color$change (binary)\n"; + print horizontal_rule($horizontal_color); + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; + my $next = <$input>; + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($new_mode) = $next =~ m/new mode (\d+)/; + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + ##################################### + # Just a regular line, print it out # + ##################################### + } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + print $line; + } + + $i++; +} + +###################################################################################################### +# End regular code, begin functions +###################################################################################################### + +# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 +sub parse_hunk_header { + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; + $o_cnt = 1 unless defined $o_cnt; + $n_cnt = 1 unless defined $n_cnt; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); +} + +# Mark the first char of an empty line +sub mark_empty_line { + my $line = shift(); + + my $reset_color = "\e\\[0?m"; + my $reset_escape = "\e\[m"; + my $invert_color = "\e\[7m"; + + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + + return $line; +} + +# String to boolean +sub boolean { + my $str = shift(); + $str = trim($str); + + if ($str eq "" || $str =~ /^(no|false|0)$/i) { + return 0; + } else { + return 1; + } +} + +# Memoize getting the git config +{ + my $static_config; + + sub git_config_raw { + if ($static_config) { + # If we already have the config return that + return $static_config; + } + + my $cmd = "git config --list"; + my @out = `$cmd`; + + $static_config = \@out; + + return \@out; + } +} + +# Fetch a textual item from the git config +sub git_config { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || ""); + + my $out = git_config_raw(); + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return $default_value; + } + + my $raw = {}; + foreach my $line (@$out) { + if ($line =~ /=/) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + $raw->{$key} = $value; + } + } + + # If we're given a search key return that, else return the hash + if ($search_key) { + return $raw->{$search_key} || $default_value; + } else { + return $raw; + } +} + +# Fetch a boolean item from the git config +sub git_config_boolean { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || 0); # Default to false + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return boolean($default_value); + } + + my $result = git_config($search_key,$default_value); + my $ret = boolean($result); + + return $ret; +} + +# Check if we're inside of BATS +sub in_unit_test { + if ($ENV{BATS_CWD}) { + return 1; + } else { + return 0; + } +} + +sub get_less_charset { + my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); + foreach (@less_char_vars) { + return $ENV{$_} if defined $ENV{$_}; + } + + return ""; +} + +sub should_print_unicode { + if (-t STDOUT) { + # Always print unicode chars if we're not piping stuff, e.g. to less(1) + return 1; + } + + # Otherwise, assume we're piping to less(1) + my $less_charset = get_less_charset(); + if ($less_charset =~ /utf-?8/i) { + return 1; + } + + return 0; +} + +# Return git config as a hash +sub get_git_config_hash { + my $out = git_config_raw(); + + my %hash; + foreach my $line (@$out) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + my @path = split(/\./,$key); + + my $last = pop @path; + my $p = \%hash; + $p = $p->{$_} ||= {} for @path; + $p->{$last} = $value; + } + + return \%hash; } -call_dsf () { - "$(get_script_dir)/libexec/diff-so-fancy.pl" "$@" +# Try and be smart about what line the diff hunk starts on +sub start_line_calc { + my ($line_num,$diff_context) = @_; + my $ret; + + if ($line_num == 0 && $diff_context == 0) { + return 1; + } + + # Git defaults to three lines of context + my $default_context_lines = 3; + # Three lines on either side, and the line itself = 7 + my $expected_context = ($default_context_lines * 2 + 1); + + # The first three lines + if ($line_num == 1 && $diff_context < $expected_context) { + $ret = $diff_context - $default_context_lines; + } else { + $ret = $line_num + $default_context_lines; + } + + if ($ret < 1) { + $ret = 1; + } + + return $ret; } -call_dh () { - # find my diff-highlight - if hash diff-highlight 2> /dev/null; then - "diff-highlight" - else - "$(get_script_dir)/third_party/diff-highlight/diff-highlight" - fi +# Remove + or - at the beginning of the lines +sub strip_leading_indicators { + my $line = shift(); # Array passed in by reference + my $columns_to_remove = shift(); # Don't remove any lines by default + + if ($columns_to_remove == 0) { + return $line; # Nothing to do + } + + $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; + + return $line; } -################################################################################ +# Count the number of a given char in a string +sub char_count { + my ($needle,$str) = @_; + my $len = length($str); + my $ret = 0; -# Check to see if there is an STDIN -if [[ ! -t 0 ]]; then - call_dh | call_dsf -# We've been called stand-alone, pass options directly through -else - call_dsf "$@" - exit -fi + for (my $i = 0; $i < $len; $i++) { + my $found = substr($str,$i,1); + + if ($needle eq $found) { $ret++; } + } + + return $ret; +} + +# Remove all ANSI codes from a string +sub bleach_text { + my $str = shift(); + $str =~ s/\e\[\d*(;\d+)*m//mg; + + return $str; +} + +# Remove all trailing and leading spaces +sub trim { + my $s = shift(); + if (!$s) { return ""; } + $s =~ s/^\s*|\s*$//g; + + return $s; +} + +# Print a line of em-dash or line-drawing chars the full width of the screen +sub horizontal_rule { + my $color = $_[0] || ""; + my $width = `tput cols`; + my $uname = `uname -s`; + + if ($uname =~ /MINGW32|MSYS/) { + $width--; + } + + # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm + #my $dash = "\x{2014}"; + # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm + my $dash; + if ($use_unicode_dash_for_ruler && should_print_unicode()) { + $dash = "\x{2500}"; + } else { + $dash = "-"; + } + + # Draw the line + my $ret = $color . ($dash x $width) . "\n"; + + return $ret; +} + +sub file_change_string { + my $file_1 = shift(); + my $file_2 = shift(); + + # If they're the same it's a modify + if ($file_1 eq $file_2) { + return "modified: $file_1"; + # If the first is /dev/null it's a new file + } elsif ($file_1 eq "/dev/null") { + return "added: $file_2"; + # If the second is /dev/null it's a deletion + } elsif ($file_2 eq "/dev/null") { + return "deleted: $file_1"; + # If the files aren't the same it's a rename + } elsif ($file_1 ne $file_2) { + return "renamed: $file_1 to $file_2"; + # Something we haven't thought of yet + } else { + return "$file_1 -> $file_2"; + } +} + +# Check to see if STDIN is connected to an interactive terminal +sub has_stdin { + my $i = -t STDIN; + my $ret = int(!$i); + + return $ret; +} + +# We use this instead of Getopt::Long because it's faster and we're not parsing any +# crazy arguments +# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html +sub argv { + my $ret = {}; + + for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + # If the next item does not start with "--" it's the value for this item + if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { + $ret->{$key} = $ARGV[$i + 1]; + # Bareword like --verbose with no options + } else { + $ret->{$key}++; + } + } + } + + # We're looking for a certain item + if ($_[0]) { return $ret->{$_[0]}; } + + return $ret; +} + +# Output the command line usage for d-s-f +sub usage { + my $out = "Usage: + + # One off fanciness + git diff --color | diff-so-fancy + + # Configure git to use d-s-f for all diff operations + git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" + + # Create a git alias to run d-s-f on demand + git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n\n"; + + return $out; +} + +# Output the current version string +sub version { + my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; + $ret .= "Version : $VERSION\n\n"; + + return $ret; +} + +# Feed the raw git input through diff-highlight to get line level highlights +# +# Note: This returns a file handle +sub filter_stdin_through_diff_highlight { + my ($cmd_output,$cmd_input); + my $dh = find_diff_highlight(); + my $pid = open2($cmd_output, $cmd_input, $dh); + + foreach () { + print $cmd_input "$_"; + } + + close($cmd_input); + + return $cmd_output; +} + +# Find the location of diff-highlight +sub find_diff_highlight { + my $dh = "diff-highlight"; + my $dh_in_path = trim(`which $dh 2>/dev/null`); + my $ret; + + # The user may have a local version of diff-highlight + if ($dh_in_path) { + $ret = $dh_in_path; + # We use the one bundled with d-s-f + } else { + $ret = dirname($0) . "/third_party/$dh/diff-highlight"; + } + + if (!-X $ret) { + die("Error locating diff-highlight\n"); + } + + return $ret; +} diff --git a/libexec/diff-so-fancy.pl b/libexec/diff-so-fancy.pl deleted file mode 100755 index b3b540d..0000000 --- a/libexec/diff-so-fancy.pl +++ /dev/null @@ -1,523 +0,0 @@ -#!/usr/bin/env perl - -my $VERSION = "0.12.0"; - -################################################################################# - -use strict; -use warnings FATAL => 'all'; -use File::Basename; - -# Set the input (STDIN) as UTF8, but don't give warnings about unknown chars -use open qw(:std :utf8); # http://stackoverflow.com/a/519359 -no warnings 'utf8'; - -# Set the output to always be UTF8 -binmode STDOUT,':encoding(UTF-8)'; - -my $remove_file_add_header = 1; -my $remove_file_delete_header = 1; -my $clean_permission_changes = 1; -my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); -my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); -my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); -my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); -my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); -my $has_stdin = has_stdin(); - -# We only process ARGV if we don't have STDIN -if (!$has_stdin) { - my $args = argv(); - - if ($args->{v} || $args->{version}) { - die(version()); - } - - if (!%$args) { - print usage(); exit; - die(usage()); - } -} - -################################################################################# - -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my $dim_magenta = "\e[38;5;146m"; -my $reset_color = "\e[0m"; -my $bold = "\e[1m"; -my $horizontal_color = ""; - -my $columns_to_remove = 0; - -my ($file_1,$file_2); -my $last_file_seen = ""; -my $i = 0; -my $in_hunk = 0; - -while (my $line = <>) { - - ###################################################### - # Pre-process the line before we do any other markup # - ###################################################### - - # If the first line of the input is a blank line, skip that - if ($i == 0 && $line =~ /^\s*$/) { - next; - } - - ###################### - # End pre-processing # - ###################### - - ####################################################################### - - #################################################################### - # Look for git index and replace it horizontal line (header later) # - #################################################################### - if ($line =~ /^${ansi_color_regex}index /) { - # Print the line color and then the actual line - $horizontal_color = $1; - print horizontal_rule($horizontal_color); - ######################### - # Look for the filename # - ######################### - } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { - $last_file_seen = $5; - $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). - $in_hunk = 0; - ######################################## - # Find the first file: --- a/README.md # - ######################################## - } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_1 = $file_dir . $5; - } else { - $file_1 = $5; - } - - # Find the second file on the next line: +++ b/README.md - my $next = <>; - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; - if ($1) { - print $1; # Print out whatever color we're using - } - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_2 = $file_dir . $5; - } else { - $file_2 = $5; - } - - if ($file_2 ne "/dev/null") { - $last_file_seen = $file_2; - } - - print file_change_string($file_1,$file_2) . "\n"; - - # Print out the bottom horizontal line of the header - print horizontal_rule($horizontal_color); - ######################################## - # Check for "@@ -3,41 +3,63 @@" syntax # - ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - my $hunk_header = $4; - my $remain = bleach_text($5); - - # The number of colums to remove (1 or 2) is based on how many commas in the hunk header - $columns_to_remove = (char_count(",",$hunk_header)) - 1; - # On single line removes there is NO comma in the hunk so we force one - $columns_to_remove ||= 1; - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - $last_file_seen = basename($last_file_seen); - - # Figure out the start line - my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; - ################################### - # Remove any new file permissions # - ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { - # Don't print the line (i.e. remove it from the output); - ###################################### - # Remove any delete file permissions # - ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { - # Don't print the line (i.e. remove it from the output); - ################################ - # Look for binary file changes # - ################################ - } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - my $change = file_change_string($2,$4); - print "$horizontal_color$change (binary)\n"; - print horizontal_rule($horizontal_color); - ##################################################### - # Check if we're changing the permissions of a file # - ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { - my ($old_mode) = $4; - my $next = <>; - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($new_mode) = $next =~ m/new mode (\d+)/; - print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; - ##################################### - # Just a regular line, print it out # - ##################################### - } else { - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } - - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); - } - print $line; - } - - $i++; -} - -###################################################################################################### -# End regular code, begin functions -###################################################################################################### - -# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 -sub parse_hunk_header { - my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; - $o_cnt = 1 unless defined $o_cnt; - $n_cnt = 1 unless defined $n_cnt; - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); -} - -# Mark the first char of an empty line -sub mark_empty_line { - my $line = shift(); - - my $reset_color = "\e\\[0?m"; - my $reset_escape = "\e\[m"; - my $invert_color = "\e\[7m"; - - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; - - return $line; -} - -# String to boolean -sub boolean { - my $str = shift(); - $str = trim($str); - - if ($str eq "" || $str =~ /^(no|false|0)$/i) { - return 0; - } else { - return 1; - } -} - -# Memoize getting the git config -{ - my $static_config; - - sub git_config_raw { - if ($static_config) { - # If we already have the config return that - return $static_config; - } - - my $cmd = "git config --list"; - my @out = `$cmd`; - - $static_config = \@out; - - return \@out; - } -} - -# Fetch a textual item from the git config -sub git_config { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || ""); - - my $out = git_config_raw(); - - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return $default_value; - } - - my $raw = {}; - foreach my $line (@$out) { - if ($line =~ /=/) { - my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - $raw->{$key} = $value; - } - } - - # If we're given a search key return that, else return the hash - if ($search_key) { - return $raw->{$search_key} || $default_value; - } else { - return $raw; - } -} - -# Fetch a boolean item from the git config -sub git_config_boolean { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || 0); # Default to false - - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return boolean($default_value); - } - - my $result = git_config($search_key,$default_value); - my $ret = boolean($result); - - return $ret; -} - -# Check if we're inside of BATS -sub in_unit_test { - if ($ENV{BATS_CWD}) { - return 1; - } else { - return 0; - } -} - -sub get_less_charset { - my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); - foreach (@less_char_vars) { - return $ENV{$_} if defined $ENV{$_}; - } - - return ""; -} - -sub should_print_unicode { - if (-t STDOUT) { - # Always print unicode chars if we're not piping stuff, e.g. to less(1) - return 1; - } - - # Otherwise, assume we're piping to less(1) - my $less_charset = get_less_charset(); - if ($less_charset =~ /utf-?8/i) { - return 1; - } - - return 0; -} - -# Return git config as a hash -sub get_git_config_hash { - my $out = git_config_raw(); - - my %hash; - foreach my $line (@$out) { - my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - my @path = split(/\./,$key); - - my $last = pop @path; - my $p = \%hash; - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; - } - - return \%hash; -} - -# Try and be smart about what line the diff hunk starts on -sub start_line_calc { - my ($line_num,$diff_context) = @_; - my $ret; - - if ($line_num == 0 && $diff_context == 0) { - return 1; - } - - # Git defaults to three lines of context - my $default_context_lines = 3; - # Three lines on either side, and the line itself = 7 - my $expected_context = ($default_context_lines * 2 + 1); - - # The first three lines - if ($line_num == 1 && $diff_context < $expected_context) { - $ret = $diff_context - $default_context_lines; - } else { - $ret = $line_num + $default_context_lines; - } - - if ($ret < 1) { - $ret = 1; - } - - return $ret; -} - -# Remove + or - at the beginning of the lines -sub strip_leading_indicators { - my $line = shift(); # Array passed in by reference - my $columns_to_remove = shift(); # Don't remove any lines by default - - if ($columns_to_remove == 0) { - return $line; # Nothing to do - } - - $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; - - return $line; -} - -# Count the number of a given char in a string -sub char_count { - my ($needle,$str) = @_; - my $len = length($str); - my $ret = 0; - - for (my $i = 0; $i < $len; $i++) { - my $found = substr($str,$i,1); - - if ($needle eq $found) { $ret++; } - } - - return $ret; -} - -# Remove all ANSI codes from a string -sub bleach_text { - my $str = shift(); - $str =~ s/\e\[\d*(;\d+)*m//mg; - - return $str; -} - -# Remove all trailing and leading spaces -sub trim { - my $s = shift(); - if (!$s) { return ""; } - $s =~ s/^\s*|\s*$//g; - - return $s; -} - -# Print a line of em-dash or line-drawing chars the full width of the screen -sub horizontal_rule { - my $color = $_[0] || ""; - my $width = `tput cols`; - my $uname = `uname -s`; - - if ($uname =~ /MINGW32|MSYS/) { - $width--; - } - - # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm - #my $dash = "\x{2014}"; - # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm - my $dash; - if ($use_unicode_dash_for_ruler && should_print_unicode()) { - $dash = "\x{2500}"; - } else { - $dash = "-"; - } - - # Draw the line - my $ret = $color . ($dash x $width) . "\n"; - - return $ret; -} - -sub file_change_string { - my $file_1 = shift(); - my $file_2 = shift(); - - # If they're the same it's a modify - if ($file_1 eq $file_2) { - return "modified: $file_1"; - # If the first is /dev/null it's a new file - } elsif ($file_1 eq "/dev/null") { - return "added: $file_2"; - # If the second is /dev/null it's a deletion - } elsif ($file_2 eq "/dev/null") { - return "deleted: $file_1"; - # If the files aren't the same it's a rename - } elsif ($file_1 ne $file_2) { - return "renamed: $file_1 to $file_2"; - # Something we haven't thought of yet - } else { - return "$file_1 -> $file_2"; - } -} - -# Check to see if STDIN is connected to an interactive terminal -sub has_stdin { - my $i = -t STDIN; - my $ret = int(!$i); - - return $ret; -} - -# We use this instead of Getopt::Long because it's faster and we're not parsing any -# crazy arguments -# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html -sub argv { - my $ret = {}; - - for (my $i = 0; $i < scalar(@ARGV); $i++) { - # If the item starts with "-" it's a key - if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { - # If the next item does not start with "--" it's the value for this item - if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { - $ret->{$key} = $ARGV[$i + 1]; - # Bareword like --verbose with no options - } else { - $ret->{$key}++; - } - } - } - - # We're looking for a certain item - if ($_[0]) { return $ret->{$_[0]}; } - - return $ret; -} - -# Output the command line usage for d-s-f -sub usage { - my $out = "Usage: - - # One off fanciness - git diff --color | diff-so-fancy - - # Configure git to use d-s-f for all diff operations - git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" - - # Create a git alias to run d-s-f on demand - git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n\n"; - - return $out; -} - -# Output the current version string -sub version { - my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; - $ret .= "Version : $VERSION\n\n"; - - return $ret; -} From bc63594dbfa928ec0ad5cbdfab5a3709c38542fc Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 7 Mar 2017 08:13:14 -0800 Subject: [PATCH 150/443] Reap the child process per the IPC::Open2 documentation --- diff-so-fancy | 1 + 1 file changed, 1 insertion(+) diff --git a/diff-so-fancy b/diff-so-fancy index 5f55266..7cf8b31 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -538,6 +538,7 @@ sub filter_stdin_through_diff_highlight { } close($cmd_input); + waitpid $pid, 0; return $cmd_output; } From 4b68ef9b2def1359e9f81916462590a0460728fd Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 7 Mar 2017 10:42:05 -0800 Subject: [PATCH 151/443] Update usage perl @paulirish's suggestion --- diff-so-fancy | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 7cf8b31..ed15e7c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -505,12 +505,18 @@ sub argv { sub usage { my $out = "Usage: + # Print diff-so-fancy version + diff-so-fancy --version + # One off fanciness git diff --color | diff-so-fancy - # Configure git to use d-s-f for all diff operations + # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" + # If core.pager config is set, this can opt-out of using d-s-f + git --no-pager + # Create a git alias to run d-s-f on demand git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n\n"; From 1b122de9e7b343aea4f2dc19372eb36e8fe558b5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 7 Mar 2017 11:21:46 -0800 Subject: [PATCH 152/443] Add a --colors option to display the defaults This will help the user setup the first time, because the default git colors are not very good for this application --- diff-so-fancy | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index ed15e7c..fa14f72 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -33,6 +33,8 @@ if (!$has_stdin) { if ($args->{v} || $args->{version}) { die(version()); + } elsif ($args->{colors}) { + die(output_default_colors()); } elsif (!%$args) { print usage(); exit; die(usage()); @@ -523,6 +525,28 @@ sub usage { return $out; } +sub output_default_colors { + my $out = "Recommended default colors for diff-so-fancy\n"; + $out .= "--------------------------------------------\n"; + $out .= 'git config --global color.ui true + +git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldHighlight "red bold 52" +git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newHighlight "green bold 22" + +git config --global color.diff.meta "227" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "227 bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" +git config --global color.diff.whitespace "red reverse" + +'; + + return $out; +} + # Output the current version string sub version { my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; From d0574a73a20fc8ae5ed8015f25257ffc86124d55 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 7 Mar 2017 13:09:13 -0800 Subject: [PATCH 153/443] Remove a redundant line --- diff-so-fancy | 1 - 1 file changed, 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index fa14f72..9cc90b5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -36,7 +36,6 @@ if (!$has_stdin) { } elsif ($args->{colors}) { die(output_default_colors()); } elsif (!%$args) { - print usage(); exit; die(usage()); } else { die("Missing input on STDIN\n"); From 706e4f2f56f94d6f930694167ccd90c22774ede9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 20 Mar 2017 15:04:08 -0700 Subject: [PATCH 154/443] Bump version to 1.0.0 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 9cc90b5..e00b7df 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "0.12.0"; +my $VERSION = "1.0.0"; ################################################################################# From 0d1a00e1d52c3ce2f7f8c5a0cc2cf1321d9f81df Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 21 Mar 2017 16:12:23 -0700 Subject: [PATCH 155/443] Don't use IPC::Open2, when we can use vanilla open() --- diff-so-fancy | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e00b7df..105c30c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -6,8 +6,9 @@ my $VERSION = "1.0.0"; use strict; use warnings FATAL => 'all'; -use File::Basename; -use IPC::Open2; + +use File::Basename; # for dirname +use Cwd; # For realpath() # Set the input (STDIN) as UTF8, but don't give warnings about unknown chars use open qw(:std :utf8); # http://stackoverflow.com/a/519359 @@ -558,16 +559,10 @@ sub version { # # Note: This returns a file handle sub filter_stdin_through_diff_highlight { - my ($cmd_output,$cmd_input); my $dh = find_diff_highlight(); - my $pid = open2($cmd_output, $cmd_input, $dh); - - foreach () { - print $cmd_input "$_"; - } - close($cmd_input); - waitpid $pid, 0; + # Pipe the STDIN of this to diff-highlight + my $pid = open(my $cmd_output, "-|", $dh); return $cmd_output; } @@ -583,7 +578,8 @@ sub find_diff_highlight { $ret = $dh_in_path; # We use the one bundled with d-s-f } else { - $ret = dirname($0) . "/third_party/$dh/diff-highlight"; + my $path = dirname($0) . "/third_party/$dh/diff-highlight"; + $ret = Cwd::realpath($path); } if (!-X $ret) { From 4ed12dceee7f71ca39857a37d9614e256dd01d6f Mon Sep 17 00:00:00 2001 From: Marc Carmier Date: Wed, 29 Mar 2017 23:53:55 +0200 Subject: [PATCH 156/443] Remove reference to libexec in readme.md In the manual installation there is reference to a libexec directory that is not available on the repository. --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 7ccd13f..a685806 100644 --- a/readme.md +++ b/readme.md @@ -66,11 +66,9 @@ If you want, you can choose to install manually: 1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. 1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. -1. Place them in a location that is in your `PATH` directly or with symlinks. -1. Place `libexec/diff-so-fancy.pl` in the same directory as `diff-so-fancy`. You will end up something like this: +1. Place them in a location that is in your `PATH` directly or with symlinks. You will end up something like this: * `~/bin/diff-highlight` * `~/bin/diff-so-fancy` - * `~/bin/libexec/diff-so-fancy.pl` 1. Set up the git `core.pager` config, as described above. Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). From a2dbd1c76bf909bd236f4cb7dccc9f52926fc856 Mon Sep 17 00:00:00 2001 From: Leo Mao Date: Tue, 4 Apr 2017 14:24:49 +0800 Subject: [PATCH 157/443] Get the realpath of diff-so-fancy $0 may be a symlink of diff-so-fancy, then dirname($0) will point to a wrong directory. Use Cwd::realpath to figure out the actual path. --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 105c30c..f455881 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -578,7 +578,7 @@ sub find_diff_highlight { $ret = $dh_in_path; # We use the one bundled with d-s-f } else { - my $path = dirname($0) . "/third_party/$dh/diff-highlight"; + my $path = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; $ret = Cwd::realpath($path); } From c35d0e1af521aa23d531849c4c4134882f331b0a Mon Sep 17 00:00:00 2001 From: Leo Mao Date: Tue, 4 Apr 2017 21:53:04 +0800 Subject: [PATCH 158/443] There is no need to call Cwd::realpath again --- diff-so-fancy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index f455881..f8d31f7 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -578,8 +578,7 @@ sub find_diff_highlight { $ret = $dh_in_path; # We use the one bundled with d-s-f } else { - my $path = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; - $ret = Cwd::realpath($path); + $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; } if (!-X $ret) { From f779ba2ef302aff305bb5e2fbaefecb3d2ae2dcf Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 4 Apr 2017 07:50:05 -0700 Subject: [PATCH 159/443] Add --help --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index f8d31f7..74efb19 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -36,7 +36,7 @@ if (!$has_stdin) { die(version()); } elsif ($args->{colors}) { die(output_default_colors()); - } elsif (!%$args) { + } elsif (!%$args || $args->{help} || $args->{h}) { die(usage()); } else { die("Missing input on STDIN\n"); From 3adf0114da99643ec53a16253a3d6f42390e4c19 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 4 Apr 2017 07:55:48 -0700 Subject: [PATCH 160/443] Add another location to find diff-highlight --- diff-so-fancy | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 74efb19..7c1cec2 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -569,21 +569,29 @@ sub filter_stdin_through_diff_highlight { # Find the location of diff-highlight sub find_diff_highlight { - my $dh = "diff-highlight"; - my $dh_in_path = trim(`which $dh 2>/dev/null`); - my $ret; - - # The user may have a local version of diff-highlight - if ($dh_in_path) { - $ret = $dh_in_path; - # We use the one bundled with d-s-f - } else { - $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; - } - - if (!-X $ret) { - die("Error locating diff-highlight\n"); - } - - return $ret; + my $dh = "diff-highlight"; + my $dh_in_path = trim(`which $dh 2>/dev/null`); + my $ret; + + # We check for diff-highlight in three places: + # 1) System path + # 2) Same dir as d-s-f + # 3) third_party/diff-highlight/ + + # This is #1 + if ($dh_in_path) { + $ret = $dh_in_path; + # This is #2 and #3 + } else { + $ret = dirname(Cwd::realpath($0)) . "/diff-highlight"; + if (!-X $ret) { + $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; + } + } + + if (!-X $ret) { + die("Error locating diff-highlight\n"); + } + + return $ret; } From 20c65acd769d90d17cf240e32c15550b68fbb0a8 Mon Sep 17 00:00:00 2001 From: Itzik Ephraim Date: Wed, 10 May 2017 12:37:52 +0300 Subject: [PATCH 161/443] Fix curl flags in update-deps.sh After `-o` must follow the output path. The `-L` should either come before `-o` or after the path. --- update-deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update-deps.sh b/update-deps.sh index 9eda47b..dd052b3 100755 --- a/update-deps.sh +++ b/update-deps.sh @@ -1,4 +1,4 @@ #!/bin/bash -curl -oL "third_party/diff-highlight/diff-highlight" "https://github.com/git/git/raw/master/contrib/diff-highlight/diff-highlight" -curl -oL "third_party/diff-highlight/README" "https://github.com/git/git/raw/master/contrib/diff-highlight/README" +curl -Lo "third_party/diff-highlight/diff-highlight" "https://github.com/git/git/raw/master/contrib/diff-highlight/diff-highlight" +curl -Lo "third_party/diff-highlight/README" "https://github.com/git/git/raw/master/contrib/diff-highlight/README" From 46719c81bbd5517b534d0ecf28910d23ff049c7b Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 29 May 2017 15:01:32 -0700 Subject: [PATCH 162/443] circleci: fix shellcheck failure --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 18a04a3..eb8dcfa 100644 --- a/circle.yml +++ b/circle.yml @@ -18,4 +18,4 @@ checkout: test: override: - bats/bin/bats test/*.bats - - shellcheck diff-so-fancy update-deps.sh + - shellcheck *.sh From 84d65b57ca17d940be5d5d78a6077cf5dbaee921 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 29 May 2017 15:18:22 -0700 Subject: [PATCH 163/443] cleanup report-bug for shellcheck. --- report-bug.sh | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/report-bug.sh b/report-bug.sh index 8acb479..6350fa6 100755 --- a/report-bug.sh +++ b/report-bug.sh @@ -8,7 +8,7 @@ clipboard() { # while IFS= read -r buffer; do # body="$body$buffer\n"; # done - curl $PBCOPY_SERVER --data-urlencode body="$body" >/dev/null 2>&1 + curl "$PBCOPY_SERVER" --data-urlencode body="$body" >/dev/null 2>&1 return $? fi if type putclip >/dev/null 2>&1; then @@ -32,7 +32,7 @@ clipboard() { local copy_cmd="xsel -b" fi if [ -n "$copy_cmd" ] ;then - eval $copy_cmd + eval "$copy_cmd" else echo "clipboard is unavailable" 1>&2 fi @@ -40,20 +40,21 @@ clipboard() { file=${2:-diff.txt} -echo $1 > $file -eval $1 >> $file +{ + echo "$1" + eval "$1" + echo "" + echo "" + echo "" -echo " + echo "$1 --color" + eval "$1 --color" -" >> $file + echo "git config pager.diff" + eval "git config pager.diff" -echo "$1 --color" >> $file -eval "$1 --color" >> $file + echo "git config pager.show" + eval "git config pager.show" +} > "$file" -echo "git config pager.diff" >> $file -eval "git config pager.diff" >> $file - -echo "git config pager.show" >> $file -eval "git config pager.show" >> $file - -cat $file | base64 | clipboard +base64 < "$file" | clipboard From 66dc2710e7740782ff5b1c0672b6ac1b40d853c1 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 29 May 2017 15:19:37 -0700 Subject: [PATCH 164/443] enable shellcheck on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cca456d..0cc7fb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,4 @@ install: script: - ./bats/bin/bats test/*.bats - perlcritic -1 -q --theme freenode diff-so-fancy + - shellcheck *.sh From 5c9f13ee94d6ac204dc77cc8f348f53d16a52680 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 29 May 2017 21:59:45 -0700 Subject: [PATCH 165/443] comment out buffer for shellcheck --- report-bug.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report-bug.sh b/report-bug.sh index 6350fa6..ebfdac4 100755 --- a/report-bug.sh +++ b/report-bug.sh @@ -3,7 +3,7 @@ clipboard() { local copy_cmd if [ -n "$PBCOPY_SERVER" ]; then - local body="" buffer + local body="" # buffer body=$(cat) # while IFS= read -r buffer; do # body="$body$buffer\n"; From 873393ad14a82a8bccf24f9dca3543d727cc7a6f Mon Sep 17 00:00:00 2001 From: Boris Petrov Date: Wed, 7 Jun 2017 00:52:51 +0300 Subject: [PATCH 166/443] Add Perl 5.26 compatibility --- diff-so-fancy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 7c1cec2..5a6af6d 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -133,7 +133,9 @@ while (my $line = <$input>) { # The number of colums to remove (1 or 2) is based on how many commas in the hunk header $columns_to_remove = (char_count(",",$hunk_header)) - 1; # On single line removes there is NO comma in the hunk so we force one - $columns_to_remove ||= 1; + if ($columns_to_remove <= 0) { + $columns_to_remove = 1; + } if ($1) { print $1; # Print out whatever color we're using From 71bf3774163de1e00998db9f3cc909e0b156f0ac Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 7 Jun 2017 08:13:12 -0700 Subject: [PATCH 167/443] Test on Perl 5.26 and the latest dev Per: https://github.com/travis-perl/helpers --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index cca456d..12679d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ os: linux language: perl perl: + - dev + - 5.26 - 5.24 - 5.22 - 5.20 From 36bfeccd78456e93787bc84f253cab5569665fdb Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 7 Jun 2017 18:11:49 +0200 Subject: [PATCH 168/443] add a test for #233 Check the first column is correctly stripped when a hunk has no comma. --- test/diff-so-fancy.bats | 8 ++++++++ test/fixtures/hunk_no_comma.diff | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 test/fixtures/hunk_no_comma.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 7c1b675..cbe9e47 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -87,3 +87,11 @@ if begin" # Make sure the output contains SOME of the english text (i.e. it doesn't barf on the whole line) assert_output --partial "saw he conqu" } + +@test "Correctly handle hunk definition with no comma" { + output=$( load_fixture "hunk_no_comma" | $diff_so_fancy ) + # On single line removes there is NO comma in the hunk, + # make sure the first column is still correctly stripped. + run printf "%s" "$output" + assert_line --index 5 "after" +} diff --git a/test/fixtures/hunk_no_comma.diff b/test/fixtures/hunk_no_comma.diff new file mode 100644 index 0000000..4f4445f --- /dev/null +++ b/test/fixtures/hunk_no_comma.diff @@ -0,0 +1,7 @@ +diff --git i/file w/file +index 90be1f3..294186e 100644 +--- i/file ++++ w/file +@@ -1 +1 @@ +-before ++after From 6e5d39d6881a0d99c5adb157083b0852ead9a9c2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 7 Jun 2017 09:56:45 -0700 Subject: [PATCH 169/443] Tweak the usage, version and color output. This will allow us to | bash --- diff-so-fancy | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 5a6af6d..3cf9326 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -35,7 +35,9 @@ if (!$has_stdin) { if ($args->{v} || $args->{version}) { die(version()); } elsif ($args->{colors}) { - die(output_default_colors()); + # We print this to STDOUT so we can redirect to bash to auto-set the colors + print output_default_colors(); + exit; } elsif (!%$args || $args->{help} || $args->{h}) { die(usage()); } else { @@ -509,27 +511,27 @@ sub argv { sub usage { my $out = "Usage: - # Print diff-so-fancy version - diff-so-fancy --version +# Print diff-so-fancy version +diff-so-fancy --version - # One off fanciness - git diff --color | diff-so-fancy +# One off fanciness +git diff --color | diff-so-fancy - # Configure git to use d-s-f for *all* diff operations - git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" +# Configure git to use d-s-f for *all* diff operations +git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" - # If core.pager config is set, this can opt-out of using d-s-f - git --no-pager +# If core.pager config is set, this can opt-out of using d-s-f +git --no-pager - # Create a git alias to run d-s-f on demand - git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n\n"; +# Create a git alias to run d-s-f on demand +git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n"; return $out; } sub output_default_colors { - my $out = "Recommended default colors for diff-so-fancy\n"; - $out .= "--------------------------------------------\n"; + my $out = "# Recommended default colors for diff-so-fancy\n"; + $out .= "# --------------------------------------------\n"; $out .= 'git config --global color.ui true git config --global color.diff-highlight.oldNormal "red bold" @@ -543,7 +545,6 @@ git config --global color.diff.commit "227 bold" git config --global color.diff.old "red bold" git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" - '; return $out; @@ -552,7 +553,7 @@ git config --global color.diff.whitespace "red reverse" # Output the current version string sub version { my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; - $ret .= "Version : $VERSION\n\n"; + $ret .= "Version : $VERSION\n"; return $ret; } From 07c4106ca5781e1cef4587f500b3de563feeeebc Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 10 Jun 2017 08:04:53 +1000 Subject: [PATCH 170/443] 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ef2633a..6093faa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "0.11.4", + "version": "1.0.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy", From 99cd25af475c3bbdd599b653c25f6a11a41e8e53 Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Mon, 26 Jun 2017 20:41:28 +0530 Subject: [PATCH 171/443] fix: Wrap diff_highlight in quotes to avoid err on windows --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3cf9326..bba7851 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -565,7 +565,7 @@ sub filter_stdin_through_diff_highlight { my $dh = find_diff_highlight(); # Pipe the STDIN of this to diff-highlight - my $pid = open(my $cmd_output, "-|", $dh); + my $pid = open(my $cmd_output, "-|", "\"$dh\""); return $cmd_output; } From 50bb45b343fbc7beb9fc962a5df3b979048e8ac7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 26 Jun 2017 15:12:51 -0700 Subject: [PATCH 172/443] Clarify some error messages while we troubleshoot Windows --- diff-so-fancy | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index bba7851..499d1b0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -592,8 +592,10 @@ sub find_diff_highlight { } } - if (!-X $ret) { - die("Error locating diff-highlight\n"); + if (!$ret) { + die("Error #75913: Unable to locate diff-highlight\n"); + } elsif (!-X $ret) { + die("Error #19391: diff-highlight not executable\n"); } return $ret; From 50f83a5868a9e67ccdd7ca8c4fb0e547eb85a4b1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 26 Jun 2017 15:21:01 -0700 Subject: [PATCH 173/443] Clarify the path --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 499d1b0..0e54e9a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -595,7 +595,7 @@ sub find_diff_highlight { if (!$ret) { die("Error #75913: Unable to locate diff-highlight\n"); } elsif (!-X $ret) { - die("Error #19391: diff-highlight not executable\n"); + die("Error #19391: diff-highlight ($ret) not executable\n"); } return $ret; From ffc2f8f43356517a516a4e004ef863e34a49dd1c Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 27 Jun 2017 11:23:32 +1000 Subject: [PATCH 174/443] make text case consistent (#241) --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index a685806..22093e5 100644 --- a/readme.md +++ b/readme.md @@ -189,10 +189,10 @@ You'll need to install [bats](https://github.com/sstephenson/bats#installing-bat git submodule sync git submodule update --init # pull in the assertion library, bats-assert -# Run the test suite once: +# run the test suite once: bats test -# Run it on every change with `entr` +# run it on every change with `entr` brew install entr ls --color=never diff-so-fancy test/*.bats | entr bats test ``` From aaca0a38a8ed51b565573fb5b4c10733d0d36c0a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 29 Jun 2017 09:57:53 -0700 Subject: [PATCH 175/443] Add a modeline to make sure indentation is consistent --- diff-so-fancy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 0e54e9a..1fd277a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -600,3 +600,5 @@ sub find_diff_highlight { return $ret; } + +# vim: tabstop=4 shiftwidth=4 expandtab autoindent softtabstop=4 From f3dbd2dacf9f3ec978f4e1acbda36416a1d5da18 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 28 Jun 2017 14:06:20 -0700 Subject: [PATCH 176/443] Add the initial DiffHighlight.pm stuff to the script --- diff-so-fancy | 72 ++++++------- lib/DiffHighlight.pm | 233 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 44 deletions(-) create mode 100644 lib/DiffHighlight.pm diff --git a/diff-so-fancy b/diff-so-fancy index 1fd277a..0205ec3 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,15 +1,17 @@ #!/usr/bin/env perl -my $VERSION = "1.0.0"; +my $VERSION = "1.1.0"; ################################################################################# +use Cwd qw(abs_path); # For realpath() +use File::Basename; # for dirname +use lib dirname(abs_path($0)) . "/lib"; # Add the local lib/ to @INC +use DiffHighlight; + use strict; use warnings FATAL => 'all'; -use File::Basename; # for dirname -use Cwd; # For realpath() - # Set the input (STDIN) as UTF8, but don't give warnings about unknown chars use open qw(:std :utf8); # http://stackoverflow.com/a/519359 no warnings 'utf8'; @@ -28,7 +30,7 @@ my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); # We only process ARGV if we don't have STDIN -my $input; +my @input; if (!$has_stdin) { my $args = argv(); @@ -44,7 +46,7 @@ if (!$has_stdin) { die("Missing input on STDIN\n"); } } else { - $input = filter_stdin_through_diff_highlight(); + @input = filter_stdin_through_diff_highlight(); } ################################################################################# @@ -62,7 +64,7 @@ my $last_file_seen = ""; my $i = 0; my $in_hunk = 0; -while (my $line = <$input>) { +while (my $line = shift(@input)) { ###################################################### # Pre-process the line before we do any other markup # ###################################################### @@ -104,7 +106,7 @@ while (my $line = <$input>) { } # Find the second file on the next line: +++ b/README.md - my $next = <$input>; + my $next = shift(@input); $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; if ($1) { print $1; # Print out whatever color we're using @@ -171,7 +173,7 @@ while (my $line = <$input>) { ##################################################### } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { my ($old_mode) = $4; - my $next = <$input>; + my $next = shift(@input); if ($1) { print $1; # Print out whatever color we're using @@ -430,9 +432,8 @@ sub trim { sub horizontal_rule { my $color = $_[0] || ""; my $width = `tput cols`; - my $uname = `uname -s`; - if ($uname =~ /MINGW32|MSYS/) { + if (is_windows()) { $width--; } @@ -562,43 +563,26 @@ sub version { # # Note: This returns a file handle sub filter_stdin_through_diff_highlight { - my $dh = find_diff_highlight(); + my @dh_lines; + + # Have DH put the lines it's modified in an array + local $DiffHighlight::line_cb = sub { push(@dh_lines,@_) }; + + while (my $line = ) { + my $ok = DiffHighlight::handle_line($line); + } - # Pipe the STDIN of this to diff-highlight - my $pid = open(my $cmd_output, "-|", "\"$dh\""); + DiffHighlight::flush(); - return $cmd_output; + return @dh_lines; } -# Find the location of diff-highlight -sub find_diff_highlight { - my $dh = "diff-highlight"; - my $dh_in_path = trim(`which $dh 2>/dev/null`); - my $ret; - - # We check for diff-highlight in three places: - # 1) System path - # 2) Same dir as d-s-f - # 3) third_party/diff-highlight/ - - # This is #1 - if ($dh_in_path) { - $ret = $dh_in_path; - # This is #2 and #3 - } else { - $ret = dirname(Cwd::realpath($0)) . "/diff-highlight"; - if (!-X $ret) { - $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; - } - } - - if (!$ret) { - die("Error #75913: Unable to locate diff-highlight\n"); - } elsif (!-X $ret) { - die("Error #19391: diff-highlight ($ret) not executable\n"); - } - - return $ret; +sub is_windows { + if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin') { + return 1; + } else { + return 0; + } } # vim: tabstop=4 shiftwidth=4 expandtab autoindent softtabstop=4 diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm new file mode 100644 index 0000000..663992e --- /dev/null +++ b/lib/DiffHighlight.pm @@ -0,0 +1,233 @@ +package DiffHighlight; + +use 5.008; +use warnings FATAL => 'all'; +use strict; + +# Highlight by reversing foreground and background. You could do +# other things like bold or underline if you prefer. +my @OLD_HIGHLIGHT = ( + color_config('color.diff-highlight.oldnormal'), + color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + color_config('color.diff-highlight.oldreset', "\x1b[27m") +); +my @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), + color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), + color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) +); + +my $RESET = "\x1b[m"; +my $COLOR = qr/\x1b\[[0-9;]*m/; +my $BORING = qr/$COLOR|\s/; + +# The patch portion of git log -p --graph should only ever have preceding | and +# not / or \ as merge history only shows up on the commit line. +my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; + +my @removed; +my @added; +my $in_hunk; + +our $line_cb = sub { print @_ }; +our $flush_cb = sub { local $| = 1 }; + +sub handle_line { + local $_ = shift; + + if (!$in_hunk) { + $line_cb->($_); + $in_hunk = /^$GRAPH*$COLOR*\@\@ /; + } + elsif (/^$GRAPH*$COLOR*-/) { + push @removed, $_; + } + elsif (/^$GRAPH*$COLOR*\+/) { + push @added, $_; + } + else { + show_hunk(\@removed, \@added); + @removed = (); + @added = (); + + $line_cb->($_); + $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; + } + + # Most of the time there is enough output to keep things streaming, + # but for something like "git log -Sfoo", you can get one early + # commit and then many seconds of nothing. We want to show + # that one commit as soon as possible. + # + # Since we can receive arbitrary input, there's no optimal + # place to flush. Flushing on a blank line is a heuristic that + # happens to match git-log output. + if (!length) { + $flush_cb->(); + } +} + +sub flush { + # Flush any queued hunk (this can happen when there is no trailing + # context in the final diff of the input). + show_hunk(\@removed, \@added); +} + +sub highlight_stdin { + while () { + handle_line($_); + } + flush(); +} + +# Ideally we would feed the default as a human-readable color to +# git-config as the fallback value. But diff-highlight does +# not otherwise depend on git at all, and there are reports +# of it being used in other settings. Let's handle our own +# fallback, which means we will work even if git can't be run. +sub color_config { + my ($key, $default) = @_; + my $s = `git config --get-color $key 2>/dev/null`; + return length($s) ? $s : $default; +} + +sub show_hunk { + my ($a, $b) = @_; + + # If one side is empty, then there is nothing to compare or highlight. + if (!@$a || !@$b) { + $line_cb->(@$a, @$b); + return; + } + + # If we have mismatched numbers of lines on each side, we could try to + # be clever and match up similar lines. But for now we are simple and + # stupid, and only handle multi-line hunks that remove and add the same + # number of lines. + if (@$a != @$b) { + $line_cb->(@$a, @$b); + return; + } + + my @queue; + for (my $i = 0; $i < @$a; $i++) { + my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); + $line_cb->($rm); + push @queue, $add; + } + $line_cb->(@queue); +} + +sub highlight_pair { + my @a = split_line(shift); + my @b = split_line(shift); + + # Find common prefix, taking care to skip any ansi + # color codes. + my $seen_plusminus; + my ($pa, $pb) = (0, 0); + while ($pa < @a && $pb < @b) { + if ($a[$pa] =~ /$COLOR/) { + $pa++; + } + elsif ($b[$pb] =~ /$COLOR/) { + $pb++; + } + elsif ($a[$pa] eq $b[$pb]) { + $pa++; + $pb++; + } + elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { + $seen_plusminus = 1; + $pa++; + $pb++; + } + else { + last; + } + } + + # Find common suffix, ignoring colors. + my ($sa, $sb) = ($#a, $#b); + while ($sa >= $pa && $sb >= $pb) { + if ($a[$sa] =~ /$COLOR/) { + $sa--; + } + elsif ($b[$sb] =~ /$COLOR/) { + $sb--; + } + elsif ($a[$sa] eq $b[$sb]) { + $sa--; + $sb--; + } + else { + last; + } + } + + if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), + highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); + } + else { + return join('', @a), + join('', @b); + } +} + +# we split either by $COLOR or by character. This has the side effect of +# leaving in graph cruft. It works because the graph cruft does not contain "-" +# or "+" +sub split_line { + local $_ = shift; + return utf8::decode($_) ? + map { utf8::encode($_); $_ } + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/ : + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/; +} + +sub highlight_line { + my ($line, $prefix, $suffix, $theme) = @_; + + my $start = join('', @{$line}[0..($prefix-1)]); + my $mid = join('', @{$line}[$prefix..$suffix]); + my $end = join('', @{$line}[($suffix+1)..$#$line]); + + # If we have a "normal" color specified, then take over the whole line. + # Otherwise, we try to just manipulate the highlighted bits. + if (defined $theme->[0]) { + s/$COLOR//g for ($start, $mid, $end); + chomp $end; + return join('', + $theme->[0], $start, $RESET, + $theme->[1], $mid, $RESET, + $theme->[0], $end, $RESET, + "\n" + ); + } else { + return join('', + $start, + $theme->[1], $mid, $theme->[2], + $end + ); + } +} + +# Pairs are interesting to highlight only if we are going to end up +# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting +# is just useless noise. We can detect this by finding either a matching prefix +# or suffix (disregarding boring bits like whitespace and colorization). +sub is_pair_interesting { + my ($a, $pa, $sa, $b, $pb, $sb) = @_; + my $prefix_a = join('', @$a[0..($pa-1)]); + my $prefix_b = join('', @$b[0..($pb-1)]); + my $suffix_a = join('', @$a[($sa+1)..$#$a]); + my $suffix_b = join('', @$b[($sb+1)..$#$b]); + + return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || + $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || + $suffix_a !~ /^$BORING*$/ || + $suffix_b !~ /^$BORING*$/; +} From 0262f8cfe62ff5c0de5a768ec2278968abab3ca0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 28 Jun 2017 15:54:11 -0700 Subject: [PATCH 177/443] Remove standalone diff-highlight, it's a .pm now --- test/diff-highlight.bats | 26 --- third_party/diff-highlight/README | 193 ------------------- third_party/diff-highlight/diff-highlight | 225 ---------------------- 3 files changed, 444 deletions(-) delete mode 100644 test/diff-highlight.bats delete mode 100644 third_party/diff-highlight/README delete mode 100755 third_party/diff-highlight/diff-highlight diff --git a/test/diff-highlight.bats b/test/diff-highlight.bats deleted file mode 100644 index 8253141..0000000 --- a/test/diff-highlight.bats +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bats - -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' -load 'test_helper/util' - - -# bats fails to handle our multiline result, so we save to $output ourselves -output=$( load_fixture "ls-function" | $diff_so_fancy ) - - -setup() { - users_path=$PATH - mv "$BATS_TEST_DIRNAME/../third_party/diff-highlight/diff-highlight" "$BATS_TEST_DIRNAME" - export PATH=$BATS_TEST_DIRNAME:$PATH -} - -@test "diff-highlight resolved when already on path" { - assert_output --partial 'eval "env CLICOLOR_FORCE=1 command $ls $param $argv"' - assert_output --partial 'eval $ls $param "$argv"' -} - -teardown() { - export PATH=$users_path - mv "$BATS_TEST_DIRNAME/diff-highlight" "$BATS_TEST_DIRNAME/../third_party/diff-highlight" -} diff --git a/third_party/diff-highlight/README b/third_party/diff-highlight/README deleted file mode 100644 index 836b97a..0000000 --- a/third_party/diff-highlight/README +++ /dev/null @@ -1,193 +0,0 @@ -diff-highlight -============== - -Line oriented diffs are great for reviewing code, because for most -hunks, you want to see the old and the new segments of code next to each -other. Sometimes, though, when an old line and a new line are very -similar, it's hard to immediately see the difference. - -You can use "--color-words" to highlight only the changed portions of -lines. However, this can often be hard to read for code, as it loses -the line structure, and you end up with oddly formatted bits. - -Instead, this script post-processes the line-oriented diff, finds pairs -of lines, and highlights the differing segments. It's currently very -simple and stupid about doing these tasks. In particular: - - 1. It will only highlight hunks in which the number of removed and - added lines is the same, and it will pair lines within the hunk by - position (so the first removed line is compared to the first added - line, and so forth). This is simple and tends to work well in - practice. More complex changes don't highlight well, so we tend to - exclude them due to the "same number of removed and added lines" - restriction. Or even if we do try to highlight them, they end up - not highlighting because of our "don't highlight if the whole line - would be highlighted" rule. - - 2. It will find the common prefix and suffix of two lines, and - consider everything in the middle to be "different". It could - instead do a real diff of the characters between the two lines and - find common subsequences. However, the point of the highlight is to - call attention to a certain area. Even if some small subset of the - highlighted area actually didn't change, that's OK. In practice it - ends up being more readable to just have a single blob on the line - showing the interesting bit. - -The goal of the script is therefore not to be exact about highlighting -changes, but to call attention to areas of interest without being -visually distracting. Non-diff lines and existing diff coloration is -preserved; the intent is that the output should look exactly the same as -the input, except for the occasional highlight. - -Use ---- - -You can try out the diff-highlight program with: - ---------------------------------------------- -git log -p --color | /path/to/diff-highlight ---------------------------------------------- - -If you want to use it all the time, drop it in your $PATH and put the -following in your git configuration: - ---------------------------------------------- -[pager] - log = diff-highlight | less - show = diff-highlight | less - diff = diff-highlight | less ---------------------------------------------- - - -Color Config ------------- - -You can configure the highlight colors and attributes using git's -config. The colors for "old" and "new" lines can be specified -independently. There are two "modes" of configuration: - - 1. You can specify a "highlight" color and a matching "reset" color. - This will retain any existing colors in the diff, and apply the - "highlight" and "reset" colors before and after the highlighted - portion. - - 2. You can specify a "normal" color and a "highlight" color. In this - case, existing colors are dropped from that line. The non-highlighted - bits of the line get the "normal" color, and the highlights get the - "highlight" color. - -If no "new" colors are specified, they default to the "old" colors. If -no "old" colors are specified, the default is to reverse the foreground -and background for highlighted portions. - -Examples: - ---------------------------------------------- -# Underline highlighted portions -[color "diff-highlight"] -oldHighlight = ul -oldReset = noul ---------------------------------------------- - ---------------------------------------------- -# Varying background intensities -[color "diff-highlight"] -oldNormal = "black #f8cbcb" -oldHighlight = "black #ffaaaa" -newNormal = "black #cbeecb" -newHighlight = "black #aaffaa" ---------------------------------------------- - - -Bugs ----- - -Because diff-highlight relies on heuristics to guess which parts of -changes are important, there are some cases where the highlighting is -more distracting than useful. Fortunately, these cases are rare in -practice, and when they do occur, the worst case is simply a little -extra highlighting. This section documents some cases known to be -sub-optimal, in case somebody feels like working on improving the -heuristics. - -1. Two changes on the same line get highlighted in a blob. For example, - highlighting: - ----------------------------------------------- --foo(buf, size); -+foo(obj->buf, obj->size); ----------------------------------------------- - - yields (where the inside of "+{}" would be highlighted): - ----------------------------------------------- --foo(buf, size); -+foo(+{obj->buf, obj->}size); ----------------------------------------------- - - whereas a more semantically meaningful output would be: - ----------------------------------------------- --foo(buf, size); -+foo(+{obj->}buf, +{obj->}size); ----------------------------------------------- - - Note that doing this right would probably involve a set of - content-specific boundary patterns, similar to word-diff. Otherwise - you get junk like: - ------------------------------------------------------ --this line has some -{i}nt-{ere}sti-{ng} text on it -+this line has some +{fa}nt+{a}sti+{c} text on it ------------------------------------------------------ - - which is less readable than the current output. - -2. The multi-line matching assumes that lines in the pre- and post-image - match by position. This is often the case, but can be fooled when a - line is removed from the top and a new one added at the bottom (or - vice versa). Unless the lines in the middle are also changed, diffs - will show this as two hunks, and it will not get highlighted at all - (which is good). But if the lines in the middle are changed, the - highlighting can be misleading. Here's a pathological case: - ------------------------------------------------------ --one --two --three --four -+two 2 -+three 3 -+four 4 -+five 5 ------------------------------------------------------ - - which gets highlighted as: - ------------------------------------------------------ --one --t-{wo} --three --f-{our} -+two 2 -+t+{hree 3} -+four 4 -+f+{ive 5} ------------------------------------------------------ - - because it matches "two" to "three 3", and so forth. It would be - nicer as: - ------------------------------------------------------ --one --two --three --four -+two +{2} -+three +{3} -+four +{4} -+five 5 ------------------------------------------------------ - - which would probably involve pre-matching the lines into pairs - according to some heuristic. diff --git a/third_party/diff-highlight/diff-highlight b/third_party/diff-highlight/diff-highlight deleted file mode 100755 index ecf4195..0000000 --- a/third_party/diff-highlight/diff-highlight +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env perl - -use 5.008; -use warnings FATAL => 'all'; -use strict; - -# Highlight by reversing foreground and background. You could do -# other things like bold or underline if you prefer. -my @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") -); -my @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) -); - -my $RESET = "\x1b[m"; -my $COLOR = qr/\x1b\[[0-9;]*m/; -my $BORING = qr/$COLOR|\s/; - -# The patch portion of git log -p --graph should only ever have preceding | and -# not / or \ as merge history only shows up on the commit line. -my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; - -my @removed; -my @added; -my $in_hunk; - -# Some scripts may not realize that SIGPIPE is being ignored when launching the -# pager--for instance scripts written in Python. -$SIG{PIPE} = 'DEFAULT'; - -while (<>) { - if (!$in_hunk) { - print; - $in_hunk = /^$GRAPH*$COLOR*\@\@ /; - } - elsif (/^$GRAPH*$COLOR*-/) { - push @removed, $_; - } - elsif (/^$GRAPH*$COLOR*\+/) { - push @added, $_; - } - else { - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - - print; - $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; - } - - # Most of the time there is enough output to keep things streaming, - # but for something like "git log -Sfoo", you can get one early - # commit and then many seconds of nothing. We want to show - # that one commit as soon as possible. - # - # Since we can receive arbitrary input, there's no optimal - # place to flush. Flushing on a blank line is a heuristic that - # happens to match git-log output. - if (!length) { - local $| = 1; - } -} - -# Flush any queued hunk (this can happen when there is no trailing context in -# the final diff of the input). -show_hunk(\@removed, \@added); - -exit 0; - -# Ideally we would feed the default as a human-readable color to -# git-config as the fallback value. But diff-highlight does -# not otherwise depend on git at all, and there are reports -# of it being used in other settings. Let's handle our own -# fallback, which means we will work even if git can't be run. -sub color_config { - my ($key, $default) = @_; - my $s = `git config --get-color $key 2>/dev/null`; - return length($s) ? $s : $default; -} - -sub show_hunk { - my ($a, $b) = @_; - - # If one side is empty, then there is nothing to compare or highlight. - if (!@$a || !@$b) { - print @$a, @$b; - return; - } - - # If we have mismatched numbers of lines on each side, we could try to - # be clever and match up similar lines. But for now we are simple and - # stupid, and only handle multi-line hunks that remove and add the same - # number of lines. - if (@$a != @$b) { - print @$a, @$b; - return; - } - - my @queue; - for (my $i = 0; $i < @$a; $i++) { - my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - print $rm; - push @queue, $add; - } - print @queue; -} - -sub highlight_pair { - my @a = split_line(shift); - my @b = split_line(shift); - - # Find common prefix, taking care to skip any ansi - # color codes. - my $seen_plusminus; - my ($pa, $pb) = (0, 0); - while ($pa < @a && $pb < @b) { - if ($a[$pa] =~ /$COLOR/) { - $pa++; - } - elsif ($b[$pb] =~ /$COLOR/) { - $pb++; - } - elsif ($a[$pa] eq $b[$pb]) { - $pa++; - $pb++; - } - elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { - $seen_plusminus = 1; - $pa++; - $pb++; - } - else { - last; - } - } - - # Find common suffix, ignoring colors. - my ($sa, $sb) = ($#a, $#b); - while ($sa >= $pa && $sb >= $pb) { - if ($a[$sa] =~ /$COLOR/) { - $sa--; - } - elsif ($b[$sb] =~ /$COLOR/) { - $sb--; - } - elsif ($a[$sa] eq $b[$sb]) { - $sa--; - $sb--; - } - else { - last; - } - } - - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), - highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); - } - else { - return join('', @a), - join('', @b); - } -} - -# we split either by $COLOR or by character. This has the side effect of -# leaving in graph cruft. It works because the graph cruft does not contain "-" -# or "+" -sub split_line { - local $_ = shift; - return utf8::decode($_) ? - map { utf8::encode($_); $_ } - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/ : - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/; -} - -sub highlight_line { - my ($line, $prefix, $suffix, $theme) = @_; - - my $start = join('', @{$line}[0..($prefix-1)]); - my $mid = join('', @{$line}[$prefix..$suffix]); - my $end = join('', @{$line}[($suffix+1)..$#$line]); - - # If we have a "normal" color specified, then take over the whole line. - # Otherwise, we try to just manipulate the highlighted bits. - if (defined $theme->[0]) { - s/$COLOR//g for ($start, $mid, $end); - chomp $end; - return join('', - $theme->[0], $start, $RESET, - $theme->[1], $mid, $RESET, - $theme->[0], $end, $RESET, - "\n" - ); - } else { - return join('', - $start, - $theme->[1], $mid, $theme->[2], - $end - ); - } -} - -# Pairs are interesting to highlight only if we are going to end up -# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting -# is just useless noise. We can detect this by finding either a matching prefix -# or suffix (disregarding boring bits like whitespace and colorization). -sub is_pair_interesting { - my ($a, $pa, $sa, $b, $pb, $sb) = @_; - my $prefix_a = join('', @$a[0..($pa-1)]); - my $prefix_b = join('', @$b[0..($pb-1)]); - my $suffix_a = join('', @$a[($sa+1)..$#$a]); - my $suffix_b = join('', @$b[($sb+1)..$#$b]); - - return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || - $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || - $suffix_a !~ /^$BORING*$/ || - $suffix_b !~ /^$BORING*$/; -} From ef91e55499739e0d557682796af0ad5a7ac6f4cd Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 28 Jun 2017 16:44:04 -0700 Subject: [PATCH 178/443] Initial fatpack build script --- diff-so-fancy | 4 +- package-lock.json | 5 ++ package.json | 3 +- third_party/build_fatpack/build.pl | 139 +++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 package-lock.json create mode 100755 third_party/build_fatpack/build.pl diff --git a/diff-so-fancy b/diff-so-fancy index 0205ec3..7ee5635 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -560,8 +560,6 @@ sub version { } # Feed the raw git input through diff-highlight to get line level highlights -# -# Note: This returns a file handle sub filter_stdin_through_diff_highlight { my @dh_lines; @@ -585,4 +583,4 @@ sub is_windows { } } -# vim: tabstop=4 shiftwidth=4 expandtab autoindent softtabstop=4 +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3b0792a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "diff-so-fancy", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/package.json b/package.json index 6093faa..f4f09fb 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "1.0.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { - "diff-so-fancy": "diff-so-fancy", - "diff-highlight": "third_party/diff-highlight/diff-highlight" + "diff-so-fancy": "diff-so-fancy" }, "scripts": { "test": "bats test" diff --git a/third_party/build_fatpack/build.pl b/third_party/build_fatpack/build.pl new file mode 100755 index 0000000..2baea88 --- /dev/null +++ b/third_party/build_fatpack/build.pl @@ -0,0 +1,139 @@ +#!/usr/bin/env perl + +########################################################################### +# This will build a stand-a-lone version of diff-so-fancy using Fatpack +# +# Scott Baker - 2017-06-28 +# +# Usage: perl build.pl [--output /tmp/diff-so-fancy] +########################################################################### + +use strict; +use warnings; +use File::Basename; +use Cwd qw(abs_path getcwd); + +my $args = argv(); + +my $output_file = "/tmp/diff-so-fancy"; +my $input_file = "diff-so-fancy"; + +# Allow overriding the output file +if ($args->{output}) { + $output_file = $args->{output}; +} + +my $dir = dirname($0); +my $root = abs_path("$dir/../../"); +my $cwd = getcwd(); + +# Change to the root of the tree +chdir($root); +my $dsf_version = get_version($input_file); + +my $cmd = "fatpack pack $input_file 2>/dev/null > $output_file"; + +my $exit = system($cmd); +$exit = $exit >> 8; + +rmdir("fatlib"); # fatpack leaves empty fatlib dirs so we remove them +my $size = -s $output_file; + +my $good = color("82bold"); +my $bad = color("160bold"); +my $warn = color("226bold"); +my $vers = color("230bold"); +my $white = color("15bold"); +my $reset = color(); + +if (!$exit) { + print "${good}Success:${reset} Wrote of diff-so-fancy ${vers}v$dsf_version${reset} to $output_file ($size bytes)\n"; + chmod 0755,$output_file; # Make the output executable +} else { + print "${bad}Error :${reset} Fatpack failed to build $output_file with exit code: ${warn}$exit${reset}\n"; + print "${white}Command: $cmd$reset\n"; +} + +chdir($cwd); + +############################################################################# + +sub argv { + my $ret = {}; + + for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + # If the next item does not start with "--" it's the value for this item + if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { + $ret->{$key} = $ARGV[$i + 1]; + # Bareword like --verbose with no options + } else { + $ret->{$key}++; + } + } + } + + # We're looking for a certain item + if ($_[0]) { return $ret->{$_[0]}; } + + return $ret; +} + +# String format: '115', '165bold', '10_on_140', 'reset', 'on_173' +sub color { + my $str = shift(); + + # If we're NOT connected to a an interactive terminal don't do color + if (-t STDOUT == 0) { return ''; } + + # No string sent in, so we just reset + if (!$str || $str eq 'reset') { + return "\e[0m"; + } + + # Get foreground and bold + my ($fc,$bold) = $str =~ /^(\d+)(b|bold)?/g; + # Get the background color (if present) + my ($bc) = $str =~ /on_?(\d+)$/g; + + my $ret = ''; + if ($bold) { $ret .= "\e[1m"; } + if ($fc) { $ret .= "\e[38;5;${fc}m"; } + if ($bc) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + +sub pfile { + my $file = shift(); + if (!-r $file) { return ''; } # Make sure the file is readable + + my $ret = ''; + + open(INPUT, "<", $file); + while () { + $ret .= $_; + } + close INPUT; + + if (wantarray) { + return split(/\n/,$ret); + } else { + return $ret; + } +} + +sub get_version { + my $file = shift(); + my @lines = pfile($file); + + foreach my $l (@lines) { + if ($l =~ /\$VERSION\s+=\s+"(.+?)"/) { + my $ver = $1; + return $ver; + } + } + + return undef; +} From 43fa32630df3dfb99bddf1f218c9ef53b03ae25c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 30 Jun 2017 13:50:48 -0700 Subject: [PATCH 179/443] Add tests for file rename --- test/fixtures/file-rename.diff | 22 ++++++++++++++++++++++ test/header_clean.bats | 8 ++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/fixtures/file-rename.diff diff --git a/test/fixtures/file-rename.diff b/test/fixtures/file-rename.diff new file mode 100644 index 0000000..f9355d1 --- /dev/null +++ b/test/fixtures/file-rename.diff @@ -0,0 +1,22 @@ +diff --git a/Changes.new b/bin/Changes.new +similarity index 100% +rename from Changes.new +rename to bin/Changes.new +diff --git a/dist.ini b/dist.ini +index ca8c2ad..d2874a4 100644 +--- a/dist.ini ++++ b/dist.ini +@@ -1,4 +1,4 @@ +-name = csvgrep ++name = csvgrepper + author = Neil Bowers + license = Perl_5 + copyright_holder = Neil Bowers +@@ -9,6 +9,7 @@ version = 0.05 + [@Basic] + [PkgVersion] + [AutoPrereqs] ++[BAZ] + + [GithubMeta] + [MetaJSON] diff --git a/test/header_clean.bats b/test/header_clean.bats index 37c6833..9ac8369 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -7,6 +7,14 @@ load 'test_helper/util' # bats fails to handle our multiline result, so we save to $output ourselves output=$( load_fixture "file-moves" | $diff_so_fancy ) +@test "Handle file renames" { + output=$( load_fixture "file-rename" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 1 --partial "renamed:" + assert_line --index 1 --partial "Changes.new" + assert_line --index 1 --partial "bin/" +} + @test "header_clean 'added:'" { assert_output --partial 'added: hello.txt' } From 29c08025e34051a106c782fac46a77cabbf56795 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 30 Jun 2017 13:51:11 -0700 Subject: [PATCH 180/443] Make d-s-f handle file renames better. Addresses #213 --- diff-so-fancy | 55 ++++++++++++++++----- lib/DiffHighlight.pm | 18 +++++-- third_party/build_fatpack/build.pl | 77 ++++++++++++++++++++---------- 3 files changed, 108 insertions(+), 42 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 7ee5635..5687310 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -55,14 +55,13 @@ my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; my $dim_magenta = "\e[38;5;146m"; my $reset_color = "\e[0m"; my $bold = "\e[1m"; -my $horizontal_color = ""; - -my $columns_to_remove = 0; +my $meta_color = ""; my ($file_1,$file_2); -my $last_file_seen = ""; -my $i = 0; -my $in_hunk = 0; +my $last_file_seen = ""; +my $i = 0; +my $in_hunk = 0; +my $columns_to_remove = 0; while (my $line = shift(@input)) { ###################################################### @@ -85,8 +84,8 @@ while (my $line = shift(@input)) { #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line - $horizontal_color = $1; - print horizontal_rule($horizontal_color); + $meta_color = $1; + print horizontal_rule($meta_color); ######################### # Look for the filename # ######################### @@ -125,7 +124,7 @@ while (my $line = shift(@input)) { print file_change_string($file_1,$file_2) . "\n"; # Print out the bottom horizontal line of the header - print horizontal_rule($horizontal_color); + print horizontal_rule($meta_color); ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## @@ -166,8 +165,8 @@ while (my $line = shift(@input)) { ################################ } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { my $change = file_change_string($2,$4); - print "$horizontal_color$change (binary)\n"; - print horizontal_rule($horizontal_color); + print "$meta_color$change (binary)\n"; + print horizontal_rule($meta_color); ##################################################### # Check if we're changing the permissions of a file # ##################################################### @@ -181,6 +180,30 @@ while (my $line = shift(@input)) { my ($new_mode) = $next =~ m/new mode (\d+)/; print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + + ############### + # File rename # + ############### + } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { + my $next = shift(@input); + my ($file1) = $next =~ /rename from (.+)/; + + $next = shift(@input); + my ($file2) = $next =~ /rename to (.+)/; + + if ($file1 && $file2) { + # We may not have extracted this yet, so we pull from the config if not + $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + + my $change = file_change_string($file1,$file2); + + print horizontal_rule($meta_color); + print $meta_color . $change . "\n"; + print horizontal_rule($meta_color); + } + + $i += 3; # We've consumed three lines + next; ##################################### # Just a regular line, print it out # ##################################### @@ -468,7 +491,15 @@ sub file_change_string { return "deleted: $file_1"; # If the files aren't the same it's a rename } elsif ($file_1 ne $file_2) { - return "renamed: $file_1 to $file_2"; + my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); + $old = trim($old); + $new = trim($new); + + # highlight_pair resets the colors, but we want it to be the meta color + $old =~ s/(\e0?\[m)/$1$meta_color/g; + $new =~ s/(\e0?\[m)/$1$meta_color/g; + + return "renamed: $old to $new"; # Something we haven't thought of yet } else { return "$file_1 -> $file_2"; diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 663992e..8c70105 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -9,12 +9,12 @@ use strict; my @OLD_HIGHLIGHT = ( color_config('color.diff-highlight.oldnormal'), color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") + "\x1b[27m", ); my @NEW_HIGHLIGHT = ( color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) + $OLD_HIGHLIGHT[2], ); my $RESET = "\x1b[m"; @@ -121,6 +121,7 @@ sub show_hunk { sub highlight_pair { my @a = split_line(shift); my @b = split_line(shift); + my $opts = shift(); # Find common prefix, taking care to skip any ansi # color codes. @@ -165,9 +166,18 @@ sub highlight_pair { } } + my @OLD_COLOR_SPEC = @OLD_HIGHLIGHT; + my @NEW_COLOR_SPEC = @NEW_HIGHLIGHT; + + # If we're only highlight the differences temp disable the old/new normal colors + if ($opts->{'only_diff'}) { + $OLD_COLOR_SPEC[0] = ''; + $NEW_COLOR_SPEC[0] = ''; + } + if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), - highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); + return highlight_line(\@a, $pa, $sa, \@OLD_COLOR_SPEC), + highlight_line(\@b, $pb, $sb, \@NEW_COLOR_SPEC); } else { return join('', @a), diff --git a/third_party/build_fatpack/build.pl b/third_party/build_fatpack/build.pl index 2baea88..0d93743 100755 --- a/third_party/build_fatpack/build.pl +++ b/third_party/build_fatpack/build.pl @@ -14,6 +14,12 @@ use Cwd qw(abs_path getcwd); my $args = argv(); +my $ok = has_fatpack(); + +if (!$ok) { + printf("%sError:%s App::Fatpack must be installed to build diff-so-fancy\n",color('red_bold'),color('reset')); + exit; +} my $output_file = "/tmp/diff-so-fancy"; my $input_file = "diff-so-fancy"; @@ -47,7 +53,7 @@ my $reset = color(); if (!$exit) { - print "${good}Success:${reset} Wrote of diff-so-fancy ${vers}v$dsf_version${reset} to $output_file ($size bytes)\n"; + print "${good}Success:${reset} Wrote diff-so-fancy ${vers}v$dsf_version${reset} to $output_file ($size bytes)\n"; chmod 0755,$output_file; # Make the output executable } else { print "${bad}Error :${reset} Fatpack failed to build $output_file with exit code: ${warn}$exit${reset}\n"; @@ -80,31 +86,6 @@ sub argv { return $ret; } -# String format: '115', '165bold', '10_on_140', 'reset', 'on_173' -sub color { - my $str = shift(); - - # If we're NOT connected to a an interactive terminal don't do color - if (-t STDOUT == 0) { return ''; } - - # No string sent in, so we just reset - if (!$str || $str eq 'reset') { - return "\e[0m"; - } - - # Get foreground and bold - my ($fc,$bold) = $str =~ /^(\d+)(b|bold)?/g; - # Get the background color (if present) - my ($bc) = $str =~ /on_?(\d+)$/g; - - my $ret = ''; - if ($bold) { $ret .= "\e[1m"; } - if ($fc) { $ret .= "\e[38;5;${fc}m"; } - if ($bc) { $ret .= "\e[48;5;${bc}m"; } - - return $ret; -} - sub pfile { my $file = shift(); if (!-r $file) { return ''; } # Make sure the file is readable @@ -137,3 +118,47 @@ sub get_version { return undef; } + +sub has_fatpack { + my $out = `which fatpack 2>/dev/null`; + + return trim($out); +} + +sub trim { + my $s = shift(); + if (length($s) == 0) { return ""; } + $s =~ s/^\s*|\s*$//g; + + return $s; +} + +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173' +sub color { + my $str = shift(); + + # If we're NOT connected to a an interactive terminal don't do color + if (-t STDOUT == 0) { return ''; } + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s/$_/$color_map{$_}/g for keys %color_map; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /^(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)$/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd || 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} From d0eca9fc7302589727dec83206a0f6b507563a94 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 08:36:28 -0700 Subject: [PATCH 181/443] Vim modelines --- third_party/ansi-reveal/ansi-reveal.pl | 2 ++ third_party/build_fatpack/build.pl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/third_party/ansi-reveal/ansi-reveal.pl b/third_party/ansi-reveal/ansi-reveal.pl index 692591a..5410fb3 100755 --- a/third_party/ansi-reveal/ansi-reveal.pl +++ b/third_party/ansi-reveal/ansi-reveal.pl @@ -136,3 +136,5 @@ BEGIN *k = sub { print Data::Dumper::Dumper($_[0]) }; } } + +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 diff --git a/third_party/build_fatpack/build.pl b/third_party/build_fatpack/build.pl index 0d93743..a78cb96 100755 --- a/third_party/build_fatpack/build.pl +++ b/third_party/build_fatpack/build.pl @@ -162,3 +162,5 @@ sub color { return $ret; } + +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 From 499f7f9f0d39fdf8e41bc5ca5316089ff4278b64 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 10:06:50 -0700 Subject: [PATCH 182/443] Update readme to reflect new 1.1.0 changes --- readme.md | 193 ++++++++++-------------------------------------------- 1 file changed, 35 insertions(+), 158 deletions(-) diff --git a/readme.md b/readme.md index 22093e5..a4ca563 100644 --- a/readme.md +++ b/readme.md @@ -1,209 +1,86 @@ # diff-so-fancy [![Circle CI build](https://circleci.com/gh/so-fancy/diff-so-fancy.svg?style=svg)](https://circleci.com/gh/so-fancy/diff-so-fancy) [![TravisCI build](https://travis-ci.org/so-fancy/diff-so-fancy.svg?branch=master)](https://travis-ci.org/so-fancy/diff-so-fancy) [![AppVeyor build](https://ci.appveyor.com/api/projects/status/github/so-fancy/diff-so-fancy?branch=master&svg=true)](https://ci.appveyor.com/project/stevemao/diff-so-fancy/branch/master) -diff-so-fancy builds on the good-lookin' output of [git contrib](https://github.com/git/git/tree/master/contrib)'s [diff-highlight](https://github.com/git/git/tree/master/contrib/diff-highlight) to upgrade -your diffs' appearances. +`diff-so-fancy` strives to make your diff's **human** readable instead of machine readable. This helps improve code quality and help you spot defects faster. -* Output will not be in standard patch format, but will be readable. -* No pesky `+` or `-` at line-start, making for easier copy-paste. ## Screenshot -*`git diff` vs `git diff --color | diff-so-fancy`* +Vanilla `git diff` vs `git` and `diff-so-fancy` ![diff-highlight vs diff-so-fancy](https://cloud.githubusercontent.com/assets/39191/13622719/7cc7c54c-e555-11e5-86c4-7045d91af041.png) -## Usage - -You can do one-off fanciness: -```shell -git diff --color | diff-so-fancy -``` +## Install -**But**, you'll probably want to fancify all your diffs. Set your `core.pager` to run `diff-so-fancy`, and pipe the output through your existing pager, or if unset we recommend `less --tabs=4 -RFX`: -```shell -git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" -``` +Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path -However, if you'd prefer to do the fanciness on-demand with `git dsf`, add an alias to your `~/.gitconfig` by running: -```shell -git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ -'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' -``` +`diff-so-fancy` is also available from NPM, brew, and as a package on Arch Linux. -## Install - -For convenience, the recommended installation is via NPM. If you'd prefer, you may choose to do a [manual installation](#manual-install) instead. -```shell -npm install -g diff-so-fancy -``` -This will install and link the `diff-so-fancy` and `diff-highlight` scripts. You can also upgrade to the latest version with this command. +## Usage -On Mac, you can install via Homebrew: +Configure git to use `diff-so-fancy` for all diff output (recommended): ```shell -brew update -brew install diff-so-fancy -``` - -A package is available in Arch linux: -``` -pacman -S diff-so-fancy +git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` ### Improved colors for the highlighted bits -`diff-highlight` has default colors that are arguably a little nasty. They'll work fine, but you can try some fancier colors: +The default colors are not the best so we recommend the following color settings as a base. ```shell +git config --global color.ui true + git config --global color.diff-highlight.oldNormal "red bold" git config --global color.diff-highlight.oldHighlight "red bold 52" git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -``` -You may also want to configure [general diff colors](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/.gitconfig#L23-L36). - -## Manual install - -If you want, you can choose to install manually: - -1. Grab the two scripts (`diff-highlight` and `diff-so-fancy`) via either downloading or cloning the repo. -1. If you download `diff-highlight` from the official git repo, give it a `chmod +x`. -1. Place them in a location that is in your `PATH` directly or with symlinks. You will end up something like this: - * `~/bin/diff-highlight` - * `~/bin/diff-so-fancy` -1. Set up the git `core.pager` config, as described above. -Note: The `diff-highlight` dependency is an [official git-contrib script](https://github.com/git/git/tree/master/contrib/diff-highlight), duplicated here for convenience. If you prefer less fancy in your diff, you also use diff-highlight [on it's own](https://news.ycombinator.com/item?id=11068436). +git config --global color.diff.meta "227" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "227 bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" +git config --global color.diff.whitespace "red reverse" +``` -## options +## Options ### markEmptyLines -Should the first block of an empty line be colored. - -### changeHunkIndicators - -Simplify git header chunks to a more human readable format. - -### stripLeadingSymbols - -Should the pesky `+` or `-` at line-start be removed. - -### useUnicodeRuler - -By default the separator for the file header uses Unicode line drawing characters. -If this is causing output errors on your terminal set this to `false` to use ASCII characters instead. - -By default all the configs are true. You can turn any off by running: - +Should the first block of an empty line be colored. (Default: true) ``` git config --bool --global diff-so-fancy.markEmptyLines false -git config --bool --global diff-so-fancy.changeHunkIndicators false -git config --bool --global diff-so-fancy.stripLeadingSymbols false -git config --bool --global diff-so-fancy.useUnicodeRuler false ``` -To reset them to default (`true`): +### changeHunkIndicators +Simplify git header chunks to a more human readable format. (Default: true) ``` -git config --unset --global diff-so-fancy.markEmptyLines -git config --unset --global diff-so-fancy.changeHunkIndicators -git config --unset --global diff-so-fancy.stripLeadingSymbols -git config --unset --global diff-so-fancy.useUnicodeRuler +git config --bool --global diff-so-fancy.changeHunkIndicators false ``` -## Pro-tips - -#### Opting-out - -Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: +### stripLeadingSymbols -```shell -git --no-pager diff +Should the pesky `+` or `-` at line-start be removed. (Default: true) ``` - -#### Raw patches - -As a shortcut for a 'normal' diff to save as a patch for emailing or later -application, it may be helpful to configure an alias: -```ini -[alias] - patch = !git --no-pager diff --no-color +git config --bool --global diff-so-fancy.stripLeadingSymbols false ``` -which can then be used as `git patch > changes.patch`. -#### Moving around in the diff +### useUnicodeRuler -You can pre-seed your `less` pager with a search pattern, so you can move -between files with `n`/`N` keys: -```ini -[pager] - diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' +By default the separator for the file header uses Unicode line drawing characters. If this is causing output errors on your terminal set this to `false` to use ASCII characters instead. (Default: true) +``` +git config --bool --global diff-so-fancy.useUnicodeRuler false ``` - -## History - -`diff-so-fancy` started as [a commit in paulirish's dotfiles](https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13349456), which grew into a [standalone script](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/bin/diff-so-fancy). Later, [@stevemao](https://github.com/stevemao) brought it into its [own repo](https://github.com/so-fancy/diff-so-fancy) (here), and gave it the room to mature. It's quickly grown into a [widely collaborative project](https://github.com/so-fancy/diff-so-fancy/graphs/contributors). ## Contributing Pull requests quite welcome, along with any feedback or ideas. -### Reporting bugs - -If you find a bug using the following command - -```sh -git diff HEAD..HEAD^ -``` - -You can use [report-bug.sh](./report-bug.sh) we provide +### Other documentation -```sh -./report-bug.sh 'git diff HEAD..HEAD^' - -# or - -curl https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/report-bug.sh | bash -s 'git diff HEAD..HEAD^' 'diff.txt' -``` - -Grab the output file and attach to the GitHub issue you create. A base64 version is also copied to your clipboard so you can paste to the issue. - -### Hacking - -```sh -# fork and clone the diff-so-fancy repo. -git clone https://github.com/so-fancy/diff-so-fancy/ && cd diff-so-fancy - -# test a saved diff against your local version -cat test/fixtures/ls-function.diff | ./diff-so-fancy - -# setup symlinks to use local copy -npm link -cd ~/projects/catfabulator && git diff -``` - -### Running tests - -You'll need to install [bats](https://github.com/sstephenson/bats#installing-bats-from-source), the Bash automated testing system. It's also available as `brew install bats` - -```sh -git submodule sync -git submodule update --init # pull in the assertion library, bats-assert - -# run the test suite once: -bats test - -# run it on every change with `entr` -brew install entr -ls --color=never diff-so-fancy test/*.bats | entr bats test -``` -When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` - -You can lint your scripts via shellcheck, our CI bots will also check. - -```sh -brew install shellcheck -shellcheck diff-so-fancy update-deps.sh -``` +* [Pro-tips on advanced usage](pro-tips.md) +* [Reporting Bugs](reporting-bugs.md) +* [Hacking and Testing](hacking-and-testing.md) +* [History](history.md) ## License From d7e1e5c530b9e987c21be3abeff30f16345102a3 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 10:07:28 -0700 Subject: [PATCH 183/443] Additional documentation the I broke off the main page --- hacking-and-testing.md | 36 ++++++++++++++++++++++++++++++++++++ history.md | 3 +++ pro-tips.md | 38 ++++++++++++++++++++++++++++++++++++++ reporting-bugs.md | 19 +++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 hacking-and-testing.md create mode 100644 history.md create mode 100644 pro-tips.md create mode 100644 reporting-bugs.md diff --git a/hacking-and-testing.md b/hacking-and-testing.md new file mode 100644 index 0000000..27558e7 --- /dev/null +++ b/hacking-and-testing.md @@ -0,0 +1,36 @@ +### Hacking + +```sh +# fork and clone the diff-so-fancy repo. +git clone https://github.com/so-fancy/diff-so-fancy/ && cd diff-so-fancy + +# test a saved diff against your local version +cat test/fixtures/ls-function.diff | ./diff-so-fancy + +# setup symlinks to use local copy +npm link +cd ~/projects/catfabulator && git diff +``` + +### Running tests + +You'll need to install [bats](https://github.com/sstephenson/bats#installing-bats-from-source), the Bash automated testing system. It's also available as `brew install bats` + +```sh +git submodule sync +git submodule update --init # pull in the assertion library, bats-assert + +# run the test suite once: +bats test + +# run it on every change with `entr` +brew install entr +ls --color=never diff-so-fancy test/*.bats | entr bats test +``` +When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` + +You can lint your scripts via shellcheck, our CI bots will also check. + +```sh +brew install shellcheck +shellcheck diff-so-fancy update-deps.sh diff --git a/history.md b/history.md new file mode 100644 index 0000000..f6776e0 --- /dev/null +++ b/history.md @@ -0,0 +1,3 @@ +## History + +`diff-so-fancy` started as [a commit in paulirish's dotfiles](https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13349456), which grew into a [standalone script](https://github.com/paulirish/dotfiles/blob/63cb8193b0e66cf80ab6332477f1f52c7fbb9311/bin/diff-so-fancy). Later, [@stevemao](https://github.com/stevemao) brought it into its [own repo](https://github.com/so-fancy/diff-so-fancy) (here), and gave it the room to mature. It's quickly grown into a [widely collaborative project](https://github.com/so-fancy/diff-so-fancy/graphs/contributors). diff --git a/pro-tips.md b/pro-tips.md new file mode 100644 index 0000000..62e4e78 --- /dev/null +++ b/pro-tips.md @@ -0,0 +1,38 @@ +## Pro-tips + +#### One-off fanciness or a specific diff-so-fancy alias + +You can do also do a one-off or a specific `diff-so-fancy` alias: +```shell +git diff --color | diff-so-fancy + +git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ +'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' +``` + +#### Opting-out + +Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: + +```shell +git --no-pager diff +``` + +#### Raw patches + +As a shortcut for a 'normal' diff to save as a patch for emailing or later +application, it may be helpful to configure an alias: +```ini +[alias] + patch = !git --no-pager diff --no-color +``` +which can then be used as `git patch > changes.patch`. + +#### Moving around in the diff + +You can pre-seed your `less` pager with a search pattern, so you can move +between files with `n`/`N` keys: +```ini +[pager] + diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' +``` diff --git a/reporting-bugs.md b/reporting-bugs.md new file mode 100644 index 0000000..109fd19 --- /dev/null +++ b/reporting-bugs.md @@ -0,0 +1,19 @@ +### Reporting bugs + +If you find a bug using the following command + +```sh +git diff HEAD..HEAD^ +``` + +You can use [report-bug.sh](./report-bug.sh) we provide + +```sh +./report-bug.sh 'git diff HEAD..HEAD^' + +# or + +curl https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/report-bug.sh | bash -s 'git diff HEAD..HEAD^' 'diff.txt' +``` + +Grab the output file and attach to the GitHub issue you create. A base64 version is also copied to your clipboard so you can paste to the issue. From 6982e98d35da032d67770a3bf9802648a4c0bf5e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 10:08:51 -0700 Subject: [PATCH 184/443] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a4ca563..93a23af 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ Installation is as simple as downloading the [diff-so-fancy](https://raw.githubu ## Usage -Configure git to use `diff-so-fancy` for all diff output (recommended): +Configure git to use `diff-so-fancy` for all diff output: ```shell git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` From a933f496ef8c3bc94d5bfbf32074c6a0a2a468b0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 13:49:58 -0700 Subject: [PATCH 185/443] Add a set_defaults option, and a warning if first time running --- diff-so-fancy | 111 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 5687310..198f82b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -36,9 +36,11 @@ if (!$has_stdin) { if ($args->{v} || $args->{version}) { die(version()); + } elsif ($args->{set_defaults}) { + my $ok = set_defaults(); } elsif ($args->{colors}) { # We print this to STDOUT so we can redirect to bash to auto-set the colors - print output_default_colors(); + print get_default_colors(); exit; } elsif (!%$args || $args->{help} || $args->{h}) { die(usage()); @@ -46,6 +48,9 @@ if (!$has_stdin) { die("Missing input on STDIN\n"); } } else { + # Check to see if were using default settings + check_first_run(); + @input = filter_stdin_through_diff_highlight(); } @@ -561,7 +566,7 @@ git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREF return $out; } -sub output_default_colors { +sub get_default_colors { my $out = "# Recommended default colors for diff-so-fancy\n"; $out .= "# --------------------------------------------\n"; $out .= 'git config --global color.ui true @@ -614,4 +619,106 @@ sub is_windows { } } +sub check_first_run { + my $i = get_git_config_hash(); + my $ret = 0; + + # If first-run is not set, or it's set to "true" + my $first_run = boolean(!$i->{'diff-so-fancy'}->{'first-run'} || ($i->{'diff-so-fancy'}->{'first-run'} eq "true")); + # See if they're previously set SOME diff-highlight colors + my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); + + if ($first_run || $has_dh_colors) { + return 1; + } else { + my $interactive_shell = !has_stdin(); + + # d-s-f run inside of LESS (can't prompt user) + if (!$interactive_shell) { + my $warn = color("yellow_on_red"); + my $bold = color("bold"); + my $blink = color("blink"); + my $reset = color("reset"); + + printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); + printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n\n"); + + return 0; + } + + print "This appears to be the first time you've run diff-so-fancy, would you like to\n"; + print "configure git to use diff-so-fancy for all diff operations and use the\n"; + print "recommended color scheme (y/N)?\n"; + + my $input = ; + $input = uc(trim($input)); + + # Set the default colors and git alias + if ($input eq "Y") { + set_defaults(); + # Just set the first-run flag to false so we don't pop this up again + } else { + my $cmd = 'git config --global diff-so-fancy.first-run false'; + print "CMD: $cmd\n"; + system($cmd); + } + } +} + +sub set_defaults { + my $color_config = get_default_colors(); + my $git_config = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"'; + my $first_cmd = 'git config --global diff-so-fancy.first-run false'; + + my @cmds = split(/\n/,$color_config); + push(@cmds,$git_config); + push(@cmds,$first_cmd); + + # Remove all comments from the commands + foreach my $x (@cmds) { + $x =~ s/#.*//g; + } + + # Remove any empty commands + @cmds = grep($_,@cmds); + + foreach my $cmd (@cmds) { + system($cmd); + my $exit = ($? >> 8); + + if ($exit != 0) { + die("Error running: '$cmd' (error #18941)\n"); + } + } + + return 1; +} + +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red_bold', 'red_on_blue', 'blink', 'italic' +sub color { + my $str = shift(); + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s/$_/$color_map{$_}/g for keys %color_map; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /^(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)$/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd || 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + # vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 From 6b13bedea3ab0726f0e622ba94cffd3076dd6b1b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Jul 2017 14:05:53 -0700 Subject: [PATCH 186/443] Properly detect if they are on first_run --- diff-so-fancy | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 198f82b..564470e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -43,7 +43,11 @@ if (!$has_stdin) { print get_default_colors(); exit; } elsif (!%$args || $args->{help} || $args->{h}) { - die(usage()); + my $first = check_first_run(); + + if (!$first) { + die(usage()); + } } else { die("Missing input on STDIN\n"); } @@ -619,6 +623,7 @@ sub is_windows { } } +# Return value is whether this is the first time they've run d-s-f sub check_first_run { my $i = get_git_config_hash(); my $ret = 0; @@ -628,8 +633,8 @@ sub check_first_run { # See if they're previously set SOME diff-highlight colors my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); - if ($first_run || $has_dh_colors) { - return 1; + if (!$first_run || $has_dh_colors) { + return 0; } else { my $interactive_shell = !has_stdin(); @@ -659,10 +664,11 @@ sub check_first_run { # Just set the first-run flag to false so we don't pop this up again } else { my $cmd = 'git config --global diff-so-fancy.first-run false'; - print "CMD: $cmd\n"; system($cmd); } } + + return 1; } sub set_defaults { From 58477f2b0567f8c76a5bd5d4cadce99002263b33 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 6 Jul 2017 08:32:38 -0700 Subject: [PATCH 187/443] Use the correct package name --- third_party/build_fatpack/build.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/build_fatpack/build.pl b/third_party/build_fatpack/build.pl index a78cb96..942527f 100755 --- a/third_party/build_fatpack/build.pl +++ b/third_party/build_fatpack/build.pl @@ -17,7 +17,7 @@ my $ok = has_fatpack(); if (!$ok) { - printf("%sError:%s App::Fatpack must be installed to build diff-so-fancy\n",color('red_bold'),color('reset')); + printf("%sError:%s App::FatPacker must be installed to build diff-so-fancy\n",color('red_bold'),color('reset')); exit; } From 9d789d402f568d37b550e688c903c5774bd4ee00 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 08:20:09 -0700 Subject: [PATCH 188/443] Detect msys as "windows" also --- diff-so-fancy | 66 +++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1fd277a..902fe27 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.0.0"; +my $VERSION = "1.0.1"; ################################################################################# @@ -572,33 +572,41 @@ sub filter_stdin_through_diff_highlight { # Find the location of diff-highlight sub find_diff_highlight { - my $dh = "diff-highlight"; - my $dh_in_path = trim(`which $dh 2>/dev/null`); - my $ret; - - # We check for diff-highlight in three places: - # 1) System path - # 2) Same dir as d-s-f - # 3) third_party/diff-highlight/ - - # This is #1 - if ($dh_in_path) { - $ret = $dh_in_path; - # This is #2 and #3 - } else { - $ret = dirname(Cwd::realpath($0)) . "/diff-highlight"; - if (!-X $ret) { - $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; - } - } - - if (!$ret) { - die("Error #75913: Unable to locate diff-highlight\n"); - } elsif (!-X $ret) { - die("Error #19391: diff-highlight ($ret) not executable\n"); - } - - return $ret; + my $dh = "diff-highlight"; + my $dh_in_path; + my $ret; + + # Windows needs the where command because 'which' is not reliable + if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { + $dh_in_path = trim(`where $dh`); + # Linux / OS X etc + } else { + $dh_in_path = trim(`which $dh 2>/dev/null`); + } + + # We check for diff-highlight in three places: + # 1) System path + # 2) Same dir as d-s-f + # 3) third_party/diff-highlight/ + + # This is #1 + if ($dh_in_path) { + $ret = $dh_in_path; + # This is #2 and #3 + } else { + $ret = dirname(Cwd::realpath($0)) . "/diff-highlight"; + if (!-X $ret) { + $ret = dirname(Cwd::realpath($0)) . "/third_party/$dh/diff-highlight"; + } + } + + if (!$ret) { + die("Error #75913: Unable to locate diff-highlight\n"); + } elsif (!-X $ret) { + die("Error #19391: diff-highlight ($ret) not executable\n"); + } + + return $ret; } -# vim: tabstop=4 shiftwidth=4 expandtab autoindent softtabstop=4 +# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 68b153acb7a43a6498b93aac879f0995445d5bb5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 08:35:36 -0700 Subject: [PATCH 189/443] Properly detect 'msys' as Windows --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 564470e..f576094 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -616,7 +616,7 @@ sub filter_stdin_through_diff_highlight { } sub is_windows { - if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin') { + if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { return 1; } else { return 0; From 4a5227f8921727b5b387e94e8d5379d353111316 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 08:46:13 -0700 Subject: [PATCH 190/443] Update usage to be more clear --- diff-so-fancy | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index f576094..35d9e97 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -36,7 +36,7 @@ if (!$has_stdin) { if ($args->{v} || $args->{version}) { die(version()); - } elsif ($args->{set_defaults}) { + } elsif ($args->{'set-defaults'}) { my $ok = set_defaults(); } elsif ($args->{colors}) { # We print this to STDOUT so we can redirect to bash to auto-set the colors @@ -550,22 +550,16 @@ sub argv { # Output the command line usage for d-s-f sub usage { - my $out = "Usage: + my $out = color("white_bold") . version() . color("reset") . "\n"; -# Print diff-so-fancy version -diff-so-fancy --version + $out .= "Usage: -# One off fanciness -git diff --color | diff-so-fancy +git diff --color | diff-so-fancy # Use d-s-f on one diff +git diff --colors # View the commands to set the recommended colors +git diff --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations -git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" - -# If core.pager config is set, this can opt-out of using d-s-f -git --no-pager - -# Create a git alias to run d-s-f on demand -git config --global alias.dsf '!f() { [ -z \"\$GIT_PREFIX\" ] || cd \"\$GIT_PREFIX\" && git diff --color \"\$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f'\n"; +git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; return $out; } From 0ae3e58e953136132b071d218f7d5262bb7ff712 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 10:46:50 -0700 Subject: [PATCH 191/443] Don't bomb on our invalid UTF8 --- lib/DiffHighlight.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 8c70105..64ff2a1 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -3,6 +3,7 @@ package DiffHighlight; use 5.008; use warnings FATAL => 'all'; use strict; +no warnings 'utf8'; # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. From 066ad1cbac6abaee0af981763cf098e602a7419a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 14:12:11 -0700 Subject: [PATCH 192/443] Don't auto-convert everything to UTF-8 which should work around weird encodings Potentially addresses #203 --- diff-so-fancy | 4 ++-- lib/DiffHighlight.pm | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 35d9e97..ed4ec62 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -13,11 +13,11 @@ use strict; use warnings FATAL => 'all'; # Set the input (STDIN) as UTF8, but don't give warnings about unknown chars -use open qw(:std :utf8); # http://stackoverflow.com/a/519359 +#use open qw(:std :utf8); # http://stackoverflow.com/a/519359 no warnings 'utf8'; # Set the output to always be UTF8 -binmode STDOUT,':encoding(UTF-8)'; +#binmode STDOUT,':encoding(UTF-8)'; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 64ff2a1..c8bf213 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -3,7 +3,7 @@ package DiffHighlight; use 5.008; use warnings FATAL => 'all'; use strict; -no warnings 'utf8'; +use Encode; # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -191,8 +191,8 @@ sub highlight_pair { # or "+" sub split_line { local $_ = shift; - return utf8::decode($_) ? - map { utf8::encode($_); $_ } + return eval { $_ = Encode::decode('UTF-8', $_, 1); 1 } ? + map { Encode::encode('UTF-8', $_) } map { /$COLOR/ ? $_ : (split //) } split /($COLOR+)/ : map { /$COLOR/ ? $_ : (split //) } From c33e866219eea49c60ec3050e6c1b85348e6d692 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 10 Jul 2017 16:57:32 -0700 Subject: [PATCH 193/443] Leave all input as bytes... and properly encode the unicode output --- diff-so-fancy | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index ed4ec62..fca2654 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -12,13 +12,6 @@ use DiffHighlight; use strict; use warnings FATAL => 'all'; -# Set the input (STDIN) as UTF8, but don't give warnings about unknown chars -#use open qw(:std :utf8); # http://stackoverflow.com/a/519359 -no warnings 'utf8'; - -# Set the output to always be UTF8 -#binmode STDOUT,':encoding(UTF-8)'; - my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; @@ -474,7 +467,7 @@ sub horizontal_rule { # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm my $dash; if ($use_unicode_dash_for_ruler && should_print_unicode()) { - $dash = "\x{2500}"; + $dash = Encode::encode('UTF-8', "\x{2500}"); } else { $dash = "-"; } From 6c1fa92ebe6992fac34490c4c0a79954ca23d7e0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 14 Jul 2017 09:01:14 -0700 Subject: [PATCH 194/443] Add the v1.1.0 "binary" to the tree so users can wget it --- third_party/build_fatpack/diff-so-fancy | 1006 +++++++++++++++++++++++ 1 file changed, 1006 insertions(+) create mode 100755 third_party/build_fatpack/diff-so-fancy diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy new file mode 100755 index 0000000..232c547 --- /dev/null +++ b/third_party/build_fatpack/diff-so-fancy @@ -0,0 +1,1006 @@ +#!/usr/bin/env perl + +# This chunk of stuff was generated by App::FatPacker. To find the original +# file's code, look for the end of this BEGIN block or the string 'FATPACK' +BEGIN { +my %fatpacked; + +$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT'; + package DiffHighlight; + + use 5.008; + use warnings FATAL => 'all'; + use strict; + use Encode; + + # Highlight by reversing foreground and background. You could do + # other things like bold or underline if you prefer. + my @OLD_HIGHLIGHT = ( + color_config('color.diff-highlight.oldnormal'), + color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + "\x1b[27m", + ); + my @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), + color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), + $OLD_HIGHLIGHT[2], + ); + + my $RESET = "\x1b[m"; + my $COLOR = qr/\x1b\[[0-9;]*m/; + my $BORING = qr/$COLOR|\s/; + + # The patch portion of git log -p --graph should only ever have preceding | and + # not / or \ as merge history only shows up on the commit line. + my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; + + my @removed; + my @added; + my $in_hunk; + + our $line_cb = sub { print @_ }; + our $flush_cb = sub { local $| = 1 }; + + sub handle_line { + local $_ = shift; + + if (!$in_hunk) { + $line_cb->($_); + $in_hunk = /^$GRAPH*$COLOR*\@\@ /; + } + elsif (/^$GRAPH*$COLOR*-/) { + push @removed, $_; + } + elsif (/^$GRAPH*$COLOR*\+/) { + push @added, $_; + } + else { + show_hunk(\@removed, \@added); + @removed = (); + @added = (); + + $line_cb->($_); + $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; + } + + # Most of the time there is enough output to keep things streaming, + # but for something like "git log -Sfoo", you can get one early + # commit and then many seconds of nothing. We want to show + # that one commit as soon as possible. + # + # Since we can receive arbitrary input, there's no optimal + # place to flush. Flushing on a blank line is a heuristic that + # happens to match git-log output. + if (!length) { + $flush_cb->(); + } + } + + sub flush { + # Flush any queued hunk (this can happen when there is no trailing + # context in the final diff of the input). + show_hunk(\@removed, \@added); + } + + sub highlight_stdin { + while () { + handle_line($_); + } + flush(); + } + + # Ideally we would feed the default as a human-readable color to + # git-config as the fallback value. But diff-highlight does + # not otherwise depend on git at all, and there are reports + # of it being used in other settings. Let's handle our own + # fallback, which means we will work even if git can't be run. + sub color_config { + my ($key, $default) = @_; + my $s = `git config --get-color $key 2>/dev/null`; + return length($s) ? $s : $default; + } + + sub show_hunk { + my ($a, $b) = @_; + + # If one side is empty, then there is nothing to compare or highlight. + if (!@$a || !@$b) { + $line_cb->(@$a, @$b); + return; + } + + # If we have mismatched numbers of lines on each side, we could try to + # be clever and match up similar lines. But for now we are simple and + # stupid, and only handle multi-line hunks that remove and add the same + # number of lines. + if (@$a != @$b) { + $line_cb->(@$a, @$b); + return; + } + + my @queue; + for (my $i = 0; $i < @$a; $i++) { + my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); + $line_cb->($rm); + push @queue, $add; + } + $line_cb->(@queue); + } + + sub highlight_pair { + my @a = split_line(shift); + my @b = split_line(shift); + my $opts = shift(); + + # Find common prefix, taking care to skip any ansi + # color codes. + my $seen_plusminus; + my ($pa, $pb) = (0, 0); + while ($pa < @a && $pb < @b) { + if ($a[$pa] =~ /$COLOR/) { + $pa++; + } + elsif ($b[$pb] =~ /$COLOR/) { + $pb++; + } + elsif ($a[$pa] eq $b[$pb]) { + $pa++; + $pb++; + } + elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { + $seen_plusminus = 1; + $pa++; + $pb++; + } + else { + last; + } + } + + # Find common suffix, ignoring colors. + my ($sa, $sb) = ($#a, $#b); + while ($sa >= $pa && $sb >= $pb) { + if ($a[$sa] =~ /$COLOR/) { + $sa--; + } + elsif ($b[$sb] =~ /$COLOR/) { + $sb--; + } + elsif ($a[$sa] eq $b[$sb]) { + $sa--; + $sb--; + } + else { + last; + } + } + + my @OLD_COLOR_SPEC = @OLD_HIGHLIGHT; + my @NEW_COLOR_SPEC = @NEW_HIGHLIGHT; + + # If we're only highlight the differences temp disable the old/new normal colors + if ($opts->{'only_diff'}) { + $OLD_COLOR_SPEC[0] = ''; + $NEW_COLOR_SPEC[0] = ''; + } + + if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + return highlight_line(\@a, $pa, $sa, \@OLD_COLOR_SPEC), + highlight_line(\@b, $pb, $sb, \@NEW_COLOR_SPEC); + } + else { + return join('', @a), + join('', @b); + } + } + + # we split either by $COLOR or by character. This has the side effect of + # leaving in graph cruft. It works because the graph cruft does not contain "-" + # or "+" + sub split_line { + local $_ = shift; + return eval { $_ = Encode::decode('UTF-8', $_, 1); 1 } ? + map { Encode::encode('UTF-8', $_) } + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/ : + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/; + } + + sub highlight_line { + my ($line, $prefix, $suffix, $theme) = @_; + + my $start = join('', @{$line}[0..($prefix-1)]); + my $mid = join('', @{$line}[$prefix..$suffix]); + my $end = join('', @{$line}[($suffix+1)..$#$line]); + + # If we have a "normal" color specified, then take over the whole line. + # Otherwise, we try to just manipulate the highlighted bits. + if (defined $theme->[0]) { + s/$COLOR//g for ($start, $mid, $end); + chomp $end; + return join('', + $theme->[0], $start, $RESET, + $theme->[1], $mid, $RESET, + $theme->[0], $end, $RESET, + "\n" + ); + } else { + return join('', + $start, + $theme->[1], $mid, $theme->[2], + $end + ); + } + } + + # Pairs are interesting to highlight only if we are going to end up + # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting + # is just useless noise. We can detect this by finding either a matching prefix + # or suffix (disregarding boring bits like whitespace and colorization). + sub is_pair_interesting { + my ($a, $pa, $sa, $b, $pb, $sb) = @_; + my $prefix_a = join('', @$a[0..($pa-1)]); + my $prefix_b = join('', @$b[0..($pb-1)]); + my $suffix_a = join('', @$a[($sa+1)..$#$a]); + my $suffix_b = join('', @$b[($sb+1)..$#$b]); + + return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || + $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || + $suffix_a !~ /^$BORING*$/ || + $suffix_b !~ /^$BORING*$/; + } +DIFFHIGHLIGHT + +s/^ //mg for values %fatpacked; + +my $class = 'FatPacked::'.(0+\%fatpacked); +no strict 'refs'; +*{"${class}::files"} = sub { keys %{$_[0]} }; + +if ($] < 5.008) { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + my $pos = 0; + my $last = length $fat; + return (sub { + return 0 if $pos == $last; + my $next = (1 + index $fat, "\n", $pos) || $last; + $_ .= substr $fat, $pos, $next - $pos; + $pos = $next; + return 1; + }); + } + }; +} + +else { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + open my $fh, '<', \$fat + or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; + return $fh; + } + return; + }; +} + +unshift @INC, bless \%fatpacked, $class; + } # END OF FATPACK CODE + + +my $VERSION = "1.1.0"; + +################################################################################# + +use Cwd qw(abs_path); # For realpath() +use File::Basename; # for dirname +use lib dirname(abs_path($0)) . "/lib"; # Add the local lib/ to @INC +use DiffHighlight; + +use strict; +use warnings FATAL => 'all'; + +my $remove_file_add_header = 1; +my $remove_file_delete_header = 1; +my $clean_permission_changes = 1; +my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); +my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); +my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); +my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); +my $has_stdin = has_stdin(); + +# We only process ARGV if we don't have STDIN +my @input; +if (!$has_stdin) { + my $args = argv(); + + if ($args->{v} || $args->{version}) { + die(version()); + } elsif ($args->{'set-defaults'}) { + my $ok = set_defaults(); + } elsif ($args->{colors}) { + # We print this to STDOUT so we can redirect to bash to auto-set the colors + print get_default_colors(); + exit; + } elsif (!%$args || $args->{help} || $args->{h}) { + my $first = check_first_run(); + + if (!$first) { + die(usage()); + } + } else { + die("Missing input on STDIN\n"); + } +} else { + # Check to see if were using default settings + check_first_run(); + + @input = filter_stdin_through_diff_highlight(); +} + +################################################################################# + +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $dim_magenta = "\e[38;5;146m"; +my $reset_color = "\e[0m"; +my $bold = "\e[1m"; +my $meta_color = ""; + +my ($file_1,$file_2); +my $last_file_seen = ""; +my $i = 0; +my $in_hunk = 0; +my $columns_to_remove = 0; + +while (my $line = shift(@input)) { + ###################################################### + # Pre-process the line before we do any other markup # + ###################################################### + + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; + } + + ###################### + # End pre-processing # + ###################### + + ####################################################################### + + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $meta_color = $1; + print horizontal_rule($meta_color); + ######################### + # Look for the filename # + ######################### + } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + $last_file_seen = $5; + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } + + # Find the second file on the next line: +++ b/README.md + my $next = shift(@input); + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } + + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } + + print file_change_string($file_1,$file_2) . "\n"; + + # Print out the bottom horizontal line of the header + print horizontal_rule($meta_color); + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + if ($columns_to_remove <= 0) { + $columns_to_remove = 1; + } + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); + $last_file_seen = basename($last_file_seen); + + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + # Don't print the line (i.e. remove it from the output); + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + my $change = file_change_string($2,$4); + print "$meta_color$change (binary)\n"; + print horizontal_rule($meta_color); + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; + my $next = shift(@input); + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($new_mode) = $next =~ m/new mode (\d+)/; + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + + ############### + # File rename # + ############### + } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { + my $next = shift(@input); + my ($file1) = $next =~ /rename from (.+)/; + + $next = shift(@input); + my ($file2) = $next =~ /rename to (.+)/; + + if ($file1 && $file2) { + # We may not have extracted this yet, so we pull from the config if not + $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + + my $change = file_change_string($file1,$file2); + + print horizontal_rule($meta_color); + print $meta_color . $change . "\n"; + print horizontal_rule($meta_color); + } + + $i += 3; # We've consumed three lines + next; + ##################################### + # Just a regular line, print it out # + ##################################### + } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + print $line; + } + + $i++; +} + +###################################################################################################### +# End regular code, begin functions +###################################################################################################### + +# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 +sub parse_hunk_header { + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; + $o_cnt = 1 unless defined $o_cnt; + $n_cnt = 1 unless defined $n_cnt; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); +} + +# Mark the first char of an empty line +sub mark_empty_line { + my $line = shift(); + + my $reset_color = "\e\\[0?m"; + my $reset_escape = "\e\[m"; + my $invert_color = "\e\[7m"; + + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + + return $line; +} + +# String to boolean +sub boolean { + my $str = shift(); + $str = trim($str); + + if ($str eq "" || $str =~ /^(no|false|0)$/i) { + return 0; + } else { + return 1; + } +} + +# Memoize getting the git config +{ + my $static_config; + + sub git_config_raw { + if ($static_config) { + # If we already have the config return that + return $static_config; + } + + my $cmd = "git config --list"; + my @out = `$cmd`; + + $static_config = \@out; + + return \@out; + } +} + +# Fetch a textual item from the git config +sub git_config { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || ""); + + my $out = git_config_raw(); + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return $default_value; + } + + my $raw = {}; + foreach my $line (@$out) { + if ($line =~ /=/) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + $raw->{$key} = $value; + } + } + + # If we're given a search key return that, else return the hash + if ($search_key) { + return $raw->{$search_key} || $default_value; + } else { + return $raw; + } +} + +# Fetch a boolean item from the git config +sub git_config_boolean { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || 0); # Default to false + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return boolean($default_value); + } + + my $result = git_config($search_key,$default_value); + my $ret = boolean($result); + + return $ret; +} + +# Check if we're inside of BATS +sub in_unit_test { + if ($ENV{BATS_CWD}) { + return 1; + } else { + return 0; + } +} + +sub get_less_charset { + my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); + foreach (@less_char_vars) { + return $ENV{$_} if defined $ENV{$_}; + } + + return ""; +} + +sub should_print_unicode { + if (-t STDOUT) { + # Always print unicode chars if we're not piping stuff, e.g. to less(1) + return 1; + } + + # Otherwise, assume we're piping to less(1) + my $less_charset = get_less_charset(); + if ($less_charset =~ /utf-?8/i) { + return 1; + } + + return 0; +} + +# Return git config as a hash +sub get_git_config_hash { + my $out = git_config_raw(); + + my %hash; + foreach my $line (@$out) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + my @path = split(/\./,$key); + + my $last = pop @path; + my $p = \%hash; + $p = $p->{$_} ||= {} for @path; + $p->{$last} = $value; + } + + return \%hash; +} + +# Try and be smart about what line the diff hunk starts on +sub start_line_calc { + my ($line_num,$diff_context) = @_; + my $ret; + + if ($line_num == 0 && $diff_context == 0) { + return 1; + } + + # Git defaults to three lines of context + my $default_context_lines = 3; + # Three lines on either side, and the line itself = 7 + my $expected_context = ($default_context_lines * 2 + 1); + + # The first three lines + if ($line_num == 1 && $diff_context < $expected_context) { + $ret = $diff_context - $default_context_lines; + } else { + $ret = $line_num + $default_context_lines; + } + + if ($ret < 1) { + $ret = 1; + } + + return $ret; +} + +# Remove + or - at the beginning of the lines +sub strip_leading_indicators { + my $line = shift(); # Array passed in by reference + my $columns_to_remove = shift(); # Don't remove any lines by default + + if ($columns_to_remove == 0) { + return $line; # Nothing to do + } + + $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; + + return $line; +} + +# Count the number of a given char in a string +sub char_count { + my ($needle,$str) = @_; + my $len = length($str); + my $ret = 0; + + for (my $i = 0; $i < $len; $i++) { + my $found = substr($str,$i,1); + + if ($needle eq $found) { $ret++; } + } + + return $ret; +} + +# Remove all ANSI codes from a string +sub bleach_text { + my $str = shift(); + $str =~ s/\e\[\d*(;\d+)*m//mg; + + return $str; +} + +# Remove all trailing and leading spaces +sub trim { + my $s = shift(); + if (!$s) { return ""; } + $s =~ s/^\s*|\s*$//g; + + return $s; +} + +# Print a line of em-dash or line-drawing chars the full width of the screen +sub horizontal_rule { + my $color = $_[0] || ""; + my $width = `tput cols`; + + if (is_windows()) { + $width--; + } + + # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm + #my $dash = "\x{2014}"; + # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm + my $dash; + if ($use_unicode_dash_for_ruler && should_print_unicode()) { + $dash = Encode::encode('UTF-8', "\x{2500}"); + } else { + $dash = "-"; + } + + # Draw the line + my $ret = $color . ($dash x $width) . "\n"; + + return $ret; +} + +sub file_change_string { + my $file_1 = shift(); + my $file_2 = shift(); + + # If they're the same it's a modify + if ($file_1 eq $file_2) { + return "modified: $file_1"; + # If the first is /dev/null it's a new file + } elsif ($file_1 eq "/dev/null") { + return "added: $file_2"; + # If the second is /dev/null it's a deletion + } elsif ($file_2 eq "/dev/null") { + return "deleted: $file_1"; + # If the files aren't the same it's a rename + } elsif ($file_1 ne $file_2) { + my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); + $old = trim($old); + $new = trim($new); + + # highlight_pair resets the colors, but we want it to be the meta color + $old =~ s/(\e0?\[m)/$1$meta_color/g; + $new =~ s/(\e0?\[m)/$1$meta_color/g; + + return "renamed: $old to $new"; + # Something we haven't thought of yet + } else { + return "$file_1 -> $file_2"; + } +} + +# Check to see if STDIN is connected to an interactive terminal +sub has_stdin { + my $i = -t STDIN; + my $ret = int(!$i); + + return $ret; +} + +# We use this instead of Getopt::Long because it's faster and we're not parsing any +# crazy arguments +# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html +sub argv { + my $ret = {}; + + for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + # If the next item does not start with "--" it's the value for this item + if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { + $ret->{$key} = $ARGV[$i + 1]; + # Bareword like --verbose with no options + } else { + $ret->{$key}++; + } + } + } + + # We're looking for a certain item + if ($_[0]) { return $ret->{$_[0]}; } + + return $ret; +} + +# Output the command line usage for d-s-f +sub usage { + my $out = color("white_bold") . version() . color("reset") . "\n"; + + $out .= "Usage: + +git diff --color | diff-so-fancy # Use d-s-f on one diff +git diff --colors # View the commands to set the recommended colors +git diff --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors + +# Configure git to use d-s-f for *all* diff operations +git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; + + return $out; +} + +sub get_default_colors { + my $out = "# Recommended default colors for diff-so-fancy\n"; + $out .= "# --------------------------------------------\n"; + $out .= 'git config --global color.ui true + +git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldHighlight "red bold 52" +git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newHighlight "green bold 22" + +git config --global color.diff.meta "227" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "227 bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" +git config --global color.diff.whitespace "red reverse" +'; + + return $out; +} + +# Output the current version string +sub version { + my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; + $ret .= "Version : $VERSION\n"; + + return $ret; +} + +# Feed the raw git input through diff-highlight to get line level highlights +sub filter_stdin_through_diff_highlight { + my @dh_lines; + + # Have DH put the lines it's modified in an array + local $DiffHighlight::line_cb = sub { push(@dh_lines,@_) }; + + while (my $line = ) { + my $ok = DiffHighlight::handle_line($line); + } + + DiffHighlight::flush(); + + return @dh_lines; +} + +sub is_windows { + if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { + return 1; + } else { + return 0; + } +} + +# Return value is whether this is the first time they've run d-s-f +sub check_first_run { + my $i = get_git_config_hash(); + my $ret = 0; + + # If first-run is not set, or it's set to "true" + my $first_run = boolean(!$i->{'diff-so-fancy'}->{'first-run'} || ($i->{'diff-so-fancy'}->{'first-run'} eq "true")); + # See if they're previously set SOME diff-highlight colors + my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); + + if (!$first_run || $has_dh_colors) { + return 0; + } else { + my $interactive_shell = !has_stdin(); + + # d-s-f run inside of LESS (can't prompt user) + if (!$interactive_shell) { + my $warn = color("yellow_on_red"); + my $bold = color("bold"); + my $blink = color("blink"); + my $reset = color("reset"); + + printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); + printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n\n"); + + return 0; + } + + print "This appears to be the first time you've run diff-so-fancy, would you like to\n"; + print "configure git to use diff-so-fancy for all diff operations and use the\n"; + print "recommended color scheme (y/N)?\n"; + + my $input = ; + $input = uc(trim($input)); + + # Set the default colors and git alias + if ($input eq "Y") { + set_defaults(); + # Just set the first-run flag to false so we don't pop this up again + } else { + my $cmd = 'git config --global diff-so-fancy.first-run false'; + system($cmd); + } + } + + return 1; +} + +sub set_defaults { + my $color_config = get_default_colors(); + my $git_config = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"'; + my $first_cmd = 'git config --global diff-so-fancy.first-run false'; + + my @cmds = split(/\n/,$color_config); + push(@cmds,$git_config); + push(@cmds,$first_cmd); + + # Remove all comments from the commands + foreach my $x (@cmds) { + $x =~ s/#.*//g; + } + + # Remove any empty commands + @cmds = grep($_,@cmds); + + foreach my $cmd (@cmds) { + system($cmd); + my $exit = ($? >> 8); + + if ($exit != 0) { + die("Error running: '$cmd' (error #18941)\n"); + } + } + + return 1; +} + +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red_bold', 'red_on_blue', 'blink', 'italic' +sub color { + my $str = shift(); + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s/$_/$color_map{$_}/g for keys %color_map; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /^(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)$/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd || 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 From fc831379cbf5a3969b9447a54925b5ad06923d00 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 17 Jul 2017 16:40:37 +0200 Subject: [PATCH 195/443] Include more info in first run warning Closes #244 --- diff-so-fancy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index f1aaae3..e859803 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -633,7 +633,8 @@ sub check_first_run { my $reset = color("reset"); printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); - printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n\n"); + printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n"); + printf("If you see this message multiple times, either run 'diff-so-fancy' standalone, or 'git config --global diff-so-fancy.first-run false'.\n\n"); return 0; } From 866e1cd07c2d215a35fc310e521a52f04872ddbd Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 Jul 2017 09:00:41 -0700 Subject: [PATCH 196/443] Clarify some logic so it's more readable --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index fca2654..794954a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -616,7 +616,7 @@ sub check_first_run { my $ret = 0; # If first-run is not set, or it's set to "true" - my $first_run = boolean(!$i->{'diff-so-fancy'}->{'first-run'} || ($i->{'diff-so-fancy'}->{'first-run'} eq "true")); + my $first_run = git_config_boolean($i->{'diff-so-fancy'}->{'first-run'}); # See if they're previously set SOME diff-highlight colors my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); From 3224f531412fd605cfca7b328daeb9b1a5fb2218 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 Jul 2017 09:01:02 -0700 Subject: [PATCH 197/443] Make sure we have a key AND a value --- diff-so-fancy | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 794954a..a88b545 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -367,13 +367,17 @@ sub get_git_config_hash { my %hash; foreach my $line (@$out) { my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - my @path = split(/\./,$key); - my $last = pop @path; - my $p = \%hash; - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; + if ($key && $value) { + $value =~ s/\s+$//; + my @path = split(/\./,$key); + my $last = pop @path; + my $p = \%hash; + + # Build the tree for each section + $p = $p->{$_} ||= {} for @path; + $p->{$last} = $value; + } } return \%hash; From ef5d8f3e46fc34e194971afd2428ff470d8243d1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 Jul 2017 09:15:30 -0700 Subject: [PATCH 198/443] Make the test colors match the recommended colors --- test/test_helper/util.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_helper/util.bash b/test/test_helper/util.bash index 26a1a03..440d3bd 100644 --- a/test/test_helper/util.bash +++ b/test/test_helper/util.bash @@ -13,9 +13,9 @@ set_env() { # applying colors so ANSI color values will match # FIXME: not everyone will have these set, so we need to test for that. -git config color.diff.meta "yellow bold" -git config color.diff.commit "green bold" +git config color.diff.meta "227" git config color.diff.frag "magenta bold" +git config color.diff.commit "227 bold" git config color.diff.old "red bold" git config color.diff.new "green bold" git config color.diff.whitespace "red reverse" From 5964b516f09e42c32546ece9236b0666555d18d9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 Jul 2017 10:26:01 -0700 Subject: [PATCH 199/443] Use a flat config instead of a tree. Fixes #245 --- diff-so-fancy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 2210148..c709dcf 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -616,13 +616,13 @@ sub is_windows { # Return value is whether this is the first time they've run d-s-f sub check_first_run { - my $i = get_git_config_hash(); + my $i = git_config(); my $ret = 0; # If first-run is not set, or it's set to "true" my $first_run = git_config_boolean($i->{'diff-so-fancy'}->{'first-run'}); # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); + my $has_dh_colors = boolean($i->{'color.diff-highlight.oldnormal'} || $i->{'color.diff-highlight.newnormal'}); if (!$first_run || $has_dh_colors) { return 0; From cce755af1aeeaaaeeca0393200a673323188ad6b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 Jul 2017 11:29:14 -0700 Subject: [PATCH 200/443] Properly load the config for the unit tests --- diff-so-fancy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index c709dcf..57a50be 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -616,13 +616,12 @@ sub is_windows { # Return value is whether this is the first time they've run d-s-f sub check_first_run { - my $i = git_config(); my $ret = 0; # If first-run is not set, or it's set to "true" - my $first_run = git_config_boolean($i->{'diff-so-fancy'}->{'first-run'}); + my $first_run = git_config_boolean('diff-so-fancy.first-run'); # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = boolean($i->{'color.diff-highlight.oldnormal'} || $i->{'color.diff-highlight.newnormal'}); + my $has_dh_colors = git_config_boolean($i->{'color.diff-highlight.oldnormal'}) || git_config_boolean($i->{'color.diff-highlight.newnormal'}); if (!$first_run || $has_dh_colors) { return 0; From 748393f6524cc42f8ba1f8ed30253372771bd1a3 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 18 Jul 2017 01:19:44 +0200 Subject: [PATCH 201/443] simplify message --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index e859803..94a64a5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -634,7 +634,7 @@ sub check_first_run { printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n"); - printf("If you see this message multiple times, either run 'diff-so-fancy' standalone, or 'git config --global diff-so-fancy.first-run false'.\n\n"); + printf("To silence this error run: 'git config --global diff-so-fancy.first-run false'.\n\n"); return 0; } From abf8c626a90f588db0e922b036e7a5cd756eaba3 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 Jul 2017 13:42:07 -0700 Subject: [PATCH 202/443] Bump the version so it's clear we're on 1.1.1 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 57a50be..741593c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.1.0"; +my $VERSION = "1.1.1"; ################################################################################# From afe977f9699d1ea92515ef109ae1a4516cd3c3ba Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 Jul 2017 14:28:18 -0700 Subject: [PATCH 203/443] Logic fix for reading the config --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 741593c..d6d7b34 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -621,7 +621,7 @@ sub check_first_run { # If first-run is not set, or it's set to "true" my $first_run = git_config_boolean('diff-so-fancy.first-run'); # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = git_config_boolean($i->{'color.diff-highlight.oldnormal'}) || git_config_boolean($i->{'color.diff-highlight.newnormal'}); + my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); if (!$first_run || $has_dh_colors) { return 0; From 070011f6344413769fb434cdf9180f54515dff4b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 09:14:59 -0700 Subject: [PATCH 204/443] Starting work on 1.2.0 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 8ef2b98..04ccbe4 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.1.1"; +my $VERSION = "1.2.0"; ################################################################################# From 97eb8f995f4aad589d697db2182dc4e288857c3c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 09:18:50 -0700 Subject: [PATCH 205/443] Move to DiffHighlight on the fly instead of all at once (#251) --- diff-so-fancy | 323 ++++++++++++++++++++++++++------------------------ 1 file changed, 171 insertions(+), 152 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 04ccbe4..66da172 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -22,6 +22,18 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $dim_magenta = "\e[38;5;146m"; +my $reset_color = "\e[0m"; +my $bold = "\e[1m"; +my $meta_color = ""; + +my ($file_1,$file_2); +my $last_file_seen = ""; +my $i = 0; +my $in_hunk = 0; +my $columns_to_remove = 0; + # We only process ARGV if we don't have STDIN my @input; if (!$has_stdin) { @@ -48,181 +60,188 @@ if (!$has_stdin) { # Check to see if were using default settings check_first_run(); - @input = filter_stdin_through_diff_highlight(); + my @lines; + local $DiffHighlight::line_cb = sub { + push(@lines,@_); + + # Buffer 100 lines before we try and output anything + if (@lines > 100) { + do_dsf_stuff(\@lines); + @lines = (); + } + }; + + while (my $line = ) { + my $ok = DiffHighlight::handle_line($line); + } + + DiffHighlight::flush(); + do_dsf_stuff(\@lines); } ################################################################################# -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my $dim_magenta = "\e[38;5;146m"; -my $reset_color = "\e[0m"; -my $bold = "\e[1m"; -my $meta_color = ""; +sub do_dsf_stuff { + my $input = shift(); + while (my $line = shift(@$input)) { + ###################################################### + # Pre-process the line before we do any other markup # + ###################################################### -my ($file_1,$file_2); -my $last_file_seen = ""; -my $i = 0; -my $in_hunk = 0; -my $columns_to_remove = 0; + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; + } -while (my $line = shift(@input)) { - ###################################################### - # Pre-process the line before we do any other markup # - ###################################################### + ###################### + # End pre-processing # + ###################### - # If the first line of the input is a blank line, skip that - if ($i == 0 && $line =~ /^\s*$/) { - next; - } + ####################################################################### - ###################### - # End pre-processing # - ###################### - - ####################################################################### - - #################################################################### - # Look for git index and replace it horizontal line (header later) # - #################################################################### - if ($line =~ /^${ansi_color_regex}index /) { - # Print the line color and then the actual line - $meta_color = $1; - print horizontal_rule($meta_color); - ######################### - # Look for the filename # - ######################### - } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { - $last_file_seen = $5; - $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). - $in_hunk = 0; - ######################################## - # Find the first file: --- a/README.md # - ######################################## - } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_1 = $file_dir . $5; - } else { - $file_1 = $5; - } + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $meta_color = $1; + print horizontal_rule($meta_color); + ######################### + # Look for the filename # + ######################### + } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + $last_file_seen = $5; + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } - # Find the second file on the next line: +++ b/README.md - my $next = shift(@input); - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; - if ($1) { - print $1; # Print out whatever color we're using - } - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_2 = $file_dir . $5; - } else { - $file_2 = $5; - } + # Find the second file on the next line: +++ b/README.md + my $next = shift(@$input); + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } - if ($file_2 ne "/dev/null") { - $last_file_seen = $file_2; - } + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } - print file_change_string($file_1,$file_2) . "\n"; - - # Print out the bottom horizontal line of the header - print horizontal_rule($meta_color); - ######################################## - # Check for "@@ -3,41 +3,63 @@" syntax # - ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - my $hunk_header = $4; - my $remain = bleach_text($5); - - # The number of colums to remove (1 or 2) is based on how many commas in the hunk header - $columns_to_remove = (char_count(",",$hunk_header)) - 1; - # On single line removes there is NO comma in the hunk so we force one - if ($columns_to_remove <= 0) { - $columns_to_remove = 1; - } + print file_change_string($file_1,$file_2) . "\n"; - if ($1) { - print $1; # Print out whatever color we're using - } + # Print out the bottom horizontal line of the header + print horizontal_rule($meta_color); + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + if ($columns_to_remove <= 0) { + $columns_to_remove = 1; + } - my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - $last_file_seen = basename($last_file_seen); - - # Figure out the start line - my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; - ################################### - # Remove any new file permissions # - ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { - # Don't print the line (i.e. remove it from the output); - ###################################### - # Remove any delete file permissions # - ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { - # Don't print the line (i.e. remove it from the output); - ################################ - # Look for binary file changes # - ################################ - } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - my $change = file_change_string($2,$4); - print "$meta_color$change (binary)\n"; - print horizontal_rule($meta_color); - ##################################################### - # Check if we're changing the permissions of a file # - ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { - my ($old_mode) = $4; - my $next = shift(@input); - - if ($1) { - print $1; # Print out whatever color we're using - } + if ($1) { + print $1; # Print out whatever color we're using + } - my ($new_mode) = $next =~ m/new mode (\d+)/; - print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); + $last_file_seen = basename($last_file_seen); + + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + # Don't print the line (i.e. remove it from the output); + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + my $change = file_change_string($2,$4); + print "$meta_color$change (binary)\n"; + print horizontal_rule($meta_color); + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; + my $next = shift(@$input); + + if ($1) { + print $1; # Print out whatever color we're using + } - ############### - # File rename # - ############### - } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { - my $next = shift(@input); - my ($file1) = $next =~ /rename from (.+)/; + my ($new_mode) = $next =~ m/new mode (\d+)/; + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; - $next = shift(@input); - my ($file2) = $next =~ /rename to (.+)/; + ############### + # File rename # + ############### + } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { + my $next = shift(@$input); + my ($file1) = $next =~ /rename from (.+)/; - if ($file1 && $file2) { - # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + $next = shift(@$input); + my ($file2) = $next =~ /rename to (.+)/; - my $change = file_change_string($file1,$file2); + if ($file1 && $file2) { + # We may not have extracted this yet, so we pull from the config if not + $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); - print horizontal_rule($meta_color); - print $meta_color . $change . "\n"; - print horizontal_rule($meta_color); - } + my $change = file_change_string($file1,$file2); - $i += 3; # We've consumed three lines - next; - ##################################### - # Just a regular line, print it out # - ##################################### - } else { - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } + print horizontal_rule($meta_color); + print $meta_color . $change . "\n"; + print horizontal_rule($meta_color); + } + + $i += 3; # We've consumed three lines + next; + ##################################### + # Just a regular line, print it out # + ##################################### + } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + print $line; } - print $line; - } - $i++; + $i++; + } } ###################################################################################################### From 7371103ab6476094e9632bc27212a8996884e6ee Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 09:37:34 -0700 Subject: [PATCH 206/443] Tests for #252 --- test/diff-so-fancy.bats | 12 ++++++++++++ test/fixtures/add_empty_file.diff | 9 +++++++++ test/fixtures/remove_empty_file.diff | 9 +++++++++ 3 files changed, 30 insertions(+) create mode 100644 test/fixtures/add_empty_file.diff create mode 100644 test/fixtures/remove_empty_file.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index cbe9e47..e97e098 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -95,3 +95,15 @@ if begin" run printf "%s" "$output" assert_line --index 5 "after" } + +@test "Empty file add" { + output=$( load_fixture "add_empty_file" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 5 --partial "added: empty_file.txt" +} + +@test "Empty file delete" { + output=$( load_fixture "remove_empty_file" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 5 --partial "deleted: empty_file.txt" +} diff --git a/test/fixtures/add_empty_file.diff b/test/fixtures/add_empty_file.diff new file mode 100644 index 0000000..8ecd427 --- /dev/null +++ b/test/fixtures/add_empty_file.diff @@ -0,0 +1,9 @@ +commit ef0e63dbd7e8df6cde4ee5599ad65db7820888ef +Author: Scott Baker +Date: Wed Jul 19 08:00:11 2017 -0700 + + Add empty file + +diff --git a/empty_file.txt b/empty_file.txt +new file mode 100644 +index 0000000..e69de29 diff --git a/test/fixtures/remove_empty_file.diff b/test/fixtures/remove_empty_file.diff new file mode 100644 index 0000000..cba8ff4 --- /dev/null +++ b/test/fixtures/remove_empty_file.diff @@ -0,0 +1,9 @@ +commit 0148734e753690a9207ebcb8cd117f343e230dec +Author: Scott Baker +Date: Wed Jul 19 08:12:45 2017 -0700 + + remvo + +diff --git a/empty_file.txt b/empty_file.txt +deleted file mode 100644 +index e69de29..0000000 From 58571989bac284a3b467aa3a6d7ae82252125616 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 09:37:55 -0700 Subject: [PATCH 207/443] Fixes #252 --- diff-so-fancy | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 66da172..3b9d4de 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -30,12 +30,12 @@ my $meta_color = ""; my ($file_1,$file_2); my $last_file_seen = ""; +my $last_file_mode = ""; my $i = 0; my $in_hunk = 0; my $columns_to_remove = 0; # We only process ARGV if we don't have STDIN -my @input; if (!$has_stdin) { my $args = argv(); @@ -104,8 +104,31 @@ sub do_dsf_stuff { #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line - $meta_color = $1; + $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); print horizontal_rule($meta_color); + + # Get the next line without incrementing counter while loop + my $next = $input->[0] || ""; + my ($file_1,$file_2); + + # The line immediately after the "index" line should be the --- file line + # If it's not it's an empty file add/delete + if ($next !~ /^$ansi_color_regex---/) { + + # We fake out the file names since it's a raw add/delete + if ($last_file_mode eq "add") { + $file_1 = "/dev/null"; + $file_2 = $last_file_seen; + } elsif ($last_file_mode eq "delete") { + $file_1 = $last_file_seen; + $file_2 = "/dev/null"; + } + } + + if ($file_1 && $file_2) { + print $meta_color . file_change_string($file_1,$file_2) . "\n"; + print horizontal_rule($meta_color); + } ######################### # Look for the filename # ######################### @@ -175,11 +198,13 @@ sub do_dsf_stuff { ################################### } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { # Don't print the line (i.e. remove it from the output); + $last_file_mode = "add"; ###################################### # Remove any delete file permissions # ###################################### } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { # Don't print the line (i.e. remove it from the output); + $last_file_mode = "delete"; ################################ # Look for binary file changes # ################################ From a7f8ccde89d29836d3a40d3bbcd389b3695f8a44 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 09:39:48 -0700 Subject: [PATCH 208/443] Update the "binary" --- third_party/build_fatpack/diff-so-fancy | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 232c547..72a3a72 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -289,7 +289,7 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.1.0"; +my $VERSION = "1.1.1"; ################################################################################# @@ -656,13 +656,17 @@ sub get_git_config_hash { my %hash; foreach my $line (@$out) { my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - my @path = split(/\./,$key); - my $last = pop @path; - my $p = \%hash; - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; + if ($key && $value) { + $value =~ s/\s+$//; + my @path = split(/\./,$key); + my $last = pop @path; + my $p = \%hash; + + # Build the tree for each section + $p = $p->{$_} ||= {} for @path; + $p->{$last} = $value; + } } return \%hash; @@ -901,13 +905,12 @@ sub is_windows { # Return value is whether this is the first time they've run d-s-f sub check_first_run { - my $i = get_git_config_hash(); my $ret = 0; # If first-run is not set, or it's set to "true" - my $first_run = boolean(!$i->{'diff-so-fancy'}->{'first-run'} || ($i->{'diff-so-fancy'}->{'first-run'} eq "true")); + my $first_run = git_config_boolean('diff-so-fancy.first-run'); # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); + my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); if (!$first_run || $has_dh_colors) { return 0; @@ -922,7 +925,8 @@ sub check_first_run { my $reset = color("reset"); printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); - printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n\n"); + printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n"); + printf("To silence this error run: 'git config --global diff-so-fancy.first-run false'.\n\n"); return 0; } @@ -1003,4 +1007,4 @@ sub color { return $ret; } -# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 +# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 407841983b6f66066ef48e69681c93f9e229f12f Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 14:30:44 -0700 Subject: [PATCH 209/443] Make sure we have enough data for d-s-f to do lookaheads --- diff-so-fancy | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3b9d4de..88e842b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -64,8 +64,17 @@ if (!$has_stdin) { local $DiffHighlight::line_cb = sub { push(@lines,@_); - # Buffer 100 lines before we try and output anything - if (@lines > 100) { + my $last_line = $lines[-1]; + + # Buffer X lines before we try and output anything + # Also make sure we're sending enough data to d-s-f to do it's magic. + # Certain things require a look-ahead line or two to function so + # we make sure we don't break on those sections prematurely + if (@lines > 24 && ($last_line !~ /---|old mode|similarity index/)) { + #print "START -------------------------------------------------\n"; + #print join("",@lines); + #print "END ---------------------------------------------------\n"; + do_dsf_stuff(\@lines); @lines = (); } From 32b3e3d4f4b5add80cc223f53b8d5f2814a3410c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 16:26:38 -0700 Subject: [PATCH 210/443] Clarify the regexp when to get more lines --- diff-so-fancy | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 88e842b..6008361 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -70,11 +70,7 @@ if (!$has_stdin) { # Also make sure we're sending enough data to d-s-f to do it's magic. # Certain things require a look-ahead line or two to function so # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /---|old mode|similarity index/)) { - #print "START -------------------------------------------------\n"; - #print join("",@lines); - #print "END ---------------------------------------------------\n"; - + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|old mode|similarity index)/)) { do_dsf_stuff(\@lines); @lines = (); } @@ -92,6 +88,11 @@ if (!$has_stdin) { sub do_dsf_stuff { my $input = shift(); + + #print STDERR "START -------------------------------------------------\n"; + #print STDERR join("",@$input); + #print STDERR "END ---------------------------------------------------\n"; + while (my $line = shift(@$input)) { ###################################################### # Pre-process the line before we do any other markup # From cd2c7c2482ee9d95d942b7bbd565a3eae3b4ce70 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 20 Jul 2017 10:26:57 +1000 Subject: [PATCH 211/443] 1.1.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b0792a..1b60a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "diff-so-fancy", - "version": "1.0.0", + "version": "1.1.1", "lockfileVersion": 1 } diff --git a/package.json b/package.json index f4f09fb..23fcf0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.0.0", + "version": "1.1.1", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From 790afa74f1329f7a9545c7f84e07fa956dcdcb2c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 21:25:13 -0700 Subject: [PATCH 212/443] Update tests for color coded file add/deletes --- test/diff-so-fancy.bats | 4 ++-- test/header_clean.bats | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index e97e098..a58a496 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -99,11 +99,11 @@ if begin" @test "Empty file add" { output=$( load_fixture "add_empty_file" | $diff_so_fancy ) run printf "%s" "$output" - assert_line --index 5 --partial "added: empty_file.txt" + assert_line --index 5 --regexp "added:.*empty_file.txt" } @test "Empty file delete" { output=$( load_fixture "remove_empty_file" | $diff_so_fancy ) run printf "%s" "$output" - assert_line --index 5 --partial "deleted: empty_file.txt" + assert_line --index 5 --regexp "deleted:.*empty_file.txt" } diff --git a/test/header_clean.bats b/test/header_clean.bats index 9ac8369..1dca5e9 100644 --- a/test/header_clean.bats +++ b/test/header_clean.bats @@ -16,7 +16,7 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) } @test "header_clean 'added:'" { - assert_output --partial 'added: hello.txt' + assert_output --regexp 'added:.*hello.txt' } @test "header_clean 'modified:'" { @@ -24,7 +24,7 @@ output=$( load_fixture "file-moves" | $diff_so_fancy ) } @test "header_clean 'deleted:'" { - assert_output --partial 'deleted: circle.yml' + assert_output --regexp 'deleted:.*circle.yml' } @test "header_clean permission changes" { From 5c96bddd5b53df4171cb3ebc1a2e1e16f0611806 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 21:25:59 -0700 Subject: [PATCH 213/443] Colorize file add/deletes (#253) --- diff-so-fancy | 6 ++++-- lib/DiffHighlight.pm | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 6008361..ebac1d4 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -545,10 +545,12 @@ sub file_change_string { return "modified: $file_1"; # If the first is /dev/null it's a new file } elsif ($file_1 eq "/dev/null") { - return "added: $file_2"; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + return "added: $add_color$file_2$reset_color"; # If the second is /dev/null it's a deletion } elsif ($file_2 eq "/dev/null") { - return "deleted: $file_1"; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + return "deleted: $del_color$file_1$reset_color"; # If the files aren't the same it's a rename } elsif ($file_1 ne $file_2) { my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index c8bf213..df9ed32 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -7,12 +7,12 @@ use Encode; # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. -my @OLD_HIGHLIGHT = ( +our @OLD_HIGHLIGHT = ( color_config('color.diff-highlight.oldnormal'), color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), "\x1b[27m", ); -my @NEW_HIGHLIGHT = ( +our@NEW_HIGHLIGHT = ( color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), $OLD_HIGHLIGHT[2], From 8262d8127855c95107bc02649f395f2772950845 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 19 Jul 2017 21:50:48 -0700 Subject: [PATCH 214/443] Clarify the color suggestions in README --- readme.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index 93a23af..2f49b44 100644 --- a/readme.md +++ b/readme.md @@ -24,20 +24,20 @@ git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ### Improved colors for the highlighted bits -The default colors are not the best so we recommend the following color settings as a base. +The default Git colors are not optimal. We suggest the following colors instead. ```shell git config --global color.ui true -git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldNormal "red bold" git config --global color.diff-highlight.oldHighlight "red bold 52" -git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "227" -git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "227 bold" -git config --global color.diff.old "red bold" -git config --global color.diff.new "green bold" +git config --global color.diff.meta "227" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "227 bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" ``` From 9e63a7a34ed3fd824af455660c5e94e3aca75956 Mon Sep 17 00:00:00 2001 From: ricktang Date: Mon, 31 Jul 2017 11:19:23 +0100 Subject: [PATCH 215/443] fix #254: Displays wrong commands on initial screen in usage --- diff-so-fancy | 4 ++-- third_party/build_fatpack/diff-so-fancy | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 57a50be..b14deb5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -552,8 +552,8 @@ sub usage { $out .= "Usage: git diff --color | diff-so-fancy # Use d-s-f on one diff -git diff --colors # View the commands to set the recommended colors -git diff --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 232c547..070ad7b 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -837,8 +837,8 @@ sub usage { $out .= "Usage: git diff --color | diff-so-fancy # Use d-s-f on one diff -git diff --colors # View the commands to set the recommended colors -git diff --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; From 7d6549e29abcfc5408bd3a2c037ab137e3252fe2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 31 Jul 2017 08:58:38 -0700 Subject: [PATCH 216/443] Update readme to reflect pull requests should target `next` branch --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2f49b44..9e3abab 100644 --- a/readme.md +++ b/readme.md @@ -73,7 +73,7 @@ git config --bool --global diff-so-fancy.useUnicodeRuler false ## Contributing -Pull requests quite welcome, along with any feedback or ideas. +Pull requests are quite welcome, and should target the `next` branch. We are also looking for any feedback or ideas on how to make diff-so-fancy even better. ### Other documentation From c5fc60c30fa33286d0e62c245ce760bc4a7def3d Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 1 Aug 2017 09:54:17 +1000 Subject: [PATCH 217/443] submit PR against the next branch --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 93a23af..9e365e2 100644 --- a/readme.md +++ b/readme.md @@ -73,7 +73,7 @@ git config --bool --global diff-so-fancy.useUnicodeRuler false ## Contributing -Pull requests quite welcome, along with any feedback or ideas. +Any feedback or ideas are welcome. Please submit your pull requests against the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). ### Other documentation From f29cb71fffa9d11de94311ff1555dc58ddcd46a6 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 2 Aug 2017 17:15:48 +1000 Subject: [PATCH 218/443] wording copied from 7d6549e29abcfc5408bd3a2c037ab137e3252fe2 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9e365e2..33e0694 100644 --- a/readme.md +++ b/readme.md @@ -73,7 +73,7 @@ git config --bool --global diff-so-fancy.useUnicodeRuler false ## Contributing -Any feedback or ideas are welcome. Please submit your pull requests against the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). +Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make diff-so-fancy even better. ### Other documentation From 21244f67107b51da4dc7e3578d5d72b5057ca02a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Aug 2017 08:10:47 -0700 Subject: [PATCH 219/443] Make sure we explicitly 'use Encode' to fix part of #260 --- diff-so-fancy | 1 + 1 file changed, 1 insertion(+) diff --git a/diff-so-fancy b/diff-so-fancy index 46ce237..bf68644 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -6,6 +6,7 @@ my $VERSION = "1.2.0"; use Cwd qw(abs_path); # For realpath() use File::Basename; # for dirname +use Encode; # For handling UTF8 stuff use lib dirname(abs_path($0)) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; From 31737b5b161c01735ee0edd95048c544117cee11 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Aug 2017 14:10:14 -0700 Subject: [PATCH 220/443] Test for #258 --- test/diff-so-fancy.bats | 6 ++++++ test/fixtures/move_with_content_change.diff | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 test/fixtures/move_with_content_change.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index a58a496..a05b9a5 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -107,3 +107,9 @@ if begin" run printf "%s" "$output" assert_line --index 5 --regexp "deleted:.*empty_file.txt" } + +@test "Move with content change" { + output=$( load_fixture "move_with_content_change" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 1 --regexp "renamed:" +} diff --git a/test/fixtures/move_with_content_change.diff b/test/fixtures/move_with_content_change.diff new file mode 100644 index 0000000..2ce2a18 --- /dev/null +++ b/test/fixtures/move_with_content_change.diff @@ -0,0 +1,13 @@ +diff --git a/a.txt b/b.txt +similarity index 84% +rename from a.txt +rename to b.txt +index 6674929..1c877ab 100644 +--- a/a.txt ++++ b/b.txt +@@ -2,4 +2,4 @@ height: '10px', + width: '100%', + display: 'block', + position: 'absolute', +-bottom: '0', ++bottom: '0' From d1862862ecf227522763eefcf06215f58c4503f8 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Aug 2017 14:11:28 -0700 Subject: [PATCH 221/443] Fix for #258 --- diff-so-fancy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index bf68644..84ac06a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -260,6 +260,10 @@ sub do_dsf_stuff { $i += 3; # We've consumed three lines next; + } elsif ($line =~ /^${ansi_color_regex}similarity index/) { + shift(@$input); + shift(@$input); + next; ##################################### # Just a regular line, print it out # ##################################### From 49612cd4940f32cf70b307ab09337113e14b421d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Aug 2017 15:23:33 -0700 Subject: [PATCH 222/443] Change how/where the logic for file renames is --- diff-so-fancy | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 84ac06a..186388b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -240,7 +240,16 @@ sub do_dsf_stuff { ############### # File rename # ############### - } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { + } elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) { + my $simil = $4; + + # If it's a move with content change we ignore this and the next two lines + if ($simil != 100) { + shift(@$input); + shift(@$input); + next; + } + my $next = shift(@$input); my ($file1) = $next =~ /rename from (.+)/; @@ -260,10 +269,6 @@ sub do_dsf_stuff { $i += 3; # We've consumed three lines next; - } elsif ($line =~ /^${ansi_color_regex}similarity index/) { - shift(@$input); - shift(@$input); - next; ##################################### # Just a regular line, print it out # ##################################### From c8fe79003a46ffbac21b416b52589918c755b0bf Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Aug 2017 16:33:36 -0700 Subject: [PATCH 223/443] Don't split on renames... fixes #262 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 186388b..356836d 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -71,7 +71,7 @@ if (!$has_stdin) { # Also make sure we're sending enough data to d-s-f to do it's magic. # Certain things require a look-ahead line or two to function so # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|old mode|similarity index)/)) { + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|old mode|similarity index|rename (from|to))/)) { do_dsf_stuff(\@lines); @lines = (); } From 2f3d7bb909d8575e72ca0a1a3e845db27a9cad43 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 15 Aug 2017 16:22:26 -0700 Subject: [PATCH 224/443] Don't break on "index" lines we need to see the next line (fixes #261) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 356836d..081e3f9 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -71,7 +71,7 @@ if (!$has_stdin) { # Also make sure we're sending enough data to d-s-f to do it's magic. # Certain things require a look-ahead line or two to function so # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|old mode|similarity index|rename (from|to))/)) { + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { do_dsf_stuff(\@lines); @lines = (); } From 96b6ca05777123865986aec98d2d7d03a73ec7ed Mon Sep 17 00:00:00 2001 From: Itzik Ephraim Date: Thu, 17 Aug 2017 10:00:24 +0300 Subject: [PATCH 225/443] update-deps: Fix DiffHighlight's URL and refactor Fetch `DiffHighlight.pm` instead of `diff-highlight` --- update-deps.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/update-deps.sh b/update-deps.sh index dd052b3..775e92e 100755 --- a/update-deps.sh +++ b/update-deps.sh @@ -1,4 +1,11 @@ #!/bin/bash -curl -Lo "third_party/diff-highlight/diff-highlight" "https://github.com/git/git/raw/master/contrib/diff-highlight/diff-highlight" -curl -Lo "third_party/diff-highlight/README" "https://github.com/git/git/raw/master/contrib/diff-highlight/README" +DIFFHIGHLIGHT_RAW_URL_BASE="https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight" +DIFFHIGHLIGHT_FILES=( "DiffHighlight.pm" "README" ) + +for file in "${DIFFHIGHLIGHT_FILES[@]}"; +do + url="$DIFFHIGHLIGHT_RAW_URL_BASE/$file" + echo "$url" + curl -#Lo "lib/$file" "$url" +done From c118c48d1bcf6e5c2abca0832bf53d591545d667 Mon Sep 17 00:00:00 2001 From: stanislavromanov Date: Fri, 29 Sep 2017 23:54:23 +0200 Subject: [PATCH 226/443] fix path resolution on windows for UNIX emulated perl --- diff-so-fancy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 081e3f9..dcf4332 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,10 +4,10 @@ my $VERSION = "1.2.0"; ################################################################################# -use Cwd qw(abs_path); # For realpath() -use File::Basename; # for dirname -use Encode; # For handling UTF8 stuff -use lib dirname(abs_path($0)) . "/lib"; # Add the local lib/ to @INC +use File::Spec; # For catdir +use File::Basename; # For dirname +use Encode; # For handling UTF8 stuff +use lib dirname(File::Spec->catdir($0)) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; use strict; From a78644d642180df5c873ffad71702a3f9ea6bc14 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 5 Oct 2017 13:14:01 -0700 Subject: [PATCH 227/443] Make diff-so-fancy as a symlink work --- diff-so-fancy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index dcf4332..930087a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,10 +4,11 @@ my $VERSION = "1.2.0"; ################################################################################# -use File::Spec; # For catdir -use File::Basename; # For dirname -use Encode; # For handling UTF8 stuff -use lib dirname(File::Spec->catdir($0)) . "/lib"; # Add the local lib/ to @INC +use File::Spec; # For catdir +use File::Basename; # For dirname +use Encode; # For handling UTF8 stuff +use Cwd qw(abs_path); # For realpath() +use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; use strict; From 326dfeae866f38ecffa67f36f33a9fb9035fdb91 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 12 Oct 2017 21:54:58 -0700 Subject: [PATCH 228/443] Fix #269 binary file deletes on last line of diff --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 930087a..e5b1ef0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -125,7 +125,7 @@ sub do_dsf_stuff { # The line immediately after the "index" line should be the --- file line # If it's not it's an empty file add/delete - if ($next !~ /^$ansi_color_regex---/) { + if ($next !~ /^$ansi_color_regex(---|Binary files)/) { # We fake out the file names since it's a raw add/delete if ($last_file_mode eq "add") { From e8be2f335f7f2c20d60f562951093e97abbfaff3 Mon Sep 17 00:00:00 2001 From: Matt <32581276+mehandes@users.noreply.github.com> Date: Sat, 14 Oct 2017 19:48:52 +0200 Subject: [PATCH 229/443] Added additional way of obtaining the package added Nix package manager option --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 33e0694..4544cc4 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path -`diff-so-fancy` is also available from NPM, brew, and as a package on Arch Linux. +`diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. ## Usage From 287311642dc198d399d0a665991e0ecb4fc55543 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 27 Dec 2017 15:14:53 -0800 Subject: [PATCH 230/443] Use "yellow" instead of ANSI 227. Addresses #249 --- diff-so-fancy | 8 ++++---- readme.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e5b1ef0..925bf26 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -116,7 +116,7 @@ sub do_dsf_stuff { #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line - $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); print horizontal_rule($meta_color); # Get the next line without incrementing counter while loop @@ -259,7 +259,7 @@ sub do_dsf_stuff { if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); my $change = file_change_string($file1,$file2); @@ -638,9 +638,9 @@ git config --global color.diff-highlight.oldHighlight "red bold 52" git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "227" +git config --global color.diff.meta "yellow" git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "227 bold" +git config --global color.diff.commit "yellow bold" git config --global color.diff.old "red bold" git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" diff --git a/readme.md b/readme.md index 9e3abab..864c70e 100644 --- a/readme.md +++ b/readme.md @@ -33,9 +33,9 @@ git config --global color.diff-highlight.oldHighlight "red bold 52" git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "227" +git config --global color.diff.meta "yellow" git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "227 bold" +git config --global color.diff.commit "yellow bold" git config --global color.diff.old "red bold" git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" From 1e20fbdd952826404e623b187e0ddb776b0185b0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 27 Dec 2017 15:16:48 -0800 Subject: [PATCH 231/443] Don't change users colors, just offer the suggestion --- diff-so-fancy | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 925bf26..7bf5049 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -690,40 +690,22 @@ sub check_first_run { # See if they're previously set SOME diff-highlight colors my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); + #$first_run = 1; $has_dh_colors = 0; + if (!$first_run || $has_dh_colors) { return 0; } else { - my $interactive_shell = !has_stdin(); - - # d-s-f run inside of LESS (can't prompt user) - if (!$interactive_shell) { - my $warn = color("yellow_on_red"); - my $bold = color("bold"); - my $blink = color("blink"); - my $reset = color("reset"); - - printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); - printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n"); - printf("To silence this error run: 'git config --global diff-so-fancy.first-run false'.\n\n"); - - return 0; - } + print "This appears to be the first time you've run diff-so-fancy, please note\n"; + print "that the default git colors are not ideal. Diff-so-fancy recommends the\n"; + print "following colors.\n\n"; - print "This appears to be the first time you've run diff-so-fancy, would you like to\n"; - print "configure git to use diff-so-fancy for all diff operations and use the\n"; - print "recommended color scheme (y/N)?\n"; + print get_default_colors(); - my $input = ; - $input = uc(trim($input)); + # Set the first run flag to false + my $cmd = 'git config --global diff-so-fancy.first-run false'; + system($cmd); - # Set the default colors and git alias - if ($input eq "Y") { - set_defaults(); - # Just set the first-run flag to false so we don't pop this up again - } else { - my $cmd = 'git config --global diff-so-fancy.first-run false'; - system($cmd); - } + exit; } return 1; From ea8af6f84d02c2323cfa9289e8b4f0124cf008b6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 27 Dec 2017 15:18:43 -0800 Subject: [PATCH 232/443] Update screenshot in README --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 864c70e..bf44a43 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` -![diff-highlight vs diff-so-fancy](https://cloud.githubusercontent.com/assets/39191/13622719/7cc7c54c-e555-11e5-86c4-7045d91af041.png) +![diff-highlight vs diff-so-fancy](https://user-images.githubusercontent.com/3429760/32387617-44c873da-c082-11e7-829c-6160b853adcb.png) ## Install From 5fade278633e3d8f0daad09fa18cb308f90b19df Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 27 Dec 2017 15:19:21 -0800 Subject: [PATCH 233/443] Rename the README so it's consistent with other projects --- readme.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.md => README.md (100%) diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md From c18fe128682c24a72b3319b06166cd5c0841acd9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 27 Dec 2017 15:21:52 -0800 Subject: [PATCH 234/443] Change alignment so it looks nicer --- diff-so-fancy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 7bf5049..be81d6e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -633,16 +633,16 @@ sub get_default_colors { $out .= "# --------------------------------------------\n"; $out .= 'git config --global color.ui true -git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldNormal "red bold" git config --global color.diff-highlight.oldHighlight "red bold 52" -git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "yellow" -git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "yellow bold" -git config --global color.diff.old "red bold" -git config --global color.diff.new "green bold" +git config --global color.diff.meta "yellow" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "yellow bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" '; From b8e631501e33e7bdc6501ab618ac8a795e4f7781 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 28 Dec 2017 09:06:23 -0800 Subject: [PATCH 235/443] Bump the fatpack script to 1.2.0 --- third_party/build_fatpack/diff-so-fancy | 461 +++++++++++++----------- 1 file changed, 257 insertions(+), 204 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 070ad7b..8e99f1e 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -15,12 +15,12 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. - my @OLD_HIGHLIGHT = ( + our @OLD_HIGHLIGHT = ( color_config('color.diff-highlight.oldnormal'), color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), "\x1b[27m", ); - my @NEW_HIGHLIGHT = ( + our@NEW_HIGHLIGHT = ( color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), $OLD_HIGHLIGHT[2], @@ -289,13 +289,15 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.1.0"; +my $VERSION = "1.2.0"; ################################################################################# -use Cwd qw(abs_path); # For realpath() -use File::Basename; # for dirname -use lib dirname(abs_path($0)) . "/lib"; # Add the local lib/ to @INC +use File::Spec; # For catdir +use File::Basename; # For dirname +use Encode; # For handling UTF8 stuff +use Cwd qw(abs_path); # For realpath() +use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; use strict; @@ -311,8 +313,20 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $dim_magenta = "\e[38;5;146m"; +my $reset_color = "\e[0m"; +my $bold = "\e[1m"; +my $meta_color = ""; + +my ($file_1,$file_2); +my $last_file_seen = ""; +my $last_file_mode = ""; +my $i = 0; +my $in_hunk = 0; +my $columns_to_remove = 0; + # We only process ARGV if we don't have STDIN -my @input; if (!$has_stdin) { my $args = argv(); @@ -337,181 +351,232 @@ if (!$has_stdin) { # Check to see if were using default settings check_first_run(); - @input = filter_stdin_through_diff_highlight(); -} + my @lines; + local $DiffHighlight::line_cb = sub { + push(@lines,@_); -################################################################################# + my $last_line = $lines[-1]; -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my $dim_magenta = "\e[38;5;146m"; -my $reset_color = "\e[0m"; -my $bold = "\e[1m"; -my $meta_color = ""; + # Buffer X lines before we try and output anything + # Also make sure we're sending enough data to d-s-f to do it's magic. + # Certain things require a look-ahead line or two to function so + # we make sure we don't break on those sections prematurely + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { + do_dsf_stuff(\@lines); + @lines = (); + } + }; -my ($file_1,$file_2); -my $last_file_seen = ""; -my $i = 0; -my $in_hunk = 0; -my $columns_to_remove = 0; + while (my $line = ) { + my $ok = DiffHighlight::handle_line($line); + } -while (my $line = shift(@input)) { - ###################################################### - # Pre-process the line before we do any other markup # - ###################################################### + DiffHighlight::flush(); + do_dsf_stuff(\@lines); +} - # If the first line of the input is a blank line, skip that - if ($i == 0 && $line =~ /^\s*$/) { - next; - } +################################################################################# - ###################### - # End pre-processing # - ###################### - - ####################################################################### - - #################################################################### - # Look for git index and replace it horizontal line (header later) # - #################################################################### - if ($line =~ /^${ansi_color_regex}index /) { - # Print the line color and then the actual line - $meta_color = $1; - print horizontal_rule($meta_color); - ######################### - # Look for the filename # - ######################### - } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { - $last_file_seen = $5; - $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). - $in_hunk = 0; - ######################################## - # Find the first file: --- a/README.md # - ######################################## - } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_1 = $file_dir . $5; - } else { - $file_1 = $5; - } +sub do_dsf_stuff { + my $input = shift(); - # Find the second file on the next line: +++ b/README.md - my $next = shift(@input); - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; - if ($1) { - print $1; # Print out whatever color we're using - } - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_2 = $file_dir . $5; - } else { - $file_2 = $5; - } + #print STDERR "START -------------------------------------------------\n"; + #print STDERR join("",@$input); + #print STDERR "END ---------------------------------------------------\n"; - if ($file_2 ne "/dev/null") { - $last_file_seen = $file_2; - } + while (my $line = shift(@$input)) { + ###################################################### + # Pre-process the line before we do any other markup # + ###################################################### - print file_change_string($file_1,$file_2) . "\n"; - - # Print out the bottom horizontal line of the header - print horizontal_rule($meta_color); - ######################################## - # Check for "@@ -3,41 +3,63 @@" syntax # - ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - my $hunk_header = $4; - my $remain = bleach_text($5); - - # The number of colums to remove (1 or 2) is based on how many commas in the hunk header - $columns_to_remove = (char_count(",",$hunk_header)) - 1; - # On single line removes there is NO comma in the hunk so we force one - if ($columns_to_remove <= 0) { - $columns_to_remove = 1; + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; } - if ($1) { - print $1; # Print out whatever color we're using - } + ###################### + # End pre-processing # + ###################### - my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - $last_file_seen = basename($last_file_seen); - - # Figure out the start line - my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; - ################################### - # Remove any new file permissions # - ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { - # Don't print the line (i.e. remove it from the output); - ###################################### - # Remove any delete file permissions # - ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { - # Don't print the line (i.e. remove it from the output); - ################################ - # Look for binary file changes # - ################################ - } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - my $change = file_change_string($2,$4); - print "$meta_color$change (binary)\n"; - print horizontal_rule($meta_color); - ##################################################### - # Check if we're changing the permissions of a file # - ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { - my ($old_mode) = $4; - my $next = shift(@input); - - if ($1) { - print $1; # Print out whatever color we're using - } + ####################################################################### - my ($new_mode) = $next =~ m/new mode (\d+)/; - print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + print horizontal_rule($meta_color); - ############### - # File rename # - ############### - } elsif ($line =~ /^${ansi_color_regex}similarity index 100%/) { - my $next = shift(@input); - my ($file1) = $next =~ /rename from (.+)/; + # Get the next line without incrementing counter while loop + my $next = $input->[0] || ""; + my ($file_1,$file_2); + + # The line immediately after the "index" line should be the --- file line + # If it's not it's an empty file add/delete + if ($next !~ /^$ansi_color_regex(---|Binary files)/) { + + # We fake out the file names since it's a raw add/delete + if ($last_file_mode eq "add") { + $file_1 = "/dev/null"; + $file_2 = $last_file_seen; + } elsif ($last_file_mode eq "delete") { + $file_1 = $last_file_seen; + $file_2 = "/dev/null"; + } + } - $next = shift(@input); - my ($file2) = $next =~ /rename to (.+)/; + if ($file_1 && $file_2) { + print $meta_color . file_change_string($file_1,$file_2) . "\n"; + print horizontal_rule($meta_color); + } + ######################### + # Look for the filename # + ######################### + } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { + $last_file_seen = $5; + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } - if ($file1 && $file2) { - # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;227m"); + # Find the second file on the next line: +++ b/README.md + my $next = shift(@$input); + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } - my $change = file_change_string($file1,$file2); + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } + print file_change_string($file_1,$file_2) . "\n"; + + # Print out the bottom horizontal line of the header print horizontal_rule($meta_color); - print $meta_color . $change . "\n"; + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + if ($columns_to_remove <= 0) { + $columns_to_remove = 1; + } + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); + $last_file_seen = basename($last_file_seen); + + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + # Don't print the line (i.e. remove it from the output); + $last_file_mode = "add"; + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + $last_file_mode = "delete"; + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + my $change = file_change_string($2,$4); + print "$meta_color$change (binary)\n"; print horizontal_rule($meta_color); - } + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; + my $next = shift(@$input); + + if ($1) { + print $1; # Print out whatever color we're using + } - $i += 3; # We've consumed three lines - next; - ##################################### - # Just a regular line, print it out # - ##################################### - } else { - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } + my ($new_mode) = $next =~ m/new mode (\d+)/; + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + + ############### + # File rename # + ############### + } elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) { + my $simil = $4; + + # If it's a move with content change we ignore this and the next two lines + if ($simil != 100) { + shift(@$input); + shift(@$input); + next; + } + + my $next = shift(@$input); + my ($file1) = $next =~ /rename from (.+)/; + + $next = shift(@$input); + my ($file2) = $next =~ /rename to (.+)/; + + if ($file1 && $file2) { + # We may not have extracted this yet, so we pull from the config if not + $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); + my $change = file_change_string($file1,$file2); + + print horizontal_rule($meta_color); + print $meta_color . $change . "\n"; + print horizontal_rule($meta_color); + } + + $i += 3; # We've consumed three lines + next; + ##################################### + # Just a regular line, print it out # + ##################################### + } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + print $line; } - print $line; - } - $i++; + $i++; + } } ###################################################################################################### @@ -656,13 +721,17 @@ sub get_git_config_hash { my %hash; foreach my $line (@$out) { my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - my @path = split(/\./,$key); - my $last = pop @path; - my $p = \%hash; - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; + if ($key && $value) { + $value =~ s/\s+$//; + my @path = split(/\./,$key); + my $last = pop @path; + my $p = \%hash; + + # Build the tree for each section + $p = $p->{$_} ||= {} for @path; + $p->{$last} = $value; + } } return \%hash; @@ -776,10 +845,12 @@ sub file_change_string { return "modified: $file_1"; # If the first is /dev/null it's a new file } elsif ($file_1 eq "/dev/null") { - return "added: $file_2"; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + return "added: $add_color$file_2$reset_color"; # If the second is /dev/null it's a deletion } elsif ($file_2 eq "/dev/null") { - return "deleted: $file_1"; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + return "deleted: $del_color$file_1$reset_color"; # If the files aren't the same it's a rename } elsif ($file_1 ne $file_2) { my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); @@ -851,16 +922,16 @@ sub get_default_colors { $out .= "# --------------------------------------------\n"; $out .= 'git config --global color.ui true -git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldNormal "red bold" git config --global color.diff-highlight.oldHighlight "red bold 52" -git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "227" -git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "227 bold" -git config --global color.diff.old "red bold" -git config --global color.diff.new "green bold" +git config --global color.diff.meta "yellow" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "yellow bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" '; @@ -901,47 +972,29 @@ sub is_windows { # Return value is whether this is the first time they've run d-s-f sub check_first_run { - my $i = get_git_config_hash(); my $ret = 0; # If first-run is not set, or it's set to "true" - my $first_run = boolean(!$i->{'diff-so-fancy'}->{'first-run'} || ($i->{'diff-so-fancy'}->{'first-run'} eq "true")); + my $first_run = git_config_boolean('diff-so-fancy.first-run'); # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = boolean($i->{color}->{'diff-highlight'}->{oldnormal} || $i->{color}->{'diff-highlight'}->{newnormal}); + my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); + + #$first_run = 1; $has_dh_colors = 0; if (!$first_run || $has_dh_colors) { return 0; } else { - my $interactive_shell = !has_stdin(); - - # d-s-f run inside of LESS (can't prompt user) - if (!$interactive_shell) { - my $warn = color("yellow_on_red"); - my $bold = color("bold"); - my $blink = color("blink"); - my $reset = color("reset"); - - printf("\n%s%s%sWarning:%s This appears to be the first time you've run diff-so-fancy. Please note that the\n",$blink,$bold,$warn,$reset); - printf("default colors may not be optimal. Please run 'diff-so-fancy --colors' to see our color recommendations.\n\n"); - - return 0; - } + print "This appears to be the first time you've run diff-so-fancy, please note\n"; + print "that the default git colors are not ideal. Diff-so-fancy recommends the\n"; + print "following colors.\n\n"; - print "This appears to be the first time you've run diff-so-fancy, would you like to\n"; - print "configure git to use diff-so-fancy for all diff operations and use the\n"; - print "recommended color scheme (y/N)?\n"; + print get_default_colors(); - my $input = ; - $input = uc(trim($input)); + # Set the first run flag to false + my $cmd = 'git config --global diff-so-fancy.first-run false'; + system($cmd); - # Set the default colors and git alias - if ($input eq "Y") { - set_defaults(); - # Just set the first-run flag to false so we don't pop this up again - } else { - my $cmd = 'git config --global diff-so-fancy.first-run false'; - system($cmd); - } + exit; } return 1; @@ -1003,4 +1056,4 @@ sub color { return $ret; } -# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 +# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From b6b5be7fb9832854fb4df3b513ef2716caa2c83d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 28 Dec 2017 09:39:08 -0800 Subject: [PATCH 236/443] Make Perl "dev" work on Travis CI --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e51bb4e..b72c891 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ os: linux language: perl perl: + - blead - dev - 5.26 - 5.24 @@ -15,6 +16,8 @@ matrix: osx_image: xcode7.3 language: generic perl: 5.24 + allow_failures: + - perl: "blead" before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git @@ -24,6 +27,8 @@ before_install: mkdir -p ~/perl5; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + eval $(curl https://travis-perl.github.io/init) --perl; + sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; sudo apt-get -y update; sudo apt-get -y install shellcheck; @@ -33,6 +38,6 @@ install: - eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode script: - - ./bats/bin/bats test/*.bats - perlcritic -1 -q --theme freenode diff-so-fancy + - ./bats/bin/bats test/*.bats - shellcheck *.sh From b75743036238887b2711cb51f07696ef60261490 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 28 Dec 2017 15:26:24 -0800 Subject: [PATCH 237/443] Initial attempt at Mercurial support --- diff-so-fancy | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index be81d6e..d909f50 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -36,6 +36,7 @@ my $last_file_mode = ""; my $i = 0; my $in_hunk = 0; my $columns_to_remove = 0; +my $is_mercurial = 0; # We only process ARGV if we don't have STDIN if (!$has_stdin) { @@ -116,7 +117,7 @@ sub do_dsf_stuff { #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line - $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + $meta_color = $1 || get_config_color("meta"); print horizontal_rule($meta_color); # Get the next line without incrementing counter while loop @@ -144,8 +145,16 @@ sub do_dsf_stuff { ######################### # Look for the filename # ######################### - } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { - $last_file_seen = $5; + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?) (.+?)(\s|\e|$)/) { + if ($4 eq "-r") { + $is_mercurial = 1; + $meta_color ||= get_config_color("meta"); + print horizontal_rule($meta_color); + $last_file_seen = $6; + } else { + $last_file_seen = $5; + } + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). $in_hunk = 0; ######################################## @@ -176,6 +185,10 @@ sub do_dsf_stuff { $last_file_seen = $file_2; } + if ($is_mercurial) { + print $reset_color . $meta_color; + } + print file_change_string($file_1,$file_2) . "\n"; # Print out the bottom horizontal line of the header @@ -259,7 +272,7 @@ sub do_dsf_stuff { if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + $meta_color ||= get_config_color("meta"); my $change = file_change_string($file1,$file2); @@ -767,4 +780,27 @@ sub color { return $ret; } +# Memoize getting the git config +{ + my $static_config; + + sub get_config_color { + my $str = shift(); + + my $ret = ""; + if ($static_config->{$str}) { + return $static_config->{$str}; + } + + if ($str eq "meta") { + $ret = DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + } + + # Cache (memoize) the entry for later + $static_config->{$str} = $ret; + + return $ret; + } +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From f0337f7f30197a7733795b01e387f52c8e79677f Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 29 Dec 2017 13:47:45 -0800 Subject: [PATCH 238/443] Rearrange some horizontal separator logic --- diff-so-fancy | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index d909f50..587678b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -118,7 +118,6 @@ sub do_dsf_stuff { if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line $meta_color = $1 || get_config_color("meta"); - print horizontal_rule($meta_color); # Get the next line without incrementing counter while loop my $next = $input->[0] || ""; @@ -139,18 +138,22 @@ sub do_dsf_stuff { } if ($file_1 && $file_2) { + print horizontal_rule($meta_color); print $meta_color . file_change_string($file_1,$file_2) . "\n"; print horizontal_rule($meta_color); } ######################### # Look for the filename # ######################### + # $4 $5 $6 } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?) (.+?)(\s|\e|$)/) { + + # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { $is_mercurial = 1; $meta_color ||= get_config_color("meta"); - print horizontal_rule($meta_color); $last_file_seen = $6; + # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy } else { $last_file_seen = $5; } @@ -185,10 +188,16 @@ sub do_dsf_stuff { $last_file_seen = $file_2; } + # Print out the top horizontal line of the header + print $reset_color; + print horizontal_rule($meta_color); + + # Mercurial coloring is slightly different so we need to hard reset colors if ($is_mercurial) { - print $reset_color . $meta_color; + print $reset_color; } + print $meta_color; print file_change_string($file_1,$file_2) . "\n"; # Print out the bottom horizontal line of the header @@ -235,6 +244,7 @@ sub do_dsf_stuff { ################################ } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { my $change = file_change_string($2,$4); + print horizontal_rule($meta_color); print "$meta_color$change (binary)\n"; print horizontal_rule($meta_color); ##################################################### From 0494bd3feacd89d3f8cc2771d484d7a17cbeb53d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 29 Dec 2017 14:42:35 -0800 Subject: [PATCH 239/443] Test for #193 --- test/diff-so-fancy.bats | 6 ++++++ test/fixtures/hg.diff | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 test/fixtures/hg.diff diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index a05b9a5..4319fe1 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -113,3 +113,9 @@ if begin" run printf "%s" "$output" assert_line --index 1 --regexp "renamed:" } + +@test "Mercurial support" { + output=$( load_fixture "hg" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 1 --regexp "modified: hello.c" +} diff --git a/test/fixtures/hg.diff b/test/fixtures/hg.diff new file mode 100644 index 0000000..0ac71f5 --- /dev/null +++ b/test/fixtures/hg.diff @@ -0,0 +1,22 @@ +diff -r 82e55d328c8c hello.c +--- a/hello.c Fri Aug 26 01:21:28 2005 -0700 ++++ b/hello.c Fri Dec 29 14:37:26 2017 -0800 +@@ -1,16 +1,15 @@ + /* + * hello.c + * +- * Placed in the public domain by Bryan O'Sullivan +- * + * This program is not covered by patents in the United States or other + * countries. + */ + +-#include ++#include + + int main(int argc, char **argv) + { + printf("hello, world!\n"); ++ exit 99; + return 0; + } From f31626393bb0f519ae4e1612990463e1c32d9591 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 29 Dec 2017 15:29:28 -0800 Subject: [PATCH 240/443] Change the d-h defaults to our d-s-f defaults --- lib/DiffHighlight.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index df9ed32..9cceb6c 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -8,13 +8,13 @@ use Encode; # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + color_config('color.diff-highlight.oldnormal', "\e[1;31m"), + color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), "\x1b[27m", ); -our@NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), +our @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', "\e[1;32m"), + color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), $OLD_HIGHLIGHT[2], ); From 61bf2a151fa6edc0560b92b35c011ab822800a93 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 29 Dec 2017 15:40:12 -0800 Subject: [PATCH 241/443] Make sure we pull Meta if we don't already have it --- diff-so-fancy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 587678b..7f12d03 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -164,6 +164,8 @@ sub do_dsf_stuff { # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + $meta_color ||= get_config_color("meta"); + if ($git_strip_prefix) { my $file_dir = $4 || ""; $file_1 = $file_dir . $5; From 6ff25566f0da6e7c9c2444b1f5b10eb9c5f5840d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 09:37:53 -0800 Subject: [PATCH 242/443] Clean up and combine the tests --- test/diff-so-fancy.bats | 134 ++++++++++++++++++++++++++++++++++------ test/header_clean.bats | 105 ------------------------------- 2 files changed, 116 insertions(+), 123 deletions(-) delete mode 100644 test/header_clean.bats diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 4319fe1..f25bded 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -10,31 +10,28 @@ set_env output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "diff-so-fancy runs exits without error" { - load_fixture "ls-function" | $diff_so_fancy - assert_success -} - -@test "original source is indented by a single space" { - assert_output --partial " -if begin" + load_fixture "ls-function" | $diff_so_fancy + assert_success } @test "index line is removed entirely" { - refute_output --partial "index 33c3d8b..fd54db2 100644" + refute_output --partial "index 33c3d8b..fd54db2 100644" } @test "+/- line symbols are stripped" { - refute_output --partial " --" - refute_output --partial " -+" + run printf "%s" "$output" + refute_line --index 9 --regexp "\+ set -x CLICOLOR_FORCE 1" + refute_line --index 22 --regexp "- eval \"env CLICOLOR" } @test "empty lines added/removed are marked" { - assert_output --partial "  - set -x CLICOLOR_FORCE 1" - assert_output --partial "  - if not set -q LS_COLORS" + run printf "%s" "$output" + + assert_line --index 7 --partial " " + assert_line --index 24 --partial " " + + #assert_output --partial " " + #assert_output --partial " " } @test "diff --git line is removed entirely" { @@ -85,7 +82,8 @@ if begin" @test "Handle latin1 encoding sanely" { output=$( load_fixture "latin1" | $diff_so_fancy ) # Make sure the output contains SOME of the english text (i.e. it doesn't barf on the whole line) - assert_output --partial "saw he conqu" + run printf "%s" "$output" + assert_line --index 6 --regexp "saw he conqu" } @test "Correctly handle hunk definition with no comma" { @@ -93,7 +91,7 @@ if begin" # On single line removes there is NO comma in the hunk, # make sure the first column is still correctly stripped. run printf "%s" "$output" - assert_line --index 5 "after" + assert_line --index 5 --regexp "after" } @test "Empty file add" { @@ -119,3 +117,103 @@ if begin" run printf "%s" "$output" assert_line --index 1 --regexp "modified: hello.c" } + +@test "Handle file renames" { + output=$( load_fixture "file-rename" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 1 --partial "renamed:" + assert_line --index 1 --partial "Changes.new" + assert_line --index 1 --partial "bin/" +} + +@test "header_clean 'added:'" { + output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --regexp 'added:.*hello.txt' +} + +@test "header_clean 'modified:'" { + output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --partial 'modified: appveyor.yml' +} + +@test "header_clean 'deleted:'" { + output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --regexp 'deleted:.*circle.yml' +} + +@test "header_clean permission changes" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + assert_output --partial 'circle.yml changed file mode from 100644 to 100755' +} + +@test "header_clean 'new file mode' is removed" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'new file mode' +} + +@test "header_clean 'deleted file mode' is removed" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'deleted file mode' +} + +@test "header_clean remove 'git --diff' header" { + output=$( load_fixture "file-perms" | $diff_so_fancy ) + refute_output --partial 'diff --git' +} + +@test "Reworked hunks" { + output=$( load_fixture "file-moves" | $diff_so_fancy ) + assert_output --partial '@ square.yml:4 @' + assert_output --partial '@ package.json:1 @' +} + +@test "Reworked hunks (noprefix)" { + output=$( load_fixture "noprefix" | $diff_so_fancy ) + assert_output --partial '@ setup-a-new-machine.sh:33 @' + assert_output --partial '@ setup-a-new-machine.sh:219 @' +} + +@test "Reworked hunks (deleted files)" { + output=$( load_fixture "dotfiles" | $diff_so_fancy ) + assert_output --partial '@ diff-so-fancy:1 @' +} + +@test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { + # stderr forced into output + output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 ) + assert_output --partial '@ header_clean.pl:107 @' + refute_output --partial 'Use of uninitialized value' +} + +@test "Hunk formatting: @@ -1,6 +1,6 @@" { + # stderr forced into output + output=$( load_fixture "first-three-line" | $diff_so_fancy ) + assert_output --partial '@ package.json:3 @' +} + +@test "Hunk formatting: @@ -1 0,0 @@" { + # stderr forced into output + output=$( load_fixture "single-line-remove" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 4 --regexp 'var delayedMessage = "It worked";' +} + +@test "Three way merge" { + # stderr forced into output + output=$( load_fixture "complex-hunks" | $diff_so_fancy ) + # Lines should not have + or - in at the start + refute_output --partial '- my $foo = shift(); # Array passed in by reference' + refute_output --partial '+ my $array = shift(); # Array passed in by reference' + refute_output --partial ' sub parse_hunk_header {' +} + +@test "mnemonicprefix handling" { + output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) + assert_output --partial 'modified: test/header_clean.bats' +} + +@test "non-git diff parsing" { + output=$( load_fixture "weird" | $diff_so_fancy ) + assert_output --partial 'modified: doc/manual.xml.head' + assert_output --partial '@ manual.xml.head:8355 @' +} diff --git a/test/header_clean.bats b/test/header_clean.bats deleted file mode 100644 index 1dca5e9..0000000 --- a/test/header_clean.bats +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bats - -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' -load 'test_helper/util' - -# bats fails to handle our multiline result, so we save to $output ourselves -output=$( load_fixture "file-moves" | $diff_so_fancy ) - -@test "Handle file renames" { - output=$( load_fixture "file-rename" | $diff_so_fancy ) - run printf "%s" "$output" - assert_line --index 1 --partial "renamed:" - assert_line --index 1 --partial "Changes.new" - assert_line --index 1 --partial "bin/" -} - -@test "header_clean 'added:'" { - assert_output --regexp 'added:.*hello.txt' -} - -@test "header_clean 'modified:'" { - assert_output --partial 'modified: appveyor.yml' -} - -@test "header_clean 'deleted:'" { - assert_output --regexp 'deleted:.*circle.yml' -} - -@test "header_clean permission changes" { - output=$( load_fixture "file-perms" | $diff_so_fancy ) - assert_output --partial 'circle.yml changed file mode from 100644 to 100755' -} - -@test "header_clean 'new file mode' is removed" { - output=$( load_fixture "file-perms" | $diff_so_fancy ) - refute_output --partial 'new file mode' -} - -@test "header_clean 'deleted file mode' is removed" { - output=$( load_fixture "file-perms" | $diff_so_fancy ) - refute_output --partial 'deleted file mode' -} - -@test "header_clean remove 'git --diff' header" { - output=$( load_fixture "file-perms" | $diff_so_fancy ) - refute_output --partial 'diff --git' -} - -@test "Reworked hunks" { - output=$( load_fixture "file-moves" | $diff_so_fancy ) - assert_output --partial '@ square.yml:4 @' - assert_output --partial '@ package.json:1 @' -} - -@test "Reworked hunks (noprefix)" { - output=$( load_fixture "noprefix" | $diff_so_fancy ) - assert_output --partial '@ setup-a-new-machine.sh:33 @' - assert_output --partial '@ setup-a-new-machine.sh:219 @' -} - -@test "Reworked hunks (deleted files)" { - output=$( load_fixture "dotfiles" | $diff_so_fancy ) - assert_output --partial '@ diff-so-fancy:1 @' -} - -@test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { - # stderr forced into output - output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 ) - assert_output --partial '@ header_clean.pl:107 @' - refute_output --partial 'Use of uninitialized value' -} - -@test "Hunk formatting: @@ -1,6 +1,6 @@" { - # stderr forced into output - output=$( load_fixture "first-three-line" | $diff_so_fancy ) - assert_output --partial '@ package.json:3 @' -} - -@test "Hunk formatting: @@ -1 0,0 @@" { - # stderr forced into output - output=$( load_fixture "single-line-remove" | $diff_so_fancy ) - run printf "%s" "$output" - assert_line --index 4 'var delayedMessage = "It worked";' -} - -@test "Three way merge" { - # stderr forced into output - output=$( load_fixture "complex-hunks" | $diff_so_fancy ) - # Lines should not have + or - in at the start - refute_output --partial '- my $foo = shift(); # Array passed in by reference' - refute_output --partial '+ my $array = shift(); # Array passed in by reference' - refute_output --partial ' sub parse_hunk_header {' -} - -@test "mnemonicprefix handling" { - output=$( load_fixture "mnemonicprefix" | $diff_so_fancy ) - assert_output --partial 'modified: test/header_clean.bats' -} - -@test "non-git diff parsing" { - output=$( load_fixture "weird" | $diff_so_fancy ) - assert_output --partial 'modified: doc/manual.xml.head' - assert_output --partial '@ manual.xml.head:8355 @' -} From 6dabb37f4a37851a76ee4ae271fde47f9948e96b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 11:22:58 -0800 Subject: [PATCH 243/443] Initial support for colorizing stand-alone diff/patch files (#220) --- diff-so-fancy | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 7f12d03..0a3f3d3 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -17,6 +17,7 @@ use warnings FATAL => 'all'; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; +my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); @@ -79,8 +80,16 @@ if (!$has_stdin) { } }; + my $line_count = 0; while (my $line = ) { + # If the very first line of the diff doesn't start with ANSI color we're assuming + # it's a raw patch file, and we have to color the added/removed lines ourself + if ($line_count == 0 && starts_with_ansi($line)) { + $manually_color_lines = 1; + } + my $ok = DiffHighlight::handle_line($line); + $line_count++; } DiffHighlight::flush(); @@ -510,7 +519,17 @@ sub strip_leading_indicators { return $line; # Nothing to do } - $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; + $line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/; + + if ($manually_color_lines) { + if (defined($5) && $5 eq "+") { + my $add_line_color = get_config_color("add_line"); + $line = $add_line_color . $line . $reset_color; + } elsif (defined($5) && $5 eq "-") { + my $remove_line_color = get_config_color("remove_line"); + $line = $remove_line_color . $line . $reset_color; + } + } return $line; } @@ -805,7 +824,16 @@ sub color { } if ($str eq "meta") { - $ret = DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + # Default ANSI yellow + $ret = DiffHighlight::color_config('color.diff.meta', color(11)); + } elsif ($str eq "reset") { + $ret = color("reset"); + } elsif ($str eq "add_line") { + # Default ANSI green + $ret = DiffHighlight::color_config('color.diff.new', color('bold') . color(2)); + } elsif ($str eq "remove_line") { + # Default ANSI red + $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); } # Cache (memoize) the entry for later @@ -815,4 +843,14 @@ sub color { } } +sub starts_with_ansi { + my $str = shift(); + + if ($str =~ /^$ansi_color_regex/) { + return 1; + } else { + return 0; + } +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 8b8be27d2d19f0914e49a31c76f408a58ebcdb86 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 11:37:01 -0800 Subject: [PATCH 244/443] Add an option to force line coloring on/off (really debug only) --- diff-so-fancy | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0a3f3d3..bec3d26 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -32,17 +32,27 @@ my $bold = "\e[1m"; my $meta_color = ""; my ($file_1,$file_2); +my $args = argv(); # Hashref of all the ARGV stuff my $last_file_seen = ""; my $last_file_mode = ""; my $i = 0; my $in_hunk = 0; my $columns_to_remove = 0; my $is_mercurial = 0; +my $color_forced = 0; # Has the color been forced on/off + +# We try and be smart about whether we need to do line coloring, but +# this is an option to force it on/off +if ($args->{color_on}) { + $manually_color_lines = 1; + $color_forced = 1; +} elsif ($args->{color_off}) { + $manually_color_lines = 0; + $color_forced = 1; +} # We only process ARGV if we don't have STDIN if (!$has_stdin) { - my $args = argv(); - if ($args->{v} || $args->{version}) { die(version()); } elsif ($args->{'set-defaults'}) { @@ -84,7 +94,7 @@ if (!$has_stdin) { while (my $line = ) { # If the very first line of the diff doesn't start with ANSI color we're assuming # it's a raw patch file, and we have to color the added/removed lines ourself - if ($line_count == 0 && starts_with_ansi($line)) { + if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { $manually_color_lines = 1; } From ce7eb3d051594acef4e6d6c6cd18b4714343dcb2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 14:37:50 -0800 Subject: [PATCH 245/443] Remove two old and unused functions --- diff-so-fancy | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index bec3d26..b65a0a5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -469,29 +469,6 @@ sub should_print_unicode { return 0; } -# Return git config as a hash -sub get_git_config_hash { - my $out = git_config_raw(); - - my %hash; - foreach my $line (@$out) { - my ($key,$value) = split("=",$line,2); - - if ($key && $value) { - $value =~ s/\s+$//; - my @path = split(/\./,$key); - my $last = pop @path; - my $p = \%hash; - - # Build the tree for each section - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; - } - } - - return \%hash; -} - # Try and be smart about what line the diff hunk starts on sub start_line_calc { my ($line_num,$diff_context) = @_; @@ -711,22 +688,6 @@ sub version { return $ret; } -# Feed the raw git input through diff-highlight to get line level highlights -sub filter_stdin_through_diff_highlight { - my @dh_lines; - - # Have DH put the lines it's modified in an array - local $DiffHighlight::line_cb = sub { push(@dh_lines,@_) }; - - while (my $line = ) { - my $ok = DiffHighlight::handle_line($line); - } - - DiffHighlight::flush(); - - return @dh_lines; -} - sub is_windows { if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { return 1; From 4172462c3b558d91ceac62e03accb903e8c997cc Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 14:38:22 -0800 Subject: [PATCH 246/443] Reword a function description (bad copy/paste) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index b65a0a5..fd31e82 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -782,7 +782,7 @@ sub color { return $ret; } -# Memoize getting the git config +# Get colors used for various output sections (memoized) { my $static_config; From 4bd14c37c78fd900459accd8a6a6c99de4c181a5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 14:40:29 -0800 Subject: [PATCH 247/443] Newer version of color() --- diff-so-fancy | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index fd31e82..0e6de67 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -755,7 +755,8 @@ sub set_defaults { return 1; } -# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red_bold', 'red_on_blue', 'blink', 'italic' +# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my $str = shift(); @@ -764,15 +765,15 @@ sub color { # Some predefined colors my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); - $str =~ s/$_/$color_map{$_}/g for keys %color_map; + $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands - my ($fc,$cmd) = $str =~ /^(\d+)?_?(\w+)?/g; - my ($bc) = $str =~ /on_?(\d+)$/g; + my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)/g; # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); - my $cmd_num = $cmd_map{$cmd || 0}; + my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } From 9ffbbb9db9721e3c46c0279cac85a8e23bc7a68e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 14:49:35 -0800 Subject: [PATCH 248/443] Make some color logic consistent --- diff-so-fancy | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0e6de67..6007247 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -26,9 +26,8 @@ my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my $dim_magenta = "\e[38;5;146m"; -my $reset_color = "\e[0m"; -my $bold = "\e[1m"; +my $reset_color = color("reset"); +my $bold = color("bold"); my $meta_color = ""; my ($file_1,$file_2); @@ -247,7 +246,10 @@ sub do_dsf_stuff { # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + + # Last function has it's own color + my $last_function_color = get_config_color("last_function"); + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### @@ -806,6 +808,8 @@ sub color { } elsif ($str eq "remove_line") { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + } elsif ($str eq "last_function") { + $ret = color(146); } # Cache (memoize) the entry for later From 7dc91b2d56b44fbb43a30b444d8e375c3c4f983a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Jan 2018 15:13:48 -0800 Subject: [PATCH 249/443] Fix a failing unit test on OSX --- test/diff-so-fancy.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index f25bded..27eb383 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -83,7 +83,7 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) output=$( load_fixture "latin1" | $diff_so_fancy ) # Make sure the output contains SOME of the english text (i.e. it doesn't barf on the whole line) run printf "%s" "$output" - assert_line --index 6 --regexp "saw he conqu" + assert_line --index 6 --partial "saw he conqu" } @test "Correctly handle hunk definition with no comma" { From 3a7accb6997d476801a53fe216305823366b24ce Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 4 Jan 2018 08:24:36 -0800 Subject: [PATCH 250/443] Bump the version to 1.2.1 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 6007247..05e2977 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.2.0"; +my $VERSION = "1.2.1"; ################################################################################# From 0d2492c0cc81c11588040982f9544b6be8541bcb Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Mon, 8 Jan 2018 10:16:23 +1100 Subject: [PATCH 251/443] 1.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b60a84..d329017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "diff-so-fancy", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 1 } diff --git a/package.json b/package.json index 23fcf0c..97965ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.1.1", + "version": "1.2.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From 9bb5bc798af23e88665a0d4252b87accf25b82e6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 10 Jan 2018 13:22:29 -0800 Subject: [PATCH 252/443] Fix a git error where it wasn't hiding the git diff line --- diff-so-fancy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 05e2977..bece8e0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -163,14 +163,13 @@ sub do_dsf_stuff { ######################### # Look for the filename # ######################### - # $4 $5 $6 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?) (.+?)(\s|\e|$)/) { + # $4 $5 + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { $is_mercurial = 1; $meta_color ||= get_config_color("meta"); - $last_file_seen = $6; # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy } else { $last_file_seen = $5; From 068c0ec9abed601258fa6c6c5be52b66a1d07a86 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 16 Jan 2018 13:31:15 -0800 Subject: [PATCH 253/443] Force some test to look at specific lines --- test/diff-so-fancy.bats | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 27eb383..31c8046 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -175,13 +175,17 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "Reworked hunks (deleted files)" { output=$( load_fixture "dotfiles" | $diff_so_fancy ) - assert_output --partial '@ diff-so-fancy:1 @' + run printf "%s" "$output" + + assert_line --index 188 --partial "@ diff-so-fancy:1 @" } @test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { # stderr forced into output output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 ) - assert_output --partial '@ header_clean.pl:107 @' + run printf "%s" "$output" + + assert_line --index 4 --partial "@ header_clean.pl:107 @" refute_output --partial 'Use of uninitialized value' } @@ -214,6 +218,8 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "non-git diff parsing" { output=$( load_fixture "weird" | $diff_so_fancy ) - assert_output --partial 'modified: doc/manual.xml.head' - assert_output --partial '@ manual.xml.head:8355 @' + run printf "%s" "$output" + + assert_line --index 1 --partial "modified: doc/manual.xml.head" + assert_line --index 3 --partial "@ manual.xml.head:8355 @" } From 49969f04db747138243d598c43d766c7e65c1f27 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 17 Jan 2018 16:19:50 -0800 Subject: [PATCH 254/443] Hunk sections should use the full path, not basename() (Addresses #280) --- diff-so-fancy | 2 +- test/diff-so-fancy.bats | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index bece8e0..c74a883 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -241,7 +241,7 @@ sub do_dsf_stuff { } my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - $last_file_seen = basename($last_file_seen); + #$last_file_seen = basename($last_file_seen); # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 31c8046..27b06e0 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -177,7 +177,7 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) output=$( load_fixture "dotfiles" | $diff_so_fancy ) run printf "%s" "$output" - assert_line --index 188 --partial "@ diff-so-fancy:1 @" + assert_line --index 188 --partial "@ bin/diff-so-fancy:1 @" } @test "Hunk formatting: @@@ -A,B -C,D +E,F @@@" { @@ -185,7 +185,7 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 ) run printf "%s" "$output" - assert_line --index 4 --partial "@ header_clean.pl:107 @" + assert_line --index 4 --partial "@ libs/header_clean/header_clean.pl:107 @" refute_output --partial 'Use of uninitialized value' } @@ -221,5 +221,5 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) run printf "%s" "$output" assert_line --index 1 --partial "modified: doc/manual.xml.head" - assert_line --index 3 --partial "@ manual.xml.head:8355 @" + assert_line --index 3 --partial "@ doc/manual.xml.head:8355 @" } From 403fa5e8628b9488107c55803cf1dff3a92392d4 Mon Sep 17 00:00:00 2001 From: UnderHeavyDevelopment Date: Tue, 30 Jan 2018 16:18:10 +0100 Subject: [PATCH 255/443] Update installation instruction (#277) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf44a43..725594d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install -Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path +Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. You can check which directories are in your path with `echo $PATH`. `diff-so-fancy` is also available from NPM, brew, and as a package on Arch Linux. From f35024397536256d1cabe9d56a950aa04f62058d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 5 Feb 2018 11:14:33 -0800 Subject: [PATCH 256/443] Add support for quad ANSI codes (for background colors) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index c74a883..cc52933 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -25,7 +25,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,4})[mK])?/; my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; From feba20f279211316aebf2ada951ea0aa2397c33c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 5 Feb 2018 11:14:33 -0800 Subject: [PATCH 257/443] Add support for quad ANSI codes (for background colors) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index c74a883..cc52933 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -25,7 +25,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,4})[mK])?/; my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; From 8739a574307ec404fc24bb09e263e115120f99f2 Mon Sep 17 00:00:00 2001 From: Gegengift Date: Sun, 11 Feb 2018 15:12:44 +0100 Subject: [PATCH 258/443] Adding Hint for Windows users --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae5ff9a..bdf9155 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install -Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path +Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path (additional for Windows users: just [install the Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10)) `diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. From 86631e77f81683b2cc9e847e88d9c209a8a81a57 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 26 Feb 2018 14:23:07 -0800 Subject: [PATCH 259/443] Update argv() to properly parse --two-word arguments (Addresses #288) --- diff-so-fancy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index cc52933..4b88be5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -626,8 +626,9 @@ sub argv { my $ret = {}; for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key - if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) { # If the next item does not start with "--" it's the value for this item if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { $ret->{$key} = $ARGV[$i + 1]; From bcb11eb53c019a7a15ec858d521c494d0310df31 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 26 Feb 2018 14:24:09 -0800 Subject: [PATCH 260/443] Expand the ANSI regexp to detect 6 "octets" Needed for zebra coloring --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 4b88be5..6c6f1dd 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -25,7 +25,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,4})[mK])?/; +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,6})[mK])?/; my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; From 4148829844a03ba47d04f39ff39b661dd97be002 Mon Sep 17 00:00:00 2001 From: Gegengift Date: Tue, 27 Feb 2018 19:18:21 +0100 Subject: [PATCH 261/443] Review changes - seperat sentence for better readability --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bdf9155..1c23f60 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install -Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path (additional for Windows users: just [install the Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10)) +Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. +Windows users may need to [install the Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). `diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. From dc16a3d0a32ef59157a100ac71050e6e15f467d6 Mon Sep 17 00:00:00 2001 From: Gegengift Date: Tue, 27 Feb 2018 19:26:49 +0100 Subject: [PATCH 262/443] Review changes - shorter link for better readability --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c23f60..1ce07bb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. -Windows users may need to [install the Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +Windows users may need to install the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). `diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. From 7da9ddbe43763fa7712bad116b7f972158e6021a Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski Date: Thu, 1 Mar 2018 12:40:06 +0100 Subject: [PATCH 263/443] pro-tips: Information about Zsh plugin/package providing diff-so-fancy --- pro-tips.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pro-tips.md b/pro-tips.md index 62e4e78..9e24652 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -36,3 +36,22 @@ between files with `n`/`N` keys: [pager] diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' ``` + +#### Zsh plugin providing diff-so-fancy + +Zsh plugin [zdharma/zsh-diff-so-fancy](https://github.com/zdharma/zsh-diff-so-fancy) has this +project as a submodule so installing the plugin installs `diff-so-fancy`. The plugin provides +subcommand `git dsf` out of the box. Installation with Zplugin, Zplug and Zgen: + +```zsh +# Zplugin +zplugin ice as"program" pick"bin/git-dsf" +zplugin light zdharma/zsh-diff-so-fancy + +# Or zplug +zplug "zdharma/zsh-diff-so-fancy", as:command, use:bin/git-dsf + +# Or zgen and others +zgen zdharma/zsh-diff-so-fancy +``` + From 8bcbb63e0c9974087d6fcb94d18be5379e12eebc Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 2 Mar 2018 09:52:19 -0800 Subject: [PATCH 264/443] Use the function color from the config (addresses #290) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 6c6f1dd..a895979 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -809,7 +809,7 @@ sub color { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); } elsif ($str eq "last_function") { - $ret = color(146); + $ret = DiffHighlight::color_config('color.diff.func', color(146)); } # Cache (memoize) the entry for later From c31ce934cc58c53b78277a9c67206fb410c88450 Mon Sep 17 00:00:00 2001 From: Wes Malone Date: Tue, 27 Mar 2018 18:27:34 -0500 Subject: [PATCH 265/443] Add config for file header ruler width (rulerWidth) --- README.md | 7 +++++++ diff-so-fancy | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cebe171..8b624af 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,13 @@ By default the separator for the file header uses Unicode line drawing character git config --bool --global diff-so-fancy.useUnicodeRuler false ``` +### rulerWidth + +By default the separator for the file header spans the full width of the terminal. Use this setting to set the width of the file header manually. +``` +git config --global diff-so-fancy.rulerWidth 47 # git log's commit header width +``` + ## Contributing Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make diff-so-fancy even better. diff --git a/diff-so-fancy b/diff-so-fancy index a895979..0a5b5f1 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -22,6 +22,7 @@ my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkInd my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); @@ -557,7 +558,7 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - my $width = `tput cols`; + my $width = $ruler_width || `tput cols`; if (is_windows()) { $width--; From 0c964df801f6760ea2f0fe05cb9254c48d2d673b Mon Sep 17 00:00:00 2001 From: Buddh Prakash Date: Mon, 30 Apr 2018 23:29:53 +0100 Subject: [PATCH 266/443] Make diff-so-fancy fancier. :) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ce07bb..9d83029 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ git config --bool --global diff-so-fancy.useUnicodeRuler false ## Contributing -Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make diff-so-fancy even better. +Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make diff-so-fancy even *fancier*. ### Other documentation From f2e5dffafdde1d7c86bc86ed8a16f13edc8a2e99 Mon Sep 17 00:00:00 2001 From: C Anthony Risinger Date: Mon, 7 May 2018 00:33:50 -0500 Subject: [PATCH 267/443] Match escape sequences wide enough to hold bold, truecolor fg, and truecolor bg. After a `git config --global color.diff.new '#859900 #002b36 bold'`, git will start emitting escape sequences that look like `^[[1;38;2;133;153;0;48;2;0;43;54m`. Update regex repetition to number of possible arguments in play (bold=1, fg=5, bg=5). Closes #299. --- diff-so-fancy | 2 +- test/diff-so-fancy.bats | 8 ++++++++ test/fixtures/truecolor.diff | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/truecolor.diff diff --git a/diff-so-fancy b/diff-so-fancy index a895979..703d5fb 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -25,7 +25,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,6})[mK])?/; +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 27b06e0..d9a65c4 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -24,6 +24,14 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) refute_line --index 22 --regexp "- eval \"env CLICOLOR" } +@test "+/- line symbols are stripped (truecolor)" { + output=$( load_fixture "truecolor" | $diff_so_fancy ) + refute_output --partial " +-" + refute_output --partial " ++" +} + @test "empty lines added/removed are marked" { run printf "%s" "$output" diff --git a/test/fixtures/truecolor.diff b/test/fixtures/truecolor.diff new file mode 100644 index 0000000..dde4724 --- /dev/null +++ b/test/fixtures/truecolor.diff @@ -0,0 +1,14 @@ +diff --git package.json package.json +index 97965ab..f3ce90a 100644 +--- package.json ++++ package.json +@@ -13,8 +13,8 @@ + "url": "git+https://github.com/so-fancy/diff-so-fancy.git" + }, + "keywords": [ +- "git", + "diff", ++ "git", + "fancy", + "good-lookin'", + "diff-highlight", From 2aeee72ee96e5125e38be718ad42eca9b82493e2 Mon Sep 17 00:00:00 2001 From: serkan Date: Tue, 17 Jul 2018 20:16:01 -0400 Subject: [PATCH 268/443] projects currently running on CircleCI 1.0 will no longer be supported after August 31, 2018.Upgrading to CircleCI 2.0. --- .circleci/config.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..dcf60b1 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,21 @@ +version: 2 +jobs: + build: + machine: true + steps: + - checkout + - run: + name: + command: | + sudo apt-get -y update + sudo apt-get -y install shellcheck git + + - run: git clone --depth 1 https://github.com/sstephenson/bats.git + - run: git submodule update --init + - run: + name: tests + command: | + printenv + export TERM=dumb && bats/bin/bats test/*.bats + shellcheck *.sh + \ No newline at end of file From 2f5a9475339e55281bd2ad07bf93312e881ecc8b Mon Sep 17 00:00:00 2001 From: serkan Date: Tue, 17 Jul 2018 21:30:17 -0400 Subject: [PATCH 269/443] last --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dcf60b1..45d23f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,6 @@ jobs: - run: name: tests command: | - printenv export TERM=dumb && bats/bin/bats test/*.bats shellcheck *.sh \ No newline at end of file From b60a243ef6fb35cada6519bee385ee3ba6b27c29 Mon Sep 17 00:00:00 2001 From: Kevin Goslar Date: Wed, 5 Sep 2018 13:22:39 -0500 Subject: [PATCH 270/443] Unify build status badge look This makes the CircleCI badge look similar to the other badges. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ce07bb..f76b9ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# diff-so-fancy [![Circle CI build](https://circleci.com/gh/so-fancy/diff-so-fancy.svg?style=svg)](https://circleci.com/gh/so-fancy/diff-so-fancy) [![TravisCI build](https://travis-ci.org/so-fancy/diff-so-fancy.svg?branch=master)](https://travis-ci.org/so-fancy/diff-so-fancy) [![AppVeyor build](https://ci.appveyor.com/api/projects/status/github/so-fancy/diff-so-fancy?branch=master&svg=true)](https://ci.appveyor.com/project/stevemao/diff-so-fancy/branch/master) +# diff-so-fancy [![Circle CI build](https://circleci.com/gh/so-fancy/diff-so-fancy.svg?style=shield)](https://circleci.com/gh/so-fancy/diff-so-fancy) [![TravisCI build](https://travis-ci.org/so-fancy/diff-so-fancy.svg?branch=master)](https://travis-ci.org/so-fancy/diff-so-fancy) [![AppVeyor build](https://ci.appveyor.com/api/projects/status/github/so-fancy/diff-so-fancy?branch=master&svg=true)](https://ci.appveyor.com/project/stevemao/diff-so-fancy/branch/master) `diff-so-fancy` strives to make your diff's **human** readable instead of machine readable. This helps improve code quality and help you spot defects faster. From 4a75f223313a4485ff654a5e909af9d89f26372d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 5 Sep 2018 13:31:55 -0700 Subject: [PATCH 271/443] circle.yml is no longer needed per #304 --- circle.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index eb8dcfa..0000000 --- a/circle.yml +++ /dev/null @@ -1,21 +0,0 @@ -machine: - environment: - - -dependencies: - pre: - - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; - - sudo apt-get -y update - - sudo apt-get -y install shellcheck; - - post: - - git clone --depth 1 https://github.com/sstephenson/bats.git - -checkout: - post: - - git submodule update --init - -test: - override: - - bats/bin/bats test/*.bats - - shellcheck *.sh From bdaf062493b8b47cdc73713b67e8da08f90a63f6 Mon Sep 17 00:00:00 2001 From: serkan Date: Thu, 6 Sep 2018 08:37:21 -0400 Subject: [PATCH 272/443] circle.yml(v1) file deleted --- circle.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index eb8dcfa..0000000 --- a/circle.yml +++ /dev/null @@ -1,21 +0,0 @@ -machine: - environment: - - -dependencies: - pre: - - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; - - sudo apt-get -y update - - sudo apt-get -y install shellcheck; - - post: - - git clone --depth 1 https://github.com/sstephenson/bats.git - -checkout: - post: - - git submodule update --init - -test: - override: - - bats/bin/bats test/*.bats - - shellcheck *.sh From 9ff39e340561afad236c31cf40403836ff779b1a Mon Sep 17 00:00:00 2001 From: Kevin Goslar Date: Sat, 8 Sep 2018 15:38:54 -0500 Subject: [PATCH 273/443] Works with MinGW on Windows --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f76b9ef..450535a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. -Windows users may need to install the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw/files/) or the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). `diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. From 31b2ecf5805bbf5dd6483cee1b47969c7b15998e Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 9 Oct 2018 17:59:08 +1100 Subject: [PATCH 274/443] fix tests --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index e51bb4e..e2859dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,6 @@ perl: - 5.18 - 5.14 -matrix: - include: - - os: osx - osx_image: xcode7.3 - language: generic - perl: 5.24 - before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then From 03526283b6cd069f61158b4165371a0a3b63d9c8 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 25 Oct 2018 15:34:45 -0700 Subject: [PATCH 275/443] Fix so-fancy#311 Windows doesn't nave '\dev\null' Update DiffHighlight to use File::Spec->devnull() to avoid platform problems. --- lib/DiffHighlight.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 9cceb6c..7b5a6ae 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -4,6 +4,9 @@ use 5.008; use warnings FATAL => 'all'; use strict; use Encode; +use File::Spec; # For devnull + +my $NULL = File::Spec->devnull(); # get correct value for unix/win # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -88,7 +91,7 @@ sub highlight_stdin { # fallback, which means we will work even if git can't be run. sub color_config { my ($key, $default) = @_; - my $s = `git config --get-color $key 2>/dev/null`; + my $s = `git config --get-color $key 2>$NULL`; return length($s) ? $s : $default; } From c3f6329ba1c70790e7dedbbba91444f492580f96 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 25 Oct 2018 15:34:45 -0700 Subject: [PATCH 276/443] Fix so-fancy#311 Windows doesn't have '\dev\null' Update DiffHighlight to use File::Spec->devnull() to avoid platform problems. --- lib/DiffHighlight.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 9cceb6c..7b5a6ae 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -4,6 +4,9 @@ use 5.008; use warnings FATAL => 'all'; use strict; use Encode; +use File::Spec; # For devnull + +my $NULL = File::Spec->devnull(); # get correct value for unix/win # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -88,7 +91,7 @@ sub highlight_stdin { # fallback, which means we will work even if git can't be run. sub color_config { my ($key, $default) = @_; - my $s = `git config --get-color $key 2>/dev/null`; + my $s = `git config --get-color $key 2>$NULL`; return length($s) ? $s : $default; } From 9a61747704d9ee2647a1fce1db5d5a1264b96393 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 17 Jan 2019 11:53:40 -0800 Subject: [PATCH 277/443] Bump version 1.2.5 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 33d1107..c4530a6 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.2.1"; +my $VERSION = "1.2.5"; ################################################################################# From 09e4bea641029317a3dc10599f2a9c664a4acf8d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 17 Jan 2019 13:28:15 -0800 Subject: [PATCH 278/443] Build fat-packed version of 1.2.5 --- third_party/build_fatpack/diff-so-fancy | 195 ++++++++++++++++-------- 1 file changed, 129 insertions(+), 66 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 8e99f1e..2e39e19 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -16,13 +16,13 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + color_config('color.diff-highlight.oldnormal', "\e[1;31m"), + color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), "\x1b[27m", ); - our@NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), + our @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', "\e[1;32m"), + color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), $OLD_HIGHLIGHT[2], ); @@ -289,7 +289,7 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.2.0"; +my $VERSION = "1.2.5"; ################################################################################# @@ -306,30 +306,42 @@ use warnings FATAL => 'all'; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; +my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,3})[mK])?/; -my $dim_magenta = "\e[38;5;146m"; -my $reset_color = "\e[0m"; -my $bold = "\e[1m"; +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; +my $reset_color = color("reset"); +my $bold = color("bold"); my $meta_color = ""; my ($file_1,$file_2); +my $args = argv(); # Hashref of all the ARGV stuff my $last_file_seen = ""; my $last_file_mode = ""; my $i = 0; my $in_hunk = 0; my $columns_to_remove = 0; +my $is_mercurial = 0; +my $color_forced = 0; # Has the color been forced on/off + +# We try and be smart about whether we need to do line coloring, but +# this is an option to force it on/off +if ($args->{color_on}) { + $manually_color_lines = 1; + $color_forced = 1; +} elsif ($args->{color_off}) { + $manually_color_lines = 0; + $color_forced = 1; +} # We only process ARGV if we don't have STDIN if (!$has_stdin) { - my $args = argv(); - if ($args->{v} || $args->{version}) { die(version()); } elsif ($args->{'set-defaults'}) { @@ -367,8 +379,16 @@ if (!$has_stdin) { } }; + my $line_count = 0; while (my $line = ) { + # If the very first line of the diff doesn't start with ANSI color we're assuming + # it's a raw patch file, and we have to color the added/removed lines ourself + if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { + $manually_color_lines = 1; + } + my $ok = DiffHighlight::handle_line($line); + $line_count++; } DiffHighlight::flush(); @@ -405,8 +425,7 @@ sub do_dsf_stuff { #################################################################### if ($line =~ /^${ansi_color_regex}index /) { # Print the line color and then the actual line - $meta_color = $1 || DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); - print horizontal_rule($meta_color); + $meta_color = $1 || get_config_color("meta"); # Get the next line without incrementing counter while loop my $next = $input->[0] || ""; @@ -427,20 +446,33 @@ sub do_dsf_stuff { } if ($file_1 && $file_2) { + print horizontal_rule($meta_color); print $meta_color . file_change_string($file_1,$file_2) . "\n"; print horizontal_rule($meta_color); } ######################### # Look for the filename # ######################### - } elsif ($line =~ /^${ansi_color_regex}diff --(git|cc) (.*?)(\s|\e|$)/) { - $last_file_seen = $5; + # $4 $5 + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) { + + # Mercurial looks like: diff -r 82e55d328c8c hello.c + if ($4 eq "-r") { + $is_mercurial = 1; + $meta_color ||= get_config_color("meta"); + # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy + } else { + $last_file_seen = $5; + } + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). $in_hunk = 0; ######################################## # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + $meta_color ||= get_config_color("meta"); + if ($git_strip_prefix) { my $file_dir = $4 || ""; $file_1 = $file_dir . $5; @@ -465,6 +497,16 @@ sub do_dsf_stuff { $last_file_seen = $file_2; } + # Print out the top horizontal line of the header + print $reset_color; + print horizontal_rule($meta_color); + + # Mercurial coloring is slightly different so we need to hard reset colors + if ($is_mercurial) { + print $reset_color; + } + + print $meta_color; print file_change_string($file_1,$file_2) . "\n"; # Print out the bottom horizontal line of the header @@ -489,11 +531,14 @@ sub do_dsf_stuff { } my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - $last_file_seen = basename($last_file_seen); + #$last_file_seen = basename($last_file_seen); # Figure out the start line my $start_line = start_line_calc($new_offset,$new_count); - print "@ $last_file_seen:$start_line \@${bold}${dim_magenta}${remain}${reset_color}\n"; + + # Last function has it's own color + my $last_function_color = get_config_color("last_function"); + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### @@ -511,6 +556,7 @@ sub do_dsf_stuff { ################################ } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { my $change = file_change_string($2,$4); + print horizontal_rule($meta_color); print "$meta_color$change (binary)\n"; print horizontal_rule($meta_color); ##################################################### @@ -548,7 +594,7 @@ sub do_dsf_stuff { if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= DiffHighlight::color_config('color.diff.meta',"\e[38;5;11m"); + $meta_color ||= get_config_color("meta"); my $change = file_change_string($file1,$file2); @@ -714,29 +760,6 @@ sub should_print_unicode { return 0; } -# Return git config as a hash -sub get_git_config_hash { - my $out = git_config_raw(); - - my %hash; - foreach my $line (@$out) { - my ($key,$value) = split("=",$line,2); - - if ($key && $value) { - $value =~ s/\s+$//; - my @path = split(/\./,$key); - my $last = pop @path; - my $p = \%hash; - - # Build the tree for each section - $p = $p->{$_} ||= {} for @path; - $p->{$last} = $value; - } - } - - return \%hash; -} - # Try and be smart about what line the diff hunk starts on sub start_line_calc { my ($line_num,$diff_context) = @_; @@ -774,7 +797,17 @@ sub strip_leading_indicators { return $line; # Nothing to do } - $line =~ s/^(${ansi_color_regex})[ +-]{${columns_to_remove}}/$1/; + $line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/; + + if ($manually_color_lines) { + if (defined($5) && $5 eq "+") { + my $add_line_color = get_config_color("add_line"); + $line = $add_line_color . $line . $reset_color; + } elsif (defined($5) && $5 eq "-") { + my $remove_line_color = get_config_color("remove_line"); + $line = $remove_line_color . $line . $reset_color; + } + } return $line; } @@ -814,7 +847,7 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - my $width = `tput cols`; + my $width = $ruler_width || `tput cols`; if (is_windows()) { $width--; @@ -883,8 +916,9 @@ sub argv { my $ret = {}; for (my $i = 0; $i < scalar(@ARGV); $i++) { + # If the item starts with "-" it's a key - if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) { + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) { # If the next item does not start with "--" it's the value for this item if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { $ret->{$key} = $ARGV[$i + 1]; @@ -946,22 +980,6 @@ sub version { return $ret; } -# Feed the raw git input through diff-highlight to get line level highlights -sub filter_stdin_through_diff_highlight { - my @dh_lines; - - # Have DH put the lines it's modified in an array - local $DiffHighlight::line_cb = sub { push(@dh_lines,@_) }; - - while (my $line = ) { - my $ok = DiffHighlight::handle_line($line); - } - - DiffHighlight::flush(); - - return @dh_lines; -} - sub is_windows { if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { return 1; @@ -1029,7 +1047,8 @@ sub set_defaults { return 1; } -# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red_bold', 'red_on_blue', 'blink', 'italic' +# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my $str = shift(); @@ -1038,15 +1057,15 @@ sub color { # Some predefined colors my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); - $str =~ s/$_/$color_map{$_}/g for keys %color_map; + $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands - my ($fc,$cmd) = $str =~ /^(\d+)?_?(\w+)?/g; - my ($bc) = $str =~ /on_?(\d+)$/g; + my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)/g; # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); - my $cmd_num = $cmd_map{$cmd || 0}; + my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } @@ -1056,4 +1075,48 @@ sub color { return $ret; } +# Get colors used for various output sections (memoized) +{ + my $static_config; + + sub get_config_color { + my $str = shift(); + + my $ret = ""; + if ($static_config->{$str}) { + return $static_config->{$str}; + } + + if ($str eq "meta") { + # Default ANSI yellow + $ret = DiffHighlight::color_config('color.diff.meta', color(11)); + } elsif ($str eq "reset") { + $ret = color("reset"); + } elsif ($str eq "add_line") { + # Default ANSI green + $ret = DiffHighlight::color_config('color.diff.new', color('bold') . color(2)); + } elsif ($str eq "remove_line") { + # Default ANSI red + $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + } elsif ($str eq "last_function") { + $ret = DiffHighlight::color_config('color.diff.func', color(146)); + } + + # Cache (memoize) the entry for later + $static_config->{$str} = $ret; + + return $ret; + } +} + +sub starts_with_ansi { + my $str = shift(); + + if ($str =~ /^$ansi_color_regex/) { + return 1; + } else { + return 0; + } +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 67f58c8fbbe965b83fadd870f9e6d7523d0ce45f Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 25 Oct 2018 15:34:45 -0700 Subject: [PATCH 279/443] Fix so-fancy#311 Windows doesn't have '\dev\null' Update DiffHighlight to use File::Spec->devnull() to avoid platform problems. --- lib/DiffHighlight.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 9cceb6c..7b5a6ae 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -4,6 +4,9 @@ use 5.008; use warnings FATAL => 'all'; use strict; use Encode; +use File::Spec; # For devnull + +my $NULL = File::Spec->devnull(); # get correct value for unix/win # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -88,7 +91,7 @@ sub highlight_stdin { # fallback, which means we will work even if git can't be run. sub color_config { my ($key, $default) = @_; - my $s = `git config --get-color $key 2>/dev/null`; + my $s = `git config --get-color $key 2>$NULL`; return length($s) ? $s : $default; } From 4786a8e3d000187e2f13c7c92ebd7fc124641879 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 24 Jan 2019 13:56:46 -0800 Subject: [PATCH 280/443] Update the README colors to reflect the screenshot --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92a6580..bb66d05 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ### Improved colors for the highlighted bits -The default Git colors are not optimal. We suggest the following colors instead. +The default Git colors are not optimal. The colors used for the screenshot above were: ```shell git config --global color.ui true @@ -34,7 +34,7 @@ git config --global color.diff-highlight.oldHighlight "red bold 52" git config --global color.diff-highlight.newNormal "green bold" git config --global color.diff-highlight.newHighlight "green bold 22" -git config --global color.diff.meta "yellow" +git config --global color.diff.meta "11" git config --global color.diff.frag "magenta bold" git config --global color.diff.commit "yellow bold" git config --global color.diff.old "red bold" From abbc3c7d2aa2dd28a873feef086ecd4412b6e5fe Mon Sep 17 00:00:00 2001 From: Bernhard Webstudio Date: Sun, 27 Jan 2019 18:12:12 +0100 Subject: [PATCH 281/443] Upgrade Travis MacOS version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b72c891..0004513 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ perl: matrix: include: - os: osx - osx_image: xcode7.3 + osx_image: xcode9.4 language: generic perl: 5.24 allow_failures: From 52da5860ae9a5f5d9c0bfd7f5126f00e665ab4a6 Mon Sep 17 00:00:00 2001 From: Bernhard Webstudio Date: Sun, 27 Jan 2019 18:16:50 +0100 Subject: [PATCH 282/443] Use travis homebrew addon to install necessary packages Instructions can also be found in the documentation: https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-macos --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0004513..be547f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,16 @@ matrix: allow_failures: - perl: "blead" +addons: + homebrew: + packages: + - perl + - shellcheck + - cpanminus + before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew update; - brew install shellcheck perl cpanminus; mkdir -p ~/perl5; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then From 69498e9e212ec720180829d9ce4bb1f1c4c8fa24 Mon Sep 17 00:00:00 2001 From: Bernhard Webstudio Date: Mon, 28 Jan 2019 10:05:13 +0100 Subject: [PATCH 283/443] Fix SC2230 This is the official fix of the schellcheck error SC2230 (https://github.com/koalaman/shellcheck/wiki/SC2230) preventing the tests to succeed on MacOS. --- report-bug.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/report-bug.sh b/report-bug.sh index ebfdac4..8f9fd8d 100755 --- a/report-bug.sh +++ b/report-bug.sh @@ -23,12 +23,12 @@ clipboard() { copy_cmd=clip fi # copy_cmd=clip - elif which pbcopy >/dev/null 2>&1; then + elif command -v pbcopy >/dev/null 2>&1; then copy_cmd="pbcopy" - elif which xclip >/dev/null 2>&1; then + elif command -v xclip >/dev/null 2>&1; then # copy_cmd="xclip -i -selection clipboard" copy_cmd="xclip" - elif which xsel >/dev/null 2>&1 ; then + elif command -v xsel >/dev/null 2>&1 ; then local copy_cmd="xsel -b" fi if [ -n "$copy_cmd" ] ;then From 8728c8badd72995f515b247da938e163d959cdf6 Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Thu, 7 Feb 2019 00:37:59 +0000 Subject: [PATCH 284/443] Fix doc typos --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bb66d05..ae4dcb2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # diff-so-fancy [![Circle CI build](https://circleci.com/gh/so-fancy/diff-so-fancy.svg?style=shield)](https://circleci.com/gh/so-fancy/diff-so-fancy) [![TravisCI build](https://travis-ci.org/so-fancy/diff-so-fancy.svg?branch=master)](https://travis-ci.org/so-fancy/diff-so-fancy) [![AppVeyor build](https://ci.appveyor.com/api/projects/status/github/so-fancy/diff-so-fancy?branch=master&svg=true)](https://ci.appveyor.com/project/stevemao/diff-so-fancy/branch/master) -`diff-so-fancy` strives to make your diff's **human** readable instead of machine readable. This helps improve code quality and help you spot defects faster. +`diff-so-fancy` strives to make your diffs **human** readable instead of machine readable. This helps improve code quality and helps you spot defects faster. ## Screenshot @@ -11,7 +11,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install -Installation is as simple as downloading the [diff-so-fancy](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. +Installation is as simple as downloading the [`diff-so-fancy`](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw/files/) or the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). `diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. @@ -19,6 +19,7 @@ Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw ## Usage Configure git to use `diff-so-fancy` for all diff output: + ```shell git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ``` @@ -26,6 +27,7 @@ git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" ### Improved colors for the highlighted bits The default Git colors are not optimal. The colors used for the screenshot above were: + ```shell git config --global color.ui true @@ -47,6 +49,7 @@ git config --global color.diff.whitespace "red reverse" ### markEmptyLines Should the first block of an empty line be colored. (Default: true) + ``` git config --bool --global diff-so-fancy.markEmptyLines false ``` @@ -54,6 +57,7 @@ git config --bool --global diff-so-fancy.markEmptyLines false ### changeHunkIndicators Simplify git header chunks to a more human readable format. (Default: true) + ``` git config --bool --global diff-so-fancy.changeHunkIndicators false ``` @@ -61,27 +65,30 @@ git config --bool --global diff-so-fancy.changeHunkIndicators false ### stripLeadingSymbols Should the pesky `+` or `-` at line-start be removed. (Default: true) + ``` git config --bool --global diff-so-fancy.stripLeadingSymbols false ``` ### useUnicodeRuler -By default the separator for the file header uses Unicode line drawing characters. If this is causing output errors on your terminal set this to `false` to use ASCII characters instead. (Default: true) +By default, the separator for the file header uses Unicode line-drawing characters. If this is causing output errors on your terminal, set this to `false` to use ASCII characters instead. (Default: true) + ``` git config --bool --global diff-so-fancy.useUnicodeRuler false ``` ### rulerWidth -By default the separator for the file header spans the full width of the terminal. Use this setting to set the width of the file header manually. +By default, the separator for the file header spans the full width of the terminal. Use this setting to set the width of the file header manually. + ``` git config --global diff-so-fancy.rulerWidth 47 # git log's commit header width ``` ## Contributing -Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make diff-so-fancy even *fancier*. +Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make `diff-so-fancy` even *fancier*. ### Other documentation From a9b02dc7cd86fed79908ad495a56d1e10d4f2da9 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Fri, 15 Feb 2019 12:11:46 -0800 Subject: [PATCH 285/443] v1.2.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97965ab..dbd724b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.2.0", + "version": "1.2.5", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From c12b91a4f3cf323645a9d1fd003de80f4b8e158a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sat, 16 Feb 2019 15:46:08 -0800 Subject: [PATCH 286/443] Add the term-colors.pl util for troubleshooting --- third_party/term-colors/term-colors.pl | 211 +++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 third_party/term-colors/term-colors.pl diff --git a/third_party/term-colors/term-colors.pl b/third_party/term-colors/term-colors.pl new file mode 100644 index 0000000..0ce3038 --- /dev/null +++ b/third_party/term-colors/term-colors.pl @@ -0,0 +1,211 @@ +#!/usr/bin/perl + +use strict; + +my $args = join(" ",@ARGV); +my ($perl) = $args =~ /--perl/; +my ($both) = $args =~ /--both/; + +# If we want both, we set perl also +if ($both) { + $perl = 1; +} + +# Term::ANSIColor didn't get 256 color constants until 4.0 +if ($perl && has_term_ansicolor(4.0)) { + require Term::ANSIColor; + Term::ANSIColor->import(':constants','color','uncolor'); + + #print "TERM::ANSIColor constant names:\n"; + term_ansicolor(); +} else { + my $cols = 120; + my $rows = 24; + if (-f '/bin/stty') { + ($rows,$cols) = split(/ /,`/bin/stty size`); + } + + my $section = 1; + my $grouping = 8; + + for (my $i=0;$i<256;$i++) { + print set_bcolor($i); # Set the background color + + if (needs_white($i)) { + print set_fcolor(15); # White + printf(" %03d ",$i); # Ouput the color number in white + } else { + print set_fcolor(0); # Black + printf(" %03d ",$i); # Ouput the color number in black + } + + print set_fcolor(); # Reset both colors + print " "; # Seperators + + if ($i == 15 || $i == 231) { + print set_bcolor(); # Reset + print "\n\n"; + $section = 0; + $grouping = 6; + } elsif ($section > 0 && ($section % $grouping == 0)) { + print set_bcolor(); # Reset + print "\n"; + } + + $section++; + } +} + +END { + print set_fcolor(); # Reset the colors + print "\n"; +} + +################################################################################# + +sub has_term_ansicolor { + my $version = shift(); + $version ||= 4; + + eval { + # Check if we have Term::ANSIColor version 4.0 + require Term::ANSIColor; + Term::ANSIColor->VERSION($version); + }; + + if ($@) { + return 0; + } else { + return 1; + } +} + +sub set_fcolor { + my $c = shift(); + + my $ret = ''; + if (!defined($c)) { $ret = "\e[0m"; } # Reset the color + else { $ret = "\e[38;5;${c}m"; } + + return $ret; +} + +sub set_bcolor { + my $c = shift(); + + my $ret = ''; + if (!defined($c)) { $ret = "\e[0m"; } # Reset the color + else { $ret .= "\e[48;5;${c}m"; } + + return $ret; +} + +sub highlight_string { + my $needle = shift(); + my $haystack = shift(); + my $color = shift() || 2; # Green if they don't pass in a color + + my $fc = set_fcolor($color); + my $reset = set_fcolor(); + + $haystack =~ s/$needle/$fc.$needle.$reset/e; + + return $haystack; +} + +sub get_color_mapping { + my $map = {}; + + for (my $i = 0; $i < 256; $i++) { + my $str = "\e[38;5;${i}m"; + my ($acc) = uncolor($str); + + $map->{$acc} = int($i); + } + + return $map; +} + +sub term_ansicolor { + my @colors = get_color_names(); + my $map = get_color_mapping(); + + my $absolute = 0; + my $group = 0; + my $grouping = 8; + + print "Showing Term::ANSIColor constant names\n\n"; + + foreach my $name (@colors) { + my $bg = "on_$name"; + my $map_num = int($map->{$name}); + my $perl_name = sprintf("%6s",$name); + my $ansi_number = sprintf("#%03i",$map_num); + + my $name_string = ""; + if ($both) { + $name_string = "$perl_name / $ansi_number"; + } else { + $name_string = "$perl_name"; + } + + if (needs_white($map_num)) { + print color($bg) . " " . color('bright_white') . $name_string . " "; + } else { + print color($bg) . " " . color("black") . $name_string . " "; + } + print color('reset') . " "; + + $absolute++; + $group++; + + if ($absolute == 16 || $absolute == 232) { + print "\n\n"; + $group = 0; + $grouping = 6; + } elsif ($group % $grouping == 0) { + print "\n"; + } + } +} + +sub get_color_names { + my @colors = (); + my ($r,$g,$b) = 0; + + for (my $i = 0; $i < 16; $i++) { + my $name = "ansi$i"; + push(@colors,$name); + } + + for ($r = 0; $r <= 5; $r++) { + for ($g = 0; $g <= 5; $g++) { + for ($b = 0; $b <= 5; $b++) { + my $name = "rgb$r$g$b"; + push(@colors,$name); + } + } + } + + for (my $i = 0; $i < 24; $i++) { + my $name = "grey$i"; + push(@colors,$name); + } + + return @colors; +} + +sub needs_white { + my $num = shift(); + + # Sorta lame, but it's a hard coded list of which background colors need a white foreground + my @white = qw(0 1 4 5 8 232 233 234 235 236 237 238 239 240 241 242 243 16 17 18 + 19 20 21 22 28 52 53 54 55 25 56 57 58 59 60 88 89 90 91 92 93 124 125 29 30 31 26 + 27 61 62 64 160 196 161 126 63 94 95 100 101 127 128 129 12 130 131 23 24); + + if (grep(/\b$num\b/,@white)) { + return 1, + } else { + return 0; + } +} From 77de3bd63b5832a6f2bf5a2e957c8b2125a8a6a0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 24 Jun 2019 10:09:49 -0700 Subject: [PATCH 287/443] Update DiffHighlight.pm to (hopefully) address #199 --- lib/DiffHighlight.pm | 112 +++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 7b5a6ae..9feb06c 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -3,10 +3,11 @@ package DiffHighlight; use 5.008; use warnings FATAL => 'all'; use strict; -use Encode; -use File::Spec; # For devnull -my $NULL = File::Spec->devnull(); # get correct value for unix/win +# Use the correct value for both UNIX and Windows (/dev/null vs nul) +use File::Spec; + +my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -21,41 +22,88 @@ our @NEW_HIGHLIGHT = ( $OLD_HIGHLIGHT[2], ); + + my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; my $BORING = qr/$COLOR|\s/; -# The patch portion of git log -p --graph should only ever have preceding | and -# not / or \ as merge history only shows up on the commit line. -my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; - my @removed; my @added; my $in_hunk; +my $graph_indent = 0; our $line_cb = sub { print @_ }; our $flush_cb = sub { local $| = 1 }; -sub handle_line { +# Count the visible width of a string, excluding any terminal color sequences. +sub visible_width { local $_ = shift; + my $ret = 0; + while (length) { + if (s/^$COLOR//) { + # skip colors + } elsif (s/^.//) { + $ret++; + } + } + return $ret; +} + +# Return a substring of $str, omitting $len visible characters from the +# beginning, where terminal color sequences do not count as visible. +sub visible_substr { + my ($str, $len) = @_; + while ($len > 0) { + if ($str =~ s/^$COLOR//) { + next + } + $str =~ s/^.//; + $len--; + } + return $str; +} + +sub handle_line { + my $orig = shift; + local $_ = $orig; + + # match a graph line that begins a commit + if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space + $COLOR?\*$COLOR?[ ] # a "*" with its trailing space + (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" + [ ]* # trailing whitespace for merges + /x) { + my $graph_prefix = $&; + + # We must flush before setting graph indent, since the + # new commit may be indented differently from what we + # queued. + flush(); + $graph_indent = visible_width($graph_prefix); + + } elsif ($graph_indent) { + if (length($_) < $graph_indent) { + $graph_indent = 0; + } else { + $_ = visible_substr($_, $graph_indent); + } + } if (!$in_hunk) { - $line_cb->($_); - $in_hunk = /^$GRAPH*$COLOR*\@\@ /; + $line_cb->($orig); + $in_hunk = /^$COLOR*\@\@ /; } - elsif (/^$GRAPH*$COLOR*-/) { - push @removed, $_; + elsif (/^$COLOR*-/) { + push @removed, $orig; } - elsif (/^$GRAPH*$COLOR*\+/) { - push @added, $_; + elsif (/^$COLOR*\+/) { + push @added, $orig; } else { - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - - $line_cb->($_); - $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; + flush(); + $line_cb->($orig); + $in_hunk = /^$COLOR*[\@ ]/; } # Most of the time there is enough output to keep things streaming, @@ -75,6 +123,8 @@ sub flush { # Flush any queued hunk (this can happen when there is no trailing # context in the final diff of the input). show_hunk(\@removed, \@added); + @removed = (); + @added = (); } sub highlight_stdin { @@ -125,7 +175,6 @@ sub show_hunk { sub highlight_pair { my @a = split_line(shift); my @b = split_line(shift); - my $opts = shift(); # Find common prefix, taking care to skip any ansi # color codes. @@ -170,18 +219,9 @@ sub highlight_pair { } } - my @OLD_COLOR_SPEC = @OLD_HIGHLIGHT; - my @NEW_COLOR_SPEC = @NEW_HIGHLIGHT; - - # If we're only highlight the differences temp disable the old/new normal colors - if ($opts->{'only_diff'}) { - $OLD_COLOR_SPEC[0] = ''; - $NEW_COLOR_SPEC[0] = ''; - } - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_COLOR_SPEC), - highlight_line(\@b, $pb, $sb, \@NEW_COLOR_SPEC); + return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), + highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); } else { return join('', @a), @@ -194,8 +234,8 @@ sub highlight_pair { # or "+" sub split_line { local $_ = shift; - return eval { $_ = Encode::decode('UTF-8', $_, 1); 1 } ? - map { Encode::encode('UTF-8', $_) } + return utf8::decode($_) ? + map { utf8::encode($_); $_ } map { /$COLOR/ ? $_ : (split //) } split /($COLOR+)/ : map { /$COLOR/ ? $_ : (split //) } @@ -240,8 +280,8 @@ sub is_pair_interesting { my $suffix_a = join('', @$a[($sa+1)..$#$a]); my $suffix_b = join('', @$b[($sb+1)..$#$b]); - return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || - $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || + return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || + visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || $suffix_a !~ /^$BORING*$/ || $suffix_b !~ /^$BORING*$/; } From 6cfa6257f4ea9e38510e0975d2dc46cdca148006 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 24 Jun 2019 10:45:32 -0700 Subject: [PATCH 288/443] Update the travis config to fix shellcheck stuff (hopefully) --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index be547f6..4487bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: perl perl: - blead - dev + - 5.30 + - 5.28 - 5.26 - 5.24 - 5.22 @@ -23,7 +25,6 @@ addons: homebrew: packages: - perl - - shellcheck - cpanminus before_install: @@ -36,7 +37,6 @@ before_install: sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; sudo apt-get -y update; - sudo apt-get -y install shellcheck; fi install: - cpanm --quiet --notest --local-lib=~/perl5 local::lib @@ -45,4 +45,3 @@ install: script: - perlcritic -1 -q --theme freenode diff-so-fancy - ./bats/bin/bats test/*.bats - - shellcheck *.sh From 7d42f0b0ec862b799466c5054396d027c14f83a0 Mon Sep 17 00:00:00 2001 From: Bernhard Webstudio Date: Mon, 24 Jun 2019 20:37:17 +0200 Subject: [PATCH 289/443] Attempt to fix Travis cpanminus MacOS issue --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index be547f6..306f610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,12 +28,11 @@ addons: before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git + - eval $(curl https://travis-perl.github.io/init) --perl; - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir -p ~/perl5; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - eval $(curl https://travis-perl.github.io/init) --perl; - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; sudo apt-get -y update; sudo apt-get -y install shellcheck; From 77e85cd9604561c284cb9b9f556acfacb5180159 Mon Sep 17 00:00:00 2001 From: Bernhard Webstudio Date: Mon, 24 Jun 2019 20:51:29 +0200 Subject: [PATCH 290/443] Attempt to fix Travis cpanminus MacOS issue Assuming the main issue to be the error message `Your Homebrew is outdated. Please run _brew update_.`, this commit employs the travis [configuration](https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-macos) `update` to automatically update homebrew. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 306f610..9c5df02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,14 +25,15 @@ addons: - perl - shellcheck - cpanminus + update: true before_install: - git clone --depth 1 https://github.com/sstephenson/bats.git - - eval $(curl https://travis-perl.github.io/init) --perl; - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir -p ~/perl5; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + eval $(curl https://travis-perl.github.io/init) --perl; sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe"; sudo apt-get -y update; sudo apt-get -y install shellcheck; From 0e792f9356ad7743dbba8a45529b7a77031eb7f2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 28 Jun 2019 14:58:17 -0700 Subject: [PATCH 291/443] Bump version --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index c4530a6..3bc2071 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.2.5"; +my $VERSION = "1.2.6"; ################################################################################# From 009d0cc855a5b206b07809d1c40eabdb6243b3fc Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 28 Jun 2019 14:59:27 -0700 Subject: [PATCH 292/443] Update the fatpacked version --- third_party/build_fatpack/diff-so-fancy | 115 ++++++++++++++++-------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 2e39e19..4df0aad 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -11,7 +11,11 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D use 5.008; use warnings FATAL => 'all'; use strict; - use Encode; + + # Use the correct value for both UNIX and Windows (/dev/null vs nul) + use File::Spec; + + my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. @@ -26,41 +30,88 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D $OLD_HIGHLIGHT[2], ); + + my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; my $BORING = qr/$COLOR|\s/; - # The patch portion of git log -p --graph should only ever have preceding | and - # not / or \ as merge history only shows up on the commit line. - my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; - my @removed; my @added; my $in_hunk; + my $graph_indent = 0; our $line_cb = sub { print @_ }; our $flush_cb = sub { local $| = 1 }; - sub handle_line { + # Count the visible width of a string, excluding any terminal color sequences. + sub visible_width { local $_ = shift; + my $ret = 0; + while (length) { + if (s/^$COLOR//) { + # skip colors + } elsif (s/^.//) { + $ret++; + } + } + return $ret; + } + + # Return a substring of $str, omitting $len visible characters from the + # beginning, where terminal color sequences do not count as visible. + sub visible_substr { + my ($str, $len) = @_; + while ($len > 0) { + if ($str =~ s/^$COLOR//) { + next + } + $str =~ s/^.//; + $len--; + } + return $str; + } + + sub handle_line { + my $orig = shift; + local $_ = $orig; + + # match a graph line that begins a commit + if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space + $COLOR?\*$COLOR?[ ] # a "*" with its trailing space + (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" + [ ]* # trailing whitespace for merges + /x) { + my $graph_prefix = $&; + + # We must flush before setting graph indent, since the + # new commit may be indented differently from what we + # queued. + flush(); + $graph_indent = visible_width($graph_prefix); + + } elsif ($graph_indent) { + if (length($_) < $graph_indent) { + $graph_indent = 0; + } else { + $_ = visible_substr($_, $graph_indent); + } + } if (!$in_hunk) { - $line_cb->($_); - $in_hunk = /^$GRAPH*$COLOR*\@\@ /; + $line_cb->($orig); + $in_hunk = /^$COLOR*\@\@ /; } - elsif (/^$GRAPH*$COLOR*-/) { - push @removed, $_; + elsif (/^$COLOR*-/) { + push @removed, $orig; } - elsif (/^$GRAPH*$COLOR*\+/) { - push @added, $_; + elsif (/^$COLOR*\+/) { + push @added, $orig; } else { - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - - $line_cb->($_); - $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; + flush(); + $line_cb->($orig); + $in_hunk = /^$COLOR*[\@ ]/; } # Most of the time there is enough output to keep things streaming, @@ -80,6 +131,8 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # Flush any queued hunk (this can happen when there is no trailing # context in the final diff of the input). show_hunk(\@removed, \@added); + @removed = (); + @added = (); } sub highlight_stdin { @@ -96,7 +149,7 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # fallback, which means we will work even if git can't be run. sub color_config { my ($key, $default) = @_; - my $s = `git config --get-color $key 2>/dev/null`; + my $s = `git config --get-color $key 2>$NULL`; return length($s) ? $s : $default; } @@ -130,7 +183,6 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D sub highlight_pair { my @a = split_line(shift); my @b = split_line(shift); - my $opts = shift(); # Find common prefix, taking care to skip any ansi # color codes. @@ -175,18 +227,9 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D } } - my @OLD_COLOR_SPEC = @OLD_HIGHLIGHT; - my @NEW_COLOR_SPEC = @NEW_HIGHLIGHT; - - # If we're only highlight the differences temp disable the old/new normal colors - if ($opts->{'only_diff'}) { - $OLD_COLOR_SPEC[0] = ''; - $NEW_COLOR_SPEC[0] = ''; - } - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_COLOR_SPEC), - highlight_line(\@b, $pb, $sb, \@NEW_COLOR_SPEC); + return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), + highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); } else { return join('', @a), @@ -199,8 +242,8 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # or "+" sub split_line { local $_ = shift; - return eval { $_ = Encode::decode('UTF-8', $_, 1); 1 } ? - map { Encode::encode('UTF-8', $_) } + return utf8::decode($_) ? + map { utf8::encode($_); $_ } map { /$COLOR/ ? $_ : (split //) } split /($COLOR+)/ : map { /$COLOR/ ? $_ : (split //) } @@ -245,8 +288,8 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D my $suffix_a = join('', @$a[($sa+1)..$#$a]); my $suffix_b = join('', @$b[($sb+1)..$#$b]); - return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || - $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || + return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || + visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || $suffix_a !~ /^$BORING*$/ || $suffix_b !~ /^$BORING*$/; } @@ -289,7 +332,7 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.2.5"; +my $VERSION = "1.2.6"; ################################################################################# From bc5ea917c57410a18caf6c25a84e6cc2837c9084 Mon Sep 17 00:00:00 2001 From: Charles Strahan Date: Sat, 29 Jun 2019 19:43:22 -0500 Subject: [PATCH 293/443] Don't force bold for last function Also, reset color so e.g. bold from the hunk header doesn't carry over to the last function. --- diff-so-fancy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3bc2071..5bc1a2f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -249,7 +249,7 @@ sub do_dsf_stuff { # Last function has it's own color my $last_function_color = get_config_color("last_function"); - print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; + print "@ $last_file_seen:$start_line \@${reset_color}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### @@ -810,7 +810,7 @@ sub color { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); } elsif ($str eq "last_function") { - $ret = DiffHighlight::color_config('color.diff.func', color(146)); + $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); } # Cache (memoize) the entry for later From 5cf50ae3a72ec29f1d3fd528be40e95be13d80b7 Mon Sep 17 00:00:00 2001 From: ryenus Date: Mon, 5 Aug 2019 22:46:25 +0800 Subject: [PATCH 294/443] avoid line wrapping in less When line numbers are shown in less, the horizontal ruler would wrap around if line wrapping is turned on, there're 2 options: 1. turn off line wrapping in less, using `-S` 2. decrease horizontal_rule width, e.g.: `$width -= 8`, to accommodate line numbers up to 4 digits. I added `-S`, but It should be fine to use either here. --- pro-tips.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pro-tips.md b/pro-tips.md index 9e24652..65b9a5e 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -7,7 +7,7 @@ You can do also do a one-off or a specific `diff-so-fancy` alias: git diff --color | diff-so-fancy git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ -'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFX; }; f' +'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFXS; }; f' ``` #### Opting-out @@ -34,7 +34,7 @@ You can pre-seed your `less` pager with a search pattern, so you can move between files with `n`/`N` keys: ```ini [pager] - diff = diff-so-fancy | less --tabs=4 -RFX --pattern '^(Date|added|deleted|modified): ' + diff = diff-so-fancy | less --tabs=4 -RFXS --pattern '^(Date|added|deleted|modified): ' ``` #### Zsh plugin providing diff-so-fancy From 7f47044a16cf300a1de49c0ab3f026f68956cf60 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 25 Jul 2019 11:39:07 -0700 Subject: [PATCH 295/443] Better support for standalone diffs The fragment color wasn't being picked up properly --- diff-so-fancy | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 5bc1a2f..41c3b80 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -248,8 +248,22 @@ sub do_dsf_stuff { my $start_line = start_line_calc($new_offset,$new_count); # Last function has it's own color - my $last_function_color = get_config_color("last_function"); - print "@ $last_file_seen:$start_line \@${reset_color}${last_function_color}${remain}${reset_color}\n"; + my $last_function_color = ""; + if ($remain) { + $last_function_color = get_config_color("last_function"); + } + + # Check to see if we have the color for the fragment from git + if ($5 =~ /\e\[\d/) { + #print "Has ANSI color for fragment\n"; + } else { + # We don't have the ANSI sequence so we shell out to get it + #print "No ANSI color for fragment\n"; + my $frag_color = get_config_color("fragment"); + print $frag_color; + } + + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### @@ -798,6 +812,8 @@ sub color { return $static_config->{$str}; } + #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); + if ($str eq "meta") { # Default ANSI yellow $ret = DiffHighlight::color_config('color.diff.meta', color(11)); @@ -809,6 +825,8 @@ sub color { } elsif ($str eq "remove_line") { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + } elsif ($str eq "fragment") { + $ret = DiffHighlight::color_config('color.diff.frag', color('13_bold')); } elsif ($str eq "last_function") { $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); } From 19d85697d5f95e2425c62dcb1916cad8febdcdf2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 25 Jul 2019 11:39:34 -0700 Subject: [PATCH 296/443] Start work on 1.2.7 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 41c3b80..de9c80e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.2.6"; +my $VERSION = "1.2.7"; ################################################################################# From d92fbaae93119457e8d0057d5abc4b4e8cbea264 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 9 Aug 2019 11:14:11 -0700 Subject: [PATCH 297/443] Make sure we land in the $in_hunk code. Addresses #341 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index de9c80e..be4c4a0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -225,7 +225,7 @@ sub do_dsf_stuff { ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + } elsif ($line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { $in_hunk = 1; my $hunk_header = $4; my $remain = bleach_text($5); From 8f39d5406ab0191d8c7e6a2887e00632db8b4d94 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 9 Aug 2019 11:49:53 -0700 Subject: [PATCH 298/443] Really fix chunks --- diff-so-fancy | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index be4c4a0..bea8c5e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -225,8 +225,13 @@ sub do_dsf_stuff { ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## - } elsif ($line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; + } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + + print $line; + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + my $hunk_header = $4; my $remain = bleach_text($5); From ac3ea382cad012cc10e057034c3c3f78d26372a0 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 28 Aug 2019 11:31:16 -0700 Subject: [PATCH 299/443] 1.2.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d329017..782991a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "diff-so-fancy", - "version": "1.2.0", + "version": "1.2.7", "lockfileVersion": 1 } diff --git a/package.json b/package.json index dbd724b..e35d13f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.2.5", + "version": "1.2.7", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From 05e2a1aca01746ed5de24e27f1ca7832393dc216 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 28 Aug 2019 11:35:49 -0700 Subject: [PATCH 300/443] docs: update shellcheck cmd --- hacking-and-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hacking-and-testing.md b/hacking-and-testing.md index 27558e7..9a11c0e 100644 --- a/hacking-and-testing.md +++ b/hacking-and-testing.md @@ -33,4 +33,4 @@ You can lint your scripts via shellcheck, our CI bots will also check. ```sh brew install shellcheck -shellcheck diff-so-fancy update-deps.sh +shellcheck *.sh From f65cbdf84db4c21ee43ab25cc9a937b697fd34da Mon Sep 17 00:00:00 2001 From: Martijn Engler Date: Sat, 23 Nov 2019 21:49:53 +0100 Subject: [PATCH 301/443] Add info about using dsf as a "normal" diff tool diff-so-fancy is great for git of course, but it's also very nice to use for just normal diffs between two files Fixes #328 --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index ae4dcb2..a8658f0 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,26 @@ Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw ## Usage +diff-so-fancy can be used with the standard `diff` tool, and can even be configured as the default for diffs with git. + +### With diff + +Use `-u` with diff to for unified output, and pipe the output to `diff-so-fancy`: + +```shell +diff -u file_a file_b | diff-so-fancy +``` + +You could, of course, create a function for it too. In your `~/.bashrc` or `~/.zshrcz` for example: + +```shell +function dsf() { diff -u $1 $2 | diff-so-fancy; } +``` + +And than you can just type `dsf file_a file_b` to get nice looking diff output. + +### With git + Configure git to use `diff-so-fancy` for all diff output: ```shell From 7cd8f4cf66e9dfa0b7b3478debe059613152b4ff Mon Sep 17 00:00:00 2001 From: Martijn Engler Date: Tue, 26 Nov 2019 20:40:33 +0100 Subject: [PATCH 302/443] Fix potential issue in dsf alias with spaces If there are no quotes around the arguments the dsf alias won't work with spaces when used in bash. (Does seem to work in zsh.) Goes to issue #328 / PR #358 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8658f0..8528958 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ diff -u file_a file_b | diff-so-fancy You could, of course, create a function for it too. In your `~/.bashrc` or `~/.zshrcz` for example: ```shell -function dsf() { diff -u $1 $2 | diff-so-fancy; } +function dsf() { diff -u "$1" "$2" | diff-so-fancy; } ``` And than you can just type `dsf file_a file_b` to get nice looking diff output. From 24f89240d21187993cdc2e2d21da4144b9792441 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 20 Dec 2019 15:39:26 -0800 Subject: [PATCH 303/443] Potential fix for #360 (files names with spaces) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index bea8c5e..aceb385 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -165,7 +165,7 @@ sub do_dsf_stuff { # Look for the filename # ######################### # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) { + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { From 6c1989f4758dcae0080892100c1f3b21a8e73082 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 20 Dec 2019 15:49:48 -0800 Subject: [PATCH 304/443] Add a test for #360 --- test/bugs.bats | 7 +++++++ test/fixtures/file_with_space.diff | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 test/fixtures/file_with_space.diff diff --git a/test/bugs.bats b/test/bugs.bats index e2e8fd1..5fac5e6 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -16,3 +16,10 @@ empty_remove_highlight="" assert_output --partial "show: function($empty_remove_highlight)" assert_output --partial "{!Document} */ (WebInspector.Dialog._modalHostView.element.ownerDocument$empty_remove_highlight)" } + +@test "File with space in the name (#360)" { + output=$( load_fixture "file_with_space" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 1 --regexp "added:.*a b" +} diff --git a/test/fixtures/file_with_space.diff b/test/fixtures/file_with_space.diff new file mode 100644 index 0000000..3c38de0 --- /dev/null +++ b/test/fixtures/file_with_space.diff @@ -0,0 +1,3 @@ +diff --git a/a b b/a b +new file mode 100644 +index 0000000..e69de29 From 4910572d0e287c7642b052b74d452164f8fa8930 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 23 Apr 2020 08:20:19 -0700 Subject: [PATCH 305/443] Reset the color after a horizontal line to address #360 --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index aceb385..3563bfb 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -594,7 +594,7 @@ sub horizontal_rule { } # Draw the line - my $ret = $color . ($dash x $width) . "\n"; + my $ret = $color . ($dash x $width) . "$reset_color\n"; return $ret; } From b18ec0273e712e63116dbc983c9e7270f4155f41 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 23 Apr 2020 08:32:16 -0700 Subject: [PATCH 306/443] Add some usage text to help address #354 --- diff-so-fancy | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3563bfb..787f637 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -671,9 +671,12 @@ sub usage { $out .= "Usage: -git diff --color | diff-so-fancy # Use d-s-f on one diff -diff-so-fancy --colors # View the commands to set the recommended colors -diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +git diff --color | diff-so-fancy # Use d-s-f on one diff +cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file +diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output + +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; From 1af2be44c4cb89e1d4e90e3cc74a56f1065051f6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 23 Apr 2020 11:06:40 -0700 Subject: [PATCH 307/443] Add a test for #366 --- test/bugs.bats | 8 ++++++++ test/fixtures/add_remove_empty_lines.diff | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 test/fixtures/add_remove_empty_lines.diff diff --git a/test/bugs.bats b/test/bugs.bats index 5fac5e6..819731e 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -23,3 +23,11 @@ empty_remove_highlight="" assert_line --index 1 --regexp "added:.*a b" } + +@test "Vanilla diff with add/remove empty lines (#366)" { + output=$( load_fixture "add_remove_empty_lines" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 5 --partial "[1;32" + assert_line --index 8 --partial "[1;31" +} diff --git a/test/fixtures/add_remove_empty_lines.diff b/test/fixtures/add_remove_empty_lines.diff new file mode 100644 index 0000000..f6d0b10 --- /dev/null +++ b/test/fixtures/add_remove_empty_lines.diff @@ -0,0 +1,9 @@ +--- one.txt 2020-04-23 10:15:29.193452325 -0700 ++++ two.txt 2020-04-23 10:15:37.634463619 -0700 +@@ -1,5 +1,5 @@ + 1 ++ + 2 + 3 +- + 4 From 03c386e2d11917127b2a27db2521b3879b8ac856 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 23 Apr 2020 11:07:14 -0700 Subject: [PATCH 308/443] Bugfix for #366 --- diff-so-fancy | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 787f637..84c39a6 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -375,8 +375,19 @@ sub mark_empty_line { my $reset_color = "\e\\[0?m"; my $reset_escape = "\e\[m"; my $invert_color = "\e\[7m"; - - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + + # This captures lines that do not have any ANSI in them (raw vanilla diff) + if ($line eq "+\n") { + $line = $invert_color . $add_color . " " . color('reset') . "\n"; + # This captures lines that do not have any ANSI in them (raw vanilla diff) + } elsif ($line eq "-\n") { + $line = $invert_color . $del_color . " " . color('reset') . "\n"; + # This handles everything else + } else { + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + } return $line; } From 462d7ccd3b1ec68ea13083f086d96a99dbe0e1ad Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 24 Apr 2020 10:45:34 -0700 Subject: [PATCH 309/443] Add some error handling around tput We end up getting a lot of bug reports due to broken tput installations. This will wrapper tput and keep errors to a minimum --- diff-so-fancy | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 84c39a6..1af5172 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,6 +4,7 @@ my $VERSION = "1.2.7"; ################################################################################# +use 5.010; # Require Perl 5.10 for 'state' variables use File::Spec; # For catdir use File::Basename; # For dirname use Encode; # For handling UTF8 stuff @@ -588,7 +589,18 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - my $width = $ruler_width || `tput cols`; + + # Make width static so we only calculate it once + state $width; + if (!$width) { + my $tput = `tput cols 2>1`; + if (!$tput) { + print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; + $tput = 80; + } + + $width = $ruler_width || $tput; + } if (is_windows()) { $width--; From c3004ea4dac84749a3ed0f8062dce2a9f9319306 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 24 Apr 2020 11:04:36 -0700 Subject: [PATCH 310/443] Move terminal width calculation to it's own function for readability --- diff-so-fancy | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1af5172..e287b11 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -589,22 +589,7 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - - # Make width static so we only calculate it once - state $width; - if (!$width) { - my $tput = `tput cols 2>1`; - if (!$tput) { - print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; - $tput = 80; - } - - $width = $ruler_width || $tput; - } - - if (is_windows()) { - $width--; - } + my $width = get_terminal_width(); # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm #my $dash = "\x{2014}"; @@ -879,4 +864,34 @@ sub starts_with_ansi { } } +sub get_terminal_width { + # Make width static so we only calculate it once + state $width; + + if ($width) { + return $width; + } + + # If there is a ruler width in the config we use that + if ($ruler_width) { + $width = $ruler_width; + # Otherwise we check the terminal width using tput + } else { + my $tput = `tput cols`; + + if ($tput) { + $width = int($tput); + + if (is_windows()) { + $width--; + } + } else { + print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; + $width = 80; + } + } + + return $width; +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 0b863563136650b5464fdc304c5bdeba5a1b370b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 24 Apr 2020 11:10:51 -0700 Subject: [PATCH 311/443] Bump the version to 1.3.0 to avoid confusion --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index e287b11..dc04ac9 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.2.7"; +my $VERSION = "1.3.0"; ################################################################################# From 70d4d9dceaba4c8c76cd027ea92fe94619e239de Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 24 Apr 2020 11:20:38 -0700 Subject: [PATCH 312/443] Update README.md --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ae4dcb2..637933a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ `diff-so-fancy` strives to make your diffs **human** readable instead of machine readable. This helps improve code quality and helps you spot defects faster. - ## Screenshot Vanilla `git diff` vs `git` and `diff-so-fancy` @@ -11,10 +10,11 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` ## Install -Installation is as simple as downloading the [`diff-so-fancy`](https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/third_party/build_fatpack/diff-so-fancy) script to a directory in your path. -Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw/files/) or the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). +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. + +`diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch and Debian Linux. -`diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch Linux. +**Note:** Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw/files/) or the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). ## Usage @@ -86,6 +86,16 @@ By default, the separator for the file header spans the full width of the termin git config --global diff-so-fancy.rulerWidth 47 # git log's commit header width ``` +## The diff-so-fancy team + +| Person | Role | +| --------------------- | ---------------- | +| @scottchiefbaker | Project lead | +| @OJFord | Bug triage | +| @GenieTim | Travis OSX fixes | +| @AOS | Debian packager | +| @Stevemao/@Paul Irish | NPM release team | + ## Contributing Pull requests are quite welcome, and should target the [`next` branch](https://github.com/so-fancy/diff-so-fancy/tree/next). We are also looking for any feedback or ideas on how to make `diff-so-fancy` even *fancier*. From c0099d9e4f24f2968ea677c5c2c5cb7dfae66b51 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 24 Apr 2020 11:25:12 -0700 Subject: [PATCH 313/443] Bump the fatpacked version up to v1.3.0 --- third_party/build_fatpack/diff-so-fancy | 96 ++++++++++++++++++++----- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 4df0aad..8dc9a99 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -332,10 +332,11 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.2.6"; +my $VERSION = "1.3.0"; ################################################################################# +use 5.010; # Require Perl 5.10 for 'state' variables use File::Spec; # For catdir use File::Basename; # For dirname use Encode; # For handling UTF8 stuff @@ -497,7 +498,7 @@ sub do_dsf_stuff { # Look for the filename # ######################### # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) { + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { @@ -557,8 +558,13 @@ sub do_dsf_stuff { ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## + } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + + print $line; } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; + $in_hunk = 1; + my $hunk_header = $4; my $remain = bleach_text($5); @@ -580,7 +586,21 @@ sub do_dsf_stuff { my $start_line = start_line_calc($new_offset,$new_count); # Last function has it's own color - my $last_function_color = get_config_color("last_function"); + my $last_function_color = ""; + if ($remain) { + $last_function_color = get_config_color("last_function"); + } + + # Check to see if we have the color for the fragment from git + if ($5 =~ /\e\[\d/) { + #print "Has ANSI color for fragment\n"; + } else { + # We don't have the ANSI sequence so we shell out to get it + #print "No ANSI color for fragment\n"; + my $frag_color = get_config_color("fragment"); + print $frag_color; + } + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # @@ -688,8 +708,19 @@ sub mark_empty_line { my $reset_color = "\e\\[0?m"; my $reset_escape = "\e\[m"; my $invert_color = "\e\[7m"; - - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + + # This captures lines that do not have any ANSI in them (raw vanilla diff) + if ($line eq "+\n") { + $line = $invert_color . $add_color . " " . color('reset') . "\n"; + # This captures lines that do not have any ANSI in them (raw vanilla diff) + } elsif ($line eq "-\n") { + $line = $invert_color . $del_color . " " . color('reset') . "\n"; + # This handles everything else + } else { + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + } return $line; } @@ -890,11 +921,7 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - my $width = $ruler_width || `tput cols`; - - if (is_windows()) { - $width--; - } + my $width = get_terminal_width(); # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm #my $dash = "\x{2014}"; @@ -907,7 +934,7 @@ sub horizontal_rule { } # Draw the line - my $ret = $color . ($dash x $width) . "\n"; + my $ret = $color . ($dash x $width) . "$reset_color\n"; return $ret; } @@ -984,9 +1011,12 @@ sub usage { $out .= "Usage: -git diff --color | diff-so-fancy # Use d-s-f on one diff -diff-so-fancy --colors # View the commands to set the recommended colors -diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +git diff --color | diff-so-fancy # Use d-s-f on one diff +cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file +diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output + +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; @@ -1130,6 +1160,8 @@ sub color { return $static_config->{$str}; } + #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); + if ($str eq "meta") { # Default ANSI yellow $ret = DiffHighlight::color_config('color.diff.meta', color(11)); @@ -1141,8 +1173,10 @@ sub color { } elsif ($str eq "remove_line") { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + } elsif ($str eq "fragment") { + $ret = DiffHighlight::color_config('color.diff.frag', color('13_bold')); } elsif ($str eq "last_function") { - $ret = DiffHighlight::color_config('color.diff.func', color(146)); + $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); } # Cache (memoize) the entry for later @@ -1162,4 +1196,34 @@ sub starts_with_ansi { } } +sub get_terminal_width { + # Make width static so we only calculate it once + state $width; + + if ($width) { + return $width; + } + + # If there is a ruler width in the config we use that + if ($ruler_width) { + $width = $ruler_width; + # Otherwise we check the terminal width using tput + } else { + my $tput = `tput cols`; + + if ($tput) { + $width = int($tput); + + if (is_windows()) { + $width--; + } + } else { + print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; + $width = 80; + } + } + + return $width; +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From b67fe5ec6eb912043cd741d29a6ebf1561e844b2 Mon Sep 17 00:00:00 2001 From: Philippe Blain Date: Sun, 10 Nov 2019 11:26:23 -0500 Subject: [PATCH 314/443] docs: add 'color.diff.func' color to README The color for hunk headers (`color.diff.func`) is hardcoded in the script, and is not mentioned in the "Improved colors for the highlighted bits" section of the README. Add it there so that function headers with or without diff-so-fancy use the same color configuration. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ae4dcb2..cb34f34 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ git config --global color.diff-highlight.newHighlight "green bold 22" git config --global color.diff.meta "11" git config --global color.diff.frag "magenta bold" +git config --global color.diff.func "146 bold" git config --global color.diff.commit "yellow bold" git config --global color.diff.old "red bold" git config --global color.diff.new "green bold" From eef379ee43f860306310ba0843302c0077a4908b Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Tue, 2 Jun 2020 19:33:31 +1000 Subject: [PATCH 315/443] 1.3.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 782991a..a31466b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "diff-so-fancy", - "version": "1.2.7", + "version": "1.3.0", "lockfileVersion": 1 } diff --git a/package.json b/package.json index e35d13f..dc1c5c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.2.7", + "version": "1.3.0", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From 5391296dad26fd0b175cfe18688c085d58747d8a Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Thu, 27 Aug 2020 14:47:40 -0700 Subject: [PATCH 316/443] Add pro-tip about hg diff --- pro-tips.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pro-tips.md b/pro-tips.md index 9e24652..6567125 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -55,3 +55,12 @@ zplug "zdharma/zsh-diff-so-fancy", as:command, use:bin/git-dsf zgen zdharma/zsh-diff-so-fancy ``` +#### `hg` configuration + +You can configure `hg diff` output to use `diff-so-fancy` by adding this alias +to your `hgrc` file: + +``` +[alias] +diff = !HGPLAIN=1 $HG diff --pager=on --config pager.pager=diff-so-fancy $@ +``` From af73147369f8279abfcb2070c718a14b8c766142 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Wed, 21 Oct 2020 16:58:45 -0700 Subject: [PATCH 317/443] add delta as an alternative to the readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 637933a..9954be5 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ Pull requests are quite welcome, and should target the [`next` branch](https://g * [Hacking and Testing](hacking-and-testing.md) * [History](history.md) +## Alternatives + +* https://github.com/dandavison/delta + ## License MIT From 1848d19a2114d4c4cd0cd564b6d5d3152dd8bc6f Mon Sep 17 00:00:00 2001 From: Sean Wei Date: Sat, 31 Oct 2020 09:05:20 +0800 Subject: [PATCH 318/443] Add syntax highlight to readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9954be5..317b3a6 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ git config --global color.diff.whitespace "red reverse" Should the first block of an empty line be colored. (Default: true) -``` +```shell git config --bool --global diff-so-fancy.markEmptyLines false ``` @@ -58,7 +58,7 @@ git config --bool --global diff-so-fancy.markEmptyLines false Simplify git header chunks to a more human readable format. (Default: true) -``` +```shell git config --bool --global diff-so-fancy.changeHunkIndicators false ``` @@ -66,7 +66,7 @@ git config --bool --global diff-so-fancy.changeHunkIndicators false Should the pesky `+` or `-` at line-start be removed. (Default: true) -``` +```shell git config --bool --global diff-so-fancy.stripLeadingSymbols false ``` @@ -74,7 +74,7 @@ git config --bool --global diff-so-fancy.stripLeadingSymbols false By default, the separator for the file header uses Unicode line-drawing characters. If this is causing output errors on your terminal, set this to `false` to use ASCII characters instead. (Default: true) -``` +```shell git config --bool --global diff-so-fancy.useUnicodeRuler false ``` @@ -82,7 +82,7 @@ git config --bool --global diff-so-fancy.useUnicodeRuler false By default, the separator for the file header spans the full width of the terminal. Use this setting to set the width of the file header manually. -``` +```shell git config --global diff-so-fancy.rulerWidth 47 # git log's commit header width ``` From eb4a5e7635328affdb3289d206ee629b4b79ff61 Mon Sep 17 00:00:00 2001 From: Eric Nielsen Date: Thu, 5 Nov 2020 21:36:10 -0500 Subject: [PATCH 319/443] Don't include ansi color codes in file1 and file2 when processing renamed files. Use the same `(.+?)(\e|\t|$)` regexp used in other similar parts of the code. The motivation behind this change is to be able to process the file1 and file2 strings (e.g. get their length) without the interference of the extra ansi color codes. Also, the resulting d-s-f output is smaller with this change (34 bytes less for each "renamed: X to Y" line). --- diff-so-fancy | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index dc04ac9..69277e8 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -318,10 +318,10 @@ sub do_dsf_stuff { } my $next = shift(@$input); - my ($file1) = $next =~ /rename from (.+)/; + my ($file1) = $next =~ /rename from (.+?)(\e|\t|$)/; $next = shift(@$input); - my ($file2) = $next =~ /rename to (.+)/; + my ($file2) = $next =~ /rename to (.+?)(\e|\t|$)/; if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not @@ -625,14 +625,10 @@ sub file_change_string { # If the files aren't the same it's a rename } elsif ($file_1 ne $file_2) { my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); + # highlight_pair already includes reset_color, but adds newline characters that need to be trimmed off $old = trim($old); $new = trim($new); - - # highlight_pair resets the colors, but we want it to be the meta color - $old =~ s/(\e0?\[m)/$1$meta_color/g; - $new =~ s/(\e0?\[m)/$1$meta_color/g; - - return "renamed: $old to $new"; + return "renamed: $old$meta_color to $new" # Something we haven't thought of yet } else { return "$file_1 -> $file_2"; From a2671f036440bb09c0e7540c42a51cbff0e1afaf Mon Sep 17 00:00:00 2001 From: rmacklin <1863540+rmacklin@users.noreply.github.com> Date: Sat, 28 Nov 2020 18:12:05 -0800 Subject: [PATCH 320/443] Split up one-off and alias code samples in pro-tips.md --- pro-tips.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pro-tips.md b/pro-tips.md index 65b9a5e..5c23d44 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -2,10 +2,12 @@ #### One-off fanciness or a specific diff-so-fancy alias -You can do also do a one-off or a specific `diff-so-fancy` alias: +You can do also do a one-off: ```shell git diff --color | diff-so-fancy - +``` +or configure an alias to use `diff-so-fancy`: +```shell git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ '&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFXS; }; f' ``` From 2651b15f23fc595496e542104844c0715d9d2420 Mon Sep 17 00:00:00 2001 From: rmacklin <1863540+rmacklin@users.noreply.github.com> Date: Sat, 28 Nov 2020 18:31:43 -0800 Subject: [PATCH 321/443] Replace complicated dsf alias with simple alias and corresponding pager This setup makes use of an additional `pager` config for the `dsf` alias to avoid having to spawn a new shell command, forward the arguments, and deal with internal details like `$GIT_PREFIX`. --- pro-tips.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pro-tips.md b/pro-tips.md index 5c23d44..9c1752b 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -6,10 +6,10 @@ You can do also do a one-off: ```shell git diff --color | diff-so-fancy ``` -or configure an alias to use `diff-so-fancy`: +or configure an alias and a corresponding pager to use `diff-so-fancy`: ```shell -git config --global alias.dsf '!f() { [ -z "$GIT_PREFIX" ] || cd "$GIT_PREFIX" '\ -'&& git diff --color "$@" | diff-so-fancy | less --tabs=4 -RFXS; }; f' +git config --global alias.dsf "diff --color" +git config --global pager.dsf "diff-so-fancy | less --tabs=4 -RFXS" ``` #### Opting-out From 4f5a0e289d09c0bd999578f60bcc865d439f04fa Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 6 Dec 2020 20:01:45 -0800 Subject: [PATCH 322/443] Fix inconsistently placed color reset codes Sometimes the reset code ended up on a line by itself, and caused issues with total number of lines (important for `git add --patch`). --- diff-so-fancy | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 84c39a6..2c6f1f2 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -543,16 +543,23 @@ sub strip_leading_indicators { if ($manually_color_lines) { if (defined($5) && $5 eq "+") { my $add_line_color = get_config_color("add_line"); - $line = $add_line_color . $line . $reset_color; + $line = $add_line_color . insert_reset_at_line_end($line); } elsif (defined($5) && $5 eq "-") { my $remove_line_color = get_config_color("remove_line"); - $line = $remove_line_color . $line . $reset_color; + $line = $remove_line_color . insert_reset_at_line_end($line); } } return $line; } +# Insert the color reset code at end of line, but before any newlines +sub insert_reset_at_line_end { + my $line = shift(); + $line =~ s/^(.*)([\n\r]+)?$/${1}${reset_color}${2}/; + return $line; +} + # Count the number of a given char in a string sub char_count { my ($needle,$str) = @_; From 13d3f8949e15dd62f6b49bc652fe94af6a9bfc79 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 6 Dec 2020 21:58:57 -0800 Subject: [PATCH 323/443] Add a patch mode for `git add --patch` In this new patch mode, every time a line is consumed, we print out an empty line in its place. This keeps the same number of lines in the output, and appeases git. --- diff-so-fancy | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 2c6f1f2..a5d78f7 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -17,6 +17,7 @@ use warnings FATAL => 'all'; my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; +my $patch_mode = 0; my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); @@ -51,6 +52,13 @@ if ($args->{color_on}) { $color_forced = 1; } +# `git add --patch` requries our output to match the number of lines from the +# input. So, when patch mode is active, we print out empty lines to pad our +# output to match any lines we've consumed. +if ($args->{patch}) { + $patch_mode = 1; +} + # We only process ARGV if we don't have STDIN if (!$has_stdin) { if ($args->{v} || $args->{version}) { @@ -178,6 +186,10 @@ sub do_dsf_stuff { $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). $in_hunk = 0; + if ($patch_mode) { + # we are consuming one line, and the debt must be paid + print "\n"; + } ######################################## # Find the first file: --- a/README.md # ######################################## @@ -281,6 +293,9 @@ sub do_dsf_stuff { } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { # Don't print the line (i.e. remove it from the output); $last_file_mode = "delete"; + if ($patch_mode) { + print "\n"; + } ################################ # Look for binary file changes # ################################ @@ -301,6 +316,10 @@ sub do_dsf_stuff { } my ($new_mode) = $next =~ m/new mode (\d+)/; + + if ($patch_mode) { + print "\n"; + } print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; ############### From 8d4ef03ac848c3c383a9e7e1ef4a659172ae0bcc Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 8 Dec 2020 14:46:22 -0800 Subject: [PATCH 324/443] update usage for new flag --- diff-so-fancy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index a5d78f7..76f8611 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -714,9 +714,13 @@ diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output diff-so-fancy --colors # View the commands to set the recommended colors diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +diff-so-fancy --patch # Use diff-so-fancy in patch mode (interoperable with `git add --patch`) # Configure git to use d-s-f for *all* diff operations -git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; +git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" + +# Configure git to use d-s-f for `git add --patch` +git config --global interactive.diffFilter \"diff-so-fancy --patch\"\n"; return $out; } From ed24bc15b1e789f7c7304898016f255588e95c96 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 8 Dec 2020 14:48:01 -0800 Subject: [PATCH 325/443] fix typo per pr review --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 76f8611..077448a 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -52,7 +52,7 @@ if ($args->{color_on}) { $color_forced = 1; } -# `git add --patch` requries our output to match the number of lines from the +# `git add --patch` requires our output to match the number of lines from the # input. So, when patch mode is active, we print out empty lines to pad our # output to match any lines we've consumed. if ($args->{patch}) { From 7d261466f591840a0c5ac9e75c580f6c1f683c03 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Dec 2020 19:09:49 -0800 Subject: [PATCH 326/443] Add the `--patch` mode stuff to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fa6dcbe..344f591 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Configure git to use `diff-so-fancy` for all diff output: ```shell git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" +git config --global interactive.diffFilter "diff-so-fancy --patch" ``` ### Improved colors for the highlighted bits From 9eb28170e3187ce77c41df97e698d3fba2b1c52d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Dec 2020 19:10:44 -0800 Subject: [PATCH 327/443] Bump the version --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 2445ce8..6345876 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.3.0"; +my $VERSION = "1.4.0"; ################################################################################# From 8a8f8272a6251052f584ca19db9470782aabea27 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 22 Dec 2020 17:35:30 -0800 Subject: [PATCH 328/443] Include new files in patch mode It turns out that new files can be part of `git add --patch` by using `git add --intent-to-add` on them first. --- diff-so-fancy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 6345876..1986a86 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -288,6 +288,9 @@ sub do_dsf_stuff { } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { # Don't print the line (i.e. remove it from the output); $last_file_mode = "add"; + if ($patch_mode) { + print "\n"; + } ###################################### # Remove any delete file permissions # ###################################### From 5dc67382ba867881aeb2ea84a976957ce97c0679 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sun, 27 Dec 2020 12:31:43 -0800 Subject: [PATCH 329/443] Feature: Add a --debug option to help troubleshooting --- diff-so-fancy | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 6345876..0de88df 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -53,6 +53,11 @@ if ($args->{color_on}) { $color_forced = 1; } +if ($args->{debug}) { + show_debug_info(); + exit(); +} + # `git add --patch` requires our output to match the number of lines from the # input. So, when patch mode is active, we print out empty lines to pad our # output to match any lines we've consumed. @@ -924,4 +929,31 @@ sub get_terminal_width { return $width; } +sub show_debug_info { + my $git_ver = trim(`git --version`); + $git_ver =~ s/[^\d.]//g; + + print "Diff-so-fancy : v$VERSION\n"; + print "Git : v$git_ver\n"; + print "Perl : $^V\n"; + print "\n"; + + print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; + print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; + print "Terminal width : " . get_terminal_width() . "\n"; + print "Terminal \$LANG : " . $ENV{LANG} . "\n"; + print "Is Windows : " . yes_no(is_windows()) . "\n"; + print "Operating System: $^O\n"; +} + +sub yes_no { + my $val = shift(); + + if ($val) { + return "Yes"; + } else { + return "No"; + } +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From d96b105b7cc297d11e1a93cabc00dc498018c717 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 29 Dec 2020 13:48:41 -0800 Subject: [PATCH 330/443] Include the charset LESS uses in --debug --- diff-so-fancy | 1 + 1 file changed, 1 insertion(+) diff --git a/diff-so-fancy b/diff-so-fancy index 581659a..b617b91 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -945,6 +945,7 @@ sub show_debug_info { print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; print "Terminal width : " . get_terminal_width() . "\n"; print "Terminal \$LANG : " . $ENV{LANG} . "\n"; + print "Less Charset : " . get_less_charset() . "\n"; print "Is Windows : " . yes_no(is_windows()) . "\n"; print "Operating System: $^O\n"; } From 75587c6115cdda03adb2c288a14b805ebd29527c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 29 Dec 2020 14:26:35 -0800 Subject: [PATCH 331/443] Further expand --debug --- diff-so-fancy | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index b617b91..064a1bc 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -507,11 +507,15 @@ sub in_unit_test { sub get_less_charset { my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); - foreach (@less_char_vars) { - return $ENV{$_} if defined $ENV{$_}; + foreach my $key (@less_char_vars) { + my $val = $ENV{$key}; + + if (defined $val) { + return ($key, $val); + } } - return ""; + return (); } sub should_print_unicode { @@ -521,7 +525,7 @@ sub should_print_unicode { } # Otherwise, assume we're piping to less(1) - my $less_charset = get_less_charset(); + my ($less_env_var, $less_charset) = get_less_charset(); if ($less_charset =~ /utf-?8/i) { return 1; } @@ -933,19 +937,24 @@ sub get_terminal_width { } sub show_debug_info { + my @less = get_less_charset(); my $git_ver = trim(`git --version`); - $git_ver =~ s/[^\d.]//g; + $git_ver =~ s/[^\d.]//g; print "Diff-so-fancy : v$VERSION\n"; print "Git : v$git_ver\n"; print "Perl : $^V\n"; print "\n"; - print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; - print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; print "Terminal width : " . get_terminal_width() . "\n"; print "Terminal \$LANG : " . $ENV{LANG} . "\n"; - print "Less Charset : " . get_less_charset() . "\n"; + print "\n"; + print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; + print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; + print "\n"; + print "Less Charset Var: " . ($less[0] // "") . "\n"; + print "Less Charset : " . ($less[1] // "") . "\n"; + print "\n"; print "Is Windows : " . yes_no(is_windows()) . "\n"; print "Operating System: $^O\n"; } From c94e4de55b67cb32698a1219272c925831201e6a Mon Sep 17 00:00:00 2001 From: Dots Util Date: Sun, 3 Jan 2021 03:25:18 +0200 Subject: [PATCH 332/443] Update pro-tips.md --- pro-tips.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pro-tips.md b/pro-tips.md index 65b9a5e..c08205b 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -41,12 +41,12 @@ between files with `n`/`N` keys: Zsh plugin [zdharma/zsh-diff-so-fancy](https://github.com/zdharma/zsh-diff-so-fancy) has this project as a submodule so installing the plugin installs `diff-so-fancy`. The plugin provides -subcommand `git dsf` out of the box. Installation with Zplugin, Zplug and Zgen: +subcommand `git dsf` out of the box. Installation with Zinit, Zplug and Zgen: ```zsh -# Zplugin -zplugin ice as"program" pick"bin/git-dsf" -zplugin light zdharma/zsh-diff-so-fancy +# zinit +zinit ice lucid as"program" pick"bin/git-dsf" +zinit load zdharma/zsh-diff-so-fancy # Or zplug zplug "zdharma/zsh-diff-so-fancy", as:command, use:bin/git-dsf From 93f360bd0c7f86d423bc2ade391bcde6109c95ae Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 10 Feb 2021 09:03:56 -0800 Subject: [PATCH 333/443] Update the README --- README.md | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8d14679..e9af566 100644 --- a/README.md +++ b/README.md @@ -18,24 +18,6 @@ Installation is as simple as cloning this repo and then putting the `diff-so-fan ## Usage -diff-so-fancy can be used with the standard `diff` tool, and can even be configured as the default for diffs with git. - -### With diff - -Use `-u` with diff to for unified output, and pipe the output to `diff-so-fancy`: - -```shell -diff -u file_a file_b | diff-so-fancy -``` - -You could, of course, create a function for it too. In your `~/.bashrc` or `~/.zshrcz` for example: - -```shell -function dsf() { diff -u "$1" "$2" | diff-so-fancy; } -``` - -And than you can just type `dsf file_a file_b` to get nice looking diff output. - ### With git Configure git to use `diff-so-fancy` for all diff output: @@ -66,6 +48,14 @@ git config --global color.diff.new "green bold" git config --global color.diff.whitespace "red reverse" ``` +### With diff + +Use `-u` with `diff` for unified output, and pipe the output to `diff-so-fancy`: + +```shell +diff -u file_a file_b | diff-so-fancy +``` + ## Options ### markEmptyLines From 0a0cf1ce31a90a297a4fb292480cb95a0a850633 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 10 Feb 2021 13:13:09 -0800 Subject: [PATCH 334/443] Fix a failing bats test --- diff-so-fancy | 1 + 1 file changed, 1 insertion(+) diff --git a/diff-so-fancy b/diff-so-fancy index 064a1bc..a9f27cf 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -526,6 +526,7 @@ sub should_print_unicode { # Otherwise, assume we're piping to less(1) my ($less_env_var, $less_charset) = get_less_charset(); + $less_charset //= ""; if ($less_charset =~ /utf-?8/i) { return 1; } From 94f2638efde8e8568114cd2810c000be26bc7ccb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 10 Feb 2021 13:13:33 -0800 Subject: [PATCH 335/443] Bump version so we know we're working on new stuff --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index a9f27cf..511ccc0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.4.0"; +my $VERSION = "1.4.1"; ################################################################################# From 29a0346c01d7b45548f085f985739076150686d5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 11 Feb 2021 09:31:41 -0800 Subject: [PATCH 336/443] Don't include the fatpacked binary anymore... just the script to build it --- third_party/build_fatpack/diff-so-fancy | 1229 ----------------------- 1 file changed, 1229 deletions(-) delete mode 100755 third_party/build_fatpack/diff-so-fancy diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy deleted file mode 100755 index 8dc9a99..0000000 --- a/third_party/build_fatpack/diff-so-fancy +++ /dev/null @@ -1,1229 +0,0 @@ -#!/usr/bin/env perl - -# This chunk of stuff was generated by App::FatPacker. To find the original -# file's code, look for the end of this BEGIN block or the string 'FATPACK' -BEGIN { -my %fatpacked; - -$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT'; - package DiffHighlight; - - use 5.008; - use warnings FATAL => 'all'; - use strict; - - # Use the correct value for both UNIX and Windows (/dev/null vs nul) - use File::Spec; - - my $NULL = File::Spec->devnull(); - - # Highlight by reversing foreground and background. You could do - # other things like bold or underline if you prefer. - our @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal', "\e[1;31m"), - color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), - "\x1b[27m", - ); - our @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', "\e[1;32m"), - color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), - $OLD_HIGHLIGHT[2], - ); - - - - my $RESET = "\x1b[m"; - my $COLOR = qr/\x1b\[[0-9;]*m/; - my $BORING = qr/$COLOR|\s/; - - my @removed; - my @added; - my $in_hunk; - my $graph_indent = 0; - - our $line_cb = sub { print @_ }; - our $flush_cb = sub { local $| = 1 }; - - # Count the visible width of a string, excluding any terminal color sequences. - sub visible_width { - local $_ = shift; - my $ret = 0; - while (length) { - if (s/^$COLOR//) { - # skip colors - } elsif (s/^.//) { - $ret++; - } - } - return $ret; - } - - # Return a substring of $str, omitting $len visible characters from the - # beginning, where terminal color sequences do not count as visible. - sub visible_substr { - my ($str, $len) = @_; - while ($len > 0) { - if ($str =~ s/^$COLOR//) { - next - } - $str =~ s/^.//; - $len--; - } - return $str; - } - - sub handle_line { - my $orig = shift; - local $_ = $orig; - - # match a graph line that begins a commit - if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space - $COLOR?\*$COLOR?[ ] # a "*" with its trailing space - (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" - [ ]* # trailing whitespace for merges - /x) { - my $graph_prefix = $&; - - # We must flush before setting graph indent, since the - # new commit may be indented differently from what we - # queued. - flush(); - $graph_indent = visible_width($graph_prefix); - - } elsif ($graph_indent) { - if (length($_) < $graph_indent) { - $graph_indent = 0; - } else { - $_ = visible_substr($_, $graph_indent); - } - } - - if (!$in_hunk) { - $line_cb->($orig); - $in_hunk = /^$COLOR*\@\@ /; - } - elsif (/^$COLOR*-/) { - push @removed, $orig; - } - elsif (/^$COLOR*\+/) { - push @added, $orig; - } - else { - flush(); - $line_cb->($orig); - $in_hunk = /^$COLOR*[\@ ]/; - } - - # Most of the time there is enough output to keep things streaming, - # but for something like "git log -Sfoo", you can get one early - # commit and then many seconds of nothing. We want to show - # that one commit as soon as possible. - # - # Since we can receive arbitrary input, there's no optimal - # place to flush. Flushing on a blank line is a heuristic that - # happens to match git-log output. - if (!length) { - $flush_cb->(); - } - } - - sub flush { - # Flush any queued hunk (this can happen when there is no trailing - # context in the final diff of the input). - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - } - - sub highlight_stdin { - while () { - handle_line($_); - } - flush(); - } - - # Ideally we would feed the default as a human-readable color to - # git-config as the fallback value. But diff-highlight does - # not otherwise depend on git at all, and there are reports - # of it being used in other settings. Let's handle our own - # fallback, which means we will work even if git can't be run. - sub color_config { - my ($key, $default) = @_; - my $s = `git config --get-color $key 2>$NULL`; - return length($s) ? $s : $default; - } - - sub show_hunk { - my ($a, $b) = @_; - - # If one side is empty, then there is nothing to compare or highlight. - if (!@$a || !@$b) { - $line_cb->(@$a, @$b); - return; - } - - # If we have mismatched numbers of lines on each side, we could try to - # be clever and match up similar lines. But for now we are simple and - # stupid, and only handle multi-line hunks that remove and add the same - # number of lines. - if (@$a != @$b) { - $line_cb->(@$a, @$b); - return; - } - - my @queue; - for (my $i = 0; $i < @$a; $i++) { - my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - $line_cb->($rm); - push @queue, $add; - } - $line_cb->(@queue); - } - - sub highlight_pair { - my @a = split_line(shift); - my @b = split_line(shift); - - # Find common prefix, taking care to skip any ansi - # color codes. - my $seen_plusminus; - my ($pa, $pb) = (0, 0); - while ($pa < @a && $pb < @b) { - if ($a[$pa] =~ /$COLOR/) { - $pa++; - } - elsif ($b[$pb] =~ /$COLOR/) { - $pb++; - } - elsif ($a[$pa] eq $b[$pb]) { - $pa++; - $pb++; - } - elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { - $seen_plusminus = 1; - $pa++; - $pb++; - } - else { - last; - } - } - - # Find common suffix, ignoring colors. - my ($sa, $sb) = ($#a, $#b); - while ($sa >= $pa && $sb >= $pb) { - if ($a[$sa] =~ /$COLOR/) { - $sa--; - } - elsif ($b[$sb] =~ /$COLOR/) { - $sb--; - } - elsif ($a[$sa] eq $b[$sb]) { - $sa--; - $sb--; - } - else { - last; - } - } - - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), - highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); - } - else { - return join('', @a), - join('', @b); - } - } - - # we split either by $COLOR or by character. This has the side effect of - # leaving in graph cruft. It works because the graph cruft does not contain "-" - # or "+" - sub split_line { - local $_ = shift; - return utf8::decode($_) ? - map { utf8::encode($_); $_ } - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/ : - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/; - } - - sub highlight_line { - my ($line, $prefix, $suffix, $theme) = @_; - - my $start = join('', @{$line}[0..($prefix-1)]); - my $mid = join('', @{$line}[$prefix..$suffix]); - my $end = join('', @{$line}[($suffix+1)..$#$line]); - - # If we have a "normal" color specified, then take over the whole line. - # Otherwise, we try to just manipulate the highlighted bits. - if (defined $theme->[0]) { - s/$COLOR//g for ($start, $mid, $end); - chomp $end; - return join('', - $theme->[0], $start, $RESET, - $theme->[1], $mid, $RESET, - $theme->[0], $end, $RESET, - "\n" - ); - } else { - return join('', - $start, - $theme->[1], $mid, $theme->[2], - $end - ); - } - } - - # Pairs are interesting to highlight only if we are going to end up - # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting - # is just useless noise. We can detect this by finding either a matching prefix - # or suffix (disregarding boring bits like whitespace and colorization). - sub is_pair_interesting { - my ($a, $pa, $sa, $b, $pb, $sb) = @_; - my $prefix_a = join('', @$a[0..($pa-1)]); - my $prefix_b = join('', @$b[0..($pb-1)]); - my $suffix_a = join('', @$a[($sa+1)..$#$a]); - my $suffix_b = join('', @$b[($sb+1)..$#$b]); - - return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || - visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || - $suffix_a !~ /^$BORING*$/ || - $suffix_b !~ /^$BORING*$/; - } -DIFFHIGHLIGHT - -s/^ //mg for values %fatpacked; - -my $class = 'FatPacked::'.(0+\%fatpacked); -no strict 'refs'; -*{"${class}::files"} = sub { keys %{$_[0]} }; - -if ($] < 5.008) { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - my $pos = 0; - my $last = length $fat; - return (sub { - return 0 if $pos == $last; - my $next = (1 + index $fat, "\n", $pos) || $last; - $_ .= substr $fat, $pos, $next - $pos; - $pos = $next; - return 1; - }); - } - }; -} - -else { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - open my $fh, '<', \$fat - or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; - return $fh; - } - return; - }; -} - -unshift @INC, bless \%fatpacked, $class; - } # END OF FATPACK CODE - - -my $VERSION = "1.3.0"; - -################################################################################# - -use 5.010; # Require Perl 5.10 for 'state' variables -use File::Spec; # For catdir -use File::Basename; # For dirname -use Encode; # For handling UTF8 stuff -use Cwd qw(abs_path); # For realpath() -use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC -use DiffHighlight; - -use strict; -use warnings FATAL => 'all'; - -my $remove_file_add_header = 1; -my $remove_file_delete_header = 1; -my $clean_permission_changes = 1; -my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this -my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); -my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); -my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); -my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); -my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); -my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); -my $has_stdin = has_stdin(); - -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; -my $reset_color = color("reset"); -my $bold = color("bold"); -my $meta_color = ""; - -my ($file_1,$file_2); -my $args = argv(); # Hashref of all the ARGV stuff -my $last_file_seen = ""; -my $last_file_mode = ""; -my $i = 0; -my $in_hunk = 0; -my $columns_to_remove = 0; -my $is_mercurial = 0; -my $color_forced = 0; # Has the color been forced on/off - -# We try and be smart about whether we need to do line coloring, but -# this is an option to force it on/off -if ($args->{color_on}) { - $manually_color_lines = 1; - $color_forced = 1; -} elsif ($args->{color_off}) { - $manually_color_lines = 0; - $color_forced = 1; -} - -# We only process ARGV if we don't have STDIN -if (!$has_stdin) { - if ($args->{v} || $args->{version}) { - die(version()); - } elsif ($args->{'set-defaults'}) { - my $ok = set_defaults(); - } elsif ($args->{colors}) { - # We print this to STDOUT so we can redirect to bash to auto-set the colors - print get_default_colors(); - exit; - } elsif (!%$args || $args->{help} || $args->{h}) { - my $first = check_first_run(); - - if (!$first) { - die(usage()); - } - } else { - die("Missing input on STDIN\n"); - } -} else { - # Check to see if were using default settings - check_first_run(); - - my @lines; - local $DiffHighlight::line_cb = sub { - push(@lines,@_); - - my $last_line = $lines[-1]; - - # Buffer X lines before we try and output anything - # Also make sure we're sending enough data to d-s-f to do it's magic. - # Certain things require a look-ahead line or two to function so - # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { - do_dsf_stuff(\@lines); - @lines = (); - } - }; - - my $line_count = 0; - while (my $line = ) { - # If the very first line of the diff doesn't start with ANSI color we're assuming - # it's a raw patch file, and we have to color the added/removed lines ourself - if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { - $manually_color_lines = 1; - } - - my $ok = DiffHighlight::handle_line($line); - $line_count++; - } - - DiffHighlight::flush(); - do_dsf_stuff(\@lines); -} - -################################################################################# - -sub do_dsf_stuff { - my $input = shift(); - - #print STDERR "START -------------------------------------------------\n"; - #print STDERR join("",@$input); - #print STDERR "END ---------------------------------------------------\n"; - - while (my $line = shift(@$input)) { - ###################################################### - # Pre-process the line before we do any other markup # - ###################################################### - - # If the first line of the input is a blank line, skip that - if ($i == 0 && $line =~ /^\s*$/) { - next; - } - - ###################### - # End pre-processing # - ###################### - - ####################################################################### - - #################################################################### - # Look for git index and replace it horizontal line (header later) # - #################################################################### - if ($line =~ /^${ansi_color_regex}index /) { - # Print the line color and then the actual line - $meta_color = $1 || get_config_color("meta"); - - # Get the next line without incrementing counter while loop - my $next = $input->[0] || ""; - my ($file_1,$file_2); - - # The line immediately after the "index" line should be the --- file line - # If it's not it's an empty file add/delete - if ($next !~ /^$ansi_color_regex(---|Binary files)/) { - - # We fake out the file names since it's a raw add/delete - if ($last_file_mode eq "add") { - $file_1 = "/dev/null"; - $file_2 = $last_file_seen; - } elsif ($last_file_mode eq "delete") { - $file_1 = $last_file_seen; - $file_2 = "/dev/null"; - } - } - - if ($file_1 && $file_2) { - print horizontal_rule($meta_color); - print $meta_color . file_change_string($file_1,$file_2) . "\n"; - print horizontal_rule($meta_color); - } - ######################### - # Look for the filename # - ######################### - # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { - - # Mercurial looks like: diff -r 82e55d328c8c hello.c - if ($4 eq "-r") { - $is_mercurial = 1; - $meta_color ||= get_config_color("meta"); - # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy - } else { - $last_file_seen = $5; - } - - $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). - $in_hunk = 0; - ######################################## - # Find the first file: --- a/README.md # - ######################################## - } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - $meta_color ||= get_config_color("meta"); - - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_1 = $file_dir . $5; - } else { - $file_1 = $5; - } - - # Find the second file on the next line: +++ b/README.md - my $next = shift(@$input); - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; - if ($1) { - print $1; # Print out whatever color we're using - } - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_2 = $file_dir . $5; - } else { - $file_2 = $5; - } - - if ($file_2 ne "/dev/null") { - $last_file_seen = $file_2; - } - - # Print out the top horizontal line of the header - print $reset_color; - print horizontal_rule($meta_color); - - # Mercurial coloring is slightly different so we need to hard reset colors - if ($is_mercurial) { - print $reset_color; - } - - print $meta_color; - print file_change_string($file_1,$file_2) . "\n"; - - # Print out the bottom horizontal line of the header - print horizontal_rule($meta_color); - ######################################## - # Check for "@@ -3,41 +3,63 @@" syntax # - ######################################## - } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - - print $line; - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - - my $hunk_header = $4; - my $remain = bleach_text($5); - - # The number of colums to remove (1 or 2) is based on how many commas in the hunk header - $columns_to_remove = (char_count(",",$hunk_header)) - 1; - # On single line removes there is NO comma in the hunk so we force one - if ($columns_to_remove <= 0) { - $columns_to_remove = 1; - } - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - #$last_file_seen = basename($last_file_seen); - - # Figure out the start line - my $start_line = start_line_calc($new_offset,$new_count); - - # Last function has it's own color - my $last_function_color = ""; - if ($remain) { - $last_function_color = get_config_color("last_function"); - } - - # Check to see if we have the color for the fragment from git - if ($5 =~ /\e\[\d/) { - #print "Has ANSI color for fragment\n"; - } else { - # We don't have the ANSI sequence so we shell out to get it - #print "No ANSI color for fragment\n"; - my $frag_color = get_config_color("fragment"); - print $frag_color; - } - - print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; - ################################### - # Remove any new file permissions # - ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { - # Don't print the line (i.e. remove it from the output); - $last_file_mode = "add"; - ###################################### - # Remove any delete file permissions # - ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { - # Don't print the line (i.e. remove it from the output); - $last_file_mode = "delete"; - ################################ - # Look for binary file changes # - ################################ - } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - my $change = file_change_string($2,$4); - print horizontal_rule($meta_color); - print "$meta_color$change (binary)\n"; - print horizontal_rule($meta_color); - ##################################################### - # Check if we're changing the permissions of a file # - ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { - my ($old_mode) = $4; - my $next = shift(@$input); - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($new_mode) = $next =~ m/new mode (\d+)/; - print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; - - ############### - # File rename # - ############### - } elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) { - my $simil = $4; - - # If it's a move with content change we ignore this and the next two lines - if ($simil != 100) { - shift(@$input); - shift(@$input); - next; - } - - my $next = shift(@$input); - my ($file1) = $next =~ /rename from (.+)/; - - $next = shift(@$input); - my ($file2) = $next =~ /rename to (.+)/; - - if ($file1 && $file2) { - # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= get_config_color("meta"); - - my $change = file_change_string($file1,$file2); - - print horizontal_rule($meta_color); - print $meta_color . $change . "\n"; - print horizontal_rule($meta_color); - } - - $i += 3; # We've consumed three lines - next; - ##################################### - # Just a regular line, print it out # - ##################################### - } else { - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } - - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); - } - print $line; - } - - $i++; - } -} - -###################################################################################################### -# End regular code, begin functions -###################################################################################################### - -# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 -sub parse_hunk_header { - my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; - $o_cnt = 1 unless defined $o_cnt; - $n_cnt = 1 unless defined $n_cnt; - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); -} - -# Mark the first char of an empty line -sub mark_empty_line { - my $line = shift(); - - my $reset_color = "\e\\[0?m"; - my $reset_escape = "\e\[m"; - my $invert_color = "\e\[7m"; - my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; - my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; - - # This captures lines that do not have any ANSI in them (raw vanilla diff) - if ($line eq "+\n") { - $line = $invert_color . $add_color . " " . color('reset') . "\n"; - # This captures lines that do not have any ANSI in them (raw vanilla diff) - } elsif ($line eq "-\n") { - $line = $invert_color . $del_color . " " . color('reset') . "\n"; - # This handles everything else - } else { - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; - } - - return $line; -} - -# String to boolean -sub boolean { - my $str = shift(); - $str = trim($str); - - if ($str eq "" || $str =~ /^(no|false|0)$/i) { - return 0; - } else { - return 1; - } -} - -# Memoize getting the git config -{ - my $static_config; - - sub git_config_raw { - if ($static_config) { - # If we already have the config return that - return $static_config; - } - - my $cmd = "git config --list"; - my @out = `$cmd`; - - $static_config = \@out; - - return \@out; - } -} - -# Fetch a textual item from the git config -sub git_config { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || ""); - - my $out = git_config_raw(); - - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return $default_value; - } - - my $raw = {}; - foreach my $line (@$out) { - if ($line =~ /=/) { - my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - $raw->{$key} = $value; - } - } - - # If we're given a search key return that, else return the hash - if ($search_key) { - return $raw->{$search_key} || $default_value; - } else { - return $raw; - } -} - -# Fetch a boolean item from the git config -sub git_config_boolean { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || 0); # Default to false - - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return boolean($default_value); - } - - my $result = git_config($search_key,$default_value); - my $ret = boolean($result); - - return $ret; -} - -# Check if we're inside of BATS -sub in_unit_test { - if ($ENV{BATS_CWD}) { - return 1; - } else { - return 0; - } -} - -sub get_less_charset { - my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); - foreach (@less_char_vars) { - return $ENV{$_} if defined $ENV{$_}; - } - - return ""; -} - -sub should_print_unicode { - if (-t STDOUT) { - # Always print unicode chars if we're not piping stuff, e.g. to less(1) - return 1; - } - - # Otherwise, assume we're piping to less(1) - my $less_charset = get_less_charset(); - if ($less_charset =~ /utf-?8/i) { - return 1; - } - - return 0; -} - -# Try and be smart about what line the diff hunk starts on -sub start_line_calc { - my ($line_num,$diff_context) = @_; - my $ret; - - if ($line_num == 0 && $diff_context == 0) { - return 1; - } - - # Git defaults to three lines of context - my $default_context_lines = 3; - # Three lines on either side, and the line itself = 7 - my $expected_context = ($default_context_lines * 2 + 1); - - # The first three lines - if ($line_num == 1 && $diff_context < $expected_context) { - $ret = $diff_context - $default_context_lines; - } else { - $ret = $line_num + $default_context_lines; - } - - if ($ret < 1) { - $ret = 1; - } - - return $ret; -} - -# Remove + or - at the beginning of the lines -sub strip_leading_indicators { - my $line = shift(); # Array passed in by reference - my $columns_to_remove = shift(); # Don't remove any lines by default - - if ($columns_to_remove == 0) { - return $line; # Nothing to do - } - - $line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/; - - if ($manually_color_lines) { - if (defined($5) && $5 eq "+") { - my $add_line_color = get_config_color("add_line"); - $line = $add_line_color . $line . $reset_color; - } elsif (defined($5) && $5 eq "-") { - my $remove_line_color = get_config_color("remove_line"); - $line = $remove_line_color . $line . $reset_color; - } - } - - return $line; -} - -# Count the number of a given char in a string -sub char_count { - my ($needle,$str) = @_; - my $len = length($str); - my $ret = 0; - - for (my $i = 0; $i < $len; $i++) { - my $found = substr($str,$i,1); - - if ($needle eq $found) { $ret++; } - } - - return $ret; -} - -# Remove all ANSI codes from a string -sub bleach_text { - my $str = shift(); - $str =~ s/\e\[\d*(;\d+)*m//mg; - - return $str; -} - -# Remove all trailing and leading spaces -sub trim { - my $s = shift(); - if (!$s) { return ""; } - $s =~ s/^\s*|\s*$//g; - - return $s; -} - -# Print a line of em-dash or line-drawing chars the full width of the screen -sub horizontal_rule { - my $color = $_[0] || ""; - my $width = get_terminal_width(); - - # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm - #my $dash = "\x{2014}"; - # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm - my $dash; - if ($use_unicode_dash_for_ruler && should_print_unicode()) { - $dash = Encode::encode('UTF-8', "\x{2500}"); - } else { - $dash = "-"; - } - - # Draw the line - my $ret = $color . ($dash x $width) . "$reset_color\n"; - - return $ret; -} - -sub file_change_string { - my $file_1 = shift(); - my $file_2 = shift(); - - # If they're the same it's a modify - if ($file_1 eq $file_2) { - return "modified: $file_1"; - # If the first is /dev/null it's a new file - } elsif ($file_1 eq "/dev/null") { - my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; - return "added: $add_color$file_2$reset_color"; - # If the second is /dev/null it's a deletion - } elsif ($file_2 eq "/dev/null") { - my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; - return "deleted: $del_color$file_1$reset_color"; - # If the files aren't the same it's a rename - } elsif ($file_1 ne $file_2) { - my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); - $old = trim($old); - $new = trim($new); - - # highlight_pair resets the colors, but we want it to be the meta color - $old =~ s/(\e0?\[m)/$1$meta_color/g; - $new =~ s/(\e0?\[m)/$1$meta_color/g; - - return "renamed: $old to $new"; - # Something we haven't thought of yet - } else { - return "$file_1 -> $file_2"; - } -} - -# Check to see if STDIN is connected to an interactive terminal -sub has_stdin { - my $i = -t STDIN; - my $ret = int(!$i); - - return $ret; -} - -# We use this instead of Getopt::Long because it's faster and we're not parsing any -# crazy arguments -# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html -sub argv { - my $ret = {}; - - for (my $i = 0; $i < scalar(@ARGV); $i++) { - - # If the item starts with "-" it's a key - if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) { - # If the next item does not start with "--" it's the value for this item - if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { - $ret->{$key} = $ARGV[$i + 1]; - # Bareword like --verbose with no options - } else { - $ret->{$key}++; - } - } - } - - # We're looking for a certain item - if ($_[0]) { return $ret->{$_[0]}; } - - return $ret; -} - -# Output the command line usage for d-s-f -sub usage { - my $out = color("white_bold") . version() . color("reset") . "\n"; - - $out .= "Usage: - -git diff --color | diff-so-fancy # Use d-s-f on one diff -cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file -diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output - -diff-so-fancy --colors # View the commands to set the recommended colors -diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors - -# Configure git to use d-s-f for *all* diff operations -git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; - - return $out; -} - -sub get_default_colors { - my $out = "# Recommended default colors for diff-so-fancy\n"; - $out .= "# --------------------------------------------\n"; - $out .= 'git config --global color.ui true - -git config --global color.diff-highlight.oldNormal "red bold" -git config --global color.diff-highlight.oldHighlight "red bold 52" -git config --global color.diff-highlight.newNormal "green bold" -git config --global color.diff-highlight.newHighlight "green bold 22" - -git config --global color.diff.meta "yellow" -git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "yellow bold" -git config --global color.diff.old "red bold" -git config --global color.diff.new "green bold" -git config --global color.diff.whitespace "red reverse" -'; - - return $out; -} - -# Output the current version string -sub version { - my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; - $ret .= "Version : $VERSION\n"; - - return $ret; -} - -sub is_windows { - if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { - return 1; - } else { - return 0; - } -} - -# Return value is whether this is the first time they've run d-s-f -sub check_first_run { - my $ret = 0; - - # If first-run is not set, or it's set to "true" - my $first_run = git_config_boolean('diff-so-fancy.first-run'); - # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); - - #$first_run = 1; $has_dh_colors = 0; - - if (!$first_run || $has_dh_colors) { - return 0; - } else { - print "This appears to be the first time you've run diff-so-fancy, please note\n"; - print "that the default git colors are not ideal. Diff-so-fancy recommends the\n"; - print "following colors.\n\n"; - - print get_default_colors(); - - # Set the first run flag to false - my $cmd = 'git config --global diff-so-fancy.first-run false'; - system($cmd); - - exit; - } - - return 1; -} - -sub set_defaults { - my $color_config = get_default_colors(); - my $git_config = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"'; - my $first_cmd = 'git config --global diff-so-fancy.first-run false'; - - my @cmds = split(/\n/,$color_config); - push(@cmds,$git_config); - push(@cmds,$first_cmd); - - # Remove all comments from the commands - foreach my $x (@cmds) { - $x =~ s/#.*//g; - } - - # Remove any empty commands - @cmds = grep($_,@cmds); - - foreach my $cmd (@cmds) { - system($cmd); - my $exit = ($? >> 8); - - if ($exit != 0) { - die("Error running: '$cmd' (error #18941)\n"); - } - } - - return 1; -} - -# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html -# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' -sub color { - my $str = shift(); - - # No string sent in, so we just reset - if (!length($str) || $str eq 'reset') { return "\e[0m"; } - - # Some predefined colors - my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); - $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; - - # Get foreground/background and any commands - my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g; - my ($bc) = $str =~ /on_?(\d+)/g; - - # Some predefined commands - my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); - my $cmd_num = $cmd_map{$cmd // 0}; - - my $ret = ''; - if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } - if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } - if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } - - return $ret; -} - -# Get colors used for various output sections (memoized) -{ - my $static_config; - - sub get_config_color { - my $str = shift(); - - my $ret = ""; - if ($static_config->{$str}) { - return $static_config->{$str}; - } - - #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); - - if ($str eq "meta") { - # Default ANSI yellow - $ret = DiffHighlight::color_config('color.diff.meta', color(11)); - } elsif ($str eq "reset") { - $ret = color("reset"); - } elsif ($str eq "add_line") { - # Default ANSI green - $ret = DiffHighlight::color_config('color.diff.new', color('bold') . color(2)); - } elsif ($str eq "remove_line") { - # Default ANSI red - $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); - } elsif ($str eq "fragment") { - $ret = DiffHighlight::color_config('color.diff.frag', color('13_bold')); - } elsif ($str eq "last_function") { - $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); - } - - # Cache (memoize) the entry for later - $static_config->{$str} = $ret; - - return $ret; - } -} - -sub starts_with_ansi { - my $str = shift(); - - if ($str =~ /^$ansi_color_regex/) { - return 1; - } else { - return 0; - } -} - -sub get_terminal_width { - # Make width static so we only calculate it once - state $width; - - if ($width) { - return $width; - } - - # If there is a ruler width in the config we use that - if ($ruler_width) { - $width = $ruler_width; - # Otherwise we check the terminal width using tput - } else { - my $tput = `tput cols`; - - if ($tput) { - $width = int($tput); - - if (is_windows()) { - $width--; - } - } else { - print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; - $width = 80; - } - } - - return $width; -} - -# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 9caa68ed490823ab90fad134980b745d4e883096 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 31 Mar 2021 09:29:15 -0700 Subject: [PATCH 337/443] Clarify the LESS_CHARSET a little better --- diff-so-fancy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 975dbee..35a0e23 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -526,8 +526,7 @@ sub should_print_unicode { # Otherwise, assume we're piping to less(1) my ($less_env_var, $less_charset) = get_less_charset(); - $less_charset //= ""; - if ($less_charset =~ /utf-?8/i) { + if ($less_charset && $less_charset =~ /utf-?8/i) { return 1; } From edcfd043a01b2c798375c76d17a7b8733f30e3d4 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 31 Mar 2021 09:29:15 -0700 Subject: [PATCH 338/443] Clarify the LESS_CHARSET a little better --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 8c667b5..1c01883 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -526,7 +526,7 @@ sub should_print_unicode { # Otherwise, assume we're piping to less(1) my ($less_env_var, $less_charset) = get_less_charset(); - if ($less_charset =~ /utf-?8/i) { + if ($less_charset && $less_charset =~ /utf-?8/i) { return 1; } From 61bbdf70ccd7d37b62bf3600d23dbb3d927f94e1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sat, 3 Apr 2021 15:17:28 -0700 Subject: [PATCH 339/443] Work around an undef error (really uncommon) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 064a1bc..b1f3d60 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -947,7 +947,7 @@ sub show_debug_info { print "\n"; print "Terminal width : " . get_terminal_width() . "\n"; - print "Terminal \$LANG : " . $ENV{LANG} . "\n"; + print "Terminal \$LANG : " . ($ENV{LANG} || "") . "\n"; print "\n"; print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; From ae76d3058f4feccbfdbc6e3ab56108663d1a0ccc Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Tue, 4 May 2021 20:17:09 +0200 Subject: [PATCH 340/443] Use newer xcode version in attempt to fix tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f4ad15..0dc622e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,9 @@ perl: matrix: include: - os: osx - osx_image: xcode9.4 + osx_image: xcode12.2 language: generic - perl: 5.24 + perl: 5.30 allow_failures: - perl: "blead" From dc949fe026c49f51b96c6716921ee118cf02bf17 Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Tue, 4 May 2021 20:23:51 +0200 Subject: [PATCH 341/443] Run cpanm in non-quiet mode to have more output to try fix MacOS Tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0dc622e..c07bb04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,9 +39,9 @@ before_install: sudo apt-get -y update; fi install: - - cpanm --quiet --notest --local-lib=~/perl5 local::lib + - cpanm --notest --local-lib=~/perl5 local::lib - eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode + - cpanm --notest Test::Perl::Critic Perl::Critic::Freenode script: - perlcritic -1 -q --theme freenode diff-so-fancy - ./bats/bin/bats test/*.bats From 262c4f8622f73eaf561703382153124af03d51af Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Tue, 4 May 2021 20:59:00 +0200 Subject: [PATCH 342/443] Revert non-required quiet mode change --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c07bb04..0dc622e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,9 +39,9 @@ before_install: sudo apt-get -y update; fi install: - - cpanm --notest --local-lib=~/perl5 local::lib + - cpanm --quiet --notest --local-lib=~/perl5 local::lib - eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) - - cpanm --notest Test::Perl::Critic Perl::Critic::Freenode + - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode script: - perlcritic -1 -q --theme freenode diff-so-fancy - ./bats/bin/bats test/*.bats From 566d951e23a3307fb117b8bbc6c379fbc3b351ce Mon Sep 17 00:00:00 2001 From: Oliver Ford Date: Thu, 13 May 2021 18:14:45 +0100 Subject: [PATCH 343/443] Link installation docs to known packages Closes #380. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9af566..ebf152c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`diff-so-fancy` is also available from NPM, Nix, brew, and as a package on Arch and Debian Linux. +`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/git-and-tools/diff-so-fancy/default.nix), in the [Arch community repo](https://archlinux.org/packages/community/any/diff-so-fancy/), and [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). + +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. **Note:** Windows users may need to install [MinGW](https://sourceforge.net/projects/mingw/files/) or the [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10). From 9145233de53f6df66a0eb79b01bdb1ffe8467049 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:13:38 -0700 Subject: [PATCH 344/443] Reorganize the header so it's more consistent with my other projects --- diff-so-fancy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1c01883..e081579 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,7 +4,10 @@ my $VERSION = "1.4.0"; ################################################################################# -use 5.010; # Require Perl 5.10 for 'state' variables +use v5.010; # Require Perl 5.10 for 'state' variables +use warnings FATAL => 'all'; +use strict; + use File::Spec; # For catdir use File::Basename; # For dirname use Encode; # For handling UTF8 stuff @@ -12,9 +15,6 @@ use Cwd qw(abs_path); # For realpath() use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; -use strict; -use warnings FATAL => 'all'; - my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; From c975956dac8c5d5ac5b2ef3c70bb00d636ee48f0 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:15:04 -0700 Subject: [PATCH 345/443] Use 'state' for git_config_raw() --- diff-so-fancy | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e081579..39c8c1e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -433,22 +433,20 @@ sub boolean { } # Memoize getting the git config -{ - my $static_config; +sub git_config_raw { + state $static_config; - sub git_config_raw { - if ($static_config) { - # If we already have the config return that - return $static_config; - } + if ($static_config) { + # If we already have the config return that + return $static_config; + } - my $cmd = "git config --list"; - my @out = `$cmd`; + my $cmd = "git config --list"; + my @out = `$cmd`; - $static_config = \@out; + $static_config = \@out; - return \@out; - } + return \@out; } # Fetch a textual item from the git config From 21e01bb841c8602506452459927bfb1503e4c85c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:15:41 -0700 Subject: [PATCH 346/443] Cache parsing the git config to speed things up --- diff-so-fancy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 39c8c1e..3686617 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -461,7 +461,15 @@ sub git_config { return $default_value; } - my $raw = {}; + state $raw = {}; + if (%$raw && $search_key) { + return $raw->{$search_key} || $default_value; + } + + if ($args->{debug}) { + print "Parsing git config\n"; + } + foreach my $line (@$out) { if ($line =~ /=/) { my ($key,$value) = split("=",$line,2); From 477079e5aabfc0635c19fa6cff25fbf0ffb907bf Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:13:38 -0700 Subject: [PATCH 347/443] Reorganize the header so it's more consistent with my other projects --- diff-so-fancy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 35a0e23..4c559be 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,7 +4,10 @@ my $VERSION = "1.4.1"; ################################################################################# -use 5.010; # Require Perl 5.10 for 'state' variables +use v5.010; # Require Perl 5.10 for 'state' variables +use warnings FATAL => 'all'; +use strict; + use File::Spec; # For catdir use File::Basename; # For dirname use Encode; # For handling UTF8 stuff @@ -12,9 +15,6 @@ use Cwd qw(abs_path); # For realpath() use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; -use strict; -use warnings FATAL => 'all'; - my $remove_file_add_header = 1; my $remove_file_delete_header = 1; my $clean_permission_changes = 1; From 908184c07ae2283af95f4e056f046bd0d5b00e94 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:15:04 -0700 Subject: [PATCH 348/443] Use 'state' for git_config_raw() --- diff-so-fancy | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 4c559be..69d602f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -433,22 +433,20 @@ sub boolean { } # Memoize getting the git config -{ - my $static_config; +sub git_config_raw { + state $static_config; - sub git_config_raw { - if ($static_config) { - # If we already have the config return that - return $static_config; - } + if ($static_config) { + # If we already have the config return that + return $static_config; + } - my $cmd = "git config --list"; - my @out = `$cmd`; + my $cmd = "git config --list"; + my @out = `$cmd`; - $static_config = \@out; + $static_config = \@out; - return \@out; - } + return \@out; } # Fetch a textual item from the git config From d57346d307dcc3a71751c594ae39fbf1f3435954 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 16:15:41 -0700 Subject: [PATCH 349/443] Cache parsing the git config to speed things up --- diff-so-fancy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 69d602f..f4d281e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -461,7 +461,15 @@ sub git_config { return $default_value; } - my $raw = {}; + state $raw = {}; + if (%$raw && $search_key) { + return $raw->{$search_key} || $default_value; + } + + if ($args->{debug}) { + print "Parsing git config\n"; + } + foreach my $line (@$out) { if ($line =~ /=/) { my ($key,$value) = split("=",$line,2); From d4eb9da6146937a47046f38e6aae00e2ffd50968 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 20:53:36 -0700 Subject: [PATCH 350/443] Remove the STDERR redirect to speed up execution --- lib/DiffHighlight.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 9feb06c..07df73b 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -141,7 +141,11 @@ sub highlight_stdin { # fallback, which means we will work even if git can't be run. sub color_config { my ($key, $default) = @_; - my $s = `git config --get-color $key 2>$NULL`; + + # Removing the redirect speeds up execution by about 12ms + #my $s = `git config --get-color $key 2>$NULL`; + my $s = `git config --get-color $key`; + return length($s) ? $s : $default; } From 9b9058dcf203c8521754caa6bbc178ec871c9c34 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 17 May 2021 20:54:15 -0700 Subject: [PATCH 351/443] Remove Encode to speed up startup by ~15ms --- diff-so-fancy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 31b9bb9..e1a080e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -10,7 +10,6 @@ use strict; use File::Spec; # For catdir use File::Basename; # For dirname -use Encode; # For handling UTF8 stuff use Cwd qw(abs_path); # For realpath() use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC use DiffHighlight; @@ -640,7 +639,8 @@ sub horizontal_rule { # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm my $dash; if ($use_unicode_dash_for_ruler && should_print_unicode()) { - $dash = Encode::encode('UTF-8', "\x{2500}"); + #$dash = Encode::encode('UTF-8', "\x{2500}"); + $dash = "\xE2\x94\x80"; } else { $dash = "-"; } From e0e5af4e884ace90d678fe1b11c3f03e909ff71d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 09:46:17 -0700 Subject: [PATCH 352/443] Add early implementation for parsing git config color strings --- diff-so-fancy | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index e1a080e..f59d1bc 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -898,6 +898,74 @@ sub color { } } +sub git_ansi_color { + my $str = shift(); + my @parts = split(' ', $str); + + if (!@parts) { + return ''; + } + + my $colors = { + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + }; + + my $fg = $parts[0] || ""; + my $mod = $parts[1] || ""; + my $bg = $parts[2] || ""; + + my @ansi_part = (); + ############################################# + + if ($mod eq 'bold') { + push(@ansi_part, "1"); + } + + ############################################# + + # It's an RGB value + if (is_numeric($fg)) { + push(@ansi_part, "38;5;$fg"); + # It's a simple 16 color OG ansi + } elsif ($fg) { + push(@ansi_part, $colors->{$fg} + 30); + } + + ############################################# + + # It's an RGB value + if (is_numeric($bg)) { + push(@ansi_part, "48;5;$bg"); + # It's a simple 16 color OG ansi + } elsif ($bg) { + push(@ansi_part, $colors->{$fg} + 40); + } + + ############################################# + + my $ansi_str = join(";", @ansi_part); + my $ret = "\e[" . $ansi_str . "m"; + + return $ret; +} + +sub is_numeric { + my $s = shift(); + + if ($s =~ /^\d+$/) { + return 1; + } + + return 0; +} + sub starts_with_ansi { my $str = shift(); From 5ed615984e9c16445d3ab5b229015af1c5503f59 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 09:46:54 -0700 Subject: [PATCH 353/443] Don't use DiffHighlight to get the colors because it shells out and is slow --- diff-so-fancy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index f59d1bc..164d8b1 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -876,19 +876,19 @@ sub color { if ($str eq "meta") { # Default ANSI yellow - $ret = DiffHighlight::color_config('color.diff.meta', color(11)); + $ret = git_ansi_color(git_config('color.diff.meta')) || color(11); } elsif ($str eq "reset") { $ret = color("reset"); } elsif ($str eq "add_line") { # Default ANSI green - $ret = DiffHighlight::color_config('color.diff.new', color('bold') . color(2)); + $ret = git_ansi_color(git_config('color.diff.new')) || color("2_bold"); } elsif ($str eq "remove_line") { # Default ANSI red - $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + $ret = git_ansi_color(git_config('color.diff.old')) || color("1_bold"); } elsif ($str eq "fragment") { - $ret = DiffHighlight::color_config('color.diff.frag', color('13_bold')); + $ret = git_ansi_color(git_config('color.diff.frag')) || color("13_bold"); } elsif ($str eq "last_function") { - $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); + $ret = git_ansi_color(git_config('color.diff.func')) || color("146_bold"); } # Cache (memoize) the entry for later From 4ef5a78ecaed8e84e01dd47de2db6d549bd94d62 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 10:15:54 -0700 Subject: [PATCH 354/443] Use the cached git config to set the DiffHiglight colors This should speedup start up a decent amount --- diff-so-fancy | 11 +++++++++++ lib/DiffHighlight.pm | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 164d8b1..1cce75e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -32,6 +32,9 @@ my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; +# Set the diff highlight colors from the config +init_diff_highlight_colors(); + my ($file_1,$file_2); my $args = argv(); # Hashref of all the ARGV stuff my $last_file_seen = ""; @@ -1039,4 +1042,12 @@ sub yes_no { } } +sub init_diff_highlight_colors { + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); + + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 07df73b..1368b3f 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -12,13 +12,13 @@ my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal', "\e[1;31m"), - color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), + "\e[1;31m", + "\e[1;31;48;5;52m", "\x1b[27m", ); our @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', "\e[1;32m"), - color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), + "\e[1;32m", + "\e[1;32;48;5;22m", $OLD_HIGHLIGHT[2], ); From 9044411df6fda82ad5ff093dc628c55d178b9cf6 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 10:19:20 -0700 Subject: [PATCH 355/443] Add cli_bench to the third_party tools --- third_party/cli_bench/cli_bench.pl | 177 +++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 third_party/cli_bench/cli_bench.pl diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl new file mode 100644 index 0000000..1f3606e --- /dev/null +++ b/third_party/cli_bench/cli_bench.pl @@ -0,0 +1,177 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use v5.16; + +############################################################################### +# Used to benchmark optimizations to CLI scripts +# +# Usage: cli_bench [--num 50] 'cat /tmp/diff.patch | diff-so-fancy' +############################################################################### + +use Time::HiRes qw(time); +use Getopt::Long; + +############################################################################### +############################################################################### + +my $num = 50; +my $ignore = 1; + +my $ok = GetOptions( + 'num=i' => \$num, + 'ignore=i' => \$ignore, +); + +my $cmd = join(" ", @ARGV); + +$| = 0; # Disable output buffering + +my @res; +my $out; +my $exit = 0; +for (my $i = 0; $i < ($num + $ignore); $i++) { + my $start = time(); + $out = `$cmd`; + $exit = $? >> 8; + + my $total = int((time() - $start) * 1000); + push(@res, $total); + + print "."; +} + +print "\n"; + +# Throw away the first X to give things time to warm up and be cached +@res = splice(@res, $ignore); + +# Remove the top and bottom 10% +my $outlier = $num / 10; +@res = splice(@res, $outlier, $num - $outlier * 2); + +my $avg = int(average(@res)); + +print "Ran '$cmd' $num times with average completion time of $avg ms\n"; + +if ($exit != 0) { + print $out; +} + +############################################################################### +############################################################################### + +sub average { + my $ret = 0; + + foreach (@_) { + $ret += $_; + } + + my $count = scalar(@_); + $ret = $ret / $count; + + return $ret; +} + +sub random_int { + my $ret = rand() * 90 + 10; + $ret = int($ret); + + return $ret; +} + +sub round { + my $num = shift(); + + #https://stackoverflow.com/questions/178539/how-do-you-round-a-floating-point-number-in-perl + #my $ret = int($num + $num/abs($num * 2 || 1)); + + my $ret; + if ($num < 0) { + $ret = int($num - 0.5); + } else { + $ret = int($num + 0.5); + } + + return $ret; +} + +sub trim { + my $s = shift(); + if (!defined($s) || length($s) == 0) { return ""; } + $s =~ s/^\s*//; + $s =~ s/\s*$//; + + return $s; +} + +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' +sub color { + my $str = shift(); + + # If we're NOT connected to a an interactive terminal don't do color + if (-t STDOUT == 0) { return ''; } + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; + my ($bc) = $str =~ /on_(\d{1,3})$/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd // 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + +sub file_get_contents { + my $file = shift(); + open (my $fh, "<", $file) or return undef; + + my $ret; + while (<$fh>) { $ret .= $_; } + + return $ret; +} + +sub file_put_contents { + my ($file, $data) = @_; + open (my $fh, ">", $file) or return undef; + + print $fh $data; + return length($data); +} + +# Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper +# Creates methods k() and kd() to print, and print & die respectively +BEGIN { + if (eval { require Data::Dump::Color }) { + *k = sub { Data::Dump::Color::dd(@_) }; + } else { + require Data::Dumper; + *k = sub { print Data::Dumper::Dumper(\@_) }; + } + + sub kd { + k(@_); + + printf("Died at %2\$s line #%3\$s\n",caller()); + exit(15); + } +} + +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 + From 29839a0aeb686df2b5d7b0bef537b3b053754c4e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 11:01:48 -0700 Subject: [PATCH 356/443] Fix the tests by using the previous ANSI defaults --- diff-so-fancy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1cce75e..3e804f8 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1043,11 +1043,11 @@ sub yes_no { } sub init_diff_highlight_colors { - $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); - $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || "\e[1;32m"; + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || "\e[1;32;48;5;22m"; - $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); - $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || "\e[1;31m"; + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || "\e[1;31;48;5;52m"; } # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From f1c3dd8c62db08b24a5ed17e05a5c47b1533dce2 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 11:42:27 -0700 Subject: [PATCH 357/443] upgrade to modern bats --- .gitmodules | 3 +++ hacking-and-testing.md | 10 ++++++---- package.json | 2 +- test/bats | 1 + test/diff-so-fancy.bats | 2 +- test/test_helper/bats-assert | 2 +- test/test_helper/bats-support | 2 +- 7 files changed, 14 insertions(+), 8 deletions(-) create mode 160000 test/bats diff --git a/.gitmodules b/.gitmodules index b4374b5..ec78a86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "test/test_helper/bats-assert"] path = test/test_helper/bats-assert url = https://github.com/ztombol/bats-assert +[submodule "test/bats"] + path = test/bats + url = https://github.com/bats-core/bats-core.git diff --git a/hacking-and-testing.md b/hacking-and-testing.md index 9a11c0e..28fc9bc 100644 --- a/hacking-and-testing.md +++ b/hacking-and-testing.md @@ -14,19 +14,20 @@ cd ~/projects/catfabulator && git diff ### Running tests -You'll need to install [bats](https://github.com/sstephenson/bats#installing-bats-from-source), the Bash automated testing system. It's also available as `brew install bats` +The tests use [bats-core](https://bats-core.readthedocs.io/en/latest/index.html), the Bash automated testing system. ```sh git submodule sync -git submodule update --init # pull in the assertion library, bats-assert +git submodule update --init # run the test suite once: -bats test +./test/bats/bin/bats test # run it on every change with `entr` brew install entr -ls --color=never diff-so-fancy test/*.bats | entr bats test +ls --color=never diff-so-fancy test/*.bats | entr ./test/bats/bin/bats test ``` + When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` You can lint your scripts via shellcheck, our CI bots will also check. @@ -34,3 +35,4 @@ You can lint your scripts via shellcheck, our CI bots will also check. ```sh brew install shellcheck shellcheck *.sh +``` diff --git a/package.json b/package.json index dc1c5c5..d2e3c32 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "diff-so-fancy": "diff-so-fancy" }, "scripts": { - "test": "bats test" + "test": "./test/bats/bin/bats test" }, "repository": { "type": "git", diff --git a/test/bats b/test/bats new file mode 160000 index 0000000..49b377a --- /dev/null +++ b/test/bats @@ -0,0 +1 @@ +Subproject commit 49b377a751e6f9379abfdfb3dfa3aafabd8495a1 diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index d9a65c4..25a10c7 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -11,7 +11,7 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "diff-so-fancy runs exits without error" { load_fixture "ls-function" | $diff_so_fancy - assert_success + run assert_success } @test "index line is removed entirely" { diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert index 3c30cb6..e0de84e 160000 --- a/test/test_helper/bats-assert +++ b/test/test_helper/bats-assert @@ -1 +1 @@ -Subproject commit 3c30cb6424b9760884f8f8b7378a9367b76692b7 +Subproject commit e0de84e9c011223e7f88b7ccf1c929f4327097ba diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support index 88f342e..d140a65 160000 --- a/test/test_helper/bats-support +++ b/test/test_helper/bats-support @@ -1 +1 @@ -Subproject commit 88f342e29681cbcb3c183bd47dfbb45965f42eb7 +Subproject commit d140a65044b2d6810381935ae7f0c94c7023c8c3 From e5d5e7952ea342d4c82e15ecdb712076f8bc7853 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:12:38 -0700 Subject: [PATCH 358/443] improve present-removed-lines test --- test/bugs.bats | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index 819731e..70f1d21 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -4,17 +4,15 @@ load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load 'test_helper/util' - -output=$( load_fixture "chromium-modaltoelement" | $diff_so_fancy ) - - -empty_remove_highlight="" - # https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13459061 @test "All removed lines are present in diff" { - assert_output --partial "WebInspector.Dialog = function($empty_remove_highlight)" - assert_output --partial "show: function($empty_remove_highlight)" - assert_output --partial "{!Document} */ (WebInspector.Dialog._modalHostView.element.ownerDocument$empty_remove_highlight)" + output=$( load_fixture "chromium-modaltoelement" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 7 --partial "WebInspector.Dialog" + assert_line --index 7 --partial "5;52m" # red oldhighlight + assert_line --index 8 --partial "WebInspector.Dialog" + assert_line --index 8 --partial "5;22m" # green newhighlight } @test "File with space in the name (#360)" { @@ -28,6 +26,6 @@ empty_remove_highlight="" output=$( load_fixture "add_remove_empty_lines" | $diff_so_fancy ) run printf "%s" "$output" - assert_line --index 5 --partial "[1;32" - assert_line --index 8 --partial "[1;31" + assert_line --index 5 --partial "[1;32" # green added line + assert_line --index 8 --partial "[1;31" # red removed line } From 5133fe84164b5f4ece5bcbca2e891368cd152e60 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:12:44 -0700 Subject: [PATCH 359/443] Revert "Fix the tests by using the previous ANSI defaults" This reverts commit 29839a0aeb686df2b5d7b0bef537b3b053754c4e. --- diff-so-fancy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3e804f8..1cce75e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1043,11 +1043,11 @@ sub yes_no { } sub init_diff_highlight_colors { - $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || "\e[1;32m"; - $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || "\e[1;32;48;5;22m"; + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); - $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || "\e[1;31m"; - $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || "\e[1;31;48;5;52m"; + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); } # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 45086129c916200b6bccdda0acdefd9f5ccac0ed Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:18:48 -0700 Subject: [PATCH 360/443] more specific ansi escape assertion for add/remove empty, too --- hacking-and-testing.md | 2 +- test/bugs.bats | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hacking-and-testing.md b/hacking-and-testing.md index 28fc9bc..508c810 100644 --- a/hacking-and-testing.md +++ b/hacking-and-testing.md @@ -25,7 +25,7 @@ git submodule update --init # run it on every change with `entr` brew install entr -ls --color=never diff-so-fancy test/*.bats | entr ./test/bats/bin/bats test +find ./* test/* test/fixtures/* -maxdepth 0 | entr ./test/bats/bin/bats test ``` When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` diff --git a/test/bugs.bats b/test/bugs.bats index 70f1d21..0e49052 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -26,6 +26,6 @@ load 'test_helper/util' output=$( load_fixture "add_remove_empty_lines" | $diff_so_fancy ) run printf "%s" "$output" - assert_line --index 5 --partial "[1;32" # green added line - assert_line --index 8 --partial "[1;31" # red removed line + assert_line --index 5 --partial "5;22m" # green added line + assert_line --index 8 --partial "5;52m" # red removed line } From c7d0ecab7fde2158dec1207e1c3e5ea4b90282d1 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:30:21 -0700 Subject: [PATCH 361/443] update CI for bats --- .circleci/config.yml | 9 ++++----- .travis.yml | 5 +++-- appveyor.yml | 6 ++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 45d23f6..7f2c059 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,16 +5,15 @@ jobs: steps: - checkout - run: - name: + name: command: | sudo apt-get -y update sudo apt-get -y install shellcheck git - - - run: git clone --depth 1 https://github.com/sstephenson/bats.git + + - run: git submodule sync - run: git submodule update --init - run: name: tests command: | - export TERM=dumb && bats/bin/bats test/*.bats + export TERM=dumb && ./test/bats/bin/bats test shellcheck *.sh - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 7f4ad15..fb5a31c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,8 @@ addons: update: true before_install: - - git clone --depth 1 https://github.com/sstephenson/bats.git + - git submodule sync + - git submodule update --init - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir -p ~/perl5; fi @@ -44,4 +45,4 @@ install: - cpanm --quiet --notest Test::Perl::Critic Perl::Critic::Freenode script: - perlcritic -1 -q --theme freenode diff-so-fancy - - ./bats/bin/bats test/*.bats + - ./test/bats/bin/bats test diff --git a/appveyor.yml b/appveyor.yml index a6fb95e..6e9b61d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,8 @@ -install: - - git clone --depth 1 https://github.com/sstephenson/bats.git - build: false before_test: + - git submodule sync - git submodule update --init test_script: - - C:\cygwin\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER; bats/libexec/bats test/*.bats" + - C:\cygwin\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER; ./test/bats/bin/bats test" From 23723779419771b1d57f4a831ae710f9bd387d08 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:45:57 -0700 Subject: [PATCH 362/443] remove old submodules --- .gitmodules | 6 ------ test/test_helper/bats-assert | 1 - test/test_helper/bats-support | 1 - 3 files changed, 8 deletions(-) delete mode 160000 test/test_helper/bats-assert delete mode 160000 test/test_helper/bats-support diff --git a/.gitmodules b/.gitmodules index ec78a86..91716b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ -[submodule "test/test_helper/bats-core"] - path = test/test_helper/bats-support - url = https://github.com/ztombol/bats-core -[submodule "test/test_helper/bats-assert"] - path = test/test_helper/bats-assert - url = https://github.com/ztombol/bats-assert [submodule "test/bats"] path = test/bats url = https://github.com/bats-core/bats-core.git diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert deleted file mode 160000 index e0de84e..0000000 --- a/test/test_helper/bats-assert +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0de84e9c011223e7f88b7ccf1c929f4327097ba diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support deleted file mode 160000 index d140a65..0000000 --- a/test/test_helper/bats-support +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d140a65044b2d6810381935ae7f0c94c7023c8c3 From aa5cf05e95356d7fde26578fa6c83c267e11abd9 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 12:48:50 -0700 Subject: [PATCH 363/443] fresh submodules --- .gitmodules | 6 ++++++ test/test_helper/bats-assert | 1 + test/test_helper/bats-support | 1 + 3 files changed, 8 insertions(+) create mode 160000 test/test_helper/bats-assert create mode 160000 test/test_helper/bats-support diff --git a/.gitmodules b/.gitmodules index 91716b4..b7efcb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "test/bats"] path = test/bats url = https://github.com/bats-core/bats-core.git +[submodule "test/test_helper/bats-support"] + path = test/test_helper/bats-support + url = https://github.com/bats-core/bats-support.git +[submodule "test/test_helper/bats-assert"] + path = test/test_helper/bats-assert + url = https://github.com/bats-core/bats-assert.git diff --git a/test/test_helper/bats-assert b/test/test_helper/bats-assert new file mode 160000 index 0000000..e0de84e --- /dev/null +++ b/test/test_helper/bats-assert @@ -0,0 +1 @@ +Subproject commit e0de84e9c011223e7f88b7ccf1c929f4327097ba diff --git a/test/test_helper/bats-support b/test/test_helper/bats-support new file mode 160000 index 0000000..d140a65 --- /dev/null +++ b/test/test_helper/bats-support @@ -0,0 +1 @@ +Subproject commit d140a65044b2d6810381935ae7f0c94c7023c8c3 From 90c107374f265526eb8e289a40907ecae98362b9 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 13:21:53 -0700 Subject: [PATCH 364/443] Add a usage string when run without options --- third_party/cli_bench/cli_bench.pl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl index 1f3606e..3d7f3a9 100644 --- a/third_party/cli_bench/cli_bench.pl +++ b/third_party/cli_bench/cli_bench.pl @@ -24,7 +24,12 @@ 'ignore=i' => \$ignore, ); -my $cmd = join(" ", @ARGV); + +my $cmd = trim(join(" ", @ARGV)); + +if (!$cmd) { + die(usage()); +} $| = 0; # Disable output buffering @@ -49,6 +54,7 @@ # Remove the top and bottom 10% my $outlier = $num / 10; +@res = sort(@res); @res = splice(@res, $outlier, $num - $outlier * 2); my $avg = int(average(@res)); @@ -173,5 +179,8 @@ BEGIN } } -# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 +sub usage { + return "Usage: $0 [--num 50] 'cat /tmp/simple.diff | diff-so-fancy'\n"; +} +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 From 471c1b528220a98a2cc007aecbfbdd147b772b70 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 13:32:27 -0700 Subject: [PATCH 365/443] bats init in update-deps.sh --- hacking-and-testing.md | 6 +++--- update-deps.sh | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hacking-and-testing.md b/hacking-and-testing.md index 508c810..16f5bcd 100644 --- a/hacking-and-testing.md +++ b/hacking-and-testing.md @@ -17,8 +17,8 @@ cd ~/projects/catfabulator && git diff The tests use [bats-core](https://bats-core.readthedocs.io/en/latest/index.html), the Bash automated testing system. ```sh -git submodule sync -git submodule update --init +# initalize the bats components +git submodule sync && git submodule update --init # run the test suite once: ./test/bats/bin/bats test @@ -28,7 +28,7 @@ brew install entr find ./* test/* test/fixtures/* -maxdepth 0 | entr ./test/bats/bin/bats test ``` -When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | diff-so-fancy > output.txt` +When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | ./diff-so-fancy > output.txt` You can lint your scripts via shellcheck, our CI bots will also check. diff --git a/update-deps.sh b/update-deps.sh index 775e92e..52d9592 100755 --- a/update-deps.sh +++ b/update-deps.sh @@ -1,5 +1,9 @@ #!/bin/bash +# initalize the bats components +git submodule sync && git submodule update --init + + DIFFHIGHLIGHT_RAW_URL_BASE="https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight" DIFFHIGHLIGHT_FILES=( "DiffHighlight.pm" "README" ) From fe79bb3932818d7e80ad9a4fd46c2fe59a79e5e0 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 18 May 2021 13:41:20 -0700 Subject: [PATCH 366/443] shellcheck isn't too relevant anymore imo --- hacking-and-testing.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hacking-and-testing.md b/hacking-and-testing.md index 16f5bcd..10f8936 100644 --- a/hacking-and-testing.md +++ b/hacking-and-testing.md @@ -29,10 +29,3 @@ find ./* test/* test/fixtures/* -maxdepth 0 | entr ./test/bats/bin/bats test ``` When writing assertions, you'll likely want to compare to expected output. To grab that reliably, you can use something like `git --no-pager diff | ./diff-so-fancy > output.txt` - -You can lint your scripts via shellcheck, our CI bots will also check. - -```sh -brew install shellcheck -shellcheck *.sh -``` From b08ea1d92bc1a9b9d366757ec043b4352d0ad550 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 13:58:58 -0700 Subject: [PATCH 367/443] Add a basic histograph for the data --- third_party/cli_bench/cli_bench.pl | 42 ++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl index 3d7f3a9..9f05ee2 100644 --- a/third_party/cli_bench/cli_bench.pl +++ b/third_party/cli_bench/cli_bench.pl @@ -16,12 +16,14 @@ ############################################################################### ############################################################################### -my $num = 50; -my $ignore = 1; +my $num = 50; +my $ignore = 1; +my $details = 1; my $ok = GetOptions( 'num=i' => \$num, 'ignore=i' => \$ignore, + 'details!' => \$details, ); @@ -59,6 +61,11 @@ my $avg = int(average(@res)); +if ($details) { + show_details(@res); + print "\n"; +} + print "Ran '$cmd' $num times with average completion time of $avg ms\n"; if ($exit != 0) { @@ -68,6 +75,37 @@ ############################################################################### ############################################################################### +sub show_details { + my @res = @_; + + my $x = {}; + my $max = 0; + + # Build a hash of all the times:count + foreach my $time (@res) { + $x->{$time}++; + + if ($x->{$time} > $max) { + $max = $x->{$time}; + } + } + + my $target_width = 100; # How wide we want the bar + text + my $total = scalar(@res); + my $scale = ($target_width - 15) / $max; + + print "\n"; + + # Print out a basic histogram of the times + foreach my $time (sort(keys %$x)) { + my $count = $x->{$time}; + my $percent = sprintf("%0.1f", ($count / $total) * 100); + + my $bar = "%" x ($count * $scale); + print "$time ms: $bar ($percent%)\n"; + } +} + sub average { my $ret = 0; From e526f6b7abaac9e174b2956a16255c425a063c22 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 18 May 2021 14:21:23 -0700 Subject: [PATCH 368/443] Don't pull in upstream DiffHighlight We've diverged a little from upstream, so pulling it in directly will break things --- update-deps.sh | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/update-deps.sh b/update-deps.sh index 52d9592..cec889e 100755 --- a/update-deps.sh +++ b/update-deps.sh @@ -2,14 +2,3 @@ # initalize the bats components git submodule sync && git submodule update --init - - -DIFFHIGHLIGHT_RAW_URL_BASE="https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight" -DIFFHIGHLIGHT_FILES=( "DiffHighlight.pm" "README" ) - -for file in "${DIFFHIGHLIGHT_FILES[@]}"; -do - url="$DIFFHIGHLIGHT_RAW_URL_BASE/$file" - echo "$url" - curl -#Lo "lib/$file" "$url" -done From b0d5af29f98ae3338c16fc3d243f39009e218f7b Mon Sep 17 00:00:00 2001 From: Eric Nielsen Date: Wed, 19 May 2021 20:12:34 -0500 Subject: [PATCH 369/443] Only call sub git_config_raw once and no need to memoize its output, since the parsed output is already memoized in sub git_config. --- diff-so-fancy | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1cce75e..f3cac1b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -434,30 +434,19 @@ sub boolean { } } -# Memoize getting the git config +# Get the git config sub git_config_raw { - state $static_config; - - if ($static_config) { - # If we already have the config return that - return $static_config; - } - my $cmd = "git config --list"; my @out = `$cmd`; - $static_config = \@out; - return \@out; } -# Fetch a textual item from the git config +# Memoize fetching a textual item from the git config sub git_config { my $search_key = lc($_[0] || ""); my $default_value = lc($_[1] || ""); - my $out = git_config_raw(); - # If we're in a unit test, use the default (don't read the users config) if (in_unit_test()) { return $default_value; @@ -472,6 +461,8 @@ sub git_config { print "Parsing git config\n"; } + my $out = git_config_raw(); + foreach my $line (@$out) { if ($line =~ /=/) { my ($key,$value) = split("=",$line,2); From 800cf91f3169f9ac2c5ee1b04decb5439c4227ca Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 26 May 2021 08:52:50 -0700 Subject: [PATCH 370/443] Clean up an if/else to make it more readable --- diff-so-fancy | 70 +++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1cce75e..5518d09 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -86,42 +86,52 @@ if (!$has_stdin) { } else { die("Missing input on STDIN\n"); } -} else { - # Check to see if were using default settings - check_first_run(); - - my @lines; - local $DiffHighlight::line_cb = sub { - push(@lines,@_); - - my $last_line = $lines[-1]; - - # Buffer X lines before we try and output anything - # Also make sure we're sending enough data to d-s-f to do it's magic. - # Certain things require a look-ahead line or two to function so - # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { - do_dsf_stuff(\@lines); - @lines = (); - } - }; +} - my $line_count = 0; - while (my $line = ) { - # If the very first line of the diff doesn't start with ANSI color we're assuming - # it's a raw patch file, and we have to color the added/removed lines ourself - if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { - $manually_color_lines = 1; - } +################################################################################# +################################################################################# + +# Check to see if were using default settings +check_first_run(); - my $ok = DiffHighlight::handle_line($line); - $line_count++; +# The logic here is that we run all the lines through DiffHighlight first. This +# highlights all the intra-word changes. Then we take those lines and send them +# to do_dsf_stuff() to convert the diff to human readable d-s-f output and add +# appropriate fanciness + +my @lines; +local $DiffHighlight::line_cb = sub { + push(@lines,@_); + + my $last_line = $lines[-1]; + + # Buffer X lines before we try and output anything + # Also make sure we're sending enough data to d-s-f to do it's magic. + # Certain things require a look-ahead line or two to function so + # we make sure we don't break on those sections prematurely + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { + do_dsf_stuff(\@lines); + @lines = (); + } +}; + +my $line_count = 0; +while (my $line = ) { + # If the very first line of the diff doesn't start with ANSI color we're assuming + # it's a raw patch file, and we have to color the added/removed lines ourself + if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { + $manually_color_lines = 1; } - DiffHighlight::flush(); - do_dsf_stuff(\@lines); + my $ok = DiffHighlight::handle_line($line); + $line_count++; } +# If we're mid hunk above process anything still pending +DiffHighlight::flush(); +do_dsf_stuff(\@lines); + +################################################################################# ################################################################################# sub do_dsf_stuff { From be50775a70e6cafc720a4ebc0b30ea193bc54e7b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 26 May 2021 09:05:17 -0700 Subject: [PATCH 371/443] Add a fatpacked version so it's hosted on GitHub --- third_party/build_fatpack/diff-so-fancy | 1390 +++++++++++++++++++++++ 1 file changed, 1390 insertions(+) create mode 100755 third_party/build_fatpack/diff-so-fancy diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy new file mode 100755 index 0000000..0eccb98 --- /dev/null +++ b/third_party/build_fatpack/diff-so-fancy @@ -0,0 +1,1390 @@ +#!/usr/bin/env perl + +# This chunk of stuff was generated by App::FatPacker. To find the original +# file's code, look for the end of this BEGIN block or the string 'FATPACK' +BEGIN { +my %fatpacked; + +$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT'; + package DiffHighlight; + + use 5.008; + use warnings FATAL => 'all'; + use strict; + + # Use the correct value for both UNIX and Windows (/dev/null vs nul) + use File::Spec; + + my $NULL = File::Spec->devnull(); + + # Highlight by reversing foreground and background. You could do + # other things like bold or underline if you prefer. + our @OLD_HIGHLIGHT = ( + "\e[1;31m", + "\e[1;31;48;5;52m", + "\x1b[27m", + ); + our @NEW_HIGHLIGHT = ( + "\e[1;32m", + "\e[1;32;48;5;22m", + $OLD_HIGHLIGHT[2], + ); + + + + my $RESET = "\x1b[m"; + my $COLOR = qr/\x1b\[[0-9;]*m/; + my $BORING = qr/$COLOR|\s/; + + my @removed; + my @added; + my $in_hunk; + my $graph_indent = 0; + + our $line_cb = sub { print @_ }; + our $flush_cb = sub { local $| = 1 }; + + # Count the visible width of a string, excluding any terminal color sequences. + sub visible_width { + local $_ = shift; + my $ret = 0; + while (length) { + if (s/^$COLOR//) { + # skip colors + } elsif (s/^.//) { + $ret++; + } + } + return $ret; + } + + # Return a substring of $str, omitting $len visible characters from the + # beginning, where terminal color sequences do not count as visible. + sub visible_substr { + my ($str, $len) = @_; + while ($len > 0) { + if ($str =~ s/^$COLOR//) { + next + } + $str =~ s/^.//; + $len--; + } + return $str; + } + + sub handle_line { + my $orig = shift; + local $_ = $orig; + + # match a graph line that begins a commit + if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space + $COLOR?\*$COLOR?[ ] # a "*" with its trailing space + (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" + [ ]* # trailing whitespace for merges + /x) { + my $graph_prefix = $&; + + # We must flush before setting graph indent, since the + # new commit may be indented differently from what we + # queued. + flush(); + $graph_indent = visible_width($graph_prefix); + + } elsif ($graph_indent) { + if (length($_) < $graph_indent) { + $graph_indent = 0; + } else { + $_ = visible_substr($_, $graph_indent); + } + } + + if (!$in_hunk) { + $line_cb->($orig); + $in_hunk = /^$COLOR*\@\@ /; + } + elsif (/^$COLOR*-/) { + push @removed, $orig; + } + elsif (/^$COLOR*\+/) { + push @added, $orig; + } + else { + flush(); + $line_cb->($orig); + $in_hunk = /^$COLOR*[\@ ]/; + } + + # Most of the time there is enough output to keep things streaming, + # but for something like "git log -Sfoo", you can get one early + # commit and then many seconds of nothing. We want to show + # that one commit as soon as possible. + # + # Since we can receive arbitrary input, there's no optimal + # place to flush. Flushing on a blank line is a heuristic that + # happens to match git-log output. + if (!length) { + $flush_cb->(); + } + } + + sub flush { + # Flush any queued hunk (this can happen when there is no trailing + # context in the final diff of the input). + show_hunk(\@removed, \@added); + @removed = (); + @added = (); + } + + sub highlight_stdin { + while () { + handle_line($_); + } + flush(); + } + + # Ideally we would feed the default as a human-readable color to + # git-config as the fallback value. But diff-highlight does + # not otherwise depend on git at all, and there are reports + # of it being used in other settings. Let's handle our own + # fallback, which means we will work even if git can't be run. + sub color_config { + my ($key, $default) = @_; + + # Removing the redirect speeds up execution by about 12ms + #my $s = `git config --get-color $key 2>$NULL`; + my $s = `git config --get-color $key`; + + return length($s) ? $s : $default; + } + + sub show_hunk { + my ($a, $b) = @_; + + # If one side is empty, then there is nothing to compare or highlight. + if (!@$a || !@$b) { + $line_cb->(@$a, @$b); + return; + } + + # If we have mismatched numbers of lines on each side, we could try to + # be clever and match up similar lines. But for now we are simple and + # stupid, and only handle multi-line hunks that remove and add the same + # number of lines. + if (@$a != @$b) { + $line_cb->(@$a, @$b); + return; + } + + my @queue; + for (my $i = 0; $i < @$a; $i++) { + my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); + $line_cb->($rm); + push @queue, $add; + } + $line_cb->(@queue); + } + + sub highlight_pair { + my @a = split_line(shift); + my @b = split_line(shift); + + # Find common prefix, taking care to skip any ansi + # color codes. + my $seen_plusminus; + my ($pa, $pb) = (0, 0); + while ($pa < @a && $pb < @b) { + if ($a[$pa] =~ /$COLOR/) { + $pa++; + } + elsif ($b[$pb] =~ /$COLOR/) { + $pb++; + } + elsif ($a[$pa] eq $b[$pb]) { + $pa++; + $pb++; + } + elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { + $seen_plusminus = 1; + $pa++; + $pb++; + } + else { + last; + } + } + + # Find common suffix, ignoring colors. + my ($sa, $sb) = ($#a, $#b); + while ($sa >= $pa && $sb >= $pb) { + if ($a[$sa] =~ /$COLOR/) { + $sa--; + } + elsif ($b[$sb] =~ /$COLOR/) { + $sb--; + } + elsif ($a[$sa] eq $b[$sb]) { + $sa--; + $sb--; + } + else { + last; + } + } + + if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), + highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); + } + else { + return join('', @a), + join('', @b); + } + } + + # we split either by $COLOR or by character. This has the side effect of + # leaving in graph cruft. It works because the graph cruft does not contain "-" + # or "+" + sub split_line { + local $_ = shift; + return utf8::decode($_) ? + map { utf8::encode($_); $_ } + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/ : + map { /$COLOR/ ? $_ : (split //) } + split /($COLOR+)/; + } + + sub highlight_line { + my ($line, $prefix, $suffix, $theme) = @_; + + my $start = join('', @{$line}[0..($prefix-1)]); + my $mid = join('', @{$line}[$prefix..$suffix]); + my $end = join('', @{$line}[($suffix+1)..$#$line]); + + # If we have a "normal" color specified, then take over the whole line. + # Otherwise, we try to just manipulate the highlighted bits. + if (defined $theme->[0]) { + s/$COLOR//g for ($start, $mid, $end); + chomp $end; + return join('', + $theme->[0], $start, $RESET, + $theme->[1], $mid, $RESET, + $theme->[0], $end, $RESET, + "\n" + ); + } else { + return join('', + $start, + $theme->[1], $mid, $theme->[2], + $end + ); + } + } + + # Pairs are interesting to highlight only if we are going to end up + # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting + # is just useless noise. We can detect this by finding either a matching prefix + # or suffix (disregarding boring bits like whitespace and colorization). + sub is_pair_interesting { + my ($a, $pa, $sa, $b, $pb, $sb) = @_; + my $prefix_a = join('', @$a[0..($pa-1)]); + my $prefix_b = join('', @$b[0..($pb-1)]); + my $suffix_a = join('', @$a[($sa+1)..$#$a]); + my $suffix_b = join('', @$b[($sb+1)..$#$b]); + + return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || + visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || + $suffix_a !~ /^$BORING*$/ || + $suffix_b !~ /^$BORING*$/; + } +DIFFHIGHLIGHT + +s/^ //mg for values %fatpacked; + +my $class = 'FatPacked::'.(0+\%fatpacked); +no strict 'refs'; +*{"${class}::files"} = sub { keys %{$_[0]} }; + +if ($] < 5.008) { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + my $pos = 0; + my $last = length $fat; + return (sub { + return 0 if $pos == $last; + my $next = (1 + index $fat, "\n", $pos) || $last; + $_ .= substr $fat, $pos, $next - $pos; + $pos = $next; + return 1; + }); + } + }; +} + +else { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + open my $fh, '<', \$fat + or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; + return $fh; + } + return; + }; +} + +unshift @INC, bless \%fatpacked, $class; + } # END OF FATPACK CODE + + +my $VERSION = "1.4.1"; + +################################################################################# + +use v5.010; # Require Perl 5.10 for 'state' variables +use warnings FATAL => 'all'; +use strict; + +use File::Spec; # For catdir +use File::Basename; # For dirname +use Cwd qw(abs_path); # For realpath() +use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC +use DiffHighlight; + +my $remove_file_add_header = 1; +my $remove_file_delete_header = 1; +my $clean_permission_changes = 1; +my $patch_mode = 0; +my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this +my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); +my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); +my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); +my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); +my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); +my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); +my $has_stdin = has_stdin(); + +my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; +my $reset_color = color("reset"); +my $bold = color("bold"); +my $meta_color = ""; + +# Set the diff highlight colors from the config +init_diff_highlight_colors(); + +my ($file_1,$file_2); +my $args = argv(); # Hashref of all the ARGV stuff +my $last_file_seen = ""; +my $last_file_mode = ""; +my $i = 0; +my $in_hunk = 0; +my $columns_to_remove = 0; +my $is_mercurial = 0; +my $color_forced = 0; # Has the color been forced on/off + +# We try and be smart about whether we need to do line coloring, but +# this is an option to force it on/off +if ($args->{color_on}) { + $manually_color_lines = 1; + $color_forced = 1; +} elsif ($args->{color_off}) { + $manually_color_lines = 0; + $color_forced = 1; +} + +if ($args->{debug}) { + show_debug_info(); + exit(); +} + +# `git add --patch` requires our output to match the number of lines from the +# input. So, when patch mode is active, we print out empty lines to pad our +# output to match any lines we've consumed. +if ($args->{patch}) { + $patch_mode = 1; +} + +# We only process ARGV if we don't have STDIN +if (!$has_stdin) { + if ($args->{v} || $args->{version}) { + die(version()); + } elsif ($args->{'set-defaults'}) { + my $ok = set_defaults(); + } elsif ($args->{colors}) { + # We print this to STDOUT so we can redirect to bash to auto-set the colors + print get_default_colors(); + exit; + } elsif (!%$args || $args->{help} || $args->{h}) { + my $first = check_first_run(); + + if (!$first) { + die(usage()); + } + } else { + die("Missing input on STDIN\n"); + } +} + +################################################################################# +################################################################################# + +# Check to see if were using default settings +check_first_run(); + +# The logic here is that we run all the lines through DiffHighlight first. This +# highlights all the intra-word changes. Then we take those lines and send them +# to do_dsf_stuff() to convert the diff to human readable d-s-f output and add +# appropriate fanciness + +my @lines; +local $DiffHighlight::line_cb = sub { + push(@lines,@_); + + my $last_line = $lines[-1]; + + # Buffer X lines before we try and output anything + # Also make sure we're sending enough data to d-s-f to do it's magic. + # Certain things require a look-ahead line or two to function so + # we make sure we don't break on those sections prematurely + if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { + do_dsf_stuff(\@lines); + @lines = (); + } +}; + +my $line_count = 0; +while (my $line = ) { + # If the very first line of the diff doesn't start with ANSI color we're assuming + # it's a raw patch file, and we have to color the added/removed lines ourself + if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { + $manually_color_lines = 1; + } + + my $ok = DiffHighlight::handle_line($line); + $line_count++; +} + +# If we're mid hunk above process anything still pending +DiffHighlight::flush(); +do_dsf_stuff(\@lines); + +################################################################################# +################################################################################# + +sub do_dsf_stuff { + my $input = shift(); + + #print STDERR "START -------------------------------------------------\n"; + #print STDERR join("",@$input); + #print STDERR "END ---------------------------------------------------\n"; + + while (my $line = shift(@$input)) { + ###################################################### + # Pre-process the line before we do any other markup # + ###################################################### + + # If the first line of the input is a blank line, skip that + if ($i == 0 && $line =~ /^\s*$/) { + next; + } + + ###################### + # End pre-processing # + ###################### + + ####################################################################### + + #################################################################### + # Look for git index and replace it horizontal line (header later) # + #################################################################### + if ($line =~ /^${ansi_color_regex}index /) { + # Print the line color and then the actual line + $meta_color = $1 || get_config_color("meta"); + + # Get the next line without incrementing counter while loop + my $next = $input->[0] || ""; + my ($file_1,$file_2); + + # The line immediately after the "index" line should be the --- file line + # If it's not it's an empty file add/delete + if ($next !~ /^$ansi_color_regex(---|Binary files)/) { + + # We fake out the file names since it's a raw add/delete + if ($last_file_mode eq "add") { + $file_1 = "/dev/null"; + $file_2 = $last_file_seen; + } elsif ($last_file_mode eq "delete") { + $file_1 = $last_file_seen; + $file_2 = "/dev/null"; + } + } + + if ($file_1 && $file_2) { + print horizontal_rule($meta_color); + print $meta_color . file_change_string($file_1,$file_2) . "\n"; + print horizontal_rule($meta_color); + } + ######################### + # Look for the filename # + ######################### + # $4 $5 + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { + + # Mercurial looks like: diff -r 82e55d328c8c hello.c + if ($4 eq "-r") { + $is_mercurial = 1; + $meta_color ||= get_config_color("meta"); + # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy + } else { + $last_file_seen = $5; + } + + $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). + $in_hunk = 0; + if ($patch_mode) { + # we are consuming one line, and the debt must be paid + print "\n"; + } + ######################################## + # Find the first file: --- a/README.md # + ######################################## + } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { + $meta_color ||= get_config_color("meta"); + + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_1 = $file_dir . $5; + } else { + $file_1 = $5; + } + + # Find the second file on the next line: +++ b/README.md + my $next = shift(@$input); + $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; + if ($1) { + print $1; # Print out whatever color we're using + } + if ($git_strip_prefix) { + my $file_dir = $4 || ""; + $file_2 = $file_dir . $5; + } else { + $file_2 = $5; + } + + if ($file_2 ne "/dev/null") { + $last_file_seen = $file_2; + } + + # Print out the top horizontal line of the header + print $reset_color; + print horizontal_rule($meta_color); + + # Mercurial coloring is slightly different so we need to hard reset colors + if ($is_mercurial) { + print $reset_color; + } + + print $meta_color; + print file_change_string($file_1,$file_2) . "\n"; + + # Print out the bottom horizontal line of the header + print horizontal_rule($meta_color); + ######################################## + # Check for "@@ -3,41 +3,63 @@" syntax # + ######################################## + } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + + print $line; + } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + + my $hunk_header = $4; + my $remain = bleach_text($5); + + # The number of colums to remove (1 or 2) is based on how many commas in the hunk header + $columns_to_remove = (char_count(",",$hunk_header)) - 1; + # On single line removes there is NO comma in the hunk so we force one + if ($columns_to_remove <= 0) { + $columns_to_remove = 1; + } + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); + #$last_file_seen = basename($last_file_seen); + + # Figure out the start line + my $start_line = start_line_calc($new_offset,$new_count); + + # Last function has it's own color + my $last_function_color = ""; + if ($remain) { + $last_function_color = get_config_color("last_function"); + } + + # Check to see if we have the color for the fragment from git + if ($5 =~ /\e\[\d/) { + #print "Has ANSI color for fragment\n"; + } else { + # We don't have the ANSI sequence so we shell out to get it + #print "No ANSI color for fragment\n"; + my $frag_color = get_config_color("fragment"); + print $frag_color; + } + + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; + ################################### + # Remove any new file permissions # + ################################### + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + # Don't print the line (i.e. remove it from the output); + $last_file_mode = "add"; + if ($patch_mode) { + print "\n"; + } + ###################################### + # Remove any delete file permissions # + ###################################### + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + # Don't print the line (i.e. remove it from the output); + $last_file_mode = "delete"; + if ($patch_mode) { + print "\n"; + } + ################################ + # Look for binary file changes # + ################################ + } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { + my $change = file_change_string($2,$4); + print horizontal_rule($meta_color); + print "$meta_color$change (binary)\n"; + print horizontal_rule($meta_color); + ##################################################### + # Check if we're changing the permissions of a file # + ##################################################### + } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { + my ($old_mode) = $4; + my $next = shift(@$input); + + if ($1) { + print $1; # Print out whatever color we're using + } + + my ($new_mode) = $next =~ m/new mode (\d+)/; + + if ($patch_mode) { + print "\n"; + } + print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; + + ############### + # File rename # + ############### + } elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) { + my $simil = $4; + + # If it's a move with content change we ignore this and the next two lines + if ($simil != 100) { + shift(@$input); + shift(@$input); + next; + } + + my $next = shift(@$input); + my ($file1) = $next =~ /rename from (.+?)(\e|\t|$)/; + + $next = shift(@$input); + my ($file2) = $next =~ /rename to (.+?)(\e|\t|$)/; + + if ($file1 && $file2) { + # We may not have extracted this yet, so we pull from the config if not + $meta_color ||= get_config_color("meta"); + + my $change = file_change_string($file1,$file2); + + print horizontal_rule($meta_color); + print $meta_color . $change . "\n"; + print horizontal_rule($meta_color); + } + + $i += 3; # We've consumed three lines + next; + ##################################### + # Just a regular line, print it out # + ##################################### + } else { + # Mark empty line with a red/green box indicating addition/removal + if ($mark_empty_lines) { + $line = mark_empty_line($line); + } + + # Remove the correct number of leading " " or "+" or "-" + if ($strip_leading_indicators) { + $line = strip_leading_indicators($line,$columns_to_remove); + } + print $line; + } + + $i++; + } +} + +###################################################################################################### +# End regular code, begin functions +###################################################################################################### + +# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 +sub parse_hunk_header { + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; + $o_cnt = 1 unless defined $o_cnt; + $n_cnt = 1 unless defined $n_cnt; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); +} + +# Mark the first char of an empty line +sub mark_empty_line { + my $line = shift(); + + my $reset_color = "\e\\[0?m"; + my $reset_escape = "\e\[m"; + my $invert_color = "\e\[7m"; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + + # This captures lines that do not have any ANSI in them (raw vanilla diff) + if ($line eq "+\n") { + $line = $invert_color . $add_color . " " . color('reset') . "\n"; + # This captures lines that do not have any ANSI in them (raw vanilla diff) + } elsif ($line eq "-\n") { + $line = $invert_color . $del_color . " " . color('reset') . "\n"; + # This handles everything else + } else { + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + } + + return $line; +} + +# String to boolean +sub boolean { + my $str = shift(); + $str = trim($str); + + if ($str eq "" || $str =~ /^(no|false|0)$/i) { + return 0; + } else { + return 1; + } +} + +# Get the git config +sub git_config_raw { + my $cmd = "git config --list"; + my @out = `$cmd`; + + return \@out; +} + +# Memoize fetching a textual item from the git config +sub git_config { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || ""); + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return $default_value; + } + + state $raw = {}; + if (%$raw && $search_key) { + return $raw->{$search_key} || $default_value; + } + + if ($args->{debug}) { + print "Parsing git config\n"; + } + + my $out = git_config_raw(); + + foreach my $line (@$out) { + if ($line =~ /=/) { + my ($key,$value) = split("=",$line,2); + $value =~ s/\s+$//; + $raw->{$key} = $value; + } + } + + # If we're given a search key return that, else return the hash + if ($search_key) { + return $raw->{$search_key} || $default_value; + } else { + return $raw; + } +} + +# Fetch a boolean item from the git config +sub git_config_boolean { + my $search_key = lc($_[0] || ""); + my $default_value = lc($_[1] || 0); # Default to false + + # If we're in a unit test, use the default (don't read the users config) + if (in_unit_test()) { + return boolean($default_value); + } + + my $result = git_config($search_key,$default_value); + my $ret = boolean($result); + + return $ret; +} + +# Check if we're inside of BATS +sub in_unit_test { + if ($ENV{BATS_CWD}) { + return 1; + } else { + return 0; + } +} + +sub get_less_charset { + my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); + foreach my $key (@less_char_vars) { + my $val = $ENV{$key}; + + if (defined $val) { + return ($key, $val); + } + } + + return (); +} + +sub should_print_unicode { + if (-t STDOUT) { + # Always print unicode chars if we're not piping stuff, e.g. to less(1) + return 1; + } + + # Otherwise, assume we're piping to less(1) + my ($less_env_var, $less_charset) = get_less_charset(); + if ($less_charset && $less_charset =~ /utf-?8/i) { + return 1; + } + + return 0; +} + +# Try and be smart about what line the diff hunk starts on +sub start_line_calc { + my ($line_num,$diff_context) = @_; + my $ret; + + if ($line_num == 0 && $diff_context == 0) { + return 1; + } + + # Git defaults to three lines of context + my $default_context_lines = 3; + # Three lines on either side, and the line itself = 7 + my $expected_context = ($default_context_lines * 2 + 1); + + # The first three lines + if ($line_num == 1 && $diff_context < $expected_context) { + $ret = $diff_context - $default_context_lines; + } else { + $ret = $line_num + $default_context_lines; + } + + if ($ret < 1) { + $ret = 1; + } + + return $ret; +} + +# Remove + or - at the beginning of the lines +sub strip_leading_indicators { + my $line = shift(); # Array passed in by reference + my $columns_to_remove = shift(); # Don't remove any lines by default + + if ($columns_to_remove == 0) { + return $line; # Nothing to do + } + + $line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/; + + if ($manually_color_lines) { + if (defined($5) && $5 eq "+") { + my $add_line_color = get_config_color("add_line"); + $line = $add_line_color . insert_reset_at_line_end($line); + } elsif (defined($5) && $5 eq "-") { + my $remove_line_color = get_config_color("remove_line"); + $line = $remove_line_color . insert_reset_at_line_end($line); + } + } + + return $line; +} + +# Insert the color reset code at end of line, but before any newlines +sub insert_reset_at_line_end { + my $line = shift(); + $line =~ s/^(.*)([\n\r]+)?$/${1}${reset_color}${2}/; + return $line; +} + +# Count the number of a given char in a string +sub char_count { + my ($needle,$str) = @_; + my $len = length($str); + my $ret = 0; + + for (my $i = 0; $i < $len; $i++) { + my $found = substr($str,$i,1); + + if ($needle eq $found) { $ret++; } + } + + return $ret; +} + +# Remove all ANSI codes from a string +sub bleach_text { + my $str = shift(); + $str =~ s/\e\[\d*(;\d+)*m//mg; + + return $str; +} + +# Remove all trailing and leading spaces +sub trim { + my $s = shift(); + if (!$s) { return ""; } + $s =~ s/^\s*|\s*$//g; + + return $s; +} + +# Print a line of em-dash or line-drawing chars the full width of the screen +sub horizontal_rule { + my $color = $_[0] || ""; + my $width = get_terminal_width(); + + # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm + #my $dash = "\x{2014}"; + # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm + my $dash; + if ($use_unicode_dash_for_ruler && should_print_unicode()) { + #$dash = Encode::encode('UTF-8', "\x{2500}"); + $dash = "\xE2\x94\x80"; + } else { + $dash = "-"; + } + + # Draw the line + my $ret = $color . ($dash x $width) . "$reset_color\n"; + + return $ret; +} + +sub file_change_string { + my $file_1 = shift(); + my $file_2 = shift(); + + # If they're the same it's a modify + if ($file_1 eq $file_2) { + return "modified: $file_1"; + # If the first is /dev/null it's a new file + } elsif ($file_1 eq "/dev/null") { + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + return "added: $add_color$file_2$reset_color"; + # If the second is /dev/null it's a deletion + } elsif ($file_2 eq "/dev/null") { + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + return "deleted: $del_color$file_1$reset_color"; + # If the files aren't the same it's a rename + } elsif ($file_1 ne $file_2) { + my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); + # highlight_pair already includes reset_color, but adds newline characters that need to be trimmed off + $old = trim($old); + $new = trim($new); + return "renamed: $old$meta_color to $new" + # Something we haven't thought of yet + } else { + return "$file_1 -> $file_2"; + } +} + +# Check to see if STDIN is connected to an interactive terminal +sub has_stdin { + my $i = -t STDIN; + my $ret = int(!$i); + + return $ret; +} + +# We use this instead of Getopt::Long because it's faster and we're not parsing any +# crazy arguments +# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html +sub argv { + my $ret = {}; + + for (my $i = 0; $i < scalar(@ARGV); $i++) { + + # If the item starts with "-" it's a key + if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) { + # If the next item does not start with "--" it's the value for this item + if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { + $ret->{$key} = $ARGV[$i + 1]; + # Bareword like --verbose with no options + } else { + $ret->{$key}++; + } + } + } + + # We're looking for a certain item + if ($_[0]) { return $ret->{$_[0]}; } + + return $ret; +} + +# Output the command line usage for d-s-f +sub usage { + my $out = color("white_bold") . version() . color("reset") . "\n"; + + $out .= "Usage: + +git diff --color | diff-so-fancy # Use d-s-f on one diff +cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file +diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output + +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +diff-so-fancy --patch # Use diff-so-fancy in patch mode (interoperable with `git add --patch`) + +# Configure git to use d-s-f for *all* diff operations +git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" + +# Configure git to use d-s-f for `git add --patch` +git config --global interactive.diffFilter \"diff-so-fancy --patch\"\n"; + + return $out; +} + +sub get_default_colors { + my $out = "# Recommended default colors for diff-so-fancy\n"; + $out .= "# --------------------------------------------\n"; + $out .= 'git config --global color.ui true + +git config --global color.diff-highlight.oldNormal "red bold" +git config --global color.diff-highlight.oldHighlight "red bold 52" +git config --global color.diff-highlight.newNormal "green bold" +git config --global color.diff-highlight.newHighlight "green bold 22" + +git config --global color.diff.meta "yellow" +git config --global color.diff.frag "magenta bold" +git config --global color.diff.commit "yellow bold" +git config --global color.diff.old "red bold" +git config --global color.diff.new "green bold" +git config --global color.diff.whitespace "red reverse" +'; + + return $out; +} + +# Output the current version string +sub version { + my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; + $ret .= "Version : $VERSION\n"; + + return $ret; +} + +sub is_windows { + if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { + return 1; + } else { + return 0; + } +} + +# Return value is whether this is the first time they've run d-s-f +sub check_first_run { + my $ret = 0; + + # If first-run is not set, or it's set to "true" + my $first_run = git_config_boolean('diff-so-fancy.first-run'); + # See if they're previously set SOME diff-highlight colors + my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); + + #$first_run = 1; $has_dh_colors = 0; + + if (!$first_run || $has_dh_colors) { + return 0; + } else { + print "This appears to be the first time you've run diff-so-fancy, please note\n"; + print "that the default git colors are not ideal. Diff-so-fancy recommends the\n"; + print "following colors.\n\n"; + + print get_default_colors(); + + # Set the first run flag to false + my $cmd = 'git config --global diff-so-fancy.first-run false'; + system($cmd); + + exit; + } + + return 1; +} + +sub set_defaults { + my $color_config = get_default_colors(); + my $git_config = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"'; + my $first_cmd = 'git config --global diff-so-fancy.first-run false'; + + my @cmds = split(/\n/,$color_config); + push(@cmds,$git_config); + push(@cmds,$first_cmd); + + # Remove all comments from the commands + foreach my $x (@cmds) { + $x =~ s/#.*//g; + } + + # Remove any empty commands + @cmds = grep($_,@cmds); + + foreach my $cmd (@cmds) { + system($cmd); + my $exit = ($? >> 8); + + if ($exit != 0) { + die("Error running: '$cmd' (error #18941)\n"); + } + } + + return 1; +} + +# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' +sub color { + my $str = shift(); + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g; + my ($bc) = $str =~ /on_?(\d+)/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd // 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + +# Get colors used for various output sections (memoized) +{ + my $static_config; + + sub get_config_color { + my $str = shift(); + + my $ret = ""; + if ($static_config->{$str}) { + return $static_config->{$str}; + } + + #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); + + if ($str eq "meta") { + # Default ANSI yellow + $ret = git_ansi_color(git_config('color.diff.meta')) || color(11); + } elsif ($str eq "reset") { + $ret = color("reset"); + } elsif ($str eq "add_line") { + # Default ANSI green + $ret = git_ansi_color(git_config('color.diff.new')) || color("2_bold"); + } elsif ($str eq "remove_line") { + # Default ANSI red + $ret = git_ansi_color(git_config('color.diff.old')) || color("1_bold"); + } elsif ($str eq "fragment") { + $ret = git_ansi_color(git_config('color.diff.frag')) || color("13_bold"); + } elsif ($str eq "last_function") { + $ret = git_ansi_color(git_config('color.diff.func')) || color("146_bold"); + } + + # Cache (memoize) the entry for later + $static_config->{$str} = $ret; + + return $ret; + } +} + +sub git_ansi_color { + my $str = shift(); + my @parts = split(' ', $str); + + if (!@parts) { + return ''; + } + + my $colors = { + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + }; + + my $fg = $parts[0] || ""; + my $mod = $parts[1] || ""; + my $bg = $parts[2] || ""; + + my @ansi_part = (); + ############################################# + + if ($mod eq 'bold') { + push(@ansi_part, "1"); + } + + ############################################# + + # It's an RGB value + if (is_numeric($fg)) { + push(@ansi_part, "38;5;$fg"); + # It's a simple 16 color OG ansi + } elsif ($fg) { + push(@ansi_part, $colors->{$fg} + 30); + } + + ############################################# + + # It's an RGB value + if (is_numeric($bg)) { + push(@ansi_part, "48;5;$bg"); + # It's a simple 16 color OG ansi + } elsif ($bg) { + push(@ansi_part, $colors->{$fg} + 40); + } + + ############################################# + + my $ansi_str = join(";", @ansi_part); + my $ret = "\e[" . $ansi_str . "m"; + + return $ret; +} + +sub is_numeric { + my $s = shift(); + + if ($s =~ /^\d+$/) { + return 1; + } + + return 0; +} + +sub starts_with_ansi { + my $str = shift(); + + if ($str =~ /^$ansi_color_regex/) { + return 1; + } else { + return 0; + } +} + +sub get_terminal_width { + # Make width static so we only calculate it once + state $width; + + if ($width) { + return $width; + } + + # If there is a ruler width in the config we use that + if ($ruler_width) { + $width = $ruler_width; + # Otherwise we check the terminal width using tput + } else { + my $tput = `tput cols`; + + if ($tput) { + $width = int($tput); + + if (is_windows()) { + $width--; + } + } else { + print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; + $width = 80; + } + } + + return $width; +} + +sub show_debug_info { + my @less = get_less_charset(); + my $git_ver = trim(`git --version`); + $git_ver =~ s/[^\d.]//g; + + print "Diff-so-fancy : v$VERSION\n"; + print "Git : v$git_ver\n"; + print "Perl : $^V\n"; + print "\n"; + + print "Terminal width : " . get_terminal_width() . "\n"; + print "Terminal \$LANG : " . ($ENV{LANG} || "") . "\n"; + print "\n"; + print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; + print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; + print "\n"; + print "Less Charset Var: " . ($less[0] // "") . "\n"; + print "Less Charset : " . ($less[1] // "") . "\n"; + print "\n"; + print "Is Windows : " . yes_no(is_windows()) . "\n"; + print "Operating System: $^O\n"; +} + +sub yes_no { + my $val = shift(); + + if ($val) { + return "Yes"; + } else { + return "No"; + } +} + +sub init_diff_highlight_colors { + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); + + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); +} + +# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 77f2e5c2c063e55a5ff14540771ffb5ce344e988 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 26 May 2021 09:28:16 -0700 Subject: [PATCH 372/443] Begin work on the next version of d-s-f --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 25f5505..509fca0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.4.1"; +my $VERSION = "1.4.2"; ################################################################################# From e62880433a7d1c2453c6fc18fbcaa771a787610d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 26 May 2021 09:32:12 -0700 Subject: [PATCH 373/443] Clarify wording on a test --- test/diff-so-fancy.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 25a10c7..5c276c4 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -9,7 +9,7 @@ set_env # bats fails to handle our multiline result, so we save to $output ourselves output=$( load_fixture "ls-function" | $diff_so_fancy ) -@test "diff-so-fancy runs exits without error" { +@test "diff-so-fancy runs and exits without error" { load_fixture "ls-function" | $diff_so_fancy run assert_success } From 27ef4604e1046d72f12edf34d4266da549e55a8e Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 11:36:37 -0700 Subject: [PATCH 374/443] Add git color support for reverse 'red bold reverse' should now work --- diff-so-fancy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 509fca0..131a394 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -947,6 +947,9 @@ sub git_ansi_color { # It's an RGB value if (is_numeric($bg)) { push(@ansi_part, "48;5;$bg"); + # Inverse/Reverse + } elsif ($bg =~ /reverse/i) { + push(@ansi_part, 7); # It's a simple 16 color OG ansi } elsif ($bg) { push(@ansi_part, $colors->{$fg} + 40); From 8b13b49c7c5fa8505c7c12261e66558b15098035 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 13:26:25 -0700 Subject: [PATCH 375/443] Use better trim logic... minor speed up --- diff-so-fancy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 131a394..6eb6868 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -628,7 +628,9 @@ sub bleach_text { sub trim { my $s = shift(); if (!$s) { return ""; } - $s =~ s/^\s*|\s*$//g; + + $s =~ s/^\s*//u; + $s =~ s/\s*$//u; return $s; } From 433b8561072184b1a6a117e8d577d728e5d14a55 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 13:54:44 -0700 Subject: [PATCH 376/443] Switch to a faster implementation of char_count() --- diff-so-fancy | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 6eb6868..dad44b4 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -602,18 +602,13 @@ sub insert_reset_at_line_end { } # Count the number of a given char in a string +# https://www.perturb.org/display/1010_Perl_Count_occurrences_of_substring.html sub char_count { - my ($needle,$str) = @_; - my $len = length($str); - my $ret = 0; - - for (my $i = 0; $i < $len; $i++) { - my $found = substr($str,$i,1); + my ($needle, $haystack) = @_; - if ($needle eq $found) { $ret++; } - } + my $count = () = ($haystack =~ /$needle/g); - return $ret; + return $count; } # Remove all ANSI codes from a string From ed6018dfb646493f424751c1b0a75bf1cd8f9635 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 16:09:12 -0700 Subject: [PATCH 377/443] Add support for "brightred" --- diff-so-fancy | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index dad44b4..cd63356 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -936,7 +936,12 @@ sub git_ansi_color { push(@ansi_part, "38;5;$fg"); # It's a simple 16 color OG ansi } elsif ($fg) { - push(@ansi_part, $colors->{$fg} + 30); + my $bright = $fg =~ s/bright//; + my $color_num = $colors->{$fg} + 30; + + if ($bright) { $color_num += 8; } + + push(@ansi_part, $color_num); } ############################################# @@ -949,7 +954,12 @@ sub git_ansi_color { push(@ansi_part, 7); # It's a simple 16 color OG ansi } elsif ($bg) { - push(@ansi_part, $colors->{$fg} + 40); + my $bright = $bg =~ s/bright//; + my $color_num = $colors->{$bg} + 30; + + if ($bright) { $color_num += 8; } + + push(@ansi_part, $color_num); } ############################################# From e5c5c5d105d1de4837aad4642fd72db027fc4fc5 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 20:53:01 -0700 Subject: [PATCH 378/443] Better git ansi color handling --- diff-so-fancy | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index cd63356..4765a61 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -899,6 +899,7 @@ sub color { } } +# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git sub git_ansi_color { my $str = shift(); my @parts = split(' ', $str); @@ -906,7 +907,6 @@ sub git_ansi_color { if (!@parts) { return ''; } - my $colors = { 'black' => 0, 'red' => 1, @@ -918,20 +918,24 @@ sub git_ansi_color { 'white' => 7, }; - my $fg = $parts[0] || ""; - my $mod = $parts[1] || ""; - my $bg = $parts[2] || ""; - my @ansi_part = (); - ############################################# - if ($mod eq 'bold') { + if (grep { /bold/ } @parts) { push(@ansi_part, "1"); + @parts = grep { !/bold/ } @parts; # Remove from array } + if (grep { /reverse/ } @parts) { + push(@ansi_part, "7"); + @parts = grep { !/reverse/ } @parts; # Remove from array + } + + my $fg = $parts[0] || ""; + my $bg = $parts[1] || ""; + ############################################# - # It's an RGB value + # It's an numeric value, so it's an 8 bit color if (is_numeric($fg)) { push(@ansi_part, "38;5;$fg"); # It's a simple 16 color OG ansi @@ -939,25 +943,22 @@ sub git_ansi_color { my $bright = $fg =~ s/bright//; my $color_num = $colors->{$fg} + 30; - if ($bright) { $color_num += 8; } + if ($bright) { $color_num += 60; } # Set bold push(@ansi_part, $color_num); } ############################################# - # It's an RGB value + # It's an numeric value, so it's an 8 bit color if (is_numeric($bg)) { push(@ansi_part, "48;5;$bg"); - # Inverse/Reverse - } elsif ($bg =~ /reverse/i) { - push(@ansi_part, 7); # It's a simple 16 color OG ansi } elsif ($bg) { my $bright = $bg =~ s/bright//; - my $color_num = $colors->{$bg} + 30; + my $color_num = $colors->{$bg} + 40; - if ($bright) { $color_num += 8; } + if ($bright) { $color_num += 60; } # Set bold push(@ansi_part, $color_num); } From 493622636b7e367b2b8bb2cc460e0dbf372dfe0a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 27 May 2021 20:56:01 -0700 Subject: [PATCH 379/443] Add an external implementation (with basic tests) of git ansi color parsing --- third_party/git_ansi_color/git_ansi_color.pl | 235 +++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 third_party/git_ansi_color/git_ansi_color.pl diff --git a/third_party/git_ansi_color/git_ansi_color.pl b/third_party/git_ansi_color/git_ansi_color.pl new file mode 100644 index 0000000..10481e9 --- /dev/null +++ b/third_party/git_ansi_color/git_ansi_color.pl @@ -0,0 +1,235 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use v5.16; + +############################################################################### +############################################################################### + +compare_color("\e[1;91m" , git_ansi_color("brightred bold") , "brightred bold"); +compare_color("\e[1;31m" , git_ansi_color("red bold") , "red bold"); +compare_color("\e[1;32m" , git_ansi_color("green bold") , "green bold"); +compare_color("\e[33m" , git_ansi_color("yellow") , "yellow"); +compare_color("\e[1;7;32m" , git_ansi_color("green bold reverse"), "green bold reverse"); +compare_color("\e[1;35m" , git_ansi_color("magenta bold") , "magenta bold"); +compare_color("\e[1;38;5;146m" , git_ansi_color("146 bold") , "146 bold"); +compare_color("\e[1;38;5;146;48;5;22m", git_ansi_color("146 bold 22") , "146 bold 22"); +compare_color("\e[1;34;40m" , git_ansi_color("blue black bold") , "blue black gold"); +compare_color("\e[38;5;11m" , git_ansi_color("11") , "11"); +compare_color("\e[7;31m" , git_ansi_color("red reverse") , "red reverse"); +compare_color("\e[1;31;48;5;52m" , git_ansi_color("red bold 52") , "red bold 52"); +compare_color("\e[38;5;10;48;5;20m" , git_ansi_color("10 20") , "10 20"); + +############################################################################### +############################################################################### + +sub compare_color { + my ($one, $two, $desc) = @_; + + if ($one ne $two) { + #k($one, $two); + #printf("No match for %-20s %s / %s\n", $desc, ansi_to_human($one), ansi_to_human($two)); + printf("%-20s %sFAIL%s %s ne %s\n", $desc, color('red'), color(), ansi_to_human($one), ansi_to_human($two)); + } else { + printf("%-20s %sOK%s\n", $desc, color('green'), color()); + } +} + +# Convert an ANSI sequence to something printable +sub ansi_to_human { + my $str = shift(); + + $str =~ s/\e\[/[/g; + $str =~ s/m\b/]/g; + + return $str; +} + +# Alternate (unused) method to parse git config colors +sub git_ansi_color2 { + my $str = shift(); + my @parts = split(' ', $str); + + if (!@parts) { + return ''; + } + + my %map = qw( + black 30 red 31 green 32 yellow 33 blue 34 magenta 35 cyan 36 white 37 + bold 1 reverse 7 + ); + + k(\@parts); + + my @ret; + my $first = 1; + foreach my $item (@parts) { + my $val = $map{$item}; + + if (!$val) { + if (!$first && $item > 10) { + $val = $item + 10; + } + push(@ret, (38,5,$val)); + } else { + # Background color + if (!$first && $val > 10) { + $val += 10; + $first = 0; + } + push(@ret, $val); + } + + if ($val > 10) { + $first = 0; + } + } + + my $ret = "\e[" . join(";", @ret) . "m"; + + print ansi_to_human($ret); + + return $ret; +} + +# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git +sub git_ansi_color { + my $str = shift(); + my @parts = split(' ', $str); + + if (!@parts) { + return ''; + } + my $colors = { + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + }; + + my @ansi_part = (); + + if (grep { /bold/ } @parts) { + push(@ansi_part, "1"); + @parts = grep { !/bold/ } @parts; # Remove from array + } + + if (grep { /reverse/ } @parts) { + push(@ansi_part, "7"); + @parts = grep { !/reverse/ } @parts; # Remove from array + } + + my $fg = $parts[0] || ""; + my $bg = $parts[1] || ""; + + ############################################# + + # It's an numeric value, so it's an 8 bit color + if (is_numeric($fg)) { + push(@ansi_part, "38;5;$fg"); + # It's a simple 16 color OG ansi + } elsif ($fg) { + my $bright = $fg =~ s/bright//; + my $color_num = $colors->{$fg} + 30; + + if ($bright) { $color_num += 60; } # Set bold + + push(@ansi_part, $color_num); + } + + ############################################# + + # It's an numeric value, so it's an 8 bit color + if (is_numeric($bg)) { + push(@ansi_part, "48;5;$bg"); + # It's a simple 16 color OG ansi + } elsif ($bg) { + my $bright = $bg =~ s/bright//; + my $color_num = $colors->{$bg} + 40; + + if ($bright) { $color_num += 60; } # Set bold + + push(@ansi_part, $color_num); + } + + ############################################# + + my $ansi_str = join(";", @ansi_part); + my $ret = "\e[" . $ansi_str . "m"; + + return $ret; +} + +sub is_numeric { + my $s = shift(); + + if ($s =~ /^\d+$/) { + return 1; + } + + return 0; +} + +sub trim { + my $s = shift(); + if (!defined($s) || length($s) == 0) { return ""; } + $s =~ s/^\s*//; + $s =~ s/\s*$//; + + return $s; +} + +# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' +sub color { + my $str = shift(); + + # If we're NOT connected to a an interactive terminal don't do color + #if (-t STDOUT == 0) { return ''; } + + # No string sent in, so we just reset + if (!length($str) || $str eq 'reset') { return "\e[0m"; } + + # Some predefined colors + my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); + $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; + + # Get foreground/background and any commands + my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; + my ($bc) = $str =~ /on_(\d{1,3})$/g; + + # Some predefined commands + my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); + my $cmd_num = $cmd_map{$cmd // 0}; + + my $ret = ''; + if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } + if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } + if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } + + return $ret; +} + +# Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper +# Creates methods k() and kd() to print, and print & die respectively +BEGIN { + if (eval { require Data::Dump::Color }) { + *k = sub { Data::Dump::Color::dd(@_) }; + } else { + require Data::Dumper; + *k = sub { print Data::Dumper::Dumper(\@_) }; + } + + sub kd { + k(@_); + + printf("Died at %2\$s line #%3\$s\n",caller()); + exit(15); + } +} + +# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4 From 5367f9f32ff57e6ce632b0d9ae329fe60c19e758 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 28 May 2021 08:52:27 -0700 Subject: [PATCH 380/443] Move git_ansi_color stuff to the tests --- {third_party/git_ansi_color => test}/git_ansi_color.pl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {third_party/git_ansi_color => test}/git_ansi_color.pl (100%) diff --git a/third_party/git_ansi_color/git_ansi_color.pl b/test/git_ansi_color.pl similarity index 100% rename from third_party/git_ansi_color/git_ansi_color.pl rename to test/git_ansi_color.pl From e93972f7dea3db52d12aa9828066894c5ae165ce Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 28 May 2021 13:33:02 -0700 Subject: [PATCH 381/443] Support a couple more git ansi color strings --- diff-so-fancy | 20 +++++++++++++---- test/git_ansi_color.pl | 49 +++++++++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 4765a61..1545cd9 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -930,14 +930,20 @@ sub git_ansi_color { @parts = grep { !/reverse/ } @parts; # Remove from array } - my $fg = $parts[0] || ""; - my $bg = $parts[1] || ""; + my $fg = $parts[0] // ""; + my $bg = $parts[1] // ""; ############################################# # It's an numeric value, so it's an 8 bit color if (is_numeric($fg)) { - push(@ansi_part, "38;5;$fg"); + if ($fg < 8) { + push(@ansi_part, $fg + 30); + } elsif ($fg < 16) { + push(@ansi_part, $fg + 82); + } else { + push(@ansi_part, "38;5;$fg"); + } # It's a simple 16 color OG ansi } elsif ($fg) { my $bright = $fg =~ s/bright//; @@ -952,7 +958,13 @@ sub git_ansi_color { # It's an numeric value, so it's an 8 bit color if (is_numeric($bg)) { - push(@ansi_part, "48;5;$bg"); + if ($bg < 8) { + push(@ansi_part, $bg + 40); + } elsif ($bg < 16) { + push(@ansi_part, $bg + 92); + } else { + push(@ansi_part, "48;5;$bg"); + } # It's a simple 16 color OG ansi } elsif ($bg) { my $bright = $bg =~ s/bright//; diff --git a/test/git_ansi_color.pl b/test/git_ansi_color.pl index 10481e9..58fb1a6 100644 --- a/test/git_ansi_color.pl +++ b/test/git_ansi_color.pl @@ -7,19 +7,22 @@ ############################################################################### ############################################################################### -compare_color("\e[1;91m" , git_ansi_color("brightred bold") , "brightred bold"); -compare_color("\e[1;31m" , git_ansi_color("red bold") , "red bold"); -compare_color("\e[1;32m" , git_ansi_color("green bold") , "green bold"); -compare_color("\e[33m" , git_ansi_color("yellow") , "yellow"); -compare_color("\e[1;7;32m" , git_ansi_color("green bold reverse"), "green bold reverse"); -compare_color("\e[1;35m" , git_ansi_color("magenta bold") , "magenta bold"); -compare_color("\e[1;38;5;146m" , git_ansi_color("146 bold") , "146 bold"); -compare_color("\e[1;38;5;146;48;5;22m", git_ansi_color("146 bold 22") , "146 bold 22"); -compare_color("\e[1;34;40m" , git_ansi_color("blue black bold") , "blue black gold"); -compare_color("\e[38;5;11m" , git_ansi_color("11") , "11"); -compare_color("\e[7;31m" , git_ansi_color("red reverse") , "red reverse"); -compare_color("\e[1;31;48;5;52m" , git_ansi_color("red bold 52") , "red bold 52"); -compare_color("\e[38;5;10;48;5;20m" , git_ansi_color("10 20") , "10 20"); +compare_color("\e[1;91m" , git_ansi_color("brightred bold") , "brightred bold"); +compare_color("\e[1;31m" , git_ansi_color("red bold") , "red bold"); +compare_color("\e[1;32m" , git_ansi_color("green bold") , "green bold"); +compare_color("\e[33m" , git_ansi_color("yellow") , "yellow"); +compare_color("\e[1;7;32m" , git_ansi_color("green bold reverse"), "green bold reverse"); +compare_color("\e[1;35m" , git_ansi_color("magenta bold") , "magenta bold"); +compare_color("\e[1;38;5;146m" , git_ansi_color("146 bold") , "146 bold"); +compare_color("\e[1;38;5;146;48;5;22m" , git_ansi_color("146 bold 22") , "146 bold 22"); +compare_color("\e[1;34;40m" , git_ansi_color("blue black bold") , "blue black gold"); +compare_color("\e[93m" , git_ansi_color("11") , "11"); +compare_color("\e[7;31m" , git_ansi_color("red reverse") , "red reverse"); +compare_color("\e[1;31;48;5;52m" , git_ansi_color("red bold 52") , "red bold 52"); +compare_color("\e[92;48;5;20m" , git_ansi_color("10 20") , "10 20"); +compare_color("\e[30;47m" , git_ansi_color("0 7") , "0 7"); +compare_color("\e[94;105m" , git_ansi_color("12 13") , "12 13"); +compare_color("\e[1;38;5;254;48;5;255m", git_ansi_color("254 bold 255") , "254 bold 255"); ############################################################################### ############################################################################### @@ -124,14 +127,20 @@ sub git_ansi_color { @parts = grep { !/reverse/ } @parts; # Remove from array } - my $fg = $parts[0] || ""; - my $bg = $parts[1] || ""; + my $fg = $parts[0] // ""; + my $bg = $parts[1] // ""; ############################################# # It's an numeric value, so it's an 8 bit color if (is_numeric($fg)) { - push(@ansi_part, "38;5;$fg"); + if ($fg < 8) { + push(@ansi_part, $fg + 30); + } elsif ($fg < 16) { + push(@ansi_part, $fg + 82); + } else { + push(@ansi_part, "38;5;$fg"); + } # It's a simple 16 color OG ansi } elsif ($fg) { my $bright = $fg =~ s/bright//; @@ -146,7 +155,13 @@ sub git_ansi_color { # It's an numeric value, so it's an 8 bit color if (is_numeric($bg)) { - push(@ansi_part, "48;5;$bg"); + if ($bg < 8) { + push(@ansi_part, $bg + 40); + } elsif ($bg < 16) { + push(@ansi_part, $bg + 92); + } else { + push(@ansi_part, "48;5;$bg"); + } # It's a simple 16 color OG ansi } elsif ($bg) { my $bright = $bg =~ s/bright//; From 35095b66cb05ff36f4998c19ced45c4d13860969 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sun, 30 May 2021 16:24:36 -0700 Subject: [PATCH 382/443] Adding tests for #411 --- test/git_ansi_color.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/git_ansi_color.pl b/test/git_ansi_color.pl index 58fb1a6..3187069 100644 --- a/test/git_ansi_color.pl +++ b/test/git_ansi_color.pl @@ -23,6 +23,8 @@ compare_color("\e[30;47m" , git_ansi_color("0 7") , "0 7"); compare_color("\e[94;105m" , git_ansi_color("12 13") , "12 13"); compare_color("\e[1;38;5;254;48;5;255m", git_ansi_color("254 bold 255") , "254 bold 255"); +compare_color("\e[1;38;5;238;42m" , git_ansi_color("238 bold green") , "238 bold green"); +compare_color("\e[1;38;5;238;42m" , git_ansi_color("238 green bold") , "238 green bold"); ############################################################################### ############################################################################### From 5ad99c0271c4074faeb8527750bb3ba2bd3b87d7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 31 May 2021 12:58:03 -0700 Subject: [PATCH 383/443] Use the default diff-highlight colors. Addresses #374 --- lib/DiffHighlight.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index 1368b3f..d00c5de 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -12,13 +12,13 @@ my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - "\e[1;31m", - "\e[1;31;48;5;52m", - "\x1b[27m", + "", + "\e[7m", + "\e[27m", ); our @NEW_HIGHLIGHT = ( - "\e[1;32m", - "\e[1;32;48;5;22m", + $OLD_HIGHLIGHT[0], + $OLD_HIGHLIGHT[1], $OLD_HIGHLIGHT[2], ); From 2d634db110efe1f1ec1e527b3949551ce5c0ecfd Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 31 May 2021 13:03:12 -0700 Subject: [PATCH 384/443] We cache git color stuff, so we don't need the ||= anymore --- diff-so-fancy | 6 +++--- test/diff-so-fancy.bats | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 1545cd9..ee1573e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -196,7 +196,7 @@ sub do_dsf_stuff { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { $is_mercurial = 1; - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy } else { $last_file_seen = $5; @@ -212,7 +212,7 @@ sub do_dsf_stuff { # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); if ($git_strip_prefix) { my $file_dir = $4 || ""; @@ -364,7 +364,7 @@ sub do_dsf_stuff { if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); my $change = file_change_string($file1,$file2); diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 5c276c4..62d3a3d 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -53,9 +53,9 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) @test "header format uses a native line-drawing character" { header=$( printf "%s" "$output" | head -n3 ) run printf "%s" "$header" - assert_line --index 0 --partial "─────" + assert_line --index 0 --partial "─────" assert_line --index 1 --partial "modified: fish/functions/ls.fish" - assert_line --index 2 --partial "─────" + assert_line --index 2 --partial "─────" } # see https://git.io/vrOF4 @@ -64,9 +64,9 @@ output=$( load_fixture "ls-function" | $diff_so_fancy ) # pipe to cat(1) so we don't open stdout header=$( printf "%s" "$(load_fixture "ls-function" | $diff_so_fancy | cat)" | head -n3 ) run printf "%s" "$header" - assert_line --index 0 --partial "-----" + assert_line --index 0 --partial "-----" assert_line --index 1 --partial "modified: fish/functions/ls.fish" - assert_line --index 2 --partial "-----" + assert_line --index 2 --partial "-----" set_env # reset env } From 5c4da1cceaa3d84983b948f9fb3bc2cddf6e9ab8 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 31 May 2021 16:52:45 -0700 Subject: [PATCH 385/443] Correct the diff-highlight defaults --- lib/DiffHighlight.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DiffHighlight.pm b/lib/DiffHighlight.pm index d00c5de..15d2443 100644 --- a/lib/DiffHighlight.pm +++ b/lib/DiffHighlight.pm @@ -12,7 +12,7 @@ my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - "", + undef, "\e[7m", "\e[27m", ); From 15c702b04fddc74990ba743c6f5157cafb54f8cb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 31 May 2021 17:00:25 -0700 Subject: [PATCH 386/443] Use the defaults in diff-highlight if nothing is set in the git config --- diff-so-fancy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index ee1573e..326a8e3 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1066,12 +1066,13 @@ sub yes_no { } } +# If there are colors set in the gitconfig use those, otherwise leave the defaults sub init_diff_highlight_colors { - $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); - $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || $DiffHighlight::NEW_HIGHLIGHT[0]; + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || $DiffHighlight::NEW_HIGHLIGHT[1]; - $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); - $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || $DiffHighlight::OLD_HIGHLIGHT[0]; + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || $DiffHighlight::OLD_HIGHLIGHT[1]; } # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From ea42d56c372f5f43a65dd1dffb18a3164e129cf2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 1 Jun 2021 08:47:33 -0700 Subject: [PATCH 387/443] Set defaults doesn't "work" unless we exit() after. Addresses #413 --- diff-so-fancy | 1 + 1 file changed, 1 insertion(+) diff --git a/diff-so-fancy b/diff-so-fancy index 326a8e3..5ac74a6 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -73,6 +73,7 @@ if (!$has_stdin) { die(version()); } elsif ($args->{'set-defaults'}) { my $ok = set_defaults(); + exit; } elsif ($args->{colors}) { # We print this to STDOUT so we can redirect to bash to auto-set the colors print get_default_colors(); From 41305305dba6393c70f3cdcdf06b4b4b917090d7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 1 Jun 2021 08:59:03 -0700 Subject: [PATCH 388/443] The unit tests use a pre-defined config set by the tests --- diff-so-fancy | 5 ----- 1 file changed, 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 5ac74a6..7ecf46b 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -458,11 +458,6 @@ sub git_config { my $search_key = lc($_[0] || ""); my $default_value = lc($_[1] || ""); - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return $default_value; - } - state $raw = {}; if (%$raw && $search_key) { return $raw->{$search_key} || $default_value; From 3bbab41282713f17ef26a0ba8379ad78de33ad14 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 1 Jun 2021 14:05:01 -0700 Subject: [PATCH 389/443] Add debug_log() for helpful debugging later --- diff-so-fancy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 7ecf46b..bc9cb28 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1071,4 +1071,19 @@ sub init_diff_highlight_colors { $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || $DiffHighlight::OLD_HIGHLIGHT[1]; } +sub debug_log { + my $log_line = shift(); + my $file = "/tmp/diff-so-fancy.debug.log"; + + state $fh; + if (!$fh) { + printf("%sDebug log enabled:%s $file\n", color('orange'), color()); + open ($fh, ">", $file) or die("Cannot write to $file"); + } + + print $fh trim($log_line) . "\n"; + + return 1; +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 31cdf3f35f7ec9e9d9662aff1d0cee2c99744c65 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 1 Jun 2021 15:16:35 -0700 Subject: [PATCH 390/443] Add reverse and notreverse to ANSI reveal --- third_party/ansi-reveal/ansi-reveal.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/third_party/ansi-reveal/ansi-reveal.pl b/third_party/ansi-reveal/ansi-reveal.pl index 5410fb3..ba47496 100755 --- a/third_party/ansi-reveal/ansi-reveal.pl +++ b/third_party/ansi-reveal/ansi-reveal.pl @@ -98,6 +98,10 @@ sub dump_ansi { $ret .= "[BOLD]"; } elsif ($p eq "0" || $p eq "") { $ret .= "[RESET]"; + } elsif ($p eq "7") { + $ret .= "[REVERSE]"; + } elsif ($p eq "27") { + $ret .= "[NOTREV]"; } elsif ($p eq "38") { my $next = $parts[$count + 1]; my $color = $parts[$count + 2]; From 8b5e270aacd68be724d049db8d56a54037edfee7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Jun 2021 12:44:11 -0700 Subject: [PATCH 391/443] Add a conditional debug print clause --- diff-so-fancy | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index bc9cb28..049aa37 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1086,4 +1086,21 @@ sub debug_log { return 1; } +# Enable k() and kd() if there is a DSF_DEBUG environment variable +BEGIN { + if ($ENV{"DSF_DEBUG"}) { + require Data::Dump::Color; + *k = sub { Data::Dump::Color::dd(@_) }; + *kd = sub { + k(@_); + + printf("Died at %2\$s line #%3\$s\n",caller()); + exit(15); + } + } else { + *k = sub {}; + *kd = sub {}; + } +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 02c8c0085385f7d65ba35556edfc58e0f48257eb Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 14 Jun 2021 12:44:48 -0700 Subject: [PATCH 392/443] Bump the fatpacked version --- third_party/build_fatpack/diff-so-fancy | 134 ++++++++++++++++-------- 1 file changed, 93 insertions(+), 41 deletions(-) diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy index 0eccb98..2e165ad 100755 --- a/third_party/build_fatpack/diff-so-fancy +++ b/third_party/build_fatpack/diff-so-fancy @@ -20,13 +20,13 @@ $fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'D # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. our @OLD_HIGHLIGHT = ( - "\e[1;31m", - "\e[1;31;48;5;52m", - "\x1b[27m", + undef, + "\e[7m", + "\e[27m", ); our @NEW_HIGHLIGHT = ( - "\e[1;32m", - "\e[1;32;48;5;22m", + $OLD_HIGHLIGHT[0], + $OLD_HIGHLIGHT[1], $OLD_HIGHLIGHT[2], ); @@ -336,7 +336,7 @@ unshift @INC, bless \%fatpacked, $class; } # END OF FATPACK CODE -my $VERSION = "1.4.1"; +my $VERSION = "1.4.2"; ################################################################################# @@ -409,6 +409,7 @@ if (!$has_stdin) { die(version()); } elsif ($args->{'set-defaults'}) { my $ok = set_defaults(); + exit; } elsif ($args->{colors}) { # We print this to STDOUT so we can redirect to bash to auto-set the colors print get_default_colors(); @@ -532,7 +533,7 @@ sub do_dsf_stuff { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { $is_mercurial = 1; - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy } else { $last_file_seen = $5; @@ -548,7 +549,7 @@ sub do_dsf_stuff { # Find the first file: --- a/README.md # ######################################## } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); if ($git_strip_prefix) { my $file_dir = $4 || ""; @@ -700,7 +701,7 @@ sub do_dsf_stuff { if ($file1 && $file2) { # We may not have extracted this yet, so we pull from the config if not - $meta_color ||= get_config_color("meta"); + $meta_color = get_config_color("meta"); my $change = file_change_string($file1,$file2); @@ -793,11 +794,6 @@ sub git_config { my $search_key = lc($_[0] || ""); my $default_value = lc($_[1] || ""); - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return $default_value; - } - state $raw = {}; if (%$raw && $search_key) { return $raw->{$search_key} || $default_value; @@ -938,18 +934,13 @@ sub insert_reset_at_line_end { } # Count the number of a given char in a string +# https://www.perturb.org/display/1010_Perl_Count_occurrences_of_substring.html sub char_count { - my ($needle,$str) = @_; - my $len = length($str); - my $ret = 0; - - for (my $i = 0; $i < $len; $i++) { - my $found = substr($str,$i,1); + my ($needle, $haystack) = @_; - if ($needle eq $found) { $ret++; } - } + my $count = () = ($haystack =~ /$needle/g); - return $ret; + return $count; } # Remove all ANSI codes from a string @@ -964,7 +955,9 @@ sub bleach_text { sub trim { my $s = shift(); if (!$s) { return ""; } - $s =~ s/^\s*|\s*$//g; + + $s =~ s/^\s*//u; + $s =~ s/\s*$//u; return $s; } @@ -1238,6 +1231,7 @@ sub color { } } +# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git sub git_ansi_color { my $str = shift(); my @parts = split(' ', $str); @@ -1245,7 +1239,6 @@ sub git_ansi_color { if (!@parts) { return ''; } - my $colors = { 'black' => 0, 'red' => 1, @@ -1257,35 +1250,61 @@ sub git_ansi_color { 'white' => 7, }; - my $fg = $parts[0] || ""; - my $mod = $parts[1] || ""; - my $bg = $parts[2] || ""; - my @ansi_part = (); - ############################################# - if ($mod eq 'bold') { + if (grep { /bold/ } @parts) { push(@ansi_part, "1"); + @parts = grep { !/bold/ } @parts; # Remove from array + } + + if (grep { /reverse/ } @parts) { + push(@ansi_part, "7"); + @parts = grep { !/reverse/ } @parts; # Remove from array } + my $fg = $parts[0] // ""; + my $bg = $parts[1] // ""; + ############################################# - # It's an RGB value + # It's an numeric value, so it's an 8 bit color if (is_numeric($fg)) { - push(@ansi_part, "38;5;$fg"); + if ($fg < 8) { + push(@ansi_part, $fg + 30); + } elsif ($fg < 16) { + push(@ansi_part, $fg + 82); + } else { + push(@ansi_part, "38;5;$fg"); + } # It's a simple 16 color OG ansi } elsif ($fg) { - push(@ansi_part, $colors->{$fg} + 30); + my $bright = $fg =~ s/bright//; + my $color_num = $colors->{$fg} + 30; + + if ($bright) { $color_num += 60; } # Set bold + + push(@ansi_part, $color_num); } ############################################# - # It's an RGB value + # It's an numeric value, so it's an 8 bit color if (is_numeric($bg)) { - push(@ansi_part, "48;5;$bg"); + if ($bg < 8) { + push(@ansi_part, $bg + 40); + } elsif ($bg < 16) { + push(@ansi_part, $bg + 92); + } else { + push(@ansi_part, "48;5;$bg"); + } # It's a simple 16 color OG ansi } elsif ($bg) { - push(@ansi_part, $colors->{$fg} + 40); + my $bright = $bg =~ s/bright//; + my $color_num = $colors->{$bg} + 40; + + if ($bright) { $color_num += 60; } # Set bold + + push(@ansi_part, $color_num); } ############################################# @@ -1379,12 +1398,45 @@ sub yes_no { } } +# If there are colors set in the gitconfig use those, otherwise leave the defaults sub init_diff_highlight_colors { - $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || color("2_bold"); - $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || color("2_bold") . color("on_22"); + $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || $DiffHighlight::NEW_HIGHLIGHT[0]; + $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || $DiffHighlight::NEW_HIGHLIGHT[1]; + + $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || $DiffHighlight::OLD_HIGHLIGHT[0]; + $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || $DiffHighlight::OLD_HIGHLIGHT[1]; +} + +sub debug_log { + my $log_line = shift(); + my $file = "/tmp/diff-so-fancy.debug.log"; + + state $fh; + if (!$fh) { + printf("%sDebug log enabled:%s $file\n", color('orange'), color()); + open ($fh, ">", $file) or die("Cannot write to $file"); + } + + print $fh trim($log_line) . "\n"; + + return 1; +} - $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || color("1_bold"); - $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || color("1_bold") . color("on_52"); +# Enable k() and kd() if there is a DSF_DEBUG environment variable +BEGIN { + if ($ENV{"DSF_DEBUG"}) { + require Data::Dump::Color; + *k = sub { Data::Dump::Color::dd(@_) }; + *kd = sub { + k(@_); + + printf("Died at %2\$s line #%3\$s\n",caller()); + exit(15); + } + } else { + *k = sub {}; + *kd = sub {}; + } } # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From 754f9dfbd2085eb8241d71808dda9592b463c5a8 Mon Sep 17 00:00:00 2001 From: Guillermo Ramos Date: Sat, 19 Jun 2021 10:45:37 +0200 Subject: [PATCH 393/443] Fix fg/bg extraction with l.t. 2 colors --- diff-so-fancy | 5 +++-- test/git_ansi_color.pl | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 049aa37..603fbb2 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -918,14 +918,15 @@ sub git_ansi_color { if (grep { /bold/ } @parts) { push(@ansi_part, "1"); - @parts = grep { !/bold/ } @parts; # Remove from array } if (grep { /reverse/ } @parts) { push(@ansi_part, "7"); - @parts = grep { !/reverse/ } @parts; # Remove from array } + # Remove parts that aren't colors + @parts = grep { exists $colors->{$_} || is_numeric($_) } @parts; + my $fg = $parts[0] // ""; my $bg = $parts[1] // ""; diff --git a/test/git_ansi_color.pl b/test/git_ansi_color.pl index 3187069..e781de8 100644 --- a/test/git_ansi_color.pl +++ b/test/git_ansi_color.pl @@ -121,14 +121,15 @@ sub git_ansi_color { if (grep { /bold/ } @parts) { push(@ansi_part, "1"); - @parts = grep { !/bold/ } @parts; # Remove from array } if (grep { /reverse/ } @parts) { push(@ansi_part, "7"); - @parts = grep { !/reverse/ } @parts; # Remove from array } + # Remove parts that aren't colors + @parts = grep { exists $colors->{$_} || is_numeric($_) } @parts; + my $fg = $parts[0] // ""; my $bg = $parts[1] // ""; From 968214280941a3d8f8004199ef8b9ed0dade5a93 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 27 Jul 2021 13:19:35 -0700 Subject: [PATCH 394/443] update lockfile --- package-lock.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a31466b..7e3097d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,15 @@ { "name": "diff-so-fancy", "version": "1.3.0", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "1.3.0", + "license": "MIT", + "bin": { + "diff-so-fancy": "diff-so-fancy" + } + } + } } From 93f60a0765c6c24a030b7a5ccce46743e7483cd6 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 27 Jul 2021 13:20:15 -0700 Subject: [PATCH 395/443] 1.4.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e3097d..1ccc948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "diff-so-fancy", - "version": "1.3.0", + "version": "1.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.3.0", + "version": "1.4.2", "license": "MIT", "bin": { "diff-so-fancy": "diff-so-fancy" diff --git a/package.json b/package.json index d2e3c32..4988497 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.3.0", + "version": "1.4.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "diff-so-fancy" From 2719b7a39bdac7c511654de1245f1a103ffbf2bd Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 27 Jul 2021 13:23:39 -0700 Subject: [PATCH 396/443] use fatpack build for npm --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ccc948..a99ec35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "version": "1.4.2", "license": "MIT", "bin": { - "diff-so-fancy": "diff-so-fancy" + "diff-so-fancy": "third_party/build_fatpack/diff-so-fancy" } } } diff --git a/package.json b/package.json index 4988497..3682d70 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.4.2", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { - "diff-so-fancy": "diff-so-fancy" + "diff-so-fancy": "third_party/build_fatpack/diff-so-fancy" }, "scripts": { "test": "./test/bats/bin/bats test" From a673cb4d2707f64d92b86498a2f5f71c8e2643d5 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Tue, 27 Jul 2021 13:23:49 -0700 Subject: [PATCH 397/443] 1.4.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a99ec35..fe11c11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "diff-so-fancy", - "version": "1.4.2", + "version": "1.4.3", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.4.2", + "version": "1.4.3", "license": "MIT", "bin": { "diff-so-fancy": "third_party/build_fatpack/diff-so-fancy" diff --git a/package.json b/package.json index 3682d70..dd1d565 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-so-fancy", - "version": "1.4.2", + "version": "1.4.3", "description": "Good-lookin' diffs with diff-highlight and more", "bin": { "diff-so-fancy": "third_party/build_fatpack/diff-so-fancy" From 5d567c175dfd40336609b11b27a2049d191d4912 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 18 Oct 2021 14:17:23 -0700 Subject: [PATCH 398/443] regex: factor out single-sequence ansi_regex --- diff-so-fancy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 049aa37..0ef52d6 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -27,7 +27,8 @@ my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; +my $ansi_regex = qr/\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK]/; +my $ansi_color_regex = qr/(${ansi_regex})?/; my $reset_color = color("reset"); my $bold = color("bold"); my $meta_color = ""; From c60e02c4d8dd0c575d8555fba6b535f18649fc40 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Mon, 18 Oct 2021 15:54:14 -0700 Subject: [PATCH 399/443] fix: incorrect starts_with_ansi check for forced coloring --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0ef52d6..3936deb 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -121,7 +121,7 @@ my $line_count = 0; while (my $line = ) { # If the very first line of the diff doesn't start with ANSI color we're assuming # it's a raw patch file, and we have to color the added/removed lines ourself - if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { + if (!$color_forced && $line_count == 0 && !starts_with_ansi($line)) { $manually_color_lines = 1; } From 384ec32c53f452a33fd191bbe5e357d39b31ad5e Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Tue, 19 Oct 2021 17:18:15 -0700 Subject: [PATCH 400/443] fix: starts_with_ansi always returned 1 --- diff-so-fancy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 3936deb..8b16d85 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -993,7 +993,8 @@ sub is_numeric { sub starts_with_ansi { my $str = shift(); - if ($str =~ /^$ansi_color_regex/) { + # NOTE: This is not `ansi_color_regex`, which includes "no ANSI sequences". + if ($str =~ /^$ansi_regex/) { return 1; } else { return 0; From 700af6e075d2a05f59b16b12fdf6464efd79d880 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Wed, 20 Oct 2021 13:19:31 -0700 Subject: [PATCH 401/443] test: load imports/setup in setup/setup_file bat callbacks --- test/bugs.bats | 17 ++++++++++++++--- test/diff-so-fancy.bats | 23 +++++++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index 0e49052..69c019c 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -1,8 +1,19 @@ #!/usr/bin/env bats -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' -load 'test_helper/util' +# Used by both `setup_file` and `setup`, which are special bats callbacks. +__load_imports__() { + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/util' +} + +setup_file() { + __load_imports__ +} + +setup() { + __load_imports__ +} # https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13459061 @test "All removed lines are present in diff" { diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 62d3a3d..e3f5110 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -1,13 +1,24 @@ #!/usr/bin/env bats -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' -load 'test_helper/util' +# Helper invoked by `setup_file` and `setup`, which are special bats callbacks. +__load_imports__() { + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/util' +} + +setup_file() { + __load_imports__ +} -set_env +setup() { + __load_imports__ -# bats fails to handle our multiline result, so we save to $output ourselves -output=$( load_fixture "ls-function" | $diff_so_fancy ) + set_env + + # bats fails to handle our multiline result, so we save to $output ourselves + output=$( load_fixture "ls-function" | $diff_so_fancy ) +} @test "diff-so-fancy runs and exits without error" { load_fixture "ls-function" | $diff_so_fancy From 7fb648e8fa81528455ddc7c11212e605dae3926b Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Wed, 20 Oct 2021 13:21:41 -0700 Subject: [PATCH 402/443] test: cache initial setup vars --- test/diff-so-fancy.bats | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index e3f5110..370c5e3 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -9,15 +9,15 @@ __load_imports__() { setup_file() { __load_imports__ + set_env + # bats fails to handle our multiline result, so we save to $output ourselves + __dfs_cached_output="$( load_fixture "ls-function" | $diff_so_fancy )" + export __dfs_cached_output } setup() { __load_imports__ - - set_env - - # bats fails to handle our multiline result, so we save to $output ourselves - output=$( load_fixture "ls-function" | $diff_so_fancy ) + output="${__dfs_cached_output}" } @test "diff-so-fancy runs and exits without error" { From f156545804e49f4b604e71e277356ff3c6a8bcd7 Mon Sep 17 00:00:00 2001 From: Robert Estelle Date: Tue, 19 Oct 2021 20:29:26 -0700 Subject: [PATCH 403/443] test: use completely isolated, temporary GIT_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This writes a temp git config file during setup/teardown, and exports GIT_CONFIG=… to that and sets GIT_CONFIG_NOSYSTEM=1. This ensures that any git invocations _only_ use that configuration. Note that none of the tests currently test anything other than the default config; however, if they do, the generation of this test config should be moved into `setup()` rather than `setup_file()` so that it can be modified and restored between tests. --- diff-so-fancy | 14 -------------- test/bugs.bats | 5 +++++ test/diff-so-fancy.bats | 11 ++++++++--- test/test_helper/util.bash | 39 ++++++++++++++++++++++++++++---------- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 049aa37..5fd758f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -490,26 +490,12 @@ sub git_config_boolean { my $search_key = lc($_[0] || ""); my $default_value = lc($_[1] || 0); # Default to false - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return boolean($default_value); - } - my $result = git_config($search_key,$default_value); my $ret = boolean($result); return $ret; } -# Check if we're inside of BATS -sub in_unit_test { - if ($ENV{BATS_CWD}) { - return 1; - } else { - return 0; - } -} - sub get_less_charset { my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); foreach my $key (@less_char_vars) { diff --git a/test/bugs.bats b/test/bugs.bats index 69c019c..d0934ec 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -9,12 +9,17 @@ __load_imports__() { setup_file() { __load_imports__ + setup_default_dsf_git_config } setup() { __load_imports__ } +teardown_file() { + teardown_default_dsf_git_config +} + # https://github.com/paulirish/dotfiles/commit/6743b907ff586c28cd36e08d1e1c634e2968893e#commitcomment-13459061 @test "All removed lines are present in diff" { output=$( load_fixture "chromium-modaltoelement" | $diff_so_fancy ) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 370c5e3..31d9a56 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -10,14 +10,19 @@ __load_imports__() { setup_file() { __load_imports__ set_env + setup_default_dsf_git_config # bats fails to handle our multiline result, so we save to $output ourselves - __dfs_cached_output="$( load_fixture "ls-function" | $diff_so_fancy )" - export __dfs_cached_output + __dsf_cached_output="$( load_fixture "ls-function" | $diff_so_fancy )" + export __dsf_cached_output } setup() { __load_imports__ - output="${__dfs_cached_output}" + output="${__dsf_cached_output}" +} + +teardown_file() { + teardown_default_dsf_git_config } @test "diff-so-fancy runs and exits without error" { diff --git a/test/test_helper/util.bash b/test/test_helper/util.bash index 440d3bd..dbad4b1 100644 --- a/test/test_helper/util.bash +++ b/test/test_helper/util.bash @@ -10,17 +10,36 @@ set_env() { export LC_CTYPE="en_US.UTF-8" } +dsf_test_git_config() { + printf '%s/gitconfig' "${BATS_TMPDIR}" +} # applying colors so ANSI color values will match # FIXME: not everyone will have these set, so we need to test for that. -git config color.diff.meta "227" -git config color.diff.frag "magenta bold" -git config color.diff.commit "227 bold" -git config color.diff.old "red bold" -git config color.diff.new "green bold" -git config color.diff.whitespace "red reverse" +setup_default_dsf_git_config() { + GIT_CONFIG="$(dsf_test_git_config)" || return $? + cat > "${GIT_CONFIG}" < Date: Mon, 18 Oct 2021 15:41:04 -0700 Subject: [PATCH 404/443] fix: over-permissive regex for added/deleted file meta header The previous pattern uselessly included `.*`, unlike all the other patterns, and so would erroneously match on actual diff content lines. Noticed this because the line itself was filtered out of diffs in this codebase. This also tightens the pattern since the file mode will always be six octal (0-7) chars; and in the git codebase the format string is: "%s%snew file mode %06o%s\n" --- diff-so-fancy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 049aa37..903be52 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -303,7 +303,7 @@ sub do_dsf_stuff { ################################### # Remove any new file permissions # ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { + } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}new file mode [0-7]{6}/) { # Don't print the line (i.e. remove it from the output); $last_file_mode = "add"; if ($patch_mode) { @@ -312,7 +312,7 @@ sub do_dsf_stuff { ###################################### # Remove any delete file permissions # ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { + } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode [0-7]{6}/) { # Don't print the line (i.e. remove it from the output); $last_file_mode = "delete"; if ($patch_mode) { From 1a0ad54d77635c6c8a2b994c3df8cde2db82748c Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 22 Oct 2021 21:35:37 -0700 Subject: [PATCH 405/443] Bump the version so it's clear we're working on new stuff --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 636a977..5fc8241 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1,6 +1,6 @@ #!/usr/bin/env perl -my $VERSION = "1.4.2"; +my $VERSION = "1.4.4"; ################################################################################# From c0f7c221d9376633c3267d4707467a57c4ec8dd2 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Wed, 3 Nov 2021 10:48:36 -0700 Subject: [PATCH 406/443] Bump the bats stuff to v1.5.0 --- test/bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bats b/test/bats index 49b377a..a8b8d0d 160000 --- a/test/bats +++ b/test/bats @@ -1 +1 @@ -Subproject commit 49b377a751e6f9379abfdfb3dfa3aafabd8495a1 +Subproject commit a8b8d0dc7a05abe10c8511fe5041c137e06d18bf From 3ba29c9f70877576229db9f3ec6d7be2c418dc2f Mon Sep 17 00:00:00 2001 From: vladislav doster Date: Fri, 5 Nov 2021 19:47:25 -0500 Subject: [PATCH 407/443] (maint) update `pro-tips.md` Last week, @psprint deleted all `zinit` related source code. More info can be [found here](https://github.com/zdharma-continuum/I_WANT_TO_HELP) - update `zdharma` URLs to `zdharma-continuum` URLS. - apply consistent markdown formatting - add language hints to code fences --- pro-tips.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pro-tips.md b/pro-tips.md index 80404f4..b1ee2f7 100644 --- a/pro-tips.md +++ b/pro-tips.md @@ -1,18 +1,21 @@ -## Pro-tips +# Pro-tips -#### One-off fanciness or a specific diff-so-fancy alias +## One-off fanciness or a specific diff-so-fancy alias You can do also do a one-off: + ```shell git diff --color | diff-so-fancy ``` + or configure an alias and a corresponding pager to use `diff-so-fancy`: + ```shell git config --global alias.dsf "diff --color" git config --global pager.dsf "diff-so-fancy | less --tabs=4 -RFXS" ``` -#### Opting-out +## Opting-out Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: @@ -20,49 +23,52 @@ Sometimes you will want to bypass diff-so-fancy. Use `--no-pager` for that: git --no-pager diff ``` -#### Raw patches +## Raw patches As a shortcut for a 'normal' diff to save as a patch for emailing or later application, it may be helpful to configure an alias: + ```ini [alias] patch = !git --no-pager diff --no-color ``` + which can then be used as `git patch > changes.patch`. #### Moving around in the diff -You can pre-seed your `less` pager with a search pattern, so you can move +You can pre-seed your `less` pager with a search pattern so that you can move between files with `n`/`N` keys: + ```ini [pager] diff = diff-so-fancy | less --tabs=4 -RFXS --pattern '^(Date|added|deleted|modified): ' ``` -#### Zsh plugin providing diff-so-fancy +## Zsh plugin providing diff-so-fancy -Zsh plugin [zdharma/zsh-diff-so-fancy](https://github.com/zdharma/zsh-diff-so-fancy) has this -project as a submodule so installing the plugin installs `diff-so-fancy`. The plugin provides -subcommand `git dsf` out of the box. Installation with Zinit, Zplug and Zgen: +Zsh plugin [zdharma-continuum/zsh-diff-so-fancy](https://github.com/zdharma-continuum/zsh-diff-so-fancy) has this +project as a submodule, so installing the plugin installs `diff-so-fancy`. The plugin provides +the subcommand `git dsf` out of the box. Installation with Zinit, Zplug, and Zgen: ```zsh # zinit zinit ice lucid as"program" pick"bin/git-dsf" -zinit load zdharma/zsh-diff-so-fancy +zinit load zdharma-continuum/zsh-diff-so-fancy -# Or zplug -zplug "zdharma/zsh-diff-so-fancy", as:command, use:bin/git-dsf +# zplug +zplug "zdharma-continuum/zsh-diff-so-fancy", as:command, use:bin/git-dsf -# Or zgen and others -zgen zdharma/zsh-diff-so-fancy +# zgen and others +zgen zdharma-continuum/zsh-diff-so-fancy ``` -#### `hg` configuration +## `hg` configuration You can configure `hg diff` output to use `diff-so-fancy` by adding this alias to your `hgrc` file: -``` +```ini [alias] -diff = !HGPLAIN=1 $HG diff --pager=on --config pager.pager=diff-so-fancy $@ + diff = !HGPLAIN=1 $HG diff --pager=on --config pager.pager=diff-so-fancy $@ ``` From 2cbbef68d181f7dca4cdb1716625a87ab1676295 Mon Sep 17 00:00:00 2001 From: KopfKrieg Date: Sun, 23 Jan 2022 00:49:41 +0100 Subject: [PATCH 408/443] Move comparison image into the repository, fix white borders --- README.md | 2 +- diff-so-fancy.png | Bin 0 -> 151309 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 diff-so-fancy.png diff --git a/README.md b/README.md index ebf152c..f5d4f9f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` -![diff-highlight vs diff-so-fancy](https://user-images.githubusercontent.com/3429760/32387617-44c873da-c082-11e7-829c-6160b853adcb.png) +![diff-highlight vs diff-so-fancy](diff-so-fancy.png) ## Install diff --git a/diff-so-fancy.png b/diff-so-fancy.png new file mode 100644 index 0000000000000000000000000000000000000000..869e92cd55fe5b2c5097ba223973eced7f6be1cb GIT binary patch literal 151309 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≪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 =Os?#dR5p($?O6r^$yc^b6zYK?Wb& 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 literal 0 HcmV?d00001 From e65b8b93a91237a94a791b63974a52639913624e Mon Sep 17 00:00:00 2001 From: KopfKrieg Date: Sun, 23 Jan 2022 01:04:06 +0100 Subject: [PATCH 409/443] Added Fedora COPR repository to the installation section of the README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebf152c..357d89f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/diff-so-fancy/default.nix), in the [Arch community repo](https://archlinux.org/packages/community/any/diff-so-fancy/), and [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +`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/). 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. From 3c22692f16856de81b183f10d103c2ad4130ae8c Mon Sep 17 00:00:00 2001 From: "Florian \"KopfKrieg" Date: Sun, 23 Jan 2022 18:04:53 +0100 Subject: [PATCH 410/443] =?UTF-8?q?README.md:=20"as"=20=E2=86=92=20"in=20t?= =?UTF-8?q?he"=20(improved=20readability)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Ford --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 357d89f..0c02f9f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/). +`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 in the [Fedora COPR repository](https://copr.fedorainfracloud.org/coprs/kopfkrieg/diff-so-fancy/). 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. From 66052af800b162e410e33c4f11a38233278acd27 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2022 15:55:21 -0800 Subject: [PATCH 411/443] Output the average using floating point --- third_party/cli_bench/cli_bench.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl index 9f05ee2..1080ef4 100644 --- a/third_party/cli_bench/cli_bench.pl +++ b/third_party/cli_bench/cli_bench.pl @@ -59,7 +59,7 @@ @res = sort(@res); @res = splice(@res, $outlier, $num - $outlier * 2); -my $avg = int(average(@res)); +my $avg = sprintf("%.1f", average(@res)); if ($details) { show_details(@res); From 4de6d16caeb0a60a16987778eae57b25ef8adef4 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2022 15:55:57 -0800 Subject: [PATCH 412/443] Update subproject bats --- test/bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bats b/test/bats index 49b377a..eba9785 160000 --- a/test/bats +++ b/test/bats @@ -1 +1 @@ -Subproject commit 49b377a751e6f9379abfdfb3dfa3aafabd8495a1 +Subproject commit eba9785cbdea10952d73567f17dd2ee3e74cd49c From dff9de221539ee5ec8d0dda3bcbb836bf26ef04f Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 7 Apr 2022 07:11:00 -0700 Subject: [PATCH 413/443] Bump the bats testing pointer --- test/bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bats b/test/bats index a8b8d0d..5c3f335 160000 --- a/test/bats +++ b/test/bats @@ -1 +1 @@ -Subproject commit a8b8d0dc7a05abe10c8511fe5041c137e06d18bf +Subproject commit 5c3f335e9b2fed5d89306398fb4e258f8fa06666 From 8670353092aab15e414c5dd96faeefb8809e4af7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 7 Apr 2022 14:29:26 -0700 Subject: [PATCH 414/443] Use autoload to be more "smart" about debug print --- diff-so-fancy | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 42a04b8..405c26c 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -1075,20 +1075,26 @@ sub debug_log { return 1; } -# Enable k() and kd() if there is a DSF_DEBUG environment variable -BEGIN { - if ($ENV{"DSF_DEBUG"}) { - require Data::Dump::Color; - *k = sub { Data::Dump::Color::dd(@_) }; - *kd = sub { +# Borrowed from: https://www.perturb.org/display/1097_Perl_detect_if_a_module_is_installed_before_using_it.html +sub AUTOLOAD { + our $AUTOLOAD; # keep 'use strict' happy + + if ($AUTOLOAD eq 'main::k' || $AUTOLOAD eq 'main::kd') { + if (eval { require Data::Dump::Color }) { + *k = sub { Data::Dump::Color::dd(@_) }; + } else { + require Data::Dumper; + *k = sub { print Data::Dumper::Dumper(@_) }; + } + + sub kd { k(@_); printf("Died at %2\$s line #%3\$s\n",caller()); exit(15); } - } else { - *k = sub {}; - *kd = sub {}; + + eval($AUTOLOAD . '(@_)'); } } From 6a301bbba5686e07b9a38a7a9a3f12cedf2e3fe4 Mon Sep 17 00:00:00 2001 From: brewsfab <19392469+brewsfab@users.noreply.github.com> Date: Sun, 19 Jun 2022 05:35:48 +0900 Subject: [PATCH 415/443] Support vanilla diff recursive flag (#436) * Support vanilla diff recursive flag * Adjust the regex patterns and explanation * Simplify recursive regex and add comment --- README.md | 9 +++++ diff-so-fancy | 6 +-- test/diff-so-fancy.bats | 24 ++++++++++++ .../recursive_default_as_mercurial.diff | 38 +++++++++++++++++++ .../recursive_longhand_as_mercurial.diff | 38 +++++++++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/recursive_default_as_mercurial.diff create mode 100644 test/fixtures/recursive_longhand_as_mercurial.diff diff --git a/README.md b/README.md index 3931f0c..2d55448 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,15 @@ Use `-u` with `diff` for unified output, and pipe the output to `diff-so-fancy`: diff -u file_a file_b | diff-so-fancy ``` +It also supports the recursive mode of diff with `-r` or `--recursive` as **first argument** + +```shell +diff -r -u folder_a folder_b | diff-so-fancy +``` + +```shell +diff --recursive -u folder_a folder_b | diff-so-fancy +``` ## Options ### markEmptyLines diff --git a/diff-so-fancy b/diff-so-fancy index 405c26c..e729b20 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -192,11 +192,11 @@ sub do_dsf_stuff { ######################### # Look for the filename # ######################### - # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { + # $4 $5 + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--recursive|--git|--cc) (.*?)(\e| b\/|$)/) { # Mercurial looks like: diff -r 82e55d328c8c hello.c - if ($4 eq "-r") { + if ($4 eq "-r" || $4 eq "--recursive") { $is_mercurial = 1; $meta_color = get_config_color("meta"); # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 31d9a56..463cce6 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -247,3 +247,27 @@ teardown_file() { assert_line --index 1 --partial "modified: doc/manual.xml.head" assert_line --index 3 --partial "@ doc/manual.xml.head:8355 @" } + +@test "recursive vanilla diff -r -bu as Mercurial" { + output=$( load_fixture "recursive_default_as_mercurial" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 1 --partial "renamed:" + assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 19 --partial "renamed:" + assert_line --index 21 --partial "@ language/__init__.py:1 @" + assert_line --index 25 --partial "renamed:" + assert_line --index 27 --partial "@ language/README.md:1 @" +} + +@test "recursive vanilla diff --recursive -u as Mercurial" { + output=$( load_fixture "recursive_longhand_as_mercurial" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 1 --partial "renamed:" + assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 19 --partial "renamed:" + assert_line --index 21 --partial "@ language/__init__.py:1 @" + assert_line --index 25 --partial "renamed:" + assert_line --index 27 --partial "@ language/README.md:1 @" +} diff --git a/test/fixtures/recursive_default_as_mercurial.diff b/test/fixtures/recursive_default_as_mercurial.diff new file mode 100644 index 0000000..32dc000 --- /dev/null +++ b/test/fixtures/recursive_default_as_mercurial.diff @@ -0,0 +1,38 @@ +diff -r -bu core/app.py language/app.py +--- core/app.py 2022-06-08 13:42:10.658920131 +0900 ++++ language/app.py 2022-06-08 12:07:22.773069512 +0900 +@@ -1,13 +1,13 @@ +-from flask import Flask, abort +-from . import CORE_MODULES ++from flask import Flask ++from . import SECTION + + app = Flask(__name__) + +-@app.route('/') ++@app.route(f'/{SECTION}/') + def index(name): +- if name in CORE_MODULES: +- return "Welcome to the documentation for the core module" +- abort(404) ++ return f"Welcome to the documentation for {name}" + ++if __name__ == "__main__": ++ app.run(host="0.0.0.0",port=3000) + + +diff -r -bu core/__init__.py language/__init__.py +--- core/__init__.py 2022-06-08 12:16:35.203331282 +0900 ++++ language/__init__.py 2022-06-08 12:03:32.464264288 +0900 +@@ -1 +1 @@ +-CORE_MODULES=['base','utils','status'] ++SECTION="language" +diff -r -bu core/README.md language/README.md +--- core/README.md 2022-06-08 13:52:56.962174912 +0900 ++++ language/README.md 2022-06-08 13:52:39.498090643 +0900 +@@ -1,4 +1,4 @@ +-# Core ++# Language + + ## Installation + diff --git a/test/fixtures/recursive_longhand_as_mercurial.diff b/test/fixtures/recursive_longhand_as_mercurial.diff new file mode 100644 index 0000000..c9860d0 --- /dev/null +++ b/test/fixtures/recursive_longhand_as_mercurial.diff @@ -0,0 +1,38 @@ +diff --recursive -u core/app.py language/app.py +--- core/app.py 2022-06-08 13:42:10.658920131 +0900 ++++ language/app.py 2022-06-08 12:07:22.773069512 +0900 +@@ -1,13 +1,13 @@ +-from flask import Flask, abort +-from . import CORE_MODULES ++from flask import Flask ++from . import SECTION + + app = Flask(__name__) + +-@app.route('/') ++@app.route(f'/{SECTION}/') + def index(name): +- if name in CORE_MODULES: +- return "Welcome to the documentation for the core module" +- abort(404) ++ return f"Welcome to the documentation for {name}" + ++if __name__ == "__main__": ++ app.run(host="0.0.0.0",port=3000) + + +diff --recursive -u core/__init__.py language/__init__.py +--- core/__init__.py 2022-06-08 12:16:35.203331282 +0900 ++++ language/__init__.py 2022-06-08 12:03:32.464264288 +0900 +@@ -1 +1 @@ +-CORE_MODULES=['base','utils','status'] ++SECTION="language" +diff --recursive -u core/README.md language/README.md +--- core/README.md 2022-06-08 13:52:56.962174912 +0900 ++++ language/README.md 2022-06-08 13:52:39.498090643 +0900 +@@ -1,4 +1,4 @@ +-# Core ++# Language + + ## Installation + From c1660def5c60096e8422694debc924009c35ba9b Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sat, 18 Jun 2022 13:45:08 -0700 Subject: [PATCH 416/443] Move a bug/feature test to the bugs.test file --- test/bugs.bats | 24 ++++++++++++++++++++++++ test/diff-so-fancy.bats | 24 ------------------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index d0934ec..203e2ac 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -45,3 +45,27 @@ teardown_file() { assert_line --index 5 --partial "5;22m" # green added line assert_line --index 8 --partial "5;52m" # red removed line } + +@test "recursive vanilla diff -r -bu as Mercurial (#436)" { + output=$( load_fixture "recursive_default_as_mercurial" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 1 --partial "renamed:" + assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 19 --partial "renamed:" + assert_line --index 21 --partial "@ language/__init__.py:1 @" + assert_line --index 25 --partial "renamed:" + assert_line --index 27 --partial "@ language/README.md:1 @" +} + +@test "recursive vanilla diff --recursive -u as Mercurial (#436)" { + output=$( load_fixture "recursive_longhand_as_mercurial" | $diff_so_fancy ) + run printf "%s" "$output" + + assert_line --index 1 --partial "renamed:" + assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 19 --partial "renamed:" + assert_line --index 21 --partial "@ language/__init__.py:1 @" + assert_line --index 25 --partial "renamed:" + assert_line --index 27 --partial "@ language/README.md:1 @" +} diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 463cce6..31d9a56 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -247,27 +247,3 @@ teardown_file() { assert_line --index 1 --partial "modified: doc/manual.xml.head" assert_line --index 3 --partial "@ doc/manual.xml.head:8355 @" } - -@test "recursive vanilla diff -r -bu as Mercurial" { - output=$( load_fixture "recursive_default_as_mercurial" | $diff_so_fancy ) - run printf "%s" "$output" - - assert_line --index 1 --partial "renamed:" - assert_line --index 3 --partial "@ language/app.py:4 @" - assert_line --index 19 --partial "renamed:" - assert_line --index 21 --partial "@ language/__init__.py:1 @" - assert_line --index 25 --partial "renamed:" - assert_line --index 27 --partial "@ language/README.md:1 @" -} - -@test "recursive vanilla diff --recursive -u as Mercurial" { - output=$( load_fixture "recursive_longhand_as_mercurial" | $diff_so_fancy ) - run printf "%s" "$output" - - assert_line --index 1 --partial "renamed:" - assert_line --index 3 --partial "@ language/app.py:4 @" - assert_line --index 19 --partial "renamed:" - assert_line --index 21 --partial "@ language/__init__.py:1 @" - assert_line --index 25 --partial "renamed:" - assert_line --index 27 --partial "@ language/README.md:1 @" -} From 6fc812224125221e2409fe8a26ae3c246bdc2a07 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 30 Aug 2022 15:07:53 -0700 Subject: [PATCH 417/443] Potential fix for #438 --- diff-so-fancy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e729b20..e982ba0 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -931,7 +931,7 @@ sub git_ansi_color { # It's a simple 16 color OG ansi } elsif ($fg) { my $bright = $fg =~ s/bright//; - my $color_num = $colors->{$fg} + 30; + my $color_num = ($colors->{$fg} // 0) + 30; if ($bright) { $color_num += 60; } # Set bold @@ -952,7 +952,7 @@ sub git_ansi_color { # It's a simple 16 color OG ansi } elsif ($bg) { my $bright = $bg =~ s/bright//; - my $color_num = $colors->{$bg} + 40; + my $color_num = ($colors->{$bg} // 0) + 40; if ($bright) { $color_num += 60; } # Set bold From 796daa4c5f0c0faf6c9d60c009b1a46701a9a0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lajoie?= Date: Mon, 12 Sep 2022 22:45:30 +0200 Subject: [PATCH 418/443] Generate bright colors (#444) --- diff-so-fancy | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e982ba0..fa7fc59 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -900,6 +900,9 @@ sub git_ansi_color { 'cyan' => 6, 'white' => 7, }; + for my $k (keys %{ $colors }) { + $colors->{"bright" . $k} = $colors->{$k} + 60; + } my @ansi_part = (); @@ -930,11 +933,7 @@ sub git_ansi_color { } # It's a simple 16 color OG ansi } elsif ($fg) { - my $bright = $fg =~ s/bright//; my $color_num = ($colors->{$fg} // 0) + 30; - - if ($bright) { $color_num += 60; } # Set bold - push(@ansi_part, $color_num); } @@ -951,11 +950,7 @@ sub git_ansi_color { } # It's a simple 16 color OG ansi } elsif ($bg) { - my $bright = $bg =~ s/bright//; my $color_num = ($colors->{$bg} // 0) + 40; - - if ($bright) { $color_num += 60; } # Set bold - push(@ansi_part, $color_num); } From 483d51706b5f8f750468c6e6555dccb2c00c74ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lajoie?= Date: Mon, 12 Sep 2022 22:47:50 +0200 Subject: [PATCH 419/443] Add dim and underline as Select Graphic Rendition (#442) --- diff-so-fancy | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index fa7fc59..0a5f775 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -910,6 +910,14 @@ sub git_ansi_color { push(@ansi_part, "1"); } + if (grep { /dim/ } @parts) { + push(@ansi_part, "2"); + } + + if (grep { /ul/ } @parts) { + push(@ansi_part, "4"); + } + if (grep { /reverse/ } @parts) { push(@ansi_part, "7"); } From 0c877075aaaed8657c3f232d8c33be3b4b119c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lajoie?= Date: Mon, 12 Sep 2022 22:48:34 +0200 Subject: [PATCH 420/443] Reset color instead of forcing to bold for function (#441) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0a5f775..6218e6e 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -300,7 +300,7 @@ sub do_dsf_stuff { print $frag_color; } - print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; + print "@ $last_file_seen:$start_line \@${reset_color}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # ################################### From 2d519df741ae5d173046fea02afdc9c69e184569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lajoie?= Date: Mon, 12 Sep 2022 22:54:37 +0200 Subject: [PATCH 421/443] Add test for highlight and last function (#445) --- test/diff-so-fancy.bats | 7 +++++++ test/test_helper/util.bash | 1 + 2 files changed, 8 insertions(+) diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 31d9a56..9512ea3 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -247,3 +247,10 @@ teardown_file() { assert_line --index 1 --partial "modified: doc/manual.xml.head" assert_line --index 3 --partial "@ doc/manual.xml.head:8355 @" } + +@test "Functionnal part with bright color" { + output=$( load_fixture "move_with_content_change" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 3 --partial "@ height" +} + diff --git a/test/test_helper/util.bash b/test/test_helper/util.bash index dbad4b1..7b58a7b 100644 --- a/test/test_helper/util.bash +++ b/test/test_helper/util.bash @@ -26,6 +26,7 @@ setup_default_dsf_git_config() { old = red bold new = green bold whitespace = red reverse + func = brightyellow [color "diff-highlight"] oldNormal = red bold From bab2911457a0feec80a809aa13487c433f004c74 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 12 Sep 2022 13:57:44 -0700 Subject: [PATCH 422/443] Add a comment to help later --- diff-so-fancy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diff-so-fancy b/diff-so-fancy index 6218e6e..18bd990 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -890,6 +890,7 @@ sub git_ansi_color { if (!@parts) { return ''; } + my $colors = { 'black' => 0, 'red' => 1, @@ -900,6 +901,8 @@ sub git_ansi_color { 'cyan' => 6, 'white' => 7, }; + + # Bright colors are just offsets from the "regular" color for my $k (keys %{ $colors }) { $colors->{"bright" . $k} = $colors->{$k} + 60; } From 7dec9311fa69192bfaa7c513e303cd2e7eae28b7 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 12 Sep 2022 13:59:48 -0700 Subject: [PATCH 423/443] Move a test to the bugs testing file --- test/bugs.bats | 6 ++++++ test/diff-so-fancy.bats | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index 203e2ac..a148685 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -69,3 +69,9 @@ teardown_file() { assert_line --index 25 --partial "renamed:" assert_line --index 27 --partial "@ language/README.md:1 @" } + +@test "Functional part with bright color (#444)" { + output=$( load_fixture "move_with_content_change" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 3 --partial "@ height" +} diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 9512ea3..31d9a56 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -247,10 +247,3 @@ teardown_file() { assert_line --index 1 --partial "modified: doc/manual.xml.head" assert_line --index 3 --partial "@ doc/manual.xml.head:8355 @" } - -@test "Functionnal part with bright color" { - output=$( load_fixture "move_with_content_change" | $diff_so_fancy ) - run printf "%s" "$output" - assert_line --index 3 --partial "@ height" -} - From ac0cbe2443a5a8378857fa2e41b3594903ec5ae9 Mon Sep 17 00:00:00 2001 From: Vladimir Kirov Date: Thu, 3 Nov 2022 18:21:47 +0200 Subject: [PATCH 424/443] bump required perl version (#447) - /u is added in perl 5.14 - [link to perl5140delta](https://perldoc.perl.org/5.14.4/perl5140delta#/d,-/l,-/u,-and-/a-modifiers) --- diff-so-fancy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-so-fancy b/diff-so-fancy index 18bd990..0b0f439 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -4,7 +4,7 @@ my $VERSION = "1.4.4"; ################################################################################# -use v5.010; # Require Perl 5.10 for 'state' variables +use v5.014; # Require Perl 5.14 for 'state' variables and /u in regexes use warnings FATAL => 'all'; use strict; From f67767be40620095e13781655e42ce30d1e2858d Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 28 Nov 2022 10:05:01 -0800 Subject: [PATCH 425/443] Code simplifications to address #448 --- third_party/build_fatpack/build.pl | 2 +- third_party/cli_bench/cli_bench.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/build_fatpack/build.pl b/third_party/build_fatpack/build.pl index 942527f..26993af 100755 --- a/third_party/build_fatpack/build.pl +++ b/third_party/build_fatpack/build.pl @@ -40,7 +40,7 @@ my $cmd = "fatpack pack $input_file 2>/dev/null > $output_file"; my $exit = system($cmd); -$exit = $exit >> 8; +$exit >>= 8; rmdir("fatlib"); # fatpack leaves empty fatlib dirs so we remove them my $size = -s $output_file; diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl index 9f05ee2..7c3aaee 100644 --- a/third_party/cli_bench/cli_bench.pl +++ b/third_party/cli_bench/cli_bench.pl @@ -114,7 +114,7 @@ sub average { } my $count = scalar(@_); - $ret = $ret / $count; + $ret /= $count; return $ret; } From 1d8fb92a7fe5a29af0e19371a9276c09c8b670e0 Mon Sep 17 00:00:00 2001 From: Arthur Bols Date: Thu, 19 Jan 2023 15:14:38 +0100 Subject: [PATCH 426/443] Add Fedora to known packages (#452) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebf152c..9881574 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/diff-so-fancy/default.nix), in the [Arch community repo](https://archlinux.org/packages/community/any/diff-so-fancy/), and [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +`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/git-and-tools/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/), and [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). 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. From f2e94082e2fc76de2c90dcf7613812f71d97d50c Mon Sep 17 00:00:00 2001 From: Vitaly Zdanevich Date: Wed, 15 Feb 2023 20:25:31 +0400 Subject: [PATCH 427/443] README: distros: add Gentoo (#456) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9881574..94358ba 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/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/), and [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +`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/git-and-tools/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/7833a73834c5a4ffe060acd576bfdeeef2a9d5b8/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). 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. From 1f0dd538ed1a199e7f94e847ec8e59ca3cf87215 Mon Sep 17 00:00:00 2001 From: Vitaly Zdanevich Date: Wed, 15 Feb 2023 20:31:16 +0400 Subject: [PATCH 428/443] README: Gentoo package link: fix from commit to master (#457) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94358ba..b59d992 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/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/7833a73834c5a4ffe060acd576bfdeeef2a9d5b8/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +`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/git-and-tools/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). 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. From d7dcc1715f5bffc575183c16a2f458caa37cfa1b Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:43:33 +0100 Subject: [PATCH 429/443] docs: fix url to Nix package --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b59d992..9c393cb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/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). +`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). 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. From e2d91b2147964659b72352835dbd938623ce3093 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sun, 5 Mar 2023 10:36:39 -0800 Subject: [PATCH 430/443] Update some unit tests to have the correct context offset lines --- test/bugs.bats | 4 ++-- test/diff-so-fancy.bats | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/bugs.bats b/test/bugs.bats index a148685..c209be7 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -51,7 +51,7 @@ teardown_file() { run printf "%s" "$output" assert_line --index 1 --partial "renamed:" - assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 3 --partial "@ language/app.py:1 @" assert_line --index 19 --partial "renamed:" assert_line --index 21 --partial "@ language/__init__.py:1 @" assert_line --index 25 --partial "renamed:" @@ -63,7 +63,7 @@ teardown_file() { run printf "%s" "$output" assert_line --index 1 --partial "renamed:" - assert_line --index 3 --partial "@ language/app.py:4 @" + assert_line --index 3 --partial "@ language/app.py:1 @" assert_line --index 19 --partial "renamed:" assert_line --index 21 --partial "@ language/__init__.py:1 @" assert_line --index 25 --partial "renamed:" diff --git a/test/diff-so-fancy.bats b/test/diff-so-fancy.bats index 31d9a56..cddac9d 100644 --- a/test/diff-so-fancy.bats +++ b/test/diff-so-fancy.bats @@ -187,8 +187,10 @@ teardown_file() { @test "Reworked hunks" { output=$( load_fixture "file-moves" | $diff_so_fancy ) - assert_output --partial '@ square.yml:4 @' - assert_output --partial '@ package.json:1 @' + run printf "%s" "$output" + + assert_line --index 46 --partial "@ package.json:1 @" + assert_line --index 79 --partial "@ square.yml:1 @" } @test "Reworked hunks (noprefix)" { From 022a86aa4f92e4fce358a863c17be886bcc77875 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Sun, 5 Mar 2023 10:42:41 -0800 Subject: [PATCH 431/443] Attempt to calculate context lines for #460 --- diff-so-fancy | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 0b0f439..e440fda 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -26,6 +26,7 @@ my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRul my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); my $has_stdin = has_stdin(); +my $CONTEXT_LINES = undef; # Number of lines of context diff used my $ansi_regex = qr/\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK]/; my $ansi_color_regex = qr/(${ansi_regex})?/; @@ -139,6 +140,11 @@ do_dsf_stuff(\@lines); sub do_dsf_stuff { my $input = shift(); + # Calculate the context lines the first time + if (!defined $CONTEXT_LINES) { + $CONTEXT_LINES = calculate_context_lines(@lines); + } + #print STDERR "START -------------------------------------------------\n"; #print STDERR join("",@$input); #print STDERR "END ---------------------------------------------------\n"; @@ -534,16 +540,14 @@ sub start_line_calc { return 1; } - # Git defaults to three lines of context - my $default_context_lines = 3; # Three lines on either side, and the line itself = 7 - my $expected_context = ($default_context_lines * 2 + 1); + my $expected_context = ($CONTEXT_LINES * 2 + 1); # The first three lines if ($line_num == 1 && $diff_context < $expected_context) { - $ret = $diff_context - $default_context_lines; + $ret = $diff_context - $CONTEXT_LINES; } else { - $ret = $line_num + $default_context_lines; + $ret = $line_num + $CONTEXT_LINES; } if ($ret < 1) { @@ -1081,6 +1085,27 @@ sub debug_log { return 1; } +sub calculate_context_lines { + my @lines = @_; + my $count = 0; + my $hunk_line = 0; + + foreach my $line (@lines) { + if ($line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $hunk_line = $count; + } elsif ($hunk_line && $line =~ /^${ansi_color_regex}[+-]/) { + my $diff = $count - $hunk_line - 1; + #print "Hunk: $hunk_line, ChangedLine: $count ($diff context)\n"; + return $diff; + } + + $count++; + }; + + # If for some reason we can't figure it out, assume 3 + return 3; +} + # Borrowed from: https://www.perturb.org/display/1097_Perl_detect_if_a_module_is_installed_before_using_it.html sub AUTOLOAD { our $AUTOLOAD; # keep 'use strict' happy From 3642fce87a523a13518eb27b55370b690c817d66 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 8 Mar 2022 15:55:21 -0800 Subject: [PATCH 432/443] Output the average using floating point --- third_party/cli_bench/cli_bench.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/cli_bench/cli_bench.pl b/third_party/cli_bench/cli_bench.pl index 7c3aaee..e2e0214 100644 --- a/third_party/cli_bench/cli_bench.pl +++ b/third_party/cli_bench/cli_bench.pl @@ -59,7 +59,7 @@ @res = sort(@res); @res = splice(@res, $outlier, $num - $outlier * 2); -my $avg = int(average(@res)); +my $avg = sprintf("%.1f", average(@res)); if ($details) { show_details(@res); From 8ef749f4edf5ea8fcc666f526b528218bf3ea830 Mon Sep 17 00:00:00 2001 From: Vitaly Zdanevich Date: Wed, 15 Feb 2023 20:25:31 +0400 Subject: [PATCH 433/443] README: distros: add Gentoo (#456) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2d55448..f4115b0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,11 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. +<<<<<<< HEAD `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 in the [Fedora COPR repository](https://copr.fedorainfracloud.org/coprs/kopfkrieg/diff-so-fancy/). +======= +`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/git-and-tools/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/7833a73834c5a4ffe060acd576bfdeeef2a9d5b8/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +>>>>>>> f2e9408 (README: distros: add Gentoo (#456)) 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. From 63b4530d270fdb10abd83cdd6941b18e33955009 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 8 Jun 2023 15:03:10 -0700 Subject: [PATCH 434/443] README: Gentoo package link: fix from commit to master (#457) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f4115b0..07957ff 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -<<<<<<< HEAD -`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 in the [Fedora COPR repository](https://copr.fedorainfracloud.org/coprs/kopfkrieg/diff-so-fancy/). -======= `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/git-and-tools/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/7833a73834c5a4ffe060acd576bfdeeef2a9d5b8/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). ->>>>>>> f2e9408 (README: distros: add Gentoo (#456)) 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. From 8fede3b2c766682791dd77d83ef734352f063e44 Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:43:33 +0100 Subject: [PATCH 435/443] docs: fix url to Nix package --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07957ff..e1e1fc7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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/git-and-tools/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/7833a73834c5a4ffe060acd576bfdeeef2a9d5b8/app-misc/diff-so-fancy), [ppa:aos for Debian/Ubuntu Linux](https://github.com/aos/dsf-debian). +`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). 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. From 006ede37b62c7b574710b823fae7750ea661c02f Mon Sep 17 00:00:00 2001 From: KopfKrieg Date: Sun, 23 Jan 2022 01:04:06 +0100 Subject: [PATCH 436/443] Added Fedora COPR repository to the installation section of the README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1e1fc7..c97f551 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Vanilla `git diff` vs `git` and `diff-so-fancy` 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. -`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). +`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/). 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. From 32f5c1d36eff33c1268e4ea93781227942b224aa Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 8 Jun 2023 15:00:02 -0700 Subject: [PATCH 437/443] Potential fix for #469 --- test/bugs.bats | 6 ++++++ test/fixtures/ansi_reset_without_zero.diff | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 test/fixtures/ansi_reset_without_zero.diff diff --git a/test/bugs.bats b/test/bugs.bats index c209be7..460a989 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -75,3 +75,9 @@ teardown_file() { run printf "%s" "$output" assert_line --index 3 --partial "@ height" } + +@test "ANSI reset without leading zero (#469)" { + output=$( load_fixture "ansi_reset_without_zero" | $diff_so_fancy ) + run printf "%s" "$output" + assert_line --index 4 --partial 'Hello' +} diff --git a/test/fixtures/ansi_reset_without_zero.diff b/test/fixtures/ansi_reset_without_zero.diff new file mode 100644 index 0000000..d83b4f2 --- /dev/null +++ b/test/fixtures/ansi_reset_without_zero.diff @@ -0,0 +1,5 @@ +--- /tmp/baker/one.txt 2023-06-08 14:48:26.883146532 -0700 ++++ /tmp/baker/two.txt 2023-06-08 14:48:35.080137160 -0700 +@@ -1 +1 @@ +-Hello world ++Hello Jason From ee88b686460f0a037804644e99e16bf198c1a504 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Thu, 8 Jun 2023 15:19:14 -0700 Subject: [PATCH 438/443] Revert "Potential fix for #469" This reverts commit 32f5c1d36eff33c1268e4ea93781227942b224aa. --- test/bugs.bats | 6 ------ test/fixtures/ansi_reset_without_zero.diff | 5 ----- 2 files changed, 11 deletions(-) delete mode 100644 test/fixtures/ansi_reset_without_zero.diff diff --git a/test/bugs.bats b/test/bugs.bats index 460a989..c209be7 100644 --- a/test/bugs.bats +++ b/test/bugs.bats @@ -75,9 +75,3 @@ teardown_file() { run printf "%s" "$output" assert_line --index 3 --partial "@ height" } - -@test "ANSI reset without leading zero (#469)" { - output=$( load_fixture "ansi_reset_without_zero" | $diff_so_fancy ) - run printf "%s" "$output" - assert_line --index 4 --partial 'Hello' -} diff --git a/test/fixtures/ansi_reset_without_zero.diff b/test/fixtures/ansi_reset_without_zero.diff deleted file mode 100644 index d83b4f2..0000000 --- a/test/fixtures/ansi_reset_without_zero.diff +++ /dev/null @@ -1,5 +0,0 @@ ---- /tmp/baker/one.txt 2023-06-08 14:48:26.883146532 -0700 -+++ /tmp/baker/two.txt 2023-06-08 14:48:35.080137160 -0700 -@@ -1 +1 @@ --Hello world -+Hello Jason From 0b39dfb145b9d17ef6f4d63f7f5ade7929e6a4b1 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 9 Jun 2023 10:32:02 -0700 Subject: [PATCH 439/443] Update `report-bug.sh` for #461 --- report-bug.sh | 106 ++++++++++++++++++++++++---------------------- reporting-bugs.md | 6 +-- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/report-bug.sh b/report-bug.sh index 8f9fd8d..7419fbd 100755 --- a/report-bug.sh +++ b/report-bug.sh @@ -1,60 +1,66 @@ #!/bin/bash clipboard() { - local copy_cmd - if [ -n "$PBCOPY_SERVER" ]; then - local body="" # buffer - body=$(cat) - # while IFS= read -r buffer; do - # body="$body$buffer\n"; - # done - curl "$PBCOPY_SERVER" --data-urlencode body="$body" >/dev/null 2>&1 - return $? - fi - if type putclip >/dev/null 2>&1; then - copy_cmd="putclip" - elif [ -e /dev/clipboard ];then - cat > /dev/clipboard - return 0 - elif type clip >/dev/null 2>&1; then - if [[ $LANG = UTF-8 ]]; then - copy_cmd="iconv -f utf-8 -t shift_jis | clip" - else - copy_cmd=clip - fi - # copy_cmd=clip - elif command -v pbcopy >/dev/null 2>&1; then - copy_cmd="pbcopy" - elif command -v xclip >/dev/null 2>&1; then - # copy_cmd="xclip -i -selection clipboard" - copy_cmd="xclip" - elif command -v xsel >/dev/null 2>&1 ; then - local copy_cmd="xsel -b" - fi - if [ -n "$copy_cmd" ] ;then - eval "$copy_cmd" - else - echo "clipboard is unavailable" 1>&2 - fi + local copy_cmd + + if [ -n "$PBCOPY_SERVER" ]; then + local body="" # buffer + body=$(cat) + # while IFS= read -r buffer; do + # body="$body$buffer\n"; + # done + curl "$PBCOPY_SERVER" --data-urlencode body="$body" >/dev/null 2>&1 + return $? + fi + + if type putclip >/dev/null 2>&1; then + copy_cmd="putclip" + elif [ -e /dev/clipboard ];then + cat > /dev/clipboard + return 0 + elif type clip >/dev/null 2>&1; then + if [[ $LANG = UTF-8 ]]; then + copy_cmd="iconv -f utf-8 -t shift_jis | clip" + else + copy_cmd=clip + fi + # copy_cmd=clip + elif command -v pbcopy >/dev/null 2>&1; then + copy_cmd="pbcopy" + elif command -v xclip >/dev/null 2>&1; then + # copy_cmd="xclip -i -selection clipboard" + copy_cmd="xclip" + elif command -v xsel >/dev/null 2>&1 ; then + local copy_cmd="xsel -b" + fi + + if [ -n "$copy_cmd" ] ;then + eval "$copy_cmd" + else + echo "clipboard is unavailable" 1>&2 + fi } -file=${2:-diff.txt} +if [ $# -eq 0 ]; then + echo "Usage: $0 'git diff HEAD..HEAD^'" + exit 7 +fi -{ - echo "$1" - eval "$1" - echo "" - echo "" - echo "" +file=${2:-dsf-bug-report.txt} - echo "$1 --color" - eval "$1 --color" +{ + echo "$1" + eval "$1" + echo "" + echo "" + echo "" - echo "git config pager.diff" - eval "git config pager.diff" + echo "$1 --color" + eval "$1 --color" - echo "git config pager.show" - eval "git config pager.show" -} > "$file" + echo "git config --list | grep pager" + eval "git config --list | grep pager" +} | base64 > "$file" -base64 < "$file" | clipboard +echo "Wrote file: $file" +echo "Please open a new issue on Github and attach it" diff --git a/reporting-bugs.md b/reporting-bugs.md index 109fd19..eb0989d 100644 --- a/reporting-bugs.md +++ b/reporting-bugs.md @@ -10,10 +10,6 @@ You can use [report-bug.sh](./report-bug.sh) we provide ```sh ./report-bug.sh 'git diff HEAD..HEAD^' - -# or - -curl https://raw.githubusercontent.com/so-fancy/diff-so-fancy/master/report-bug.sh | bash -s 'git diff HEAD..HEAD^' 'diff.txt' ``` -Grab the output file and attach to the GitHub issue you create. A base64 version is also copied to your clipboard so you can paste to the issue. +Attach the output file to the GitHub issue you create. From 9ffea48cc4eadedd9d280c11fc8919bf2aef9e41 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 9 Jun 2023 10:49:10 -0700 Subject: [PATCH 440/443] Don't error out if git is not installed (#429) --- diff-so-fancy | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index e440fda..516378f 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -454,7 +454,7 @@ sub boolean { # Get the git config sub git_config_raw { - my $cmd = "git config --list"; + my $cmd = "git config --list 2>&1"; my @out = `$cmd`; return \@out; @@ -1030,11 +1030,17 @@ sub get_terminal_width { sub show_debug_info { my @less = get_less_charset(); - my $git_ver = trim(`git --version`); + my $git_ver = trim(`git --version 2>&1`); $git_ver =~ s/[^\d.]//g; + if ($git_ver !~ /git/) { + $git_ver = "Unknown"; + } else { + $git_ver = "v" . $git_ver; + } + print "Diff-so-fancy : v$VERSION\n"; - print "Git : v$git_ver\n"; + print "Git : $git_ver\n"; print "Perl : $^V\n"; print "\n"; From 8949ad9fc8bae393c9403531bf444dbccfc79142 Mon Sep 17 00:00:00 2001 From: "Chris. Webster" Date: Fri, 9 Jun 2023 11:01:30 -0700 Subject: [PATCH 441/443] fix and maint: match most git supported color settings (#453) * maint: set ansi colors in common routine Setting foreground and background colors is now using the same code. Signed-off-by: Chris. Webster * fix: ignore negative color attributes with tests The colors in the git config may have attributes and negative versions of them (no/no- prefix). The negative versions will no longer cause a false positive checking for the attribute. A new test file is specifically for testing the settings of the ansi codes and the possible settings in the git config. Signed-off-by: Chris. Webster * fix: support git config colors normal and default Normal is a placeholder and will be ignored. Default will generate ansi codes for the default foreground or background color. Git validates the config so a bright prefix on these values would not be allowed. They are added to the hash to keep the code simple. Signed-off-by: Chris. Webster * fix: support #rgb colors in git config Git supports diff colors in #rrggbb notation. diff-so-fancy now supports them as well. Signed-off-by: Chris. Webster --------- Signed-off-by: Chris. Webster --- diff-so-fancy | 70 +++++++++--------- test/git-config.bats | 171 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 test/git-config.bats diff --git a/diff-so-fancy b/diff-so-fancy index 516378f..34c9753 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -904,6 +904,8 @@ sub git_ansi_color { 'magenta' => 5, 'cyan' => 6, 'white' => 7, + 'default' => 9, # pseudo color (39/49 = set default) + 'normal' => -1, # placeholder color to be ignored }; # Bright colors are just offsets from the "regular" color @@ -913,68 +915,62 @@ sub git_ansi_color { my @ansi_part = (); - if (grep { /bold/ } @parts) { + if (grep { /^bold$/ } @parts) { push(@ansi_part, "1"); } - if (grep { /dim/ } @parts) { + if (grep { /^dim$/ } @parts) { push(@ansi_part, "2"); } - if (grep { /ul/ } @parts) { + if (grep { /^ul$/ } @parts) { push(@ansi_part, "4"); } - if (grep { /reverse/ } @parts) { + if (grep { /^reverse$/ } @parts) { push(@ansi_part, "7"); } # Remove parts that aren't colors - @parts = grep { exists $colors->{$_} || is_numeric($_) } @parts; + @parts = grep { exists $colors->{$_} || is_numeric($_) || /^\#/ } @parts; my $fg = $parts[0] // ""; my $bg = $parts[1] // ""; + set_ansi_color( $fg, 0, \@ansi_part, $colors ) if $fg; + set_ansi_color( $bg, 10, \@ansi_part, $colors ) if $bg; + ############################################# - # It's an numeric value, so it's an 8 bit color - if (is_numeric($fg)) { - if ($fg < 8) { - push(@ansi_part, $fg + 30); - } elsif ($fg < 16) { - push(@ansi_part, $fg + 82); - } else { - push(@ansi_part, "38;5;$fg"); - } - # It's a simple 16 color OG ansi - } elsif ($fg) { - my $color_num = ($colors->{$fg} // 0) + 30; - push(@ansi_part, $color_num); - } + my $ansi_str = join(";", @ansi_part); + my $ret = "\e[" . $ansi_str . "m"; - ############################################# + return $ret; +} - # It's an numeric value, so it's an 8 bit color - if (is_numeric($bg)) { - if ($bg < 8) { - push(@ansi_part, $bg + 40); - } elsif ($bg < 16) { - push(@ansi_part, $bg + 92); +sub set_ansi_color { + my ($color, $increment, $ansi_part, $colors) = @_; + my $base_code = 30 + $increment; + my $base8_code = 38 + $increment; + my $ext_code = 82 + $increment; + + if (is_numeric($color)) { + if ($color < 8) { + push(@$ansi_part, $color + $base_code); + } elsif ($color < 16) { + push(@$ansi_part, $color + $ext_code); } else { - push(@ansi_part, "48;5;$bg"); + push(@$ansi_part, "$base8_code;5;$color"); } + # It's a full rgb code + } elsif ($color =~ /^#/) { + my ($rgbr, $rgbg, $rgbb) = $color =~ /.(..)(..)(..)/; + push(@$ansi_part, "$base8_code;2;" . hex($rgbr) . ";" . hex($rgbg) . ";" . hex($rgbb)); # It's a simple 16 color OG ansi - } elsif ($bg) { - my $color_num = ($colors->{$bg} // 0) + 40; - push(@ansi_part, $color_num); + } elsif ($color ne "normal") { + my $color_num = $colors->{$color} + $base_code; + push(@$ansi_part, $color_num); } - - ############################################# - - my $ansi_str = join(";", @ansi_part); - my $ret = "\e[" . $ansi_str . "m"; - - return $ret; } sub is_numeric { diff --git a/test/git-config.bats b/test/git-config.bats new file mode 100644 index 0000000..462ee74 --- /dev/null +++ b/test/git-config.bats @@ -0,0 +1,171 @@ +#!/usr/bin/env bats + +# Used by both `setup_file` and `setup`, which are special bats callbacks. +__load_imports__() { + load 'test_helper/bats-support/load' + load 'test_helper/bats-assert/load' + load 'test_helper/util' +} + +# ansi related values +escape=$'\e' +ansi_bold=1 +ansi_dim=2 +ansi_ul=4 +ansi_reverse=7 + +# hash of colors +declare -Ag ansi_colors=( + [black]='0' + [red]='1' + [green]='2' + [yellow]='3' + [blue]='4' + [magenta]='5' + [cyan]='6' + [white]='7' + [default]='9' +) + +# get a foreground or background color code +ansi_color() { + color=$1 + incr=$2 + base_code=$((30+$incr)) + + # handle bright prefix + if [[ "${1}" =~ (bright)(.*) ]] + then + color=${BASH_REMATCH[2]} + base_code=$((90+$incr)) + fi + code=$(($base_code+${ansi_colors[$color]})) + echo "$code" +} + +# get a foreground color code +fg_color() { + ansi_color $1 0 +} + +# get a background color code +bg_color() { + ansi_color $1 10 +} + +# get rgb color codes from hex +rgb_color() { + incr=$2 + base_code=$((38+$incr)) + [[ $1 =~ ^.(..)(..)(..)$ ]] + rgb1="${BASH_REMATCH[1]}" + rgb2="${BASH_REMATCH[2]}" + rgb3="${BASH_REMATCH[3]}" + echo "${base_code};2;$(( 16#${rgb1} ));$(( 16#${rgb2} ));$(( 16#${rgb3} ))" +} + +# get a foreground color code +fg_rgb_color() { + rgb_color $1 0 +} + +# get a background color code +bg_rgb_color() { + rgb_color $1 10 +} + +# build config using passed in values +setup_dsf_git_config() { + GIT_CONFIG="$(dsf_test_git_config)" || return $? + cat > "${GIT_CONFIG}" < Date: Fri, 9 Jun 2023 11:02:51 -0700 Subject: [PATCH 442/443] We don't need to store the fatpacked version here anymore --- third_party/build_fatpack/diff-so-fancy | 1442 ----------------------- 1 file changed, 1442 deletions(-) delete mode 100755 third_party/build_fatpack/diff-so-fancy diff --git a/third_party/build_fatpack/diff-so-fancy b/third_party/build_fatpack/diff-so-fancy deleted file mode 100755 index 2e165ad..0000000 --- a/third_party/build_fatpack/diff-so-fancy +++ /dev/null @@ -1,1442 +0,0 @@ -#!/usr/bin/env perl - -# This chunk of stuff was generated by App::FatPacker. To find the original -# file's code, look for the end of this BEGIN block or the string 'FATPACK' -BEGIN { -my %fatpacked; - -$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT'; - package DiffHighlight; - - use 5.008; - use warnings FATAL => 'all'; - use strict; - - # Use the correct value for both UNIX and Windows (/dev/null vs nul) - use File::Spec; - - my $NULL = File::Spec->devnull(); - - # Highlight by reversing foreground and background. You could do - # other things like bold or underline if you prefer. - our @OLD_HIGHLIGHT = ( - undef, - "\e[7m", - "\e[27m", - ); - our @NEW_HIGHLIGHT = ( - $OLD_HIGHLIGHT[0], - $OLD_HIGHLIGHT[1], - $OLD_HIGHLIGHT[2], - ); - - - - my $RESET = "\x1b[m"; - my $COLOR = qr/\x1b\[[0-9;]*m/; - my $BORING = qr/$COLOR|\s/; - - my @removed; - my @added; - my $in_hunk; - my $graph_indent = 0; - - our $line_cb = sub { print @_ }; - our $flush_cb = sub { local $| = 1 }; - - # Count the visible width of a string, excluding any terminal color sequences. - sub visible_width { - local $_ = shift; - my $ret = 0; - while (length) { - if (s/^$COLOR//) { - # skip colors - } elsif (s/^.//) { - $ret++; - } - } - return $ret; - } - - # Return a substring of $str, omitting $len visible characters from the - # beginning, where terminal color sequences do not count as visible. - sub visible_substr { - my ($str, $len) = @_; - while ($len > 0) { - if ($str =~ s/^$COLOR//) { - next - } - $str =~ s/^.//; - $len--; - } - return $str; - } - - sub handle_line { - my $orig = shift; - local $_ = $orig; - - # match a graph line that begins a commit - if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space - $COLOR?\*$COLOR?[ ] # a "*" with its trailing space - (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" - [ ]* # trailing whitespace for merges - /x) { - my $graph_prefix = $&; - - # We must flush before setting graph indent, since the - # new commit may be indented differently from what we - # queued. - flush(); - $graph_indent = visible_width($graph_prefix); - - } elsif ($graph_indent) { - if (length($_) < $graph_indent) { - $graph_indent = 0; - } else { - $_ = visible_substr($_, $graph_indent); - } - } - - if (!$in_hunk) { - $line_cb->($orig); - $in_hunk = /^$COLOR*\@\@ /; - } - elsif (/^$COLOR*-/) { - push @removed, $orig; - } - elsif (/^$COLOR*\+/) { - push @added, $orig; - } - else { - flush(); - $line_cb->($orig); - $in_hunk = /^$COLOR*[\@ ]/; - } - - # Most of the time there is enough output to keep things streaming, - # but for something like "git log -Sfoo", you can get one early - # commit and then many seconds of nothing. We want to show - # that one commit as soon as possible. - # - # Since we can receive arbitrary input, there's no optimal - # place to flush. Flushing on a blank line is a heuristic that - # happens to match git-log output. - if (!length) { - $flush_cb->(); - } - } - - sub flush { - # Flush any queued hunk (this can happen when there is no trailing - # context in the final diff of the input). - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - } - - sub highlight_stdin { - while () { - handle_line($_); - } - flush(); - } - - # Ideally we would feed the default as a human-readable color to - # git-config as the fallback value. But diff-highlight does - # not otherwise depend on git at all, and there are reports - # of it being used in other settings. Let's handle our own - # fallback, which means we will work even if git can't be run. - sub color_config { - my ($key, $default) = @_; - - # Removing the redirect speeds up execution by about 12ms - #my $s = `git config --get-color $key 2>$NULL`; - my $s = `git config --get-color $key`; - - return length($s) ? $s : $default; - } - - sub show_hunk { - my ($a, $b) = @_; - - # If one side is empty, then there is nothing to compare or highlight. - if (!@$a || !@$b) { - $line_cb->(@$a, @$b); - return; - } - - # If we have mismatched numbers of lines on each side, we could try to - # be clever and match up similar lines. But for now we are simple and - # stupid, and only handle multi-line hunks that remove and add the same - # number of lines. - if (@$a != @$b) { - $line_cb->(@$a, @$b); - return; - } - - my @queue; - for (my $i = 0; $i < @$a; $i++) { - my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - $line_cb->($rm); - push @queue, $add; - } - $line_cb->(@queue); - } - - sub highlight_pair { - my @a = split_line(shift); - my @b = split_line(shift); - - # Find common prefix, taking care to skip any ansi - # color codes. - my $seen_plusminus; - my ($pa, $pb) = (0, 0); - while ($pa < @a && $pb < @b) { - if ($a[$pa] =~ /$COLOR/) { - $pa++; - } - elsif ($b[$pb] =~ /$COLOR/) { - $pb++; - } - elsif ($a[$pa] eq $b[$pb]) { - $pa++; - $pb++; - } - elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { - $seen_plusminus = 1; - $pa++; - $pb++; - } - else { - last; - } - } - - # Find common suffix, ignoring colors. - my ($sa, $sb) = ($#a, $#b); - while ($sa >= $pa && $sb >= $pb) { - if ($a[$sa] =~ /$COLOR/) { - $sa--; - } - elsif ($b[$sb] =~ /$COLOR/) { - $sb--; - } - elsif ($a[$sa] eq $b[$sb]) { - $sa--; - $sb--; - } - else { - last; - } - } - - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), - highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); - } - else { - return join('', @a), - join('', @b); - } - } - - # we split either by $COLOR or by character. This has the side effect of - # leaving in graph cruft. It works because the graph cruft does not contain "-" - # or "+" - sub split_line { - local $_ = shift; - return utf8::decode($_) ? - map { utf8::encode($_); $_ } - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/ : - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/; - } - - sub highlight_line { - my ($line, $prefix, $suffix, $theme) = @_; - - my $start = join('', @{$line}[0..($prefix-1)]); - my $mid = join('', @{$line}[$prefix..$suffix]); - my $end = join('', @{$line}[($suffix+1)..$#$line]); - - # If we have a "normal" color specified, then take over the whole line. - # Otherwise, we try to just manipulate the highlighted bits. - if (defined $theme->[0]) { - s/$COLOR//g for ($start, $mid, $end); - chomp $end; - return join('', - $theme->[0], $start, $RESET, - $theme->[1], $mid, $RESET, - $theme->[0], $end, $RESET, - "\n" - ); - } else { - return join('', - $start, - $theme->[1], $mid, $theme->[2], - $end - ); - } - } - - # Pairs are interesting to highlight only if we are going to end up - # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting - # is just useless noise. We can detect this by finding either a matching prefix - # or suffix (disregarding boring bits like whitespace and colorization). - sub is_pair_interesting { - my ($a, $pa, $sa, $b, $pb, $sb) = @_; - my $prefix_a = join('', @$a[0..($pa-1)]); - my $prefix_b = join('', @$b[0..($pb-1)]); - my $suffix_a = join('', @$a[($sa+1)..$#$a]); - my $suffix_b = join('', @$b[($sb+1)..$#$b]); - - return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || - visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || - $suffix_a !~ /^$BORING*$/ || - $suffix_b !~ /^$BORING*$/; - } -DIFFHIGHLIGHT - -s/^ //mg for values %fatpacked; - -my $class = 'FatPacked::'.(0+\%fatpacked); -no strict 'refs'; -*{"${class}::files"} = sub { keys %{$_[0]} }; - -if ($] < 5.008) { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - my $pos = 0; - my $last = length $fat; - return (sub { - return 0 if $pos == $last; - my $next = (1 + index $fat, "\n", $pos) || $last; - $_ .= substr $fat, $pos, $next - $pos; - $pos = $next; - return 1; - }); - } - }; -} - -else { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - open my $fh, '<', \$fat - or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; - return $fh; - } - return; - }; -} - -unshift @INC, bless \%fatpacked, $class; - } # END OF FATPACK CODE - - -my $VERSION = "1.4.2"; - -################################################################################# - -use v5.010; # Require Perl 5.10 for 'state' variables -use warnings FATAL => 'all'; -use strict; - -use File::Spec; # For catdir -use File::Basename; # For dirname -use Cwd qw(abs_path); # For realpath() -use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC -use DiffHighlight; - -my $remove_file_add_header = 1; -my $remove_file_delete_header = 1; -my $clean_permission_changes = 1; -my $patch_mode = 0; -my $manually_color_lines = 0; # Usually git/hg colorizes the lines, but for raw patches we use this -my $change_hunk_indicators = git_config_boolean("diff-so-fancy.changeHunkIndicators","true"); -my $strip_leading_indicators = git_config_boolean("diff-so-fancy.stripLeadingSymbols","true"); -my $mark_empty_lines = git_config_boolean("diff-so-fancy.markEmptyLines","true"); -my $use_unicode_dash_for_ruler = git_config_boolean("diff-so-fancy.useUnicodeRuler","true"); -my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef); -my $git_strip_prefix = git_config_boolean("diff.noprefix","false"); -my $has_stdin = has_stdin(); - -my $ansi_color_regex = qr/(\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK])?/; -my $reset_color = color("reset"); -my $bold = color("bold"); -my $meta_color = ""; - -# Set the diff highlight colors from the config -init_diff_highlight_colors(); - -my ($file_1,$file_2); -my $args = argv(); # Hashref of all the ARGV stuff -my $last_file_seen = ""; -my $last_file_mode = ""; -my $i = 0; -my $in_hunk = 0; -my $columns_to_remove = 0; -my $is_mercurial = 0; -my $color_forced = 0; # Has the color been forced on/off - -# We try and be smart about whether we need to do line coloring, but -# this is an option to force it on/off -if ($args->{color_on}) { - $manually_color_lines = 1; - $color_forced = 1; -} elsif ($args->{color_off}) { - $manually_color_lines = 0; - $color_forced = 1; -} - -if ($args->{debug}) { - show_debug_info(); - exit(); -} - -# `git add --patch` requires our output to match the number of lines from the -# input. So, when patch mode is active, we print out empty lines to pad our -# output to match any lines we've consumed. -if ($args->{patch}) { - $patch_mode = 1; -} - -# We only process ARGV if we don't have STDIN -if (!$has_stdin) { - if ($args->{v} || $args->{version}) { - die(version()); - } elsif ($args->{'set-defaults'}) { - my $ok = set_defaults(); - exit; - } elsif ($args->{colors}) { - # We print this to STDOUT so we can redirect to bash to auto-set the colors - print get_default_colors(); - exit; - } elsif (!%$args || $args->{help} || $args->{h}) { - my $first = check_first_run(); - - if (!$first) { - die(usage()); - } - } else { - die("Missing input on STDIN\n"); - } -} - -################################################################################# -################################################################################# - -# Check to see if were using default settings -check_first_run(); - -# The logic here is that we run all the lines through DiffHighlight first. This -# highlights all the intra-word changes. Then we take those lines and send them -# to do_dsf_stuff() to convert the diff to human readable d-s-f output and add -# appropriate fanciness - -my @lines; -local $DiffHighlight::line_cb = sub { - push(@lines,@_); - - my $last_line = $lines[-1]; - - # Buffer X lines before we try and output anything - # Also make sure we're sending enough data to d-s-f to do it's magic. - # Certain things require a look-ahead line or two to function so - # we make sure we don't break on those sections prematurely - if (@lines > 24 && ($last_line !~ /^${ansi_color_regex}(---|index|old mode|similarity index|rename (from|to))/)) { - do_dsf_stuff(\@lines); - @lines = (); - } -}; - -my $line_count = 0; -while (my $line = ) { - # If the very first line of the diff doesn't start with ANSI color we're assuming - # it's a raw patch file, and we have to color the added/removed lines ourself - if (!$color_forced && $line_count == 0 && starts_with_ansi($line)) { - $manually_color_lines = 1; - } - - my $ok = DiffHighlight::handle_line($line); - $line_count++; -} - -# If we're mid hunk above process anything still pending -DiffHighlight::flush(); -do_dsf_stuff(\@lines); - -################################################################################# -################################################################################# - -sub do_dsf_stuff { - my $input = shift(); - - #print STDERR "START -------------------------------------------------\n"; - #print STDERR join("",@$input); - #print STDERR "END ---------------------------------------------------\n"; - - while (my $line = shift(@$input)) { - ###################################################### - # Pre-process the line before we do any other markup # - ###################################################### - - # If the first line of the input is a blank line, skip that - if ($i == 0 && $line =~ /^\s*$/) { - next; - } - - ###################### - # End pre-processing # - ###################### - - ####################################################################### - - #################################################################### - # Look for git index and replace it horizontal line (header later) # - #################################################################### - if ($line =~ /^${ansi_color_regex}index /) { - # Print the line color and then the actual line - $meta_color = $1 || get_config_color("meta"); - - # Get the next line without incrementing counter while loop - my $next = $input->[0] || ""; - my ($file_1,$file_2); - - # The line immediately after the "index" line should be the --- file line - # If it's not it's an empty file add/delete - if ($next !~ /^$ansi_color_regex(---|Binary files)/) { - - # We fake out the file names since it's a raw add/delete - if ($last_file_mode eq "add") { - $file_1 = "/dev/null"; - $file_2 = $last_file_seen; - } elsif ($last_file_mode eq "delete") { - $file_1 = $last_file_seen; - $file_2 = "/dev/null"; - } - } - - if ($file_1 && $file_2) { - print horizontal_rule($meta_color); - print $meta_color . file_change_string($file_1,$file_2) . "\n"; - print horizontal_rule($meta_color); - } - ######################### - # Look for the filename # - ######################### - # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { - - # Mercurial looks like: diff -r 82e55d328c8c hello.c - if ($4 eq "-r") { - $is_mercurial = 1; - $meta_color = get_config_color("meta"); - # Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy - } else { - $last_file_seen = $5; - } - - $last_file_seen =~ s|^\w/||; # Remove a/ (and handle diff.mnemonicPrefix). - $in_hunk = 0; - if ($patch_mode) { - # we are consuming one line, and the debt must be paid - print "\n"; - } - ######################################## - # Find the first file: --- a/README.md # - ######################################## - } elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) { - $meta_color = get_config_color("meta"); - - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_1 = $file_dir . $5; - } else { - $file_1 = $5; - } - - # Find the second file on the next line: +++ b/README.md - my $next = shift(@$input); - $next =~ /^$ansi_color_regex\+\+\+ (\w\/)?(.+?)(\e|\t|$)/; - if ($1) { - print $1; # Print out whatever color we're using - } - if ($git_strip_prefix) { - my $file_dir = $4 || ""; - $file_2 = $file_dir . $5; - } else { - $file_2 = $5; - } - - if ($file_2 ne "/dev/null") { - $last_file_seen = $file_2; - } - - # Print out the top horizontal line of the header - print $reset_color; - print horizontal_rule($meta_color); - - # Mercurial coloring is slightly different so we need to hard reset colors - if ($is_mercurial) { - print $reset_color; - } - - print $meta_color; - print file_change_string($file_1,$file_2) . "\n"; - - # Print out the bottom horizontal line of the header - print horizontal_rule($meta_color); - ######################################## - # Check for "@@ -3,41 +3,63 @@" syntax # - ######################################## - } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - - print $line; - } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; - - my $hunk_header = $4; - my $remain = bleach_text($5); - - # The number of colums to remove (1 or 2) is based on how many commas in the hunk header - $columns_to_remove = (char_count(",",$hunk_header)) - 1; - # On single line removes there is NO comma in the hunk so we force one - if ($columns_to_remove <= 0) { - $columns_to_remove = 1; - } - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($orig_offset, $orig_count, $new_offset, $new_count) = parse_hunk_header($hunk_header); - #$last_file_seen = basename($last_file_seen); - - # Figure out the start line - my $start_line = start_line_calc($new_offset,$new_count); - - # Last function has it's own color - my $last_function_color = ""; - if ($remain) { - $last_function_color = get_config_color("last_function"); - } - - # Check to see if we have the color for the fragment from git - if ($5 =~ /\e\[\d/) { - #print "Has ANSI color for fragment\n"; - } else { - # We don't have the ANSI sequence so we shell out to get it - #print "No ANSI color for fragment\n"; - my $frag_color = get_config_color("fragment"); - print $frag_color; - } - - print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; - ################################### - # Remove any new file permissions # - ################################### - } elsif ($remove_file_add_header && $line =~ /^${ansi_color_regex}.*new file mode/) { - # Don't print the line (i.e. remove it from the output); - $last_file_mode = "add"; - if ($patch_mode) { - print "\n"; - } - ###################################### - # Remove any delete file permissions # - ###################################### - } elsif ($remove_file_delete_header && $line =~ /^${ansi_color_regex}deleted file mode/) { - # Don't print the line (i.e. remove it from the output); - $last_file_mode = "delete"; - if ($patch_mode) { - print "\n"; - } - ################################ - # Look for binary file changes # - ################################ - } elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) { - my $change = file_change_string($2,$4); - print horizontal_rule($meta_color); - print "$meta_color$change (binary)\n"; - print horizontal_rule($meta_color); - ##################################################### - # Check if we're changing the permissions of a file # - ##################################################### - } elsif ($clean_permission_changes && $line =~ /^${ansi_color_regex}old mode (\d+)/) { - my ($old_mode) = $4; - my $next = shift(@$input); - - if ($1) { - print $1; # Print out whatever color we're using - } - - my ($new_mode) = $next =~ m/new mode (\d+)/; - - if ($patch_mode) { - print "\n"; - } - print "$last_file_seen changed file mode from $old_mode to $new_mode\n"; - - ############### - # File rename # - ############### - } elsif ($line =~ /^${ansi_color_regex}similarity index (\d+)%/) { - my $simil = $4; - - # If it's a move with content change we ignore this and the next two lines - if ($simil != 100) { - shift(@$input); - shift(@$input); - next; - } - - my $next = shift(@$input); - my ($file1) = $next =~ /rename from (.+?)(\e|\t|$)/; - - $next = shift(@$input); - my ($file2) = $next =~ /rename to (.+?)(\e|\t|$)/; - - if ($file1 && $file2) { - # We may not have extracted this yet, so we pull from the config if not - $meta_color = get_config_color("meta"); - - my $change = file_change_string($file1,$file2); - - print horizontal_rule($meta_color); - print $meta_color . $change . "\n"; - print horizontal_rule($meta_color); - } - - $i += 3; # We've consumed three lines - next; - ##################################### - # Just a regular line, print it out # - ##################################### - } else { - # Mark empty line with a red/green box indicating addition/removal - if ($mark_empty_lines) { - $line = mark_empty_line($line); - } - - # Remove the correct number of leading " " or "+" or "-" - if ($strip_leading_indicators) { - $line = strip_leading_indicators($line,$columns_to_remove); - } - print $line; - } - - $i++; - } -} - -###################################################################################################### -# End regular code, begin functions -###################################################################################################### - -# Courtesy of github.com/git/git/blob/ab5d01a/git-add--interactive.perl#L798-L805 -sub parse_hunk_header { - my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = $line =~ /^\@\@+(?: -(\d+)(?:,(\d+))?)+ \+(\d+)(?:,(\d+))? \@\@+/; - $o_cnt = 1 unless defined $o_cnt; - $n_cnt = 1 unless defined $n_cnt; - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); -} - -# Mark the first char of an empty line -sub mark_empty_line { - my $line = shift(); - - my $reset_color = "\e\\[0?m"; - my $reset_escape = "\e\[m"; - my $invert_color = "\e\[7m"; - my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; - my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; - - # This captures lines that do not have any ANSI in them (raw vanilla diff) - if ($line eq "+\n") { - $line = $invert_color . $add_color . " " . color('reset') . "\n"; - # This captures lines that do not have any ANSI in them (raw vanilla diff) - } elsif ($line eq "-\n") { - $line = $invert_color . $del_color . " " . color('reset') . "\n"; - # This handles everything else - } else { - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; - } - - return $line; -} - -# String to boolean -sub boolean { - my $str = shift(); - $str = trim($str); - - if ($str eq "" || $str =~ /^(no|false|0)$/i) { - return 0; - } else { - return 1; - } -} - -# Get the git config -sub git_config_raw { - my $cmd = "git config --list"; - my @out = `$cmd`; - - return \@out; -} - -# Memoize fetching a textual item from the git config -sub git_config { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || ""); - - state $raw = {}; - if (%$raw && $search_key) { - return $raw->{$search_key} || $default_value; - } - - if ($args->{debug}) { - print "Parsing git config\n"; - } - - my $out = git_config_raw(); - - foreach my $line (@$out) { - if ($line =~ /=/) { - my ($key,$value) = split("=",$line,2); - $value =~ s/\s+$//; - $raw->{$key} = $value; - } - } - - # If we're given a search key return that, else return the hash - if ($search_key) { - return $raw->{$search_key} || $default_value; - } else { - return $raw; - } -} - -# Fetch a boolean item from the git config -sub git_config_boolean { - my $search_key = lc($_[0] || ""); - my $default_value = lc($_[1] || 0); # Default to false - - # If we're in a unit test, use the default (don't read the users config) - if (in_unit_test()) { - return boolean($default_value); - } - - my $result = git_config($search_key,$default_value); - my $ret = boolean($result); - - return $ret; -} - -# Check if we're inside of BATS -sub in_unit_test { - if ($ENV{BATS_CWD}) { - return 1; - } else { - return 0; - } -} - -sub get_less_charset { - my @less_char_vars = ("LESSCHARSET", "LESSCHARDEF", "LC_ALL", "LC_CTYPE", "LANG"); - foreach my $key (@less_char_vars) { - my $val = $ENV{$key}; - - if (defined $val) { - return ($key, $val); - } - } - - return (); -} - -sub should_print_unicode { - if (-t STDOUT) { - # Always print unicode chars if we're not piping stuff, e.g. to less(1) - return 1; - } - - # Otherwise, assume we're piping to less(1) - my ($less_env_var, $less_charset) = get_less_charset(); - if ($less_charset && $less_charset =~ /utf-?8/i) { - return 1; - } - - return 0; -} - -# Try and be smart about what line the diff hunk starts on -sub start_line_calc { - my ($line_num,$diff_context) = @_; - my $ret; - - if ($line_num == 0 && $diff_context == 0) { - return 1; - } - - # Git defaults to three lines of context - my $default_context_lines = 3; - # Three lines on either side, and the line itself = 7 - my $expected_context = ($default_context_lines * 2 + 1); - - # The first three lines - if ($line_num == 1 && $diff_context < $expected_context) { - $ret = $diff_context - $default_context_lines; - } else { - $ret = $line_num + $default_context_lines; - } - - if ($ret < 1) { - $ret = 1; - } - - return $ret; -} - -# Remove + or - at the beginning of the lines -sub strip_leading_indicators { - my $line = shift(); # Array passed in by reference - my $columns_to_remove = shift(); # Don't remove any lines by default - - if ($columns_to_remove == 0) { - return $line; # Nothing to do - } - - $line =~ s/^(${ansi_color_regex})([ +-]){${columns_to_remove}}/$1/; - - if ($manually_color_lines) { - if (defined($5) && $5 eq "+") { - my $add_line_color = get_config_color("add_line"); - $line = $add_line_color . insert_reset_at_line_end($line); - } elsif (defined($5) && $5 eq "-") { - my $remove_line_color = get_config_color("remove_line"); - $line = $remove_line_color . insert_reset_at_line_end($line); - } - } - - return $line; -} - -# Insert the color reset code at end of line, but before any newlines -sub insert_reset_at_line_end { - my $line = shift(); - $line =~ s/^(.*)([\n\r]+)?$/${1}${reset_color}${2}/; - return $line; -} - -# Count the number of a given char in a string -# https://www.perturb.org/display/1010_Perl_Count_occurrences_of_substring.html -sub char_count { - my ($needle, $haystack) = @_; - - my $count = () = ($haystack =~ /$needle/g); - - return $count; -} - -# Remove all ANSI codes from a string -sub bleach_text { - my $str = shift(); - $str =~ s/\e\[\d*(;\d+)*m//mg; - - return $str; -} - -# Remove all trailing and leading spaces -sub trim { - my $s = shift(); - if (!$s) { return ""; } - - $s =~ s/^\s*//u; - $s =~ s/\s*$//u; - - return $s; -} - -# Print a line of em-dash or line-drawing chars the full width of the screen -sub horizontal_rule { - my $color = $_[0] || ""; - my $width = get_terminal_width(); - - # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm - #my $dash = "\x{2014}"; - # BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm - my $dash; - if ($use_unicode_dash_for_ruler && should_print_unicode()) { - #$dash = Encode::encode('UTF-8', "\x{2500}"); - $dash = "\xE2\x94\x80"; - } else { - $dash = "-"; - } - - # Draw the line - my $ret = $color . ($dash x $width) . "$reset_color\n"; - - return $ret; -} - -sub file_change_string { - my $file_1 = shift(); - my $file_2 = shift(); - - # If they're the same it's a modify - if ($file_1 eq $file_2) { - return "modified: $file_1"; - # If the first is /dev/null it's a new file - } elsif ($file_1 eq "/dev/null") { - my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; - return "added: $add_color$file_2$reset_color"; - # If the second is /dev/null it's a deletion - } elsif ($file_2 eq "/dev/null") { - my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; - return "deleted: $del_color$file_1$reset_color"; - # If the files aren't the same it's a rename - } elsif ($file_1 ne $file_2) { - my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1}); - # highlight_pair already includes reset_color, but adds newline characters that need to be trimmed off - $old = trim($old); - $new = trim($new); - return "renamed: $old$meta_color to $new" - # Something we haven't thought of yet - } else { - return "$file_1 -> $file_2"; - } -} - -# Check to see if STDIN is connected to an interactive terminal -sub has_stdin { - my $i = -t STDIN; - my $ret = int(!$i); - - return $ret; -} - -# We use this instead of Getopt::Long because it's faster and we're not parsing any -# crazy arguments -# Borrowed from: https://www.perturb.org/display/1153_Perl_Quick_extract_variables_from_ARGV.html -sub argv { - my $ret = {}; - - for (my $i = 0; $i < scalar(@ARGV); $i++) { - - # If the item starts with "-" it's a key - if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_-]*\w)$/) && ($ARGV[$i] !~ /^-\w\w/)) { - # If the next item does not start with "--" it's the value for this item - if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) { - $ret->{$key} = $ARGV[$i + 1]; - # Bareword like --verbose with no options - } else { - $ret->{$key}++; - } - } - } - - # We're looking for a certain item - if ($_[0]) { return $ret->{$_[0]}; } - - return $ret; -} - -# Output the command line usage for d-s-f -sub usage { - my $out = color("white_bold") . version() . color("reset") . "\n"; - - $out .= "Usage: - -git diff --color | diff-so-fancy # Use d-s-f on one diff -cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file -diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output - -diff-so-fancy --colors # View the commands to set the recommended colors -diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors -diff-so-fancy --patch # Use diff-so-fancy in patch mode (interoperable with `git add --patch`) - -# Configure git to use d-s-f for *all* diff operations -git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\" - -# Configure git to use d-s-f for `git add --patch` -git config --global interactive.diffFilter \"diff-so-fancy --patch\"\n"; - - return $out; -} - -sub get_default_colors { - my $out = "# Recommended default colors for diff-so-fancy\n"; - $out .= "# --------------------------------------------\n"; - $out .= 'git config --global color.ui true - -git config --global color.diff-highlight.oldNormal "red bold" -git config --global color.diff-highlight.oldHighlight "red bold 52" -git config --global color.diff-highlight.newNormal "green bold" -git config --global color.diff-highlight.newHighlight "green bold 22" - -git config --global color.diff.meta "yellow" -git config --global color.diff.frag "magenta bold" -git config --global color.diff.commit "yellow bold" -git config --global color.diff.old "red bold" -git config --global color.diff.new "green bold" -git config --global color.diff.whitespace "red reverse" -'; - - return $out; -} - -# Output the current version string -sub version { - my $ret = "Diff-so-fancy: https://github.com/so-fancy/diff-so-fancy\n"; - $ret .= "Version : $VERSION\n"; - - return $ret; -} - -sub is_windows { - if ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2' or $^O eq 'cygwin' or $^O eq 'msys') { - return 1; - } else { - return 0; - } -} - -# Return value is whether this is the first time they've run d-s-f -sub check_first_run { - my $ret = 0; - - # If first-run is not set, or it's set to "true" - my $first_run = git_config_boolean('diff-so-fancy.first-run'); - # See if they're previously set SOME diff-highlight colors - my $has_dh_colors = git_config_boolean('color.diff-highlight.oldnormal') || git_config_boolean('color.diff-highlight.newnormal'); - - #$first_run = 1; $has_dh_colors = 0; - - if (!$first_run || $has_dh_colors) { - return 0; - } else { - print "This appears to be the first time you've run diff-so-fancy, please note\n"; - print "that the default git colors are not ideal. Diff-so-fancy recommends the\n"; - print "following colors.\n\n"; - - print get_default_colors(); - - # Set the first run flag to false - my $cmd = 'git config --global diff-so-fancy.first-run false'; - system($cmd); - - exit; - } - - return 1; -} - -sub set_defaults { - my $color_config = get_default_colors(); - my $git_config = 'git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"'; - my $first_cmd = 'git config --global diff-so-fancy.first-run false'; - - my @cmds = split(/\n/,$color_config); - push(@cmds,$git_config); - push(@cmds,$first_cmd); - - # Remove all comments from the commands - foreach my $x (@cmds) { - $x =~ s/#.*//g; - } - - # Remove any empty commands - @cmds = grep($_,@cmds); - - foreach my $cmd (@cmds) { - system($cmd); - my $exit = ($? >> 8); - - if ($exit != 0) { - die("Error running: '$cmd' (error #18941)\n"); - } - } - - return 1; -} - -# Borrowed from: https://www.perturb.org/display/1167_Perl_ANSI_colors.html -# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' -sub color { - my $str = shift(); - - # No string sent in, so we just reset - if (!length($str) || $str eq 'reset') { return "\e[0m"; } - - # Some predefined colors - my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0); - $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; - - # Get foreground/background and any commands - my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g; - my ($bc) = $str =~ /on_?(\d+)/g; - - # Some predefined commands - my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); - my $cmd_num = $cmd_map{$cmd // 0}; - - my $ret = ''; - if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } - if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } - if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } - - return $ret; -} - -# Get colors used for various output sections (memoized) -{ - my $static_config; - - sub get_config_color { - my $str = shift(); - - my $ret = ""; - if ($static_config->{$str}) { - return $static_config->{$str}; - } - - #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); - - if ($str eq "meta") { - # Default ANSI yellow - $ret = git_ansi_color(git_config('color.diff.meta')) || color(11); - } elsif ($str eq "reset") { - $ret = color("reset"); - } elsif ($str eq "add_line") { - # Default ANSI green - $ret = git_ansi_color(git_config('color.diff.new')) || color("2_bold"); - } elsif ($str eq "remove_line") { - # Default ANSI red - $ret = git_ansi_color(git_config('color.diff.old')) || color("1_bold"); - } elsif ($str eq "fragment") { - $ret = git_ansi_color(git_config('color.diff.frag')) || color("13_bold"); - } elsif ($str eq "last_function") { - $ret = git_ansi_color(git_config('color.diff.func')) || color("146_bold"); - } - - # Cache (memoize) the entry for later - $static_config->{$str} = $ret; - - return $ret; - } -} - -# https://www.git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_colors_in_git -sub git_ansi_color { - my $str = shift(); - my @parts = split(' ', $str); - - if (!@parts) { - return ''; - } - my $colors = { - 'black' => 0, - 'red' => 1, - 'green' => 2, - 'yellow' => 3, - 'blue' => 4, - 'magenta' => 5, - 'cyan' => 6, - 'white' => 7, - }; - - my @ansi_part = (); - - if (grep { /bold/ } @parts) { - push(@ansi_part, "1"); - @parts = grep { !/bold/ } @parts; # Remove from array - } - - if (grep { /reverse/ } @parts) { - push(@ansi_part, "7"); - @parts = grep { !/reverse/ } @parts; # Remove from array - } - - my $fg = $parts[0] // ""; - my $bg = $parts[1] // ""; - - ############################################# - - # It's an numeric value, so it's an 8 bit color - if (is_numeric($fg)) { - if ($fg < 8) { - push(@ansi_part, $fg + 30); - } elsif ($fg < 16) { - push(@ansi_part, $fg + 82); - } else { - push(@ansi_part, "38;5;$fg"); - } - # It's a simple 16 color OG ansi - } elsif ($fg) { - my $bright = $fg =~ s/bright//; - my $color_num = $colors->{$fg} + 30; - - if ($bright) { $color_num += 60; } # Set bold - - push(@ansi_part, $color_num); - } - - ############################################# - - # It's an numeric value, so it's an 8 bit color - if (is_numeric($bg)) { - if ($bg < 8) { - push(@ansi_part, $bg + 40); - } elsif ($bg < 16) { - push(@ansi_part, $bg + 92); - } else { - push(@ansi_part, "48;5;$bg"); - } - # It's a simple 16 color OG ansi - } elsif ($bg) { - my $bright = $bg =~ s/bright//; - my $color_num = $colors->{$bg} + 40; - - if ($bright) { $color_num += 60; } # Set bold - - push(@ansi_part, $color_num); - } - - ############################################# - - my $ansi_str = join(";", @ansi_part); - my $ret = "\e[" . $ansi_str . "m"; - - return $ret; -} - -sub is_numeric { - my $s = shift(); - - if ($s =~ /^\d+$/) { - return 1; - } - - return 0; -} - -sub starts_with_ansi { - my $str = shift(); - - if ($str =~ /^$ansi_color_regex/) { - return 1; - } else { - return 0; - } -} - -sub get_terminal_width { - # Make width static so we only calculate it once - state $width; - - if ($width) { - return $width; - } - - # If there is a ruler width in the config we use that - if ($ruler_width) { - $width = $ruler_width; - # Otherwise we check the terminal width using tput - } else { - my $tput = `tput cols`; - - if ($tput) { - $width = int($tput); - - if (is_windows()) { - $width--; - } - } else { - print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; - $width = 80; - } - } - - return $width; -} - -sub show_debug_info { - my @less = get_less_charset(); - my $git_ver = trim(`git --version`); - $git_ver =~ s/[^\d.]//g; - - print "Diff-so-fancy : v$VERSION\n"; - print "Git : v$git_ver\n"; - print "Perl : $^V\n"; - print "\n"; - - print "Terminal width : " . get_terminal_width() . "\n"; - print "Terminal \$LANG : " . ($ENV{LANG} || "") . "\n"; - print "\n"; - print "Supports Unicode: " . yes_no(should_print_unicode()) . "\n"; - print "Unicode Ruler : " . yes_no($use_unicode_dash_for_ruler) . "\n"; - print "\n"; - print "Less Charset Var: " . ($less[0] // "") . "\n"; - print "Less Charset : " . ($less[1] // "") . "\n"; - print "\n"; - print "Is Windows : " . yes_no(is_windows()) . "\n"; - print "Operating System: $^O\n"; -} - -sub yes_no { - my $val = shift(); - - if ($val) { - return "Yes"; - } else { - return "No"; - } -} - -# If there are colors set in the gitconfig use those, otherwise leave the defaults -sub init_diff_highlight_colors { - $DiffHighlight::NEW_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.newnormal')) || $DiffHighlight::NEW_HIGHLIGHT[0]; - $DiffHighlight::NEW_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.newhighlight')) || $DiffHighlight::NEW_HIGHLIGHT[1]; - - $DiffHighlight::OLD_HIGHLIGHT[0] = git_ansi_color(git_config('color.diff-highlight.oldnormal')) || $DiffHighlight::OLD_HIGHLIGHT[0]; - $DiffHighlight::OLD_HIGHLIGHT[1] = git_ansi_color(git_config('color.diff-highlight.oldhighlight')) || $DiffHighlight::OLD_HIGHLIGHT[1]; -} - -sub debug_log { - my $log_line = shift(); - my $file = "/tmp/diff-so-fancy.debug.log"; - - state $fh; - if (!$fh) { - printf("%sDebug log enabled:%s $file\n", color('orange'), color()); - open ($fh, ">", $file) or die("Cannot write to $file"); - } - - print $fh trim($log_line) . "\n"; - - return 1; -} - -# Enable k() and kd() if there is a DSF_DEBUG environment variable -BEGIN { - if ($ENV{"DSF_DEBUG"}) { - require Data::Dump::Color; - *k = sub { Data::Dump::Color::dd(@_) }; - *kd = sub { - k(@_); - - printf("Died at %2\$s line #%3\$s\n",caller()); - exit(15); - } - } else { - *k = sub {}; - *kd = sub {}; - } -} - -# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4 From c9df4a5a370eb0df63f77df19399718ec0024b94 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 9 Jun 2023 11:05:43 -0700 Subject: [PATCH 443/443] Code alignment for readability --- diff-so-fancy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/diff-so-fancy b/diff-so-fancy index 34c9753..31d71e5 100755 --- a/diff-so-fancy +++ b/diff-so-fancy @@ -937,8 +937,8 @@ sub git_ansi_color { my $fg = $parts[0] // ""; my $bg = $parts[1] // ""; - set_ansi_color( $fg, 0, \@ansi_part, $colors ) if $fg; - set_ansi_color( $bg, 10, \@ansi_part, $colors ) if $bg; + set_ansi_color($fg, 0 , \@ansi_part, $colors) if $fg; + set_ansi_color($bg, 10, \@ansi_part, $colors) if $bg; ############################################# @@ -950,9 +950,10 @@ sub git_ansi_color { sub set_ansi_color { my ($color, $increment, $ansi_part, $colors) = @_; - my $base_code = 30 + $increment; + + my $base_code = 30 + $increment; my $base8_code = 38 + $increment; - my $ext_code = 82 + $increment; + my $ext_code = 82 + $increment; if (is_numeric($color)) { if ($color < 8) {