skip to content

In Git, how do you define an alias, and what changes when it starts with an exclamation mark?

level: middleimportance: should knowfreq 45%

answer

  1. it is just a config key
  2. a section named for what it does
  3. one character changes everything
  4. runs from the repo root, not your cwd
  5. built-ins can never be shadowed

basics

~20 s

An alias is a config key under the alias section, so git config --global alias.st status makes git st work. A leading exclamation mark makes the value a shell command instead of Git subcommand arguments, run from the top of the working tree.

solid answer

~50 s

Aliases live in configuration like anything else: `git config --global alias.lg "log --oneline --graph --decorate"` creates `git lg`, and the same thing can be written directly in the `[alias]` section of `~/.gitconfig`. Without a `!`, the value is treated as arguments to a Git subcommand and anything you type after the alias is appended to the end. With a leading `!`, Git hands the rest to the shell, so you can call other programs, chain commands and use pipes — but it then runs from the **top level of the working tree**, with `GIT_PREFIX` set to your relative path, not from your current directory. Because arguments are appended at the end, a shell alias that needs a positional argument in the middle must wrap itself in a function: `!f() { git log --author="$1" --oneline; }; f`. One firm rule: an alias can never shadow a built-in Git command — define `alias.status` and Git still runs its own `status`.

code

bash · 6 lines
bash
git config --global alias.lg "log --oneline --graph --decorate"
git config --global alias.who '!f() { git log --author="$1" --oneline; }; f'
git config --get-regexp '^alias\.'

git lg -5
git who [email protected]

go deeper

for a junior

Know that an alias is a config key under the alias section and that git config --global alias.<name> "<command>" creates it. Be able to write a simple one for a command you type constantly.

for a middle

Explain the difference the leading exclamation mark makes — shell execution, running from the repository root — and why arguments append at the end so a function wrapper is needed for positional use.

for a senior

Show the operational judgment: aliases are per-user and never travel with a clone, shell aliases execute arbitrary commands so untrusted config is dangerous, and destructive helpers should not be one keystroke away.

for a principal

Be ready to argue where team tooling belongs: a tracked script everyone can read and review versus per-developer aliases that make two engineers' shells behave differently on the same repository.

## Defining one An alias is nothing more than a configuration key in the `alias` section, so every rule about scopes applies: `git config --global alias.co checkout` writes to `~/.gitconfig` and is available everywhere, while the same command without `--global` writes to one repository's `.git/config`. You can also edit the file directly, which is easier for long values: `[alias]` then `lg = log --oneline --graph --decorate --all`. `git config --get-regexp '^alias\.'` lists what you currently have, which is useful when moving to a new machine. ## Plain aliases Without a leading `!`, the alias value is expanded as arguments to `git`. `git lg` becomes `git log --oneline --graph --decorate --all`. Extra arguments you type are appended after the expansion, so `git lg -5` works naturally and `git co -b topic` works for `alias.co = checkout`. This is textual expansion, not a macro language: there are no parameters, no conditionals, and no way to inject an argument anywhere except at the end. ## Shell aliases Prefix the value with `!` and everything after it is passed to the shell. That unlocks pipelines, other tools, and multiple commands: `alias.cleanup = !git branch --merged | grep -v '\*' | xargs -r git branch -d`. Two behaviours are specific to shell aliases and both bite people: **Working directory.** A `!` alias runs from the top level of the working tree, not from where you typed it. If your alias operates on relative paths, they resolve against the repository root. Git sets the `GIT_PREFIX` environment variable to the path from the top level to your original directory, so an alias that must respect where you stood can use it. **Argument position.** Arguments are still appended at the end of the expanded string. `!echo Building && make` with a typed argument produces `... make <arg>`, not what you probably meant. The idiomatic workaround is to define and immediately call a shell function, which gives you real positional parameters: `!f() { git log --author="$1" --oneline "${@:2}"; }; f`. Everything you type lands as arguments to `f`. Inside a `!` alias, `git` means whatever `git` your shell resolves; some people write the full expansion carefully to avoid recursion surprises. ## Aliases cannot override built-ins Git only consults aliases for a command name it does not already know. Defining `alias.status = log` leaves `git status` doing exactly what it always did — the alias is simply never reached. This is deliberate: it means scripts and documentation that call standard commands keep working no matter what a user has configured. If you want a different default behaviour for a built-in, you change its configuration or write a differently-named alias; you cannot rename the command out from under Git. ## Where aliases should live Because `.git/config` is not tracked and never travels with a clone, repository-local aliases only exist for the person who created them. Aliases are personal ergonomics: keep them global, or in a dotfiles repository you install on each machine. A project that wants everyone to have the same helper is better served by a checked-in script than by an alias nobody else will have. ## Practical cautions A `!` alias runs arbitrary commands, so treat configuration from an untrusted source the way you would treat a downloaded shell script — this is one of the reasons Git never reads configuration from inside a cloned repository's tracked files. Keep destructive aliases explicit rather than short: an alias that force-pushes or hard-resets should be named so you cannot fire it by muscle memory. And prefer aliases that are transparent enough that you can still explain what the underlying command was in an interview, rather than ones that hide the mechanism you are expected to know.

  • Why does a shell alias need to define a function to use its first argument?
    Because Git appends what you type to the end of the expanded string; there is no placeholder syntax. Writing `!f() { ..."$1"...; }; f` makes the shell bind your arguments as the function's positional parameters, letting you place them anywhere in the command.
  • What happens if you define alias.status?
    Nothing visible. Git resolves built-in command names before consulting aliases, so `git status` keeps running the built-in. The rule exists so that documented commands behave the same on every machine regardless of a user's configuration.
  • From which directory does a ! alias run, and how do you get back to where you were?
    From the top level of the working tree. Git exports `GIT_PREFIX` containing the path from that top level to the directory where you invoked the alias, so a script that must resolve paths relative to your original location can `cd "$GIT_PREFIX"` first.
  • Should team-wide aliases go in the repository's .git/config?
    No — `.git/config` is untracked and never travels with a clone, so nobody else gets them. Aliases are personal ergonomics best kept in your global file or dotfiles; a helper the whole team needs should be a checked-in script that everyone can actually see.

saying these in an interview costs you the question

  • Thinks an alias can override git status or git commit
  • Expects typed arguments to land in the middle of the expansion
  • Assumes a shell alias runs in the current directory
  • Believes aliases in .git/config are shared with everyone who clones
  • Treats aliases as a shell feature rather than Git configuration

context