git-commit-notifier 0.11.2 → 0.11.3
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.
- data/VERSION +1 -1
- data/git-commit-notifier.gemspec +1 -1
- data/lib/git_commit_notifier/commit_hook.rb +46 -15
- data/lib/git_commit_notifier/diff_callback.rb +7 -1
- data/lib/git_commit_notifier/diff_to_html.rb +83 -66
- data/lib/git_commit_notifier/emailer.rb +83 -16
- data/lib/git_commit_notifier/escape_helper.rb +11 -2
- data/lib/git_commit_notifier/executor.rb +1 -1
- data/lib/git_commit_notifier/git.rb +43 -32
- data/lib/git_commit_notifier/result_processor.rb +7 -5
- data/spec/lib/git_commit_notifier/commit_hook_spec.rb +1 -1
- data/spec/lib/git_commit_notifier/diff_to_html_spec.rb +5 -5
- data/spec/lib/git_commit_notifier/emailer_spec.rb +4 -0
- data/spec/lib/git_commit_notifier/git_spec.rb +3 -3
- metadata +29 -32
@@ -2,32 +2,51 @@
|
|
2
2
|
|
3
3
|
require 'premailer'
|
4
4
|
|
5
|
+
# Represents email sender.
|
5
6
|
class GitCommitNotifier::Emailer
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
19
|
+
GitCommitNotifier::Emailer.config
|
12
20
|
end
|
13
21
|
|
14
22
|
def initialize(config, options = {})
|
15
|
-
|
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 =
|
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
|
-
|
63
|
-
|
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}"
|
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
|
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(" ", " ")
|
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")
|
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
|
-
#
|
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
|
-
|
30
|
-
data
|
40
|
+
from_shell("git show #{rev.strip}#{gitopt}")
|
31
41
|
end
|
32
42
|
|
33
|
-
#
|
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
|
-
|
41
|
-
data
|
50
|
+
from_shell("git log --pretty=fuller #{rev1}..#{rev2}").strip
|
42
51
|
end
|
43
52
|
|
44
|
-
#
|
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
|
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
|
-
|
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 =
|
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 =
|
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 =
|
97
|
-
s.merge(not_branches.
|
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}")
|
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 =
|
114
|
-
commits = lines.
|
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
|
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') { [] }
|