@@ -0,0 +1,216 @@
#!/usr/bin/env ruby
#
# Update JIRA with git commit messages
#
# == Usage ==
#
# To update a JIRA issue, prepend the first line of your git commit message
# with the issue key and a colon:
#
# $ git commit -m "GIT-1: Updates something"
#
# A comment will be added to the GIT-1 issue that looks something like:
#
# Commit: <Hash>
# Author: Bob Example <bob@example.com>
# Date: Mon Jul 14 14:00:00 -0400 2008
#
# GIT-1: Updates something
#
# To change an issue's status, append an action string:
#
# GIT-1 resolved: Updates something
# GIT-1 closed: Finishes this
# GIT-1 reopen: Starting work on this
#
# To update multiple issues, separate them with a comma:
#
# GIT-1, GIT-2: Adds comments to GIT-1 and GIT-2
# GIT-1, GIT-2 resolved: Updates GIT-1 and resolves GIT-2
#
# == Installation ==
#
# To get this working, first install a few gems:
#
# $ gem install soap4r
#
# Now, jira4r, which has to be pulled down from subversion:
#
# $ svn co http://svn.rubyhaus.org/jira4r/trunk jira4r
# $ cd jira4r
# $ gem build jira4r.gemspec
# $ gem install jira4r-*.gem
#
# And finally, grit, a Ruby git library. As of today (July 14, 2008),
# the most updated fork is being maintained by Scott Chacon on github.
# For whatever reason, my attempt to install the gem directly wasn't
# working (doesn't appear to be exposed?), so I cloned and installed
# directly:
#
# $ git clone git://github.com/schacon/grit.git
# $ cd grit
# $ gem build grit.gemspec
# $ gem install grit-*.gem
#
# When the gem gets fixed, it should be a simple:
#
# $ gem sources --add http://gems.github.com
# $ gem install schacon-grit
#
# Now just copy/symlink/move an executable copy of this file into your
# .git/hooks directory (be sure not to overwrite an existing hook):
#
# $ cp jira-post-receive /path/to/repository/.git/hooks/post-receive
#
# And don't forget to update some globals below. Voila. You should be
# in business.
#
# == TODO ==
#
# * Get status changes with comments working.
#
require "rubygems"
require "jira4r/jira_tool"
require "grit"
# Don't forget to set these.
#
# I'd recommend creating a dedicated user in JIRA to execute these updates.
# That user will need permissions to:
#
# * Browse Projects
# * Resolve Issues
# * Close Issues
# * Add Comments
#
# (I think that's comprehensive.)
JIRA_ADDRESS = "http://yourserver.com/jira"
JIRA_PROJECT = "DEWEBMO"
JIRA_USERNAME = "user"
JIRA_PASSWORD = "password"
class JiraPostReceive
def initialize ( old_commit , new_commit , ref )
@old_commit = old_commit
@new_commit = new_commit
@ref = ref
@repo = Grit ::Repo . new ( "." )
end
def jira
unless @jira
@jira = Jira4R ::JiraTool . new ( 2 , JIRA_ADDRESS )
@jira . logger = Logger . new ( "/dev/null" )
@jira . login ( JIRA_USERNAME , JIRA_PASSWORD )
end
@jira
end
def run
unless issues . empty?
jira # Sets up access to Jira4R::V2 constants
issues . each do |issue |
begin
send_comment ( issue )
send_new_status ( issue ) if issue [ :new_status ]
rescue
next
end
end
end
end
# Adds a comment to the JIRA issue
#
# Unfortunately, all comments originate from the dedicated JIRA user
# that's used to post the comment. It's possible to set a different
# author for the comment, but looking one up via email in JIRA doesn't
# seem possible without giving the user administrative rights.
def send_comment ( issue )
comment = Jira4R ::V2 ::RemoteComment . new
comment . author = JIRA_USERNAME
comment . body = generate_comment ( issue [ :commit ] )
jira . addComment ( issue [ :key ] , comment )
end
def send_new_status ( issue )
status_string = case issue [ :new_status ]
when "resolved" then "Resolve Issue"
when "closed" then "Close Issue"
when "reopen" then "Reopen Issue"
end
if status = jira . getAvailableActions ( issue [ :key ] ) .
find { |a | a . name == status_string }
jira . progressWorkflowAction ( issue [ :key ] , status . id . to_s , [ ] )
end
end
def issues
issues = [ ]
issued_commits . each do |commit |
issue_string = commit . short_message . match ( /(.*?):/ ) [ 1 ]
issue_string . split ( "," ) . each do |snippet |
snippet . strip!
snippet =~ /(#{ JIRA_PROJECT } -\d +)\s ?(resolved|closed|reopen)?/i
issues << { :key => $1, :new_status => $2, :commit => commit }
end
end
issues
end
def issued_commits
new_commits . select do |commit |
commit . short_message =~ /(#{ JIRA_PROJECT } -\d +)(.*):/
end
end
# Fetch commits that are new to the repository
#
# That super-piped git command makes sure that we only update JIRA with
# commits that are new, and haven't been seen in any other branches.
# It's lifted verbatim from the post-receive-email hook that's shipped
# in the git repository (contrib/hooks/post-receive-email).
def new_commits
common_cmd = "git rev-parse --not --branches | " +
"grep -v $(git rev-parse #{ @ref } ) | " +
"git rev-list --stdin "
commit_ids = if branch_created?
`#{ common_cmd } #{ @new_commit } ` . split
elsif branch_updated?
`#{ common_cmd } #{ @old_commit } ..#{ @new_commit } ` . split
else
[ ]
end
commit_ids . map { |id | @repo . commit ( id ) } . reverse
end
def generate_comment ( commit )
<<-EOS
Commit: #{ commit . id }
Author: #{ commit . author . name } <#{ commit . author . email } >
Date: #{ commit . authored_date }
#{ commit . message }
EOS
end
def branch_created?
@ref =~ /refs\/ heads/ && @old_commit =~ /^0+$/
end
def branch_updated?
@ref =~ /refs\/ heads/ && @old_commit !~ /^0+$/ && @new_commit !~ /^0+$/
end
end
old_commit , new_commit , ref = STDIN . gets . split
JiraPostReceive . new ( old_commit , new_commit , ref ) . run
exit 0