Last active
October 2, 2018 09:52
-
-
Save MaxymVlasov/ce6d2b0cfc9aee7e9f09671ef050f7cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user] | |
name = MaxymVlasov | |
email = [email protected] | |
signingkey = D17008C3CDCF957F | |
[gpg] | |
program = /usr/bin/gpg | |
[core] | |
editor = "vim" | |
[commit] | |
gpgsign = true | |
[http] | |
cookiefile = /home/vm/.gitcookies | |
[status] | |
submoduleSummary = true | |
[diff] | |
submodule = log | |
[pull] | |
rebase = preserve | |
[alias] | |
b = branch -vv | |
bb = branch -vv --all | |
bbb = "!git for-each-ref --format='%(committerdate) %09 %(authorname) %09 %(refname:short)' | sort" | |
l = log --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%an]%Creset\\ %s" --since=1.weeks | |
ll = log --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%an]%Creset\\ %s" | |
lg = log --graph --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%an]%Creset\\ %s" | |
lga = log --graph --all --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%an]%Creset\\ %s" | |
s = "!f() { [ -z \"$GIT_PREFIX\" ] || cd \"$GIT_PREFIX\" && git diff --color \"$@\" | diff-so-fancy | less --tabs=4 -RFX; }; f" | |
ls = log --pretty=format:"%C(yellow)%h%Cred%d\\%Cblue[%an]\\%Creset%s" --decorate --numstat | |
lss = log -p --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%an]%Creset\\ %s" | |
st = stash | |
stsv = !git stash save $(date "+%F_%T") | |
stls = stash list | |
stpu = stash push | |
stpo = stash pop | |
stdr = stash drop | |
stm = "!branch=`git rev-parse --abbrev-ref HEAD -- | head -n 1` && git stash && git checkout master && git fetch --all && git rebase origin/master && git branch -D $branch && git stash pop" | |
count = git-count | |
stats = git-count | |
spull = "git_spull() { git pull \"$@\" && git submodule sync --recursive && git submodule update --init --recursive; }; __git_spull" | |
au = add --update | |
unstage = reset HEAD | |
la = "!git config -l | grep alias | cut -c 7-" | |
[push] | |
recurseSubmodules = on-demand | |
[diff-so-fancy] | |
markEmptyLines = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 (<STDIN>) { | |
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.1"; | |
################################################################################# | |
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); | |
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) = @_; | |
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 = <STDIN>) { | |
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 $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'); | |
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, 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 = <STDIN>; | |
$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 noexpandtab autoindent softtabstop=4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Count git commits or branches. | |
# Version: 0.1.1 | |
# | |
# Usage: | |
# | |
# git count {c|b|ca} | |
# | |
# Options: | |
# c Count commits | |
# b Count branches | |
# p Counting the total number of commits for each participant | |
# | |
# Original script was received thanks @ZDroid | |
# https://github.com/ZDroid/dotfiles/blob/master/git/count | |
case "$1" in | |
c) | |
git rev-list HEAD --count | |
;; | |
b) | |
git branch | wc -l | |
;; | |
p) | |
git shortlog -sn --no-merges | |
;; | |
*) | |
printf "\n" | |
printf "\e[1;33mgit-count\033[0m v0.1.1" | |
printf "\nCount git commits or branches.\n" | |
printf "\nUsage:" | |
printf "\ngit count {c|b|p}" | |
printf "\n" | |
printf "\nOptions:" | |
printf "\n c\tCount commits" | |
printf "\n b\tCount branches" | |
printf "\n p\tCounting the total number of commits for each participant" | |
printf "\n\n" | |
exit 1 | |
esac | |
# vim:ts=8:sw=2:sts=2:tw=80:et |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More can be found here: