Save your commit history to a running Harvest timer

August 15, 2016

A git hook is a script that will run at certain points in git’s execution. They are per repository, and remain local so they do not push to remotes.

Usually they’re written in bash or sh. But you can run them in any language you’d prefer. We’re going to use a nodejs post-commit hook to post information about our commit to a currently running timer.

I thought what better way to create meaningful timesheet entries than including my actual commit history. Perfect for reflecting on your own efficiency or communicating exactly what happened to clients.

The .git/hooks/post-commit hook will run after a commit has been made.

Check out the annotated source code below

#!/usr/bin/env node
'use strict';


// Assume dependencies are installed at the project, or
// required commands have been installed globally
var request = require('request');

// make the logOutput available anywhere
var logOutput;

// Your Havest info
var credentials = {
  account: 'HARVESTACCOUNT',
  username: 'HARVESTUSER',
  password: 'HARVESTPASS'

var options = {
  url: 'https://' + credentials.account + '',
  auth: {
    username: credentials.username,
    password: credentials.password
  headers: {
    Accept: 'application/json',
    'User-Agent': 'git post commit hook'

function harvest(cb) {
  console.log('[Harvest] Looking for running timer...');
  request(options, function(error, response, body) {
    var today = JSON.parse(body);
    if (error) cb(error, {});
    if (today.day_entries.length === 0) cb(false, 'No running timer found');
    today.day_entries.forEach(function(entry) {
      // Check for running timer
      if (typeof entry.timer_started_at !== 'undefined') {
        // Create a notes variable if there isn't one
        if (typeof entry.notes === 'undefined') entry.notes = '';
        // Append our commit to the notes
        entry.notes += '\n' + logOutput;
        // Modify original request to post an update to the active timer
        var postParams = Object.assign({ method: 'POST' }, options);
        postParams.url += '/update/' +;
        postParams.formData = entry;
        request(postParams, function(err, resp, body) {
          if (err) {
            cb(err, {});
          } else {
            cb(false, 'Added commit to running timer');

// Allows us to launch an external shell command
var spawn = require('child_process').spawn;
// Run our command and save the reference so we can kill it if something bad happens.
var child = spawn('git', [
  '--no-pager', // Print straight away, don't use a pager
  '--oneline', // first 7 digits of commit, and message on single line
  '--no-decorate', // Don't include ref names for commits
  '-1', // Only show the latest commit
  'HEAD' // From the current branch

child.stdout.on('data', function(data) {
  // Save our one liner out to a global variable, strip newlines
  logOutput = data.toString();
  logOutput = logOutput.replace(/(\r\n|\n|\r)/gm, '');

child.on('close', function(code) {
  if (code !== 0) {
    // 0 = good, otherwise bad
    console.error('Something went wrong, code: ', code);
  } else {
    harvest(function(err, cb) {
      if (err) console.error(err);
      console.log('[Harvest] ' + cb);

process.on('uncaughtException', function(err) {
  console.error('Uncaught Exception: ', err.stack);
Luke Clark

Personal site of Luke Clark. Links, snippets and sometimes words.

gram, rss, noot, tweet