A close look at Git Alias

git
 
setup
 

I finally decided to overcome my laziness and set up my aliases for git commands. Naturally, I got my mind set on using bash alias to set it up, until discovering git provides native support for alias.

A few things I considered before choosing git alias over bash alias:

  • In my view, git alias is a cleaner solution - it reduces the number of setups in my bash alias scripts, and git related setups would be centralized in a single git config file .gitconfig;

  • I personally don’t feel the need to shorten the simplest commands to a single word (e.g. git add to ga), and some examples doing this ‘pollute’ almost the entire ‘g starting command space’. On top of that, I don’t think typing git (git and a space) is a bottleneck of my productivity and I am happy to use git as a git command dispatcher. What does bottleneck my work though, is typing out the longer commands (e.g. commit, cherry-pick, etc), and even more so the combinations of a sequence of commands that I almost always use together. It might be simpler using bash functions, but bash alias does not offer native support for aliasing two words separated by a space;

  • git alias does come out nicely with support of bash completion (i.e. tab completion on typing command; for normal scripts we can achieve it by implementing the bash function complete) (e.g. co = checkout provides us suggestions of branch names, which is not provided natively with alias gch = "git checkout"), and native support for debugging the commands execution flow with GIT_TRACE variable set.

To create git aliases, add the following block in .gitconfig:

[alias]:
	command_name = git_command [options]

. To achieve more complex commands, prepend ! to invoke bash commands.

Debugging Git Aliases

Before setting up, let’s introduce an useful alias to debug the aliasing results:

debug = !GIT_TRACE=1 git

And debug git aliases by

git debug aliased_command 

. It would show a full trace of executed git commands. Some may argue it an overkill, but personally I think it is an easy to remember and handy alias.

Complex Aliases - Multiple Commands and/or Taking Arguments

I will avoid going into details of each alias I have, and focus on how to set up complex git aliases. Example - I want to alias upush to

  1. add all updated files that are currently being tracked to the staging area;
  2. commit the changes;
  3. push the changes.

To do so, we make alias:

upush = ! git add -u && git commit -m \"$1\" && git push && :

Explanation:

  • ! instructs the alias processing unit in Git that the command is aliased to a bash command;
  • $1 grabs the first argument passed to the aliased command to commit and \"...\" add quotation around the first argument to make sure multi-words arguments are passed to commit as one.
  • && : colon is shell builtin command that does nothing (exists for historical reason). It fixes the issue that the arguments are appended to the end of the last command by appending a noop as the last command to consume the arguments.

Diving into Git

Let’s first see what does git do when an aliased command is called. We start by using the debug alias we created earlier:

~/pointless (master) $ git debug upush "commit message"
07\:43\:20.154449 git.c:576               trace: exec: git-upush 'commit message'
07\:43\:20.154495 run-command.c:646       trace: run_command: git-upush 'commit message'
07\:43\:20.154718 run-command.c:646       trace: run_command: ' git add -u && git commit -m "$1" && git push && :' 'commit message'
07\:43\:20.157330 git.c:344               trace: built-in: git add -u
07\:43\:20.172075 git.c:344               trace: built-in: git commit -m 'commit message'
07\:43\:20.174112 run-command.c:646       trace: run_command: git gc --auto
07\:43\:20.175971 git.c:344               trace: built-in: git gc --auto
[master 36d6f3c] commit message
 1 file changed, 1 insertion(+)
07\:43\:20.178749 git.c:344               trace: built-in: git push
07\:43\:20.179459 run-command.c:646       trace: run_command: unset GIT_PREFIX; ssh git@gitlab.com 'git-receive-pack '
from the git debug trace, we can see the execution flow:
1. our aliased command is executed as `git-upush 'commit message'`;
2. it translates to a bash command `git add -u && git commit -m "$1" && git push && :`, and it executes with argument `commit message`;
3. the built-in git command `git add -u` is executed;
4. the built-in git command loads the argument of the bash command and executes as `git commit -m 'commit message'`. It invokes built-in command `git gc --auto`;
5. the built-in git command `git push` is executed invoking other git commands.

The `git debug` hints at `run-command.c` and `git.c`. Let's checkout the commit of the git version installed in the system:

$ git –version git version 2.17.1 $ git checkout v2.17.1

.
It turned out to be a little hard to trace down the logics than I expected it to be, with plenty of early exits `exit()`, `die()` lying around.
Em fim, we have a call tree that we care about:

cmd_main() - main entry point of the command dispatcher ├── handle_builtin() - look up in the command table and run the built-in command if found │ └── run_builtin() ├── run_argv() │ └── execv_dashed_external() - execute the external commands │ └── run_command() └── handle_alias() - lookup in the alias table and execute if found └── run_command()


*Postscriptum*: I will keep testing my *git aliases* and post my setup after I find my desired setup.

**Drop a comment on your thoughts about git aliases and your most loved aliases!**
'rafaeljin/pointless.git'
from the git debug trace, we can see the execution flow:
1. our aliased command is executed as `git-upush 'commit message'`;
2. it translates to a bash command `git add -u && git commit -m "$1" && git push && :`, and it executes with argument `commit message`;
3. the built-in git command `git add -u` is executed;
4. the built-in git command loads the argument of the bash command and executes as `git commit -m 'commit message'`. It invokes built-in command `git gc --auto`;
5. the built-in git command `git push` is executed invoking other git commands.

The `git debug` hints at `run-command.c` and `git.c`. Let's checkout the commit of the git version installed in the system:

$ git –version git version 2.17.1 $ git checkout v2.17.1

.
It turned out to be a little hard to trace down the logics than I expected it to be, with plenty of early exits `exit()`, `die()` lying around.
Em fim, we have a call tree that we care about:

cmd_main() - main entry point of the command dispatcher ├── handle_builtin() - look up in the command table and run the built-in command if found │ └── run_builtin() ├── run_argv() │ └── execv_dashed_external() - execute the external commands │ └── run_command() └── handle_alias() - lookup in the alias table and execute if found └── run_command()


*Postscriptum*: I will keep testing my *git aliases* and post my setup after I find my desired setup.

**Drop a comment on your thoughts about git aliases and your most loved aliases!**
''
07\:43\:20.737693 run-command.c:646       trace: run_command: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress
07\:43\:20.739727 git.c:344               trace: built-in: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress
Counting objects: 3, done.
Delta compression using up to 16 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 326 bytes | 326.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To gitlab.com:rafaeljin/pointless.git
   f32a594..36d6f3c  master -> master

from the git debug trace, we can see the execution flow:

  1. our aliased command is executed as git-upush 'commit message';
  2. it translates to a bash command git add -u && git commit -m "$1" && git push && :, and it executes with argument commit message;
  3. the built-in git command git add -u is executed;
  4. the built-in git command loads the argument of the bash command and executes as git commit -m 'commit message'. It invokes built-in command git gc --auto;
  5. the built-in git command git push is executed invoking other git commands.

The git debug hints at run-command.c and git.c. Let’s checkout the commit of the git version installed in the system:

$ git --version
git version 2.17.1
$ git checkout v2.17.1

. It turned out to be a little hard to trace down the logics than I expected it to be, with plenty of early exits exit(), die() lying around. Em fim, we have a call tree that we care about:

cmd_main() - main entry point of the command dispatcher
├── handle_builtin() - look up in the command table and run the built-in command if found
│   └── run_builtin()
├── run_argv()
│   └── execv_dashed_external() - execute the external commands
│       └── run_command()
└── handle_alias() - lookup in the alias table and execute if found
   └── run_command()

Postscriptum: I will keep testing my git aliases and post my setup after I find my desired setup.

Drop a comment on your thoughts about git aliases and your most loved aliases!