Motivation
Like most people, I use git daily. For many years, I used to have these two aliases:
alias gdf="git diff"
alias gdfc="git diff --cached"
Last year I started working professionally as a developer and I began to work on many different repos at the same time.
Oftentimes I would do either a git diff
or git diff --cached
, then come back to it 10 minutes later but then forget whether the diff had a --cached
flag or not.
I needed to script some more git helpers functions!
Implementation
I created a new shell function called gdf
to replace the two aliases above.
It works by first showing you the git diff
output, then the git diff --cached
output.
For both outputs, a vertical colored “ribbon” is printed on the left margin to denote whether it’s the working tree (git diff
) or index (git diff --cached
aka “staging area”).
The name of the repo is prepended/appended to the output as well to further disambiguate it.
Here are the functions:
#!/usr/bin/env zsh
gdf()
{
not_git_repo && return
local c_blue="\x1b[1;34m"
local c_green="\x1b[1;32m"
local c_magenta="\x1b[1;35m"
local ce="\x1b[0m"
local git_repo=$(git_find_repo)
local tag=" ${c_blue}-- $git_repo --${ce}\n"
local git_diff=$(git diff --color=always)
local git_diff_c=$(git diff --cached --color=always)
if [[ -z "$git_diff" && -z "$git_diff_c" ]]; then
printf "${c_green}NO CHANGES!$ce\n\n"
gst
else
local msg=""
if [[ -n "$git_diff" ]]; then
# The sed '/^$/d' below is to remove the extra trailing whitespace
# line that seems to get added into `git diff' but not `git diff
# --cached'.
msg="$tag"
msg+=$(vertical_label \
"git diff --color=always | sed '/^$/d'" \
"TREE ------------------------ " \
"$c_green")
msg+="\n$tag"
printf $msg | less
fi
if [[ -n "$git_diff_c" ]]; then
msg="$tag"
msg+=$(vertical_label \
"git diff --cached --color=always" \
"INDEX ------------------------ " \
"$c_magenta")
msg+="\n$tag"
printf $msg | less
fi
gst
fi
}
gdf
[GitHub]
[Download]
#!/usr/bin/env zsh
vertical_label()
{
c=$3
ce="\x1b[0m"
label=$2
i=1
# The `s/^/x/' marks each line's beginning with a non-whitespace character
# `x' so that when we pipe it to the `read' zsh builtin, we read all leading
# indentation as well (otherwise we lose it). The `s/\t/ /g' standardizes
# all tab characters to four spaces; this is purely for visual aesthetics.
eval $1 | sed 's/^/x/ ; s/\t/ /g ; s/%/%%%%/g' | while read -r line; do
case $label[$i] in
" ") printf " " ;;
"-") printf " $c\u2503$ce" ;;
*) printf " $c$label[$i]$ce" ;;
esac
line_without_x=$line[2,-1]
printf " ${line_without_x//\\/\\\\\\\\}\n"
((i+=1))
if (( i > $#label )); then
i=1
fi
done
}
vertical_label
[GitHub]
[Download]
#!/usr/bin/env zsh
git_find_repo()
{
while [[ ! -d .git ]]; do
cd ..
if [[ $PWD == / ]]; then
echo "error: .git folder not found"
return
fi
done
echo ${PWD##*/}
}
git_find_repo
[GitHub]
[Download]
I use Zsh as my shell, so I wrote the above in Zsh. I simply drop these files inside my autoloaded directory, which is defined like this:
fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload -U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
Here is some sample output (used in the course of writing this blog post):
-- blog --
T diff --git a/post/2016-07-17-git-diff-improved.org b/post/2016-07-17-git-diff-improved.org
R index 631bcf4..9f6fbac 100644
E --- a/post/2016-07-17-git-diff-improved.org
E +++ b/post/2016-07-17-git-diff-improved.org
@@ -7,7 +7,7 @@ tags: programming
┃
┃ * Motivation
┃
┃ -Like most people, I use git every day.
┃ +Like most people, I use git daily.
┃ For many years, I used to have these two aliases:
┃
┃ #+begin_src shell
┃ @@ -15,9 +15,9 @@ alias gdf="git diff"
┃ alias gdfc="git diff --cached"
┃ #+end_src
┃
┃ -Last year, I started working professionally as a developer, and I began to work on many different repos at the same time.
┃ +Last year I started working professionally as a developer and I began to work on many different repos at the same time.
┃ Oftentimes I would do either a ~git diff~ or ~git diff --cached~, then come back to it 10 minutes later but then forget whether the diff had a ~--cached~ flag or not.
┃ -So, I needed to script some more git helpers functions!
┃ +I needed to script some more git helpers functions!
┃
┃ * Implementation
┃
┃ @@ -41,9 +41,11 @@ autoload -U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
┃ Here is some sample output:
┃
┃ #+begin_src diff
+ foo
T #+end_src
R
E #+begin_src diff
E +
+ bar
┃ #+end_src
┃
┃ * Conclusion -- blog --
-- blog --
I diff --git a/post/2016-07-17-git-diff-improved.org b/post/2016-07-17-git-diff-improved.org
N index dc2fca4..631bcf4 100644
D --- a/post/2016-07-17-git-diff-improved.org
E +++ b/post/2016-07-17-git-diff-improved.org
X @@ -38,6 +38,14 @@ fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload -U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
┃ #+end_src
┃
┃ +Here is some sample output:
┃ +
┃ +#+begin_src diff
┃ +#+end_src
┃ +
┃ +#+begin_src diff
┃ +#+end_src
┃ +
┃ * Conclusion
┃
┃ I've been a happy ~gdf~ user for some months now. -- blog --
Conclusion
I’ve been a happy gdf
user for some months now.
The only “downside” is that because of the vertical ribbon (and the repo name at the top/bottom), the output is no longer readable by patch
(or copy-pastable into a diff-reading utility/service).
But, this is a minor grievance at best as one can easily invoke the low-level git diff
or git diff --cached
directly to get the raw (patch
-able) output.
Happy hacking!