A close look at Git Alias
27 Mar 2020I 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
toga
), and some examples doing this ‘pollute’ almost the entire ‘g starting command space’. On top of that, I don’t think typinggit
(git and a space) is a bottleneck of my productivity and I am happy to usegit
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, butbash 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 functioncomplete
) (e.g.co = checkout
provides us suggestions of branch names, which is not provided natively withalias gch = "git checkout"
), and native support for debugging the commands execution flow withGIT_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
- add all updated files that are currently being tracked to the staging area;
- commit the changes;
- 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 tocommit
and\"...\"
add quotation around the first argument to make sure multi-words arguments are passed tocommit
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:
- our aliased command is executed as
git-upush 'commit message'
; - it translates to a bash command
git add -u && git commit -m "$1" && git push && :
, and it executes with argumentcommit message
; - the built-in git command
git add -u
is executed; - the built-in git command loads the argument of the bash command and executes as
git commit -m 'commit message'
. It invokes built-in commandgit gc --auto
; - 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!