git cd

Change directory relative to repository root

This post describes a simple but neat trick I used to implement a git cd alias that acts like cd relative to the repository root. You can skip ahead to the last section if you just want the code, or read the rest for some background on why the obvious implementation of this alias doesn’t work.

git cd

Git allows you to specify your own aliases in your .gitconfig. The basics are quite straightforward.

[alias]
   amend = commit --amend

The above alias would let you write git amend which would then be equivalent to git commit --amend. You can also write more advanced aliases by starting the command with the ! character and writing any valid external command thereafter. Specify an alias like this: hello = !echo "Hello world!" and an invocation of git hello would print Hello world! to the command line.

So far so good. That makes the implementation of git cd seem fairly straightforward. Getting the root folder of a Git repository is possible with rev-parse:

git rev-parse --show-toplevel

Now it seems pretty clear that an alias for changing directory relative to the root can be implemented approximately like this:

cd = !cd `git rev-parse --show-toplevel`/"$1"

Why doesn’t it work?

The cd alias above looks like it should work. Let’s test it out in a simple repository that looks like this:

du@testhost:~/testrepo$ tree
.
├── f1
│   └── smartcode.c
├── f2
│   ├── legacycode.c
│   └── subf2
│       └── arcane_magic.c
├── f3
│   └── library.c
└── main.c

4 directories, 5 files

Here goes:

du@testhost:~/testrepo$ cd f2
du@testhost:~/testrepo/f2$ git cd f1
du@testhost:~/testrepo/f2$ git cd
du@testhost:~/testrepo/f2$

The alias doesn’t work. There’s no error, but we remain in the same folder no matter what. As it turns out, this is because Git aliases get executed in a Subshell. When you run a Git alias that calls an external command, Git spawns a subshell and runs the external command there. This means that the alias successfully runs cd in the subshell before returning to the parent shell - the subshell’s directory is changed, but not that of our shell.

A quick test shows that making a separate git-cd script somewhere in your PATH suffers from the same problem.

Solution: Bash Function

To get the cd working, I need to run it in the same shell. This can be done by making it a Bash function. So my solution is to add a function in my .bash_aliases, and to let Git take care of anything that is not git cd as usual.

gitcd()
{
    if [ "$1" == "cd" ]; then
        git rev-parse --show-toplevel > /dev/null 2>&1   
        if [ $? == 0 ]; then
            cd `git rev-parse --show-toplevel`/"$2"
        fi
    else
        git "$@"
    fi
}
alias git=gitcd

The gitcd function simply invokes git if the first argument is not cd. In the cd case, the return value from git rev-parse is checked in order to avoid doing anything in folders that are not in a Git working tree. With this code, git cd works as expected, changing the directory relative to the repository root.

 
comments powered by Disqus