Git status as terminal prompt
I recently read an inspiring short post named Update your Mac Terminal to display your current git branch and status which shows how to change the prompt of the terminal to display the name of the git branch and its status, if you are in a git repository. I had been thinking about such a helper for some time, but I never had really time to dedicate specifically to it so that that post actually arrived on purpose. However, it didn’t fit my requirements so this short note is about an alternative on Linux.
Introduction
If I work in the ~/tmp
directory, my primary prompt is
~/tmp
maurycy@kali>
which is the result of the following configuration in my ~/.bashrc
file:
export PS1="\w\n\u@\h> "
where
\w
displays the working directory, here~
\u
is the user name,maurycy
, and\h
is the hostname, herekali
The objective is to change the prompt so that if the working directory is a git repository, it will display the current branch and a short status (if there are files that had been added, removed, etc). Cherry on the cake, let’s have the branch name written in red if there are some pending commits to be done or in green if all is clean. The result is shown on the screenshot below:
How to modify the shell prompt dynamically
Changing the prompt is a matter of modifying the PS1
bash
environment variable which is displayed every time the primary prompt
is to be displayed. So:
maurycy> # current prompt
PS1="> "
> # new prompt
Now, if we want a dynamic content, that is a content that changes
every time the prompt is displayed, we have to replace the
"> "
by the output of a program. For example, imagine we want the
current time as a prompt, we can use the date
program with the
following parameter date +"%H:%M:%S"
. An important thing is that as
we need PS1
to be expanded every time it is displayed we need to use
single quotes instead of double quotes. Putting all that together, we
get:
> # The prompt is just '> '
> PS1='$(date +"%H:%M:%S")> '
09:41:40> # The new prompt that ...
09:41:41> # ... changes every time it's displyed
09:41:42>
One last point is that we also can use shell functions. For example:
> function get_date () { # define a function
> date +"%H:%M:%S"
> }
> PS1='$(get_date)> ' # invoke that function
14:23:29>
14:23:30>
So, now that we know how to change the prompt every time it is displayed, the only thing that remains to be done is to display the status of the git repository if the current directory is one.
Getting the status of a git repository
To get the status of a git repository, we can call the git status
command. However, the output is quite verbose and cumbersome to
parse. Hopefully, git also proposes the --porcelain
parameter which
purpose, according to the man page, is to be simple to parse and to
remain stable across versions. Perfect! An other interesting parameter
is -b
which prints the name of the current branch. In a repository
with only two freshly created files, we get:
maurycy@kali> LANGUAGE=en git status -b --porcelain
## master
?? f1
?? f2
~/tmp/demorepo
If your workstation is configured in a language different from
English, as mine in French, LANGUAGE=en
ensures everything will be
always written in English.
The leading ??
defines the status of each file. More generally, the
status is a XY
string X
and Y
can take the following values:
' '
(space) : unmodified?
: untrackedM
: modifiedA
: addedD
: deletedR
: renamedC
: copiedU
: updated but unmerged
The meaning of the couple XY
is described in the man page, but
basically X
represents the status of the index and Y
the status of
the working tree unless there is a merge conflict in which case X
and Y
represent the status of each side. As our goal is not to
provide the full information but just an insight of the changes that
had happened in the repository, we’ll only take into account the Y
part.
Calculating the prompt
Now we have all the building blocks to define our “git prompt”.
We will define a shell function, get_git_status()
, that will execute
the command LANGUAGE=en git status -b --porcelain 2>&1
and pipe the
output into an awk script which will parse it and emit a string
representing the status.
There are three possible states:
-
We are not in a repository: If the current directory is not a repository,
git status ...
prints a line stating withfatal
onstderr
; this is the reason why we redirectstderr
intostdout
with2>&1
above. -
We are in a git repository and there are non-committed modifications : In this case
git status ...
output has the branch name on the first line and, then, for every non-committed file, it prints a line with the statusXY
as explained above. We will concatenate theY
part into one string. At the end of the process, if the calculated status is non empty, it will be printed in red. -
We are in a git repository and all is clean: In this case
git status ...
just prints one line with the branch name. The calculated status will be empty, so it will be printed in green as there is no pending commit.
To print strings in red or green we’ll use the ANSI escape
sequences. They are of the form ESC[<color code>m
. For example in
the string "\033[31m"
, \033
is the escape character in octal and
the code 31
sets the foreground to red. ANSI escape sequences are
fully described on
Wikipedia; see more
specifically the
SGR
section and following.
Putting all this together, our get_git_status()
function looks like this:
function get_git_status () {
LANGUAGE=en git status -b --porcelain 2>&1 | awk '
BEGIN {
branch_name = ""
not_in_git = 0 # by default we are in a git repo
status = ""
}
{
if($1=="fatal:") { # in git repo
not_in_git = 1
exit
}
if($1=="##") { # format is : ## <branch name>
branch_name = $2
next
}
if(length($1) == 2) # Get the Y of the 'XY' status
car = substr($1, 1, 1)
else
car = substr($1, 0, 1)
if(car=="?" || car=="M" || car=="A" || car =="D" ||
car=="R" || car=="C" || car=="U") {
status = status car
next
}
}
END {
s = ""
if(not_in_git == 0) {
if(length(status) > 0)
s = " (\033[31m"branch_name" "status"\033[39m)"
else
s = " (\033[32m"branch_name"\033[39m)"
}
if(s)
print s
}
'
}
Displaying the prompt
Now that we have the get_git_status()
function displaying the prompt
is a matter of defining the PS1
variable:
Conclusion
This post was not only about setting the git repository status as a shell prompt. We also saw how to change the shell prompt dynamically, what are the possible statuses of a repository and how to get it and how to put some colors in a shell window. That was fun!
One last remark: while writing, I had to google for some information
as I’m not an expert of bash or git and I came across few scripts
doing the same; probably the most complete one is called
git-prompt.sh
. It can be found
here.