git diff --stat
のフォーマットからファイル一覧を得る
$ git diff --stat=256 origin/master..HEAD
test/lib/populus/test_configuration.rb | 36 ++++++++++++++++++++++++++++++++++++
test/lib/test_populus.rb | 7 +++++++
2 files changed, 43 insertions(+)
git diff --stat
のフィールドは、空白で分割すると1番目がファイルパス、4番目が追加削除の様子、になるのでそれを利用できる。
長いファイル名だとtruncateされるので --stat=256
みたいに横幅を明示すると良さそう。
# origin/master..${current SHA1} みたいなのを渡す
@range = ARGV[0]
data = `git diff --stat=256 --no-color #{@range}`
rolenames = data.lines
.map {|l| l.strip.split(/\s+/) }
.select {|r| r[3].include? '+' }
.map {|r| detect_foobar_from_filename(r[0]) }
.compact
.flatten
detect_foobar_from_filename
で、例えばファイル名からPuppetやChefのロールを検出することができそう。手元では、ロールを導出して最後に計算してgit diffから一番影響を与えるロールを検出し、そのロールだけテストするみたいなことをやった。
パッペット事例のご紹介
以下、思いっきりPuppetの事例だが、みんな Puppet 使ってると思うし、あとディレクトリ構成は 名著 の構成を前提にしたものとしているのでそのまま使えると思う。え、あのディレクトリ構成を守ってないんですか......。
@range = ARGV[0]
data = `git diff --stat=256 --no-color #{@range}`
detect_role_from_filename = lambda {|filename|
case filename
when %r<^roles/([_a-z]+)/.*$>
# ロールのマニフェスト直接変更の場合は10ポイント
[$1] * 10
when %r<^spec/([_a-z]+)/.*\.rb$>
# 関連するspecの変更の場合は5ポイント
# この辺の傾斜は徐々に調整するといいのでは
if $1 != 'support' # serverspecサポート用のファイルを除外する
[$1] * 5
else
nil
end
when %r<^modules/([_a-z]+)/.*>
# そのほか、普通のモジュールは
# 影響度を雑に計算している
`git grep 'include ::#{$1}' roles/`
.lines
.map{|l| l.split('/')[1] }
.uniq
else
nil # detection failed
end
}
rolenames = data.lines
.map {|l| l.strip.split(/\s+/) }
# ファイル削除の場合は無視
.select {|r| r[3].include? '+' }
.map {|r| detect_role_from_filename.(r[0]) }
.compact
.flatten
# group_by みたいなんを自分でやってる...
stats = rolenames.inject({}) { |dst, record|
dst[record] ||= 0
dst[record] += 1
dst
}
$stderr.puts "Summary: #{stats.inspect}"
# TODO: 同率の場合辞書順...
detected = stats.max_by{|(k, v)| v }
if detected
$stderr.puts "Detected: role #{detected.inspect}"
puts detected[0]
else
$stderr.puts "No role detected. Fall back to testing against <base>"
puts 'base'
end
# おしまい
exit
以下のように、stdoutに最終的に www
みたいなロールを吐き出すようになっていて、シェルスクリプト内部で変数にアサインできる。
$ role=$( script/get_role_by_change.rb origin/master..a123bc45 )
=> stderr: Summary: {"www"=>5, "log"=>3}
=> stderr: Detected: role ["www", 5]
=> `www' is assigned to $role
関係ないとこをテストして厭っぽいとか時間かかりすぎるとか、よくあると思うので、こういうハックをして乗り切るのも一興だと思う。