Skip to content

Instantly share code, notes, and snippets.

@avdgaag
Last active December 15, 2015 02:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avdgaag/5188436 to your computer and use it in GitHub Desktop.
Save avdgaag/5188436 to your computer and use it in GitHub Desktop.
Auto-labeling Git commits with Pivotal Tracker IDs found in the current branch name. This script is meant to be used as a Git `commit-msg` hook. It expects a single argument, the location of the temporary file with the commit message the user entered, and it outputs that same message with maybe some extra labels added to it.
#!/usr/bin/env ruby
# ## Introduction
#
# Auto-labeling Git commits with issues tracker IDs found in the current
# branch name. This script is meant to be used as a Git commit-msg hook. It
# expects a single argument, the location of the temporary file with the commit
# message the user entered, and it outputs that same message with maybe some
# extra labels added to it.
#
# ## Example
#
# For example, when on branch `feature_123456_my_new_feature`, it will transform a
# commit message like this:
#
# Implement new feature
#
# Into something like this:
#
# Implement new feature
#
# [#123456]
#
# When your commit message already includes a reference to the user story ID
# this script would want to add, it does not add it again. Also, when you
# include the special `NOREF` string somewhere in the commit message, that will
# be stripped out, but no other changes will be made.
#
# ## Supported formats
#
# The following formats are currently supported:
#
# * Pivotal Tracker IDs: 7-9 digits
# * Jira issue IDs: Two or more capitals, dash, one or more digits
#
# You can add more formats by subclassing the `Issue` class.
#
# ## Testing
#
# To run this script's tests, simply run it with no arguments:
#
# $ ./auto_reference_pt_ids
#
# This will cause it to run its own unit tests (requires Rubygems and Rspec).
#
# ## Installation
#
# To install, copy it into `path/to/project/.git/hooks/commit-msg` and make it
# executable.
#
# Author: Arjan van der Gaag
# URL: http://arjanvandergaag.nl
class Issue
attr_reader :branch_name
private :branch_name
@subclasses = []
def self.inherited(subclass)
@subclasses << subclass
end
def self.matching(branch_name)
@subclasses
.map { |subclass| subclass.new(branch_name) }
.find { |issue| issue.matches? } || NullIssue.new
end
def initialize(branch_name)
@branch_name = branch_name
end
def id
branch_name[pattern, 1]
end
def id?
true
end
def matches?
pattern === branch_name
end
def pattern
//
end
end
class NullIssue
def matches?
true
end
def id?
false
end
def format
''
end
end
class JiraIssue < Issue
def pattern
/([A-Z]{2,}[\-_]\d+)/
end
def format
"\n\n%s" % id.sub('_', '-')
end
end
class PivotalTrackerIssue < Issue
def pattern
/(\d{7,9})/
end
def format
"\n\n[#%s]" % id
end
end
class Labeler
OUTPUT_FORMAT = "\n\n[#%s]"
ID_IN_BRANCH_FORMAT = /(\d{7,9}|[A-Z]{2,}[\-_]\d+)/
ESCAPE = 'NOREF'
attr_reader :original_message
def initialize(original_message)
@original_message = original_message
end
def filtered_message
if requires_update?
original_message + formatted_id_from_branch_name
elsif contains_escape?
original_message.sub(ESCAPE, '').strip
else
original_message
end
end
def branch_name
@branch_name ||= `git branch --no-color 2>/dev/null`[/^\* (\w+)$/, 1]
end
private
def requires_update?
!contains_escape? &&
branch_name_contains_id? &&
!original_message_already_contains_reference?
end
def contains_escape?
original_message.include?(ESCAPE)
end
def formatted_id_from_branch_name
issue.format
end
def issue
@issue ||= Issue.matching(branch_name)
end
def original_message_already_contains_reference?
original_message.include?(formatted_id_from_branch_name.strip)
end
def branch_name_contains_id?
branch_name && issue.id?
end
end
if ARGV.any?
commit_message_filename = ARGV[0]
labeler = Labeler.new(File.read(commit_message_filename))
File.open(commit_message_filename, 'w') do |f|
f.write(labeler.filtered_message)
end
else
require 'rubygems'
require 'rspec/autorun'
describe Labeler do
let(:message) { "Add new feature.\n\nDetailed description." }
subject { described_class.new(message) }
before do
allow(subject).to receive(:`).with('git branch --no-color 2>/dev/null').and_return(branch_list)
end
context 'on a regular branch' do
let(:branch_list) { "* new_feature\n other_branch" }
its(:branch_name) { should eql('new_feature') }
its(:filtered_message) { should eql(message) }
end
context 'on a branch with a JIRA issue ID' do
let(:branch_list) { " other_branch\n* JIR_136_timezones" }
its(:branch_name) { should eql('JIR_136_timezones') }
context 'when using a simple message' do
its(:filtered_message) { should eql("Add new feature.\n\nDetailed description.\n\nJIR-136") }
end
context 'when it already mentions the ID' do
let(:message) { "JIR-136: Add new feature.\n\nDetailed description." }
its(:filtered_message) { should eql(message) }
end
context 'when it contains the special NOREF comment' do
let(:message) { "Add new feature.\n\nDetailed description. NOREF" }
its(:filtered_message) { should eql("Add new feature.\n\nDetailed description." ) }
end
end
context 'on a branch with a Pivotal Tracker ID' do
let(:branch_list) { " other_branch\n* 12345678_new_feature" }
its(:branch_name) { should eql('12345678_new_feature') }
context 'when using a simple message' do
its(:filtered_message) { should eql("Add new feature.\n\nDetailed description.\n\n[#12345678]") }
end
context 'when it already mentions the ID' do
let(:message) { "Add new feature.\n\nDetailed description. [#12345678]" }
its(:filtered_message) { should eql(message) }
end
context 'when it contains the special NOREF comment' do
let(:message) { "Add new feature.\n\nDetailed description. NOREF" }
its(:filtered_message) { should eql("Add new feature.\n\nDetailed description." ) }
end
end
context 'not on any branch' do
let(:branch_list) { '' }
its(:branch_name) { should be_nil }
its(:filtered_message) { should eql(message) }
end
end
end
@ariejan
Copy link

ariejan commented Mar 19, 2013

Why only the ID and not include a link to the story as well?

@avdgaag
Copy link
Author

avdgaag commented Mar 19, 2013

This is only about using Pivotal Tracker's SCM integration. I've never thought of including links before. It's not as if URLs in git-log output would be clickable, so they'd save you the copy/pasting…

Now, putting the link to a user story in a pull request description, that would be useful, no?

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