Skip to content
shellhacks.dev
Git

How to Use Git Aliases to Shorten Common Commands Properly

Create Git aliases for status, log, undo and branch cleanup, use the bang prefix for shell commands, pass arguments correctly, and keep them in dotfiles.

6 min read
Summarize in AI
Illustration for "How to Use Git Aliases to Shorten Common Commands Properly": a terminal session, a git branch graph, an exit status badge.

Git commands tend to be long and used constantly, which makes them good candidates for short names. Git has a built-in alias feature that works from any shell and any platform, unlike shell aliases that live in one dotfile. The examples use Git 2.45; ready-made sets are in useful Git aliases, and shell-level aliases are in Bash aliases.

Simple aliases

Shell
git config --global alias.st "status -sb"
git config --global alias.co checkout
git config --global alias.lg "log --oneline --graph --decorate --all -20"
git config --global alias.unstage "restore --staged --"
git config --global alias.last "log -1 --stat"
git st
Text
## main...origin/main
 M src/app.py
?? notes.txt

An alias maps a name to a Git command and its options, and anything you type after the alias is appended. The same entries appear in ~/.gitconfig under an [alias] section, and you can edit the file by hand. Aliases cannot replace a built-in command, so git config alias.status ... has no effect; give the shortcut a new name.

Shell aliases with the bang

INI
[alias]
  cleanup = "!git branch --merged main | grep -v '^[ *]*main$' | xargs -r git branch -d"
  root = rev-parse --show-toplevel
  whoami = "!git config user.name; git config user.email"

A value starting with ! is run by the shell instead of being read as a Git command, so it can use pipes, xargs and several commands. Git runs such aliases from the top of the repository, regardless of your current directory, which is surprising for relative paths. cleanup deletes local branches already merged into main, using the lowercase -d so unmerged work is refused.

Arguments need a function

INI
[alias]
  fixup = "!f() { git commit --fixup=\"$1\"; }; f"
  find = "!f() { git log --all --oneline -S\"$1\"; }; f"
  pushnew = "!f() { git push -u origin \"$(git branch --show-current)\"; }; f"

For a shell alias, Git appends the arguments to the end of the command line, which is rarely where you need them. Wrapping the body in a function and calling it, f() { ...; }; f, lets you use $1 and $@ where they belong, and quote them properly. Remember to escape double quotes inside the config value.

Share and inspect

Shell
git config --get-regexp '^alias\.'
git config --global include.path ~/dotfiles/gitconfig-aliases

List them to remember what you set. Keeping the aliases in a separate file in a dotfiles repository and including it from ~/.gitconfig gives every machine the same set, as described in GNU Stow for dotfiles. When an alias grows past a line or two, turn it into a script called git-name on PATH, which Git runs as git name.

How Git resolves an alias

When a command name is not a built-in, Git looks it up in the alias. section of its configuration, reading system, global and local files in that order, with later files overriding earlier ones. A repository can therefore define an alias that shadows a global one, which is handy for project-specific shortcuts and a surprise when a coworker's clone behaves differently. Built-in command names always win: an alias called status or log is ignored, and Git prints a warning. Aliases can refer to other aliases, to a depth that Git checks to avoid loops, and an alias can add default options to a built-in, as in lg = log --graph, though a name that shadows nothing is the safe habit.

The second point is the working directory. A plain alias runs where you ran it, but a shell alias with a leading bang runs from the top of the work tree, and sets GIT_PREFIX to the directory you came from. A function that needs relative paths can use that variable to cd back, which matters for aliases that accept file names, such as one that opens files in an editor.

Aliases worth having

A short set earns its keep every day. A compact status with branch information saves a screen of text. A graph log with decorations is the quickest orientation in an unfamiliar repository. An alias that shows the last commit with its changed files answers the question of what the last change was. One to undo the last commit but keep the changes, undo = reset --soft HEAD~1, is safe because the work stays staged. An alias that lists branches sorted by recent commit, recent = branch --sort=-committerdate --format='%(committerdate:relative)%09%(refname:short)', finds the branch you worked on last Tuesday. Keep the names short and memorable, and avoid more than a dozen, because an alias you forget exists is worse than the full command.

Safety in destructive shortcuts

An alias is typed without thinking, which is its value and its risk. Do not bind reset --hard, clean -fd or a force push to two or three letters. If a destructive alias is needed, give it a long name and include the safer variant: pushf = push --force-with-lease refuses to overwrite commits you have not seen, whereas plain --force overwrites whatever is there. A cleanup alias that deletes merged branches should show the list first, or use -d rather than -D, so unmerged work is kept. Run any new destructive alias on a throwaway clone before relying on it.

Debugging an alias

When an alias does not behave, expand it. GIT_TRACE=1 git st prints the command Git runs after expansion, including the shell invocation for bang aliases. Quoting errors are the usual cause: the value in the config file passes through Git's own parser, which treats double quotes and backslashes specially, and then through the shell. Writing complex aliases as small scripts named git-name and placed on the PATH sidesteps the problem, since Git runs any executable called git-something as git something, with proper editor support and testing.

Aliases across a team

Shared aliases belong in documentation, not in each person's head. A team that agrees on a handful, written in a file in the repository and installed with git config --local include.path ../.gitaliases, finds that pairing and code review go faster, because everyone reads the same short commands. Keep the shared set small and conservative, avoid anything destructive, and leave personal shortcuts in the global file. When a command appears in a runbook, write out the full form, so that a new teammate or an automation account with no aliases can run it unchanged. This is also the answer to reports that a command behaves differently on one computer caused by a private alias that changes what a built-in command does.

Review the list once a year. Aliases accumulate, a few stop being used, and a couple may refer to branches or remotes that no longer exist, such as a hard-coded origin/master. Delete the ones that have not been used, and update the rest to use the repository's default branch through git symbolic-ref refs/remotes/origin/HEAD in place of a fixed name, so they work across repositories with different defaults.

Where this does not apply

Aliases that hide destructive commands, such as one that runs reset --hard, make a dangerous action too easy; name them clearly or leave them out. In scripts and CI, write the full command, since an alias exists only on your machine and the script then breaks elsewhere. On a shared account, put aliases in a per-repository config rather than the global one. Teaching a team is easier when the alias names stay close to the real commands. The Pro Git chapter on aliases and the git config alias documentation are the references.

Taggedgit
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.