Nobody enjoys doing timesheets. It can be a tedious manual task but it is a necessary evil. I wrote a git hook that not only makes timesheets easier to do, but it also makes them more accurate.
August 23, 2015
After each commit, the git hook will ask you how long it took to code that particular commit. Answer in weeks, days, hours, minutes or a combination of the formats.
The git hook reads the JIRA ticket in your branch name and then logs the amount of time specified to that ticket using your commit message as the description.
The timesheet on the left is for a developer that I work with before she installed the git hook. The timesheet on the right is from her first day using the git hook. The right hand side gives a much more accurate depiction of what happened during the day.
I'll walk through the git hook code itself. If you would rather just read the full code or fork the repo, feel free to do so here. I also welcome pull requests! Okay, let's get started!
Wait... What's a post-commit file? A post-commit file is a script that fires immediately after a commit is made. The script can be written in shell, python or ruby (I wrote mine in shell just because I wanted to try something new). The post-commit file lives in the root directory of the github repository in the /.git/hooks directory and is named simply:
post-commit. The file needs to have permission to be executed hence the
chmod +x post-commit command in the installation steps. You can read more about git hooks in Git's git hook documentation.
The first thing we want to do is check JIRA credentials. We cannot log time via the JIRA API without these. We will be storing the credentials in the same directory as our post-commit file. This command will assign the
jira_credentials_file variable to the file that lives in the user's git hook directory named
`git rev-parse --git-dir`/hooks/jira_credentials.txt rather than just
/.git/hooks/jira_credentials.txt because `git rev-parse --git-dir` command will the will change the user's directory to the proper git hooks directory even if they are using git submodules.
We then check if that file exists. If it does, we will assume the credentials within are valid. If the file does not exist, we will need to prompt the user to enter their JIRA credentials
This is the parse_jira_credentials function that gets called if we do not find JIRA credentials stored in the user's git hook directory. It first asks the user to enter their JIRA username and stores the input into a variable called "username." Then, it asks the user to enter their JIRA password (the input is made to be invisible on the user's terminal). Then the script will base64 encode the credentials and save them to a file in the git hooks directory called jira_credentials.txt. Note: as a future improvement, I would like to permission this file to only allow the creator to read it.
Now let's find out where to log the time. This git hook assumes that developers are creating branches with JIRA ticket numbers included in the name. If you aren't doing this already, I highly recommend doing so. Tying JIRA to your codebase can help keep your code organized and tell the bigger story behind the code.
Now that we know to look for a JIRA ticket in the git branch name, let's parse the git branch for it using this code. It will assign the variable
ticket to the result of the function:
parse_jira_ticket_in_branch function starts off by calling a different function:
parse_git_branch. That's because we first need to determine what the name of the git branch is before we can start looking for JIRA tickets in it. I found both of these functions online at Monk Seal Software's Blog.
parse_git_branch function returns the branch name and then the
parse_jira_ticket_in_branch function the ticket name by using a regular expression that captures issue key using patterns such as "ABC-123" or "abc-123" (it doesn't matter how many letters or numbers your keys have). Now we know which ticket we are going to log time to and it is stored in our variable named
Let's take a look at the commit message. The commit message will be used as the description for the user's timesheet. The git hook documentation gives us a command for retrieving the commit in a post-commit hook. I took it one step further by grabbing only the commit message and storing it in a variable called
commit_message using the following code:
Some of the developers I work with like using something called smart commits to log their time. Smart commits are baked into the JIRA/GitHub integration and they allow developers to log their time via commits without the use of this git hook. I found that these smart commits require too many key strokes for my developers to adopt. Additionally, they can make the commit history looks sort of ugly, but I digress. If we see a smart commit that logs time (i.e. includes the characters "#time"), we should not prompt the user to log time with this git hook. We can keep track if the commit is a smart commit using a variable that we'll call
Should we prompt the user? We know the ticket that the user is working on and we know the commit message that the user entered but if we couldn't find a ticket number or if the user entered a smart commit, we shouldn't prompt the user for time.
Let's log some time! The function starts off by prompting the user to enter the amount of time they spent on the commit. It reads that value into a variable called
time. If the user did not enter any time, the script quits but if the user did enter something, the script wil send JIRA a POST request on behalf of the user. The POST request will log the amount of time that the user entered and will use the commit message as the description of the worklog. We can determine if we successfully logged time based on the response code of the POST request. If we successfully logged time, we show a success message. If we get an unauthorized response code, we give the user the option to call the
parse_jira_credentials method so that they can update their JIRA credentials. Finally, if we get a response code that indicates failure, we present a failed message to the user. That's it!