Recently, I worked on a visual object tracker implemented in C++. The project has several compile configurations and a time-consuming test suite, some parts of which need a GPU not present in my laptop. Because many parts of the code depend heavily on compile configuration, changing them requires recompilation and ideally also retesting of all configurations to make sure that everything works correctly. Since my development laptop not only lacks the GPU needed for testing but also has only four cores, which makes the compilation unnecessarily slow, I looked for an easy way to compile and test the code being edited on a powerful remote server.

Initially, I tried to use git commit and git push to capture the state of the working directory and transfer it to the remote server, but this was not ideal for many reasons. Then I discovered git stash create, which captures the state of the working directory and creates a “volatile” commit object not attached to any branch. This command became the basis of my git-sarah script, which is an acronym standing for git stash and run at host, and you can see find its code below.

With git-sarah script, instead of instructing my editor or IDE to run:

make

I instruct it to run the following:

git sarah example.org dir -- make

This command copies the current state of the work tree to $HOME/dir on the example.org server and runs make in the directory corresponding to the current directory on the local host. For example, if the current local directory is ~/projects/kcf/src, with kcf being the root directory of the git repository, the make command will be run in $HOME/dir/src on the server. In the output (both stdout and stderr) of the command, the path of the server directory, e.g., /home/mylogin/dir is replaced with its local counterpart: /home/me/projects/kcf.

If a single command (make) is not enough, you can instruct git-sarah to run commands from a script (e.g., commands.sh), which need not to be stored in your git repository. For that, run:

git sarah example.org dir -- sh < ./commands.sh

The code of git-sarah (further development happens on GitHub) is quite simple:

#!/bin/bash

set -e -o pipefail

OPTS_SPEC="\
git sarah <host> <remote_dir> [ -- ] [ command ... ]

git stash and run at host
--
h,help    show the help
"
eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
echo "$*"
while \[[ $# -gt 0 ]]; do
    case "$1" in
        -h) exit 0;;
        --) shift; break;;
    esac
    shift
done

HOST=${1:?Host not specified}; shift
DIR=${1:?Remote directory not specified}; shift
PREFIX=$(git rev-parse --show-prefix)

worktree=$(git stash create)
worktree=${worktree:-HEAD}

git push -f $HOST:$DIR $worktree:refs/heads/git-sarah

# Use ssh to translate possibly relative $DIR to the absolute one.
ABS_DIR=$(ssh "$HOST" "cd '$DIR' && pwd")

# Run the command at $HOST and translate its output so that
# file/directory names appear as on localhost. Use perl's \Q to limit
# the possibility of interpretating ABS_DIR as a search pattern.
ssh "$HOST" "cd '$DIR/$PREFIX' && git -c advice.detachedHead='' checkout --detach git-sarah && ${*:?Command not specified}" \
    |& perl -pe "s|\Q${ABS_DIR}\E|$(git rev-parse --show-toplevel)|g"