Grunt Task to Guard Against Deploying the Wrong Branch

If you use a Git hook to deploy your projects you might have run across the problem of accidentally deploying the wrong branch. This can easily be resolved by adding a check in your post-receive hook but what if you have a project that you build locally before deploying and your dist folder is not under version control? I wrote a quick inline Grunt task to guard against accidentally deploying the wrong branch. You can use this code directly within your Gruntfile. I may roll this into a plugin in the future.

The problem

I have a project with two long running branches; master for the latest stable release and develop for the latest changes. It’s a static website that I deploy using rsync. I would normally opt for a Git hook but in my case I don’t want to have to keep my built and packaged files in my Git repo. After all, there are a few things you should never keep in version control:

Things not to keep under version control

  • Dependencies – These should be easy to install locally whenever you get a fresh clone of a project
  • System and editor files – OS X always drops a .DS_Store file in random folders and your .sublime-project file shouldn’t be committed either because you and your team should be free to use any editor or IDE you want without having your Git repository force someone else’s project-specific settings on you.
  • Built release files – Identical builds of any project should be easy to reproduce with one simple terminal command. Whether that’s Grunt, Ant, or anything else, it shouldn’t matter. Built files can vary drastically, committing builds adds the unnecessary weight of your entire project plus the built version of your project to your repo, and if you rev your front-end assets then you end up having to clean up a whole bunch of “deleted” files in your build folder. Have you ever had 10 CSS or JavaScript files in your project that, after a build, were stored as style.cb83ac322.min.css or something similar (note the hash in the file name) while the previous build produced file names with different hashes? And so then you had to go back into git status, identify the built files that no long have the same name and then git rm them from your repository? Why go through the trouble?

It would be easy to simply keep the folder containing my built project under version control and have a Git hook deploy it but because of reason number 3 above, I refuse to do that. An alternative would be to deploy the project to the server and have the build script triggered there after a deploy. In that case you’d push, the post-receive hook would checkout the latest version of your project to /www/myproject.com/ and then run grunt build (or whatever) to reproduce your built files on the server. Then I could just set the site’s document root to /www/myproject.com/dist/ in my Nginx or Apache config. I actually am considering this but there are a few circumstances that could prevent that from being possible:

(Excuses for why) You can’t always run builds on the server

  1. It’s kind of hard. There’s a lot of troubleshooting that goes into writing a post-receive hook that does all that without fail. My build process still isn’t perfect enough for this solution.
  2. You may be running a task like imageoptim on all your images that only works on OS X but you deploy to a Linux server. This applies to me too.

The solution

So for now, I have a Grunt task that creates a fresh build of my project and puts it in /dist. When I run the deploy task the build task gets fired and then my tests are run with CasperJS, and if those tests pass then I have a simple bash script which will use rsync to deploy my built project into production. The only problem is that I only want to deploy from the master branch. There are a lot of ways you can configure your Git repo so that two branches actually contain slightly different versions of a project even after a merge and because of that, its very important that only your official release branch gets pushed into production.

The Grunt task for guarding your deploy branches

I’ve given a ton of background, now for the Grunt task. Like I mentioned, this task will check the current branch and if it doesn’t match the one you choose as the “deploy” branch then it will fail and stop the deploy process. I use it as the line line of defense before my deploy script is run:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Do not allow deploy unless current branch is master
grunt.registerTask('deployguard', 'A task to make sure only the master branch is deployed', function() {
  var done = this.async(); // Make task asynchronous
  git.branch(function(branch) {
    if (branch === 'master') { // Send a little 'all is well' message if branch is okay for deployment
      grunt.log.writeln('On branch master. Safe to deploy.');
      done();
    } else { // Fail the task and tell user to take action
      grunt.log.writeln('Wrong deploy branch: ' + branch + '. Merge your work into master before trying to deploy.');
      done(false); // Passing false to done() makes grunt fail a task
    }
  });
});

You can stick that task right into your Gruntfile and it’ll work (with caveats*). No need to write a full plugin.

*The caveat

I kind of lied. If you want to use this task for real then you need to do some setup work. Very little:

Install dependencies

You’ll need the git-rev library from npm.

1
npm install git-rev --save-dev

Prep your Gruntfile

You’ll need to require git-rev in your Gruntfile and maybe add a few options to make this task a little cleaner. Here’s a sample Gruntfile you can use as a base. This Gruntfile on its own will work as-is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Modified from example at gruntjs.com/getting-started
module.exports = function(grunt) {

  // Require dependencies
  var git = require('git-rev');
  var deployBranch = 'master'; // Change this to your preferred deploy branch

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json')
    // Your plugin configurations go here...
  });

  // Default task(s).
  grunt.registerTask('default', ['deployguard']);

  // Do not allow deploy unless current branch is master
  grunt.registerTask('deployguard', 'A task to make sure only the master branch is deployed', function() {
    var done = this.async(); // Make task wait for async response
    git.branch(function(branch) {
      if (branch === deployBranch) {
        grunt.log.writeln('On branch ' + branch + '. Safe to deploy.');
        done();
      } else {
        grunt.log.writeln('Not deploying. Currently on ' + branch + '. Merge your work into ' + deployBranch + ' before trying to deploy.');
        done(false);
      }
    });
  });

};

If you add this task as part of a series of tasks that builds, tests, and deploys your project you can save yourself from accidentally deploying the latest experimental branch into production.

I would really like to make this into a Grunt plugin but the fact that its so incredibly simple doesn’t allow me to justify the effort and I doubt it’ll be any more valuable to others as a plugin. If you have any ideas for features that would make this plugin-worthy let me know because it’s something I’d love to work on.

Web development

« Web Design is Neither a Career Path or Business How do Node web apps work in production? »

Comments