Last active
December 15, 2015 02:39
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
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
Why only the ID and not include a link to the story as well?