Sometimes, it is interesting to automatically push after every commit. In this blogpost, I’ll show you how to accomplish this with a post-commit hook in git. We’ll also look at git -C and two interesting environment variables: GIT_DIR and GIT_WORK_TREE . We will use my favourite password manager, password-store, as context for the examples.

Password-store is an application that uses PGP to keep your passwords (and other data) secure. You enter your secret data into a text file which pass then encrypts with your public PGP key and stores in a folder. You can also set up this folder as a git repository. If you do so pass will create a commit in for every change you make. By using pass git push you can then push your changes to other devices.

To automagically call pass git push after every commit we can create a post-commit hook to trigger an action at the right moment. Because pass makes a commit for every change, this means that we can call a program after every edit.

To do this, create a file .git/hooks/post-commit in your password store and set its content to:

#!/bin/sh
set -x
git pull --rebase # get edits by other devices
git push          # send the latest commit

We use git here, instead of pass git, because hooks always execute in the root of the password store repository. To make it more likely that the hook succeeds, we pull in remote changes first. Thanks to the set -x we ensure that no push will occur if the pull failed.

Use git -C to set a working directory

Existing pass users may have noticed that pass git acts like git in the password store, even when working elsewhere in the filesystem. How can pass git do that? Well, it can first cd into that directory and then execute the git actions. So basically:

cd "/path/to/folder/"
git $@

We can also accomplish the same with git -C, like how pass does it internally. The following is equivalent1 to the previous block of code:

git -C "/path/to/folder/" $@

Bonus: GIT_DIR and GIT_WORK_TREE

We can also combine the GIT_WORK_TREE and GIT_DIR environment variables to achieve the same result:

  • GIT_DIR specifies the location of the .git directory of the repository want to interact with
  • GIT_WORK_TREE contains the path to the root of the repository (that is not bare)

So, the following command stages /path/to/folder/file in the git repository in /path/to/folder no mater where we are in the filesystem.

GIT_DIR="/path/to/folder/.git" GIT_WORK_TREE="/path/to/folder" git add file

If we only want to inspect the history, GIT_DIR will suffice. As an example, the following will act like git log executed in /path/to/folder/:

GIT_DIR="/path/to/folder/.git" git log

Although these environment variables look cool, they are also non-trivial. If you want to carry out more than one action on a repository, and don’t need stuff from other places it might be best to just cd into the right directory or use git -C. Combining these environment variables with -C can lead to unexpected results as the environment variables GIT_DIR and GIT_WORK_TREE will be interpreted relative to the path given to -C. If you are using git in a script that may be used in bizarre environments, you should clear these environment variables like pass does:

unset GIT_DIR GIT_WORK_TREE GIT_NAMESPACE GIT_INDEX_FILE GIT_INDEX_VERSION GIT_OBJECT_DIRECTORY GIT_COMMON_DIR

Final notes

The approach laid out here to push changes does not poll the remote repository for incoming changes. I personally do not need that, because I can just pass git pull whenever a login fails. I almost always have internet when trying to log in to places, and thanks to the post-commit hook, the latest passwords are always in the remote.

Sources

  1. Almost, after executing this, your current working directory remains the same.