Customize your ZSH prompt with this one weird trick!

I like having the last commit's sha in the command line. Gives me instant access to the info. I'll walk you through how I made zsh to show it to me.

Customize your ZSH prompt with this one weird trick!

Glad you clicked through, turns out clickbait headers work! šŸ˜

I like being pedantic about what my terminal looks like. Iā€™ve recently acquired a new machine to develop on and I recreated my environment from scratch instead of using time machine to migrate my old computer. Main reason being is that I donā€™t like the cruft that just accumulated over the years however careful I was with what I installed.

Among the first few bits I customised were iTerm2, oh my zsh to be my terminal, and the agnoster theme for it. Yes, I also installed the powerline fonts, though Iā€™m using Fira Code, which has those built in already.

Once iTerm2 is set to solarized light, background is somewhat opaque and blurry, Agnoster set as the theme, and Fira Code is at 16pt size, this is what my terminal looks like:

Screenshot of terminal running iTerm2, solarized light, ZSH, Agnoster theme, Fira Code font

However what Iā€™m missing is the last commitā€™s short sha after the branch name, so letā€™s do that!

First off, I know itā€™s possible, because Iā€™ve done it in the past. Sadly none of my computers (dating back to 2011) have that setting any more, so need to find how to do it again. From memory I had to mess around with some environment variables in my ~/.zshrc file, but thatā€™s the only thing I have to go on.

Having talked to a bunch of friends on a Discord server, they suggested I check out the agnoster themeā€™s file to see how the prompt is actually being printed. The file itself is ~/.oh-my-zsh/themes/agnoster.zsh-theme, and within there we need to look at the prompt_git function.

At the time of writing thatā€™s a 43 line bash function with a LOT of cryptic things going on. However the very last line is an echo, which means thatā€™s probably the line that I actually see in the terminal. Changing that in place and reloading the terminal confirms that.

The bash command to get the short sha is git rev-parse --short HEAD, which does show up in the actual body of the function but itā€™s sent into oblivion so nothing actually happens.

Then thereā€™s the section about zstyle and vcs_info; two things I had zero idea about.

zstyle

Couldnā€™t find a lot about it while Googling before I got a better result. My first excursion took me to this StackOverflow article: https://unix.stackexchange.com/questions/214657/what-does-zstyle-do.

In it they talk about how zstyle also handles how whatever vcs_info does ends up looking like.

vcs_info

This is a function that seems to be in zsh internals: https://github.com/zsh-users/zsh/blob/zsh-5.8/Functions/VCS_Info/vcs_info

Thereā€™s a WHOLE lot of documentation around it on the man pages in section 26.5: http://zsh.sourceforge.net/Doc/Release/User-Contributions.html#Version-Control-Information. I donā€™t precisely remember how I got here, but it involved searching and a lot of quick filter checks to see whether the document has useful info in it or not. Read it, itā€™s useful, however for the tldr crowd:

context

:vcs_info:vcs-string:user-context:repo-root-name

This controls how specific we get. vcs_info means weā€™re dealing with that function. vcs-string is the name of the current vcs (version control system) being used. Stuff like git, hg, svn, etc... The documentation has a full list.

user-context is an arbitrary string that gets passed in. Iā€™m not 100% sure what this is or how it works, but we donā€™t need it.

repo-root-name is the root name of the current repository where we are in.

Putting it all together, a string like this

:vcs_info:git:*:zsh

means that weā€™re only dealing with git repositories in whatever user context where the repository name is zsh. Itā€™s going to be pretty restrictive.

This one: :vcs_info:* however matches for every folder thatā€™s tracked in any of the 20 or so supported version control systems in all user contexts whatever the repositoriesā€™ names are, ie MATCH ALL THE THINGS!

With that knowledge armed, letā€™s look at what Agnoster does for that and what those lines mean!

What Agnoster does

Hereā€™s this snippet towards the very end of the prompt_git function:

autoload -Uz vcs_info

zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' get-revision true
zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:*' stagedstr 'āœš'
zstyle ':vcs_info:*' unstagedstr 'ā—'
zstyle ':vcs_info:*' formats ' %u%c'
zstyle ':vcs_info:*' actionformats ' %u%c'
vcs_info
echo -n "${ref/refs\/heads\//$PL_BRANCH_CHAR }${vcs_info_msg_0_%% }${mode}"

The autoload line makes sure that the rest of the script calls vcs_info as a function. This is useful in case you have a program called vcs_info and naming conflicts would ensue. Anyways hereā€™s the StackOverflow answer about it with references.

Then the next few lines set context around the function. enable git means itā€™s only going to deal with git repositories (but not hg, svn...).

get-revision true means it will fetch the current revision number (ie last commit sha) and make it available for us.

check-for-changes true means it will pay attention to whether we have uncommitted changes either staged or unstaged.

stagedstr and unstagedstr needs the previous to be true. They control what the string is going to be to signify staged changes and unstaged changes repsectively. They show up as %u and %c.

formats is a string that is used to display THINGS about your current repository. It takes a bunch of placeholders and arbitrary characters. %u%c will display the dot and/or the plus sign if there are staged / unstaged changes present, otherwise it displays nothing.

actionformats is like the previous, but only used when something is currently being done to the repository like a merge conflict, rebase conflict, or interactive rebase.

The next line actually calls the vcs_info function. The autoload line did not call the function, it just made sure that when it is called, the right thing gets called.

And then we echo. Technically the branch name (this thing: ${ref/refs/heads//$PL_BRANCH_CHAR }) is available as %b, but then Agnoster couldnā€™t put the branch icon before the branch name.

This also means that the branch icon and branch name come first, then whatever the vcs_info wants to output, then ${mode} (signifies git action like rebase >R>, bisect <B>, or merge >M<).

Override

By far the easiest solution would be to edit the theme file, but I donā€™t like to do that for many reasons. One of them is I keep zsh updated regularly and any update would wipe my changes, therefore anything I do would need to happen inside my own .zshrc file.

I copied the vcs_info formats line into there to see what happened, reloaded the terminal, and lo and behold, my changes show up!

# this is in .zshrc somewhere
zstyle ':vcs_info:git:*' formats ' herp'
Terminal window, prompt says "master herp" with our changes applied

%i gets us the revision, but itā€™s the long revision.

Prompt showing the branch name, @ symbol, and the long revision ID.

From there itā€™s a question of truncating that and getting the first 8 characters. Googling further, I found that %8.8i will achieve what I want, but I have no idea why. I think it has to do with printf formatting, but couldnā€™t actually find anything concrete there. Something about width / decimals maybe.

One more niggly bit was that I donā€™t need to see my user@computername bit, because itā€™s not relevant to me, and it takes up space.

Putting the following line into .zshrc as well, but strictly after the source line makes the user@computer (context) disappear.

# in .zshrc file
source $ZSH/oh-my-zsh.sh # this is already in there, find it

prompt_context(){}

And with these two changes, my terminal looks the way I want it to look like!

Conclusion

Thereā€™s not much. Read documentation. Experiment! The terminal is your oyster! Let me know if you found it useful!

Photo by Victor Garcia on Unsplash