Color Theme

Handy Git Aliases

Simple customizations make using the terminal easy.

Originally Published
Last Updated

Skip the backstory and give me the code.

I spend a lot more time in the terminal since I moved from Visual Studio Code to a terminal based editor (Helix). And it’s great! The terminal based programs I rely on are fast, do their jobs well, and tend to have text based configurations that are easy to backup. But one thing I started missing very quickly was VS Code’s built-in Git integration.

At the beginning of my career I used Git from the terminal, but over time I relied on the VS Code UI for more and more Git operations. When I ditched VS Code, some Git commands were easy to start using again: git status, git stash, git checkout <branch>. git commit was slightly more annoying since commit messages have to be wrapped with quotes and have newlines escaped. Then I learned I could just drop the -m flag and Git would open my editor for me to type the commit message into. Perfect!

Unfortunatly there were a few commands that were painful to start using again, git add chief among them. I’ve since gotten better about using shell completions when typing file paths, but I missed being able to easily browse all my changed files, see their diffs, and select what I wanted to stage for a commit with no typing.

There are lots of dedicated Git clients out there and I’m sure they solve this and other Git pain points well. I enjoy learning and solving small problems with simple solutions, so instead I decided to see if I could solve this using some of the terminal programs I already had installed on my machine. Time to add a new shell alias and get cracking!

A new what?

For those who are new to terminal jargon, here's a quick breakdown of terms as I'm using them.

Terminal
A graphical program you open to type commands into. Examples include kitty, Ghostty, and Windows Terminal.
Shell
A program the terminal sends your commands to that actually runs your commands and sends output back to the terminal to be displayed. Shell's have their own scripting languages and can be configured via text configuration files. Examples include Bash, Zsh, and fish.
Shell Alias
A short name that executes some other command when passed to a shell. These names are configured by running a shell-provided command, typically in a shell configuration file.

gitadd

The solution I came up with feeds the output of git status into fzf, which in turn uses bat or Git to show a preview. Since I’m using Bash for my shell I added the following declarations to my .bashrc file to create a gitadd alias.

Update 2026-04-05: I’ve tweaked this script a fair bit since this article was originally posted.

Improvements Over Original
  • Only shows files that haven’t already been staged for commit
  • Doesn’t show a bat error for deleted files in the fzf preview
  • Only displays/searches file names in the file list (hides git status codes)
  • Uses delta on for better git diff previews
  • Improves spacing around the header with fzf borders
  • Allows the user to wrap around the file list when using arrow keys
  • Explicitly allows multi-select rather than depending on user changing the fzf default
#! /usr/bin/env bash

gitadd-preview() {
	if [ "$1" == 'D' ]; then
		echo File Deleted
		return
	fi

	local diff_arg=()
	if [ "$1" != '??' ]; then
		diff_arg+=(-d)
	fi

	bat --color=always --paging=never --style=numbers,changes,snip "${diff_arg[@]}" "$2"
}

export -f gitadd-preview

# The purpose of running ripgrep here is to filter out files which have already been staged.
# Using `git ls-files --modified --others --exclude-standard -z` would do this without the
# need for ripgrep, but then the lines passed to fzf would just be filenames and lack the extra
# information used by gitadd-preview.
#
# The use of ripgrep's --null-data option implies --null in practice, even though this isn't
# currently documented in the command's manual or --help output. Hence the need for fzf's --read0
# option. https://github.com/BurntSushi/ripgrep/issues/1289
git status --untracked-files --no-renames -z | rg --null-data --invert-match '^\w\s'| fzf \
--read0 \
--multi \
--with-nth 2.. \
--cycle \
--header 'Ctrl-r: reload, Ctrl-d: view git diff' \
--header-border line \
--input-border line \
--bind 'ctrl-d:preview:git diff {2..} | delta --file-style omit' \
--bind 'ctrl-r:reload(git status --untracked-files --no-renames -z)' \
--bind 'enter:become(git add --verbose {+2..})' \
--preview 'gitadd-preview {1} {2..}'
Original Version
# This line isn't specific to gitadd, but I already had it set and my impelementation of gitadd depends on these options being set to look and function correctly.
export FZF_DEFAULT_OPTS="--multi --reverse --border --margin 1% --padding 2%,1% --info hidden"

gitaddPreview() {
	local diffArg=()
	if [ $1 != '??' ] && [ $1 != 'A' ]; then
		diffArg+=(-d)
	fi
	bat --color=always --style=numbers,changes,snip "${diffArg[@]}" "$2"
}

alias gitadd="git status --untracked-files -z | fzf \
  --read0 \
  --header 'Ctrl-r: reload, Ctrl-d: view git diff' \
  --bind 'ctrl-d:preview:git diff --color=always {2..}' \
  --bind 'ctrl-r:reload(git status -u -z)' \
  --bind 'enter:become(git add --verbose {+2..})' \
  --preview 'gitaddPreview {1} {2..}'"

This worked really well for my needs, so I decided to create a few more aliases to smooth out other pain points in my workflow.

gitundo

VS Code had a menu option for undoing the most recent commit which I used every now and then. I don’t do that very often so I kept having to search for the equivalent command after I moved to the termnial (keeping git reset, git restore, and git revert straight is a pain when you don’t use them regularly). This gitundo alias takes care of that once and for all.

alias gitundo="git reset HEAD~"

gitpublish

Normally its a simple git push to move commits from your local machine up to the Git remote. An exception I often run into is when you first create a new local branch that doesn’t exist on the remote. If you try to push commits on such a branch Git will remind you that you need to use the --set-upstream option. This isn’t problematic, but it is annoying. This gitpublish alias removes the need for me to type out the option, remote name (I usually only have one remote named origin), and branch name which is really nice.

alias gitpublish="git push --set-upstream origin $(git branch --show-current)"

Go Forth and Personalize

If you find these aliases helpful then that’s great! What I really hope you take away is how easy it can be to smooth out the rough edges of a terminal workflow with the right tweaks. If you have to (or choose to) spend significant time working in the terminal, do yourself a favor and personalize your shell’s configuration file to make your environment work for you.