git-commit-notifier 0.10.5 → 0.11.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/.yardopts CHANGED
@@ -2,4 +2,6 @@
2
2
  --markup-provider redcarpet
3
3
  --charset utf-8
4
4
  --readme README.md
5
-
5
+ -
6
+ README.md
7
+ LICENSE
data/README.md CHANGED
@@ -102,7 +102,7 @@ old commits in processes of forking, branching etc.
102
102
 
103
103
  ## Note on Patches/Pull Requests
104
104
 
105
- * Fork the project.
105
+ * Fork [the project](https://github.com/bitboxer/git-commit-notifier).
106
106
  * Make your feature addition or bug fix.
107
107
  * Add tests for it. This is important so I don't break it in a
108
108
  future version unintentionally.
@@ -126,5 +126,5 @@ Thanks for [putpat.tv](http://www.putpat.tv), [Primalgrasp](http://www.primalgra
126
126
 
127
127
  ## License
128
128
 
129
- MIT License, see the file LICENSE.
129
+ MIT License, see the {file:LICENSE}.
130
130
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.5
1
+ 0.11.0
@@ -4,11 +4,40 @@ ignore_merge: false
4
4
  # Optional parameter for the subject-line of the mail
5
5
  # emailprefix: GIT
6
6
 
7
- # Limit lines per diff
8
- # lines_per_diff: 300
7
+ # Set limit on maximum number of lines per file diff
8
+ lines_per_diff: 300
9
9
 
10
10
  # Show file list only when too many files
11
- # too_many_files: 1000
11
+ too_many_files: 50
12
+
13
+ # Whether to expand css inline. May be compatible with more mail readers
14
+ # but consumes much more space and more cpu. Defaults to true
15
+ # expand_css: true
16
+
17
+ # Determine whether to add plaintext alternative section to generated email.
18
+ # This allows text-only readers an option, but greatly increases the size
19
+ # of generated emails. Defaults to true
20
+ # add_plaintext: true
21
+
22
+ # Subject: the subject may be set by specifying a template.
23
+ #
24
+ # Substitution into the template is available for the following
25
+ # words, via the form ${word}
26
+ #
27
+ # prefix - The emailprefix set, defaulting to repo_name
28
+ # repo_name - Name of the git repo
29
+ # branch_name - Name of the git branch (master is "" unless show_master_branch_name)
30
+ # commit_id - The git commit id (hash)
31
+ # message - The commit message
32
+ # commit_number - The commit number within the push; 1-based
33
+ # commit_count - The number of commits within the push
34
+ # commit_count_phrase - The number of commits, as "1 commit", "2 commits", etc.
35
+ #
36
+ # The default subject template varies a little depending on whether
37
+ # or not group_email_by_push is set.
38
+ #
39
+ #subject: "[${prefix}${branch_name}] ${commit_count_phrase}: ${message}"
40
+ #subject: "[${prefix}${branch_name}][${commit_number}] ${message}"
12
41
 
13
42
  # defines what branches to email for (defaults to all)
14
43
  # include_branches: ['master', 'some_other_branch']
@@ -68,7 +97,8 @@ nntp_settings:
68
97
  address: your.nntp.host.here
69
98
  port: 119
70
99
 
71
- # Decorate files and commit ids with link to a webview. Possible values: none, gitweb, gitorious, cgit or trac
100
+ # Decorate files and commit ids with link to a webview. Possible values: none, gitweb,
101
+ # gitorious, cgit, trac, gitlabhq, or redmine
72
102
  link_files: none
73
103
 
74
104
  # If link_files is set to "gitweb", you need to configure the path to your gitweb
@@ -94,6 +124,21 @@ cgit:
94
124
  trac:
95
125
  path: http://example.com/changeset
96
126
 
127
+ # If link_files is set to "gitlabhq", you need to configure the path to your gitlabhq
128
+ # instance
129
+ gitlabhq:
130
+ path: http://gitlabhq.example.com
131
+ version: 1.2
132
+
133
+ # If link_files is set to "redmine", you need to configure the path to your redmine
134
+ # instance
135
+ redmine:
136
+ path: http://redmine.example.com
137
+ # project: project-name
138
+ # keywords: [refs, fixes]
139
+ # project defaults to git repository name, keywords defaults to refs, fixes (for use with message_integration)
140
+
141
+
97
142
  # commit message URL map
98
143
  message_map:
99
144
  # '\brefs\s*\#(\d+)': 'http://example.com/redmine/issues/show/\1'
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
20
20
 
21
- s.homepage = %q{http://github.com/bitboxer/git-commit-notifier}
21
+ s.homepage = %q{http://bitboxer.github.com/git-commit-notifier/}
22
22
  s.require_paths = ["lib"]
23
23
  s.rubygems_version = %q{1.3.7}
24
24
  s.summary = %q{Sends git commit messages with diffs}
@@ -47,11 +47,9 @@ module GitCommitNotifier
47
47
  project_path = Dir.getwd
48
48
  recipient = config["mailinglist"] || Git.mailing_list_address
49
49
 
50
+ # If no recipients specified, bail out gracefully. This is not an error, and might be intentional
50
51
  if recipient.nil? || recipient.length == 0
51
- CommitHook.show_error(
52
- "Please add a recipient for the emails. Eg : \n" +
53
- " git config hooks.mailinglist developer@example.com"
54
- )
52
+ info("bypassing commit notification; no recipients specified (consider setting git config hooks.mailinglist)")
55
53
  return
56
54
  end
57
55
 
@@ -62,7 +60,9 @@ module GitCommitNotifier
62
60
  logger.debug("rev2: #{rev2}")
63
61
  logger.debug("included branches: #{include_branches.join(', ')}") unless include_branches.nil?
64
62
 
65
- prefix = config["emailprefix"] || Git.repo_name
63
+ repo_name = Git.repo_name
64
+ prefix = config["emailprefix"] || repo_name
65
+
66
66
  branch_name = if ref_name =~ /^refs\/heads\/(.+)$/
67
67
  $1
68
68
  else
@@ -76,10 +76,30 @@ module GitCommitNotifier
76
76
  info("Supressing mail for branch #{branch_name}...")
77
77
  return
78
78
  end
79
-
79
+
80
80
  branch_name = "/#{branch_name}"
81
81
  branch_name = "" if !config["show_master_branch_name"] && branch_name == '/master'
82
-
82
+
83
+ # Replacements for subject template
84
+ # prefix
85
+ # repo_name
86
+ # branch_name
87
+ # commit_id (hash)
88
+ # short_message
89
+ # commit_number
90
+ # commit_count
91
+ # commit_count_phrase (1 commit, 2 commits, etc)
92
+ subject_words = {
93
+ :prefix => prefix,
94
+ :repo_name => repo_name,
95
+ :branch_name => branch_name,
96
+ :commit_id => nil,
97
+ :message => nil,
98
+ :commit_number => nil,
99
+ :commit_count => nil,
100
+ :commit_count_phrase => nil
101
+ }
102
+
83
103
  info("Sending mail...")
84
104
 
85
105
  diff2html = DiffToHtml.new(Dir.pwd, config)
@@ -102,39 +122,63 @@ module GitCommitNotifier
102
122
  text << result[:text_content]
103
123
  html << result[:html_content]
104
124
  end
125
+
126
+ # Form the subject from template
127
+ revised_subject_words = subject_words.merge({
128
+ :commit_id => result[:commit_info][:commit],
129
+ :message => result[:commit_info][:message],
130
+ :commit_number => 1,
131
+ :commit_count => diffresult.size,
132
+ :commit_count_phrase => diffresult.size == 1 ? "#{diffresult.size} commit" : "#{diffresult.size} commits"
133
+ })
134
+ subject_template = config['subject'] || "[${prefix}${branch_name}] ${commit_count_phrase}: ${message}"
135
+ subject = subject_template.gsub(/\$\{(\w+)\}/) { |m| revised_subject_words[$1.intern] }
105
136
 
106
137
  emailer = Emailer.new(config,
107
138
  :project_path => project_path,
108
139
  :recipient => recipient,
109
140
  :from_address => config["from"] || result[:commit_info][:email],
110
141
  :from_alias => result[:commit_info][:author],
111
- :subject => "[#{prefix}#{branch_name}] #{diffresult.size > 1 ? "#{diffresult.size} commits: " : ''}#{result[:commit_info][:message]}",
142
+ :subject => subject,
112
143
  :text_message => text.join("------------------------------------------\n\n"),
113
144
  :html_message => html.join("<hr /><br />"),
114
145
  :old_rev => rev1,
115
146
  :new_rev => rev2,
116
- :ref_name => ref_name
147
+ :ref_name => ref_name,
148
+ :repo_name => repo_name
117
149
  )
118
150
  emailer.send
119
151
  else
120
- i = 0
152
+ commit_number = 1
121
153
  diff2html.diff_between_revisions(rev1, rev2, prefix, ref_name) do |result|
122
- next if config["ignore_merge"] && merge_commit?(result)
123
- nr = number(i)
154
+ next if config["ignore_merge"] && merge_commit?(result)
155
+
156
+ # Form the subject from template
157
+ revised_subject_words = subject_words.merge({
158
+ :commit_id => result[:commit_info][:commit],
159
+ :message => result[:commit_info][:message],
160
+ :commit_number => commit_number,
161
+ :commit_count => 1,
162
+ :commit_count_phrase => "1 commit"
163
+ })
164
+ subject_template = config['subject'] || "[${prefix}${branch_name}][${commit_number}] ${message}"
165
+ subject = subject_template.gsub(/\$\{(\w+)\}/) { |m| revised_subject_words[$1.intern] }
166
+
124
167
  emailer = Emailer.new(config,
125
168
  :project_path => project_path,
126
169
  :recipient => recipient,
127
170
  :from_address => config["from"] || result[:commit_info][:email],
128
171
  :from_alias => result[:commit_info][:author],
129
- :subject => "[#{prefix}#{branch_name}]#{nr} #{result[:commit_info][:message]}",
172
+ :subject => subject,
130
173
  :text_message => result[:text_content],
131
174
  :html_message => result[:html_content],
132
175
  :old_rev => rev1,
133
176
  :new_rev => rev2,
134
- :ref_name => ref_name
177
+ :ref_name => ref_name,
178
+ :repo_name => repo_name
135
179
  )
136
180
  emailer.send
137
- i += 1
181
+ commit_number += 1
138
182
  end
139
183
  end
140
184
  end
@@ -10,20 +10,27 @@ module GitCommitNotifier
10
10
 
11
11
  INTEGRATION_MAP = {
12
12
  :mediawiki => { :search_for => /\[\[([^\[\]]+)\]\]/, :replace_with => '#{url}/\1' },
13
- :redmine => { :search_for => /\b(?:refs|fixes)([\s&,]+\#\d+)+/i, :replace_with => lambda do |m, url|
14
- # we can provide Proc that gets matched string and configuration url.
15
- # result should be in form of:
16
- # { :phrase => 'phrase started with', :links => [ { :title => 'title of url', :url => 'target url' }, ... ] }
17
- match = m.match(/^(refs|fixes)(.*)$/i)
18
- return m unless match
19
- r = { :phrase => match[1] }
20
- captures = match[2].split(/[\s\&\,]+/).map { |m| (m =~ /(\d+)/) ? $1 : m }.reject { |c| c.empty? }
21
- r[:links] = captures.map { |mn| { :title => "##{mn}", :url => "#{url}/issues/show/#{mn}" } }
22
- r
23
- end },
13
+ :redmine => {
14
+ :search_for => lambda do |config|
15
+ keywords = (config['redmine'] && config['redmine']['keywords']) || ["refs", "fixes"]
16
+ /\b(?:#{keywords.join('\b|')})([\s&,]+\#\d+)+/i
17
+ end,
18
+ :replace_with => lambda do |m, url, config|
19
+ # we can provide Proc that gets matched string and configuration url.
20
+ # result should be in form of:
21
+ # { :phrase => 'phrase started with', :links => [ { :title => 'title of url', :url => 'target url' }, ... ] }
22
+ keywords = (config['redmine'] && config['redmine']['keywords']) || ["refs", "fixes"]
23
+ match = m.match(/^(#{keywords.join('\b|')})(.*)$/i)
24
+ return m unless match
25
+ r = { :phrase => match[1] }
26
+ captures = match[2].split(/[\s\&\,]+/).map { |m| (m =~ /(\d+)/) ? $1 : m }.reject { |c| c.empty? }
27
+ r[:links] = captures.map { |mn| { :title => "##{mn}", :url => "#{url}/issues/show/#{mn}" } }
28
+ r
29
+ end },
24
30
  :bugzilla => { :search_for => /\bBUG\s*(\d+)/i, :replace_with => '#{url}/show_bug.cgi?id=\1' },
25
31
  :fogbugz => { :search_for => /\bbugzid:\s*(\d+)/i, :replace_with => '#{url}\1' }
26
32
  }.freeze
33
+ MAX_LINE_LENGTH = 512
27
34
  MAX_COMMITS_PER_ACTION = 10000
28
35
  HANDLED_COMMITS_FILE = 'previously.txt'.freeze
29
36
  NEW_HANDLED_COMMITS_FILE = 'previously_new.txt'.freeze
@@ -64,17 +71,13 @@ module GitCommitNotifier
64
71
  end
65
72
 
66
73
  def lines_per_diff
67
- config['lines_per_diff']
74
+ @config['lines_per_diff']
68
75
  end
69
76
 
70
- def ignore_whitespace?
77
+ def ignore_whitespaces?
71
78
  @config['ignore_whitespace'].nil? || @config['ignore_whitespace']
72
79
  end
73
80
 
74
- def skip_lines?
75
- lines_per_diff && (@lines_added >= lines_per_diff)
76
- end
77
-
78
81
  def add_separator
79
82
  @diff_result << '<tr class="sep"><td class="sep" colspan="3" title="Unchanged content skipped between diff. blocks">&hellip;</td></tr>'
80
83
  end
@@ -84,7 +87,6 @@ module GitCommitNotifier
84
87
  end
85
88
 
86
89
  def add_line_to_result(line, escape)
87
- @lines_added += 1
88
90
  klass = line_class(line)
89
91
  content = (escape == :escape) ? escape_content(line[:content]) : line[:content]
90
92
  padding = '&nbsp;' if klass != ''
@@ -155,7 +157,8 @@ module GitCommitNotifier
155
157
  end
156
158
 
157
159
  file_name = @current_file_name
158
-
160
+
161
+ # TODO: these filenames, etc, should likely be properly html escaped
159
162
  if config['link_files']
160
163
  file_name = if config["link_files"] == "gitweb" && config["gitweb"]
161
164
  "<a href='#{config['gitweb']['path']}?p=#{Git.repo_name}.git;f=#{file_name};h=#{@current_sha};hb=#{@current_commit}'>#{file_name}</a>"
@@ -163,10 +166,18 @@ module GitCommitNotifier
163
166
  "<a href='#{config['gitorious']['path']}/#{config['gitorious']['project']}/#{config['gitorious']['repository']}/blobs/#{branch_name}/#{file_name}'>#{file_name}</a>"
164
167
  elsif config["link_files"] == "cgit" && config["cgit"]
165
168
  "<a href='#{config['cgit']['path']}/#{config['cgit']['project']}/tree/#{file_name}'>#{file_name}</a>"
169
+ elsif config["link_files"] == "gitlabhq" && config["gitlabhq"]
170
+ if config["gitlabhq"]["version"] && config["gitlabhq"]["version"] < 1.2
171
+ "<a href='#{config['gitlabhq']['path']}/#{Git.repo_name.gsub(".", "_")}/tree/#{@current_commit}/#{file_name}'>#{file_name}</a>"
172
+ else
173
+ "<a href='#{config['gitlabhq']['path']}/#{Git.repo_name.gsub(".", "_")}/#{@current_commit}/tree/#{file_name}'>#{file_name}</a>"
174
+ end
175
+ elsif config["link_files"] == "redmine" && config["redmine"]
176
+ "<a href='#{config['redmine']['path']}/projects/#{config['redmine']['project'] || Git.repo_name}/repository/revisions/#{@current_commit}/entry/#{file_name}'>#{file_name}</a>"
166
177
  else
167
178
  file_name
168
179
  end
169
- end
180
+ end
170
181
 
171
182
  header = "#{op} #{binary}file #{file_name}"
172
183
  "<h2>#{header}</h2>\n"
@@ -184,19 +195,26 @@ module GitCommitNotifier
184
195
 
185
196
  def add_changes_to_result
186
197
  return if @current_file_name.nil?
198
+
199
+ @lines_added = 0
187
200
  @diff_result << operation_description
188
201
  if !@diff_lines.empty? && !@too_many_files
189
202
  @diff_result << '<table>'
190
203
  removals = []
191
204
  additions = []
192
- @diff_lines.each_with_index do |line, index|
193
- if skip_lines?
194
- add_skip_notification
195
- break
196
- end
205
+
206
+ lines = if lines_per_diff.nil?
207
+ line_budget = nil
208
+ @diff_lines
209
+ else
210
+ line_budget = lines_per_diff - @lines_added
211
+ @diff_lines.slice(0, line_budget)
212
+ end
213
+
214
+ lines.each_with_index do |line, index|
197
215
  removals << line if line[:op] == :removal
198
216
  additions << line if line[:op] == :addition
199
- if line[:op] == :unchanged || index == @diff_lines.size - 1 # unchanged line or end of block, add prev lines to result
217
+ if line[:op] == :unchanged || index == lines.size - 1 # unchanged line or end of block, add prev lines to result
200
218
  if removals.size > 0 && additions.size > 0 # block of removed and added lines - perform intelligent diff
201
219
  add_block_to_results(lcs_diff(removals, additions), :dont_escape)
202
220
  else # some lines removed or added - no need to perform intelligent diff
@@ -204,14 +222,17 @@ module GitCommitNotifier
204
222
  end
205
223
  removals = []
206
224
  additions = []
207
- if index > 0 && index != @diff_lines.size - 1
208
- prev_line = @diff_lines[index - 1]
225
+ if index > 0 && index != lines.size - 1
226
+ prev_line = lines[index - 1]
209
227
  add_separator unless lines_are_sequential?(prev_line, line)
210
228
  end
211
229
  add_line_to_result(line, :escape) if line[:op] == :unchanged
212
230
  end
213
-
231
+ @lines_added += 1
214
232
  end
233
+
234
+ add_skip_notification if !line_budget.nil? && line_budget < @diff_lines.size
235
+
215
236
  @diff_result << '</table>'
216
237
  @diff_lines = []
217
238
  end
@@ -424,10 +445,50 @@ module GitCommitNotifier
424
445
  def merge_commit?(commit_info)
425
446
  ! commit_info[:merge].nil?
426
447
  end
448
+
449
+ def truncate_long_lines(text)
450
+ StringIO.open("", "w") do |output|
451
+ # Match encoding of output string to that of input string
452
+ output.string.force_encoding(text.encoding) if output.string.respond_to?(:force_encoding)
453
+
454
+ input = StringIO.new(text, "r")
455
+ input.each_line "\n" do |line|
456
+ if line.length > MAX_LINE_LENGTH && MAX_LINE_LENGTH >= 9
457
+ # Truncate the line
458
+ line.slice!(MAX_LINE_LENGTH-3..-1)
459
+
460
+ # Ruby < 1.9 doesn't know how to slice between
461
+ # characters, so deal specially with that case
462
+ # so that we don't truncate in the middle of a UTF8 sequence,
463
+ # which would be invalid.
464
+ if !line.respond_to?(:force_encoding)
465
+ # If the last remaining character is part of a UTF8 multibyte character,
466
+ # keep truncating until we go past the start of a UTF8 character.
467
+ # This assumes that this is a UTF8 string, which may be a false assumption
468
+ # unless somebody has taken care to check the encoding of the source file.
469
+ # We truncate at most 6 additional bytes, which is the length of the longest
470
+ # UTF8 sequence
471
+ 6.times do
472
+ c = line[-1, 1].to_i
473
+ break if (c & 0x80) == 0 # Last character is plain ASCII: don't truncate
474
+ line.slice!(-1, 1) # Truncate character
475
+ break if (c & 0xc0) == 0xc0 # Last character was the start of a UTF8 sequence, so we can stop now
476
+ end
477
+ end
478
+
479
+ # Append three dots to the end of line to indicate it's been truncated
480
+ # (avoiding ellipsis character so as not to introduce more encoding issues)
481
+ line << "...\n"
482
+ end
483
+ output << line
484
+ end
485
+ output.string
486
+ end
487
+ end
427
488
 
428
489
  def diff_for_commit(commit)
429
490
  @current_commit = commit
430
- raw_diff = Git.show(commit, ignore_whitespace?)
491
+ raw_diff = truncate_long_lines(Git.show(commit, :ignore_whitespaces => ignore_whitespaces?))
431
492
  raise "git show output is empty" if raw_diff.empty?
432
493
 
433
494
  commit_info = extract_commit_info_from_git_show_output(raw_diff)
@@ -457,6 +518,10 @@ module GitCommitNotifier
457
518
  "<a href='#{config['trac']['path']}/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
458
519
  elsif config["link_files"] == "cgit" && config["cgit"]
459
520
  "<a href='#{config['cgit']['path']}/#{config['cgit']['project']}/commit/?id=#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
521
+ elsif config["link_files"] == "gitlabhq" && config["gitlabhq"]
522
+ "<a href='#{config['gitlabhq']['path']}/#{Git.repo_name.gsub(".", "_")}/commits/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
523
+ elsif config["link_files"] == "redmine" && config["redmine"]
524
+ "<a href='#{config['redmine']['path']}/projects/#{config['redmine']['project'] || Git.repo_name}/repository/revisions/#{commit_info[:commit]}'>#{commit_info[:commit]}</a>"
460
525
  else
461
526
  " #{commit_info[:commit]}"
462
527
  end
@@ -504,13 +569,12 @@ module GitCommitNotifier
504
569
  []
505
570
  else
506
571
  log = Git.log(rev1, rev2)
507
- log.scan(/^commit\s([a-f0-9]+)/).map { |a| a.first }
572
+ log.scan(/^commit\s([a-f0-9]+)/).map { |a| a.first }.reverse
508
573
  end
509
574
 
510
575
  commits = check_handled_commits(commits)
511
576
 
512
577
  commits.each do |commit|
513
- @lines_added = 0 unless config["group_email_by_push"]
514
578
  begin
515
579
  commit_result = diff_for_commit(commit)
516
580
  next if commit_result.nil?
@@ -539,9 +603,11 @@ module GitCommitNotifier
539
603
  return message unless config['message_integration'].respond_to?(:each_pair)
540
604
  config['message_integration'].each_pair do |pm, url|
541
605
  pm_def = DiffToHtml::INTEGRATION_MAP[pm.to_sym] or next
606
+ search_for = pm_def[:search_for]
607
+ search_for = search_for.kind_of?(Proc) ? search_for.call(@config) : search_for
542
608
  replace_with = pm_def[:replace_with]
543
- replace_with = replace_with.kind_of?(Proc) ? lambda { |m| pm_def[:replace_with].call(m, url) } : replace_with.gsub('#{url}', url)
544
- message_replace!(message, pm_def[:search_for], replace_with)
609
+ replace_with = replace_with.kind_of?(Proc) ? lambda { |m| pm_def[:replace_with].call(m, url, @config) } : replace_with.gsub('#{url}', url)
610
+ message_replace!(message, search_for, replace_with)
545
611
  end
546
612
  message
547
613
  end
@@ -3,7 +3,7 @@ require 'premailer'
3
3
  class GitCommitNotifier::Emailer
4
4
  DEFAULT_STYLESHEET_PATH = File.join(File.dirname(__FILE__), '/../../template/styles.css').freeze
5
5
  TEMPLATE = File.join(File.dirname(__FILE__), '/../../template/email.html.erb').freeze
6
- PARAMETERS = %w[project_path recipient from_address from_alias subject text_message html_message ref_name old_rev new_rev].freeze
6
+ PARAMETERS = %w[project_path recipient from_address from_alias subject text_message html_message repo_name ref_name old_rev new_rev].freeze
7
7
 
8
8
  def config
9
9
  @@config
@@ -43,8 +43,11 @@ class GitCommitNotifier::Emailer
43
43
 
44
44
  def mail_html_message
45
45
  html = GitCommitNotifier::Emailer.template.result(binding)
46
- premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
47
- premailer.to_inline_css
46
+ if config['expand_css'].nil? || config['expand_css']
47
+ premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
48
+ html = premailer.to_inline_css
49
+ end
50
+ html
48
51
  end
49
52
 
50
53
  def boundary
@@ -78,10 +81,13 @@ class GitCommitNotifier::Emailer
78
81
  main_smtp.start( settings['domain'],
79
82
  settings['user_name'], settings['password'], settings['authentication']) do |smtp|
80
83
 
81
- recp = @recipient.split(",")
82
- smtp.open_message_stream(@from_address, recp) do |f|
84
+ recipients = @recipient.dup
85
+ recipients.force_encoding('ASCII-8BIT') if recipients.respond_to?(:force_encoding)
86
+ recipients = recipients.split(",")
87
+ smtp.open_message_stream(@from_address, recipients) do |f|
83
88
  content.each do |line|
84
- f.puts line
89
+ line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
90
+ f.print(line, "\r\n")
85
91
  end
86
92
  end
87
93
  end
@@ -94,7 +100,9 @@ class GitCommitNotifier::Emailer
94
100
  }.merge(options || {})
95
101
  command = "#{sendmail_settings['location']} #{sendmail_settings['arguments']}"
96
102
  IO.popen(command, "w+") do |f|
97
- f.write(content.join("\n"))
103
+ content.each do |line|
104
+ f.print(line, "\r\n")
105
+ end
98
106
  f.flush
99
107
  end
100
108
  end
@@ -110,27 +118,37 @@ class GitCommitNotifier::Emailer
110
118
  to_tag = config['delivery_method'] == 'nntp' ? 'Newsgroups' : 'To'
111
119
  quoted_from_alias = quote_if_necessary("#{@from_alias}",'utf-8')
112
120
  from = @from_alias.empty? ? @from_address : "#{quoted_from_alias} <#{@from_address}>"
121
+
122
+ plaintext = if config['add_plaintext'].nil? || config['add_plaintext']
123
+ @text_message
124
+ else
125
+ "Plain text part omitted. Consider setting add_plaintext in configuration."
126
+ end
113
127
 
114
128
  content = [
115
129
  "From: #{from}",
116
130
  "#{to_tag}: #{quote_if_necessary(@recipient, 'utf-8')}",
117
131
  "Subject: #{quote_if_necessary(@subject, 'utf-8')}",
118
132
  "X-Mailer: git-commit-notifier",
133
+ "X-Git-Repository: #{@repo_name}",
119
134
  "X-Git-Refname: #{@ref_name}",
120
135
  "X-Git-Oldrev: #{@old_rev}",
121
136
  "X-Git-Newrev: #{@new_rev}",
122
137
  "Mime-Version: 1.0",
123
- "Content-Type: multipart/alternative; boundary=#{boundary}\n\n\n",
138
+ "Content-Type: multipart/alternative; boundary=#{boundary}",
139
+ "",
124
140
  "--#{boundary}",
125
141
  "Content-Type: text/plain; charset=utf-8",
126
- "Content-Transfer-Encoding: 8bit",
127
- "Content-Disposition: inline\n\n\n",
128
- @text_message,
129
- "\n--#{boundary}",
142
+ "Content-Transfer-Encoding: quoted-printable",
143
+ "Content-Disposition: inline",
144
+ "",
145
+ encode_quoted_printable_message(plaintext),
146
+ "--#{boundary}",
130
147
  "Content-Type: text/html; charset=utf-8",
131
- "Content-Transfer-Encoding: 8bit",
132
- "Content-Disposition: inline\n\n\n",
133
- mail_html_message,
148
+ "Content-Transfer-Encoding: quoted-printable",
149
+ "Content-Disposition: inline",
150
+ "",
151
+ encode_quoted_printable_message(mail_html_message),
134
152
  "--#{boundary}--"]
135
153
 
136
154
  if @recipient.empty?
@@ -147,6 +165,45 @@ class GitCommitNotifier::Emailer
147
165
  end
148
166
  end
149
167
 
168
+ # Convert a message into quoted printable encoding,
169
+ # limiting line length to 76 characters per spec
170
+ # Encoding messages in this way ensures that they
171
+ # won't violate rules for maximum line length, which
172
+ # can result in the MTA breaking lines at inconvenient points,
173
+ # such as in the middle of UTF8 characters.
174
+ def encode_quoted_printable_message(text)
175
+ StringIO.open("", "w") do |output|
176
+ # Character encoding of output string can be plain US-ASCII since quoted-printable is plain ASCII
177
+ output.string.force_encoding("US-ASCII") if output.string.respond_to?(:force_encoding)
178
+
179
+ line_max = 76
180
+ line_len = 0
181
+
182
+ input = StringIO.new(text, "r")
183
+ input.each_byte do |b|
184
+ case (b)
185
+ when 9, 32..60, 62..126
186
+ if line_len >= line_max - 1
187
+ output << "=\r\n"
188
+ line_len = 0
189
+ end
190
+ output << b.chr
191
+ line_len += 1
192
+ else
193
+ if line_len >= line_max - 3
194
+ output << "=\r\n"
195
+ line_len = 0
196
+ end
197
+ output << "=%02X" % b
198
+ line_len += 3
199
+ end
200
+ end
201
+
202
+ output << "=\r\n" if line_len > 0
203
+ output.string
204
+ end
205
+ end
206
+
150
207
  # Convert the given text into quoted printable format, with an instruction
151
208
  # that the text be eventually interpreted in the given charset.
152
209
  def quoted_printable(text, charset)
@@ -2,8 +2,13 @@
2
2
 
3
3
  require 'git_commit_notifier'
4
4
 
5
+ # Git commit notifier namespace
5
6
  module GitCommitNotifier
7
+ # binaries code
6
8
  class Executor
9
+ # runs git commit notifier life
10
+ # @param [Array(String)] args Command line arguments
11
+ # @return [nil] Nothing
7
12
  def self.run!(args)
8
13
  case args.length
9
14
  when 0
@@ -24,6 +29,7 @@ module GitCommitNotifier
24
29
  else
25
30
  GitCommitNotifier::CommitHook.run args.first, args[1], args[2], args[3]
26
31
  end
32
+ nil
27
33
  end
28
34
  end
29
35
  end
@@ -1,5 +1,9 @@
1
+ # Git methods
1
2
  class GitCommitNotifier::Git
2
3
  class << self
4
+ # Runs specified command.
5
+ # @return (String) Shell command STDOUT (forced to UTF-8)
6
+ # @raise [ArgumentError] when command exits with nonzero status.
3
7
  def from_shell(cmd)
4
8
  r = `#{cmd}`
5
9
  raise ArgumentError.new("#{cmd} failed") unless $?.exitstatus.zero?
@@ -7,19 +11,38 @@ class GitCommitNotifier::Git
7
11
  r
8
12
  end
9
13
 
10
- def show(rev, ignore_whitespace)
14
+ # runs `git show`
15
+ # @note uses "--pretty=fuller" option.
16
+ # @return [String] Its output
17
+ # @see from_shell
18
+ # @param [String] rev Revision
19
+ # @param [Hash] opts Options
20
+ # @option opts [Boolean] :ignore_whitespaces Ignore whitespaces or not
21
+ def show(rev, opts = {})
11
22
  gitopt = ""
12
23
  gitopt += " --pretty=fuller"
13
- gitopt += " -w" if ignore_whitespace
24
+ gitopt += " -w" if opts[:ignore_whitespaces]
14
25
  data = from_shell("git show #{rev.strip}#{gitopt}")
15
26
  data
16
27
  end
17
28
 
29
+ # runs `git log`
30
+ # @note uses "--pretty=fuller" option.
31
+ # @return [String] Its output
32
+ # @see from_shell
33
+ # @param [String] rev1 First revision
34
+ # @param [String] rev2 Second revision
18
35
  def log(rev1, rev2)
19
36
  data = from_shell("git log --pretty=fuller #{rev1}..#{rev2}").strip
20
37
  data
21
38
  end
22
39
 
40
+ # runs `git log` and extract filenames only
41
+ # @note uses "--pretty=fuller" and "--name-status" options.
42
+ # @return [Array(String)] File names
43
+ # @see from_shell
44
+ # @param [String] rev1 First revision
45
+ # @param [String] rev2 Second revision
23
46
  def changed_files(rev1, rev2)
24
47
  output = ""
25
48
  lines = from_shell("git log #{rev1}..#{rev2} --name-status --pretty=oneline")
@@ -59,6 +82,8 @@ class GitCommitNotifier::Git
59
82
 
60
83
  def mailing_list_address
61
84
  from_shell("git config hooks.mailinglist").strip
85
+ rescue ArgumentError
86
+ nil
62
87
  end
63
88
  end
64
89
  end
@@ -75,7 +75,7 @@ describe GitCommitNotifier::CommitHook do
75
75
  mock(GitCommitNotifier::Git).repo_name { 'testproject' }
76
76
  mock(GitCommitNotifier::Git).changed_files('7e4f6b4', '4f13525') { [] }
77
77
  REVISIONS.each do |rev|
78
- mock(GitCommitNotifier::Git).show(rev, true) { IO.read(FIXTURES_PATH + "git_show_#{rev}") }
78
+ mock(GitCommitNotifier::Git).show(rev, :ignore_whitespaces => true) { IO.read(FIXTURES_PATH + "git_show_#{rev}") }
79
79
  end
80
80
  end
81
81
 
@@ -104,10 +104,10 @@ describe GitCommitNotifier::CommitHook do
104
104
  end
105
105
 
106
106
  describe :run do
107
- it "should report error when no recipients specified" do
107
+ it "should report informational message when no recipients specified" do
108
108
  mock(File).exists?(:noconfig) { false }
109
109
  mock(GitCommitNotifier::Git).mailing_list_address { nil }
110
- mock(GitCommitNotifier::CommitHook).show_error(/recipient/)
110
+ mock(GitCommitNotifier::CommitHook).info(/recipient/)
111
111
  GitCommitNotifier::CommitHook.run(:noconfig, :rev1, :rev2, 'master')
112
112
  end
113
113
  end
@@ -129,9 +129,9 @@ describe GitCommitNotifier::DiffToHtml do
129
129
  mock(GitCommitNotifier::Git).changed_files('7e4f6b4', '4f13525') { [] }
130
130
  mock(GitCommitNotifier::Git).log(REVISIONS.first, REVISIONS.last) { IO.read(FIXTURES_PATH + 'git_log') }
131
131
  REVISIONS.each do |rev|
132
- mock(GitCommitNotifier::Git).show(rev, true) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
132
+ mock(GitCommitNotifier::Git).show(rev, :ignore_whitespaces => true) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
133
133
  end
134
-
134
+
135
135
  diff = GitCommitNotifier::DiffToHtml.new
136
136
  mock(diff).check_handled_commits(anything) { |commits| commits }
137
137
  diff.diff_between_revisions REVISIONS.first, REVISIONS.last, 'testproject', 'master'
@@ -142,22 +142,16 @@ describe GitCommitNotifier::DiffToHtml do
142
142
  html.should_not be_include('@@') # diff correctly processed
143
143
  end
144
144
 
145
- # first commit
146
- hp = Nokogiri::HTML diff.result.first[:html_content]
147
- (hp/"table").should have(2).tables # 2 files updated - one table for each of the files
145
+ # second commit - 51b986619d88f7ba98be7d271188785cbbb541a0
146
+ hp = Nokogiri::HTML diff.result[1][:html_content]
147
+ (hp/"table").should have(3).tables # 3 files updated
148
148
  (hp/"table"/"tr"/"td").each do |td|
149
- if td.inner_html == "require&nbsp;'iconv'"
150
- # first added line in changeset a4629e707d80a5769f7a71ca6ed9471015e14dc9
151
- td.parent.search('td')[0].inner_text.should == '' # left
152
- td.parent.search('td')[1].inner_text.should == '2' # right
153
- td.parent.search('td')[2].inner_html.should == "require&nbsp;'iconv'" # change
149
+ if td.inner_html =~ /create_btn/
150
+ cols = td.parent.search('td')
151
+ ['405', '408', ''].should be_include(cols[0].inner_text) # line 405 changed
154
152
  end
155
153
  end
156
154
 
157
- # second commit
158
- hp = Nokogiri::HTML diff.result[1][:html_content]
159
- (hp/"table").should have(1).table # 1 file updated
160
-
161
155
  # third commit - dce6ade4cdc2833b53bd600ef10f9bce83c7102d
162
156
  hp = Nokogiri::HTML diff.result[2][:html_content]
163
157
  (hp/"h2").should have(6).headers # 6 files in commit
@@ -167,13 +161,19 @@ describe GitCommitNotifier::DiffToHtml do
167
161
  (hp/"h2")[3].inner_text.should == 'Deleted file railties/doc/guides/source/icons/README'
168
162
  (hp/"h2")[4].inner_text.should == 'Added file railties/doc/guides/source/images/icons/README'
169
163
 
170
- # fourth commit - 51b986619d88f7ba98be7d271188785cbbb541a0
164
+ # fourth commit
171
165
  hp = Nokogiri::HTML diff.result[3][:html_content]
172
- (hp/"table").should have(3).tables # 3 files updated
166
+ (hp/"table").should have(1).table # 1 file updated
167
+
168
+ # fifth commit
169
+ hp = Nokogiri::HTML diff.result[4][:html_content]
170
+ (hp/"table").should have(2).tables # 2 files updated - one table for each of the files
173
171
  (hp/"table"/"tr"/"td").each do |td|
174
- if td.inner_html =~ /create_btn/
175
- cols = td.parent.search('td')
176
- ['405', '408', ''].should be_include(cols[0].inner_text) # line 405 changed
172
+ if td.inner_html == "require&nbsp;'iconv'"
173
+ # first added line in changeset a4629e707d80a5769f7a71ca6ed9471015e14dc9
174
+ td.parent.search('td')[0].inner_text.should == '' # left
175
+ td.parent.search('td')[1].inner_text.should == '2' # right
176
+ td.parent.search('td')[2].inner_html.should == "require&nbsp;'iconv'" # change
177
177
  end
178
178
  end
179
179
  end
@@ -182,7 +182,7 @@ describe GitCommitNotifier::DiffToHtml do
182
182
  first_rev, last_rev = %w[ 0000000000000000000000000000000000000000 9b15cebcc5434e27c00a4a2acea43509f9faea21 ]
183
183
  mock(GitCommitNotifier::Git).branch_commits('rvm') { %w[ ff037a73fc1094455e7bbf506171a3f3cf873ae6 ] }
184
184
  %w[ ff037a73fc1094455e7bbf506171a3f3cf873ae6 ].each do |rev|
185
- mock(GitCommitNotifier::Git).show(rev, true) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
185
+ mock(GitCommitNotifier::Git).show(rev, :ignore_whitespaces => true) { IO.read(FIXTURES_PATH + 'git_show_' + rev) }
186
186
  end
187
187
  diff = GitCommitNotifier::DiffToHtml.new
188
188
  mock(diff).check_handled_commits(anything) { |commits| commits }
@@ -15,18 +15,18 @@ describe GitCommitNotifier::Git do
15
15
  it "should get data from shell: git show without whitespaces" do
16
16
  expected = 'some data from git show'
17
17
  mock(GitCommitNotifier::Git).from_shell("git show #{SAMPLE_REV} --pretty=fuller -w") { expected }
18
- GitCommitNotifier::Git.show(SAMPLE_REV, true).should == expected
18
+ GitCommitNotifier::Git.show(SAMPLE_REV, :ignore_whitespaces => true).should == expected
19
19
  end
20
20
 
21
21
  it "should get data from shell: git show with whitespaces" do
22
22
  expected = 'some data from git show'
23
23
  mock(GitCommitNotifier::Git).from_shell("git show #{SAMPLE_REV} --pretty=fuller") { expected }
24
- GitCommitNotifier::Git.show(SAMPLE_REV, false).should == expected
24
+ GitCommitNotifier::Git.show(SAMPLE_REV, :ignore_whitespaces => false).should == expected
25
25
  end
26
26
 
27
27
  it "should strip given revision" do
28
28
  mock(GitCommitNotifier::Git).from_shell("git show #{SAMPLE_REV} --pretty=fuller -w")
29
- GitCommitNotifier::Git.show("#{SAMPLE_REV}\n", true)
29
+ GitCommitNotifier::Git.show("#{SAMPLE_REV}\n", :ignore_whitespaces => true)
30
30
  end
31
31
  end
32
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-commit-notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.5
4
+ version: 0.11.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-11-06 00:00:00.000000000 Z
13
+ date: 2011-12-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: diff-lcs
17
- requirement: &70277632158420 !ruby/object:Gem::Requirement
17
+ requirement: &70202081729720 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 1.1.2
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70277632158420
25
+ version_requirements: *70202081729720
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: nntp
28
- requirement: &70277632157800 !ruby/object:Gem::Requirement
28
+ requirement: &70202081729240 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70277632157800
36
+ version_requirements: *70202081729240
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: premailer
39
- requirement: &70277632157260 !ruby/object:Gem::Requirement
39
+ requirement: &70202081728720 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -50,10 +50,10 @@ dependencies:
50
50
  version: 1.7.2
51
51
  type: :runtime
52
52
  prerelease: false
53
- version_requirements: *70277632157260
53
+ version_requirements: *70202081728720
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: nokogiri
56
- requirement: &70277632156160 !ruby/object:Gem::Requirement
56
+ requirement: &70202081727760 !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
59
  - - ~>
@@ -61,10 +61,10 @@ dependencies:
61
61
  version: '1.4'
62
62
  type: :runtime
63
63
  prerelease: false
64
- version_requirements: *70277632156160
64
+ version_requirements: *70202081727760
65
65
  - !ruby/object:Gem::Dependency
66
66
  name: rake
67
- requirement: &70277632155540 !ruby/object:Gem::Requirement
67
+ requirement: &70202081727220 !ruby/object:Gem::Requirement
68
68
  none: false
69
69
  requirements:
70
70
  - - ~>
@@ -75,10 +75,10 @@ dependencies:
75
75
  version: 0.9.0
76
76
  type: :development
77
77
  prerelease: false
78
- version_requirements: *70277632155540
78
+ version_requirements: *70202081727220
79
79
  - !ruby/object:Gem::Dependency
80
80
  name: bundler
81
- requirement: &70277632154480 !ruby/object:Gem::Requirement
81
+ requirement: &70202081726400 !ruby/object:Gem::Requirement
82
82
  none: false
83
83
  requirements:
84
84
  - - ~>
@@ -89,10 +89,10 @@ dependencies:
89
89
  version: 1.0.10
90
90
  type: :development
91
91
  prerelease: false
92
- version_requirements: *70277632154480
92
+ version_requirements: *70202081726400
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: code-cleaner
95
- requirement: &70277632153000 !ruby/object:Gem::Requirement
95
+ requirement: &70202081725280 !ruby/object:Gem::Requirement
96
96
  none: false
97
97
  requirements:
98
98
  - - ! '>='
@@ -100,10 +100,10 @@ dependencies:
100
100
  version: '0'
101
101
  type: :development
102
102
  prerelease: false
103
- version_requirements: *70277632153000
103
+ version_requirements: *70202081725280
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: rspec-core
106
- requirement: &70277632152400 !ruby/object:Gem::Requirement
106
+ requirement: &70202081724720 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ! '>='
@@ -111,10 +111,10 @@ dependencies:
111
111
  version: '0'
112
112
  type: :development
113
113
  prerelease: false
114
- version_requirements: *70277632152400
114
+ version_requirements: *70202081724720
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: rspec-expectations
117
- requirement: &70277632151660 !ruby/object:Gem::Requirement
117
+ requirement: &70202081724240 !ruby/object:Gem::Requirement
118
118
  none: false
119
119
  requirements:
120
120
  - - ! '>='
@@ -122,10 +122,10 @@ dependencies:
122
122
  version: '0'
123
123
  type: :development
124
124
  prerelease: false
125
- version_requirements: *70277632151660
125
+ version_requirements: *70202081724240
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rr
128
- requirement: &70277632150500 !ruby/object:Gem::Requirement
128
+ requirement: &70202081723720 !ruby/object:Gem::Requirement
129
129
  none: false
130
130
  requirements:
131
131
  - - ~>
@@ -133,10 +133,10 @@ dependencies:
133
133
  version: '1.0'
134
134
  type: :development
135
135
  prerelease: false
136
- version_requirements: *70277632150500
136
+ version_requirements: *70202081723720
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: faker
139
- requirement: &70277632243320 !ruby/object:Gem::Requirement
139
+ requirement: &70202081723240 !ruby/object:Gem::Requirement
140
140
  none: false
141
141
  requirements:
142
142
  - - ~>
@@ -144,10 +144,10 @@ dependencies:
144
144
  version: 0.9.5
145
145
  type: :development
146
146
  prerelease: false
147
- version_requirements: *70277632243320
147
+ version_requirements: *70202081723240
148
148
  - !ruby/object:Gem::Dependency
149
149
  name: yard
150
- requirement: &70277632241680 !ruby/object:Gem::Requirement
150
+ requirement: &70202081722760 !ruby/object:Gem::Requirement
151
151
  none: false
152
152
  requirements:
153
153
  - - ~>
@@ -155,10 +155,10 @@ dependencies:
155
155
  version: 0.7.3
156
156
  type: :development
157
157
  prerelease: false
158
- version_requirements: *70277632241680
158
+ version_requirements: *70202081722760
159
159
  - !ruby/object:Gem::Dependency
160
160
  name: redcarpet
161
- requirement: &70277632240700 !ruby/object:Gem::Requirement
161
+ requirement: &70202081858160 !ruby/object:Gem::Requirement
162
162
  none: false
163
163
  requirements:
164
164
  - - ~>
@@ -166,7 +166,7 @@ dependencies:
166
166
  version: 1.17.2
167
167
  type: :development
168
168
  prerelease: false
169
- version_requirements: *70277632240700
169
+ version_requirements: *70202081858160
170
170
  description: This git commit notifier sends html mails with nice diffs for every changed
171
171
  file.
172
172
  email: bodo@bitboxer.de
@@ -223,7 +223,7 @@ files:
223
223
  - spec/spec_helper.rb
224
224
  - template/email.html.erb
225
225
  - template/styles.css
226
- homepage: http://github.com/bitboxer/git-commit-notifier
226
+ homepage: http://bitboxer.github.com/git-commit-notifier/
227
227
  licenses: []
228
228
  post_install_message:
229
229
  rdoc_options: []
@@ -237,7 +237,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
237
237
  version: '0'
238
238
  segments:
239
239
  - 0
240
- hash: -486808045600569914
240
+ hash: -3538399353583787974
241
241
  required_rubygems_version: !ruby/object:Gem::Requirement
242
242
  none: false
243
243
  requirements:
@@ -246,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
246
  version: '0'
247
247
  requirements: []
248
248
  rubyforge_project:
249
- rubygems_version: 1.8.11
249
+ rubygems_version: 1.8.10
250
250
  signing_key:
251
251
  specification_version: 3
252
252
  summary: Sends git commit messages with diffs