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/logger.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'tmpdir'
2
+
3
+ class Logger
4
+ DEFAULT_LOG_DIRECTORY = Dir.tmpdir.freeze
5
+ LOG_NAME = 'git-commit-notifier.log'.freeze
6
+
7
+ attr_reader :log_directory
8
+
9
+ def initialize(config)
10
+ @enabled = !!(config['debug'] && config['debug']['enabled'])
11
+ @log_directory = debug? ? (config['debug']['log_directory'] || DEFAULT_LOG_DIRECTORY) : nil
12
+ end
13
+
14
+ def debug?
15
+ @enabled
16
+ end
17
+
18
+ def log_path
19
+ return nil unless debug?
20
+ File.join(log_directory, LOG_NAME)
21
+ end
22
+
23
+ def debug(msg)
24
+ return unless debug?
25
+ File.open(log_path, 'a') do |f|
26
+ f.puts msg
27
+ end
28
+ end
29
+
30
+ def file(file_path)
31
+ return unless debug?
32
+ orig_dest_name = File.join(log_directory, File.basename(file_path))
33
+ dest_name = orig_dest_name
34
+ counter = 1
35
+ while File.exists?(dest_name)
36
+ counter += 1
37
+ dest_name = "#{orig_dest_name}.#{counter}"
38
+ end
39
+ debug("Save file #{file_path} for debugging purposes to #{dest_name}")
40
+ File.copy(file_path, dest_name)
41
+ end
42
+
43
+ end
44
+
45
+ __END__
46
+
47
+ vim: tabstop=2:expandtab:shiftwidth=2
48
+
@@ -12,6 +12,14 @@ class ResultProcessor
12
12
  [array_of_lines(@result[:removal]), array_of_lines(@result[:addition])]
13
13
  end
14
14
 
15
+ def length_in_chars(diff)
16
+ diff.inject(0) do |length, s|
17
+ token = s[:token]
18
+ token_length = token.respond_to?(:jlength) ? token.jlength : token.length
19
+ length + token_length
20
+ end
21
+ end
22
+
15
23
  private
16
24
 
17
25
  def initialize(diff)
@@ -46,15 +54,11 @@ class ResultProcessor
46
54
  @diff.insert(position, { :action => :discard_b, :token => token} )
47
55
  end
48
56
 
49
- def length_in_chars(diff)
50
- diff.inject(0) { |length, s| length + s[:token].size}
51
- end
52
-
53
57
  def filter_replaced_lines
54
58
  # 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
55
59
  # no need for intelligent diff in this case, simply show the removed and the added block with no highlighting
56
60
  # rule: if less than 33% of a block is not a match, we don't need intelligent diff for that block
57
- match_length = length_in_chars(@diff.select { |d| d[:action] == :match})
61
+ match_length = length_in_chars(@diff.select { |d| d[:action] == :match })
58
62
  total_length = length_in_chars(@diff)
59
63
 
60
64
  if total_length.to_f / match_length > 3.3
File without changes
@@ -1,6 +1,8 @@
1
1
  delivery_method: sendmail # smtp or sendmail
2
2
  ignore_merge: false # set to true if you want to ignore empty merge messages
3
3
 
4
+ from: max@example.com
5
+
4
6
  group_email_by_push: true
5
7
 
6
8
  smtp_server:
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ commit ff037a73fc1094455e7bbf506171a3f3cf873ae6
2
+ Author: Mokevnin Kirill <mokevnin@gmail.com>
3
+ Date: Tue Oct 26 15:31:13 2010 +0400
4
+
5
+ remove debug put
6
+
7
+ diff --git a/app/models/user.rb b/app/models/user.rb
8
+ index ac9399f..ea2e351 100644
9
+ --- a/app/models/user.rb
10
+ +++ b/app/models/user.rb
11
+ @@ -226,7 +226,6 @@ class User < ActiveRecord::Base
12
+ all_stats.keys.each do |type|
13
+ all_stats[type].each do |st|
14
+ stat_line = {type => st[:value]}
15
+ - puts "type #{type} st #{st}"
16
+ if results[st[:shift]]
17
+ results[st[:shift]].merge!(stat_line)
18
+ else
File without changes
@@ -0,0 +1,88 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require 'commit_hook'
3
+
4
+ describe CommitHook do
5
+
6
+ it "should ignore merge" do
7
+ # 4 commits, one email for each of them, without merge
8
+ run_with_config('spec/fixtures/git-notifier-ignore-merge.yml', 4)
9
+ end
10
+
11
+ it "should hook with merge" do
12
+ # 5 commits, one email for each of them, with merge mail
13
+ run_with_config('spec/fixtures/git-notifier-with-merge.yml', 5)
14
+ end
15
+
16
+ it "should hook group email by push" do
17
+ # 1 commit for the push, all commits in the one message
18
+ run_with_config('spec/fixtures/git-notifier-group-email-by-push.yml', 1)
19
+ end
20
+
21
+ def run_with_config(config, times)
22
+ expect_repository_access
23
+
24
+ emailer = mock!.send.times(times).subject
25
+ mock(Emailer).new(anything, anything) { emailer }.times(times)
26
+
27
+ mock(CommitHook).info(/Sending mail/)
28
+
29
+ any_instance_of(DiffToHtml, :check_handled_commits => lambda { |commits, branch| commits })
30
+ CommitHook.run config, REVISIONS.first, REVISIONS.last, 'refs/heads/master'
31
+ end
32
+
33
+ def test_commit_from
34
+ # 1 commit with a from: adress
35
+ expect_repository_access
36
+ emailer = mock!.send.subject
37
+ mock(Emailer).new(anything, hash_including(:from_address => "max@example.com")) { emailer }
38
+
39
+ CommitHook.run 'spec/fixtures/git-notifier-group-email-by-push.yml', REVISIONS.first, REVISIONS.last, 'refs/heads/master'
40
+ end
41
+
42
+ def expect_repository_access
43
+ mock(Git).log(REVISIONS.first, REVISIONS.last) { IO.read(FIXTURES_PATH + 'git_log') }
44
+ mock(Git).mailing_list_address { 'recipient@test.com' }
45
+ REVISIONS.each do |rev|
46
+ mock(Git).show(rev) { IO.read(FIXTURES_PATH + "git_show_#{rev}") }
47
+ end
48
+ end
49
+
50
+ describe :logger do
51
+ it "should be nstance of logger" do
52
+ stub(CommitHook).config { {} }
53
+ CommitHook.logger.should be_kind_of(Logger)
54
+ end
55
+ end
56
+
57
+ describe :show_error do
58
+ it "should write error to stderr" do
59
+ mock($stderr).puts("\n").times(2)
60
+ mock($stderr).puts(/GIT\sNOTIFIER\sPROBLEM/).times(2)
61
+ mock($stderr).puts('yes')
62
+ CommitHook.show_error('yes')
63
+ end
64
+ end
65
+
66
+ describe :info do
67
+ it "should write to and flush stdout" do
68
+ mock($stdout).puts('msg')
69
+ mock($stdout).flush
70
+ CommitHook.info('msg')
71
+ end
72
+ end
73
+
74
+ describe :run do
75
+ it "should report error when no recipients specified" do
76
+ mock(File).exists?(:noconfig) { false }
77
+ mock(Git).mailing_list_address { nil }
78
+ mock(CommitHook).show_error(/recipient/)
79
+ CommitHook.run(:noconfig, :rev1, :rev2, 'master')
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ __END__
86
+
87
+ vim: tabstop=2 expandtab shiftwidth=2
88
+
@@ -0,0 +1,168 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require 'diff_to_html'
3
+ require 'git'
4
+ require 'hpricot'
5
+
6
+ describe DiffToHtml do
7
+
8
+
9
+ describe :lines_are_sequential? do
10
+ before(:all) do
11
+ @diff_to_html = DiffToHtml.new
12
+ end
13
+
14
+ it "should be true if left line numbers are sequential" do
15
+ @diff_to_html.lines_are_sequential?({
16
+ :added => 2,
17
+ :removed => 2
18
+ }, {
19
+ :added => 3,
20
+ :removed => 6
21
+ }).should be_true
22
+ end
23
+
24
+ it "should be true if right line numbers are sequential" do
25
+ @diff_to_html.lines_are_sequential?({
26
+ :added => 2,
27
+ :removed => 2
28
+ }, {
29
+ :added => 7,
30
+ :removed => 3
31
+ }).should be_true
32
+ end
33
+
34
+ it "should be false unless line numbers are sequential" do
35
+ @diff_to_html.lines_are_sequential?({
36
+ :added => 2,
37
+ :removed => 2
38
+ }, {
39
+ :added => 4,
40
+ :removed => 6
41
+ }).should be_false
42
+ end
43
+
44
+ it "should be true if left line numbers are sequential (right are nil)" do
45
+ @diff_to_html.lines_are_sequential?({
46
+ :added => 2,
47
+ :removed => 2
48
+ }, {
49
+ :added => 3,
50
+ :removed => nil
51
+ }).should be_true
52
+ end
53
+
54
+ it "should be true if right line numbers are sequential (left are nil)" do
55
+ @diff_to_html.lines_are_sequential?({
56
+ :added => nil,
57
+ :removed => 2
58
+ }, {
59
+ :added => 7,
60
+ :removed => 3
61
+ }).should be_true
62
+ end
63
+
64
+ it "should be false unless line numbers are sequential (nils)" do
65
+ @diff_to_html.lines_are_sequential?({
66
+ :added => nil,
67
+ :removed => nil
68
+ }, {
69
+ :added => 4,
70
+ :removed => 6
71
+ }).should be_false
72
+ end
73
+ end
74
+
75
+ describe :unique_commits_per_branch? do
76
+ it "should be false unless specified in config" do
77
+ diff = DiffToHtml.new(nil, {})
78
+ diff.should_not be_unique_commits_per_branch
79
+ end
80
+
81
+ it "should be false if specified as false in config" do
82
+ diff = DiffToHtml.new(nil, { 'unique_commits_per_branch' => false })
83
+ diff.should_not be_unique_commits_per_branch
84
+ end
85
+
86
+ it "should be true if specified as true in config" do
87
+ diff = DiffToHtml.new(nil, { 'unique_commits_per_branch' => true })
88
+ diff.should be_unique_commits_per_branch
89
+ end
90
+ end
91
+
92
+ describe :get_previous_commits do
93
+ it "should read and parse previous file if it exists" do
94
+ fn = DiffToHtml::HANDLED_COMMITS_FILE
95
+ diff = DiffToHtml.new
96
+ mock(File).exists?(fn) { true }
97
+ mock(IO).read(fn) { "a\nb" }
98
+ diff.get_previous_commits(fn).should == %w[a b]
99
+ end
100
+ end
101
+
102
+ it "multiple commits" do
103
+ mock(Git).log(REVISIONS.first, REVISIONS.last) { IO.read(FIXTURES_PATH + 'git_log') }
104
+ REVISIONS.each do |rev|
105
+ mock(Git).show(rev) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
106
+ end
107
+
108
+ diff = DiffToHtml.new
109
+ mock(diff).check_handled_commits(anything, 'master') { |commits, branch| commits }
110
+ diff.diff_between_revisions REVISIONS.first, REVISIONS.last, 'testproject', 'master'
111
+
112
+ diff.result.should have(5).commits # one result for each of the commits
113
+
114
+ diff.result.each do |html|
115
+ html.should_not be_include('@@') # diff correctly processed
116
+ end
117
+
118
+ # first commit
119
+ hp = Hpricot diff.result.first[:html_content]
120
+ (hp/"table").should have(2).tables # 2 files updated - one table for each of the files
121
+ (hp/"table/tr/").each do |td|
122
+ if td.inner_html == "require&nbsp;'iconv'"
123
+ # first added line in changeset a4629e707d80a5769f7a71ca6ed9471015e14dc9
124
+ td.parent.search('td')[0].inner_text.should == '' # left
125
+ td.parent.search('td')[1].inner_text.should == '2' # right
126
+ td.parent.search('td')[2].inner_html.should == "require&nbsp;'iconv'" # change
127
+ end
128
+ end
129
+
130
+ # second commit
131
+ hp = Hpricot diff.result[1][:html_content]
132
+ (hp/"table").should have(1).table # 1 file updated
133
+
134
+ # third commit - dce6ade4cdc2833b53bd600ef10f9bce83c7102d
135
+ hp = Hpricot diff.result[2][:html_content]
136
+ (hp/"table").should have(6).tables # 6 files updated
137
+ (hp/"h2")[1].inner_text.should == 'Added binary file railties/doc/guides/source/images/icons/callouts/11.png'
138
+ (hp/"h2")[2].inner_text.should == 'Deleted binary file railties/doc/guides/source/icons/up.png'
139
+ (hp/"h2")[3].inner_text.should == 'Deleted file railties/doc/guides/source/icons/README'
140
+ (hp/"h2")[4].inner_text.should == 'Added file railties/doc/guides/source/images/icons/README'
141
+
142
+ # fourth commit - 51b986619d88f7ba98be7d271188785cbbb541a0
143
+ hp = Hpricot diff.result[3][:html_content]
144
+ (hp/"table").should have(3).tables # 3 files updated
145
+ (hp/"table/tr/").each do |td|
146
+ if td.inner_html =~ /create_btn/
147
+ cols = td.parent.search('td')
148
+ ['405', '408', ''].should be_include(cols[0].inner_text) # line 405 changed
149
+ end
150
+ end
151
+ end
152
+
153
+ it "should get good diff when new branch created" do
154
+ first_rev, last_rev = %w[ 0000000000000000000000000000000000000000 9b15cebcc5434e27c00a4a2acea43509f9faea21 ]
155
+ mock(Git).branch_commits('rvm') { %w[ ff037a73fc1094455e7bbf506171a3f3cf873ae6 ] }
156
+ %w[ ff037a73fc1094455e7bbf506171a3f3cf873ae6 ].each do |rev|
157
+ mock(Git).show(rev) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
158
+ end
159
+ diff = DiffToHtml.new
160
+ mock(diff).check_handled_commits(anything, 'rvm') { |commits, branch| commits }
161
+ diff.diff_between_revisions(first_rev, last_rev, 'tm-admin', 'rvm')
162
+ diff.result.should have(1).commit
163
+ hp = Hpricot diff.result.first[:html_content]
164
+ (hp/"table").should have(1).table
165
+ (hp/"tr.r").should have(1).row
166
+ end
167
+ end
168
+
@@ -0,0 +1,102 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ require 'erb'
4
+ require 'emailer'
5
+
6
+ describe Emailer do
7
+
8
+ describe :new do
9
+ it "should assign config if given" do
10
+ Emailer.new({:a => :b}).config[:a].should == :b
11
+ end
12
+
13
+ it "should use empty hash unless config given" do
14
+ cfg = Emailer.new(false).config
15
+ cfg.should be_kind_of(Hash)
16
+ cfg.should be_empty
17
+ end
18
+
19
+ it "should not generate message from template" do
20
+ any_instance_of(Emailer) do |emailer|
21
+ dont_allow(emailer).generate_message
22
+ end
23
+ Emailer.new({})
24
+ end
25
+
26
+ it "should assign parameters from options" do
27
+ options = {}
28
+ Emailer::PARAMETERS.each do |name|
29
+ options[name.to_sym] = Faker::Lorem.sentence
30
+ end
31
+ emailer = Emailer.new({}, options)
32
+ options.each_pair do |key, value|
33
+ emailer.instance_variable_get("@#{key}").should == value
34
+ end
35
+ end
36
+ end
37
+
38
+ describe :stylesheet_string do
39
+ it "should return default stylesheet if custom is not provided" do
40
+ emailer = Emailer.new({})
41
+ mock(IO).read(Emailer::DEFAULT_STYLESHEET_PATH) { 'ok' }
42
+ emailer.stylesheet_string.should == 'ok'
43
+ end
44
+
45
+ it "should return custom stylesheet if custom is provided" do
46
+ emailer = Emailer.new({'stylesheet' => '/path/to/custom/stylesheet'})
47
+ mock(IO).read('/path/to/custom/stylesheet') { 'ok' }
48
+ dont_allow(IO).read(Emailer::DEFAULT_STYLESHEET_PATH)
49
+ emailer.stylesheet_string.should == 'ok'
50
+ end
51
+ end
52
+
53
+ describe :generate_message do
54
+ it "should generate html" do
55
+ options = {}
56
+ Emailer::PARAMETERS.each do |name|
57
+ options[name.to_sym] = Faker::Lorem.sentence
58
+ end
59
+ emailer = Emailer.new({}, options)
60
+ emailer.generate_message
61
+ emailer.instance_variable_get(:@html).should match(/html/)
62
+ end
63
+ end
64
+
65
+ describe :template do
66
+ before(:each) do
67
+ Emailer.reset_template
68
+ mock(IO).read(Emailer::TEMPLATE) { 'erb' }
69
+ end
70
+
71
+ it "should respond to result" do
72
+ Emailer.template.should respond_to(:result)
73
+ end
74
+
75
+ it "should return Erubis template if Erubis installed" do
76
+ mock(Emailer).require('erubis')
77
+ dont_allow(Emailer).require('erb')
78
+ unless defined?(Erubis)
79
+ module Erubis
80
+ class Eruby
81
+ def initialize(erb)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ mock.proxy(Erubis::Eruby).new('erb')
87
+ Emailer.template.should be_kind_of(Erubis::Eruby)
88
+ end
89
+
90
+ it "should return ERB template unless Erubis installed" do
91
+ mock(Emailer).require('erubis') { raise LoadError.new('erubis') }
92
+ mock(Emailer).require('erb')
93
+ mock.proxy(ERB).new('erb')
94
+ Emailer.template.should be_kind_of(ERB)
95
+ end
96
+ end
97
+ end
98
+
99
+ __END__
100
+
101
+ vim: tabstop=2 expandtab shiftwidth=2
102
+