Saturday, March 11

git-do: execute commands in the top level of a git repo

tl;dr: Use git config --global alias.exec '!exec '. Or for some reason you could write a script like this one to execute commands in the root of a git repository.

This blog lives in a git repository with entries broken up by date. So, for example, when editing this entry, I might be in a path like so:

~/p1k3/archives/2017/3/11/

I also have a Makefile in the top level of the repo, which contains targets like render and publish. Since make expects to find a Makefile in the current working directory, I often found myself in a loop where I edit a blog entry, change to the top level of a repo, run a make command, and then need to change the blog entry again.

This seems silly, and generalizes to a bunch of different repositories.

Stack Overflow says that you can do git rev-parse --show-toplevel to get the root directory of the repository, which led me to something like:

$ git config --global alias.root "rev-parse --show-toplevel"
$ cd `git root` && make render && cd -

That works, but it’s a stupid amount of typing. There are a couple of solutions for this. One, from that SO post, is:

$ git config --global alias.exec '!exec '
$ git exec make render

This works because the leading ! on the command tells git to treat it as a shell command, and by default git executes aliases to shell commands from the top-level directory of the repo. This is probably good enough, but I didn’t notice it at first so I added a git subcommand called git-do to do pretty much the same. A very simple version looks like this:

#!/bin/sh

git_root=`git rev-parse --show-toplevel`
cd "$git_root"
$@

A slightly more “sophisticated” one uses git-sh-setup, which provides some boilerplate for shell scripts written as git subcommands:

#!/usr/bin/env bash

# Help messages:
USAGE="<command>"
LONG_USAGE="Executes <command> from the root of the repository."

# Tell git-sh-setup we're ok with being in a subdir:
SUBDIRECTORY_OK=1

source "$(git --exec-path)/git-sh-setup"

# Make sure we're in a working tree, jump to top level, do command:
require_work_tree_exists
cd_to_toplevel
$@

In both cases, $@ is a variable containing all of the arguments to the script, which run as shell commands.