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.
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:
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'
%i
gets us the revision, but itās the long revision.
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