git-cd

Posted on 22 March 2014

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:

On the other hand, I haven’t found anything like this neither in git/contrib nor bashrc files of any GNU/Linux distribution.

Do you have a comment or question? You can contact me.