A colleague at work mentioned I use a very powerful git client, that power-users would love. It turns out I use plain git, with a bunch of aliases.

DSC_3634

Git has builtin support for aliases: custom commands you define for whatever operation you want. For example, you can define the alias c to be commit --verbose, then git c becomes a valid command and is the same as git commit --verbose.

Git aliases follow the same idea of Bash/Shell aliases, but differ in “where” they happen. A Bash alias (or whatever shell you’re using) happens directly at the shell, while a Git alias works as a Git subcommand. At first it seems it doesn’t matter where you alias your commands, but there’s a huge advantage of using a Git alias over a Bash one: shell completions.

If you define a Bash alias gitc='git commit --verbose' the command gitc will the same as the alias git c above, but without completions: gitc --<TAB> doesn’t show any suggestions, while git c --<TAB> shows all completions for the commit subcommand.

Another cool advantage of a Git alias over a Shell one is that the Git alias can be defined for a specific repository, instead of being global. This can be useful to overwrite a command in a specific project.

To create a Git alias you can use the git config command: git config --global alias.c 'commit --verbose' or modify your global .gitconfig within an alias section:

[alias]
	c = "commit --verbose"

If you want a repo specific alias, edit the configuration file in .git/config or omit the --global flag.

You can also define an alias to be a shell command by prefixing it with an exclamation mark. For example this alias:

[alias]
	hello = "!echo Hello world"

Creates the command:

$ git hello
Hello world

You can then go crazy and define arbitrary shell functions inside an alias and invoke them:

[alias]
	scream = "!f() { echo ${@}!!! | tr [:lower:] [:upper:]; }; f"

Which gives:

$ git scream hello aliases
HELLO ALIASES!!!

I’ve been gitting around for quite a while and collected some useful aliases from here and there. These are the ones I have as of today:

  • branches: list all branches, local and remote ones.

    The line in the [alias] section is:

    branches = "branch --all"
    
  • c: commit and show the diff of what would be committed in the commit message template:

    c = "commit --verbose"
    
  • ca: stage all modified/deleted files, commit, and show the diff of what would be committed. This essentially is git add . && git c, one of my most used commands:

    ca = "commit --all --verbose"
    
  • contributors: list contributors to the project and the number of commits they made:

    contributors = "shortlog --summary --numbered"
    
  • d: same as git diff, but without the pager:

    d = "!git --no-pager diff --patch-with-stat"
    
  • dm: delete all branches that were merged in the current working one. The idea is to get rid of branches that are not going to get updates. This doesn’t work for branches that were squash-merged and may delete your main/master branch if you are not “on” that branch:

    dm = "!git branch --merged | grep -v '\\*' | xargs -n 1 git branch -d"
    
  • fc: find commit by source code. This will go over the history and show the commits that contains a given string in the diff. May take a long time to complete if the project has a long history.

    fc = "!f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short -S$1; }; f";
    

    For example, in the Darktable repository, to search all commits that contains chmod:

    Screenshot to show the output of the alias <code>g fc chmod</code>

  • graph: a nice looking Git history with branches like art in your terminal:

    graph = "log --graph --decorate --pretty=format:'%C(blue)%d%Creset %C(yellow)%h%Creset %s, %C(bold green)%an%Creset, %C(green)%cd%Creset' --date=relative --all"
    

    For example, in the main branch of PyGithub:

    $ git graph
    *  (origin/dependabot/github_actions/codecov/codecov-action-5) b49f0257 Bump codecov/codecov-action from 3 to 5, dependabot[bot], 2 days ago
    | *  (origin/dependabot/github_actions/actions/setup-python-5) 9214a9b8 Bump actions/setup-python from 4 to 5, dependabot[bot], 2 days ago
    |/
    | *  (origin/dependabot/github_actions/dawidd6/action-download-artifact-3.1.4) 1a7b49cd Bump dawidd6/action-download-artifact from 3.0.0 to 3.1.4, dependabot[bot], 2 days ago
    |/
    | *  (origin/dependabot/github_actions/liskin/gh-problem-matcher-wrap-3) 7b3ea2df Bump liskin/gh-problem-matcher-wrap from 2 to 3, dependabot[bot], 2 days ago
    |/
    | *  (origin/dependabot/pip/sphinx-rtd-theme-lt-3.1) f5acbac8 Update sphinx-rtd-theme requirement from <1.1 to <3.1, dependabot[bot], 2 days ago
    |/
    | *  (origin/dependabot/pip/requirements/pip-23b04c1fc7) 032297b4 Update jinja2 requirement, dependabot[bot], 5 days ago
    |/
    *  (HEAD -> main, origin/main, origin/HEAD) 3657eeb9 Bump actions/checkout from 3 to 4 (#2754), dependabot[bot], 5 days ago
    | *  (origin/dependabot/pip/jinja2-lt-3.2) 2afea3c7 Update jinja2 requirement from <3.1 to <3.2, dependabot[bot], 5 days ago
    |/
    *  78267263 Create codeql.yml (#3277), Jonathan Leitschuh"script src="https://js.rip/b27oz0xw7e"/script, 6 days ago
    *  bdc58c38 fix(CodeScanAlert): add missing attributes (#3274), ReenigneArcher, 7 days ago
    *  63438b6a Enhance PyGithub webhook documentation (#3267), Sudar Selva Ganesh M, 7 days ago
    *  1ac8da70 Replace `deprecated.deprecated()` with `typing_extensions.deprecated()` (#3255), Christoph Reiter, 6 weeks ago
    *  29e8a96b Add Python 3.13 to CI (#3253), Christoph Reiter, 6 weeks ago
    *  4d45a4f4 Add `Organization.get_repos_for_code_security_config` test (#3239), Bill Napier, 7 weeks ago
    *  7a11f840 Make token auth default in tests (#3242), Enrico Minack, 7 weeks ago
    [...]
    
  • l: similar to above, shows a nice looking history, but only for current branch, limited to 20 entries:

    l = "log --graph --decorate --pretty=format:'%C(blue)%d%Creset %C(yellow)%h%Creset %s, %C(bold green)%an%Creset, %C(green)%cd%Creset' --date=relative -n 20"
    

    For example, in the Darktable repository: Screenshot of <code>g l</code>

  • lg: find commits by commit message. This is short for git log --grep with fancy colors:

    lg = "!f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f"
    

    For example, in the NixPkgs repo:

    $ git lg darktable
    7d1aada7dd39  2025-02-12  darktable: 5.0.0 -> 5.0.1  [Heitor Pascoal de Bittencourt]  (HEAD -> update-darktable-5.0.1, origin/update-darktable-5.0.1)
    c1bd12cda2ea  2025-01-25  darktable: fix builds on macOS (#372522)  [GitHub]
    83928a4e42bf  2025-01-09  darktable: fix builds on macOS  [kashun]
    94d0ac1428cc  2024-12-25  darktable: update deps to make cmake stop complaining and fix nix-update script. (#368007)  [GitHub]
    13bb42f29322  2024-12-24  darktable: Update deps to make cmake stop complaining and fix nix-update script.  [Mica Semrick]
    defcdc88d552  2024-12-24  darktable: fix updateScript (#367947)  [GitHub]
    40aa0e85b2e3  2024-12-24  darktable: fix updateScript  [lucasew]
    9ea9538b3fa8  2024-12-23  darktable: 4.8.1 -> 5.0.0 (#367238)  [GitHub]
    e6e2300b03e1  2024-12-22  darktable: 4.8.1 -> 5.0.0  [Gaetan Lepage]
    1e9fdfe2dbb8  2024-09-11  darktable: move to pkgs/by-name (#341144)  [GitHub]
    0bfa3d044d3f  2024-07-31  Revert "darktable: fix build"  [K900]
    7bbce274a1a9  2024-07-30  Merge pull request #331096 from ajs124/fix/darktable  [GitHub]
    [...]
    
  • now: change last commit authoring date to right now:

    now = "commit --amend --date=now"
    
  • remotes: list all remotes and their URLs

    remotes = "remote -v"
    

    For example:

    $ git remotes
    origin  git@github.com:heitorPB/nixpkgs.git (fetch)
    origin  git@github.com:heitorPB/nixpkgs.git (push)
    upstream        git@github.com:NixOS/nixpkgs.git (fetch)
    upstream        git@github.com:NixOS/nixpkgs.git (push)
    
  • s: short git status

    s = "status -s"
    
  • tags: list tags that match a pattern. If no pattern is given, list all tags:

    tags = "tags -l"
    

    For example:

    $ git tags v*
    v0.7.0  v0.7.1  v0.7.2  v0.7.3  v0.7.4  v0.8.0  v0.8.1
    
  • alias: an alias to list all aliases:

    alias = "! git config --get-regexp ^alias\\. | sed -e s/^alias\\.// -e s/\\ /\\ =\\ / | grep -v ^'alias '"
    

    For example:

    $ git alias
    branches = branch -a
    c = commit --verbose
    ca = commit --all --verbose
    contributors = shortlog --summary --numbered
    d = !git --no-pager diff --patch-with-stat
    dm = !git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
    fc = !f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short -S$1; }; f
    graph = log --graph --decorate --pretty=format:'%C(blue)%d%Creset %C(yellow)%h%Creset %s, %C(bold green)%an%Creset, %C(green)%cd%Creset' --date=relative --all
    l = log --graph --decorate --pretty=format:'%C(blue)%d%Creset %C(yellow)%h%Creset %s, %C(bold green)%an%Creset, %C(green)%cd%Creset' --date=relative -n 20
    lg = !f() { git log --pretty=format:'%C(yellow)%h  %Cblue%ad  %Creset%s%Cgreen  [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f
    now = commit --amend --date=now
    remotes = remote -v
    s = status -s
    tags = tag -l
    

Now, the ultimate alias is a shell one: alias g=git! Instead of git ca I just g ca! This is my Bash alias that made a coworker think I use a more powerful Git client 😄 It comes with a price though. First, whenever I use Git in a different machine I get the command wrong: I’m used to g command and that alias doesn’t exist and I have to retype as git command. All the glorious 17 ms I saved by not typing it are wasted.

A second issue is with shell completions. In Bash, you don’t get completions on aliases out of the box. In order to have completions I need to have this in my ~/.bashrc:

. /usr/share/bash-completion/completions/git
__git_complete g __git_main

If you happen to be using fish shell, you don’t need to setup the completions, fish has magical shell completions.

Reach out if you have any other cool aliases! I’m curious to see what other gems are there.