Other Posts

Git Alias -- Did You Git It?

coverimage

Git alias is powerful and yet simple to set up. I’d like to share my journey in configuring git alias for my personal use. Now let’s dig it and git it!

Day 1: Why git alias?

I was using Tig for some time. It’s good but not great. The problems I got during the time:

  1. It takes time to memorize the default key bindings. Since I’m using Colemak keyboard layout, obviously, the default navigation key bindings need to be adjusted accordingly. Therefore, it takes me more time to customize them.
  2. Tig doesn’t support customized diff formatter, like my current favorite delta. An open issue from 2016 gives me the fearful feeling that Tig wouldn’t officially support this feature in the foreseeable future.
  3. Switching between different views in Tig makes the workflow complex and slow. For example, to add an untracked file: run tig, press s to switch to status view, move up and down to select an untracked file, press u to add it. Well, in this case, why using Tig instead of simply typing git add <untracked_file>?

At some point, I decided to drop Tig and tried to search for other tools to assist my git workflow.

Don’t get me wrong. Tig is a decent tool. However, my needs are just picky and I’m just too lazy 🤷.

After trying this and that, surprisingly, I find out that git alias is one ideal solution:

  1. Git alias is supported by default, no need to install any 3rd-party dependencies.
  2. Git alias is relatively easy to customize. Knowing git command and put an alias for it, that’s all about.
  3. Git alias is illegally powerful once you know how to use ! to run the external command! Yes, its existence is a crime.

Day 2: start with [alias]

The official way to add git alias is to edit ~/.gitconfig file:

  • Open ~/.gitconfig
  • Add [alias] section
  • Put a bunch of aliases
  • Save and close file, done!

After this change, my alias setup looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[alias]
  a = add
  c = commit
  ca = commit --amend
  ci = commit -m
  cm = commit -m
  co = checkout
  di = diff
  p = push origin
  pl = pull
  rsthrd = reset --hard origin/master
  s = status

Those aliases cover all my basic needs of git workflow. For example, to add an untracked file, now I can type: git a <untracked_file>.

By using one letter alias for most commonly used git commands saves my time and gives me pleasures. Yeah, you know that typing fewer letters feels amazing!

Day 3: go external !

As you may know, fzf is an extremely powerful tool (yep, it should be illegal as well). Imagine that git alias x fzf, what a dream team-up!

By using ! symbol can make this dream come true. ! allows running external commands with the alias, including git command itself. So many possibilities can be achieved by combining external commands.

For example, I add an alias ac, known as add modified files, to add modified file:

1
2
3
[alias]
  ...
  ac = ! git add $(git diff --name-only --diff-filter=M | fzf -m --bind "ctrl-a:select-all")

The steps in this video are:

  • git s to show unstaged changes
  • git ac to list modified files in fzf, ctrl-a to select all and enter to add them
  • git s to show the result that all modified files are added and staged

Day 4: go smooth

To smooth git workflow, I tweak fzf key binding to configure command execution when selection is made in fzf.

For example, I create an alias ds, known as diff staged files, to show changes in staged files and after pressing enter, it goes to commit process directly:

1
2
3
[alias]
  ...
  ds = ! git diff --name-only --cached | fzf --preview "git diff --cached -- {} | delta" --bind "enter:abort+execute(git commit)"

The steps in this video are:

  • git ds to show all staged files
  • Check changes by switching files listed in fzf preview screen
  • Press enter to go to commit
  • Type commit message and save it

As you may also notice in the ds alias, I’m now able to use delta to format git diff in fzf preview screen, by piping diff to it.

Until now, all the main problems I have with Tig are solved by git alias. I’d be happier than ever.

Day 5: go dynamic

For some reason, I’d like to pass the file name as an argument to a git command bound with an alias.

For example, I’d like to see certain change in a file, when and who made it, and all related changes in the same commit. It involves git blame command with the file name as an argument. Without doubt, it’s possible to accomplish it with git alias by using sh -c to pass arguments:

1
2
3
[alias]
  ...
  b = ! sh -c \"git blame $1 | fzf --bind 'enter:abort+execute(git diff-tree -p {1} | delta)'\"

⚠️ Pay attention to escape single quotes ' or/and double quotes " inside sh -c if needed.

An alternative and more complex method is to bind a function, like ! f() {...}; f. Once you dig deeper in this way, you will certainly find the answers for all your needs.

Day 6: go flexible

Pursuing the ambition of customizing as many aliases as possible to perfectly suit my needs, the aliases are getting complicated with many duplicated and redundant codes. It’s hard to maintain in the configuration file like this:

1
2
3
4
[alias]
  ac = ! git add $(git diff --name-only --diff-filter=M | fzf -0 -m --reverse --height=40% --bind tab:down,btab:up,ctrl-j:up,ctrl-k:down,change:top,alt-space:toggle,ctrl-a:select-all,ctrl-u:deselect-all --cycle)
  b = ! sh -c \"git blame $1 | fzf -0 --ansi --bind tab:down,btab:up,ctrl-j:up,ctrl-k:down,change:top,alt-space:toggle,ctrl-a:select-all,ctrl-u:deselect-all --bind 'enter:abort+execute(git diff-tree -p {1} | delta --theme=iceberg)' --cycle\"
  ds = ! git diff --name-only --cached | fzf -0 --ansi --preview-window=right:80:wrap --preview 'git diff --cached -- {} | delta --theme=iceberg' --bind tab:down,btab:up,ctrl-j:up,ctrl-k:down,change:top,alt-space:toggle,ctrl-a:select-all,ctrl-u:deselect-all --bind alt-k:preview-down,alt-j:preview-up,alt-h:preview-page-down,alt-l:preview-page-up --bind 'enter:abort+execute(git commit)' --cycle

For instance, if I want to update fzf preview screen key bindings for all aliases, I have to edit all aliases one by one. It’s a bummer.

Luckily, git alias can be edited via command line:

~$ git config --global alias.<alias_name> <command>

This creates the possibility to let Bash script handle flexibly the alias modifications. I can define some variables for the common parts in the aliases, which permits that the modifications in one location will affect all aliases at once.

Here is a sample of Bash script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env bash
# remove all aliases
git config --global --remove-section alias

# set variables
FZF_KEYBINDING="tab:down,btab:up,ctrl-j:up,ctrl-k:down,change:top,alt-space:toggle,ctrl-a:select-all,ctrl-u:deselect-all"
FZF_PREVIEW_KEYBINDING="alt-k:preview-down,alt-j:preview-up,alt-h:preview-page-down,alt-l:preview-page-up"
DIFF_CMD="delta --theme=iceberg"
GIT_DIFF_NAME="git diff --name-only"
GIT_DIFF_NAME_CACHED="$GIT_DIFF_NAME --cached"
GIT_DIFF_TREE="git diff-tree -p {1}"

# define alias
git config --global alias.ac "! git add \$($GIT_DIFF_NAME --diff-filter=M | fzf -0 -m --reverse --height=40% --bind $FZF_KEYBINDING --cycle)"
git config --global alias.b '! sh -c "git blame $1 | fzf -0 --ansi --bind '"$FZF_KEYBINDING"' --bind '\''enter:abort+execute('"$GIT_DIFF_TREE"' | '"$DIFF_CMD"')'\'' --cycle"'
git config --global alias.ds "! $GIT_DIFF_NAME_CACHED | fzf -0 --ansi --preview-window=right:80:wrap --preview 'git diff --cached -- {} | $DIFF_CMD' --bind $FZF_KEYBINDING --bind $FZF_PREVIEW_KEYBINDING --bind 'enter:abort+execute(git commit)' --cycle"

⚠️ Pay attention to escape $ symbol by \$, in case $ symbol indicates the variable used in alias.

In the example above, if I want to add more key bindings for fzf preview screen, I’ll modify FZF_PREVIEW_KEYBINDING then run the Bash script. All aliases will be updated accordingly and beautifully.

For more details and examples, go checkout addalias.sh.

Day 7: go extreme

After 6 days hands-on of git alias configuration, it’s time to lay down holding a beer and enjoy the efficiency brought by git alias? Wait a minute here, I still have some small problems with the current setup:

  1. Certain alias is conflicted with default git command. For example, git am is already used by git-am, and it’s impossible to overwrite git command by git alias.
  2. Typing 3-letter git to initial git command every time is fatigue.

It’s time to push the limit and go extreme. To solve those problems, the idea is to create a function in ~/.zshrc, like:

1
2
3
4
5
6
7
8
9
...
g () {
    if [[ "$1" == "am" ]]; then
        git ac
    else
        git "$@"
    fi
}
...

Now I’m able to use am as add modified files, way easier to remember than ac. If on a rare occasion I want to use git-am, I’ll just simply type git am.

Meanwhile, using g instead of git reduces the time of typing 2 letters. What a pleasure for a lazy person like me!

Sum-up

  • Git alias is amazing.

  • Git alias with ! to execute external command is powerful.

  • Git alias combined with other commands like fzf and sh -c is matchless.

  • Git alias created by Bash script is flexible.

  • Shell alias/function gives wings to git alias.


Did you git alias as well? What’s your favorite git alias? Share it with me and let me learn from you.