Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Automate the Decision to Skip Builds Using a Git Hook

DZone's Guide to

Automate the Decision to Skip Builds Using a Git Hook

Sometimes you don't want your CI tool to create a new build for a trivial commit. Here's how to automate the process of skipping builds you don't want to waste time on.

· DevOps Zone
Free Resource

The Nexus Suite is uniquely architected for a DevOps native world and creates value early in the development pipeline, provides precise contextual controls at every phase, and accelerates DevOps innovation with automation you can trust. Read how in this ebook.

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.

Fast_Forward_Icon_-_Macro_photography_of_a_remote_control.jpg

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.

In this blog post, we’re going to create our automated build skipping feature using the commit-msg Git hook.

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:

$ ls -la
total 9
drwxrwxr-x 4 felicianotech felicianotech 4096 Jun 15 00:44 ./
drwxrwxr-x 4 felicianotech felicianotech 4096 Jun 15 00:44 ../
-rw-rw-r-- 1 felicianotech felicianotech    7 Jun 15 00:32 .ciignore
-rw-rw-r-- 1 felicianotech felicianotech 1077 Jun  1 20:29 circle.yml
-rw-rw-r-- 1 felicianotech felicianotech  135 Jun  1 20:23 Dockerfile
drwxrwxr-x 8 felicianotech felicianotech 4096 Jun 15 00:52 .git/
drwxrwxr-x 2 felicianotech felicianotech 4096 Jun 15 00:45 logs/
-rw-rw-r-- 1 felicianotech felicianotech   43 Apr 29 22:23 main.go
-rw-rw-r-- 1 felicianotech felicianotech   20 Apr  1 16:40 README.md
-rw-rw-r-- 1 felicianotech felicianotech   31 Jun 15 00:44 test.txt

The .ciignore file goes in the root of the repo while our Git hook’s file path will be .git/hooks/commit-msg. The contents of each of these files can be seen via the GitHub Gists below.

logs/*
#!/usr/bin/env bash

if [[ ! -a .ciignore ]]; then
	exit # If .ciignore doesn't exists, just quit this Git hook
fi

# Load in every file that will be changed via this commit into an array
changes=( `git diff --name-only --cached` )

# Load the patterns we want to skip into an array
mapfile -t blacklist < .ciignore

for i in "${blacklist[@]}"
do
	# Remove the current pattern from the list of changes
	changes=( ${changes[@]/$i/} )

	if [[ ${#changes[@]} -eq 0 ]]; then
		# If we've exhausted the list of changes before we've finished going 
		# through patterns, that's okay, just quit the loop
		break
	fi
done

if [[ ${#changes[@]} -gt 0 ]]; then
	# If there's still changes left, then we have stuff to build, leave the commit alone.
	exit
fi

# Prefix the commit message with "[skip ci]"
sed -i '1s/^/[skip ci] /' "$1"

.ciignore — This file contains a single pattern, logs/*. We want to skip builds for changes in the logs directory.

.git/hooks/commit-msg — This is our hook, a simple Bash script. Here’s a quick breakdown:

  1. The script starts by checking for the existence of .ciignore. If it doesn’t exists, that’s cool. It’s business as usual.
  2. We then call git diff to get a machine readable list of files that are staged (going to be changed in this commit).
  3. We load .ciignore for the list of patterns to ignore.
  4. We loop through the changes, removing a change whenever it matches an ignore pattern.
  5. If we still have changes, this means we need to actually build the commit so we exit.
  6. 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.

The DevOps Zone is brought to you in partnership with Sonatype Nexus.  See how the Nexus platform infuses precise open source component intelligence into the DevOps pipeline early, everywhere, and at scale. Read how in this ebook

Topics:
circleci ,git ,continuous integration ,ci

Published at DZone with permission of Ricardo N Felicano, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}