ruby_git_hooks 0.0.40 → 0.0.44

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZWIzZWY4YWFhMDQ3OWYyMGJlOWQ1ZjExZTA2NjY0ZDBjMGFiOWVlZA==
5
+ data.tar.gz: !binary |-
6
+ ZGJiMTIwZDk4YmY5YWYwMTc3MGJiYTcyNWZlOTg5Y2VlOGM2MWIzNA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTJlOTk4MDliOTA0ZWNkYjQ2Y2FkZmMwZGJlOWQwOTUyOWU3YmUzNGJiN2Fi
10
+ NTk1NTJjMDM4Nzk1YTRkNzhjNWM3ZWZmYjJlZTAyYjAxYjZjZGFjZDIyNzQy
11
+ N2M0NzVlZTY5YjZiMjcwMGQzMDBhNTUwNWNiYWNlYmM0NTU3OWE=
12
+ data.tar.gz: !binary |-
13
+ MjNlNzQzZDVlOWZjYTI0Y2NkMTVlYmMzZjczNjlhM2I5ZGE2NjBjZjVjMTQ3
14
+ NTVlNDRmMGFkYzRkODhjODBjYjk5MTJhYmQ2MjIwYTYyMzJjNzMzYWIxY2Ri
15
+ YWFmMjFhYmE3ODdmYjM5MmUzMDViNWIzYTE0N2MxMjc4YmM4ODk=
@@ -59,11 +59,14 @@ module RubyGitHooks
59
59
 
60
60
  # refs associated with each commit
61
61
  attr_accessor :commit_ref_map
62
+
63
+ # branches included in this push
64
+ attr_accessor :branches_changed
62
65
  end
63
66
 
64
67
  # Instances of Hook delegate these methods to the class methods.
65
68
  HOOK_INFO = [ :files_changed, :file_contents, :file_diffs, :ls_files,
66
- :commits, :commit_message, :commit_message_file, :commit_ref_map ]
69
+ :commits, :commit_message, :commit_message_file, :commit_ref_map, :branches_changed ]
67
70
  HOOK_INFO.each do |info_method|
68
71
  define_method(info_method) do |*args, &block|
69
72
  Hook.send(info_method, *args, &block)
@@ -84,32 +87,38 @@ module RubyGitHooks
84
87
  base, commit, ref = line.strip.split
85
88
  changes.push [base, commit, ref]
86
89
  end
87
- self.commit_ref_map = {} #
88
- # commit_ref_map is a list of which new commits are in this push, and which branches they are associated with
90
+
91
+ self.branches_changed = {} # {ref => [base, commit], ref2 => [base, commit]}
92
+
93
+ self.commit_ref_map = {} # commit_ref_map is a list of which new commits are in this push,
94
+ # and which branches they are associated with
89
95
  # as {commit1 => [ref1, ref2], commit2 => [ref1]}
90
- # For existing branches, this information is sent in directly "base commit ref"
91
- # BUT for branch new branches, the pre/post-receive hook gets "0 commit ref"
92
- # ref is of the form refs/heads/branch_name
93
-
94
- new_branches = changes.select{|base, _, _ | base =~ /\A0+\z/ }.collect{|_,_, ref| ref[/refs\/heads\/(\S+)/,1] }
95
-
96
- if !new_branches.empty?
97
- # For new branches, we will calculate which commits are new by specifically not including commits which are
98
- # present in any other branch (and therefore will have been processed with that branch)
99
- all_branches = Hook.shell!("git branch").split(/[* \n]+/).select{|b| !b.empty?} # remove spaces and the *
100
- # ref is like refs/heads/<branch_name>
101
- existing_branches = all_branches - new_branches
102
- exclude_branches = existing_branches.inject("") {|str, b| str + " ^" + b} # "^B1 ^B2"
96
+
97
+ # figure out which commits have already been processed (everything we have seen before)
98
+ exclude_refs = [] # we know we have seen under these refs already
99
+ # includes all branches not referenced in this push
100
+ # and all commits before the base of referenced branches
101
+ all_branches = Hook.shell!("git for-each-ref --format='%(refname)' refs/heads/").split
102
+ changes.each do |base, _ , ref|
103
+ # ref is of the form refs/heads/branch_name
104
+ all_branches.delete(ref) # we don't want to use the new ref for this branch
105
+ exclude_refs << "^#{base}" unless base =~ /\A0+\z/ # add the old ref for this branch to the exclude list
106
+ # (don't add if it's 0, this is a new branch with no old ref)
103
107
  end
104
108
 
109
+ # add the branches which aren't included in this push if any
110
+ all_branches.each { |ref| exclude_refs << "^#{ref}" }
105
111
 
106
112
  self.files_changed = []
107
113
  self.file_contents = {}
108
114
  self.file_diffs = {}
109
115
 
110
116
  changes.each do |base, commit, ref|
111
- new_branch = base =~ /\A0+\z/
112
- if new_branch
117
+ self.branches_changed[ref] = [base, commit]
118
+
119
+ # TODO : calculate file_diffs and file_contents PER COMMIT for pre and post receive hooks
120
+ # for now it just does the overall diffs
121
+ if base =~ /\A0+\z/
113
122
  # if base is 000 then this is a new branch and we have no easy way know what files were added
114
123
  # so for now just don't include files changed in a new branch
115
124
  # because really this should be done per commit or at least per branch anyway
@@ -134,19 +143,11 @@ module RubyGitHooks
134
143
  file_contents[file_changed] = ""
135
144
  end
136
145
  end
137
-
138
- # now calculate which commits are new
139
- if new_branch
140
- # new branch, but we don't want to include all commits from beginning of time
141
- # so exclude any commits that are on any other branches
142
- # e.g. git rev-list <commit for B3> ^master ^B2
143
- # NOTE: have to use commit, not ref, because if this is called in pre-receive the branch name of ref won't
144
- # actually have been set up yet!
145
- new_commits = Hook.shell!("git rev-list #{commit} #{exclude_branches}").split("\n")
146
- else
147
- # existing branch, base..commit is right
148
- new_commits = Hook.shell!("git rev-list #{base}..#{commit}").split("\n")
149
- end
146
+
147
+ # calculate which commits are new - exclude any commits that are on any other branches
148
+ # e.g. git rev-list <commit for B3> ^old_B3 ^master ^B2 --
149
+ # (the "--" at the end tells git these are refs NOT file references)
150
+ new_commits = Hook.shell!("git rev-list #{commit} #{exclude_refs.join(' ')} --").split("\n")
150
151
 
151
152
  new_commits.each do |one_commit|
152
153
  self.commit_ref_map[one_commit] ||= [];
@@ -109,4 +109,14 @@ module RubyGitHooks::GitOps
109
109
  Hook.shell!("cd #{repo_name} && git rev-list --all").split("\n")
110
110
  end
111
111
 
112
+ def git_merge(repo_name = "child_repo", branch = "B1", msg = "Merge branch #{branch}")
113
+ # better be sure there's not going to be a conflict
114
+ Hook.shell!("cd #{repo_name} && git merge #{branch} --no-ff -m '#{msg}'")
115
+ end
116
+
117
+ def git_ff_merge(repo_name = "child_repo", branch = "B1", msg = "Merge branch #{branch}")
118
+ # better be sure there's not going to be a conflict
119
+ Hook.shell!("cd #{repo_name} && git merge #{branch} --ff")
120
+ end
121
+
112
122
  end
@@ -64,9 +64,6 @@ class JiraCommentAddHook < RubyGitHooks::Hook
64
64
 
65
65
 
66
66
  def check
67
- if commits.empty?
68
- STDERR.puts "JiraCommentAddHook - need list of commits to process"
69
- end
70
67
  # called with a list of commits to check, as post-receive.
71
68
  # consider it a success for now only if all commit checks are successful
72
69
  # may cause us to redo some of the checks.
@@ -114,6 +111,24 @@ class JiraCommentAddHook < RubyGitHooks::Hook
114
111
  uri = "#{repo_remote_path}/commit/#{commit}"
115
112
  end
116
113
 
114
+ def get_change_list(commit)
115
+ # we want changes from the previous commit, if any
116
+ # ideally this list should be available from the ruby_git_hooks directly
117
+ # since they go through this same process.
118
+ # use --first-parent so it lists the correct files after a merge
119
+ current, base = Hook.shell!("git log #{commit} --first-parent -2 --pretty=%H").split
120
+ if !base
121
+ # This is the initial commit so all files were added, but have to add the A ourselves
122
+ files_with_status = Hook.shell!("git ls-tree --name-status -r #{commit}").split("\n")
123
+ # put the A at the front
124
+ files_with_status.map!{|filename| "A\t" + filename}
125
+ else
126
+
127
+ files_with_status = Hook.shell!("git diff --name-status #{base}..#{current}")
128
+ end
129
+ files_with_status
130
+ end
131
+
117
132
  def get_commit_branch(commit)
118
133
  # get the branch (list) for this commit
119
134
  # will usually be a single ref ([refs/heads/branch_name]). but could
@@ -138,21 +153,19 @@ class JiraCommentAddHook < RubyGitHooks::Hook
138
153
  # M test.txt
139
154
 
140
155
  github_link = build_commit_uri(commit) # have to do this separately
141
- branch = "Branch: #{get_commit_branch(commit)}"
142
- begin
143
- content = "Revision: %h committed by %cn%nCommit date: %cd%n#{branch}%n#{github_link}%n%n#{commit_message}%n{noformat}"
144
- text = Hook.shell!("git log #{commit} -1 --name-status --pretty='#{content}'")
145
- text += "{noformat}" # git log puts changes at the bottom, we need to close the noformat tag for Jira
146
- rescue
147
- text = "No commit details available for #{commit}\n#{commit_message}"
148
- end
149
- text
156
+ changes = get_change_list(commit)
157
+ revision_and_date = Hook.shell!("git log #{commit} -1 --pretty='Revision: %h committed by %cn%nCommit date: %cd'") rescue ""
158
+ branch = "Branch: #{get_commit_branch(commit)}\n"
159
+
160
+ text = "#{revision_and_date}#{branch}#{github_link}\n\n#{commit_message}\n{noformat}\n#{changes}{noformat}"
161
+
162
+
150
163
  end
151
164
 
152
165
  def check_one_commit(commit, commit_message)
153
166
  STDERR.puts "Checking #{commit[0..6]} #{commit_message.lines.first}"
154
167
 
155
- jira_tickets = commit_message.scan(JiraReferenceCheckHook::JIRA_TICKET_REGEXP).map(&:strip)
168
+ jira_tickets = commit_message.scan(JiraReferenceCheckHook::JIRA_TICKET_REGEXP).map(&:strip).uniq
156
169
  if jira_tickets.length == 0
157
170
  STDERR.puts ">>Commit message must refer to a jira ticket"
158
171
  add_error_to_report(commit, commit_message, "no_jira")
@@ -1,5 +1,5 @@
1
1
  # Copyright (C) 2013-2014 OL2, Inc. See LICENSE.txt for details.
2
2
 
3
3
  module RubyGitHooks
4
- VERSION = "0.0.40"
4
+ VERSION = "0.0.44"
5
5
  end
@@ -51,6 +51,7 @@ class TestHook < RubyGitHooks::Hook
51
51
  def check
52
52
  File.open("#{TEST_PATH}", "w") do |f|
53
53
  f.puts commit_ref_map.inspect
54
+ f.puts branches_changed.keys
54
55
  end
55
56
 
56
57
  puts "Test hook runs!"
@@ -216,6 +217,67 @@ HOOK
216
217
 
217
218
  end
218
219
 
220
+ def test_pre_receive_with_merge_commit
221
+ @hook_name ||= "pre-receive"
222
+
223
+ add_hook("parent_repo.git", @hook_name, TEST_HOOK_MULTI)
224
+
225
+ # set up master and 2 branches with commits
226
+ # make changes to different files so no merge conflicts
227
+ new_commit("child_repo", "file1.txt","Contents", "master commit")
228
+ git_create_and_checkout_branch("child_repo", "B1")
229
+ new_commit("child_repo", "file2.txt","Contents", "B1 commit")
230
+ git_create_and_checkout_branch("child_repo", "B2")
231
+ new_commit("child_repo", "file3.txt","Contents", "B2 commit")
232
+ git_checkout("child_repo", "master")
233
+ new_commit("child_repo", "file4.txt","Contents", "master commit")
234
+
235
+ git_push_all("child_repo")
236
+ before_commits = git_revlist_all("child_repo") # commits already in the repo
237
+
238
+ # now do a merge commit
239
+ git_checkout("child_repo", "master")
240
+ git_merge("child_repo", "B1")
241
+ git_push_all("child_repo")
242
+
243
+ # make sure none of the before_commits are in the output
244
+ contents = File.read(TEST_PATH)
245
+ before_commits.each do |c|
246
+ refute contents.include?(c), "#{c} shouldn't be processed!"
247
+ end
248
+ end
249
+
250
+ def test_pre_receive_ff_merge
251
+ @hook_name ||= "pre-receive"
252
+
253
+ add_hook("parent_repo.git", @hook_name, TEST_HOOK_MULTI)
254
+
255
+ git_create_and_checkout_branch("child_repo", "B1")
256
+ new_commit("child_repo", "file22.txt","Contents", "B1 commit")
257
+ new_commit("child_repo", "file23.txt","Contents", "B1 commit 2")
258
+ git_push_all("child_repo")
259
+ before_commits = git_revlist_all("child_repo") # commits already in the repo
260
+
261
+ # now a merge ff commit
262
+ # shouldn't be any new commits
263
+ git_checkout("child_repo", "master")
264
+ git_ff_merge("child_repo", "B1")
265
+ after_commits = git_revlist_all("child_repo") # commits already in the repo
266
+
267
+ git_push_all("child_repo")
268
+
269
+ assert_empty(before_commits-after_commits) # there are no new commits
270
+ contents = File.read(TEST_PATH)
271
+ before_commits.each do |c|
272
+ refute contents.include?(c), "#{c} shouldn't be processed!"
273
+ end
274
+
275
+ # should check that branches_changed is accurate
276
+ assert contents.include?("master")
277
+
278
+
279
+ end
280
+
219
281
 
220
282
  def test_pre_receive_with_delete
221
283
  @hook_name ||= "pre-receive"
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  # Copyright (C) 2013-2014 OL2, Inc. See LICENSE.txt for details.
2
3
 
3
4
  require "test_helper"
@@ -32,12 +33,27 @@ class JiraCommentAddHookTest < HookTestCase
32
33
  "username" => "user", "password" => "password"
33
34
  end
34
35
 
35
- def fake_hook_check(msg = "Commit message")
36
+ def fake_hook_check(msg = "Commit message", with_branch_merge = false)
36
37
  new_commit("child_repo", "file.txt","Contents", msg)
37
38
  stub(@hook).commit_message { msg }
38
39
  sha = last_commit_sha("child_repo")
39
- stub(@hook).commits{[sha]}
40
- stub(@hook).commit_ref_map{ {sha => ["refs/heads/master"]} } # it's always the master branch
40
+ hook_refs = {sha => ["refs/heads/master"]} # it's always the master branch
41
+ if with_branch_merge
42
+ git_create_and_checkout_branch("child_repo", "B1")
43
+ new_commit("child_repo", "file2.txt","Contents", "B1 commit\n(#{msg})")
44
+ sha2 = last_commit_sha("child_repo")
45
+
46
+ git_checkout("child_repo", "master")
47
+ git_merge("child_repo", "B1", "Merge Branch B1\n(#{msg})")
48
+ merge_sha = last_commit_sha("child_repo")
49
+
50
+ hook_refs[sha2] = ["refs/heads/master","refs/heads/B1"]
51
+ hook_refs[merge_sha] = ["refs/heads/master"]
52
+ end
53
+
54
+ stub(@hook).commit_ref_map{ hook_refs }
55
+ stub(@hook).commits{hook_refs.keys}
56
+
41
57
  Dir.chdir("child_repo") do
42
58
  @hook.check
43
59
  end
@@ -91,6 +107,22 @@ JSON
91
107
  # but at least we'll make sure it doesn't fail.
92
108
  end
93
109
 
110
+ def test_same_good_reference_twice
111
+ mock(RestClient).get("https://user:password@jira.example.com/rest/api/latest/issue/GOOD-234") { <<JSON }
112
+ { "fields": { "status": { "name": "Open" } } }
113
+ JSON
114
+
115
+ mock(RestClient).post.with_any_args {<<JSON } # more complicated to check the args, just be sure it's called.
116
+ { "fields": { "status": { "name": "Open" } } }
117
+ JSON
118
+ git_tag("child_repo", "0.1")
119
+ fake_hook_check("Message with GOOD-234 reference to Jira and another GOOD-234" )
120
+
121
+ # as long as the mocked RestClient calls happen, we succeeded
122
+ # would be better if we had a way to check if the tag is in the message
123
+ # but at least we'll make sure it doesn't fail.
124
+ end
125
+
94
126
  def test_good_reference_with_description
95
127
 
96
128
  mock(RestClient).get("https://user:password@jira.example.com/rest/api/latest/issue/GOOD-234") { <<JSON }
@@ -103,8 +135,32 @@ JSON
103
135
  # add a tag so describe works
104
136
 
105
137
  fake_hook_check("Message with GOOD-234 reference to Jira" )
138
+ end
139
+
140
+ def test_good_reference_with_long_message
106
141
 
142
+ mock(RestClient).get("https://user:password@jira.example.com/rest/api/latest/issue/GOOD-234") { <<JSON }
143
+ { "fields": { "status": { "name": "Open" } } }
144
+ JSON
145
+
146
+ mock(RestClient).post.with_any_args {<<JSON } # more complicated to check the args, just be sure it's called.
147
+ { "fields": { "status": { "name": "Open" } } }
148
+ JSON
107
149
 
150
+ fake_hook_check("Message with GOOD-234 reference to Jira\n\nWhat if it can't handle unicode like ©?\n(Good, it can!)" )
151
+ end
152
+
153
+ def test_good_ref_with_merge
154
+ stub(RestClient).get("https://user:password@jira.example.com/rest/api/latest/issue/GOOD-234") { <<JSON }
155
+ { "fields": { "status": { "name": "Open" } } }
156
+ JSON
157
+
158
+ stub(RestClient).post.with_any_args {<<JSON } # more complicated to check the args, just be sure it's called.
159
+ { "fields": { "status": { "name": "Open" } } }
160
+ JSON
161
+ # look at output to see what gets generated for message
162
+ puts "***** STARTING MERGE REF CHECK *****"
163
+ fake_hook_check("Message with GOOD-234 reference to Jira" , true)
108
164
  end
109
165
 
110
166
 
@@ -169,4 +225,5 @@ JSON
169
225
 
170
226
 
171
227
 
228
+
172
229
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_git_hooks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.40
5
- prerelease:
4
+ version: 0.0.44
6
5
  platform: ruby
7
6
  authors:
8
7
  - Noah Gibbs
@@ -11,12 +10,11 @@ authors:
11
10
  autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2014-04-14 00:00:00.000000000 Z
13
+ date: 2014-05-01 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: pony
18
17
  requirement: !ruby/object:Gem::Requirement
19
- none: false
20
18
  requirements:
21
19
  - - ! '>='
22
20
  - !ruby/object:Gem::Version
@@ -24,7 +22,6 @@ dependencies:
24
22
  type: :runtime
25
23
  prerelease: false
26
24
  version_requirements: !ruby/object:Gem::Requirement
27
- none: false
28
25
  requirements:
29
26
  - - ! '>='
30
27
  - !ruby/object:Gem::Version
@@ -32,7 +29,6 @@ dependencies:
32
29
  - !ruby/object:Gem::Dependency
33
30
  name: rest-client
34
31
  requirement: !ruby/object:Gem::Requirement
35
- none: false
36
32
  requirements:
37
33
  - - ! '>='
38
34
  - !ruby/object:Gem::Version
@@ -40,7 +36,6 @@ dependencies:
40
36
  type: :runtime
41
37
  prerelease: false
42
38
  version_requirements: !ruby/object:Gem::Requirement
43
- none: false
44
39
  requirements:
45
40
  - - ! '>='
46
41
  - !ruby/object:Gem::Version
@@ -48,7 +43,6 @@ dependencies:
48
43
  - !ruby/object:Gem::Dependency
49
44
  name: json
50
45
  requirement: !ruby/object:Gem::Requirement
51
- none: false
52
46
  requirements:
53
47
  - - ! '>='
54
48
  - !ruby/object:Gem::Version
@@ -56,7 +50,6 @@ dependencies:
56
50
  type: :runtime
57
51
  prerelease: false
58
52
  version_requirements: !ruby/object:Gem::Requirement
59
- none: false
60
53
  requirements:
61
54
  - - ! '>='
62
55
  - !ruby/object:Gem::Version
@@ -64,7 +57,6 @@ dependencies:
64
57
  - !ruby/object:Gem::Dependency
65
58
  name: bundler
66
59
  requirement: !ruby/object:Gem::Requirement
67
- none: false
68
60
  requirements:
69
61
  - - ~>
70
62
  - !ruby/object:Gem::Version
@@ -72,7 +64,6 @@ dependencies:
72
64
  type: :development
73
65
  prerelease: false
74
66
  version_requirements: !ruby/object:Gem::Requirement
75
- none: false
76
67
  requirements:
77
68
  - - ~>
78
69
  - !ruby/object:Gem::Version
@@ -80,7 +71,6 @@ dependencies:
80
71
  - !ruby/object:Gem::Dependency
81
72
  name: minitest
82
73
  requirement: !ruby/object:Gem::Requirement
83
- none: false
84
74
  requirements:
85
75
  - - ! '>='
86
76
  - !ruby/object:Gem::Version
@@ -88,7 +78,6 @@ dependencies:
88
78
  type: :development
89
79
  prerelease: false
90
80
  version_requirements: !ruby/object:Gem::Requirement
91
- none: false
92
81
  requirements:
93
82
  - - ! '>='
94
83
  - !ruby/object:Gem::Version
@@ -96,7 +85,6 @@ dependencies:
96
85
  - !ruby/object:Gem::Dependency
97
86
  name: rr
98
87
  requirement: !ruby/object:Gem::Requirement
99
- none: false
100
88
  requirements:
101
89
  - - ! '>='
102
90
  - !ruby/object:Gem::Version
@@ -104,7 +92,6 @@ dependencies:
104
92
  type: :development
105
93
  prerelease: false
106
94
  version_requirements: !ruby/object:Gem::Requirement
107
- none: false
108
95
  requirements:
109
96
  - - ! '>='
110
97
  - !ruby/object:Gem::Version
@@ -112,7 +99,6 @@ dependencies:
112
99
  - !ruby/object:Gem::Dependency
113
100
  name: rake
114
101
  requirement: !ruby/object:Gem::Requirement
115
- none: false
116
102
  requirements:
117
103
  - - ! '>='
118
104
  - !ruby/object:Gem::Version
@@ -120,7 +106,6 @@ dependencies:
120
106
  type: :development
121
107
  prerelease: false
122
108
  version_requirements: !ruby/object:Gem::Requirement
123
- none: false
124
109
  requirements:
125
110
  - - ! '>='
126
111
  - !ruby/object:Gem::Version
@@ -187,27 +172,26 @@ files:
187
172
  homepage: ''
188
173
  licenses:
189
174
  - MIT
175
+ metadata: {}
190
176
  post_install_message:
191
177
  rdoc_options: []
192
178
  require_paths:
193
179
  - lib
194
180
  required_ruby_version: !ruby/object:Gem::Requirement
195
- none: false
196
181
  requirements:
197
182
  - - ! '>='
198
183
  - !ruby/object:Gem::Version
199
184
  version: '0'
200
185
  required_rubygems_version: !ruby/object:Gem::Requirement
201
- none: false
202
186
  requirements:
203
187
  - - ! '>='
204
188
  - !ruby/object:Gem::Version
205
189
  version: '0'
206
190
  requirements: []
207
191
  rubyforge_project:
208
- rubygems_version: 1.8.23
192
+ rubygems_version: 2.2.2
209
193
  signing_key:
210
- specification_version: 3
194
+ specification_version: 4
211
195
  summary: DSL and manager for git hooks in Ruby.
212
196
  test_files:
213
197
  - test/basic_hook_test.rb