Prompting bash with git and jj
· 2 min read · May 20, 2026 · #linux #tech I recently decided to make a push on trying out jujutsu as an alternative to git.
Being easily distracted by side-quests I decided that I should sort out my bash prompt so that it displayed similar repository status information inside a jj repository as it did when inside a git repository. This inevitably meant reworking how I set my prompt, and finding a number of dead-ends online. So here’s my attempt at it, presented top-down like I’m some kind of Scheme programmer or something.1
First, the key setting: PROMPT_COMMAND, a sequence of commands run before PS1 is rendered:
PROMPT_COMMAND='__prompt_jjgit; _bash_history_sync'Ignore _bash_history_sync for now,2 __prompt_jjgit is the function of interest—it attempts to determine whether it’s presently inside a git or jj repository or not, before setting PS1 to the appropriate status topped and tailed with codes to set HOST@USER:PATH into the window title and HOST@USER:CWD in the prompt, and the traditional $/#.
First, determine if inside a git/jj repository:
pwd_in_jjgit() {
# echo "jj" or "git" if either is found in $PWD or its parent directories
# using the shell is much faster than `git rev-parse --git-dir` or `jj root`
local D="/$PWD"
while test -n "$D"; do
test -e "$D/.jj" && {
echo jj
return
}
test -e "$D/.git" && {
echo git
return
}
D="${D%/*}"
done
}Then setup the base prompt, USER@HOST:CWD with a little light highlighting via some ANSI escape codes:
__prompt_jjgit() {
pwd_in_jjgit() {
...
}
PS1="\u@\h:\[$WHITE\]\W\[$COLOR_OFF\]"…add the repository status if needed:
case "$(pwd_in_jjgit)" in
jj) PS1="$PS1$(__prompt_jj)" ;;
git) PS1="$PS1$(__prompt_git)" ;;
esac…set the title if we’re in some sort of GUI window:
if [[ $TERM != "dumb" ]]; then
PS1="\[\e]0;\u@\h:\w\a\]$PS1"
fi…and finally, top and tail it with the usual $/#:
PS1=": $PS1\$; "
}Note the extra wrapping in : ... ; . The : and ; mean that whatever is in the prompt should be treated as parameters to be discarded to the no-op function :—this makes it easy to select & paste entire lines without worrying about having to avoid selecting the prompt.
Finally, provide the two functions that actually invoke git or jj as appropriate.
First, git
__prompt_git() {
GIT_PS1_SHOWDIRTYSTATE=true
GIT_PS1_SHOWSTASHSTATE=true
GIT_PS1_SHOWUNTRACKEDFILES=true
GIT_PS1_SHOWCOLORHINTS=true
GIT_PS1_SHOWUPSTREAM="auto"
GIT_PS1_COMPRESSSPARSESTATE=true
printf "%s" "$(__git_ps1 "#%s")"
}…as provided by some suitable package:
if [ -r ~/.git-prompt.sh ]; then
source ~/.git-prompt.sh
elif $NIXOS; then
gitdir=$(dirname $(readlink -f $(which git)))
source ${gitdir}/../share/git/contrib/completion/git-prompt.sh
source ${gitdir}/../share/git/contrib/completion/git-completion.bash
else
for d in /etc/bash_completion /etc/bash_completion.d; do
if [ -d $d ]; then
[ -r $d/git ] && source $d/git
[ -r $d/git-prompt ] && source $d/git-prompt
fi
done
fiSecond, jj by invoking it with a suitable template:
__prompt_jj() {
# can't use environment variables from [colours.sh](https://github.com/mor1/rc-files/blob/main/scripts/colours.sh) in template due to surrounding by ''
# \e[N;C] ANSI escape code, https://en.wikipedia.org/wiki/ANSI_escape_code
# N= 0[reset] 1[bold] 2[faint]
# C= 32[green] 35[purple] 37[underline white]
printf "%s" "$(
jj log --no-graph --ignore-working-copy --color=never --revisions @ \
--template 'surround(
"(",
")",
separate(
" ",
bookmarks.join(", "),
surround(
"\\[\\e[1;32m\\]",
"\\[\\e[0m\\]",
change_id.shortest(),
),
surround(
"\\[\\e[2;35m\\]",
"\\[\\e[0m\\]",
commit_id.shortest(),
),
surround(
"\\[\\e[4;37m\\]",
"\\[\\e[0m\\]",
truncate_end(15, description.first_line().trim(), "..."),
),
if(conflict, label("conflict", "×")),
if(divergent, label("divergent", "??")),
if(hidden, label("hidden prefix", "(hidden)")),
if(immutable, label("node immutable", "◆")),
coalesce(
if(
empty,
coalesce(
if(
parents.len() > 1,
label("empty", "(merged)"),
),
label("empty", "(empty)"),
),
),
label("description placeholder", "*")
),
)
)
' 2> /dev/null
)"
}And that’s it. I’ll probably keep tweaking it but it seems to work OK for now at least.