This is a continuation of a previous post - How to Set Up a Hugo Site on Github Pages - with Git Submodules! I will reference some things we did there, so be sure to check it out if you’re confused about something.
In today’s post, I want to take a look at how we can keep our Hugo themes up-to-date. ‘But my theme works fine!’ you say. ‘Why would I ever need to update it?’ Well, when you download or clone a Hugo theme from its hosted git repository, you’re getting its latest version at that point in time. So even if the theme’s creator pushes updates to the code - with fixes, new features, and outside pull requests - you’ll still be stuck using the version you downloaded before. While this is fine in some cases, many of us want the newest updates so we can take advantage of a stronger, improved theme.
The key to this process is using git submodules to track and pull updates to a theme with just a few quick commands. If you followed my previous tutorial on adding a theme to your Hugo site as a git submodule, you already have a functional Hugo site with a (forked) theme included. Starting there, let’s see how to connect our theme to its remote source repo, which git commands we need to update it, and even how to safely make our own custom changes to the theme while keeping it in line with the source.
If we don’t intend to customize a theme at all, we can keep our workflow super simple. All we need to do is add the theme repo into our project as a submodule and periodically run a few git commands to check for updates. For the moment, let’s pretend we haven’t forked the theme repo and see what it would be like to clone the source directly. We’ll see an example with our fork in the next section.
Here’s a quick refresher command for adding a submodule to our project.
> git submodule add https://github.com/panr/hugo-theme-hello-friend.git themes/hello-friend
You might be thinking you can simply run
git cloneinstead of adding the theme as a submodule, but our site is already set up as a git repo so that doesn’t work here. Git doesn’t like having
.gitfiles in a subdirectory without a
.gitmodulescomponent in the root. For more on that, see this ‘simple dev’ post.
In the example above, we added our submodule by pulling the theme’s source repo directly into our project. With the
git remote command, we can easily verify that it’s connected to the correct remote repo.
> cd themes/hello-friend > git remote --verbose origin https://github.com/panr/hugo-theme-hello-friend.git (fetch) origin https://github.com/panr/hugo-theme-hello-friend.git (push)
And there it is! With this setup, all we need to do is pull updates straight from the source. From the theme’s directory, we run the following terminal commands.
# optional: fetch any updates to view changes before merging > git fetch origin > git status [status output here] # pull updates and merge them automatically > git pull
Since we’re not making any theme changes in this workflow, we shouldn’t expect any merging issues during the
git pull. We don’t really need to fetch or check the status of the repo, but it’s always nice to see incoming changes before we bring them in.
And that’s it! Getting updates every so often is no problem if we don’t intend to make our own customizations to a theme. But hey - customization is where the fun really begins.
Here’s the scenario - We want to add a Hugo theme, make custom changes, and still to get updates from the theme’s original source. The method used above doesn’t work here for a few reasons:
- We can’t push our custom changes to the theme’s source repo. We simply don’t have those permissions on the repo. Our local changes will not be safely stored anywhere, and any pulled updates may not be easily mergeable.
Incoming updates may require merges. If we’ve modified some of the theme files that are being updated in the source, then a simple
git pullwill not be enough to deal with the resulting conflicts.
Thankfully, a more advanced git workflow would help deal with these issues. Remember how we set up a forked theme repo in the last post? Now we get to take advantage of it. Crucially, the forked repo is ours, so we have full write access to it and don’t need to worry about repo permissions. We can do whatever we want.
However, our fork doesn’t automatically receive source updates either! Here, an additional step is needed to link our fork back to the source repo. Earlier, in the direct update workflow, we used the
git remote command to verify that we had the correct remote repo connections. Running it in our forked theme directory, we see only the fork as our remote.
# my forked repo URLs as an example > git remote --verbose origin https://github.com/aormsby/F-hugo-theme-hello-friend.git (fetch) origin https://github.com/aormsby/F-hugo-theme-hello-friend.git (push)
To get updates from the source repo, we need to add a second remote repo - yes, git can do this! We simply run this command in our working directory and check our remotes again to verify the connection.
# add a new remote repo connection > git remote add upstream https://github.com/panr/hugo-theme-hello-friend.git > git remote --verbose origin https://github.com/aormsby/F-hugo-theme-hello-friend.git (fetch) origin https://github.com/aormsby/F-hugo-theme-hello-friend.git (push) upstream https://github.com/panr/hugo-theme-hello-friend.git (fetch) upstream https://github.com/panr/hugo-theme-hello-friend.git (push)
Ta-da! Our local theme repo is now linked to both our forked repo and the source repo, which we called
upstream. (This is a conventional label, but any name you choose is fine.) At this point, our setup already supports getting updates from the source, but the process is a little different than before.
Normally, when we’re updating directly from a single remote repo, all we need to do is
pull the data to our local directory so that the local matches the remote. We’ll still do that here, but we explicitly pull data from the
upstream repo we added. We’ll also have to make an additional
push to our forked repo -
origin - to update it with the new code. Here’s how that works compared to the previous example.
Assuming you’re still on your
master branch, here are the modified commands needed to pull and push the updates.
# optional: fetch any updates to view changes before merging > git fetch upstream master > git status [status output here] # pull updates and merge them automatically > git pull upstream master # push source updates to forked repo > git push origin master
With no local changes to our theme, the commands run smoothly with no errors - and we want to keep it that way! Simple is good, and an update process that doesn’t result in merge conflicts is always nice. But how do we manage to avoid conflicts if we want to customize the theme? Simple - don’t do your work on
Although you technically could make theme customizations on the
master branch, this increases the chances of having to resolve merge conflicts every time you try to pull an update. It’s fine if that’s how you want to work, but I highly recommend avoiding it. In my experience, it’s often best to keep
master sparkly clean and do your dirty work on a different branch.
For example, I do my theme in a separate branch that is always kept in parallel with
master. I’ve named it
working, as it’s the branch that I work in. (Not exactly clever, I know.) Since it has all of my theme customizations, it’s also the branch that I use when I build my site. Every so often, I switch back over to
master, go through our update process, and then explicitly merge any new theme updates from
working so that I have all the latest features in my working branch. Visually, it’s kind of like this.
This is just one possible theme customization workflow, but we can benefit from a setup like this. For example –
- Our workspace is separate from our update environment. Getting theme updates will always be a painless process, and we have more control over merging when the time comes.
- Commit histories are clearer. Histories are much easier to read if source updates and custom work don’t get tangled up, and this can be super helpful when performing more advanced git actions on a branch.
- All the data is on our forked repo. The customizations and the pulled updates all live on our fork, which means we have full control of the data.
We can test on
masterbefore merging. If we’re unsure of how a new feature will display on our site, we can run a build using the
mastercode. It’s a nice clean test bed before performing a merge.
The drawbacks are much the same as I mentioned in the last post with the addition that now we’d have at least one more working branch to manage. However, I’ve found this parallel branch workflow quite nice after getting more familiar with it. Perhaps you will, too.
Here are the commands we can use to get our
working branch up and running. (You can choose a better name if you’d like.)
# create a new branch and switch to it > git branch working > git checkout working
We make some changes, commit our work, and push to our forked repo (see these commands for help). Time passes… and we want to get some updates on
# switch to master branch > git checkout master # quick update > git pull upstream master > git push origin master
Finally, we switch back to
working and merge in the updates from
# switch to working > git checkout working # merge master into current branch (working) > git merge master
I won’t get into resolving a merge here, so let’s cross our fingers and hope there are no conflicts to deal with.
So yes - customizing your theme with this kind of workflow does take a bit of setup, and there are a few extra steps and things to keep track of to manage our code well. However, using git submodules and a parallel branch workflow do give us more leverage over our codebase, and I think that makes the extra work worthwhile. I’ve been using this workflow with my site for some time, and I don’t think I’d change anything about it.
That said, there are a number of other methods that people use for managing their themes, and it’s worth exploring them to see what fits your needs best. I recently read a post about managing your themes as Hugo modules by Nick Gracilla that I found quite interesting, and I think it’s worth checking out if you’re looking for an alternate way of doing things.
- Better Deploy - I’m still working on a better site deploy script! It’s looking good so far, but it’s not quite ready.
- Automatic Builds - This would still be great to have, so if I get around to it I’ll be sure to share my work.
Until next time! ~