I love git and I use it all the time. For my personal projects, my dotfiles, my documents and even for my home directory. But for my work, I need to use mercurial, as well as git(mercurial for mozilla-central mono-repo and git for other repositories hosted separately in GitHub mostly.) I like mercurial too. But there’s a problem, whenever I switch between those version control systems, I always forget which one I’m using right now. For example, when I switch from git repo to mercurial repo, use git command instead of hg. Then I change my command back and run the correct command after getting an error that says this is not a git repository… That was driving me crazy for a while, then I came up with a bash function that will help me not to re-write the whole command and will act like the version control of that repository.1 It’s not perfect and doesn’t solve everything. But it solves most of my issues, so I wanted to write it in case someone else needs it. By the way, for the background, I’m using g alias for git command, and h alias for hg command.

Proxy function that acts like it’s git or hg depending on your repository

Let’s see the whole function first:

f() {
    if [ -d “.git” ]; then
        git “$@“
    elif [ -d “.hg” ]; then
        hg “$@“
    else
        if git rev-parse —git-dir > /dev/null 2>&1; then
            git “$@“
        elif hg root > /dev/null 2>&1; then
             hg “$@“
         else
            echo “Not a git or hg repository!”
        fi
    fi
}
alias g=“f”
alias h=“f”

As I told you before, I’m using g and h, that’s why I aliased this f function to them at last. So, now, whenever you type a git or h command, that function will take that command as a proxy and then dispatch it to the corresponding version control system. I was using g and h commands before this function and that’s why I wanted to keep them and aliased to this. But if you want to, you can remove the aliases and just use f function itself(or you can rename and use it at your own convenience).

Now if you run g status in a hg repository. That command will automatically convert it to hg status and you won’t get any error that says “not a git repository bla bla…”.

Now let’s go through the function and see what’s going on there.

if [ -d “.git” ]; then
    git “$@“
elif [ -d “.hg” ]; then
    hg “$@“

This is the first part of our function. That will look at your current directory and see if there is .git or .hg directories inside. If there is one of them, that means we found our version control system since these are the directories that contain all the information about the repository. But that part will only work if you are in the root directory of your repository. But you may be a few level inside of that root directory. So if we fail to find the .git or .hg, we go inside of the else branch:

if git rev-parse —git-dir > /dev/null 2>&1; then
    git “$@“
elif hg root > /dev/null 2>&1; then
        hg “$@“
    else
    echo “Not a git or hg repository!”
fi

In the first if condition, we have git rev-parse --git-dir > /dev/null 2>&1, that command goes through from your current directory to your root directory and tries to find a .git directory. If it finds one, returns it, if it doesn’t find any, it says “not in a git repository” and fails with an error return code”. Since we have > /dev/null 2>&1 at the end, we won’t see any output during execution, it will pass or fail gracefully. So with that command we will be able to see if we are inside a git repository.

elif branch works very similar also, it tries to find root hg directory of the current directory. Returns that dir if it finds it, fails if it doesn’t find it.

So that way we manage to find the current repository of that directory. You may be wondering why we have the outer if, since we can also use the inner if as a more general solution and get rid of the outer one. These commands are pretty fast, but checking a directory and seeing if that’s present is much more faster. So I wanted to keep this outer if as fast track, since we mostly run our git/hg commands in the root directory.

But the commands are not the same!

There is another thing. Let’s say that you wrote g show in hg repository. That will automatically make it a hg show. But the problem is, there is no show command in mercurial :(. So hg will try to run the closest command to that and that is showconfig. So it will print all the config, but we didn’t want that. We wanted to see the latest revision. At this point, aliases come to the rescue! You can create a show alias in mercurial to act like hg log -pr if you want. The simplest alias would be like this in your .hgrc file:

[alias]
show = log -pr

That’s the simplest solution, But it will fail if we enter h show because we didn’t put any revision number after that. We can make it a little bit more sophisticated like this:

[alias]
show = !sh -c 'REV=$1; hg log -pr ${REV:-tip};' -

In this alias, we are running a shell command directly with !sh -c command. Then, we are doing argument defaulting in ${REV:-tip}. If nothing is passed to the command, it will print hg log -pr tip, if a revision number is passed it will print hg log -pr <revision>.

So yeah, I hope you got the idea. My example was about using the git command in hg. But the opposite is also possible with adding an alias to your .gitconfig file. That way you can create all the commands in git or hg that you use frequently so you won’t have to rewrite everything. Of course, sometimes it’s not possible to create an alias for that specific command you use. But hey, I didn’t say that my solution solves everything :). But it does solve majority of my problems, I hope it does yours as well!


  1. You may ask about using git-cinnabar for mercurial repositories. I know that git-cinnabar project is also pretty solid and has a great maintainer, but I personally don’t like using third-party tools to interact with my repositories. [return]