CircleCI Hacks - Automate the Decision to Skip Builds Using a Git Hook

Developers use CircleCI to build all sorts of projects. Many of those projects
follow the typical one project per repo approach to code organization. Some
include documentation, logs, vendor packages, deployment scripts, and
more. Then there are the monorepos that companies like Google use. The added
complexity that these repos bring leads to a different workflow for many of our
customers.

There are times where customers don’t want CircleCI to build a trivial
commit. Maybe they’re simply adding a note to the README, or a log file was
updated. Not building can save container time for the commits that actually
need it. CircleCI covers this by supporting the [ci skip] or [skip ci]
“flags” in a commit message. If this is needed frequently, prefixing commit messages
with [skip ci] every time can get annoying fast. This can be automated with Git hooks.

What We’ll Be Using

Git Hooks

For those not familiar, Git has a system of hooks. These are points in time,
before or after a specific action in Git where it can run a script. For example, let’s say
that every time a commit is made, we wanted to make sure we are working with
the latest upstream code. A Git hook could be utilized that updates master and rebases
current branch on master before Git saves the commit.

[skip ci]

As mentioned previously, CircleCI supports the CI standard commit flag of
[skip ci]. When this flag is present in a commit message, CircleCI will not
run a build. This can save on the number of containers running and total build
minutes used.

Using a .ciignore File

To mimic Git’s use of the .gitignore file, we’re going to create the concept
of a .ciignore file for this blog post. The Git hook we’re creating will use
this file to know which files, directories, and patterns should be ignored when
running builds

Implementing .ciignore In a Repo

The scenario we’re using in this post is a .ciignore file in the root
directory that will tell Git that we don’t want to do a build on CircleCI when
the only changes in a commit are in the logs/ directory. The root of our repo
could look something like this:

The script starts by checking for the existence of .ciignore. If it
doesn’t exists, that’s cool. It’s business as usual.

We then call git diff to get a machine readable list of files that are
staged (going to be changed in this commit).

We load .ciignore for the list of patterns to ignore.

We loop through the changes, removing a change whenever it matches an ignore
pattern.

If we still have changes, this means we need to actually build the commit so we exit.

If we reach this step, we’re going to prefix the commit message (stored in
file $1), with [skip ci], saving us some build minutes.

Steps 4 & 5 are important because even when a change happens in a directory we
want to ignore, if the main source code also changes we still want to build.
Otherwise we could end up in a scenario where we add a feature and update a log
File in the same commit, and it never gets built meaning the feature’s roll-out now gets
delayed.

Notes

One thing to keep in mind is that hooks don’t travel with the repo. What this
means is that client-side hooks, what we’re using here, don’t fall under Git’s
version control, nor do they get copied over when a repo is cloned or pushed. To
get around this, some people keep hooks in a directory in the repo itself then
symlink them into place once they clone the repo.

Using Git hooks to create a .ciignore file is just one example of how to utilize Git
hooks to improve the daily workflow with CircleCI. Other ideas could be to use a
Git hook to modify circle.yml upon commit to only run specific tests.

Do you use Git hooks with CircleCI? We’d love to hear about it on Discuss.