Skip to content

Instantly share code, notes, and snippets.

@therealklanni
Last active September 5, 2017 09:02
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save therealklanni/9966d215d2b13a9c0e6e to your computer and use it in GitHub Desktop.
Save therealklanni/9966d215d2b13a9c0e6e to your computer and use it in GitHub Desktop.
Supercharge your git hooks with gulp — Introducing git-guppy

Supercharge your git hooks with gulp

Introducing git-guppy

Automate your developer workflow with git-guppy. git-guppy allows you to add git-hooks to your project repository that are automatically installed when users clone and install your repository, so you won't have to worry about developers skipping essential workflow steps and bypassing your project guidelines.

Getting Started

So let's use a unit test scenario to illustrate an example. We're going to run unit tests before every commit (pre-commit hook) and reject the commit if the tests fail (giving the developer a chance to fix their tests before committing broken code).

Let's assume we already have a gulp task called "unit" that does what we need.

So to turn this into a pre-commit hook we first need to add the guppy dependencies to package.json:

npm install --save-dev guppy-pre-commit

This guppy-pre-commit is just a special package that installs the pre-commit hook itself into .git/hooks for us.

Now, in gulpfile.js we add the following line:

gulp.task('pre-commit', ['unit']);

Yep, that's it. You're done.

The best part is, any time your project is cloned guppy's special pre-commit hook is automatically installed. Then any time a commit is initiated, your "unit" gulp task will run. If "unit" fails, git's pre-commit condition will fail, thus aborting the commit to allow the developer to fix the broken test.

Oh, it gets better

Let's have a look at a more complex scenario. This time we'll lint any files that were staged for commit.

This scenario is more complex, because you could of course just lint everything as a pre-commit condition. But that just seems like overkill to me—maybe there are some files that will certainly fail a lint because the code is incomplete but it's not what's being committed, so it's not a concern. We can do better.

This is where using git hooks for things like this begins to get interesting. Because we know we're about to make a commit, we know that there must be specific files being committed. Because there are specific files being committed, we can lint just these as our pre-commit condition.

So let's take a look. We're going to need another dependency. This time, git-guppy itself:

npm install --save-dev git-guppy

Then we need to add a new line to our gulpfile.js:

var guppy = require('git-guppy')(gulp);

Now we can write our new lint git hook in gulpfile.js:

gulp.task('pre-commit', guppy.src('pre-commit', function (files) {
  return gulp.src(files)
    .pipe(gulpFilter(['*.js']))
    .pipe(jshint())
    .pipe(jshint.reporter(stylish))
    .pipe(jshint.reporter('fail'));
});    

By calling guppy's .src() method here and passing in the name of the git hook along with the usual gulp callback function, guppy will pass an array of the file names you staged for this commit to the gulp callback's first argument. You can then use the file name array for gulp's .src() method to construct your lint orchestration, as you normally would with gulp.

You could also express this hook like the following, if you want to skip having to call gulp.src() directly:

gulp.task('pre-commit', function () {
  return guppy.src('pre-commit')
    .pipe(gulpFilter(['*.js']))
    .pipe(jshint())
    .pipe(jshint.reporter(stylish))
    .pipe(jshint.reporter('fail'));
});    

Both approaches will produce the same result, but in some cases the former may be more useful, such as in a scenario when you want to use the file list more than once.

Refer to the git-guppy docs for information on which method should be used for certain git hooks.

Hang on, though. If we just use the file names to determine what to lint, what happens if a file was staged for commit before another change was made to it?

If you've ever staged a file and then made additional changes to it between then and the time you actually commit it without adding the additional changes to the commit, you'll know that this can be problematic.

The problem here is that when you stage a file, git lets you make additional changes to the "working copy" without affecting what you staged for commit. In other words, you could stage a file while it's in a state that would fail lint, then update the file so it would pass lint and end up committing the failing version of the file.

Well, we can't just lint the "working copy" then. We could be inadvertently allowing code smells to be committed. Now what?

For that, guppy has a .stream() method. Pre-commit is one of the hooks supported by this method, which will let us stream the staged version of the file(s) to our linter.

Here's how:

gulp.task('pre-commit', function () {
  return gulp.stream('pre-commit')
    .pipe(gulpFilter(['*.js']))
    .pipe(jshint())
    .pipe(jshint.reporter(stylish))
    .pipe(jshint.reporter('fail'));
});    

Now the lint pre-commit hook will only lint exactly what is getting committed.

Let's see gulp.watch() do that!

In conclusion

One of the biggest problems with git hooks is that it's a chore to maintain them in a collaborative scenario. It requires manual install steps and let's face it, developers are lazy (that's why we automate anything we can). You don't want to have to maintain a separate set of tools for your workflow, either. So, use the ones you have!

With guppy we can create dead simple git hooks that follow your project—without adding any unnecessary baggage—and we also saw how guppy can handle more complex workflows like linting only what's relevant in a pre-commit condition.

These were just a couple of examples of what you can do with git hooks powered by guppy and gulp. I'd love to see what you come up with for your workflows.

Check out git-guppy on GitHub and tweet me at @therealklanni with how you're using guppy!

@arnaudvalle
Copy link

Great tool and examples! I think there's a tiny issue in your last example about streaming though: you put gulp.stream rather than guppy.stream

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment