Josh Dick portrait Josh Dick

My Git Prompt for Zsh

Because git status isn't nearly as fun.

Update on June 8, 2017: After using this Git prompt for about 4½ years, I’ve made some minor tweaks to it.

Update on January 8, 2013: The Git prompt code was missing some initialization options that were local to my zsh configuration file (.zshrc). I’ve updated the code to include the necessary options.

It has become rare for me to use a computer for longer than a few hours without using Git.

Although many great tools are available for graphically interacting with Git, I use Git almost exclusively on the command line, since that’s how and where I first learned to use Git. (I believe it would be beneficial for anyone trying to learn Git to learn on the command line; doing so exposes Git’s methodology in a fundamental way that promotes understanding, but that’s a topic for another post.)

Any time I find an opportunity to offset development workflow friction from myself to the computer, I jump on it. In that vein, in order to save time and be lazier when working with Git, I’ve pieced together a helpful shell prompt that conveys lots of information about a Git repository at a glance. The prompt works with zsh, my shell of choice.

There are many examples of custom “SCM-enabled” shell prompts across the Internet, but I figured it was worth sharing mine here. The prompt’s code is adapted from this GitHub Gist, but incorporates features from other examples I’ve come across, namely:

  • Only appears if your current directory is a Git repository.
  • Shows number of commits ahead and behind upstream, as applicable.
  • Shows if a merge is currently taking place.
  • Shows a “traffic light” representation of git status:
    • Red () means there are untracked changes.
    • Yellow () means there are unstaged changes.
    • Green () means there are staged changes.

Here’s a contrived example that showcases the prompt (click on it for a larger version):

Git Prompt for Zsh

Here’s the code for the prompt, as of this writing. (If I make any changes to the code in the future, the most up-to-date version will always be available as a GitHub Gist.) You can paste this code directly into your .zshrc, or save it in its own file and source that file in your .zshrc.

# Adapted from code found at <https://gist.github.com/1712320>.

setopt prompt_subst
autoload -U colors && colors # Enable colors in prompt

# Modify the colors and symbols in these variables as desired.
GIT_PROMPT_SYMBOL="%{$fg[blue]%}±"
GIT_PROMPT_PREFIX="%{$fg[green]%}[%{$reset_color%}"
GIT_PROMPT_SUFFIX="%{$fg[green]%}]%{$reset_color%}"
GIT_PROMPT_AHEAD="%{$fg[red]%}ANUM%{$reset_color%}"
GIT_PROMPT_BEHIND="%{$fg[cyan]%}BNUM%{$reset_color%}"
GIT_PROMPT_MERGING="%{$fg_bold[magenta]%}⚡︎%{$reset_color%}"
GIT_PROMPT_UNTRACKED="%{$fg_bold[red]%}●%{$reset_color%}"
GIT_PROMPT_MODIFIED="%{$fg_bold[yellow]%}●%{$reset_color%}"
GIT_PROMPT_STAGED="%{$fg_bold[green]%}●%{$reset_color%}"

# Show Git branch/tag, or name-rev if on detached head
parse_git_branch() {
  (git symbolic-ref -q HEAD || git name-rev --name-only --no-undefined --always HEAD) 2> /dev/null
}

# Show different symbols as appropriate for various Git repository states
parse_git_state() {

  # Compose this value via multiple conditional appends.
  local GIT_STATE=""

  local NUM_AHEAD="$(git log --oneline @{u}.. 2> /dev/null | wc -l | tr -d ' ')"
  if [ "$NUM_AHEAD" -gt 0 ]; then
    GIT_STATE=$GIT_STATE${GIT_PROMPT_AHEAD//NUM/$NUM_AHEAD}
  fi

  local NUM_BEHIND="$(git log --oneline ..@{u} 2> /dev/null | wc -l | tr -d ' ')"
  if [ "$NUM_BEHIND" -gt 0 ]; then
    GIT_STATE=$GIT_STATE${GIT_PROMPT_BEHIND//NUM/$NUM_BEHIND}
  fi

  local GIT_DIR="$(git rev-parse --git-dir 2> /dev/null)"
  if [ -n $GIT_DIR ] && test -r $GIT_DIR/MERGE_HEAD; then
    GIT_STATE=$GIT_STATE$GIT_PROMPT_MERGING
  fi

  if [[ -n $(git ls-files --other --exclude-standard 2> /dev/null) ]]; then
    GIT_STATE=$GIT_STATE$GIT_PROMPT_UNTRACKED
  fi

  if ! git diff --quiet 2> /dev/null; then
    GIT_STATE=$GIT_STATE$GIT_PROMPT_MODIFIED
  fi

  if ! git diff --cached --quiet 2> /dev/null; then
    GIT_STATE=$GIT_STATE$GIT_PROMPT_STAGED
  fi

  if [[ -n $GIT_STATE ]]; then
    echo "$GIT_PROMPT_PREFIX$GIT_STATE$GIT_PROMPT_SUFFIX"
  fi

}

# If inside a Git repository, print its branch and state
git_prompt_string() {
  local git_where="$(parse_git_branch)"
  [ -n "$git_where" ] && echo "$GIT_PROMPT_SYMBOL$(parse_git_state)$GIT_PROMPT_PREFIX%{$fg[yellow]%}${git_where#(refs/heads/|tags/)}$GIT_PROMPT_SUFFIX"
}

# Set the right-hand prompt
RPS1='$(git_prompt_string)'

I hope the prompt is as useful and as big of a time saver for you as it is for me!