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.
- data/Gemfile +21 -0
- data/Gemfile.lock +105 -0
- data/README.textile +6 -1
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/bin/git-commit-notifier +3 -1
- data/config/git-notifier-config.yml.sample +28 -4
- data/git-commit-notifier.gemspec +81 -56
- data/lib/git_commit_notifier.rb +8 -0
- data/lib/git_commit_notifier/commit_hook.rb +130 -0
- data/lib/git_commit_notifier/diff_callback.rb +22 -0
- data/lib/git_commit_notifier/diff_to_html.rb +471 -0
- data/lib/{emailer.rb → git_commit_notifier/emailer.rb} +47 -31
- data/lib/git_commit_notifier/escape_helper.rb +5 -0
- data/lib/{git.rb → git_commit_notifier/git.rb} +1 -1
- data/lib/{logger.rb → git_commit_notifier/logger.rb} +1 -1
- data/lib/git_commit_notifier/result_processor.rb +131 -0
- data/spec/fixtures/git-notifier-with-branch-restrictions.yml +22 -0
- data/spec/lib/{commit_hook_spec.rb → git_commit_notifier/commit_hook_spec.rb} +37 -11
- data/spec/lib/{diff_to_html_spec.rb → git_commit_notifier/diff_to_html_spec.rb} +94 -16
- data/spec/lib/{emailer_spec.rb → git_commit_notifier/emailer_spec.rb} +9 -20
- data/spec/lib/{git_spec.rb → git_commit_notifier/git_spec.rb} +4 -6
- data/spec/lib/{logger_spec.rb → git_commit_notifier/logger_spec.rb} +4 -7
- data/spec/lib/{result_processor_spec.rb → git_commit_notifier/result_processor_spec.rb} +4 -2
- data/template/email.html.erb +1 -1
- metadata +150 -50
- data/.gitignore +0 -21
- data/lib/commit_hook.rb +0 -129
- data/lib/diff_to_html.rb +0 -449
- data/lib/result_processor.rb +0 -126
@@ -0,0 +1,22 @@
|
|
1
|
+
module GitCommitNotifier
|
2
|
+
class DiffCallback
|
3
|
+
attr_reader :tags
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@tags = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def match(event)
|
10
|
+
@tags << { :action => :match, :token => event.old_element }
|
11
|
+
end
|
12
|
+
|
13
|
+
def discard_b(event)
|
14
|
+
@tags << { :action => :discard_b, :token => event.new_element }
|
15
|
+
end
|
16
|
+
|
17
|
+
def discard_a(event)
|
18
|
+
@tags << { :action => :discard_a, :token => event.old_element }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,471 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'diff/lcs'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
require 'git_commit_notifier/escape_helper'
|
7
|
+
|
8
|
+
module GitCommitNotifier
|
9
|
+
class DiffToHtml
|
10
|
+
include EscapeHelper
|
11
|
+
|
12
|
+
INTEGRATION_MAP = {
|
13
|
+
:mediawiki => { :search_for => /\[\[([^\[\]]+)\]\]/, :replace_with => '#{url}/\1' },
|
14
|
+
:redmine => { :search_for => /\b(?:refs|fixes)([\s&,]+\#\d+)+/i, :replace_with => lambda do |m, url|
|
15
|
+
# we can provide Proc that gets matched string and configuration url.
|
16
|
+
# result should be in form of:
|
17
|
+
# { :phrase => 'phrase started with', :links => [ { :title => 'title of url', :url => 'target url' }, ... ] }
|
18
|
+
match = m.match(/^(refs|fixes)(.*)$/i)
|
19
|
+
return m unless match
|
20
|
+
r = { :phrase => match[1] }
|
21
|
+
captures = match[2].split(/[\s\&\,]+/).map { |m| (m =~ /(\d+)/) ? $1 : m }.reject { |c| c.empty? }
|
22
|
+
r[:links] = captures.map { |mn| { :title => "##{mn}", :url => "#{url}/issues/show/#{mn}" } }
|
23
|
+
r
|
24
|
+
end },
|
25
|
+
:bugzilla => { :search_for => /\bBUG\s*(\d+)/i, :replace_with => '#{url}/show_bug.cgi?id=\1' },
|
26
|
+
:fogbugz => { :search_for => /\bbugzid:\s*(\d+)/i, :replace_with => '#{url}\1' }
|
27
|
+
}.freeze
|
28
|
+
MAX_COMMITS_PER_ACTION = 10000
|
29
|
+
HANDLED_COMMITS_FILE = 'previously.txt'.freeze
|
30
|
+
NEW_HANDLED_COMMITS_FILE = 'previously_new.txt'.freeze
|
31
|
+
SECS_PER_DAY = 24 * 60 * 60
|
32
|
+
|
33
|
+
attr_accessor :file_prefix, :current_file_name
|
34
|
+
attr_reader :result, :branch
|
35
|
+
|
36
|
+
def initialize(previous_dir = nil, config = nil)
|
37
|
+
@previous_dir = previous_dir
|
38
|
+
@config = config || {}
|
39
|
+
@lines_added = 0
|
40
|
+
@file_added = false
|
41
|
+
@file_removed = false
|
42
|
+
@binary = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def range_info(range)
|
46
|
+
matches = range.match(/^@@ \-(\S+) \+(\S+)/)
|
47
|
+
return matches[1..2].map { |m| m.split(',')[0].to_i }
|
48
|
+
end
|
49
|
+
|
50
|
+
def line_class(line)
|
51
|
+
if line[:op] == :removal
|
52
|
+
return " class=\"r\""
|
53
|
+
elsif line[:op] == :addition
|
54
|
+
return " class=\"a\""
|
55
|
+
else
|
56
|
+
return ''
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_block_to_results(block, escape)
|
61
|
+
return if block.empty?
|
62
|
+
block.each do |line|
|
63
|
+
add_line_to_result(line, escape)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def lines_per_diff
|
68
|
+
@config['lines_per_diff']
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_separator
|
72
|
+
return if lines_per_diff && @lines_added >= lines_per_diff
|
73
|
+
@diff_result << '<tr class="sep"><td class="sep" colspan="3" title="Unchanged content skipped between diff. blocks">…</td></tr>'
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_line_to_result(line, escape)
|
77
|
+
@lines_added += 1
|
78
|
+
if lines_per_diff
|
79
|
+
if @lines_added == lines_per_diff
|
80
|
+
@diff_result << '<tr><td colspan="3">Diff too large and stripped…</td></tr>'
|
81
|
+
end
|
82
|
+
if @lines_added >= lines_per_diff
|
83
|
+
return
|
84
|
+
end
|
85
|
+
end
|
86
|
+
klass = line_class(line)
|
87
|
+
content = escape ? escape_content(line[:content]) : line[:content]
|
88
|
+
padding = ' ' if klass != ''
|
89
|
+
@diff_result << "<tr#{klass}>\n<td class=\"ln\">#{line[:removed]}</td>\n<td class=\"ln\">#{line[:added]}</td>\n<td>#{padding}#{content}</td></tr>"
|
90
|
+
end
|
91
|
+
|
92
|
+
def extract_block_content(block)
|
93
|
+
block.collect { |b| b[:content] }.join("\n")
|
94
|
+
end
|
95
|
+
|
96
|
+
def lcs_diff(removals, additions)
|
97
|
+
# arrays always have at least 1 element
|
98
|
+
callback = DiffCallback.new
|
99
|
+
|
100
|
+
s1 = extract_block_content(removals)
|
101
|
+
s2 = extract_block_content(additions)
|
102
|
+
|
103
|
+
s1 = tokenize_string(s1)
|
104
|
+
s2 = tokenize_string(s2)
|
105
|
+
|
106
|
+
Diff::LCS.traverse_balanced(s1, s2, callback)
|
107
|
+
|
108
|
+
processor = ResultProcessor.new(callback.tags)
|
109
|
+
|
110
|
+
diff_for_removals, diff_for_additions = processor.results
|
111
|
+
result = []
|
112
|
+
|
113
|
+
ln_start = removals[0][:removed]
|
114
|
+
diff_for_removals.each_with_index do |line, i|
|
115
|
+
result << { :removed => ln_start + i, :added => nil, :op => :removal, :content => line}
|
116
|
+
end
|
117
|
+
|
118
|
+
ln_start = additions[0][:added]
|
119
|
+
diff_for_additions.each_with_index do |line, i|
|
120
|
+
result << { :removed => nil, :added => ln_start + i, :op => :addition, :content => line}
|
121
|
+
end
|
122
|
+
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
def tokenize_string(str)
|
127
|
+
# tokenize by non-word characters
|
128
|
+
tokens = []
|
129
|
+
token = ''
|
130
|
+
str.scan(/./mu) do |ch|
|
131
|
+
if ch =~ /[^\W_]/u
|
132
|
+
token += ch
|
133
|
+
else
|
134
|
+
unless token.empty?
|
135
|
+
tokens << token
|
136
|
+
token = ''
|
137
|
+
end
|
138
|
+
tokens << ch
|
139
|
+
end
|
140
|
+
end
|
141
|
+
tokens << token unless token.empty?
|
142
|
+
tokens
|
143
|
+
end
|
144
|
+
|
145
|
+
def operation_description
|
146
|
+
binary = @binary ? 'binary ' : ''
|
147
|
+
if @file_removed
|
148
|
+
op = "Deleted"
|
149
|
+
elsif @file_added
|
150
|
+
op = "Added"
|
151
|
+
else
|
152
|
+
op = "Changed"
|
153
|
+
end
|
154
|
+
|
155
|
+
file_name = @current_file_name
|
156
|
+
|
157
|
+
if (@config["link_files"] && @config["link_files"] == "gitweb" && @config["gitweb"])
|
158
|
+
file_name = "<a href='#{@config['gitweb']['path']}?p=#{@config['gitweb']['project']};f=#{file_name};hb=HEAD'>#{file_name}</a>"
|
159
|
+
elsif (@config["link_files"] && @config["link_files"] == "gitorious" && @config["gitorious"])
|
160
|
+
file_name = "<a href='#{@config['gitorious']['path']}/#{@config['gitorious']['project']}/#{@config['gitorious']['repository']}/blobs/#{branch_name}/#{file_name}'>#{file_name}</a>"
|
161
|
+
elsif (@config["link_files"] && @config["link_files"] == "cgit" && @config["cgit"])
|
162
|
+
file_name = "<a href='#{@config['cgit']['path']}/#{@config['cgit']['project']}/tree/#{file_name}'>#{file_name}</a>"
|
163
|
+
end
|
164
|
+
|
165
|
+
header = "#{op} #{binary}file #{file_name}"
|
166
|
+
"<h2>#{header}</h2>\n"
|
167
|
+
end
|
168
|
+
|
169
|
+
def lines_are_sequential?(first, second)
|
170
|
+
result = false
|
171
|
+
[:added, :removed].each do |side|
|
172
|
+
if !first[side].nil? && !second[side].nil?
|
173
|
+
result = true if first[side] == (second[side] - 1)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
result
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_changes_to_result
|
180
|
+
return if @current_file_name.nil?
|
181
|
+
@diff_result << operation_description
|
182
|
+
@diff_result << '<table>'
|
183
|
+
unless @diff_lines.empty?
|
184
|
+
removals = []
|
185
|
+
additions = []
|
186
|
+
@diff_lines.each_with_index do |line, index|
|
187
|
+
removals << line if line[:op] == :removal
|
188
|
+
additions << line if line[:op] == :addition
|
189
|
+
if line[:op] == :unchanged || index == @diff_lines.size - 1 # unchanged line or end of block, add prev lines to result
|
190
|
+
if removals.size > 0 && additions.size > 0 # block of removed and added lines - perform intelligent diff
|
191
|
+
add_block_to_results(lcs_diff(removals, additions), escape = false)
|
192
|
+
else # some lines removed or added - no need to perform intelligent diff
|
193
|
+
add_block_to_results(removals + additions, escape = true)
|
194
|
+
end
|
195
|
+
removals = []
|
196
|
+
additions = []
|
197
|
+
if index > 0 && index != @diff_lines.size - 1
|
198
|
+
prev_line = @diff_lines[index - 1]
|
199
|
+
add_separator unless lines_are_sequential?(prev_line, line)
|
200
|
+
end
|
201
|
+
add_line_to_result(line, escape = true) if line[:op] == :unchanged
|
202
|
+
end
|
203
|
+
end
|
204
|
+
@diff_lines = []
|
205
|
+
@diff_result << '</table>'
|
206
|
+
end
|
207
|
+
# reset values
|
208
|
+
@right_ln = nil
|
209
|
+
@left_ln = nil
|
210
|
+
@file_added = false
|
211
|
+
@file_removed = false
|
212
|
+
@binary = false
|
213
|
+
end
|
214
|
+
|
215
|
+
def diff_for_revision(content)
|
216
|
+
@left_ln = @right_ln = nil
|
217
|
+
|
218
|
+
@diff_result = []
|
219
|
+
@diff_lines = []
|
220
|
+
@removed_files = []
|
221
|
+
@current_file_name = nil
|
222
|
+
|
223
|
+
content.split("\n").each do |line|
|
224
|
+
if line =~ /^diff\s\-\-git\sa\/(.*)\sb\//
|
225
|
+
file_name = $1
|
226
|
+
add_changes_to_result
|
227
|
+
@current_file_name = file_name
|
228
|
+
end
|
229
|
+
|
230
|
+
op = line[0,1]
|
231
|
+
@left_ln.nil? || op == '@' ? process_info_line(line, op) : process_code_line(line, op)
|
232
|
+
end
|
233
|
+
add_changes_to_result
|
234
|
+
@diff_result.join("\n")
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_code_line(line, op)
|
238
|
+
if op == '-'
|
239
|
+
@diff_lines << { :removed => @left_ln, :added => nil, :op => :removal, :content => line[1..-1] }
|
240
|
+
@left_ln += 1
|
241
|
+
elsif op == '+'
|
242
|
+
@diff_lines << { :added => @right_ln, :removed => nil, :op => :addition, :content => line[1..-1] }
|
243
|
+
@right_ln += 1
|
244
|
+
else
|
245
|
+
@diff_lines << { :added => @right_ln, :removed => @left_ln, :op => :unchanged, :content => line }
|
246
|
+
@right_ln += 1
|
247
|
+
@left_ln += 1
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def process_info_line(line, op)
|
252
|
+
if line =~/^deleted\sfile\s/
|
253
|
+
@file_removed = true
|
254
|
+
elsif line =~ /^\-\-\-\s/ && line =~ /\/dev\/null/
|
255
|
+
@file_added = true
|
256
|
+
elsif line =~ /^\+\+\+\s/ && line =~ /\/dev\/null/
|
257
|
+
@file_removed = true
|
258
|
+
elsif line =~ /^Binary files \/dev\/null/ # Binary files /dev/null and ... differ (addition)
|
259
|
+
@binary = true
|
260
|
+
@file_added = true
|
261
|
+
elsif line =~ /\/dev\/null differ/ # Binary files ... and /dev/null differ (removal)
|
262
|
+
@binary = true
|
263
|
+
@file_removed = true
|
264
|
+
elsif op == '@'
|
265
|
+
@left_ln, @right_ln = range_info(line)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def extract_diff_from_git_show_output(content)
|
270
|
+
diff = []
|
271
|
+
diff_found = false
|
272
|
+
content.split("\n").each do |line|
|
273
|
+
diff_found = true if line =~ /^diff\s\-\-git/
|
274
|
+
next unless diff_found
|
275
|
+
diff << line
|
276
|
+
end
|
277
|
+
diff.join("\n")
|
278
|
+
end
|
279
|
+
|
280
|
+
def extract_commit_info_from_git_show_output(content)
|
281
|
+
result = { :message => [], :commit => '', :author => '', :date => '', :email => '' }
|
282
|
+
content.split("\n").each do |line|
|
283
|
+
if line =~ /^diff/ # end of commit info, return results
|
284
|
+
return result
|
285
|
+
elsif line =~ /^commit/
|
286
|
+
result[:commit] = line[7..-1]
|
287
|
+
elsif line =~ /^Author/
|
288
|
+
result[:author], result[:email] = author_name_and_email(line[8..-1])
|
289
|
+
elsif line =~ /^Date/
|
290
|
+
result[:date] = line[8..-1]
|
291
|
+
elsif line =~ /^Merge/
|
292
|
+
result[:merge] = line[8..-1]
|
293
|
+
else
|
294
|
+
clean_line = line.strip
|
295
|
+
result[:message] << clean_line unless clean_line.empty?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
result
|
299
|
+
end
|
300
|
+
|
301
|
+
def message_array_as_html(message)
|
302
|
+
message_map(message.collect { |m| CGI.escapeHTML(m) }.join('<br />'))
|
303
|
+
end
|
304
|
+
|
305
|
+
def author_name_and_email(info)
|
306
|
+
# input string format: "autor name <author@email.net>"
|
307
|
+
return [$1, $2] if info =~ /^([^\<]+)\s+\<\s*(.*)\s*\>\s*$/ # normal operation
|
308
|
+
# incomplete author info - return it as author name
|
309
|
+
[info, '']
|
310
|
+
end
|
311
|
+
|
312
|
+
def first_sentence(message_array)
|
313
|
+
msg = message_array.first.to_s.strip
|
314
|
+
return message_array.first if msg.empty? || msg =~ /^Merge\:/
|
315
|
+
msg
|
316
|
+
end
|
317
|
+
|
318
|
+
def unique_commits_per_branch?
|
319
|
+
!!@config['unique_commits_per_branch']
|
320
|
+
end
|
321
|
+
|
322
|
+
def get_previous_commits(previous_file)
|
323
|
+
previous_list = []
|
324
|
+
if File.exists?(previous_file)
|
325
|
+
lines = IO.read(previous_file)
|
326
|
+
lines = lines.lines if lines.respond_to?(:lines) # Ruby 1.9 tweak
|
327
|
+
previous_list = lines.to_a.map { |s| s.chomp }.compact.uniq
|
328
|
+
lines = nil
|
329
|
+
end
|
330
|
+
previous_list
|
331
|
+
end
|
332
|
+
|
333
|
+
def previous_dir
|
334
|
+
(!@previous_dir.nil? && File.exists?(@previous_dir)) ? @previous_dir : '/tmp'
|
335
|
+
end
|
336
|
+
|
337
|
+
def previous_prefix
|
338
|
+
unique_commits_per_branch? ? "#{Digest::SHA1.hexdigest(branch)}." : ''
|
339
|
+
end
|
340
|
+
|
341
|
+
def previous_file_path
|
342
|
+
previous_name = "#{previous_prefix}#{HANDLED_COMMITS_FILE}"
|
343
|
+
File.join(previous_dir, previous_name)
|
344
|
+
end
|
345
|
+
|
346
|
+
def new_file_path
|
347
|
+
new_name = "#{previous_prefix}#{NEW_HANDLED_COMMITS_FILE}"
|
348
|
+
File.join(previous_dir, new_name)
|
349
|
+
end
|
350
|
+
|
351
|
+
def save_handled_commits(previous_list, flatten_commits)
|
352
|
+
return if flatten_commits.empty?
|
353
|
+
current_list = (previous_list + flatten_commits).last(MAX_COMMITS_PER_ACTION)
|
354
|
+
|
355
|
+
# use new file, unlink and rename to make it more atomic
|
356
|
+
File.open(new_file_path, 'w') { |f| f << current_list.join("\n") }
|
357
|
+
File.unlink(previous_file_path) if File.exists?(previous_file_path)
|
358
|
+
File.rename(new_file_path, previous_file_path)
|
359
|
+
end
|
360
|
+
|
361
|
+
def check_handled_commits(commits)
|
362
|
+
previous_list = get_previous_commits(previous_file_path)
|
363
|
+
commits.reject! {|c| (c.respond_to?(:lines) ? c.lines : c).find { |sha| previous_list.include?(sha) } }
|
364
|
+
save_handled_commits(previous_list, commits.flatten)
|
365
|
+
|
366
|
+
commits
|
367
|
+
end
|
368
|
+
|
369
|
+
def branch_name
|
370
|
+
branch.split('/').last
|
371
|
+
end
|
372
|
+
|
373
|
+
def old_commit?(commit_info)
|
374
|
+
return false if !@config.include?('skip_commits_older_than') || @config['skip_commits_older_than'].to_i <= 0
|
375
|
+
commit_when = Time.parse(commit_info[:date])
|
376
|
+
(Time.now - commit_when) > (SECS_PER_DAY * @config['skip_commits_older_than'].to_i)
|
377
|
+
end
|
378
|
+
|
379
|
+
def diff_between_revisions(rev1, rev2, repo, branch)
|
380
|
+
@branch = branch
|
381
|
+
@result = []
|
382
|
+
if rev1 == rev2
|
383
|
+
commits = [rev1]
|
384
|
+
elsif rev1 =~ /^0+$/
|
385
|
+
# creating a new remote branch
|
386
|
+
commits = Git.branch_commits(branch)
|
387
|
+
elsif rev2 =~ /^0+$/
|
388
|
+
# deleting an existing remote branch
|
389
|
+
commits = []
|
390
|
+
else
|
391
|
+
log = Git.log(rev1, rev2)
|
392
|
+
commits = log.scan(/^commit\s([a-f0-9]+)/).map { |a| a.first }
|
393
|
+
end
|
394
|
+
|
395
|
+
commits = check_handled_commits(commits)
|
396
|
+
|
397
|
+
commits.each_with_index do |commit, i|
|
398
|
+
raw_diff = Git.show(commit)
|
399
|
+
raise "git show output is empty" if raw_diff.empty?
|
400
|
+
|
401
|
+
commit_info = extract_commit_info_from_git_show_output(raw_diff)
|
402
|
+
next if old_commit?(commit_info)
|
403
|
+
|
404
|
+
title = "<div class=\"title\">"
|
405
|
+
title += "<strong>Message:</strong> #{message_array_as_html commit_info[:message]}<br />\n"
|
406
|
+
title += "<strong>Commit:</strong> "
|
407
|
+
|
408
|
+
if (@config["link_files"] && @config["link_files"] == "gitweb" && @config["gitweb"])
|
409
|
+
title += "<a href='#{@config['gitweb']['path']}?p=#{@config['gitweb']['project']};a=commitdiff;h=#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
|
410
|
+
elsif (@config["link_files"] && @config["link_files"] == "gitorious" && @config["gitorious"])
|
411
|
+
title += "<a href='#{@config['gitorious']['path']}/#{@config['gitorious']['project']}/#{@config['gitorious']['repository']}/commit/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
|
412
|
+
elsif (@config["link_files"] && @config["link_files"] == "trac" && @config["trac"])
|
413
|
+
title += "<a href='#{@config['trac']['path']}/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
|
414
|
+
elsif (@config["link_files"] && @config["link_files"] == "cgit" && @config["cgit"])
|
415
|
+
title += "<a href='#{@config['cgit']['path']}/#{@config['cgit']['project']}/commit/?id=#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
|
416
|
+
else
|
417
|
+
title += " #{commit_info[:commit]}"
|
418
|
+
end
|
419
|
+
|
420
|
+
title += "<br />\n"
|
421
|
+
|
422
|
+
title += "<strong>Branch:</strong> #{CGI.escapeHTML(branch_name)}\n<br />"
|
423
|
+
title += "<strong>Date:</strong> #{CGI.escapeHTML commit_info[:date]}\n<br />"
|
424
|
+
title += "<strong>Author:</strong> #{CGI.escapeHTML(commit_info[:author])} <#{commit_info[:email]}>\n</div>"
|
425
|
+
|
426
|
+
text = "#{raw_diff}\n\n\n"
|
427
|
+
|
428
|
+
html = title
|
429
|
+
html += diff_for_revision(extract_diff_from_git_show_output(raw_diff))
|
430
|
+
html += "<br /><br />"
|
431
|
+
commit_info[:message] = first_sentence(commit_info[:message])
|
432
|
+
@result << {:commit_info => commit_info, :html_content => html, :text_content => text }
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def message_replace!(message, search_for, replace_with)
|
437
|
+
if replace_with.kind_of?(Proc)
|
438
|
+
message.gsub!(Regexp.new(search_for)) do |m|
|
439
|
+
r = replace_with.call(m)
|
440
|
+
r[:phrase] + ' ' + r[:links].map { |m| "<a href=\"#{m[:url]}\">#{m[:title]}</a>" }.join(', ')
|
441
|
+
end
|
442
|
+
else
|
443
|
+
full_replace_with = "<a href=\"#{replace_with}\">\\0</a>"
|
444
|
+
message.gsub!(Regexp.new(search_for), full_replace_with)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def do_message_integration(message)
|
449
|
+
return message unless @config['message_integration'].respond_to?(:each_pair)
|
450
|
+
@config['message_integration'].each_pair do |pm, url|
|
451
|
+
pm_def = DiffToHtml::INTEGRATION_MAP[pm.to_sym] or next
|
452
|
+
replace_with = pm_def[:replace_with]
|
453
|
+
replace_with = replace_with.kind_of?(Proc) ? lambda { |m| pm_def[:replace_with].call(m, url) } : replace_with.gsub('#{url}', url)
|
454
|
+
message_replace!(message, pm_def[:search_for], replace_with)
|
455
|
+
end
|
456
|
+
message
|
457
|
+
end
|
458
|
+
|
459
|
+
def do_message_map(message)
|
460
|
+
return message unless @config['message_map'].respond_to?(:each_pair)
|
461
|
+
@config['message_map'].each_pair do |search_for, replace_with|
|
462
|
+
message_replace!(message, Regexp.new(search_for), replace_with)
|
463
|
+
end
|
464
|
+
message
|
465
|
+
end
|
466
|
+
|
467
|
+
def message_map(message)
|
468
|
+
do_message_map(do_message_integration(message))
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|