Today, I created git-create-deploy-branch
after kicking some of the ideas around for a couple years.
Git at first seems to be an ideal tool for deploying web sites and other things that don’t have object code. However, it’s never been that simple, and where there’s programming, there’s automating the tedious bits and creating derivative pieces from more humane sources.
With the addition of receive.denyCurrentBranch = updateInstead
in git 2.3.0, possibilities opened up for really reliable, simple workflows. They’ve since been refined, with a push-to-checkout
hook allowing built objects to be created on the receiving server, but I want a more verifiable, local approach.
There are two main strategies in git for dealing with this, and before git 2.3.0, those were really the only things available. In the first, git holds only the source material, and any built products are managed outside of git, whether as a directory of numbered tarballs or in a service meant for such things. Some services like the npm registry bring a lot of value, with public access and hosting and replication available; some are little more than object storage like Amazon S3. In the second approach, built products are committed back, and git becomes a dumb content tracker – conflicts in built files are resolved by regenerating them from merged source material, and the build process becomes integral to every operation on the tree of files.
I’ve long wanted a third way, using the branching, fast, and stable infrastructure of git, while keeping the strict separation of source material and built material. I want to be able to inspect what will be deployed, and inspect the differences between what was deployed each time, and separately, analyze the changes to the source material, yet still be able to relate it to the deployed, built objects. To that end, this tool can be considered a first attempt at building tools that understand the idea of a branch derived from another.
The design is simple enough: given a branch (say master
) checked out in your repository, with a build process for whatever objects need to exist in the final form, but those products ignored by a .gitignore
file, like so:
source.txt
:
aGVsbG8sIHdvcmxkCg== |
and a build script:
build.sh
:
|
and an ignore file, with both the built object and other things like editor cruft:
.gitignore
:
built.txt |
we create a file listing the files to skip excluding when creating the derived branch, like so:
.gitdeploy
:
built.txt |
The initial version of the tool is very simple, and doesn’t support wildcards or any other features of any complexity in the .gitdeploy
file. This is not out of a strong opinion, but as a matter of implementation simplicity, given that my prototype is written using bash
.
You can install it with npm:
npm install -g git-create-deploy-branch |
To create the deploy branch, we’ll run the build, then create the deploy branch with those objects present in our working directory:
./build.sh && git create-deploy-branch |
Our first run gives output like so:
[new branch] 8acba8787306 deploy/master |
and a branch deploy/master
is created, in this case with commit ID 8acba8787306
. We can show that it includes the built files:
:; git show deploy/master |
The commit also has the parent commit set to the current commit on master
, so we can track the divergence between master
and deploy/master
, both expected (with the built objects) and unexpected (errant commits made on the deploy branch).
Let’s update our source, and commit that:
source.txt
:
aGVsbG8sIHdvcmxkOiB3ZSBoYXZlIGNhbmR5Cg== |
The repository now looks something like this:
:; git graph master deploy/master |
And if we run the build and deploy again:
./build.sh && git create-deploy-branch |
We get output like so:
8acba8787306..16663a3ae945 deploy/master |
And our repository now includes a new merge commit, showing the origin of the deployed objects, and the prior deploy:
* 16663a3ae945 - (deploy/master) deploy master (68 seconds ago) <Aria Stewart> |
On a remote machine, let’s create a deploy repository, set it up to receive our deploys, and add it as a remote for us.
ssh remotemachine 'git init show-off-build && cd show-off-build && git config receive.denyCurrentBranch updateInstead && git checkout -b deploy/master' |
Now we can deploy this with a simple command:
git push remotemachine deploy/master |
So in total, deploying a new derivative of our source code consists of making our changes and committing them, then running the build and the command to create the deploy branch, then pushing:
git commit -m changes |
Stable, traceable, reliable, replicatable builds and deploys, stored in git but not cluttering the source branch.
Let’s see our handiwork:
ssh remotemachine cat show-off-build/built.txt |
And the response?
hello, world: we have candy |