Skip to content

Instantly share code, notes, and snippets.

@insoul
Created April 10, 2012 07:14
Show Gist options
  • Save insoul/2348958 to your computer and use it in GitHub Desktop.
Save insoul/2348958 to your computer and use it in GitHub Desktop.
git commit hook for redmine
#!/usr/bin/env ruby
BASEDIR = ENV["HOME"] + "/.git_redmine"
require BASEDIR + "/git_redmine"
include GitRedmine::Hooks
exit commit_msg(ARGV[0])
#host: localhost
#access_key: 1234567890abcdefg1234567890abcdefg123456
#port: 80 # default 80
#verbose: true # default nil
#reqtrace: true # default nil
#!/usr/bin/env bash
GIT_REDMINE=$HOME/.git_redmine
if [ -e $GIT_REDMINE ]; then
echo "git_redmine directory $GIT_REDMINE"
else
echo "ERROR: git_redmine directory $GIT_REDMINE not exists"
exit 1
fi
if [ -e `pwd`/.git/hooks/commit-msg ]; then
echo 'ERROR: Already there is commit-msg hook'
echo ' Remove first .git/hooks/commit-msg file'
exit 1
fi
if [ -e `pwd`/.git/hooks/post-commit ]; then
echo 'ERROR: Already there is post-commit hook'
echo ' Remove first .git/hooks/post-commit file'
exit 1
fi
echo 'symlink to .git/hooks/commit-msg'
ln -s $GIT_REDMINE/commit-msg .git/hooks/commit-msg
echo 'symlink to .git/hooks/post-commit'
ln -s $GIT_REDMINE/post-commit .git/hooks/post-commit
require 'rubygems'
require 'redminer'
require 'yaml'
module GitRedmine
class MessageFormatError < RuntimeError; end
class CommitLogError < RuntimeError; end
class DiffError < RuntimeError; end
class GitVersionError < RuntimeError; end
module Config
# host: Issue tracker host
# port: Issue tracker port # default 80
# access_key: Issue tracker api access_key
# verbose: Trace error option # default false
CONFIG = YAML.load_file(File.dirname(__FILE__) + '/config.yml') rescue {}
def host; CONFIG["host"] end
def port; CONFIG["port"] || 80 end
def access_key; CONFIG["access_key"] end
def verbose; CONFIG["verbose"] end
def redmine
opts = {:port=>port, :verbose=>verbose, :reqtrace=>CONFIG["reqtrace"]}
@redmine ||= Redminer::Base.new(host, access_key, opts)
end
def redmine_url
ret = "http://#{host}"
ret << ":#{port}" if port != 80
ret
end
end
module GitCommands
def backward_commit_hash(backward_number)
`git log -#{backward_number} --format='%H'`.split("\n").last
end
def last_commit_log
l = `git log -1 --format="%B"`
raise GitVersionError.new('git version ~> 1.7.9') if l =~ /%B/
l
end
def diff_from(hash)
`git diff #{hash}..HEAD --stat`
end
def origin_path
remote = `git remote -v`.split("\n")[0]
return if remote.nil?
url = remote.split(" ")[1]
url = url.gsub(/.*@/, '').gsub(/\.git$/, '').gsub(':', '/')
url
end
end
class CommitMessage
include Config
attr_accessor :issue_id, :message
def issue_url
"#{redmine_url}/issues/#{issue_id}"
end
end
class InputCommitMessage < CommitMessage
def initialize(commit_msg_file)
@msg = ""
File.open(commit_msg_file) do |f|
while true
begin
@msg << f.readline
rescue EOFError
break
end
end
end
match = @msg.scan(/^@(\d+) (.*)/m)
match = @msg.scan(/^(\d+) (.*)/m) if match.empty?
match = @msg.scan(/^#{redmine_url}\/issues\/(\d+) (.*)/m) if match.empty?
if match.empty?
raise MessageFormatError.new("commit message format error\n\n#{@msg}")
end
msg = match[0][1] || ""
msg = msg.strip[1..-1] if msg.strip.start_with?($/)
msg = msg.split(/diff --git/)[0] || ""
# 원본 메시지가 @로 시작하는 경우 msg의 첫번째라인은 무시한다.
# redmine 제목이고 자동으로 붙여지기 때문.
msg = msg.split($/, 2)[1] || "" if @msg.start_with?("@")
# msg의 마지막이 redmine주소인 경우 제거한다.
msg = msg.gsub(/#{$/}#{redmine_url}\/issues\/\d+#{$/}*$/, '')
@issue_id = match[0][0].to_i
@message = msg
end
end
class RemakeCommitMessage < CommitMessage
include GitCommands
attr_accessor :hash
def initialize
@hash = backward_commit_hash(2)
res = last_commit_log
raise CommitLogError.new if res.length < 3
self.parse(res)
end
def diff
df = diff_from(self.hash)
raise DiffError.new("cannot retrieve diff") if df.empty?
df
end
# commit is a InputCommitMessage instance
def self.remake(commit, issue)
remake_msg = "@#{commit.issue_id} #{issue.subject}"
unless commit.message.empty?
remake_msg << "\n#{commit.message}"
end
remake_msg << "\n\n#{redmine_url}/issues/#{issue.id}"
remake_msg
end
# res is a string of remade commit message
def parse(res)
res = res.split("\n")
@issue_id = res.delete_at(0).scan(/^@(\d+) /)[0][0] rescue nil
if @issue_id.nil? or @issue_id.empty?
raise MessageFormatError.new("cannot find issue id\n\n#{res}")
end
@issue_id = issue_id.to_i
issue_link = res.index { |ln| ln =~ /^http:\/\/#{host}/ }
unless issue_link.nil?
res.delete_at(issue_link) or
(res[its-1].strip.empty? and res.delete_at(issue_link-1))
end
@message = res
end
def origin_url
path = origin_path
return if path.nil?
"http://#{path}/commit/#{hash}"
end
end
module Hooks
include GitCommands
include Config
def commit_msg(commit_msg_file)
puts 'Hook: commit-msg'
commit = InputCommitMessage.new(commit_msg_file)
issue = redmine.issue(commit.issue_id)
remake_msg = RemakeCommitMessage.remake(commit, issue)
File.open(commit_msg_file, 'w+') { |f| f << remake_msg }
ret = 0
rescue MessageFormatError => e
error("Commit message format is not valid")
puts "Commit message format:"
puts " <issue id> <message>"
puts "Example:"
puts " 450 fix error"
puts " #{redmine_url}/issues/450 fix error"
ret = 1
rescue => e
error(e.message)
ret = 1
ensure
puts e.backtrace.join("\n") if verbose and defined?(e) and not e.nil?
ret
end
def post_commit
puts 'Hook: post-commit'
out = "Commit completed, "
commit = RemakeCommitMessage.new
issue = redmine.issue(commit.issue_id)
diff = commit.diff
diff = "<pre>#{diff}</pre>"
commit_link = commit.origin_url
commit_link = if commit_link
"commit:\"#{commit.hash[0..6]}\":#{commit.origin_url}"
else
"commit:#{commit.hash[0..6]}"
end
note = "#{commit.message}\n\n#{commit_link}\n#{diff}"
issue.update(note)
ret = 0
rescue => e
warn("#{out}, But error occurred... #{e.message}")
ret = 1
ensure
puts e.backtrace.join("\n") if verbose and defined?(e) and not e.nil?
ret
end
def error(msg)
puts "ERROR!\n\t#{msg}"
puts "\tWanna skip verification? use --no-verify option\n"
true
end
def warn(msg)
puts "WARNING!\n\t#{msg}\n"
true
end
end
end
#!/usr/bin/env bash
gem install redminer
cd $HOME
git clone git://gist.github.com/2348958.git .git_redmine
chmod +x .git_redmine/commit-msg
chmod +x .git_redmine/post-commit
chmod +x .git_redmine/git_redmine
echo 'alias git_redmine=~/.git_redmine/git_redmine' >> .bash_profile
echo ''
echo 'Install git_redmine complete!!'
echo 'You must edit ~/.git_redmine/config.yml'
echo "Usage is run 'git_redmine' in git working copy"
#!/usr/bin/env ruby
BASEDIR = ENV["HOME"] + "/.git_redmine"
require BASEDIR + "/git_redmine"
include GitRedmine::Hooks
exit post_commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment