While I was going through a 3rd party git repository the other day, it occurred
to me that it would be nice to have shell command git-cd
, which would be
like cd
, but working with file paths relative to current git repository. And
it turned out that this can be relatively easy to implement in bash.
First of all we need to figure out a path to the current git working tree, which can be easily shown like this:
$ git rev-parse --show-toplevel
Btw I defined git alias root
for this command in my .gitconfig
, but it’s
not necessary for functionality of git-cd
itself.
Since we need to change current working directory within a shell session, there
is no other way than to create a shell function (defined eg. in ~/.bashrc
):
git-cd()
{
if ! GIT_ROOT=$(git rev-parse --show-toplevel); then
return 1
fi
if [[ $# = 0 ]]; then
cd "${GIT_ROOT}"
elif [[ "$1" = - ]]; then
cd -
else
cd "${GIT_ROOT}/$1"
fi
}
The function works like this: when git-cd
is called without any parameters,
it changes current working directory to a root directory of the current git
repository (assuming we are in some git repo). Otherwise it tries to change
directory to given directory, interpreting it’s path relative to the repo:
$ cd /home/martin/projects/nitrate/trunk/nitrate/docs
$ git root
/home/martin/projects/nitrate
$ git-cd design/Milestone
$ pwd
/home/martin/projects/nitrate/design/Milestone
$ git-cd
$ pwd
/home/martin/projects/nitrate
Even though this works well already, such function would not be complete without bash completion support. Luckily this isn’t hard to do either:
# bash autocompletion for git-cd
_git-cd()
{
if ! GIT_ROOT=$(git rev-parse --show-toplevel); then
return 1
fi
# current word to complete
local CUR=${COMP_WORDS[COMP_CWORD]}
# remove absolute paths
if [[ "$CUR" =~ ^/ ]]; then
CUR=${CUR#"/"}
fi
COMPREPLY=($(cd $GIT_ROOT; compgen -S '/' -d $CUR))
}
complete -o nospace -F _git-cd git-cd
And this brings us to usable solution :-)
One small problem (for some people) could be that the shell function can’t
be directly called via git cd
because it’s not a script, but a shell
function. While this doesn’t bother me, it could be workarounded by definition
of another shell function git
, which would work as a wrapper:
git()
{
if [[ $1 = cd ]]; then
git-cd "$2"
else
/usr/bin/git "$@"
fi
}
But that would break the bash completion. This is most likely not impossible to solve, but I don’t personally consider that to be worth the effort :)
And obviously, I’m not the first one with an idea like this, see for example:
- a cd command for git projects
- jumping to git roots
- cdgit cd relative to git workdir root
(this example is interesting especially for users of
zsh
, as it contains a reference to one liner solution …)
On the other hand, I haven’t found anything like this neither in git/contrib
nor bashrc files of any GNU/Linux distribution.