Subversionのpre-commitフックを強化

前に”コミットに制約を設ける”と題してpre-commitのスクリプトを作成したが、不完全だったので作り直してみた。

コミットに制約を設ける(強化版)

制約内容:

  1. 参照用(refs)のチケット番号が指定されていて、そのチケットがRedmimeに存在すること。
  2. 完了用(fixes)のチケット番号が指定されていた場合は、そのチケットがRedmineに存在して、かつ、完了していないこと。
  • 修正したpre-commit
#!/usr/bin/env ruby

#REPOS="$1"
#TXN="$2"

# Make sure that the log message contains some text.
#SVNLOOK=/usr/bin/svnlook
#$SVNLOOK log -t "$TXN" "$REPOS" | \
#   grep "[a-zA-Z0-9]" > /dev/null || exit 1

# Check that the author of this commit has the rights to perform
# the commit on the files and directories being modified.
#commit-access-control.pl "$REPOS" "$TXN" commit-access-control.cfg || exit 1

#----------------------------------
#デバッグ出力と標準エラー出力の関数
#----------------------------------
def dbg(format, *arg)
  f = open("/tmp/pre-commit.log", "a")
  printf(f, format, *arg)
  f.close
  printf(STDERR, format, *arg)
end

#--------------------------------
#指定チケットの進捗率を求める関数
#--------------------------------
def getTicketRatio(ticketNo)
  redmine = <<-`__REDMINE__` 
#{MYSQL} -s -u root redmine <<__SQL__ 
select 
    issues.done_ratio
from 
    issues 
where
    issues.id = #{ticketNo}
; 
__SQL__
  __REDMINE__

  #数値でない場合は nil を返す
  if redmine.chomp.strip =~ /^[0-9]+$/ then
    return redmine.chomp.strip.to_i
  else 
    return nil
  end
end 

#引数セット
REPOS = ARGV[0] 
TXN = ARGV[1]

#外部プログラムのパスを設定
SVNLOOK = "/usr/bin/svnlook" 
MYSQL = "/usr/bin/mysql"

#デバッグ
dbg("--- %s ---\n", Time.now)
dbg("REPOS=[%s], TXN=[%s]\n", REPOS, TXN)

#チェック用変数初期化
refsCnt = 0
errCnt = 0

#コミットログを取得
log = `#{SVNLOOK} log -t #{TXN} #{REPOS}`
log.each do |item|
  #ログを加工
  item.chomp!                      #改行コードを取り除く
  item.strip!                      #前後の空白を取り除く
  item.downcase!                   #小文字に変換
  item.gsub!("references", "refs") #参照用キーワードを統一
  item.gsub!("IssueID", "refs")    #参照用キーワードを統一
  item.gsub!("closes", "fixes")    #修正用キーワードを統一

  #参照用チケットをチェック
  refs = item.scan(/refs[ ]*[#|0-9| |,]+/)
  refs.each do |ref|
    tickets = ref.scan(/#[0-9]+/)
    tickets.each do |ticket|
      #進捗率が求めれない場合はエラー
      if getTicketRatio(ticket.gsub("#", "")).nil? then
        dbg("Ticket %s is nothing.\n", ticket)
        errCnt += 1
      else
        dbg("Ticket %s is validity.\n", ticket)
        refsCnt += 1
      end
    end
  end

  #修正用チケットをチェック
  fixes = item.scan(/fixes[ ]*[#|0-9| |,]+/)
  fixes.each do |fix|
    tickets = fix.scan(/#[0-9]+/)
    tickets.each do |ticket|
      #進捗率が求めれない場合はエラー
      #進捗率が100%の場合はエラー
      if getTicketRatio(ticket.gsub("#", "")).nil? then
        dbg("Ticket %s is nothing.\n", ticket)
        errCnt += 1
      elsif 100 == getTicketRatio(ticket.gsub("#", "")) then
        dbg("Ticket %s is complite.\n", ticket)
        errCnt += 1
      else 
        dbg("Ticket %s is validity.\n", ticket)
      end
    end
  end
end
  
#参照用チケットがない場合はエラー
if 0 == refsCnt then
  dbg("No refs ticket.(refs #)\n") 
  errCnt += 1
end

#エラーがある場合は exit 1 で終了
if 0 < errCnt then
  dbg("--- NG %s ---\n", Time.now)
  exit 1
end

# All checks passed, so allow the commit.
dbg("--- OK %s ---\n", Time.now)
exit 0