git-commit-notifier 0.11.2 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,32 +2,51 @@
2
2
 
3
3
  require 'premailer'
4
4
 
5
+ # Represents email sender.
5
6
  class GitCommitNotifier::Emailer
6
- DEFAULT_STYLESHEET_PATH = File.join(File.dirname(__FILE__), '/../../template/styles.css').freeze
7
- TEMPLATE = File.join(File.dirname(__FILE__), '/../../template/email.html.erb').freeze
8
- PARAMETERS = %w[project_path recipient from_address from_alias subject text_message html_message repo_name ref_name old_rev new_rev].freeze
7
+ # Default CSS stylesheet file path
8
+ DEFAULT_STYLESHEET_PATH = File.join(File.dirname(__FILE__), *'../../template/styles.css'.split('/')).freeze
9
+ # Default ERB template file path
10
+ TEMPLATE = File.join(File.dirname(__FILE__), *'../../template/email.html.erb'.split('/')).freeze
11
+ # Instance variable names
12
+ PARAMETERS = %w[project_path recipient from_address from_alias date subject text_message html_message repo_name ref_name old_rev new_rev].freeze
9
13
 
14
+ # Gets config.
15
+ # @return [Hash] Configuration
16
+ # @note Helper that represents class method in instance scope.
17
+ # @see GitCommitNotifier::Emailer.config
10
18
  def config
11
- @@config
19
+ GitCommitNotifier::Emailer.config
12
20
  end
13
21
 
14
22
  def initialize(config, options = {})
15
- @@config = config || {}
23
+ GitCommitNotifier::Emailer.config = config || {}
16
24
  PARAMETERS.each do |name|
17
25
  instance_variable_set("@#{name}".to_sym, options[name.to_sym])
18
26
  end
19
27
  end
20
28
 
21
29
  class << self
30
+ # [Hash] Gets or sets config.
31
+ attr_accessor :config
32
+
33
+ # Resets compiled template.
34
+ # @note Useful for tests.
35
+ # @return [NilClass] nil
22
36
  def reset_template
23
37
  @template = nil
24
38
  end
25
39
 
40
+ # Reads template source code from file system.
41
+ # @return [String] Template source code.
26
42
  def template_source
27
- template_file = @@config['custom_template'] || TEMPLATE
43
+ template_file = config['custom_template'] || TEMPLATE
28
44
  IO.read(template_file)
29
45
  end
30
46
 
47
+ # Gets or reads compiled template.
48
+ # @return [Object] Compiled template.
49
+ # @note Erubis used as template engine if present; ERB otherwise.
31
50
  def template
32
51
  unless @template
33
52
  source = template_source
@@ -41,8 +60,30 @@ class GitCommitNotifier::Emailer
41
60
  end
42
61
  @template
43
62
  end
63
+
64
+ # Resets CSS stylesheet source.
65
+ # @note Useful for tests.
66
+ # @return [NilClass] nil
67
+ def reset_stylesheet
68
+ @stylesheet = nil
69
+ end
70
+
71
+ # Reads CSS stylesheet source code.
72
+ # @return [String] Stylesheet source code.
73
+ def stylesheet_source
74
+ stylesheet = config['stylesheet'] || DEFAULT_STYLESHEET_PATH
75
+ IO.read(stylesheet)
76
+ end
77
+
78
+ # Gets or reads CSS stylesheet.
79
+ # @return [String] Stylesheet source code.
80
+ def stylesheet
81
+ @stylesheet ||= stylesheet_source
82
+ end
44
83
  end
45
84
 
85
+ # Gets HTML-formatted message.
86
+ # @return [String] HTML-formatted message.
46
87
  def mail_html_message
47
88
  html = GitCommitNotifier::Emailer.template.result(binding)
48
89
  if config['expand_css'].nil? || config['expand_css']
@@ -52,6 +93,16 @@ class GitCommitNotifier::Emailer
52
93
  html
53
94
  end
54
95
 
96
+ # Gets stylesheet string.
97
+ # @note This is helper to provide data from class context.
98
+ # @see GitCommitNotifier::Emailer.stylesheet
99
+ # @return [String] Stylesheet source code.
100
+ def stylesheet_string
101
+ GitCommitNotifier::Emailer.stylesheet
102
+ end
103
+
104
+ # Gets or creates email part boundary
105
+ # @return [String] Email part boundary.
55
106
  def boundary
56
107
  return @boundary if @boundary
57
108
  srand
@@ -59,17 +110,17 @@ class GitCommitNotifier::Emailer
59
110
  @boundary = Digest::SHA1.hexdigest(seed)
60
111
  end
61
112
 
62
- def stylesheet_string
63
- stylesheet = config['stylesheet'] || DEFAULT_STYLESHEET_PATH
64
- IO.read(stylesheet)
65
- end
66
-
113
+ # Performs email delivery in debug mode (to STDOUT).
114
+ # @return [NilClass] nil
67
115
  def perform_delivery_debug(content)
68
116
  content.each do |line|
69
117
  puts line
70
118
  end
119
+ nil
71
120
  end
72
121
 
122
+ # Performs email delivery through SMTP.
123
+ # @return [NilClass] nil
73
124
  def perform_delivery_smtp(content, smtp_settings)
74
125
  settings = { }
75
126
  %w(address port domain user_name password authentication enable_tls).each do |key|
@@ -94,8 +145,11 @@ class GitCommitNotifier::Emailer
94
145
  end
95
146
  end
96
147
  end
148
+ nil
97
149
  end
98
150
 
151
+ # Performs email delivery through Sendmail.
152
+ # @return [NilClass] nil
99
153
  def perform_delivery_sendmail(content, options = nil)
100
154
  sendmail_settings = {
101
155
  'location' => "/usr/sbin/sendmail",
@@ -108,28 +162,38 @@ class GitCommitNotifier::Emailer
108
162
  end
109
163
  f.flush
110
164
  end
165
+ nil
111
166
  end
112
167
 
168
+ # Performs email delivery through NNTP.
169
+ # @return [NilClass] nil
113
170
  def perform_delivery_nntp(content, nntp_settings)
114
171
  require 'nntp'
115
172
  Net::NNTP.start(nntp_settings['address'], nntp_settings['port']) do |nntp|
116
173
  nntp.post content
117
174
  end
175
+ nil
118
176
  end
119
177
 
178
+ # Creates email message and sends it using configured delivery method.
179
+ # @return [NilClass] nil
120
180
  def send
121
181
  to_tag = config['delivery_method'] == 'nntp' ? 'Newsgroups' : 'To'
122
182
  quoted_from_alias = !@from_alias.nil? ? quote_if_necessary("#{@from_alias}",'utf-8') : nil
123
183
  from = (@from_alias.nil? || @from_alias.empty?) ? @from_address : "#{quoted_from_alias} <#{@from_address}>"
124
-
184
+
125
185
  plaintext = if config['add_plaintext'].nil? || config['add_plaintext']
126
186
  @text_message
127
187
  else
128
188
  "Plain text part omitted. Consider setting add_plaintext in configuration."
129
189
  end
130
-
190
+
131
191
  content = []
132
- content << "From: #{from}" if !from.nil?
192
+ content << "From: #{from}" unless from.nil?
193
+
194
+ # Setting the email date from the commit date is undesired by those
195
+ # who sort their email by send date instead of receive date
196
+ #content << "Date: #{@date}" if !@date.nil?
133
197
 
134
198
  content.concat [
135
199
  "#{to_tag}: #{quote_if_necessary(@recipient, 'utf-8')}",
@@ -168,10 +232,11 @@ class GitCommitNotifier::Emailer
168
232
  else # sendmail
169
233
  perform_delivery_sendmail(content, config['sendmail_options'])
170
234
  end
235
+ nil
171
236
  end
172
237
 
173
238
  # Convert a message into quoted printable encoding,
174
- # limiting line length to 76 characters per spec
239
+ # limiting line length to 76 characters per spec.
175
240
  # Encoding messages in this way ensures that they
176
241
  # won't violate rules for maximum line length, which
177
242
  # can result in the MTA breaking lines at inconvenient points,
@@ -180,7 +245,7 @@ class GitCommitNotifier::Emailer
180
245
  StringIO.open("", "w") do |output|
181
246
  # Character encoding of output string can be plain US-ASCII since quoted-printable is plain ASCII
182
247
  output.string.force_encoding("US-ASCII") if output.string.respond_to?(:force_encoding)
183
-
248
+
184
249
  line_max = 76
185
250
  line_len = 0
186
251
 
@@ -239,3 +304,5 @@ class GitCommitNotifier::Emailer
239
304
  end
240
305
  end
241
306
 
307
+
308
+
@@ -1,7 +1,12 @@
1
- # -*- coding: utf-8; mode: ruby; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- vim:fenc=utf-8:filetype=ruby:et:sw=2:ts=2:sts=2
1
+ # -*- coding: utf-8; mode: ruby; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2
2
+ # -*- vim:fenc=utf-8:filetype=ruby:et:sw=2:ts=2:sts=2
2
3
 
4
+ # Provides content escaping helpers.
3
5
  module GitCommitNotifier::EscapeHelper
4
-
6
+ # Expand tabs into spaces.
7
+ # @param [String] s Text to be expanded.
8
+ # @param [FixNum] tabWidth One tab indentation (in count of spaces).
9
+ # @return [String] Expanded text.
5
10
  def expand_tabs(s, tabWidth)
6
11
  delta = 0
7
12
  s.gsub(/\t/) do |m|
@@ -11,6 +16,10 @@ module GitCommitNotifier::EscapeHelper
11
16
  end
12
17
  end
13
18
 
19
+ # Escapes expanded content using CGI.escapeHTML and {#expand_tabs}.
20
+ # @param [String] s Text to be expanded and extended.
21
+ # @return [String] Escaped and expanded text.
22
+ # @see #expand_tabs
14
23
  def escape_content(s)
15
24
  CGI.escapeHTML(expand_tabs(s, 4)).gsub(" ", "&nbsp;")
16
25
  end
@@ -21,7 +21,7 @@ module GitCommitNotifier
21
21
  GitCommitNotifier::CommitHook.show_error("No data given on standard input")
22
22
  return
23
23
  end
24
-
24
+
25
25
  # Note that there may be multiple lines on stdin, such
26
26
  # as in the case of multiple tags being pushed
27
27
  $stdin.each_line do |line|
@@ -5,17 +5,28 @@ require 'set'
5
5
  # Git methods
6
6
  class GitCommitNotifier::Git
7
7
  class << self
8
- # Runs specified command.
8
+ # Runs specified command and gets its output.
9
9
  # @return (String) Shell command STDOUT (forced to UTF-8)
10
10
  # @raise [ArgumentError] when command exits with nonzero status.
11
11
  def from_shell(cmd)
12
12
  r = `#{cmd}`
13
- raise ArgumentError.new("#{cmd} failed") unless $?.exitstatus.zero?
13
+ raise ArgumentError.new("#{cmd} failed") unless $?.exitstatus.zero?
14
14
  r.force_encoding(Encoding::UTF_8) if r.respond_to?(:force_encoding)
15
15
  r
16
16
  end
17
17
 
18
- # runs `git show`
18
+ # Runs specified command and gets its output as array of lines.
19
+ # @return (Enumerable(String)) Shell command STDOUT (forced to UTF-8) as enumerable lines.
20
+ # @raise [ArgumentError] when command exits with nonzero status.
21
+ # @see from_shell
22
+ def lines_from_shell(cmd)
23
+ lines = from_shell(cmd)
24
+ # Ruby 1.9 tweak.
25
+ lines = lines.lines if lines.respond_to?(:lines)
26
+ lines
27
+ end
28
+
29
+ # Runs `git show`
19
30
  # @note uses "--pretty=fuller" option.
20
31
  # @return [String] Its output
21
32
  # @see from_shell
@@ -23,34 +34,30 @@ class GitCommitNotifier::Git
23
34
  # @param [Hash] opts Options
24
35
  # @option opts [Boolean] :ignore_whitespaces Ignore whitespaces or not
25
36
  def show(rev, opts = {})
26
- gitopt = ""
37
+ gitopt = " --date=rfc2822"
27
38
  gitopt += " --pretty=fuller"
28
39
  gitopt += " -w" if opts[:ignore_whitespaces]
29
- data = from_shell("git show #{rev.strip}#{gitopt}")
30
- data
40
+ from_shell("git show #{rev.strip}#{gitopt}")
31
41
  end
32
42
 
33
- # runs `git log`
43
+ # Runs `git log`
34
44
  # @note uses "--pretty=fuller" option.
35
45
  # @return [String] Its output
36
46
  # @see from_shell
37
47
  # @param [String] rev1 First revision
38
48
  # @param [String] rev2 Second revision
39
49
  def log(rev1, rev2)
40
- data = from_shell("git log --pretty=fuller #{rev1}..#{rev2}").strip
41
- data
50
+ from_shell("git log --pretty=fuller #{rev1}..#{rev2}").strip
42
51
  end
43
52
 
44
- # runs `git log` and extract filenames only
53
+ # Runs `git log` and extract filenames only
45
54
  # @note uses "--pretty=fuller" and "--name-status" options.
46
55
  # @return [Array(String)] File names
47
- # @see from_shell
56
+ # @see lines_from_shell
48
57
  # @param [String] rev1 First revision
49
58
  # @param [String] rev2 Second revision
50
59
  def changed_files(rev1, rev2)
51
- output = ""
52
- lines = from_shell("git log #{rev1}..#{rev2} --name-status --pretty=oneline")
53
- lines = lines.lines if lines.respond_to?(:lines)
60
+ lines = lines_from_shell("git log #{rev1}..#{rev2} --name-status --pretty=oneline")
54
61
  lines = lines.select {|line| line =~ /^\w{1}\s+\w+/} # grep out only filenames
55
62
  lines.uniq
56
63
  end
@@ -59,18 +66,16 @@ class GitCommitNotifier::Git
59
66
  args = branch_heads - [ branch_head(treeish) ]
60
67
  args.map! { |tree| "^#{tree}" }
61
68
  args << treeish
62
- lines = from_shell("git rev-list #{args.join(' ')}")
63
- lines = lines.lines if lines.respond_to?(:lines)
69
+ lines = lines_from_shell("git rev-list #{args.join(' ')}")
64
70
  lines.to_a.map { |commit| commit.chomp }
65
71
  end
66
72
 
67
73
  def branch_heads
68
- lines = from_shell("git rev-parse --branches")
69
- lines = lines.lines if lines.respond_to?(:lines)
74
+ lines = lines_from_shell("git rev-parse --branches")
70
75
  lines.to_a.map { |head| head.chomp }
71
76
  end
72
77
 
73
- def git_dir()
78
+ def git_dir
74
79
  from_shell("git rev-parse --git-dir").strip
75
80
  end
76
81
 
@@ -81,37 +86,36 @@ class GitCommitNotifier::Git
81
86
  def branch_head(treeish)
82
87
  from_shell("git rev-parse #{treeish}").strip
83
88
  end
84
-
89
+
85
90
  def new_commits(oldrev, newrev, refname, unique_to_current_branch)
86
91
  # We want to get the set of commits (^B1 ^B2 ... ^oldrev newrev)
87
92
  # Where B1, B2, ..., are any other branch
88
-
89
93
  s = Set.new
90
-
94
+
91
95
  # If we want to include only those commits that are
92
96
  # unique to this branch, then exclude commits that occur on
93
97
  # other branches
94
98
  if unique_to_current_branch
95
99
  # Make a set of all branches, not'd (^BCURRENT ^B1 ^B2...)
96
- not_branches = from_shell("git rev-parse --not --branches")
97
- s.merge(not_branches.lines.map {|l| l.chomp}.to_set)
98
-
100
+ not_branches = lines_from_shell("git rev-parse --not --branches")
101
+ s.merge(not_branches.to_a.map { |l| l.chomp }.to_set)
102
+
99
103
  # Remove the current branch (^BCURRENT) from the set
100
104
  current_branch = rev_parse(refname)
101
105
  s.delete("^#{current_branch}")
102
106
  end
103
-
107
+
104
108
  # Add not'd oldrev (^oldrev)
105
- s.add("^#{oldrev}") unless oldrev =~ /^0+$/
109
+ s.add("^#{oldrev}") unless oldrev =~ /^0+$/
106
110
 
107
111
  # Add newrev
108
112
  s.add(newrev)
109
-
113
+
110
114
  # We should now have ^B1... ^oldrev newrev
111
115
 
112
116
  # Get all the commits that match that specification
113
- lines = from_shell("git rev-list #{s.to_a.join(' ')}")
114
- commits = lines.lines.map {|l| l.chomp}
117
+ lines = lines_from_shell("git rev-list --reverse #{s.to_a.join(' ')}")
118
+ commits = lines.to_a.map { |l| l.chomp }
115
119
  end
116
120
 
117
121
  def rev_type(rev)
@@ -119,7 +123,7 @@ class GitCommitNotifier::Git
119
123
  rescue ArgumentError
120
124
  nil
121
125
  end
122
-
126
+
123
127
  def tag_info(refname)
124
128
  fields = [
125
129
  ':tagobject => %(*objectname)',
@@ -134,16 +138,23 @@ class GitCommitNotifier::Git
134
138
  eval(hash_script)
135
139
  end
136
140
 
141
+ # Gets repository name.
142
+ # @note Tries to gets human readable repository name through `git config hooks.emailprefix` call.
143
+ # If it's not specified then returns directory name (except '.git' suffix if exists).
144
+ # @return [String] Human readable repository name.
137
145
  def repo_name
138
146
  git_prefix = begin
139
147
  from_shell("git config hooks.emailprefix").strip
140
148
  rescue ArgumentError
141
149
  ''
142
150
  end
143
- return git_prefix unless git_prefix.empty?
151
+ return git_prefix unless git_prefix.empty?
144
152
  File.expand_path(git_dir).split("/").last.sub(/\.git$/, '')
145
153
  end
146
154
 
155
+ # Gets mailing list address.
156
+ # @note mailing list address retrieved through `git config hooks.mailinglist` call.
157
+ # @return [String] Mailing list address if exists; otherwise nil.
147
158
  def mailing_list_address
148
159
  from_shell("git config hooks.mailinglist").strip
149
160
  rescue ArgumentError
@@ -4,19 +4,21 @@ require 'cgi'
4
4
  require 'git_commit_notifier/escape_helper'
5
5
 
6
6
  module GitCommitNotifier
7
+ # Processes diff result
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
+ # output: two formatted html strings, one for the removals and one for the additions
7
12
  class ResultProcessor
8
13
  include EscapeHelper
9
- # input (loaded in @diff) is an array having Hash elements:
10
- # { :action => action, :token => string }
11
- # action can be :discard_a, :discard_b or :match
12
-
13
- # output: two formatted html strings, one for the removals and one for the additions
14
14
 
15
15
  def results
16
16
  close_tags # close last tag
17
17
  [array_of_lines(@result[:removal]), array_of_lines(@result[:addition])]
18
18
  end
19
19
 
20
+ # Gets length of tokenized diff in characters.
21
+ # @return [FixNum] Length of the tokenized diff in characters.
20
22
  def length_in_chars(diff)
21
23
  diff.inject(0) do |length, s|
22
24
  token = s[:token]
@@ -74,7 +74,7 @@ describe GitCommitNotifier::CommitHook do
74
74
  def expect_repository_access
75
75
  mock(GitCommitNotifier::Git).rev_type(REVISIONS.first) { "commit" }
76
76
  mock(GitCommitNotifier::Git).rev_type(REVISIONS.last) { "commit" }
77
- mock(GitCommitNotifier::Git).new_commits(anything, anything, anything, anything) { REVISIONS }
77
+ mock(GitCommitNotifier::Git).new_commits(anything, anything, anything, anything) { REVISIONS }
78
78
  mock(GitCommitNotifier::Git).mailing_list_address { 'recipient@test.com' }
79
79
  mock(GitCommitNotifier::Git).repo_name { 'testproject' }
80
80
  mock(GitCommitNotifier::Git).changed_files('7e4f6b4', '4f13525') { [] }