Josh Dick portrait Josh Dick

My Git Prompt for Zsh, Revisited

An elegant, bird's-eye view of your Git repositories.

This post is a follow-up to my previous post about the original version of my zsh Git prompt, which showed Git information on the right-hand side using zsh’s RPS1 prompt.

I like seeing useful information about Git repositories at a glance on the command line, and after using my previous Git prompt for zsh for about 4½ years, I decided it was time for some cosmetic tweaks and code cleanup.

This prompt is functionally the same as the previous one, but has been moved from the right hand side to the left-hand side, and includes different symbols for showing the “commits ahead/behind” counts.

Like the previous prompt, this prompt:

  • 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 demonstrates how the prompt works (click on it for a larger version):

Git Prompt for zsh

While creating this updated version of my Git prompt, I looked into implementing it using zsh’s built-in vcs-info mechanism, but attempting to use it only made the code more complicated and cumbersome, so I decided against it.

I use this prompt in combination with the excellent sickill/git-dude Git commit notifier, which automatically keeps repositories of your choosing up to date, and which will periodically trigger appropriate “commits behind” counts in the prompt when working with those repositories. This solution works great for my needs, but if you’d prefer a zsh Git prompt that can keep repositories up to date on its own, check out sindresorhus/pure.

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 in my .zshrc.)

You can paste this code directly into your .zshrc, or save it in its own file and source that file in your .zshrc.

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

# Echoes a username/host string when connected over SSH (empty otherwise)
ssh_info() {
  [[ "$SSH_CONNECTION" != '' ]] && echo '%(!.%{$fg[red]%}.%{$fg[yellow]%})%n%{$reset_color%}@%{$fg[green]%}%m%{$reset_color%}:' || echo ''
}

# Echoes information about Git repository status when inside a Git repository
git_info() {

  # Git branch/tag, or name-rev if on detached head
  local GIT_LOCATION=${$((git symbolic-ref -q HEAD || git name-rev --name-only --no-undefined --always HEAD) 2> /dev/null)#(refs/heads/|tags/)}

  if [ -n "$GIT_LOCATION" ]; then

    local AHEAD="%{$fg[red]%}⇡NUM%{$reset_color%}"
    local BEHIND="%{$fg[cyan]%}⇣NUM%{$reset_color%}"
    local MERGING="%{$fg[magenta]%}⚡︎%{$reset_color%}"
    local UNTRACKED="%{$fg[red]%}●%{$reset_color%}"
    local MODIFIED="%{$fg[yellow]%}●%{$reset_color%}"
    local STAGED="%{$fg[green]%}●%{$reset_color%}"

    local -a DIVERGENCES
    local -a FLAGS

    local NUM_AHEAD="$(git log --oneline @{u}.. 2> /dev/null | wc -l | tr -d ' ')"
    if [ "$NUM_AHEAD" -gt 0 ]; then
      DIVERGENCES+=( "${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
      DIVERGENCES+=( "${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
      FLAGS+=( "$MERGING" )
    fi

    if [[ -n $(git ls-files --other --exclude-standard 2> /dev/null) ]]; then
      FLAGS+=( "$UNTRACKED" )
    fi

    if ! git diff --quiet 2> /dev/null; then
      FLAGS+=( "$MODIFIED" )
    fi

    if ! git diff --cached --quiet 2> /dev/null; then
      FLAGS+=( "$STAGED" )
    fi

    local -a GIT_INFO
    GIT_INFO+=( "\033[38;5;15m±" )
    [ -n "$GIT_STATUS" ] && GIT_INFO+=( "$GIT_STATUS" )
    [[ ${#DIVERGENCES[@]} -ne 0 ]] && GIT_INFO+=( "${(j::)DIVERGENCES}" )
    [[ ${#FLAGS[@]} -ne 0 ]] && GIT_INFO+=( "${(j::)FLAGS}" )
    GIT_INFO+=( "\033[38;5;15m$GIT_LOCATION%{$reset_color%}" )
    echo "${(j: :)GIT_INFO}"

  fi

}

# Use ❯ as the non-root prompt character; # for root
# Change the prompt character color if the last command had a nonzero exit code
PS1='
$(ssh_info)%{$fg[magenta]%}%~%u $(git_info)
%(?.%{$fg[blue]%}.%{$fg[red]%})%(!.#.❯)%{$reset_color%} '

As with the original version of this prompt, I hope this prompt is as useful and as big of a time saver for you as it is for me, and I’d love to hear your thoughts about it!