git-commit-notifier 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/LICENSE +1 -1
  2. data/README.textile +20 -5
  3. data/Rakefile +40 -20
  4. data/VERSION +1 -1
  5. data/bin/git-commit-notifier +8 -1
  6. data/config/git-notifier-config.yml.sample +41 -15
  7. data/git-commit-notifier.gemspec +50 -24
  8. data/lib/commit_hook.rb +104 -74
  9. data/lib/diff_to_html.rb +120 -52
  10. data/lib/emailer.rb +43 -22
  11. data/lib/git.rb +36 -26
  12. data/lib/logger.rb +48 -0
  13. data/lib/result_processor.rb +9 -5
  14. data/{test → spec}/fixtures/existing_file_one_line.txt +0 -0
  15. data/{test → spec}/fixtures/git-notifier-group-email-by-push.yml +2 -0
  16. data/{test → spec}/fixtures/git-notifier-ignore-merge.yml +0 -0
  17. data/{test → spec}/fixtures/git-notifier-with-merge.yml +0 -0
  18. data/{test → spec}/fixtures/git_log +0 -0
  19. data/{test → spec}/fixtures/git_show_055850e7d925110322b8db4e17c3b840d76e144c +0 -0
  20. data/{test → spec}/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 +0 -0
  21. data/{test → spec}/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 +0 -0
  22. data/{test → spec}/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d +0 -0
  23. data/{test → spec}/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe +0 -0
  24. data/spec/fixtures/git_show_ff037a73fc1094455e7bbf506171a3f3cf873ae6 +18 -0
  25. data/{test → spec}/fixtures/new_file_one_line.txt +0 -0
  26. data/spec/lib/commit_hook_spec.rb +88 -0
  27. data/spec/lib/diff_to_html_spec.rb +168 -0
  28. data/spec/lib/emailer_spec.rb +102 -0
  29. data/spec/lib/git_spec.rb +93 -0
  30. data/spec/lib/logger_spec.rb +63 -0
  31. data/spec/lib/result_processor_spec.rb +102 -0
  32. data/{test/test_helper.rb → spec/spec_helper.rb} +14 -12
  33. data/template/email.html.erb +2 -2
  34. data/template/styles.css +2 -1
  35. metadata +110 -31
  36. data/test/unit/test_commit_hook.rb +0 -43
  37. data/test/unit/test_diff_to_html.rb +0 -160
  38. data/test/unit/test_result_processor.rb +0 -95
data/lib/diff_to_html.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'diff/lcs'
3
+ require 'digest/sha1'
4
+
3
5
  require File.dirname(__FILE__) + '/result_processor'
4
6
 
5
7
  def escape_content(s)
@@ -10,16 +12,23 @@ class DiffToHtml
10
12
  INTEGRATION_MAP = {
11
13
  :mediawiki => { :search_for => /\[\[([^\[\]]+)\]\]/, :replace_with => '#{url}/\1' },
12
14
  :redmine => { :search_for => /\b(?:refs|fixes)\s*\#(\d+)/i, :replace_with => '#{url}/issues/show/\1' },
13
- :bugzilla => { :search_for => /\bBUG\s*(\d+)/i, :replace_with => '#{url}/show_bug.cgi?id=\1' }
15
+ :bugzilla => { :search_for => /\bBUG\s*(\d+)/i, :replace_with => '#{url}/show_bug.cgi?id=\1' },
16
+ :fogbugz => { :search_for => /\bbugzid:\s*(\d+)/i, :replace_with => '#{url}\1' }
14
17
  }.freeze
15
-
18
+ MAX_COMMITS_PER_ACTION = 10000
19
+ HANDLED_COMMITS_FILE = 'previously.txt'.freeze
20
+ NEW_HANDLED_COMMITS_FILE = 'previously_new.txt'.freeze
21
+
16
22
  attr_accessor :file_prefix, :current_file_name
17
- attr_reader :result
23
+ attr_reader :result, :branch
18
24
 
19
25
  def initialize(previous_dir = nil, config = nil)
20
26
  @previous_dir = previous_dir
21
27
  @config = config || {}
22
28
  @lines_added = 0
29
+ @file_added = false
30
+ @file_removed = false
31
+ @binary = false
23
32
  end
24
33
 
25
34
  def range_info(range)
@@ -44,9 +53,17 @@ class DiffToHtml
44
53
  end
45
54
  end
46
55
 
56
+ def lines_per_diff
57
+ @config['lines_per_diff']
58
+ end
59
+
60
+ def add_separator
61
+ return if lines_per_diff && @lines_added >= lines_per_diff
62
+ @diff_result << '<tr class="sep"><td class="sep" colspan="3" title="Unchanged content skipped between diff. blocks">&hellip;</td></tr>'
63
+ end
64
+
47
65
  def add_line_to_result(line, escape)
48
66
  @lines_added += 1
49
- lines_per_diff = @config['lines_per_diff']
50
67
  if lines_per_diff
51
68
  if @lines_added == lines_per_diff
52
69
  @diff_result << '<tr><td colspan="3">Diff too large and stripped&hellip;</td></tr>'
@@ -62,7 +79,7 @@ class DiffToHtml
62
79
  end
63
80
 
64
81
  def extract_block_content(block)
65
- block.collect { |b| b[:content]}.join("\n")
82
+ block.collect { |b| b[:content] }.join("\n")
66
83
  end
67
84
 
68
85
  def lcs_diff(removals, additions)
@@ -96,22 +113,22 @@ class DiffToHtml
96
113
  end
97
114
 
98
115
  def tokenize_string(str)
99
- # tokenize by non-alphanumerical characters
116
+ # tokenize by non-word characters
100
117
  tokens = []
101
118
  token = ''
102
- str = str.split('')
103
- str.each_with_index do |char, i|
104
- alphanumeric = !char.match(/[a-zA-Z0-9]/).nil?
105
- if !alphanumeric || str.size == i+1
106
- token += char if alphanumeric
107
- tokens << token unless token.empty?
108
- tokens << char unless alphanumeric
109
- token = ''
119
+ str.scan(/./mu) do |ch|
120
+ if ch =~ /[^\W_]/u
121
+ token += ch
110
122
  else
111
- token += char
123
+ unless token.empty?
124
+ tokens << token
125
+ token = ''
126
+ end
127
+ tokens << ch
112
128
  end
113
129
  end
114
- return tokens
130
+ tokens << token unless token.empty?
131
+ tokens
115
132
  end
116
133
 
117
134
  def operation_description
@@ -123,19 +140,31 @@ class DiffToHtml
123
140
  else
124
141
  op = "Changed"
125
142
  end
126
-
143
+
127
144
  file_name = @current_file_name
128
-
145
+
129
146
  if (@config["link_files"] && @config["link_files"] == "gitweb" && @config["gitweb"])
130
147
  file_name = "<a href='#{@config['gitweb']['path']}?p=#{@config['gitweb']['project']};f=#{file_name};hb=HEAD'>#{file_name}</a>"
131
148
  elsif (@config["link_files"] && @config["link_files"] == "gitorious" && @config["gitorious"])
132
- file_name = "<a href='#{@config['gitorious']['path']}/#{@config['gitorious']['project']}/#{@config['gitorious']['repository']}/blobs/HEAD/#{file_name}'>#{file_name}</a>"
149
+ file_name = "<a href='#{@config['gitorious']['path']}/#{@config['gitorious']['project']}/#{@config['gitorious']['repository']}/blobs/#{branch}/#{file_name}'>#{file_name}</a>"
150
+ elsif (@config["link_files"] && @config["link_files"] == "cgit" && @config["cgit"])
151
+ file_name = "<a href='#{@config['cgit']['path']}/#{@config['cgit']['project']}/tree/#{file_name}'>#{file_name}</a>"
133
152
  end
134
-
153
+
135
154
  header = "#{op} #{binary}file #{file_name}"
136
155
  "<h2>#{header}</h2>\n"
137
156
  end
138
157
 
158
+ def lines_are_sequential?(first, second)
159
+ result = false
160
+ [:added, :removed].each do |side|
161
+ if !first[side].nil? && !second[side].nil?
162
+ result = true if first[side] == (second[side] - 1)
163
+ end
164
+ end
165
+ result
166
+ end
167
+
139
168
  def add_changes_to_result
140
169
  return if @current_file_name.nil?
141
170
  @diff_result << operation_description
@@ -143,12 +172,10 @@ class DiffToHtml
143
172
  unless @diff_lines.empty?
144
173
  removals = []
145
174
  additions = []
146
- @diff_lines.each do |line|
147
- if [:addition, :removal].include?(line[:op])
148
- removals << line if line[:op] == :removal
149
- additions << line if line[:op] == :addition
150
- end
151
- if line[:op] == :unchanged || line == @diff_lines.last # unchanged line or end of block, add prev lines to result
175
+ @diff_lines.each_with_index do |line, index|
176
+ removals << line if line[:op] == :removal
177
+ additions << line if line[:op] == :addition
178
+ if line[:op] == :unchanged || index == @diff_lines.size - 1 # unchanged line or end of block, add prev lines to result
152
179
  if removals.size > 0 && additions.size > 0 # block of removed and added lines - perform intelligent diff
153
180
  add_block_to_results(lcs_diff(removals, additions), escape = false)
154
181
  else # some lines removed or added - no need to perform intelligent diff
@@ -156,6 +183,10 @@ class DiffToHtml
156
183
  end
157
184
  removals = []
158
185
  additions = []
186
+ if index > 0 && index != @diff_lines.size - 1
187
+ prev_line = @diff_lines[index - 1]
188
+ add_separator unless lines_are_sequential?(prev_line, line)
189
+ end
159
190
  add_line_to_result(line, escape = true) if line[:op] == :unchanged
160
191
  end
161
192
  end
@@ -179,8 +210,7 @@ class DiffToHtml
179
210
  @current_file_name = nil
180
211
 
181
212
  content.split("\n").each do |line|
182
- if line =~ /^diff\s\-\-git/
183
- line.match(/diff --git a\/(.*)\sb\//)
213
+ if line =~ /^diff\s\-\-git\sa\/(.*)\sb\//
184
214
  file_name = $1
185
215
  add_changes_to_result
186
216
  @current_file_name = file_name
@@ -200,7 +230,7 @@ class DiffToHtml
200
230
  elsif op == '+'
201
231
  @diff_lines << { :added => @right_ln, :removed => nil, :op => :addition, :content => line[1..-1] }
202
232
  @right_ln += 1
203
- else @right_ln
233
+ else
204
234
  @diff_lines << { :added => @right_ln, :removed => @left_ln, :op => :unchanged, :content => line }
205
235
  @right_ln += 1
206
236
  @left_ln += 1
@@ -229,7 +259,7 @@ class DiffToHtml
229
259
  diff = []
230
260
  diff_found = false
231
261
  content.split("\n").each do |line|
232
- diff_found = true if line =~ /^diff \-\-git/
262
+ diff_found = true if line =~ /^diff\s\-\-git/
233
263
  next unless diff_found
234
264
  diff << line
235
265
  end
@@ -258,15 +288,14 @@ class DiffToHtml
258
288
  end
259
289
 
260
290
  def message_array_as_html(message)
261
- message_map(message.collect { |m| CGI.escapeHTML(m)}.join("<br />"))
291
+ message_map(message.collect { |m| CGI.escapeHTML(m) }.join('<br />'))
262
292
  end
263
293
 
264
294
  def author_name_and_email(info)
265
295
  # input string format: "autor name <author@email.net>"
266
- result = info.scan(/(.*)\s<(.*)>/)[0]
267
- return result if result.is_a?(Array) && result.size == 2 # normal operation
296
+ return [$1, $2] if info =~ /^([^\<]+)\s+\<\s*(.*)\s*\>\s*$/ # normal operation
268
297
  # incomplete author info - return it as author name
269
- return [info, ''] if result.nil?
298
+ [info, '']
270
299
  end
271
300
 
272
301
  def first_sentence(message_array)
@@ -275,7 +304,48 @@ class DiffToHtml
275
304
  msg
276
305
  end
277
306
 
307
+ def unique_commits_per_branch?
308
+ !!@config['unique_commits_per_branch']
309
+ end
310
+
311
+ def get_previous_commits(previous_file)
312
+ previous_list = []
313
+ if File.exists?(previous_file)
314
+ lines = IO.read(previous_file)
315
+ lines = lines.lines if lines.respond_to?(:lines) # Ruby 1.9 tweak
316
+ previous_list = lines.to_a.map { |s| s.chomp }.compact.uniq
317
+ lines = nil
318
+ end
319
+ previous_list
320
+ end
321
+
322
+ def check_handled_commits(commits, branch)
323
+ previous_dir = (!@previous_dir.nil? && File.exists?(@previous_dir)) ? @previous_dir : '/tmp'
324
+ prefix = unique_commits_per_branch? ? "#{Digest::SHA1.hexdigest(branch)}." : ''
325
+ previous_name = "#{prefix}#{HANDLED_COMMITS_FILE}"
326
+ new_name = "#{prefix}#{NEW_HANDLED_COMMITS_FILE}"
327
+ previous_file = File.join(previous_dir, previous_name)
328
+ new_file = File.join(previous_dir, new_name)
329
+
330
+ previous_list = get_previous_commits(previous_file)
331
+
332
+ commits.reject! {|c| c.find { |sha| previous_list.include?(sha) } }
333
+
334
+ # if commit list empty there is no need to override list of handled commits
335
+ flatten_commits = commits.flatten
336
+ unless flatten_commits.empty?
337
+ current_list = (previous_list + flatten_commits).last(MAX_COMMITS_PER_ACTION)
338
+
339
+ # use new file, unlink and rename to make it more atomic
340
+ File.open(new_file, 'w') { |f| f << current_list.join("\n") }
341
+ File.unlink(previous_file) if File.exists?(previous_file)
342
+ File.rename(new_file, previous_file)
343
+ end
344
+ commits
345
+ end
346
+
278
347
  def diff_between_revisions(rev1, rev2, repo, branch)
348
+ @branch = branch
279
349
  @result = []
280
350
  if rev1 == rev2
281
351
  commits = [rev1]
@@ -287,42 +357,36 @@ class DiffToHtml
287
357
  commits = []
288
358
  else
289
359
  log = Git.log(rev1, rev2)
290
- commits = log.scan(/^commit\s([a-f0-9]+)/).map{|match| match[0]}
360
+ commits = log.scan(/^commit\s([a-f0-9]+)/).map { |a| a.first }
291
361
  end
292
362
 
293
- if defined?(Test::Unit)
294
- previous_list = []
295
- else
296
- previous_file = (!@previous_dir.nil? && File.exists?(@previous_dir)) ? File.join(@previous_dir, "previously.txt") : "/tmp/previously.txt"
297
- previous_list = (File.read(previous_file).to_a.map {|sha| sha.chomp!} if File.exist?(previous_file)) || []
298
- end
299
-
300
- commits.reject!{|c| c.find{|sha| previous_list.include?(sha)} }
301
- current_list = (previous_list + commits.flatten).last(1000)
302
- File.open(previous_file, "w"){|f| f << current_list.join("\n") } unless current_list.empty? || defined?(Test::Unit)
363
+ commits = check_handled_commits(commits, branch)
303
364
 
304
365
  commits.each_with_index do |commit, i|
305
-
366
+
306
367
  raw_diff = Git.show(commit)
307
368
  raise "git show output is empty" if raw_diff.empty?
308
- @last_raw = raw_diff
309
369
 
310
370
  commit_info = extract_commit_info_from_git_show_output(raw_diff)
311
371
 
312
372
  title = "<div class=\"title\">"
313
373
  title += "<strong>Message:</strong> #{message_array_as_html commit_info[:message]}<br />\n"
314
374
  title += "<strong>Commit:</strong> "
315
-
375
+
316
376
  if (@config["link_files"] && @config["link_files"] == "gitweb" && @config["gitweb"])
317
377
  title += "<a href='#{@config['gitweb']['path']}?p=#{@config['gitweb']['project']};a=commitdiff;h=#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
318
378
  elsif (@config["link_files"] && @config["link_files"] == "gitorious" && @config["gitorious"])
319
379
  title += "<a href='#{@config['gitorious']['path']}/#{@config['gitorious']['project']}/#{@config['gitorious']['repository']}/commit/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
380
+ elsif (@config["link_files"] && @config["link_files"] == "trac" && @config["trac"])
381
+ title += "<a href='#{@config['trac']['path']}/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
382
+ elsif (@config["link_files"] && @config["link_files"] == "cgit" && @config["cgit"])
383
+ title += "<a href='#{@config['cgit']['path']}/#{@config['cgit']['project']}/commit/?id=#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
320
384
  else
321
385
  title += " #{commit_info[:commit]}"
322
386
  end
323
-
387
+
324
388
  title += "<br />\n"
325
-
389
+
326
390
  title += "<strong>Branch:</strong> #{branch}\n<br />" unless branch =~ /\/head/
327
391
  title += "<strong>Date:</strong> #{CGI.escapeHTML commit_info[:date]}\n<br />"
328
392
  title += "<strong>Author:</strong> #{CGI.escapeHTML(commit_info[:author])} &lt;#{commit_info[:email]}&gt;\n</div>"
@@ -343,14 +407,14 @@ class DiffToHtml
343
407
  end
344
408
 
345
409
  def message_map(message)
346
- if @config.include?('message_integration')
410
+ if @config['message_integration'].respond_to?(:each_pair)
347
411
  @config['message_integration'].each_pair do |pm, url|
348
412
  pm_def = DiffToHtml::INTEGRATION_MAP[pm.to_sym] or next
349
413
  replace_with = pm_def[:replace_with].gsub('#{url}', url)
350
414
  message_replace!(message, pm_def[:search_for], replace_with)
351
415
  end
352
416
  end
353
- if @config.include?('message_map')
417
+ if @config['message_map'].respond_to?(:each_pair)
354
418
  @config['message_map'].each_pair do |search_for, replace_with|
355
419
  message_replace!(message, Regexp.new(search_for), replace_with)
356
420
  end
@@ -379,3 +443,7 @@ class DiffCallback
379
443
  end
380
444
 
381
445
  end
446
+
447
+ __END__
448
+
449
+ vim: tabstop=2:expandtab:shiftwidth=2
data/lib/emailer.rb CHANGED
@@ -1,23 +1,42 @@
1
- require 'yaml'
2
- require 'erb'
3
1
  require 'tamtam'
4
2
 
5
3
  class Emailer
4
+ DEFAULT_STYLESHEET_PATH = File.join(File.dirname(__FILE__), '/../template/styles.css').freeze
5
+ TEMPLATE = File.join(File.dirname(__FILE__), '/../template/email.html.erb').freeze
6
+ PARAMETERS = %w[project_path recipient from_address from_alias subject text_message html_message ref_name old_rev new_rev].freeze
6
7
 
7
- def initialize(config, project_path, recipient, from_address, from_alias, subject, text_message, html_diff, old_rev, new_rev, ref_name)
8
+ attr_reader :config
9
+
10
+ def initialize(config, options = {})
8
11
  @config = config || {}
9
- @project_path = project_path
10
- @recipient = recipient
11
- @from_address = from_address
12
- @from_alias = from_alias
13
- @subject = subject
14
- @text_message = text_message
15
- @ref_name = ref_name
16
- @old_rev = old_rev
17
- @new_rev = new_rev
18
-
19
- template = File.join(File.dirname(__FILE__), '/../template/email.html.erb')
20
- @html_message = TamTam.inline(:document => ERB.new(File.read(template)).result(binding))
12
+ PARAMETERS.each do |name|
13
+ instance_variable_set("@#{name}".to_sym, options[name.to_sym])
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def reset_template
19
+ @template = nil
20
+ end
21
+
22
+ def template
23
+ unless @template
24
+ source = IO.read(TEMPLATE)
25
+ begin
26
+ require 'erubis'
27
+ @template = Erubis::Eruby.new(source)
28
+ rescue LoadError
29
+ require 'erb'
30
+ @template = ERB.new(source)
31
+ end
32
+ end
33
+ @template
34
+ end
35
+ end
36
+
37
+ def generate_message
38
+ # TODO: do not use @html, simply return value
39
+ @html = TamTam.inline(:document => Emailer.template.result(binding))
21
40
  end
22
41
 
23
42
  def boundary
@@ -28,8 +47,8 @@ class Emailer
28
47
  end
29
48
 
30
49
  def stylesheet_string
31
- stylesheet = File.join(File.dirname(__FILE__), '/../template/styles.css')
32
- File.read(stylesheet)
50
+ stylesheet = config['stylesheet'] || DEFAULT_STYLESHEET_PATH
51
+ IO.read(stylesheet)
33
52
  end
34
53
 
35
54
  def perform_delivery_smtp(content, smtp_settings)
@@ -38,12 +57,12 @@ class Emailer
38
57
  val = smtp_settings[key].to_s.empty? ? nil : smtp_settings[key]
39
58
  settings.merge!({ key => val})
40
59
  end
41
-
60
+
42
61
  Net::SMTP.start(settings['address'], settings['port'], settings['domain'],
43
62
  settings['user_name'], settings['password'], settings['authentication']) do |smtp|
44
63
 
45
64
  smtp.enable_tls if settings['enable_tls']
46
-
65
+
47
66
  smtp.open_message_stream(@from_address, [@recipient]) do |f|
48
67
  content.each do |line|
49
68
  f.puts line
@@ -63,13 +82,15 @@ class Emailer
63
82
  f.flush
64
83
  end
65
84
  end
66
-
85
+
67
86
  def send
87
+ generate_message
68
88
  from = quote_if_necessary(@from_alias.empty? ? @from_address : "#{@from_alias} <#{@from_address}>", 'utf-8')
69
89
  content = ["From: #{from}",
70
90
  "Reply-To: #{from}",
71
91
  "To: #{quote_if_necessary(@recipient, 'utf-8')}",
72
92
  "Subject: #{quote_if_necessary(@subject, 'utf-8')}",
93
+ "X-Mailer: git-commit-notifier",
73
94
  "X-Git-Refname: #{@ref_name}",
74
95
  "X-Git-Oldrev: #{@old_rev}",
75
96
  "X-Git-Newrev: #{@new_rev}",
@@ -84,7 +105,7 @@ class Emailer
84
105
  "Content-Type: text/html; charset=utf-8",
85
106
  "Content-Transfer-Encoding: 8bit",
86
107
  "Content-Disposition: inline\n\n\n",
87
- @html_message,
108
+ @html,
88
109
  "--#{boundary}--"]
89
110
 
90
111
  if @recipient.empty?
@@ -92,7 +113,7 @@ class Emailer
92
113
  return
93
114
  end
94
115
 
95
- if @config['delivery_method'] == 'smtp'
116
+ if config['delivery_method'] == 'smtp'
96
117
  perform_delivery_smtp(content, @config['smtp_server'])
97
118
  else
98
119
  perform_delivery_sendmail(content, @config['sendmail_options'])
data/lib/git.rb CHANGED
@@ -1,35 +1,45 @@
1
1
  class Git
2
- def self.show(rev)
3
- `git show #{rev.strip} -w`
4
- end
2
+ class << self
3
+ def from_shell(cmd)
4
+ `#{cmd}`
5
+ end
5
6
 
6
- def self.log(rev1, rev2)
7
- `git log #{rev1}..#{rev2}`.strip
8
- end
7
+ def show(rev)
8
+ from_shell("git show #{rev.strip} -w")
9
+ end
9
10
 
10
- def self.branch_commits(treeish)
11
- args = Git.branch_heads - [Git.branch_head(treeish)]
12
- args.map! {|tree| "^#{tree}"}
13
- args << treeish
14
- `git rev-list #{args.join(' ')}`.to_a.map{|commit| commit.chomp}
15
- end
11
+ def log(rev1, rev2)
12
+ from_shell("git log #{rev1}..#{rev2}").strip
13
+ end
16
14
 
17
- def self.branch_heads
18
- `git rev-parse --branches`.to_a.map{|head| head.chomp}
19
- end
15
+ def branch_commits(treeish)
16
+ args = Git.branch_heads - [ Git.branch_head(treeish) ]
17
+ args.map! { |tree| "^#{tree}" }
18
+ args << treeish
19
+ lines = from_shell("git rev-list #{args.join(' ')}")
20
+ lines = lines.lines if lines.respond_to?(:lines)
21
+ lines.to_a.map { |commit| commit.chomp }
22
+ end
20
23
 
21
- def self.branch_head(treeish)
22
- `git rev-parse #{treeish}`.strip
23
- end
24
+ def branch_heads
25
+ lines = from_shell("git rev-parse --branches")
26
+ lines = lines.lines if lines.respond_to?(:lines)
27
+ lines.to_a.map { |head| head.chomp }
28
+ end
24
29
 
25
- def self.repo_name
26
- git_prefix = `git config hooks.emailprefix`.strip
27
- return git_prefix unless git_prefix.empty?
28
- dir_name = `pwd`.chomp.split("/").last.gsub(/\.git$/, '')
29
- return "#{dir_name}"
30
- end
30
+ def branch_head(treeish)
31
+ from_shell("git rev-parse #{treeish}").strip
32
+ end
31
33
 
32
- def self.mailing_list_address
33
- `git config hooks.mailinglist`.strip
34
+ def repo_name
35
+ git_prefix = from_shell("git config hooks.emailprefix").strip
36
+ return git_prefix unless git_prefix.empty?
37
+ Dir.pwd.split("/").last.sub(/\.git$/, '')
38
+ end
39
+
40
+ def mailing_list_address
41
+ from_shell("git config hooks.mailinglist").strip
42
+ end
34
43
  end
35
44
  end
45
+