git-commit-notifier 0.8.0 → 0.8.1

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.
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
+