Integrating Asana and Git

Recently we at Spaceman Labs have been testing out different tools and workflows to manage our various projects. After trying a few different packages, we’ve more or less settled on Asana as our issue tracking solution. It’s got a lot of advantages: it’s free, we can have as many projects as we want, and it’s low-friction enough that we’re likely to use it regularly.

That last one is actually the biggest feature – just as the best camera is the one you have with you, the best issue tracker is the one you’ll actually use. All the bells and whistles in the world don’t matter if you’re scared away from the core functionality. But there’s one way Asana could be even easier to use. It doesn’t have a standard way to integrate with our Git workflow. We’d like to be able to commit some code with a comment like “fixes #123” and have that fix reflected in Asana.

Fortunately, all of the groundwork has been done. Asana has a really awesome, and beautifully well-documented, API. And other developers, most notably for our purposes Bruno Costa, have taken that API and put together useful scripts.

The only thing missing from Bruno’s post-commit hook (and go back and read his blog post if you haven’t, it’s great) is the ability to run it non-interactively. Our Git workflow is built around Tower; it’s an awesome tool, but it doesn’t know how to simulate reading from /dev/tty in a hook.

So we needed to make the script non-interactive. This means it loses some potential flexibility, but on the other hand, it’s faster and has a somewhat higher “just works” quotient. Once you set up your git config variables, anyway. While we were at it, we also made it handle multiple ticket numbers per checkin, and differentiate between referencing and closing a ticket. That means you can write a commit message like

"Tweaked the widget; fixed #1, #2, and #3; references #4 and #5; oh yeah, and closes #6"

and have the right thing happen. Neat, right?

You can find the script at our GitHub page; please modify and open pull requests if you’d like to see it do more. (For instance, re-opening a closed ticket would be trivial.) V1 of the script is duplicated below. To run it, save it to your local repo’s .git/hooks/post-commit, and chmod 755 that mother. Run the git config line at the top of the script with the appropriate values, and you’re good to go.

#!/bin/bash

# modified from http://brunohq.com/journal/speed-project-git-hook-for-asana/

# -----------------------
# necessary configuration:
# git config --global user.asana-key "MY_ASANA_API_KEY" (http://app.asana.com/-/account_api)
# -----------------------

apikey=$(git config user.asana-key)
if [ $apikey == '' ] ; then exit 0; fi

# hold the closed ticket numbers
declare -a closed
# hold the ticket numbers that are not closed, just touched
declare -a referenced
# track whether we're currently closing tickets or just referencing them
closes='NO'

# regex pattern to recognize a story number
taskid_pattern='#([0-9]*)'
# regex pattern to recognize a "closing this ticket" word
closes_pattern='([Ff]ix|[Cc]lose|[Cc]losing)'
# regex pattern to recognize an "and" word (eg "fixes #1, #2, and #3")
and_pattern='([Aa]nd|&)'

# get the checkin comment for parsing
comment=$(git log --pretty=oneline -n1)

# break the commit comment down into words
IFS=' ' read -a words <<< "$comment"

for element in "${words[@]}"
do
    # if we have a task id, save it to the appropriate array
    if [[ $element =~ $taskid_pattern ]]; then
	if [ "${closes}" == "YES" ]; then
	    closed=("${closed[@]}" "${BASH_REMATCH[1]}")
	fi
	referenced=("${referenced[@]}" "${BASH_REMATCH[1]}")
    # or else if we have a "closes" word, set the tracking bool accordingly
    elif [[ $element =~ $closes_pattern ]]; then
	closes='YES'
    # and if we don't, set us back to referencing
    # (if we're an "and", don't change any state)
    elif [[ ! $element =~ $and_pattern ]]; then
	closes='NO'
    fi
done

# touch the stories we've referenced
for element in "${referenced[@]}"
do
    curl -u ${apikey}: https://app.asana.com/api/1.0/tasks/${element}/stories \
         -d "text=just committed ${comment/ / with message:%0A}" > /dev/null 2>&1
done

# close the tasks we've fixed
for element in "${closed[@]}"
do
    curl --request PUT -u ${apikey}: https://app.asana.com/api/1.0/tasks/${element} \
         -d "completed=true" > /dev/null 2>&1
done
Joel Kraut

About Joel Kraut

Developing on Apple platforms for, holy shit, over ten years now. Find me on linkedin and twitter. My personal website is foon.us.
This entry was posted in Code and tagged , , , , . Bookmark the permalink.