Save your commit history to a running Harvest timer
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
.git/hooks/post-commit
1#!/usr/bin/env node2"use strict";34// https://lukeclark.com.au/posts/harvest-post-commit-hook-nodejs56// Assume dependencies are installed at the project, or7// required commands have been installed globally8var request = require("request");910// make the logOutput available anywhere11var logOutput;1213// Your Havest info14var credentials = {15account: "HARVESTACCOUNT",16username: "HARVESTUSER",17password: "HARVESTPASS",18};1920var options = {21url: "https://" + credentials.account + ".harvestapp.com/daily",22auth: {23username: credentials.username,24password: credentials.password,25},26headers: {27Accept: "application/json",28"User-Agent": "git post commit hook",29},30};3132function harvest(cb) {33console.log("[Harvest] Looking for running timer...");34request(options, function (error, response, body) {35var today = JSON.parse(body);36if (error) cb(error, {});37if (today.day_entries.length === 0) cb(false, "No running timer found");38today.day_entries.forEach(function (entry) {39// Check for running timer40if (typeof entry.timer_started_at !== "undefined") {41// Create a notes variable if there isn't one42if (typeof entry.notes === "undefined") entry.notes = "";43// Append our commit to the notes44entry.notes += "\n" + logOutput;45// Modify original request to post an update to the active timer46var postParams = Object.assign({ method: "POST" }, options);47postParams.url += "/update/" + entry.id;48postParams.formData = entry;49request(postParams, function (err, resp, body) {50if (err) {51cb(err, {});52} else {53cb(false, "Added commit to running timer");54}55});56}57});58});59}6061// Allows us to launch an external shell command62var spawn = require("child_process").spawn;63// Run our command and save the reference so we can kill it if something bad happens.64var child = spawn("git", [65"--no-pager", // Print straight away, don't use a pager66"log",67"--oneline", // first 7 digits of commit, and message on single line68"--no-decorate", // Don't include ref names for commits69"-1", // Only show the latest commit70"HEAD", // From the current branch71]);7273child.stdout.on("data", function (data) {74// Save our one liner out to a global variable, strip newlines75logOutput = data.toString();76logOutput = logOutput.replace(/(\r\n|\n|\r)/gm, "");77});7879child.on("close", function (code) {80if (code !== 0) {81// 0 = good, otherwise bad82console.error("Something went wrong, code: ", code);83process.exit(code);84} else {85harvest(function (err, cb) {86if (err) console.error(err);87console.log("[Harvest] " + cb);88process.exit(code);89});90}91});9293process.on("uncaughtException", function (err) {94console.error("Uncaught Exception: ", err.stack);95child.kill("SIGTERM");96process.exit(1);97});