Git version 2.4 (from 2015) introduced a new feature called push-to-deploy. I find this quite useful when working with software on a server, and I wanted to briefly outline how this works.

There are two types of git repositories: bare and non-bare. A bare repository is one that contains nothing but the .git folder; in other words, it has the index but lacks the actual working files. A non-bare repository is what you’re used to working with, which includes both the git index and the checked out copy of working files. Typically, a bare repository is used as a remote; we push changes from our working non-bare repository into a bare repository.

In earlier versions of git, it was not possible to push changes to a non-bare repository. This is for good reason. If you push changes to a repository that had working copies checked out, those working files could be dirty, or in other words, they could have changes that have not yet been committed. It could be bad news if someone pushed to a repository that someone else was working on. This was simply not envisioned as a way to use git.

But actually, there is a situation where pushing to a non-bare repository make sense: when that non-bare repository is not being actively worked on, but those files are being used for something else (essentially, as read-only files). For example, perhaps they are being served as a website. Previously, you could accomplish this by using a hook. You would set up a bare repository and you would push your changes there, and then use a hook on that repository such that on an accompanying non-bare repository would automatically pull those changes, checking out the latest version and updating the working tree. In this way, you could push changes and by so doing deploy a website.

With push-to-deploy, this becomes easier by cutting out the need for two repositories. Instead of pushing into a bare repository, one may now configure a non-bare repository to allow pushes by setting the configuration parameter receive.denyCurrentBranch to the value updateInstead. With this configuration change, a push to this non-bare repository will update the .git index, and also check out the latest updates into the working tree. This could then be served as a webpage, for example.

It’s useful beyond serving web pages: I’m using this now on a remote compute server. I keep local clones of my repositories, where I do my actual work. But I run the code on the server. To get my updates from my local computer to the server, I now use push-to-deploy. I’m not intending to make any updates to the repositories on the server directly; instead, I consider these read-only repositories. However, I need them to actually have the working files checked out and available, so that I can reference and run the code on the server, so they cannot be bare repositories.

Practically, the way I do this is to set a global configuration option on my server like so: git config --global receive.denyCurrentBranch updateInstead (you can also do this per-repo)

Then, in my local repository that I want to push to the server, I add a second remote that points to the .git index of the repository on the server, like this:

remote add server server:path/to/remote/repo/.git (server can be anything)

Then I can simply say git push server HEAD and it will automatically push my changes to the server, and update the working tree so that the latest files are checked out and active. It does this right over SSH using authorized keys, so the workflow is quite fast to deploy new code.