Skip to content

Instantly share code, notes, and snippets.

@oli
Last active February 27, 2019 07:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oli/7686885 to your computer and use it in GitHub Desktop.
Save oli/7686885 to your computer and use it in GitHub Desktop.
Adding extra Git repo sugar to the ZSH agnoster theme (with Solarized)
# vim:ft=zsh ts=2 sw=2 sts=2
#
# agnoster's Theme - https://gist.github.com/3712874
# A Powerline-inspired theme for ZSH
#
# # README
#
# In order for this theme to render correctly, you will need a
# [Powerline-patched font](https://github.com/Lokaltog/powerline-fonts).
#
# In addition, I recommend the
# [Solarized theme](https://github.com/altercation/solarized/) and, if you're
# using it on Mac OS X, [iTerm 2](http://www.iterm2.com/) over Terminal.app -
# it has significantly better color fidelity.
#
# # Goals
#
# The aim of this theme is to only show you *relevant* information. Like most
# prompts, it will only show git information when in a git working directory.
# However, it goes a step further: everything from the current user and
# hostname to whether the last call exited with an error to whether background
# jobs are running in this shell will all be displayed automatically when
# appropriate.
### Segment drawing
# A few utility functions to make it easy and re-usable to draw segmented prompts
CURRENT_BG='NONE'
PRIMARY_FG=black
# Characters
SEGMENT_SEPARATOR="\ue0b0"
PLUSMINUS="\u00b1"
BRANCH="\ue0a0"
DETACHED="\u27a6"
CROSS="\u2718"
LIGHTNING="\u26a1"
GEAR="\u2699"
# Begin a segment
# Takes two arguments, background and foreground. Both can be omitted,
# rendering default background/foreground.
prompt_segment() {
local bg fg
[[ -n $1 ]] && bg="%K{$1}" || bg="%k"
[[ -n $2 ]] && fg="%F{$2}" || fg="%f"
if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
print -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
else
print -n "%{$bg%}%{$fg%} "
fi
CURRENT_BG=$1
[[ -n $3 ]] && print -n $3
}
# End the prompt, closing any open segments
prompt_end() {
if [[ -n $CURRENT_BG ]]; then
print -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
else
print -n "%{%k%}"
fi
print -n "%{%f%}"
CURRENT_BG=''
}
### Prompt components
# Each component will draw itself, and hide itself if no information needs to be shown
# Context: user@hostname (who am I and where am I)
prompt_context() {
local user=`whoami`
if [[ "$user" != "$DEFAULT_USER" || -n "$SSH_CONNECTION" ]]; then
prompt_segment $PRIMARY_FG default " %(!.%{%F{yellow}%}.)$user@%m "
fi
}
# Git: branch/detached head, dirty status
# Prompt additions from @oli, ref: https://gist.github.com/oli/7686885
# Kinda synced to https://github.com/robbyrussell/oh-my-zsh/commit/74177c5320b2a1b2f8c4c695c05984b57fd7c6ea
prompt_git() {
local ref dirty
if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
# from parse_git_dirty
# needs to be present and have content or an asterisk is added
# however not displayed because $dirty isn’t in prompt
ZSH_THEME_GIT_PROMPT_DIRTY='±'
# from git_prompt_status
ZSH_THEME_GIT_PROMPT_STASHED=''
ZSH_THEME_GIT_PROMPT_UNTRACKED='?'
# these are covered by (un)stagedstr, which I can’t hide. sigh
#ZSH_THEME_GIT_PROMPT_ADDED='+'
#ZSH_THEME_GIT_PROMPT_MODIFIED='±'
#ZSH_THEME_GIT_PROMPT_RENAMED='~'
#ZSH_THEME_GIT_PROMPT_DELETED='-'
# 2013-11-19 add remote status
# ref: https://github.com/somacreates/etc/blob/master/agnoster.zsh-theme →404
# from git_remote_status
ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE=""
ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE=""
ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE=""
# from git.zsh:
# get the difference between the local and remote branches
remote=$(git_remote_status)
# Checks if working tree is dirty
dirty=$(parse_git_dirty)
# Get the status of the working tree
gps=$(git_prompt_status)
# Get branch name
ref=$(git symbolic-ref HEAD 2> /dev/null) || ref="$(git rev-parse --short HEAD 2> /dev/null)"
# change prompt background based on status
# unmerged remote changes, local commits
# note: $remote has to be before $dirty to work (why?)
if [[ "$remote" == "$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" ]] && [[ "$dirty" == "$ZSH_THEME_GIT_PROMPT_DIRTY" ]]; then
prompt_segment red $PRIMARY_FG
# unmerged remote changes
elif [[ "$remote" == "$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" ]]; then
prompt_segment magenta $PRIMARY_FG
# local commits
elif [[ -n $remote ]]; then
prompt_segment yellow $PRIMARY_FG
# local changes
elif [[ -n $dirty ]]; then
prompt_segment cyan $PRIMARY_FG
else
prompt_segment green $PRIMARY_FG
fi
setopt promptsubst
autoload -Uz vcs_info()
# http://youam.net/misc/zsh/vcs_info.html
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' get-revision true
zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:*' stagedstr '' # staged changes
zstyle ':vcs_info:git:*' unstagedstr '±' # unstaged changes
zstyle ':vcs_info:*' formats ' %u%c'
zstyle ':vcs_info:*' actionformats ' %u%c'
vcs_info
echo -n "${ref/refs\/heads\// } $gps${vcs_info_msg_0_%% } $remote"
fi
}
# Dir: current working directory
# 2014-12-01 Change to truncated:
# trim directories to first 2 and last 3 if >6 directories in path
# from http://rockhopper.dk/linux/zsh-promptwindow-title-trim-middle-of-path/
# Responsive prompt from https://www.alexscotton.com/post/a-modular-responsive-zsh-theme-and-some-bonus-unix-commands
# TODO would be nice to cap prompt to $COLUMNS (terminal width)
prompt_dir() {
# prompt_segment blue $PRIMARY_FG ' %~ '
if [[ ${COLUMNS} -gt 110 ]]; then
prompt_segment blue $PRIMARY_FG ' %8(c:%-3~/.../%4~:%~) '
elif [[ ${COLUMNS} -gt 90 ]]; then
prompt_segment blue $PRIMARY_FG ' %7(c:%-3~/.../%3~:%~) '
elif [[ ${COLUMNS} -gt 75 ]]; then
prompt_segment blue $PRIMARY_FG ' %7(c:%-3~/.../%2~:%~) '
else
prompt_segment blue $PRIMARY_FG ' %6(c:%-2~/.../%2~:%~) '
fi
}
# Virtualenv: current working virtualenv
prompt_virtualenv() {
local virtualenv_path="$VIRTUAL_ENV"
if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
prompt_segment blue black "(`basename $virtualenv_path`)"
fi
}
# Status:
# - was there an error
# - am I root
# - are there background jobs?
prompt_status() {
local symbols
symbols=()
[[ $RETVAL -ne 0 ]] && symbols+="%{%F{red}%}$CROSS"
[[ $UID -eq 0 ]] && symbols+="%{%F{yellow}%}$LIGHTNING"
[[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{cyan}%}$GEAR"
[[ -n "$symbols" ]] && prompt_segment $PRIMARY_FG default " $symbols "
}
# Setup for user input
# The manual break prevents background color from overflowing when the prompt is >1 line
# https://www.alexscotton.com/post/a-modular-responsive-zsh-theme-and-some-bonus-unix-commands
prompt_break() {
echo -n "\n"
}
prompt_cmd() {
echo -n '%# '
}
## Main prompt
prompt_agnoster_main() {
RETVAL=$?
CURRENT_BG='NONE'
prompt_status
prompt_virtualenv
prompt_context
prompt_dir
prompt_git
prompt_end
prompt_break
prompt_cmd
}
PROMPT='%{%f%b%k%}$(prompt_agnoster_main)'
# gitup: update multiple git repo remotes, then auto-pull if local is clean
# 1. array of paths to check
repos=(
#~/repos/repo1/
#~/repos/repo2/
#etc
)
# 2. git remote update
# Improved with http://stackoverflow.com/questions/5083224/git-pull-while-not-in-a-git-directory once git -v 1.8.5 is out
function gitup() {
local GIT_STATUS=''
POST_1_8_5_GIT=$(git_compare_version "1.8.5")
if [[ $POST_1_8_5_GIT -gt 0 ]]; then
for i in ${repos}; do
echo $'\n\U2605' $i
git --work-tree=$i --git-dir=$i.git remote -v update
dirty=$(parse_git_dirty)
if [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_DIRTY" ]]; then
git fetch -v --all
# add --all to show all branches rather than just current
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ..FETCH_HEAD | cat
echo $'\n' $i $'→ dirty'
elif [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_UNTRACKED" ]]; then
git fetch -v --all
# set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
git merge --ff-only
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
echo $'\n'
else
git fetch -v --all
# set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
git merge --ff-only
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
echo $''
fi
else
# for pre-git 1.8.5. Moving back to $STARTING_DIR is unreliable
local STARTING_DIR=$(pwd)
for i in ${repos}; do
cd ${i}
echo $'\n\U2605' $i
dirty=$(parse_git_dirty)
if [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_DIRTY" ]]; then
git fetch -v --all
# add --all to show all branches rather than just current
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ..FETCH_HEAD | cat
echo $'\n' $i $'→ dirty'
elif [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_UNTRACKED" ]]; then
git fetch -v --all
# set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
git merge --ff-only
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
echo $'\n'
else
git fetch -v --all
# set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
git merge --ff-only
git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
echo $''
fi
done
cd $STARTING_DIR
}
# Git version checker from Oh My ZSH git.zsh
#compare the provided version of git to the version installed and on path
#prints 1 if input version <= installed version
#prints -1 otherwise
function git_compare_version() {
local INPUT_GIT_VERSION=$1;
local INSTALLED_GIT_VERSION
INPUT_GIT_VERSION=(${(s/./)INPUT_GIT_VERSION});
INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null));
INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]});
for i in {1..3}; do
if [[ $INSTALLED_GIT_VERSION[$i] -gt $INPUT_GIT_VERSION[$i] ]]; then
echo 1
return 0
fi
if [[ $INSTALLED_GIT_VERSION[$i] -lt $INPUT_GIT_VERSION[$i] ]]; then
echo -1
return 0
fi
done
echo 0
}
#this is unlikely to change so make it all statically assigned
POST_1_8_5_GIT=$(git_compare_version "1.8.5")
#clean up the namespace slightly by removing the checker function
unset -f git_compare_version
@rezzz-dev
Copy link

I've been trying to get my prompt to work with this theme and tried your function here, but I'm getting the feeling I missed something during my install because for whatever reason, I always get a green background on the branch segment with no icons, no matter what the status if the repo is.

I've been trying various functions and came by yours and like what I can see from the code, but can't get it to work. I'm getting this error
prompt_git:9: command not found: git_remote_status

Any ideas?

@rezzz-dev
Copy link

Nevermind -- I think it was the git plugin for oh-my-zsh that I was using. I compared the one I had on my previous install which I knew worked and overwrote the one I just installed oh-my-zsh and it's working like a charm.

@rezzz-dev
Copy link

Although I can't seem to get the BEHIND_REMOTE to work...any ideas?

@oli
Copy link
Author

oli commented Jan 30, 2014

Hey Jason,

It took me a while to get all this working, because I have little to no idea what’s going on here 🐱

git_remote_status is a function in oh-my-zsh’s git.zsh lib.

I updated the gist with what I currently use (power up!), and I note I changed Line 16 from

if [[ "$remote" = "$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" ]] && [[ -n $dirty ]]; then

to

    if [[ "$remote" = "$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" ]] && [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_DIRTY" ]]; then

which may affect the issue you’re having with $ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE.

HTH!

@rezzz-dev
Copy link

Thanks for that! I added that power up but still not seeing anything different.

Maybe it's because I'm looking for something that may not be there. Here's what I'm thinking, if the repo has been updated on github (or whereever) and I cd into my local repo of it. As soon as my prompt appears, I would want it to indicate that the github repo has been changed.

Is that what this should be doing? Or is there something that I need to do to make the indicator show me there's an update?

Thanks again!

@oli
Copy link
Author

oli commented Jan 30, 2014

aah, you need to use git fetch or run the command (gitup) to get remote updates, otherwise the shell won’t have that info. I use gitup to update a bunch of repos at once in the morning, then use git fetch before pushing or when there are remote changes I want to get.

PS remember to set your repo paths before using gitup (or whatever you want to name it)

@rezzz-dev
Copy link

Ah ok -- oh well -- I would've hoped that it did it for me :).

I guess what I'm looking for is some indication that there's been changes without me doing something first. Basically to stop me from going on with my coding and only to find out that the repo has been updated and I didn't merge that update prior to my code/commit.

@oli
Copy link
Author

oli commented Feb 4, 2014

You could write a function that did. It would need to take a value (a path), change to that directory, then run the command on that directory. Oh, hey, that might be useful for me too:

#1.4. git remote update for current directory (or cd to path given)
# todo: improve with http://stackoverflow.com/questions/5083224/git-pull-while-not-in-a-git-directory once git -v 1.8.5 is out
function gitup() {
  if (( $# ))
    then cd $@
  fi
  local GIT_STATUS1=''
  local GIT_STATUS2=''
  dirty=$(parse_git_dirty)
  if [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_DIRTY" ]]; then
    #git remote -v update
    git fetch -v --all
    # add --all to show all branches rather than just current
    git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ..FETCH_HEAD | cat
    echo $'\n' $i $'→ dirty'
  elif [[ "$dirty" = "$ZSH_THEME_GIT_PROMPT_UNTRACKED" ]]; then
    git fetch -v --all
    # set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
    git merge --ff-only
    git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
    echo $'\n'
  else
    git fetch -v --all
    # set merge to use upstream by default with: git config --add --global merge.defaultToUpstream true
    git merge --ff-only
    git log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' ORIG_HEAD.. | cat
    echo $''
 fi
}

@oli
Copy link
Author

oli commented Dec 2, 2014

2014-12-02 Updated with my full Agnoster-based .zsh-theme. Stuff of interest:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment