1

I found an good shell function diff-lines() from Using git diff, how can I get added and modified lines numbers?

I'v add the function in my .bashrc file, and it works in my commandline:

[marslo@mppdev ~/Tools/Git/LinuxStuff]
$ git diff -U0 | diff-lines
Scripts/.marslorc:29:-# Inspired from https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers
Scripts/.marslorc:29:+# Inspired from https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers/12179492#12179492

However, when I tried to add the command as a git alias, here something wrong:

[marslo@mppdev ~/Tools/Git/LinuxStuff]
$ cat ~/.gitconfig | grep "ldiff ="
    ldiff = "!bash -c 'git diff -U0' | diff-lines"
[marslo@mppdev ~/Tools/Git/LinuxStuff]
$ git ldiff
sh: diff-lines: command not found
fatal: Failed to run 'bash -c 'git diff -U0' | diff-lines' when expanding alias 'ldiff'

And, bash -c 'git diff -U0' | diff-lines still works

[marslo@mppdev ~/Tools/Git/LinuxStuff]
$ bash -c 'git diff -U0' | diff-lines
Scripts/.marslorc:29:-# Inspired from https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers
Scripts/.marslorc:29:+# Inspired from https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers/12179492#12179492

Here the details: ldff

Community
  • 1
  • 1
Marslo
  • 2,994
  • 4
  • 26
  • 35

2 Answers2

2

The problem is that diff-lines is a shell function and not an actual executable. When you run "!bash -c 'git diff -U0' | diff-lines" you get an error because the shell is not sourcing your ~/.bashrc and so it does not know about diff-lines. This is normal behavior for shells--they only source those settings in specific circumstances, and running a command is not one of them.

So here are a few recommendations. First, if the line number feature is nice outside of git, consider making diff-lines a script instead of just a shell function:

#!/bin/bash

diff-lines() {
    local path=
    local line=
    while read; do
        esc=$'\033'
        if [[ $REPLY =~ ---\ (a/)?.* ]]; then
            continue
        elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
            path=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
            line=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
            echo "$path:$line:$REPLY"
            if [[ ${BASH_REMATCH[2]} != - ]]; then
                ((line++))
            fi
        fi
    done
}

diff-lines

You can then set your alias to be:

ldiff = !sh -c 'git diff "$@" | diff-lines' -

This will also allow you to pass parameters to git ldiff, just like the real diff command. You could also use diff-lines as your pager by doing the following in your ~/.gitconfig:

[pager]
    diff = diff-lines | less

Then, the regular git diff command will be piped through your diff-lines script and finally through less to get the paging. I use that same trick to highlight words changes in lines.

Another option is what Adam mentioned: create a script called git-ldiff that runs your diff command and pipes it through diff-lines

#!/bin/bash

diff-lines() {
    local path=
    local line=
    while read; do
        esc=$'\033'
        if [[ $REPLY =~ ---\ (a/)?.* ]]; then
            continue
        elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
            path=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
            line=${BASH_REMATCH[2]}
        elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
            echo "$path:$line:$REPLY"
            if [[ ${BASH_REMATCH[2]} != - ]]; then
                ((line++))
            fi
        fi
    done
}

git diff "$@" | diff-lines

Note: this is exactly the same script as above with a small modification to the last line.

John Szakmeister
  • 44,691
  • 9
  • 89
  • 79
  • Hi @jszkmeister, diff-lines cannot be set at the end of `.bashrc`. Otherwise, the bash always waiting for read when I open a terminal. – Marslo Nov 07 '13 at 17:53
  • Hi, I tired all your method, they all cannot work except create a new script and add the location to $PATH. Is there any way to alias without create a new file? Thanks – Marslo Nov 07 '13 at 18:00
  • @MarsloJiao As I said in the post, you would need to make diff-lines a script--that's the best way of doing this. There are other choices, but I'm consider them subpar for various reasons. You could, for example, source your .bashrc manually or force a login shell. The first choice would look something like this: `ldiff = !bash -c '. ~/.bashrc && git diff "$@" | diff-lines' -`. You could also do: `ldiff = !bash -l -c 'git diff "$@" | diff-lines' -` to make it a login shell (which will read your `~/.bashrc`). The best option is to create a script though. – John Szakmeister Nov 07 '13 at 21:06
  • Oh yeah!!!! It works!! The alias is exactlly `ldiff = !bash -c '. ~/.bashrc && git diff "$@" | diff-lines' -`. Thanks a lot! – Marslo Nov 08 '13 at 09:58
  • However, I have a question, .bashrc will be executed while I login. And it actually works (I can use diff-lines in command line). Why this file needs be source again? – Marslo Nov 08 '13 at 10:03
  • Okay, I think I know what's wrong. Thanks for your patience. Your answers helped me pretty much!! – Marslo Nov 08 '13 at 10:25
  • And, actually, much great outside of git can be using now. :). Here is `git info` https://gist.github.com/subtleGradient/6731, the script has been added into `.bashrc`, my alias just looks like `info = !bash -l -c 'gitinfo'`. Pretty cool!! – Marslo Nov 08 '13 at 10:47
  • @MarsloJiao You're welcome. Just be careful of what you put in your `.bashrc`. If you start interacting with the screen or user, you'll break every command that does this trick. That's why it's not a good idea. You could stash all the git-related shell functions into a `~/.gitrc` and source it from your `.bashrc` and from your aliases. That would at least protect you from such an issue. Glad you've got things working. :-) – John Szakmeister Nov 08 '13 at 11:00
  • HAHAHA, that's exactlly what I've done. I create a file named `.marslorc`, and sourced it in `.bashrc`(much bashrcs using by me). The separate script is good for maintenance. The purpose of *scripts added in `.bashrc`* just for quick understanding, :). [This](https://github.com/Marslo/LinuxStuff/blob/master/HOME/.marslo/.marslorc) is **.marslorc**, and [This](https://github.com/Marslo/LinuxStuff/tree/master/HOME/.marslo) is **.bashrcs**; [This](https://github.com/Marslo/LinuxStuff/blob/master/HOME/Git/.gitconfig) is **.gitconfig** – Marslo Nov 09 '13 at 10:49
  • Hi @jszakmister, bad news, git aliases cannot work on Ubuntu. Error message: `fatal: While expanding alias 'ldiff': 'bash -l -c 'git diff -U0 $@ | diff-lines' -': No such file or directory` – Marslo Nov 09 '13 at 19:49
  • Is bash installed? Ubuntu has switched to using dash by default, and you'll want to be using bash. – John Szakmeister Nov 09 '13 at 23:01
  • Oh yes, it's dash `lrwxrwxrwx 1 root root 4 Oct 19 00:49 /bin/sh -> dash*`. I changed the alias as `ldiff = !bash -c '. ~/.marslo/.marslorc && git diff -U0 "$@" | diff-lines' -`, and it works. Thansk! – Marslo Nov 10 '13 at 05:55
1

Instead of using an alias:

  1. Put your code into a script,
  2. Name the script git-ldiff,
  3. Use chmod +x script to make it executable, and
  4. Put the script in a directory that's in your PATH.

Now, when you type git ldiff, git will find and run your scipt.

Adam Liss
  • 47,594
  • 12
  • 108
  • 150
  • Hi @Adam, thanks for your comment. However, I don't want create scripts file alone (even if I knew this way works). I have much function in my `.bashrc`, it's not convenient to manager them. And my PATH will be pretty long.. – Marslo Nov 07 '13 at 17:35