git-commit-mailer 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/README.md +47 -0
- data/Rakefile +23 -0
- data/bin/git-commit-mailer +137 -0
- data/git-commit-mailer.gemspec +26 -0
- data/lib/git-commit-mailer.rb +1379 -0
- data/lib/git-commit-mailer/commit-info.rb +291 -0
- data/lib/git-commit-mailer/file-diff.rb +356 -0
- data/lib/git-commit-mailer/html-mail-body-formatter.rb +563 -0
- data/lib/git-commit-mailer/info.rb +51 -0
- data/lib/git-commit-mailer/mail-body-formatter.rb +97 -0
- data/lib/git-commit-mailer/push-info.rb +100 -0
- data/lib/git-commit-mailer/text-mail-body-formatter.rb +87 -0
- data/lib/git-commit-mailer/version.rb +4 -0
- data/license/GPL-3.txt +674 -0
- metadata +122 -0
@@ -0,0 +1,563 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Ryo Onodera <onodera@clear-code.com>
|
4
|
+
# Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
class GitCommitMailer
|
20
|
+
class HTMLMailBodyFormatter < MailBodyFormatter
|
21
|
+
include ERB::Util
|
22
|
+
|
23
|
+
def format
|
24
|
+
@indent_level = 0
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def template
|
30
|
+
<<-EOT
|
31
|
+
<!DOCTYPE html>
|
32
|
+
<html>
|
33
|
+
<head>
|
34
|
+
</head>
|
35
|
+
<body>
|
36
|
+
<%= dl_start %>
|
37
|
+
<%= dt("Author") %>
|
38
|
+
<%= dd(h("\#{@info.author_name} <\#{@info.author_email}>")) %>
|
39
|
+
<%= dt("Date") %>
|
40
|
+
<%= dd(h(@mailer.format_time(@info.date))) %>
|
41
|
+
<%= dt("New Revision") %>
|
42
|
+
<%= dd(format_revision) %>
|
43
|
+
<% unless @info.merge_status.empty? %>
|
44
|
+
<%= dt("Merge") %>
|
45
|
+
<%= dd_start %>
|
46
|
+
<ul>
|
47
|
+
<% @info.merge_status.each do |status| %>
|
48
|
+
<li><%= h(status) %></li>
|
49
|
+
<% end %>
|
50
|
+
</ul>
|
51
|
+
</dd>
|
52
|
+
<% end %>
|
53
|
+
<%= dt("Message") %>
|
54
|
+
<%= dd(pre(h(@info.summary.strip))) %>
|
55
|
+
<%= format_files("Added", @info.added_files) %>
|
56
|
+
<%= format_files("Copied", @info.copied_files) %>
|
57
|
+
<%= format_files("Removed", @info.deleted_files) %>
|
58
|
+
<%= format_files("Modified", @info.updated_files) %>
|
59
|
+
<%= format_files("Renamed", @info.renamed_files) %>
|
60
|
+
<%= format_files("Type Changed", @info.type_changed_files) %>
|
61
|
+
</dl>
|
62
|
+
|
63
|
+
<%= format_diffs %>
|
64
|
+
</body>
|
65
|
+
</html>
|
66
|
+
EOT
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_revision
|
70
|
+
revision = @info.revision
|
71
|
+
url = commit_url
|
72
|
+
if url
|
73
|
+
formatted_revision = "<a href=\"#{h(url)}\">#{h(revision)}</a>"
|
74
|
+
else
|
75
|
+
formatted_revision = h(revision)
|
76
|
+
end
|
77
|
+
formatted_revision
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_files(title, items)
|
81
|
+
return "" if items.empty?
|
82
|
+
|
83
|
+
formatted_files = ""
|
84
|
+
formatted_files << " #{dt(h(title) + ' files')}\n"
|
85
|
+
formatted_files << " #{dd_start}\n"
|
86
|
+
formatted_files << " <ul>\n"
|
87
|
+
items.each do |item_name, new_item_name|
|
88
|
+
if new_item_name.nil?
|
89
|
+
formatted_files << " <li>#{format_file(item_name)}</li>\n"
|
90
|
+
else
|
91
|
+
formatted_files << " <li>\n"
|
92
|
+
formatted_files << " #{format_file(new_item_name)}<br>\n"
|
93
|
+
formatted_files << " (from #{item_name})\n"
|
94
|
+
formatted_files << " </li>\n"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
formatted_files << " </ul>\n"
|
98
|
+
formatted_files << " </dd>\n"
|
99
|
+
formatted_files
|
100
|
+
end
|
101
|
+
|
102
|
+
def format_file(file)
|
103
|
+
content = h(file)
|
104
|
+
url = commit_file_url(file)
|
105
|
+
if url
|
106
|
+
content = tag("a", {"href" => url}, content)
|
107
|
+
end
|
108
|
+
content
|
109
|
+
end
|
110
|
+
|
111
|
+
def format_diffs
|
112
|
+
return "" if @info.diffs.empty?
|
113
|
+
|
114
|
+
formatted_diff = ""
|
115
|
+
formatted_diff << " #{div_diff_section_start}\n"
|
116
|
+
@indent_level = 3
|
117
|
+
@info.diffs.each do |diff|
|
118
|
+
formatted_diff << "#{format_diff(diff)}\n"
|
119
|
+
end
|
120
|
+
formatted_diff << " </div>\n"
|
121
|
+
formatted_diff
|
122
|
+
end
|
123
|
+
|
124
|
+
def format_diff(diff)
|
125
|
+
header_column = format_header_column(diff)
|
126
|
+
from_line_column, to_line_column, content_column =
|
127
|
+
format_body_columns(diff)
|
128
|
+
|
129
|
+
table_diff do
|
130
|
+
head = tag("thead") do
|
131
|
+
tr_diff_header do
|
132
|
+
tag("td", {"colspan" => "3"}) do
|
133
|
+
pre_column(header_column)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
body = tag("tbody") do
|
139
|
+
tag("tr") do
|
140
|
+
[
|
141
|
+
th_diff_line_number {pre_column(from_line_column)},
|
142
|
+
th_diff_line_number {pre_column(to_line_column)},
|
143
|
+
td_diff_content {pre_column(content_column)},
|
144
|
+
]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
[head, body]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def format_header_column(diff)
|
153
|
+
header_column = ""
|
154
|
+
diff.format_header.each_line do |line|
|
155
|
+
line = line.chomp
|
156
|
+
case line
|
157
|
+
when /^=/
|
158
|
+
header_column << span_diff_header_mark(h(line))
|
159
|
+
else
|
160
|
+
header_column << span_diff_header(h(line))
|
161
|
+
end
|
162
|
+
header_column << "\n"
|
163
|
+
end
|
164
|
+
header_column
|
165
|
+
end
|
166
|
+
|
167
|
+
def format_body_columns(diff)
|
168
|
+
from_line_column = ""
|
169
|
+
to_line_column = ""
|
170
|
+
content_column = ""
|
171
|
+
file_path = diff.file_path
|
172
|
+
diff.changes.each do |type, line_number, line|
|
173
|
+
case type
|
174
|
+
when :hunk_header
|
175
|
+
from_line_number, to_line_number = line_number
|
176
|
+
from_line_column << span_line_number_hunk_header(file_path, :from,
|
177
|
+
from_line_number)
|
178
|
+
to_line_column << span_line_number_hunk_header(file_path, :to,
|
179
|
+
to_line_number)
|
180
|
+
case line
|
181
|
+
when /\A(@@[\s0-9\-+,]+@@\s*)(.+)(\s*)\z/
|
182
|
+
hunk_info = $1
|
183
|
+
context = $2
|
184
|
+
formatted_line = h(hunk_info) + span_diff_context(h(context))
|
185
|
+
else
|
186
|
+
formatted_line = h(line)
|
187
|
+
end
|
188
|
+
content_column << span_diff_hunk_header(formatted_line)
|
189
|
+
when :added
|
190
|
+
from_line_column << span_line_number_nothing
|
191
|
+
to_line_column << span_line_number_added(file_path, line_number)
|
192
|
+
content_column << span_diff_added(h(line))
|
193
|
+
when :deleted
|
194
|
+
from_line_column << span_line_number_deleted(file_path, line_number)
|
195
|
+
to_line_column << span_line_number_nothing
|
196
|
+
content_column << span_diff_deleted(h(line))
|
197
|
+
when :not_changed
|
198
|
+
from_line_number, to_line_number = line_number
|
199
|
+
from_line_column << span_line_number_not_changed(file_path, :from,
|
200
|
+
from_line_number)
|
201
|
+
to_line_column << span_line_number_not_changed(file_path, :to,
|
202
|
+
to_line_number)
|
203
|
+
content_column << span_diff_not_changed(h(line))
|
204
|
+
end
|
205
|
+
from_line_column << "\n"
|
206
|
+
to_line_column << "\n"
|
207
|
+
content_column << "\n"
|
208
|
+
end
|
209
|
+
[from_line_column, to_line_column, content_column]
|
210
|
+
end
|
211
|
+
|
212
|
+
def tag_start(name, attributes)
|
213
|
+
start_tag = "<#{name}"
|
214
|
+
unless attributes.empty?
|
215
|
+
sorted_attributes = attributes.sort_by do |key, value|
|
216
|
+
key
|
217
|
+
end
|
218
|
+
formatted_attributes = sorted_attributes.collect do |key, value|
|
219
|
+
if value.is_a?(Hash)
|
220
|
+
sorted_value = value.sort_by do |value_key, value_value|
|
221
|
+
value_key
|
222
|
+
end
|
223
|
+
value = sorted_value.collect do |value_key, value_value|
|
224
|
+
"#{value_key}: #{value_value}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
if value.is_a?(Array)
|
228
|
+
value = value.sort.join("; ")
|
229
|
+
end
|
230
|
+
"#{h(key)}=\"#{h(value)}\""
|
231
|
+
end
|
232
|
+
formatted_attributes = formatted_attributes.join(" ")
|
233
|
+
start_tag << " #{formatted_attributes}"
|
234
|
+
end
|
235
|
+
start_tag << ">"
|
236
|
+
start_tag
|
237
|
+
end
|
238
|
+
|
239
|
+
def tag(name, attributes={}, content=nil, &block)
|
240
|
+
block_used = false
|
241
|
+
if content.nil? and block_given?
|
242
|
+
@indent_level += 1
|
243
|
+
if block.arity == 1
|
244
|
+
content = []
|
245
|
+
yield(content)
|
246
|
+
else
|
247
|
+
content = yield
|
248
|
+
end
|
249
|
+
@indent_level -= 1
|
250
|
+
block_used = true
|
251
|
+
end
|
252
|
+
content ||= ""
|
253
|
+
if content.is_a?(Array)
|
254
|
+
if block_used
|
255
|
+
separator = "\n"
|
256
|
+
else
|
257
|
+
separator = ""
|
258
|
+
end
|
259
|
+
content = content.join(separator)
|
260
|
+
end
|
261
|
+
|
262
|
+
formatted_tag = ""
|
263
|
+
formatted_tag << " " * @indent_level if block_used
|
264
|
+
formatted_tag << tag_start(name, attributes)
|
265
|
+
formatted_tag << "\n" if block_used
|
266
|
+
formatted_tag << content
|
267
|
+
formatted_tag << "\n" + (" " * @indent_level) if block_used
|
268
|
+
formatted_tag << "</#{name}>"
|
269
|
+
formatted_tag
|
270
|
+
end
|
271
|
+
|
272
|
+
def dl_start
|
273
|
+
tag_start("dl",
|
274
|
+
"style" => {
|
275
|
+
"margin-left" => "2em",
|
276
|
+
"line-height" => "1.5",
|
277
|
+
})
|
278
|
+
end
|
279
|
+
|
280
|
+
def dt_margin
|
281
|
+
8
|
282
|
+
end
|
283
|
+
|
284
|
+
def dt(content)
|
285
|
+
tag("dt",
|
286
|
+
{
|
287
|
+
"style" => {
|
288
|
+
"clear" => "both",
|
289
|
+
"float" => "left",
|
290
|
+
"width" => "#{dt_margin}em",
|
291
|
+
"font-weight" => "bold",
|
292
|
+
},
|
293
|
+
},
|
294
|
+
content)
|
295
|
+
end
|
296
|
+
|
297
|
+
def dd_start
|
298
|
+
tag_start("dd",
|
299
|
+
"style" => {
|
300
|
+
"margin-left" => "#{dt_margin + 0.5}em",
|
301
|
+
})
|
302
|
+
end
|
303
|
+
|
304
|
+
def dd(content)
|
305
|
+
"#{dd_start}#{content}</dd>"
|
306
|
+
end
|
307
|
+
|
308
|
+
def border_styles
|
309
|
+
{
|
310
|
+
"border" => "1px solid #aaa",
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
def pre(content, styles={})
|
315
|
+
font_families = [
|
316
|
+
"Consolas", "Menlo", "\"Liberation Mono\"",
|
317
|
+
"Courier", "monospace"
|
318
|
+
]
|
319
|
+
pre_styles = {
|
320
|
+
"font-family" => font_families.join(", "),
|
321
|
+
"line-height" => "1.2",
|
322
|
+
"padding" => "0.5em",
|
323
|
+
"width" => "auto",
|
324
|
+
}
|
325
|
+
pre_styles = pre_styles.merge(border_styles)
|
326
|
+
tag("pre", {"style" => pre_styles.merge(styles)}, content)
|
327
|
+
end
|
328
|
+
|
329
|
+
def div_diff_section_start
|
330
|
+
tag_start("div",
|
331
|
+
"class" => "diff-section",
|
332
|
+
"style" => {
|
333
|
+
"clear" => "both",
|
334
|
+
})
|
335
|
+
end
|
336
|
+
|
337
|
+
def div_diff_start
|
338
|
+
tag_start("div",
|
339
|
+
"class" => "diff",
|
340
|
+
"style" => {
|
341
|
+
"margin-left" => "1em",
|
342
|
+
"margin-right" => "1em",
|
343
|
+
})
|
344
|
+
end
|
345
|
+
|
346
|
+
def table_diff(&block)
|
347
|
+
styles = {
|
348
|
+
"border-collapse" => "collapse",
|
349
|
+
}
|
350
|
+
tag("table",
|
351
|
+
{
|
352
|
+
"style" => border_styles.merge(styles),
|
353
|
+
},
|
354
|
+
&block)
|
355
|
+
end
|
356
|
+
|
357
|
+
def tr_diff_header(&block)
|
358
|
+
tag("tr",
|
359
|
+
{
|
360
|
+
"class" => "diff-header",
|
361
|
+
"style" => border_styles,
|
362
|
+
},
|
363
|
+
&block)
|
364
|
+
end
|
365
|
+
|
366
|
+
def th_diff_line_number(&block)
|
367
|
+
tag("th",
|
368
|
+
{
|
369
|
+
"class" => "diff-line-number",
|
370
|
+
"style" => border_styles,
|
371
|
+
},
|
372
|
+
&block)
|
373
|
+
end
|
374
|
+
|
375
|
+
def td_diff_content(&block)
|
376
|
+
tag("td",
|
377
|
+
{
|
378
|
+
"class" => "diff-content",
|
379
|
+
"style" => border_styles,
|
380
|
+
},
|
381
|
+
&block)
|
382
|
+
end
|
383
|
+
|
384
|
+
def pre_column(column)
|
385
|
+
pre(column,
|
386
|
+
"white-space" => "normal",
|
387
|
+
"margin" => "0",
|
388
|
+
"border" => "0")
|
389
|
+
end
|
390
|
+
|
391
|
+
def span_common_styles
|
392
|
+
{
|
393
|
+
"white-space" => "pre",
|
394
|
+
"display" => "block",
|
395
|
+
}
|
396
|
+
end
|
397
|
+
|
398
|
+
def span_context_styles
|
399
|
+
{
|
400
|
+
"background-color" => "#ffffaa",
|
401
|
+
"color" => "#000000",
|
402
|
+
}
|
403
|
+
end
|
404
|
+
|
405
|
+
def span_deleted_styles
|
406
|
+
{
|
407
|
+
"background-color" => "#ffaaaa",
|
408
|
+
"color" => "#000000",
|
409
|
+
}
|
410
|
+
end
|
411
|
+
|
412
|
+
def span_added_styles
|
413
|
+
{
|
414
|
+
"background-color" => "#aaffaa",
|
415
|
+
"color" => "#000000",
|
416
|
+
}
|
417
|
+
end
|
418
|
+
|
419
|
+
def span_line_number_styles
|
420
|
+
span_common_styles
|
421
|
+
end
|
422
|
+
|
423
|
+
def span_line_number_nothing
|
424
|
+
tag("span",
|
425
|
+
{
|
426
|
+
"class" => "diff-line-number-nothing",
|
427
|
+
"style" => span_line_number_styles,
|
428
|
+
},
|
429
|
+
" ")
|
430
|
+
end
|
431
|
+
|
432
|
+
def span_line_number_hunk_header(file_path, direction, offset)
|
433
|
+
content = "..."
|
434
|
+
url = commit_file_line_number_url(file_path, direction, offset - 1)
|
435
|
+
if url
|
436
|
+
content = tag("a", {"href" => url}, content)
|
437
|
+
end
|
438
|
+
tag("span",
|
439
|
+
{
|
440
|
+
"class" => "diff-line-number-hunk-header",
|
441
|
+
"style" => span_line_number_styles,
|
442
|
+
},
|
443
|
+
content)
|
444
|
+
end
|
445
|
+
|
446
|
+
def span_line_number_deleted(file_path, line_number)
|
447
|
+
content = h(line_number.to_s)
|
448
|
+
url = commit_file_line_number_url(file_path, :from, line_number)
|
449
|
+
if url
|
450
|
+
content = tag("a", {"href" => url}, content)
|
451
|
+
end
|
452
|
+
tag("span",
|
453
|
+
{
|
454
|
+
"class" => "diff-line-number-deleted",
|
455
|
+
"style" => span_line_number_styles.merge(span_deleted_styles),
|
456
|
+
},
|
457
|
+
content)
|
458
|
+
end
|
459
|
+
|
460
|
+
def span_line_number_added(file_path, line_number)
|
461
|
+
content = h(line_number.to_s)
|
462
|
+
url = commit_file_line_number_url(file_path, :to, line_number)
|
463
|
+
if url
|
464
|
+
content = tag("a", {"href" => url}, content)
|
465
|
+
end
|
466
|
+
tag("span",
|
467
|
+
{
|
468
|
+
"class" => "diff-line-number-added",
|
469
|
+
"style" => span_line_number_styles.merge(span_added_styles),
|
470
|
+
},
|
471
|
+
content)
|
472
|
+
end
|
473
|
+
|
474
|
+
def span_line_number_not_changed(file_path, direction, line_number)
|
475
|
+
content = h(line_number.to_s)
|
476
|
+
url = commit_file_line_number_url(file_path, direction, line_number)
|
477
|
+
if url
|
478
|
+
content = tag("a", {"href" => url}, content)
|
479
|
+
end
|
480
|
+
tag("span",
|
481
|
+
{
|
482
|
+
"class" => "diff-line-number-not-changed",
|
483
|
+
"style" => span_line_number_styles,
|
484
|
+
},
|
485
|
+
content)
|
486
|
+
end
|
487
|
+
|
488
|
+
def span_diff_styles
|
489
|
+
span_common_styles
|
490
|
+
end
|
491
|
+
|
492
|
+
def span_diff_metadata_styles
|
493
|
+
styles = {
|
494
|
+
"background-color" => "#eaf2f5",
|
495
|
+
"color" => "#999999",
|
496
|
+
}
|
497
|
+
span_diff_styles.merge(styles)
|
498
|
+
end
|
499
|
+
|
500
|
+
def span_diff_header(content)
|
501
|
+
tag("span",
|
502
|
+
{
|
503
|
+
"class" => "diff-header",
|
504
|
+
"style" => span_diff_metadata_styles,
|
505
|
+
},
|
506
|
+
content)
|
507
|
+
end
|
508
|
+
|
509
|
+
def span_diff_header_mark(content)
|
510
|
+
tag("span",
|
511
|
+
{
|
512
|
+
"class" => "diff-header-mark",
|
513
|
+
"style" => span_diff_metadata_styles,
|
514
|
+
},
|
515
|
+
content)
|
516
|
+
end
|
517
|
+
|
518
|
+
def span_diff_hunk_header(content)
|
519
|
+
tag("span",
|
520
|
+
{
|
521
|
+
"class" => "diff-hunk-header",
|
522
|
+
"style" => span_diff_metadata_styles,
|
523
|
+
},
|
524
|
+
content)
|
525
|
+
end
|
526
|
+
|
527
|
+
def span_diff_context(content)
|
528
|
+
tag("span",
|
529
|
+
{
|
530
|
+
"class" => "diff-context",
|
531
|
+
"style" => span_context_styles,
|
532
|
+
},
|
533
|
+
content)
|
534
|
+
end
|
535
|
+
|
536
|
+
def span_diff_deleted(content)
|
537
|
+
tag("span",
|
538
|
+
{
|
539
|
+
"class" => "diff-deleted",
|
540
|
+
"style" => span_diff_styles.merge(span_deleted_styles),
|
541
|
+
},
|
542
|
+
content)
|
543
|
+
end
|
544
|
+
|
545
|
+
def span_diff_added(content)
|
546
|
+
tag("span",
|
547
|
+
{
|
548
|
+
"class" => "diff-added",
|
549
|
+
"style" => span_diff_styles.merge(span_added_styles),
|
550
|
+
},
|
551
|
+
content)
|
552
|
+
end
|
553
|
+
|
554
|
+
def span_diff_not_changed(content)
|
555
|
+
tag("span",
|
556
|
+
{
|
557
|
+
"class" => "diff-not-changed",
|
558
|
+
"style" => span_diff_styles,
|
559
|
+
},
|
560
|
+
content)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|