Skip to content

Instantly share code, notes, and snippets.

@MaxymVlasov
Last active October 2, 2018 09:52
Show Gist options
  • Save MaxymVlasov/ce6d2b0cfc9aee7e9f09671ef050f7cd to your computer and use it in GitHub Desktop.
Save MaxymVlasov/ce6d2b0cfc9aee7e9f09671ef050f7cd to your computer and use it in GitHub Desktop.

Revisions

  1. MaxymVlasov revised this gist Oct 2, 2018. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions .gitconfig
    Original file line number Diff line number Diff line change
    @@ -21,15 +21,15 @@
    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[%cn]%Creset\\ %s" --since=1.weeks
    ll = log --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%Creset\\ %s"
    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[%cn]%Creset\\ %s"
    lga = log --graph --all --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%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[%cn]\\%Creset%s" --decorate --numstat
    lss = log -p --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%Creset\\ %s"
    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")
  2. MaxymVlasov revised this gist Oct 1, 2018. 2 changed files with 1053 additions and 0 deletions.
    1,010 changes: 1,010 additions & 0 deletions diff-so-fancy
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1010 @@
    #!/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
    43 changes: 43 additions & 0 deletions git-count
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,43 @@
    #!/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
  3. MaxymVlasov created this gist Oct 1, 2018.
    55 changes: 55 additions & 0 deletions .gitconfig
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    [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[%cn]%Creset\\ %s" --since=1.weeks
    ll = log --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%Creset\\ %s"

    lg = log --graph --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%Creset\\ %s"
    lga = log --graph --all --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%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[%cn]\\%Creset%s" --decorate --numstat
    lss = log -p --pretty=format:"%C(yellow)[%h]%Creset\\%Cred[%ar]%Creset\\%Cblue[%cn]%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