git-commit-notifier 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- require 'tamtam'
1
+ require 'premailer'
2
2
 
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
3
+ class GitCommitNotifier::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
6
  PARAMETERS = %w[project_path recipient from_address from_alias subject text_message html_message ref_name old_rev new_rev].freeze
7
7
 
8
8
  attr_reader :config
@@ -34,9 +34,10 @@ class Emailer
34
34
  end
35
35
  end
36
36
 
37
- def generate_message
38
- # TODO: do not use @html, simply return value
39
- @html = TamTam.inline(:document => Emailer.template.result(binding))
37
+ def mail_html_message
38
+ html = Emailer.template.result(binding)
39
+ premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
40
+ premailer.to_inline_css
40
41
  end
41
42
 
42
43
  def boundary
@@ -58,12 +59,14 @@ class Emailer
58
59
  settings.merge!({ key => val})
59
60
  end
60
61
 
61
- Net::SMTP.start(settings['address'], settings['port'], settings['domain'],
62
- settings['user_name'], settings['password'], settings['authentication']) do |smtp|
62
+ main_smtp = Net::SMTP.new settings['address'], settings['port']
63
63
 
64
- smtp.enable_tls if settings['enable_tls']
64
+ main_smtp.enable_starttls if settings['enable_tls']
65
+ main_smtp.start( settings['domain'],
66
+ settings['user_name'], settings['password'], settings['authentication']) do |smtp|
65
67
 
66
- smtp.open_message_stream(@from_address, [@recipient]) do |f|
68
+ recp = @recipient.split(",")
69
+ smtp.open_message_stream(@from_address, recp) do |f|
67
70
  content.each do |line|
68
71
  f.puts line
69
72
  end
@@ -83,30 +86,39 @@ class Emailer
83
86
  end
84
87
  end
85
88
 
89
+ def perform_delivery_nntp(content, nntp_settings)
90
+ require 'rubygems'
91
+ require 'nntp'
92
+ Net::NNTP.start(nntp_settings['address'], nntp_settings['port']) do |nntp|
93
+ nntp.post content
94
+ end
95
+ end
96
+
86
97
  def send
87
- generate_message
88
- from = quote_if_necessary(@from_alias.empty? ? @from_address : "#{@from_alias} <#{@from_address}>", 'utf-8')
89
- content = ["From: #{from}",
90
- "Reply-To: #{from}",
91
- "To: #{quote_if_necessary(@recipient, 'utf-8')}",
92
- "Subject: #{quote_if_necessary(@subject, 'utf-8')}",
93
- "X-Mailer: git-commit-notifier",
94
- "X-Git-Refname: #{@ref_name}",
95
- "X-Git-Oldrev: #{@old_rev}",
96
- "X-Git-Newrev: #{@new_rev}",
97
- "Mime-Version: 1.0",
98
+ to_tag = config['delivery_method'] == 'nntp' ? 'Newsgroups' : 'To'
99
+ quoted_from_alias = quote_if_necessary("#{@from_alias}",'utf-8')
100
+ from = @from_alias.empty? ? @from_address : "#{quoted_from_alias} <#{@from_address}>"
101
+
102
+ content = ["From: #{from}\n",
103
+ "#{to_tag}: #{quote_if_necessary(@recipient, 'utf-8')}\n",
104
+ "Subject: #{quote_if_necessary(@subject, 'utf-8')}\n",
105
+ "X-Mailer: git-commit-notifier\n",
106
+ "X-Git-Refname: #{@ref_name}\n",
107
+ "X-Git-Oldrev: #{@old_rev}\n",
108
+ "X-Git-Newrev: #{@new_rev}\n",
109
+ "Mime-Version: 1.0\n",
98
110
  "Content-Type: multipart/alternative; boundary=#{boundary}\n\n\n",
99
- "--#{boundary}",
100
- "Content-Type: text/plain; charset=utf-8",
101
- "Content-Transfer-Encoding: 8bit",
111
+ "--#{boundary}\n",
112
+ "Content-Type: text/plain; charset=utf-8\n",
113
+ "Content-Transfer-Encoding: 8bit\n",
102
114
  "Content-Disposition: inline\n\n\n",
103
115
  @text_message,
104
- "--#{boundary}",
105
- "Content-Type: text/html; charset=utf-8",
106
- "Content-Transfer-Encoding: 8bit",
116
+ "\n--#{boundary}\n",
117
+ "Content-Type: text/html; charset=utf-8\n",
118
+ "Content-Transfer-Encoding: 8bit\n",
107
119
  "Content-Disposition: inline\n\n\n",
108
- @html,
109
- "--#{boundary}--"]
120
+ mail_html_message,
121
+ "\n--#{boundary}--"]
110
122
 
111
123
  if @recipient.empty?
112
124
  puts content.join("\n")
@@ -116,7 +128,11 @@ class Emailer
116
128
  if config['delivery_method'] == 'smtp'
117
129
  perform_delivery_smtp(content, @config['smtp_server'])
118
130
  else
119
- perform_delivery_sendmail(content, @config['sendmail_options'])
131
+ if config['delivery_method'] == 'nntp'
132
+ perform_delivery_nntp(content, @config['nntp_settings'])
133
+ else
134
+ perform_delivery_sendmail(content, @config['sendmail_options'])
135
+ end
120
136
  end
121
137
  end
122
138
 
@@ -0,0 +1,5 @@
1
+ module GitCommitNotifier::EscapeHelper
2
+ def escape_content(s)
3
+ CGI.escapeHTML(s).gsub(" ", "&nbsp;")
4
+ end
5
+ end
@@ -1,4 +1,4 @@
1
- class Git
1
+ class GitCommitNotifier::Git
2
2
  class << self
3
3
  def from_shell(cmd)
4
4
  `#{cmd}`
@@ -1,6 +1,6 @@
1
1
  require 'tmpdir'
2
2
 
3
- class Logger
3
+ class GitCommitNotifier::Logger
4
4
  DEFAULT_LOG_DIRECTORY = Dir.tmpdir.freeze
5
5
  LOG_NAME = 'git-commit-notifier.log'.freeze
6
6
 
@@ -0,0 +1,131 @@
1
+ require 'cgi'
2
+
3
+ require 'git_commit_notifier/escape_helper'
4
+
5
+ module GitCommitNotifier
6
+ class ResultProcessor
7
+ include EscapeHelper
8
+ # input (loaded in @diff) is an array having Hash elements:
9
+ # { :action => action, :token => string }
10
+ # action can be :discard_a, :discard_b or :match
11
+
12
+ # output: two formatted html strings, one for the removals and one for the additions
13
+
14
+ def results
15
+ close_tags # close last tag
16
+ [array_of_lines(@result[:removal]), array_of_lines(@result[:addition])]
17
+ end
18
+
19
+ def length_in_chars(diff)
20
+ diff.inject(0) do |length, s|
21
+ token = s[:token]
22
+ token_length = token.respond_to?(:jlength) ? token.jlength : token.length
23
+ length + token_length
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def initialize(diff)
30
+ @diff = diff
31
+ init
32
+ filter_replaced_lines
33
+ process
34
+ end
35
+
36
+ def init
37
+ @result = { :addition => [], :removal => [] }
38
+ @tag_open = { :addition => false, :removal => false}
39
+ end
40
+
41
+ def process
42
+ @highlight = !@diff.select { |d| d[:action] == :match}.empty? # highlight only if block contains both matches and differences
43
+ @diff.each do |diff|
44
+ case diff[:action]
45
+ when :match
46
+ match(diff)
47
+ when :discard_a
48
+ discard_a(diff)
49
+ when :discard_b
50
+ discard_b(diff)
51
+ end
52
+ end
53
+ end
54
+
55
+ def discard_match(position, token)
56
+ # replace it with 2 changes
57
+ @diff[position][:action] = :discard_a
58
+ @diff.insert(position, { :action => :discard_b, :token => token} )
59
+ end
60
+
61
+ def filter_replaced_lines
62
+ # if a block is replaced by an other one, lcs-diff will find even the single common word between the old and the new content
63
+ # no need for intelligent diff in this case, simply show the removed and the added block with no highlighting
64
+ # rule: if less than 33% of a block is not a match, we don't need intelligent diff for that block
65
+ match_length = length_in_chars(@diff.select { |d| d[:action] == :match })
66
+ total_length = length_in_chars(@diff)
67
+
68
+ if total_length.to_f / match_length > 3.3
69
+ @diff.each_with_index do |d, i|
70
+ next if d[:action] != :match
71
+ discard_match(i, d[:token])
72
+ end
73
+ end
74
+ end
75
+
76
+ def match(diff)
77
+ close_tags
78
+ all_actions do |action|
79
+ close_last_tag(diff)
80
+ @result[action] << escape_content(diff[:token])
81
+ end
82
+ end
83
+
84
+ def discard_a(diff)
85
+ open_tag(:removal, diff[:token])
86
+ close_last_tag(diff)
87
+ @result[:removal] << escape_content(diff[:token])
88
+ end
89
+
90
+ def discard_b(diff)
91
+ open_tag(:addition, diff[:token])
92
+ close_last_tag(diff)
93
+ @result[:addition] << escape_content(diff[:token])
94
+ end
95
+
96
+ def all_actions
97
+ [:addition, :removal].each do |action|
98
+ yield(action)
99
+ end
100
+ end
101
+
102
+ def open_tag(action, next_token)
103
+ return if !@highlight || next_token.strip.empty? # don't open span tag if no highlighting is needed or the first token is empty
104
+ unless @tag_open[action]
105
+ klass = action == :addition ? 'aa' : 'rr'
106
+ @result[action] << "<span class=\"#{klass}\">"
107
+ @tag_open[action] = true
108
+ end
109
+ end
110
+
111
+ def close_tags
112
+ return unless @highlight
113
+ all_actions do |action|
114
+ if @tag_open[action]
115
+ @result[action] << "</span>"
116
+ @tag_open[action] = false
117
+ end
118
+ end
119
+ end
120
+
121
+ def close_last_tag(diff)
122
+ return unless @highlight
123
+ close_tags if diff[:token] == "\n"
124
+ end
125
+
126
+ def array_of_lines(tokens)
127
+ tokens.join('').split("\n")
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,22 @@
1
+ delivery_method: sendmail # smtp or sendmail
2
+ ignore_merge: true # set to true if you want to ignore empty merge messages
3
+
4
+ # defines what branches to email for
5
+ include_branches: [master, branch2]
6
+
7
+
8
+ smtp_server:
9
+ address: localhost
10
+ port: 25
11
+ domain: localhost
12
+ user_name: user@localhost
13
+ password: password
14
+ authentication: plain
15
+ enable_tls: false
16
+
17
+ sendmail_options:
18
+ location: /usr/sbin/sendmail
19
+ arguments:
20
+
21
+
22
+
@@ -1,5 +1,7 @@
1
- require File.expand_path('../../spec_helper', __FILE__)
2
- require 'commit_hook'
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'git_commit_notifier'
3
+
4
+ include GitCommitNotifier
3
5
 
4
6
  describe CommitHook do
5
7
 
@@ -18,17 +20,46 @@ describe CommitHook do
18
20
  run_with_config('spec/fixtures/git-notifier-group-email-by-push.yml', 1)
19
21
  end
20
22
 
21
- def run_with_config(config, times)
23
+ it "should ignore commits to non specified branches if branch limits supplied" do
24
+ # 4 commits, one email for each of them, without merge
25
+ run_and_reject('spec/fixtures/git-notifier-with-branch-restrictions.yml',0,'refs/heads/branchx')
26
+ end
27
+
28
+ it "should email for commits to branch in include_branch" do
29
+ # 4 commits, one email for each of them, without merge
30
+ run_with_config('spec/fixtures/git-notifier-with-branch-restrictions.yml',4,'refs/heads/branch2')
31
+ end
32
+
33
+
34
+ it "should email for commits to master if master set as include_branch" do
35
+ # 4 commits, one email for each of them, without merge
36
+ run_with_config('spec/fixtures/git-notifier-with-branch-restrictions.yml',4)
37
+ end
38
+
39
+
40
+ def run_with_config(config, times, branch = 'refs/heads/master')
22
41
  expect_repository_access
23
42
 
24
43
  emailer = mock!.send.times(times).subject
25
44
  mock(Emailer).new(anything, anything) { emailer }.times(times)
26
-
27
45
  mock(CommitHook).info(/Sending mail/)
28
46
 
29
- any_instance_of(DiffToHtml, :check_handled_commits => lambda { |commits, branch| commits })
30
- CommitHook.run config, REVISIONS.first, REVISIONS.last, 'refs/heads/master'
47
+ any_instance_of(DiffToHtml, :check_handled_commits => lambda { |commits| commits })
48
+ CommitHook.run config, REVISIONS.first, REVISIONS.last, branch
31
49
  end
50
+
51
+
52
+ def run_and_reject(config,times,branch)
53
+ mock(Git).mailing_list_address { 'recipient@test.com' }
54
+
55
+ emailer = mock!.send.times(times).subject
56
+ mock(Emailer).new(anything, anything).times(times)
57
+
58
+ mock(CommitHook).info(/Supressing mail for branch/)
59
+
60
+ CommitHook.run config, REVISIONS.first, REVISIONS.last, branch
61
+ end
62
+
32
63
 
33
64
  def test_commit_from
34
65
  # 1 commit with a from: adress
@@ -81,8 +112,3 @@ describe CommitHook do
81
112
  end
82
113
 
83
114
  end
84
-
85
- __END__
86
-
87
- vim: tabstop=2 expandtab shiftwidth=2
88
-
@@ -1,11 +1,11 @@
1
- require File.expand_path('../../spec_helper', __FILE__)
2
- require 'diff_to_html'
3
- require 'git'
4
- require 'hpricot'
5
-
6
- describe DiffToHtml do
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'nokogiri'
3
+ require 'git_commit_notifier'
7
4
 
5
+ include GitCommitNotifier
8
6
 
7
+ describe DiffToHtml do
8
+
9
9
  describe :lines_are_sequential? do
10
10
  before(:all) do
11
11
  @diff_to_html = DiffToHtml.new
@@ -106,7 +106,7 @@ describe DiffToHtml do
106
106
  end
107
107
 
108
108
  diff = DiffToHtml.new
109
- mock(diff).check_handled_commits(anything, 'master') { |commits, branch| commits }
109
+ mock(diff).check_handled_commits(anything) { |commits| commits }
110
110
  diff.diff_between_revisions REVISIONS.first, REVISIONS.last, 'testproject', 'master'
111
111
 
112
112
  diff.result.should have(5).commits # one result for each of the commits
@@ -116,9 +116,9 @@ describe DiffToHtml do
116
116
  end
117
117
 
118
118
  # first commit
119
- hp = Hpricot diff.result.first[:html_content]
119
+ hp = Nokogiri::HTML diff.result.first[:html_content]
120
120
  (hp/"table").should have(2).tables # 2 files updated - one table for each of the files
121
- (hp/"table/tr/").each do |td|
121
+ (hp/"table"/"tr"/"td").each do |td|
122
122
  if td.inner_html == "require&nbsp;'iconv'"
123
123
  # first added line in changeset a4629e707d80a5769f7a71ca6ed9471015e14dc9
124
124
  td.parent.search('td')[0].inner_text.should == '' # left
@@ -128,11 +128,11 @@ describe DiffToHtml do
128
128
  end
129
129
 
130
130
  # second commit
131
- hp = Hpricot diff.result[1][:html_content]
131
+ hp = Nokogiri::HTML diff.result[1][:html_content]
132
132
  (hp/"table").should have(1).table # 1 file updated
133
133
 
134
134
  # third commit - dce6ade4cdc2833b53bd600ef10f9bce83c7102d
135
- hp = Hpricot diff.result[2][:html_content]
135
+ hp = Nokogiri::HTML diff.result[2][:html_content]
136
136
  (hp/"table").should have(6).tables # 6 files updated
137
137
  (hp/"h2")[1].inner_text.should == 'Added binary file railties/doc/guides/source/images/icons/callouts/11.png'
138
138
  (hp/"h2")[2].inner_text.should == 'Deleted binary file railties/doc/guides/source/icons/up.png'
@@ -140,9 +140,9 @@ describe DiffToHtml do
140
140
  (hp/"h2")[4].inner_text.should == 'Added file railties/doc/guides/source/images/icons/README'
141
141
 
142
142
  # fourth commit - 51b986619d88f7ba98be7d271188785cbbb541a0
143
- hp = Hpricot diff.result[3][:html_content]
143
+ hp = Nokogiri::HTML diff.result[3][:html_content]
144
144
  (hp/"table").should have(3).tables # 3 files updated
145
- (hp/"table/tr/").each do |td|
145
+ (hp/"table"/"tr"/"td").each do |td|
146
146
  if td.inner_html =~ /create_btn/
147
147
  cols = td.parent.search('td')
148
148
  ['405', '408', ''].should be_include(cols[0].inner_text) # line 405 changed
@@ -157,12 +157,90 @@ describe DiffToHtml do
157
157
  mock(Git).show(rev) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
158
158
  end
159
159
  diff = DiffToHtml.new
160
- mock(diff).check_handled_commits(anything, 'rvm') { |commits, branch| commits }
160
+ mock(diff).check_handled_commits(anything) { |commits| commits }
161
161
  diff.diff_between_revisions(first_rev, last_rev, 'tm-admin', 'rvm')
162
162
  diff.result.should have(1).commit
163
- hp = Hpricot diff.result.first[:html_content]
163
+ hp = Nokogiri::HTML diff.result.first[:html_content]
164
164
  (hp/"table").should have(1).table
165
165
  (hp/"tr.r").should have(1).row
166
166
  end
167
- end
168
167
 
168
+ describe :message_map do
169
+ before(:each) do
170
+ @diff = DiffToHtml.new
171
+ end
172
+
173
+ it "should do message mapping" do
174
+ stub(@diff).do_message_integration("msg") { "msg2" }
175
+ mock(@diff).do_message_map("msg2") { "msg3" }
176
+ @diff.message_map("msg").should == "msg3"
177
+ end
178
+
179
+ it "should do message integration" do
180
+ mock(@diff).do_message_integration("msg") { "msg2" }
181
+ stub(@diff).do_message_map("msg2") { "msg3" }
182
+ @diff.message_map("msg").should == "msg3"
183
+ end
184
+ end
185
+
186
+ describe :do_message_integration do
187
+ before(:each) do
188
+ @config = Hash.new
189
+ @diff = DiffToHtml.new(nil, @config)
190
+ end
191
+ =begin
192
+ return message unless @config['message_integration'].respond_to?(:each_pair)
193
+ @config['message_integration'].each_pair do |pm, url|
194
+ pm_def = DiffToHtml::INTEGRATION_MAP[pm.to_sym] or next
195
+ replace_with = pm_def[:replace_with]
196
+ replace_with = replace_with.kind_of?(Proc) ? lambda { |m| pm_def[:replace_with].call(m, url) } : replace_with.gsub('#{url}', url)
197
+ message_replace!(message, pm_def[:search_for], replace_with)
198
+ end
199
+ message
200
+ =end
201
+ it "should do nothing unless message_integration config section exists" do
202
+ mock.proxy(nil).respond_to?(:each_pair)
203
+ dont_allow(@diff).message_replace!
204
+ @diff.do_message_integration('yu').should == 'yu'
205
+ end
206
+ it "should pass MESSAGE_INTEGRATION through message_replace!" do
207
+ @config['message_integration'] = {
208
+ 'mediawiki' => 'http://example.com/wiki', # will rework [[text]] to MediaWiki pages
209
+ 'redmine' => 'http://redmine.example.com' # will rework refs #123, #125 to Redmine issues
210
+ }
211
+ @diff.do_message_integration("[[text]] refs #123, #125").should == "<a href=\"http://example.com/wiki/text\">[[text]]</a> refs <a href=\"http://redmine.example.com/issues/show/123\">#123</a>, <a href=\"http://redmine.example.com/issues/show/125\">#125</a>"
212
+ end
213
+ end
214
+
215
+ describe :old_commit? do
216
+ before(:each) do
217
+ @config = Hash.new
218
+ @diff_to_html = DiffToHtml.new(nil, @config)
219
+ end
220
+
221
+ it "should be false unless skip_commits_older_than set" do
222
+ @diff_to_html.old_commit?(Hash.new).should be_false
223
+ end
224
+
225
+ it "should be false if skip_commits_older_than less than zero" do
226
+ @config['skip_commits_older_than'] = '-7'
227
+ @diff_to_html.old_commit?(Hash.new).should be_false
228
+ end
229
+
230
+ it "should be false if skip_commits_older_than is equal to zero" do
231
+ @config['skip_commits_older_than'] = 0
232
+ @diff_to_html.old_commit?(Hash.new).should be_false
233
+ end
234
+
235
+ it "should be false if commit is newer than required by skip_commits_older_than" do
236
+ @config['skip_commits_older_than'] = 1
237
+ @diff_to_html.old_commit?({:date => (Time.now - 1).to_s}).should be_false
238
+ end
239
+
240
+ it "should be true if commit is older than required by skip_commits_older_than" do
241
+ @config['skip_commits_older_than'] = 1
242
+ @diff_to_html.old_commit?({:date => (Time.now - 2 * DiffToHtml::SECS_PER_DAY).to_s}).should be_true
243
+ end
244
+ end
245
+
246
+ end