Using git subtree with gh-pages


I recently switched to using Hugo to generate this site and needed a strategy to publish it. Previously I was using Octopress which has scripts for managing the gh-pages branch automatically. The documentation on shows a workflow that uses git subtree, a tool I knew about but had never actually used before. I found the tutorial on the page to be confusing. Here, I describe the approach I used instead.

My goal is to have a clean gh-pages branch that contains the minimum content it needs to have a meaningful history, and a master branch that makes sense.

The first thing to do is generate the site in public/ by running hugo while on master and in the root directory of the repository:

$ git checkout master
$ hugo

At this point, create an orphaned gh-pages branch:

$ git checkout --orphan gh-pages

Note: the gh-pages branch won’t show up in git branch yet. Don’t worry about that.

If you check git status you’ll see a bunch of staged files–the contents that were in master. Unstage these with git reset:

$ git reset .

Now everything that was staged will be untracked. Clean up those untracked files with git clean,  excluding public:

$ git clean --force -d --exclude public

Now you should see public/ as the only untracked directory.

Of course, GitHub Pages wants the content that’s currently in public/ as the root of the gh-pages branch. Let’s make that happen. Copy the contents of public/ to the current directory with the tool of your choice, like mv:

$ mv public/* .

At this point you can safely get rid of public/,  so run git clean again, this time only including public:

$ git clean --force -d public # or just rm -r public

Now you’re in a position to add everything that was in public/, and is now in the current directory, to make the initial commit. I used “Generate site” as the message:

$ git add .
$ git commit -m "Generate site"

Note: Only after making the above commit will the gh-pages branch appear in git branch.

Now push the gh-pages branch to origin:

$ git push -u origin gh-pages:gh-pages

At this point your GitHub Pages site will be generated by GitHub, and will be live shortly.

The following steps are what we’ll do to make re-generating and updating the site an easy affair. First, check out master:

$ git checkout master

Now that the branch is on the remote, we’ll add it as a subtree under public/ on master. Be sure to replace the <placeholder> with your own repo:

$ git subtree add --prefix public <repo_url> gh-pages

If you ls public you’ll see the content generated by hugo, and if you git log -2 you’ll see the commit you made on gh-pages in addition to the commit that added the subtree to master. Push master and you’re all set:

$ git push origin master:master

Now each time you update something in content/:

As so:

$ git add content
$ git commit -m "Added insightful post on the social lives of kittens"
$ hugo
$ git add public
$ git commit -m "Regenerate site"
$ git subtree push --prefix public <repo_url> gh-pages
$ git push origin master:master

Now when you look at the history of gh-pages,  all you’ll see are the generate/regenerate commits:

$ git fetch gh-pages
$ git log gh-pages
102c92b 6 minutes ago Kris Hicks | Regenerate site
af759b6 10 minutes ago Kris Hicks | Regenerate site
69045e6 2 hours ago Kris Hicks | Generate site

Whereas on master you’ll see the changes to the content, in addition to the changes on the subtree:

$ git log master
1df64be 7 minutes ago Kris Hicks | Regenerate site
d244956 8 minutes ago Kris Hicks | Update phone-interviews post with HN link
66c4e29 11 minutes ago Kris Hicks | Regenerate site
bea8982 11 minutes ago Kris Hicks | Update rerere post
456c79e 14 minutes ago Kris Hicks | Add static/
cfb98dc 18 minutes ago Kris Hicks | Add 'public/' from commit '69045e6db8a929e0476c7553917981e3c9545c4e'
69045e6 2 hours ago Kris Hicks | Generate site
daf97fc 3 hours ago Kris Hicks | Add keybase.txt
483bfec 3 hours ago Kris Hicks | Add CNAME

This makes more sense to me than the approach listed on the tutorial, which leaves you with weird merge commits and messy history otherwise.