]> git.pld-linux.org Git - packages/cvsspam.git/blob - cvsspam-branch.diff
drop ruby_mod_ver_requires_eq
[packages/cvsspam.git] / cvsspam-branch.diff
1 Index: cvsspam.conf
2 ===================================================================
3 --- cvsspam.conf        (.../tags/RELEASE-0_2_12)       (revision 277)
4 +++ cvsspam.conf        (.../trunk)     (revision 277)
5 @@ -34,11 +34,19 @@
6  #
7  #     When $jiraURL is given, text of the form 'project-1234' will be linked
8  #   to this issue in JIRA.
9 +#
10 +#     When $xplannerStoryURL, $xplannerIterationURL and $xplannerProjectURL are
11 +#   given, text of the form XS1234 will be linked to XPlanner stories; text of
12 +#   the form XI1234 will be linked to XPlanner iterations; and text of the form
13 +#   XP1234 will be linked to XPlanner projects.
14  
15  #$bugzillaURL = "http://bugzilla.mozilla.org/show_bug.cgi?id=%s"
16  
17  #$jiraURL = "http://jira.atlassian.com/secure/ViewIssue.jspa?key=%s"
18  
19 +#$xplannerStoryURL = "http://www.example.com/xplanner/do/view/userstory?oid=%s"
20 +#$xplannerIterationURL = "http://www.example.com/xplanner/do/view/iteration?oid=%s"
21 +#$xplannerProjectURL = "http://www.example.com/xplanner/do/view/project?oid=%s"
22  
23  # Link to Wiki systems
24  # 
25 @@ -71,6 +79,7 @@
26  
27  #$cvswebURL = "http://localhost/cgi-bin/cvsweb.cgi"
28  
29 +#$tracURL = "http://localhost/trac/project"
30  
31  
32  # Additional SMTP Headers                                            (Optional)
33 @@ -116,15 +125,32 @@
34  
35  
36  
37 -# cvsdiff keyword ignoring                  (Default: show changes in keywords)
38 +# cvsdiff keyword ignoring                (Default: show changes in keywords)
39  #
40  #     Changes in CVS keywords can be distracting.  For instance, the
41 -#   $Revision: 1.12 $ keyword will change on each commit.  Set this value to true
42 +#   $Revision$ keyword will change on each commit.  Set this value to true
43  #   to exclude changes in keyword fields (adds the -kk option to cvs diff).
44  
45  #$diff_ignore_keywords = true
46  
47  
48 +# cvsdiff whitespace ignoring         (Default: show whitespace-only changes)
49 +#
50 +#   Whitespace-only changes can distract from the rest of a diff. Set this
51 +#   value to true to exclude changes in the amount of whitespace (adds the -b
52 +#   option to cvs diff).
53 +
54 +$diff_ignore_whitespace = true
55 +
56 +
57 +# cvs diff files ignoring                                    (Default: empty)
58 +#
59 +#   Make CVSspam ignore certain files.
60 +#
61 +#      Can contain file masks, separated by whitespace.
62 +
63 +#$ignore_files = "*.al *.gif"
64 +
65  # $no_removed_file_diff and $no_added_file_diff
66  #
67  #     Set both these options, and emails will only include diffs for files
68 @@ -132,7 +158,7 @@
69  #   deleted...
70  
71  
72 -# Don't show diff for removed files             (Default: show file's contents)
73 +# Don't show diff for removed files           (Default: show file's contents)
74  #
75  #     If you aren't interested in seeing the contents of a file that was
76  #   removed, set this option to true.  The files will still appear in the index
77 @@ -166,14 +192,46 @@
78  #     Allows the specification of a character set for all generated emails.
79  #   The files CVS is dealing with should already be in the character set you
80  #   specify -- no transcoding is done.
81 +#
82 +#   Note that you can override this with --charset argument per module, etc.
83  
84  #$charset="ISO-8859-1"
85  
86  
87  
88 +# Users file                                  (Default: $CVSROOT/CVSROOT/users)
89 +#
90 +#      Specify users file to lookup From addresses for commites
91 +
92 +#$users_file = "/srv/svn/users"
93 +
94 +# Users file charset                                        (Default: $charset)
95 +#
96 +#      If the users file is encoded differently than $charset, You can override
97 +#   it here. Especially useful if you use --charset argument. See above.
98 +
99 +#$users_file_charset = "ISO-8859-1"
100 +
101 +
102  # File names in Subject                      (Default: no filenames in Subject)
103  #
104  #     Some people like file names to appear in the email subject.  To make
105  #   them happy, you can say $files_in_subject = true here.
106  
107  #$files_in_subject = false
108 +
109 +
110 +# Module Path email header           (Default: no X-CVSspam-Module-Path header)
111 +#
112 +#     Sets 'X-CVSspam-Module-Path' header to contain common path of files commited.
113 +#   Useful for server side mail filtering.
114 +
115 +#$cvsroot_email_header = true
116 +
117 +# Email size limit                                        (Default: around 2MB)
118 +#
119 +#     When large changes are committed, large CVSspam emails can result.  Here
120 +#   you can set the size of email that CVSspam is not allowed to append any
121 +#   more diffs onto.  Specify the number of bytes.
122 +
123 +#$mail_size_limit = 2097152
124
125 Property changes on: cvsspam.conf
126 ___________________________________________________________________
127 Deleted: svn:executable
128    - *
129 Modified: svn:keywords
130    - Author Date Id Revision
131    + Author Date Id
132
133 Index: collect_diffs.rb
134 ===================================================================
135 --- collect_diffs.rb    (.../tags/RELEASE-0_2_12)       (revision 277)
136 +++ collect_diffs.rb    (.../trunk)     (revision 277)
137 @@ -26,7 +26,18 @@
138  $tmpdir = ENV["TMPDIR"] || "/tmp"
139  $dirtemplate = "#cvsspam.#{Process.getpgrp}.#{Process.uid}"
140  
141 +def shell_mask2regex(mask)
142 +  '^' + mask.gsub('.', '\.').gsub('?', '.').gsub('*', '.*') + '$'
143 +end
144 +
145  def find_data_dir
146 +  if $from_address
147 +    safe_from = make_fromaddr_safe_for_filename($from_address)
148 +    Dir["#{$tmpdir}/#{$dirtemplate}.#{safe_from}-*"].each do |dir|
149 +      stat = File.stat(dir)
150 +      return dir if stat.owned?
151 +    end
152 +  end
153    Dir["#{$tmpdir}/#{$dirtemplate}-*"].each do |dir|
154      stat = File.stat(dir)
155      return dir if stat.owned?
156 @@ -35,6 +46,14 @@
157  end
158  
159  
160 +# transform any special / unexpected characters appearing in the argument to
161 +# --from so that they will not cause problems if the value is inserted into
162 +# a file or directory name
163 +def make_fromaddr_safe_for_filename(addr)
164 +  addr.gsub(/[^a-zA-Z0-1.,_-]/, "_")
165 +end
166 +
167 +
168  def blah(msg)
169    if $debug
170      $stderr.puts "collect_diffs.rb: #{msg}"
171 @@ -81,11 +100,11 @@
172      File.open("#{$datadir}/commitinfo-tags") do |file|
173        $commitinfo_tags = Hash.new
174        file.each_line do |line|
175 -       line =~ /([^\t]+)\t(.+)/
176 -       key = $2
177 -       val = $1
178 -       key.sub!(/^#{ENV['CVSROOT']}\//, '')
179 -       $commitinfo_tags[key] = val
180 +        line =~ /([^\t]+)\t(.+)/
181 +        key = $2
182 +        val = $1
183 +        key.sub!(/^#{ENV['CVSROOT']}\//, '')
184 +        $commitinfo_tags[key] = val
185        end
186      end
187    end
188 @@ -129,7 +148,14 @@
189    changes = Array.new
190    i = 0
191    while i < cvs_info.length
192 -    changes << ChangeInfo.new(cvs_info[i], cvs_info[i+=1], cvs_info[i+=1])
193 +    change_file = cvs_info[i]
194 +    # It's been reported,
195 +    # http://lists.badgers-in-foil.co.uk/pipermail/cvsspam-devel/2005-September/000380.html
196 +    # that sometimes the second revision number that CVS gives us contains a
197 +    # trailing newline character, so we strip ws from these values before use,
198 +    change_from = cvs_info[i+=1].strip
199 +    change_to = cvs_info[i+=1].strip
200 +    changes << ChangeInfo.new(change_file, change_from, change_to)
201      i+=1
202    end
203    return changes
204 @@ -194,6 +220,8 @@
205  
206      changes.each do |change|
207  
208 +      next if $ignore_file_regexes and $ignore_file_regexes.any?{|r| change.file =~ /#{r}/}
209 +
210        # record version information
211        file.puts "#V #{change.fromVer},#{change.toVer}"
212  
213 @@ -202,7 +230,7 @@
214        # note if the file is on a branch
215        tag = nil
216        if change.isRemoval
217 -       tag = get_commitinfo_tag("#{$repository_path}/#{change.file}")
218 +        tag = get_commitinfo_tag("#{$repository_path}/#{change.file}")
219        else
220          status = nil
221          safer_popen($cvs_prog, "-nq", "status", change.file) do |io|
222 @@ -210,18 +238,19 @@
223          end
224          fail "couldn't get cvs status: #{$!} (exited with #{$?})" unless ($?>>8)==0
225  
226 -       if status =~ /^\s*Sticky Tag:\s*(.+) \(branch: +/m
227 -         tag = $1
228 -       end
229 +        if status =~ /^\s*Sticky Tag:\s*(.+) \(branch: +/m
230 +          tag = $1
231 +        end
232  
233 -       if status =~ /^\s*Sticky Options:\s*-kb/m
234 -         binary_file = true
235 -       end
236 +        if status =~ /^\s*Sticky Options:\s*-kb/m
237 +          binary_file = true
238 +        end
239        end
240        file.puts "#T #{tag}" unless tag.nil?
241  
242        diff_cmd = Array.new << $cvs_prog << "-nq" << "diff" << "-Nu"
243        diff_cmd << "-kk" if $diff_ignore_keywords
244 +      diff_cmd << "-b" if $diff_ignore_whitespace
245  
246        if change.isAddition
247          file.write "#A "
248 @@ -240,24 +269,24 @@
249        file.puts "#{$repository_path}/#{change.file}"
250        diff_cmd << change.file
251        if binary_file
252 -       blah("not diffing #{change.file}; has -kb set")
253 -       # fake diff lines that will cause cvsspam.rb to consider this a binary
254 -       # file,
255 -       file.puts "#U diff x x"
256 -       file.puts "#U Binary files x and y differ"
257 +        blah("not diffing #{change.file}; has -kb set")
258 +        # fake diff lines that will cause cvsspam.rb to consider this a binary
259 +        # file,
260 +        file.puts "#U diff x x"
261 +        file.puts "#U Binary files x and y differ"
262        else
263 -       # do a cvs diff and place the output into our temp file
264 -       blah("about to run #{diff_cmd.join(' ')}")
265 -       safer_popen(*diff_cmd) do |pipe|
266 -         # skip over cvs-diff's preamble
267 -         pipe.each do |line|
268 -           break if line =~ /^diff /
269 -         end
270 -         file.puts "#U #{line}"
271 -         pipe.each do |line|
272 -           file.puts "#U #{line}"
273 -         end
274 -       end
275 +        # do a cvs diff and place the output into our temp file
276 +        blah("about to run #{diff_cmd.join(' ')}")
277 +        safer_popen(*diff_cmd) do |pipe|
278 +          # skip over cvs-diff's preamble
279 +          pipe.each do |line|
280 +            break if line =~ /^diff /
281 +          end
282 +          file.puts "#U #{line}"
283 +          pipe.each do |line|
284 +            file.puts "#U #{line}"
285 +          end
286 +        end
287        end
288        # TODO: don't how to do this reliably on different systems...
289        #fail "cvsdiff did not give exit status 1 for invocation: #{diff_cmd.join(' ')}" unless ($?>>8)==1
290 @@ -333,10 +362,13 @@
291  end
292  
293  $config = nil
294 +$from_address = nil
295  $cvs_prog = "cvs"
296  $debug = false
297  $diff_ignore_keywords = false
298 +$diff_ignore_whitespace = false
299  $task_keywords = []
300 +$ignore_file_regexes = nil
301  
302  unless ENV.has_key?('CVSROOT')
303    fail "$CVSROOT not defined.  It should be when I am invoked from CVSROOT/loginfo"
304 @@ -387,6 +419,7 @@
305    end
306    $config = arg if opt=="--config"
307    $debug = true if opt == "--debug"
308 +  $from_address = arg if opt == "--from"
309  end
310  
311  blah("CVSROOT is #{ENV['CVSROOT']}")
312 @@ -426,6 +459,9 @@
313      class GUESS
314      end
315      load $config
316 +    if $ignore_files
317 +      $ignore_file_regexes = $ignore_files.split(/\s+/).map{|i| shell_mask2regex(i)}
318 +    end
319    else
320      blah("Config file '#{$config}' not found, ignoring")
321    end
322 @@ -447,3 +483,5 @@
323    process_log(ARGV[0])
324  end
325  mailtest
326 +
327 +# vim:et:ts=2:sw=2
328 Index: record_lastdir.rb
329 ===================================================================
330 --- record_lastdir.rb   (.../tags/RELEASE-0_2_12)       (revision 277)
331 +++ record_lastdir.rb   (.../trunk)     (revision 277)
332 @@ -4,7 +4,6 @@
333  #   http://www.badgers-in-foil.co.uk/projects/cvsspam/
334  # Copyright (c) David Holroyd
335  
336 -$repositorydir = ARGV.shift
337  
338  $tmpdir = ENV["TMPDIR"] || "/tmp"
339  
340 @@ -19,6 +18,36 @@
341    nil
342  end
343  
344 +
345 +# transform any special / unexpected characters appearing in the argument to
346 +# --from so that they will not cause problems if the value is inserted into
347 +# a file or directory name
348 +def make_fromaddr_safe_for_filename(addr)
349 +  addr.gsub(/[^a-zA-Z0-1.,_-]/, "_")
350 +end
351 +
352 +# Option processing doesn't use GetoptLong (for the moment) bacause arguments
353 +# given to this script by CVS include the names of committed files.  It
354 +# seems quite possible that one of those file names could begin with a '-'
355 +# and therefore be treated by GetoptLong as a value which requires processing.
356 +# This would probably result in an error.
357 +#
358 +# [That could be worked around by placing a '--' option (which tells GetoptLong
359 +# to stop processing option arguments) at the very end of the arguments to
360 +# record_lastdir.rb in commitinfo, but that's very easily forgotten, and isn't
361 +# really backwards compatable with the behaviour of older CVSspam releases.]
362 +if ARGV.first == "--from"
363 +  # we could, of course, be tricked, if the first committed file in the list
364 +  # happened to be named '--from' :S
365 +
366 +  # drop the "--from"
367 +  ARGV.shift
368 +  # and use the value which was given following the option,
369 +  $dirtemplate << "." << make_fromaddr_safe_for_filename(ARGV.shift)
370 +end
371 +
372 +$repositorydir = ARGV.shift
373 +
374  $datadir = find_data_dir()
375  
376  if $datadir==nil
377 @@ -78,5 +107,7 @@
378  # email yet.
379  
380  File.open("#{$datadir}/lastdir", "w") { |file|
381 -       file.write $repositorydir
382 +  file.write $repositorydir
383  }
384 +
385 +# vim:et:ts=2:sw=2
386
387 Property changes on: TODO
388 ___________________________________________________________________
389 Deleted: svn:executable
390    - *
391
392 Index: project.xml
393 ===================================================================
394 Index: svn_post_commit_hook.rb
395 ===================================================================
396 --- svn_post_commit_hook.rb     (.../tags/RELEASE-0_2_12)       (revision 0)
397 +++ svn_post_commit_hook.rb     (.../trunk)     (revision 277)
398 @@ -0,0 +1,412 @@
399 +#!/usr/bin/ruby -w
400 +
401 +$svnlook_exe = "svnlook"  # default assumes the program is in $PATH
402 +
403 +def usage(msg)
404 +  $stderr.puts(msg)
405 +  exit(1)
406 +end
407 +
408 +def blah(msg)
409 +  if $debug
410 +    $stderr.puts "svn_post_commit_hook.rb: #{msg}"
411 +  end
412 +end
413 +
414 +$debug = false
415 +$tmpdir = ENV["TMPDIR"] || "/tmp"
416 +$dirtemplate = "#svnspam.#{Process.getpgrp}.#{Process.uid}"
417 +# arguments to pass though to 'cvsspam.rb'
418 +$passthrough_args = []
419 +
420 +def make_data_dir
421 +  dir = "#{$tmpdir}/#{$dirtemplate}-#{rand(99999999)}"
422 +  Dir.mkdir(dir, 0700)
423 +  dir
424 +end
425 +
426 +def init
427 +  $datadir = make_data_dir
428 +
429 +  # set PWD so that svnlook can create its .svnlook directory
430 +  Dir.chdir($datadir)
431 +end
432 +
433 +def cleanup
434 +  unless $debug
435 +    File.unlink("#{$datadir}/logfile")
436 +    Dir.rmdir($datadir)
437 +  end
438 +end
439 +
440 +def send_email
441 +  cmd = File.dirname($0) + "/cvsspam.rb"
442 +  unless system(cmd,"--svn","#{$datadir}/logfile", *$passthrough_args)
443 +    fail "problem running '#{cmd}'"
444 +  end
445 +end
446 +
447 +# Like IO.popen, but accepts multiple arguments like Kernel.exec
448 +# (So no need to escape shell metacharacters)
449 +def safer_popen(*args)
450 +  IO.popen("-") do |pipe|
451 +    if pipe==nil
452 +      exec(*args)
453 +    else
454 +      yield pipe
455 +    end
456 +  end
457 +end
458 +
459 +
460 +# Process the command-line arguments in the given list
461 +def process_args
462 +  require 'getoptlong'
463 +
464 +  opts = GetoptLong.new(
465 +    [ "--to",     "-t", GetoptLong::REQUIRED_ARGUMENT ],
466 +    [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
467 +    [ "--debug",  "-d", GetoptLong::NO_ARGUMENT ],
468 +    [ "--from",   "-u", GetoptLong::REQUIRED_ARGUMENT ],
469 +    [ "--charset",      GetoptLong::REQUIRED_ARGUMENT ]
470 +  )
471 +
472 +  opts.each do |opt, arg|
473 +    if ["--to", "--config", "--from", "--charset"].include?(opt)
474 +      $passthrough_args << opt << arg
475 +    end
476 +    if ["--debug"].include?(opt)
477 +      $passthrough_args << opt
478 +    end
479 +    $config = arg if opt=="--config"
480 +    $debug = true if opt == "--debug"
481 +  end
482 +
483 +  $repository = ARGV[0]
484 +  $revision = ARGV[1]
485 +
486 +  unless $revision =~ /^\d+$/
487 +    usage("revision must be an integer: #{revision.inspect}")
488 +  end
489 +  $revision = $revision.to_i
490 +
491 +  unless FileTest.directory?($repository)
492 +    usage("no such directory: #{$repository.inspect}")
493 +  end
494 +end
495 +
496 +# runs the given svnlook subcommand
497 +def svnlook(cmd, revision, *args)
498 +  rev = revision.to_s
499 +  safer_popen($svnlook_exe, cmd, $repository, "-r", rev, *args) do |io|
500 +    yield io
501 +  end
502 +end
503 +
504 +class Change
505 +  def initialize(filechange, propchange, path)
506 +    @filechange = filechange
507 +    @propchange = propchange
508 +    @path = path
509 +  end
510 +
511 +  attr_accessor :filechange, :propchange, :path
512 +
513 +  def property_change?
514 +    @propchange != " "
515 +  end
516 +
517 +  def file_change?
518 +    @filechange != "_"
519 +  end
520 +
521 +  def addition?
522 +    @filechange == "A"
523 +  end
524 +
525 +  def deletion?
526 +    @filechange == "D"
527 +  end
528 +end
529 +
530 +
531 +
532 +# Line-oriented access to an underlying IO object.  Remembers 'current' line
533 +# for lookahead during parsing.
534 +class LineReader
535 +  def initialize(io)
536 +    @io = io
537 +  end
538 +
539 +  def current
540 +    @line
541 +  end
542 +
543 +  def next_line
544 +    (@line = @io.gets) != nil
545 +  end
546 +
547 +  def assert_current(re)
548 +    raise "unexpected #{current.inspect}" unless @line =~ re
549 +    $~
550 +  end
551 +
552 +  def assert_next(re=nil)
553 +    raise "unexpected end of text" unless next_line
554 +    unless re.nil?
555 +      raise "unexpected #{current.inspect}" unless @line =~ re
556 +    end
557 +    $~
558 +  end
559 +end
560 +
561 +
562 +def read_modified_diff(out, lines, path)
563 +  lines.assert_next(/^=+$/)
564 +  lines.assert_next
565 +  if lines.current =~ /\(Binary files differ\)/
566 +    process_modified_binary_diff(out, lines, path)
567 +  else
568 +    process_modified_text_diff(out, lines, path)
569 +  end
570 +end
571 +
572 +
573 +def process_modified_binary_diff(out, lines, path)
574 +  prev_rev= $revision-1
575 +  next_rev= $revision
576 +  out.puts "#V #{prev_rev},#{next_rev}"
577 +  out.puts "#M #{path}"
578 +  out.puts "#U diff x x"
579 +  out.puts "#U Binary files x and y differ"
580 +end
581 +
582 +
583 +def process_modified_text_diff(out, lines, path)
584 +  m = lines.assert_current(/^---.*\(rev (\d+)\)$/)
585 +  prev_rev = m[1].to_i
586 +  diff1 = lines.current
587 +  m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
588 +  next_rev = m[1].to_i
589 +  diff2 = lines.current
590 +  out.puts "#V #{prev_rev},#{next_rev}"
591 +  out.puts "#M #{path}"
592 +  out.puts "#U #{diff1}"
593 +  out.puts "#U #{diff2}"
594 +  while lines.next_line && lines.current =~ /^[-\+ @\\]/
595 +    out.puts "#U #{lines.current}"
596 +  end
597 +end
598 +
599 +def read_added_diff(out, lines, path)
600 +  lines.assert_next(/^=+$/)
601 +  lines.assert_next
602 +  if lines.current =~ /\(Binary files differ\)/
603 +    process_added_binary_diff(out, lines, path)
604 +  else
605 +    process_added_text_diff(out, lines, path)
606 +  end
607 +end
608 +
609 +def process_added_binary_diff(out, lines, path)
610 +  next_rev= $revision
611 +  out.puts "#V NONE,#{next_rev}"
612 +  out.puts "#A #{path}"
613 +  out.puts "#U diff x x"
614 +  out.puts "#U Binary file x added"
615 +end
616 +
617 +def process_added_text_diff(out, lines, path)
618 +  m = lines.assert_current(/^---.*\(rev (\d+)\)$/)
619 +  prev_rev = m[1].to_i
620 +  diff1 = lines.current
621 +  m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
622 +  next_rev = m[1].to_i
623 +  diff2 = lines.current
624 +  out.puts "#V NONE,#{next_rev}"
625 +  out.puts "#A #{path}"
626 +  out.puts "#U #{diff1}"
627 +  out.puts "#U #{diff2}"
628 +  while lines.next_line && lines.current =~ /^[-\+ @\\]/
629 +    out.puts "#U #{lines.current}"
630 +  end
631 +end
632 +
633 +def read_deleted_diff(out, lines, path)
634 +  lines.assert_next(/^=+$/)
635 +  m = lines.assert_next(/^---.*\(rev (\d+)\)$/)
636 +  prev_rev = m[1].to_i
637 +  diff1 = lines.current
638 +  m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
639 +  next_rev = m[1].to_i
640 +  diff2 = lines.current
641 +  out.puts "#V #{prev_rev},NONE"
642 +  out.puts "#R #{path}"
643 +  out.puts "#U #{diff1}"
644 +  out.puts "#U #{diff2}"
645 +  while lines.next_line && lines.current =~ /^[-\+ @\\]/
646 +    out.puts "#U #{lines.current}"
647 +  end
648 +end
649 +
650 +def read_property_lines(path, prop_name, revision)
651 +  lines = []
652 +  svnlook("propget", revision, prop_name, path) do |io|
653 +    io.each_line do |line|
654 +      lines << line.chomp
655 +    end
656 +  end
657 +  lines
658 +end
659 +
660 +def assert_prop_match(a, b)
661 +  if !b.nil? && a != b
662 +    raise "property mismatch: #{a.inspect}!=#{b.inspect}"
663 +  end
664 +end
665 +
666 +# We need to read the property change from the output of svnlook, but have
667 +# a difficulty in that there's no unambiguous delimiter marking the end of
668 +# a potentially multi-line property value.  Therefore, we do a seperate
669 +# svn propget on the given file to get the value of the property on its own,
670 +# and then use that value as a guide as to how much data to read from the
671 +# svnlook output.
672 +def munch_prop_text(path, prop_name, revision, lines, line0)
673 +  prop = read_property_lines(path, prop_name, revision)
674 +  if prop.empty?
675 +    assert_prop_match(line0, "")
676 +    return
677 +  end
678 +  assert_prop_match(line0, prop.shift)
679 +  prop.each do |prop_line|
680 +    lines.assert_next
681 +    assert_prop_match(lines.current.chomp, prop_line)
682 +  end
683 +end
684 +
685 +def read_properties_changed(out, lines, path)
686 +  prev_rev= $revision-1
687 +  next_rev= $revision
688 +  lines.assert_next(/^_+$/)
689 +  return unless lines.next_line
690 +  out.puts "#V #{prev_rev},#{next_rev}"
691 +  out.puts "#P #{path}"
692 +# The first three get consumed and not highlighted
693 +  out.puts "#U "
694 +  out.puts "#U Property changes:"
695 +  out.puts "#U "
696 +
697 +  while true
698 +    break unless lines.current =~ /^(?:Name|Added|Deleted): (.+)$/
699 +
700 +    prop_name = $1
701 +    m = lines.assert_next(/^   ([-+]) (.*)/)
702 +    op = m[1]
703 +    line0 = m[2]
704 +    if op == "-"
705 +      munch_prop_text(path, prop_name, $revision-1, lines, line0)
706 +      if lines.next_line && lines.current =~ /^   \+ (.*)/
707 +        munch_prop_text(path, prop_name, $revision, lines, $1)
708 +        lines.next_line
709 +      end
710 +    else  # op == "+"
711 +      munch_prop_text(path, prop_name, $revision, lines, line0)
712 +      lines.next_line
713 +    end
714 +    out.puts "#U #{m[1]} #{prop_name}:#{m[2]}"
715 +  end
716 +  out.puts "#U "
717 +end
718 +
719 +def handle_copy(out, lines, path, from_ref, from_file)
720 +  prev_rev= $revision-1
721 +  next_rev= $revision
722 +  out.puts "#V #{from_file}:#{prev_rev},#{next_rev}"
723 +  out.puts "#C #{path}"
724 +  if lines.next_line && lines.current =~ /^=+$/
725 +    m = lines.assert_next(/^---.*\(rev (\d+)\)$/)
726 +    prev_rev = m[1].to_i
727 +    diff1 = lines.current
728 +    m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
729 +    next_rev = m[1].to_i
730 +    diff2 = lines.current
731 +    out.puts "#U #{diff1}"
732 +    out.puts "#U #{diff2}"
733 +    while lines.next_line && lines.current =~ /^[-\+ @\\]/
734 +      out.puts "#U #{lines.current}"
735 +    end
736 +  else
737 +    out.puts "#U "
738 +    out.puts "#U Copied from #{from_file}:#{from_ref}"
739 +    out.puts "#U "
740 +  end
741 +end
742 +
743 +def svnlook_author
744 +  svnlook("author", $revision) do |io|
745 +    return io.readline.chomp
746 +  end
747 +  nil
748 +end
749 +
750 +def find_author
751 +  return if $passthrough_args.include?("--from")
752 +  author = svnlook_author
753 +  if author
754 +    blah("Author from svnlook: '#{author}'")
755 +    $passthrough_args << "--from" << author
756 +  end
757 +end
758 +
759 +def process_svnlook_log(file)
760 +  svnlook("log", $revision) do |io|
761 +    io.each_line do |line|
762 +      file.puts("#> #{line}")
763 +    end
764 +  end
765 +end
766 +
767 +def process_svnlook_diff(file)
768 +  svnlook("diff", $revision) do |io|
769 +    lines = LineReader.new(io)
770 +    while lines.next_line
771 +      if lines.current =~ /^Modified:\s+(.*)/
772 +        read_modified_diff(file, lines, $1)
773 +      elsif lines.current =~ /^Added:\s+(.*)/
774 +        read_added_diff(file, lines, $1)
775 +      elsif lines.current =~ /^Copied:\s+(.*) \(from rev (\d+), (.*)\)$/
776 +        handle_copy(file, lines, $1, $2, $3)
777 +      elsif lines.current =~ /^Deleted:\s+(.*)/
778 +        read_deleted_diff(file, lines, $1)
779 +      elsif lines.current =~ /^Property changes on:\s+(.*)/
780 +        read_properties_changed(file, lines, $1)
781 +      elsif lines.current == "\n"
782 +        # ignore
783 +      else
784 +        raise "unable to parse line '#{lines.current.inspect}'"
785 +      end
786 +    end
787 +  end
788 +end
789 +
790 +def process_commit()
791 +  File.open("#{$datadir}/logfile", File::WRONLY|File::CREAT) do |file|
792 +    process_svnlook_log(file)
793 +    process_svnlook_diff(file)
794 +  end
795 +end
796 +
797 +
798 +def main
799 +  init()
800 +  process_args()
801 +  find_author()
802 +  process_commit()
803 +  send_email()
804 +  cleanup()
805 +end
806 +
807 +
808 +main
809 +
810 +# vim:et:ts=2:sw=2
811
812 Property changes on: svn_post_commit_hook.rb
813 ___________________________________________________________________
814 Added: svn:mergeinfo
815 Added: svn:executable
816    + *
817
818
819 Property changes on: COPYING
820 ___________________________________________________________________
821 Deleted: svn:executable
822    - *
823
824 Index: CREDITS
825 ===================================================================
826 --- CREDITS     (.../tags/RELEASE-0_2_12)       (revision 277)
827 +++ CREDITS     (.../trunk)     (revision 277)
828 @@ -29,3 +29,10 @@
829    Elan Ruusamäe
830    Steve Fox
831    Christopher Petro
832 +  Robin Getz
833 +  Glen Starrett
834 +  Jonathan Rafkind
835 +  Ryan Dlugosz
836 +  Steve Woodcock
837 +  Andy Selle
838 +  Charles Duffy
839 Index: cvsspam-doc.xml
840 ===================================================================
841 --- cvsspam-doc.xml     (.../tags/RELEASE-0_2_12)       (revision 277)
842 +++ cvsspam-doc.xml     (.../trunk)     (revision 277)
843 @@ -452,6 +452,23 @@
844  </screen></informalexample>
845        </para>
846    </section>
847 +
848 +  <section>
849 +    <title>RT</title>
850 +
851 +    <para>For Gforge, when a CVS log comment contains text like <userinput>Fix
852 +    for Bug [#123]</userinput>, or <userinput>Task [T456] ...</userinput>, the
853 +    text "[#123]" or "[T456]" will become a hyper-link to that Gforge page in
854 +    the generated email.  The format [#<replaceable>nnn</replaceable>] and
855 +    [T<replaceable>nnn</replaceable>] is taken from the existing plugin for
856 +    Gforge called cvstracker.</para>
857 +
858 +    <para>To enable, give your Gforge's URL in CVSspam's configuration file:
859 +<informalexample><screen>$gforgeBugURL = "http://gforge.org/tracker/index.php?func=detail&amp;aid=%s"
860 +$gforgeTaskURL = "http://gforge.org/pm/task.php?func=detailtask&amp;project_task_id=%s"</screen></informalexample>
861 +    The marker %s tells CVSspam where in the URL to put the bugId from the
862 +    log message.</para>
863 +  </section>
864  </section>
865  
866  <section><title>CVS Web Frontends</title>
867
868 Property changes on: cvsspam-doc.xml
869 ___________________________________________________________________
870 Deleted: svn:executable
871    - *
872
873 Index: cvsspam.rb
874 ===================================================================
875 --- cvsspam.rb  (.../tags/RELEASE-0_2_12)       (revision 277)
876 +++ cvsspam.rb  (.../trunk)     (revision 277)
877 @@ -20,11 +20,14 @@
878  
879  $version = "0.2.12"
880  
881 +require 'time'
882  
883  $maxSubjectLength = 200
884  $maxLinesPerDiff = 1000
885 -$maxDiffLineLength = 1000      # may be set to nil for no limit
886 -$charset = nil                 # nil implies 'don't specify a charset'
887 +# may be set to nil for no limit
888 +$maxDiffLineLength = 1000
889 +# nil implies 'don't specify a charset'
890 +$charset = nil
891  $mailSubject = ''
892  
893  def blah(text)
894 @@ -35,10 +38,6 @@
895    a<b ? a : b
896  end
897  
898 -# NB must ensure the time is UTC
899 -# (the Ruby Time object's strftime() doesn't supply a numeric timezone)
900 -DATE_HEADER_FORMAT = "%a, %d %b %Y %H:%M:%S +0000"
901 -
902  # Perform (possibly) multiple global substitutions on a string.
903  # the regexps given as keys must not use capturing subexpressions '(...)'
904  class MultiSub
905 @@ -48,7 +47,7 @@
906      @mash = Array.new
907      expr = nil
908      hash.each do |key,val|
909 -      if expr == nil ; expr="(" else expr<<"|(" end
910 +      if expr == nil ; expr="(" else expr << "|(" end
911        expr << key << ")"
912        @mash << val
913      end
914 @@ -116,6 +115,8 @@
915    UNDERSCORE = chr("_")
916    SPACE = chr(" ")
917    TAB = chr("\t")
918 +  HOOK = chr("?")
919 +  EQUALS = chr("=")
920  
921    # encode a header value according to the RFC-2047 quoted-printable spec,
922    # allowing non-ASCII characters to appear in header values, and wrapping
923 @@ -137,8 +138,8 @@
924    # return a string representing the given character-code in quoted-printable
925    # format
926    def quoted_encode_char(b)
927 -    if b>126 || b==UNDERSCORE || b==TAB
928 -      sprintf("=%02x", b)
929 +    if b>126 || b==UNDERSCORE || b==TAB || b==HOOK || b==EQUALS
930 +      sprintf("=%02X", b)
931      elsif b == SPACE
932        "_"
933      else
934 @@ -163,8 +164,9 @@
935  
936    # gives a string starting "=?", and including a charset specification, that
937    # marks the start of a quoted-printable character sequence
938 -  def marker_start_quoted
939 -    "=?#{@charset}?#{@encoding}?"
940 +  def marker_start_quoted(charset=nil)
941 +    charset = @charset if charset.nil?
942 +    "=?#{charset}?#{@encoding}?"
943    end
944  
945    # test to see of the given string contains non-ASCII characters
946 @@ -388,6 +390,7 @@
947  class FileEntry
948    def initialize(path)
949      @path = path
950 +    @fromVer = @toVer = nil
951      @lineAdditions = @lineRemovals = 0
952      @repository = Repository.get(path)
953      @repository.merge_common_prefix(basedir())
954 @@ -397,7 +400,7 @@
955  
956    # the full path and filename within the repository
957    attr_accessor :path
958 -  # the type of change committed 'M'=modified, 'A'=added, 'R'=removed
959 +  # the type of change committed 'M'=modified, 'A'=added, 'R'=removed, 'P'=properties, 'C'=copied
960    attr_accessor :type
961    # records number of 'addition' lines in diff output, once counted
962    attr_accessor :lineAdditions
963 @@ -452,17 +455,28 @@
964    def removal?
965      @type == "R"
966    end
967 -
968 +  
969    # was this file added during the commit?
970    def addition?
971      @type == "A"
972    end
973  
974 +  # was this file copied during the commit?
975 +  def copied?
976 +    @type == "C"
977 +  end
978 +
979    # was this file simply modified during the commit?
980    def modification?
981      @type == "M"
982    end
983 +  
984 +  # was this file simply modified during the commit?
985 +  def modifiedprops?
986 +    @type == "P"
987 +  end
988  
989 +
990    # passing true, this object remembers that a diff will appear in the email,
991    # passing false, this object remembers that no diff will appear in the email.
992    # Once the value is set, it will not be changed
993 @@ -533,6 +547,14 @@
994  # TODO: consolidate these into a nicer framework,
995  mailSub = proc { |match| "<a href=\"mailto:#{match}\">#{match}</a>" }
996  urlSub = proc { |match| "<a href=\"#{match}\">#{match}</a>" }
997 +gforgeTaskSub = proc { |match|
998 +  match =~ /([0-9]+)/
999 +  "<a href=\"#{$gforgeTaskURL.sub(/%s/, $1)}\">#{match}</a>"
1000 +}
1001 +gforgeBugSub = proc { |match|
1002 +  match =~ /([0-9]+)/
1003 +  "<a href=\"#{$gforgeBugURL.sub(/%s/, $1)}\">#{match}</a>"
1004 +}
1005  bugzillaSub = proc { |match|
1006    match =~ /([0-9]+)/
1007    "<a href=\"#{$bugzillaURL.sub(/%s/, $1)}\">#{match}</a>"
1008 @@ -544,15 +566,31 @@
1009    match =~ /([0-9]+)/
1010    "<a href=\"#{$ticketURL.sub(/%s/, $1)}\">#{match}</a>"
1011  }
1012 +issueSub = proc { |match|
1013 +  match =~ /([0-9]+)/
1014 +  "<a href=\"#{$issueURL.sub(/%s/, $1)}\">#{match}</a>"
1015 +}
1016  wikiSub = proc { |match| 
1017 -  match =~ /\[\[(.*)\]\]/
1018 +  match =~ /\[\[(.*?)\]\]/
1019    raw = $1
1020    "<a href=\"#{$wikiURL.sub(/%s/, urlEncode(raw))}\">[[#{raw}]]</a>"
1021  }
1022 +xplannerIterationSub = proc { |match|
1023 +  match =~ /([0-9]+)/
1024 +  "<a href=\"#{$xplannerIterationURL.sub(/%s/, $1)}\">#{match}</a>"
1025 +}
1026 +xplannerProjectSub = proc { |match|
1027 +  match =~ /([0-9]+)/
1028 +  "<a href=\"#{$xplannerProjectURL.sub(/%s/, $1)}\">#{match}</a>"
1029 +}
1030 +xplannerStorySub = proc { |match|
1031 +  match =~ /([0-9]+)/
1032 +  "<a href=\"#{$xplannerStoryURL.sub(/%s/, $1)}\">#{match}</a>"
1033 +}
1034  commentSubstitutions = {
1035 -               '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
1036 -               '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
1037 -               }
1038 +  '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
1039 +  '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
1040 +}
1041  
1042  # outputs commit log comment text supplied by LogReader as preformatted HTML
1043  class CommentHandler < LineConsumer
1044 @@ -670,6 +708,12 @@
1045    def diff(file)
1046      '-&gt;'
1047    end
1048 +
1049 +  # may be overridden by subclasses that are able to make a hyperlink to a
1050 +  # history log for a file
1051 +  def log(file)
1052 +    ''
1053 +  end
1054  end
1055  
1056  # Superclass for objects that can link to CVS frontends on the web (ViewCVS,
1057 @@ -710,6 +754,14 @@
1058      "<a href=\"#{diff_url(file)}\">#{super(file)}</a>"
1059    end
1060  
1061 +  def log(file)
1062 +    link = log_url(file)
1063 +    if link
1064 +      return "<span id=\"info\">(<a href=\"#{link}\">log</a>)</span>"
1065 +    end
1066 +    return nil
1067 +  end
1068 +
1069   protected
1070    def add_repo(url)
1071      if @repository_name
1072 @@ -722,6 +774,10 @@
1073        url
1074      end
1075    end
1076 +
1077 +  def log_url(file)
1078 +    nil
1079 +  end
1080  end
1081  
1082  # Link to ViewCVS
1083 @@ -745,6 +801,15 @@
1084    def diff_url(file)
1085      add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=#{file.fromVer}&amp;r2=#{file.toVer}")
1086    end
1087 +
1088 +  def log_url(file)
1089 +    if file.toVer
1090 +      log_anchor = "#rev#{file.toVer}"
1091 +    else
1092 +      log_anchor = ""
1093 +    end
1094 +    add_repo("#{@base_url}#{urlEncode(file.path)}#{log_anchor}")
1095 +  end
1096  end
1097  
1098  # Link to Chora, from the Horde framework
1099 @@ -767,9 +832,9 @@
1100  class CVSwebFrontend < WebFrontend
1101    def path_url(path, tag)
1102      if tag == nil
1103 -      add_repo(@base_url + urlEncode(path))
1104 +      add_repo(@base_url + urlEncode(path) + "/")
1105      else
1106 -      add_repo("#{@base_url}#{urlEncode(path)}?only_with_tag=#{urlEncode(tag)}")
1107 +      add_repo("#{@base_url}#{urlEncode(path)}/?only_with_tag=#{urlEncode(tag)}")
1108      end
1109    end
1110  
1111 @@ -780,9 +845,45 @@
1112    def diff_url(file)
1113      add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=text&amp;tr1=#{file.fromVer}&amp;r2=text&amp;tr2=#{file.toVer}&amp;f=h")
1114    end
1115 +
1116 +  protected
1117 +
1118 +  def log_url(file)
1119 +    if file.toVer
1120 +      log_anchor = "#rev#{file.toVer}"
1121 +    else
1122 +      log_anchor = ""
1123 +    end
1124 +    add_repo("#{@base_url}#{urlEncode(file.path)}#{log_anchor}")
1125 +  end
1126  end
1127  
1128 +# Link to Trac
1129 +class TracFrontend < WebFrontend
1130 +  def path_url(path, tag)
1131 +    add_repo("#{@base_url}browser/#{urlEncode(path)}")
1132 +  end
1133  
1134 +  def version_url(path, version)
1135 +    add_repo("#{@base_url}browser/#{urlEncode(path)}?rev=#{version}")
1136 +  end
1137 +
1138 +  def diff_url(file)
1139 +    add_repo("#{@base_url}changeset/#{file.toVer}")
1140 +  end
1141 +
1142 +  protected
1143 +
1144 +  def log_url(file)
1145 +    if file.toVer
1146 +      log_anchor = "?rev=#{file.toVer}"
1147 +    else
1148 +      log_anchor = ""
1149 +    end
1150 +    add_repo("#{@base_url}log/#{urlEncode(file.path)}#{log_anchor}")
1151 +  end
1152 +end
1153 +
1154  # in need of refactoring...
1155  
1156  # Note when LogReader finds record of a file that was added in this commit
1157 @@ -801,6 +902,15 @@
1158    end
1159  end
1160  
1161 +# Note when LogReader finds record of a file that was copied in this commit
1162 +class CopiedFileHandler < FileHandler
1163 +  def handleFile(file)
1164 +    file.type="C"
1165 +    file.fromVer=$fromVer
1166 +    file.toVer=$toVer
1167 +  end
1168 +end
1169 +
1170  # Note when LogReader finds record of a file that was modified in this commit
1171  class ModifiedFileHandler < FileHandler
1172    def handleFile(file)
1173 @@ -810,7 +920,16 @@
1174    end
1175  end
1176  
1177 +# Note when LogReader finds record of a file whose properties were modified in this commit
1178 +class ModifiedPropsFileHandler < FileHandler
1179 +  def handleFile(file)
1180 +    file.type="P"
1181 +    file.fromVer=$fromVer
1182 +    file.toVer=$toVer
1183 +  end
1184 +end
1185  
1186 +
1187  # Used by UnifiedDiffHandler to record the number of added and removed lines
1188  # appearing in a unidiff.
1189  class UnifiedDiffStats
1190 @@ -873,7 +992,10 @@
1191          addInfixSize = line.length - (prefixLen+suffixLen)
1192          oversize_change = deleteInfixSize*100/@lineJustDeleted.length>33 || addInfixSize*100/line.length>33
1193  
1194 -        if prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
1195 +        # avoid doing 'within-a-line highlighting' if a multibyte encoding
1196 +        # is suspected, as all the suffix/prefix stuff above is byte, not
1197 +        # character based
1198 +        if multibyte_encoding? || prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
1199            print(htmlEncode(@lineJustDeleted))
1200          else
1201            print(htmlEncode(@lineJustDeleted[0,prefixLen]))
1202 @@ -905,7 +1027,7 @@
1203          @lineJustDeleted = nil
1204        end
1205        shift(initial)
1206 -      if prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
1207 +      if multibyte_encoding? || prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
1208          encoded = htmlEncode(line)
1209        else
1210          encoded = htmlEncode(line[0,prefixLen]) +
1211 @@ -958,7 +1080,7 @@
1212      end
1213      shift(nil)
1214      if @truncatedLineCount>0
1215 -      println("<strong class=\"error\" title=\"#{@truncatedLineCount} lines truncated at column #{$maxDiffLineLength}\">[Note: Some over-long lines of diff output only partialy shown]</strong>")
1216 +      println("<strong class=\"error\" title=\"#{@truncatedLineCount} lines truncated at column #{$maxDiffLineLength}\">[Note: Some over-long lines of diff output only partially shown]</strong>")
1217      end
1218    end
1219  
1220 @@ -976,11 +1098,21 @@
1221          print($frontend.path($file.basedir, $file.tag))
1222          println("</span><br />")
1223          println("<div class=\"fileheader\" id=\"removed\"><big><b>#{htmlEncode($file.file)}</b></big> <small id=\"info\">removed after #{$frontend.version($file.path,$file.fromVer)}</small></div>")
1224 +      when "C"
1225 +        print("<span class=\"pathname\" id=\"copied\">")
1226 +        print($frontend.path($file.basedir, $file.tag))
1227 +        println("</span><br />")
1228 +        println("<div class=\"fileheader\" id=\"copied\"><big><b>#{htmlEncode($file.file)}</b></big> <small id=\"info\">copied from #{$frontend.version($file.path,$file.fromVer)}</small></div>")
1229        when "M"
1230          print("<span class=\"pathname\">")
1231          print($frontend.path($file.basedir, $file.tag))
1232          println("</span><br />")
1233          println("<div class=\"fileheader\"><big><b>#{htmlEncode($file.file)}</b></big> <small id=\"info\">#{$frontend.version($file.path,$file.fromVer)} #{$frontend.diff($file)} #{$frontend.version($file.path,$file.toVer)}</small></div>")
1234 +      when "P"
1235 +        print("<span class=\"pathname\">")
1236 +        print($frontend.path($file.basedir, $file.tag))
1237 +        println("</span><br />")
1238 +        println("<div class=\"fileheader\"><big><b>#{htmlEncode($file.file)}</b></big> <small id=\"info\">#{$frontend.version($file.path,$file.fromVer)} #{$frontend.diff($file)} #{$frontend.version($file.path,$file.toVer)}</small></div>")
1239      end
1240      print("<pre class=\"diff\"><small id=\"info\">")
1241      lines.each do |line|
1242 @@ -1078,7 +1210,7 @@
1243            @colour.teardown
1244          end
1245          println("</div>") # end of "file" div
1246 -       $file.has_diff = true
1247 +        $file.has_diff = true
1248        end
1249      end
1250    end
1251 @@ -1181,7 +1313,7 @@
1252  
1253  # an RFC 822 email address
1254  class EmailAddress
1255 -  def initialize(text)
1256 +  def initialize(text, charset=nil)
1257      if text =~ /^\s*([^<]+?)\s*<\s*([^>]+?)\s*>\s*$/
1258        @personal_name = $1
1259        @address = $2
1260 @@ -1189,9 +1321,10 @@
1261        @personal_name = nil
1262        @address = text
1263      end
1264 +    @charset=charset
1265    end
1266  
1267 -  attr_accessor :personal_name, :address
1268 +  attr_accessor :personal_name, :address, :charset
1269  
1270    def has_personal_name?
1271      return !@personal_name.nil?
1272 @@ -1222,9 +1355,9 @@
1273    # rfc2047 encode the word, if it contains non-ASCII characters
1274    def encode_word(word)
1275      if $encoder.requires_rfc2047?(word)
1276 -      encoded = $encoder.marker_start_quoted
1277 +      encoded = $encoder.marker_start_quoted(@charset)
1278        $encoder.each_char_encoded(word) do |code|
1279 -       encoded << code
1280 +        encoded << code
1281        end
1282        encoded << $encoder.marker_end_quoted
1283        return encoded
1284 @@ -1233,26 +1366,40 @@
1285    end
1286  end
1287  
1288 +# guess if the users selected encoding is multibyte, since some CVSspam code
1289 +# isn't multibyte-safe, and needs to be disabled.
1290 +def multibyte_encoding?
1291 +  $charset && ["utf-8", "utf-16"].include?($charset.downcase)
1292 +end
1293  
1294  cvsroot_dir = "#{ENV['CVSROOT']}/CVSROOT"
1295  $config = "#{cvsroot_dir}/cvsspam.conf"
1296  $users_file = "#{cvsroot_dir}/users"
1297 +$users_file_charset = nil
1298  
1299  $debug = false
1300 +$svn = false
1301  $recipients = Array.new
1302  $sendmail_prog = "/usr/sbin/sendmail"
1303  $hostname = ENV['HOSTNAME'] || 'localhost'
1304  $no_removed_file_diff = false
1305  $no_added_file_diff = false
1306  $no_diff = false
1307 -$task_keywords = ['TODO', 'FIXME']
1308 +$task_keywords = ['TODO', 'FIXME', 'FIXIT', 'todo']
1309  $bugzillaURL = nil
1310 +$gforgeBugURL = nil
1311 +$gforgeTaskURL = nil
1312  $wikiURL = nil
1313  $jiraURL = nil
1314  $ticketURL = nil
1315 +$issueURL = nil
1316  $viewcvsURL = nil
1317 +$xplannerIterationURL = nil
1318 +$xplannerProjectURL = nil
1319 +$xplannerStoryURL = nil
1320  $choraURL = nil
1321  $cvswebURL = nil
1322 +$tracURL = nil
1323  $from_address = nil
1324  $subjectPrefix = nil
1325  $files_in_subject = false;
1326 @@ -1261,6 +1408,7 @@
1327  # 2MiB limit on attached diffs,
1328  $mail_size_limit = 1024 * 1024 * 2
1329  $arg_charset = nil
1330 +$cvsroot_email_header = false
1331  
1332  require 'getoptlong'
1333  
1334 @@ -1268,6 +1416,7 @@
1335    [ "--to",     "-t", GetoptLong::REQUIRED_ARGUMENT ],
1336    [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
1337    [ "--debug",  "-d", GetoptLong::NO_ARGUMENT ],
1338 +  [ "--svn",    "-s", GetoptLong::NO_ARGUMENT ],
1339    [ "--from",   "-u", GetoptLong::REQUIRED_ARGUMENT ],
1340    [ "--charset",      GetoptLong::REQUIRED_ARGUMENT ]
1341  )
1342 @@ -1276,6 +1425,7 @@
1343    $recipients << EmailAddress.new(arg) if opt=="--to"
1344    $config = arg if opt=="--config"
1345    $debug = true if opt=="--debug"
1346 +  $svn = true if opt=="--svn"
1347    $from_address = EmailAddress.new(arg) if opt=="--from"
1348    # must use different variable as the config is readed later.
1349    $arg_charset = arg if opt == "--charset"
1350 @@ -1288,7 +1438,7 @@
1351    else
1352      $stderr.puts "missing required file argument"
1353    end
1354 -  puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>"
1355 +  puts "Usage: cvsspam.rb [ --svn ] [ --to <email> ] [ --config <file> ] <collect_diffs file>"
1356    exit(-1)
1357  end
1358  
1359 @@ -1321,6 +1471,8 @@
1360    blah("Config file '#{$config}' not found, ignoring")
1361  end
1362  
1363 +blah("Users file: '#{$users_file}'")
1364 +
1365  unless $arg_charset.nil?
1366    $charset = $arg_charset
1367  end
1368 @@ -1337,6 +1489,9 @@
1369  elsif $cvswebURL !=nil
1370    $cvswebURL << "/" unless $cvswebURL =~ /\/$/
1371    $frontend = CVSwebFrontend.new($cvswebURL)
1372 +elsif $tracURL !=nil
1373 +  $tracURL << "/" unless $tracURL =~ /\/$/
1374 +  $frontend = TracFrontend.new($tracURL)
1375  else
1376    $frontend = NoFrontend.new
1377  end
1378 @@ -1353,33 +1508,57 @@
1379  
1380  
1381  if $bugzillaURL != nil
1382 -  commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugzillaSub
1383 +  commentSubstitutions['\b[Bb](?:[Uu][Gg])?\s*[#:]?\s*\[?[0-9]+\]?'] = bugzillaSub
1384  end
1385 +if $gforgeBugURL != nil
1386 +  commentSubstitutions['\B\[#[0-9]+\]'] = gforgeBugSub
1387 +end
1388 +if $gforgeTaskURL != nil
1389 +  commentSubstitutions['\B\[[Tt][0-9]+\]'] = gforgeTaskSub
1390 +end
1391  if $jiraURL != nil
1392    commentSubstitutions['\b[a-zA-Z]+-[0-9]+\b'] = jiraSub
1393  end
1394  if $ticketURL != nil
1395    commentSubstitutions['\b[Tt][Ii][Cc][Kk][Ee][Tt]\s*#?[0-9]+\b'] = ticketSub
1396  end
1397 +if $issueURL != nil
1398 +  commentSubstitutions['\b[Ii][Ss][Ss][Uu][Ee]\s*#?[0-9]+\b'] = issueSub
1399 +end
1400  if $wikiURL != nil
1401    commentSubstitutions['\[\[.+\]\]'] = wikiSub
1402  end
1403 +if $xplannerIterationURL != nil
1404 +  commentSubstitutions['\bXI\[?[0-9]+\]?'] = xplannerIterationSub
1405 +end
1406 +if $xplannerProjectURL != nil
1407 +  commentSubstitutions['\bXP\[?[0-9]+\]?'] = xplannerProjectSub
1408 +end
1409 +if $xplannerStoryURL != nil
1410 +  commentSubstitutions['\bXS\[?[0-9]+\]?'] = xplannerStorySub
1411 +end
1412  $commentEncoder = MultiSub.new(commentSubstitutions)
1413  
1414  
1415  tagHandler = TagHandler.new
1416  
1417 -$handlers = Hash[">" => CommentHandler.new,
1418 -                "U" => UnifiedDiffHandler.new,
1419 -                "T" => tagHandler,
1420 -                "A" => AddedFileHandler.new,
1421 -                "R" => RemovedFileHandler.new,
1422 -                "M" => ModifiedFileHandler.new,
1423 -                "V" => VersionHandler.new]
1424 +$handlers = Hash[
1425 +  ">" => CommentHandler.new,
1426 +  "U" => UnifiedDiffHandler.new,
1427 +  "T" => tagHandler,
1428 +  "A" => AddedFileHandler.new,
1429 +  "R" => RemovedFileHandler.new,
1430 +  "C" => CopiedFileHandler.new,
1431 +  "M" => ModifiedFileHandler.new,
1432 +  "P" => ModifiedPropsFileHandler.new,
1433 +  "V" => VersionHandler.new
1434 +]
1435  
1436  $handlers["A"].setTagHandler(tagHandler)
1437  $handlers["R"].setTagHandler(tagHandler)
1438 +$handlers["C"].setTagHandler(tagHandler)
1439  $handlers["M"].setTagHandler(tagHandler)
1440 +$handlers["P"].setTagHandler(tagHandler)
1441  
1442  $fileEntries = Array.new
1443  $task_list = Array.new
1444 @@ -1404,7 +1583,11 @@
1445  end
1446  
1447  if $subjectPrefix == nil
1448 -  $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
1449 +  if $svn
1450 +    $subjectPrefix = "[SVN #{Repository.array.join(',')}]"
1451 +  else
1452 +    $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
1453 +  end
1454  end
1455  
1456  if $files_in_subject
1457 @@ -1451,6 +1634,8 @@
1458    #removed {background-color:#ffdddd;}
1459    #removedchars {background-color:#ff9999;font-weight:bolder;}
1460    tr.alt #removed {background-color:#f7cccc;}
1461 +  #copied {background-color:#ccccff;}
1462 +  tr.alt #copied {background-color:#bbbbf7;}
1463    #info {color:#888888;}
1464    #context {background-color:#eeeeee;}
1465    td {padding-left:.3em;padding-right:.3em;}
1466 @@ -1483,7 +1668,9 @@
1467  
1468    filesAdded = 0
1469    filesRemoved = 0
1470 +  filesCopied = 0
1471    filesModified  = 0
1472 +  filesModifiedProps  = 0
1473    totalLinesAdded = 0
1474    totalLinesRemoved = 0
1475    file_count = 0
1476 @@ -1492,24 +1679,26 @@
1477    $fileEntries.each do |file|
1478      unless file.repository == last_repository
1479        last_repository = file.repository
1480 -      mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 5 : 4}\">")
1481 +      mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 6 : 5}\">")
1482        if last_repository.has_multiple_tags
1483          mail.print("Mixed-tag commit")
1484        else
1485          mail.print("Commit")
1486        end
1487        mail.print(" in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
1488 -      if last_repository.trunk_only?
1489 -        mail.print("<span id=\"info\"> on MAIN</span>")
1490 -      else
1491 -        mail.print(" on ")
1492 -        tagCount = 0
1493 -        last_repository.each_tag do |tag|
1494 -          tagCount += 1
1495 -          if tagCount > 1
1496 -            mail.print tagCount<last_repository.tag_count ? ", " : " &amp; "
1497 +      if !$svn
1498 +        if last_repository.trunk_only?
1499 +          mail.print("<span id=\"info\"> on MAIN</span>")
1500 +        else
1501 +          mail.print(" on ")
1502 +          tagCount = 0
1503 +          last_repository.each_tag do |tag|
1504 +            tagCount += 1
1505 +            if tagCount > 1
1506 +              mail.print tagCount<last_repository.tag_count ? ", " : " &amp; "
1507 +            end
1508 +            mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
1509            end
1510 -          mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
1511          end
1512        end
1513        mail.puts("</td></tr>")
1514 @@ -1524,8 +1713,12 @@
1515        filesAdded += 1
1516      elsif file.removal?
1517        filesRemoved += 1
1518 +    elsif file.copied?
1519 +      filesCopied += 1
1520      elsif file.modification?
1521        filesModified += 1
1522 +    elsif file.modifiedprops?
1523 +      filesModifiedProps += 1
1524      end
1525      name = htmlEncode(file.name_after_common_prefix)
1526      slashPos = name.rindex("/")
1527 @@ -1545,17 +1738,29 @@
1528        name = "<span id=\"added\">#{name}</span>"
1529      elsif file.removal?
1530        name = "<span id=\"removed\">#{name}</span>"
1531 +    elsif file.copied?
1532 +      name = "<span id=\"copied\">#{name}</span>"
1533      end
1534 +    mail.print("<td>")
1535      if file.has_diff?
1536 -      mail.print("<td><tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a></tt></td>")
1537 +      mail.print("<tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a></tt>")
1538      else
1539 -      mail.print("<td><tt>#{prefix}#{name}</tt></td>")
1540 +      mail.print("<tt>#{prefix}#{name}</tt>")
1541      end
1542 -    if file.isEmpty
1543 -      mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
1544 +    mail.print(" #{$frontend.log(file)}")
1545 +    mail.print("</td>")
1546 +    if file.copied?
1547 +      mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[copied]</small></td>")
1548 +    elsif file.isEmpty
1549 +      mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[empty]</small></td>")
1550      elsif file.isBinary
1551 -      mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
1552 +      mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[binary]</small></td>")
1553      else
1554 +      if file.modifiedprops?
1555 +        mail.print("<td align=\"right\"><small id=\"info\">[props]</small></td>")
1556 +      else
1557 +        mail.print("<td></td>")
1558 +      end
1559        if file.lineAdditions>0
1560          totalLinesAdded += file.lineAdditions
1561          mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
1562 @@ -1582,15 +1787,19 @@
1563        mail.print("<td nowrap=\"nowrap\" align=\"right\">added #{$frontend.version(file.path,file.toVer)}</td>")
1564      elsif file.removal?
1565        mail.print("<td nowrap=\"nowrap\">#{$frontend.version(file.path,file.fromVer)} removed</td>")
1566 +    elsif file.copied?
1567 +      mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
1568      elsif file.modification?
1569        mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
1570 +    elsif file.modifiedprops?
1571 +      mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
1572      end
1573  
1574      mail.puts("</tr>")
1575    end
1576    if $fileEntries.size>1 && (totalLinesAdded+totalLinesRemoved)>0
1577      # give total number of lines added/removed accross all files
1578 -    mail.print("<tr><td></td>")
1579 +    mail.print("<tr><td></td><td></td>")
1580      if totalLinesAdded>0
1581        mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
1582      else
1583 @@ -1607,7 +1816,7 @@
1584    
1585    mail.puts("</table>")
1586  
1587 -  totalFilesChanged = filesAdded+filesRemoved+filesModified
1588 +  totalFilesChanged = filesAdded+filesRemoved+filesCopied+filesModified+filesModifiedProps
1589    if totalFilesChanged > 1
1590      mail.print("<small id=\"info\">")
1591      changeKind = 0
1592 @@ -1620,11 +1829,21 @@
1593        mail.print("#{filesRemoved} removed")
1594        changeKind += 1
1595      end
1596 +    if filesCopied>0
1597 +      mail.print(" + ") if changeKind>0
1598 +      mail.print("#{filesCopied} copied")
1599 +      changeKind += 1
1600 +    end
1601      if filesModified>0
1602        mail.print(" + ") if changeKind>0
1603        mail.print("#{filesModified} modified")
1604        changeKind += 1
1605      end
1606 +    if filesModifiedProps>0
1607 +      mail.print(" + ") if changeKind>0
1608 +      mail.print("#{filesModifiedProps} modified properties")
1609 +      changeKind += 1
1610 +    end
1611      mail.print(", total #{totalFilesChanged}") if changeKind > 1
1612      mail.puts(" files</small><br />")
1613    end
1614 @@ -1667,12 +1886,13 @@
1615  # CVSROOT/users file, if the file exists.  The argument is returned unchanged
1616  # if no alias is found.
1617  def sender_alias(email)
1618 +  blah("Lookup '#{email}' from users file")
1619    if File.exists?($users_file)
1620      File.open($users_file) do |io|
1621        io.each_line do |line|
1622          if line =~ /^([^:]+)\s*:\s*(['"]?)([^\n\r]+)(\2)/
1623            if email.address == $1
1624 -            return EmailAddress.new($3)
1625 +            return EmailAddress.new($3, $users_file_charset)
1626            end
1627          end
1628        end
1629 @@ -1686,6 +1906,8 @@
1630  # sensible header formatting, and for ensuring that the body is seperated
1631  # from the message headers by a blank line (as it is required to be).
1632  class MailContext
1633 +  ENCODE_HEADERS = ["Subject", "X-CVSspam-Module-Path"]
1634 +
1635    def initialize(io)
1636      @done_headers = false
1637      @io = io
1638 @@ -1695,8 +1917,8 @@
1639    # called
1640    def header(name, value)
1641      raise "headers already commited" if @done_headers
1642 -    if name == "Subject"
1643 -      $encoder.encode_header(@io, "Subject", value)
1644 +    if ENCODE_HEADERS.include?(name)
1645 +      $encoder.encode_header(@io, name, value)
1646      else
1647        @io.puts("#{name}: #{value}")
1648      end
1649 @@ -1769,7 +1991,7 @@
1650        ctx.header("To", recipients.map{|addr| addr.encoded}.join(','))
1651        blah("Mail From: <#{from}>")
1652        ctx.header("From", from.encoded) if from
1653 -      ctx.header("Date", Time.now.utc.strftime(DATE_HEADER_FORMAT))
1654 +      ctx.header("Date", Time.now.rfc2822)
1655        yield ctx
1656      end
1657    end
1658 @@ -1800,10 +2022,10 @@
1659    return unless $fileEntries.length == 1
1660    file = $fileEntries[0]
1661    name = zap_header_special_chars(file.path)
1662 -  unless file.fromVer == "NONE"
1663 +  if file.fromVer
1664      mail.header("References", make_msg_id("#{name}.#{file.fromVer}", $hostname))
1665    end
1666 -  unless file.toVer == "NONE"
1667 +  if file.toVer
1668      mail.header("Message-ID", make_msg_id("#{name}.#{file.toVer}", $hostname))
1669    end
1670  end
1671 @@ -1834,8 +2056,18 @@
1672      end
1673    end
1674    mail.header("X-Mailer", "CVSspam #{$version} <http://www.badgers-in-foil.co.uk/projects/cvsspam/>")
1675 +  if $cvsroot_email_header
1676 +    mod = '/'
1677 +    if Repository.count == 1
1678 +      rep = Repository.array.first
1679 +      mod << rep.common_prefix
1680 +    end
1681 +    mail.header("X-CVSspam-Module-Path", mod)
1682 +  end
1683  
1684    mail.body do |body|
1685      make_html_email(body)
1686    end
1687  end
1688 +
1689 +# vim:et:ts=2:sw=2
1690
1691 Property changes on: testcases/data/remove.png
1692 ___________________________________________________________________
1693 Deleted: svn:executable
1694    - *
1695
1696
1697 Property changes on: testcases/data/fiddlyedits.after
1698 ___________________________________________________________________
1699 Deleted: svn:executable
1700    - *
1701
1702
1703 Property changes on: testcases/data/fiddlyedits.before
1704 ___________________________________________________________________
1705 Deleted: svn:executable
1706    - *
1707
1708
1709 Property changes on: testcases/data/add.png
1710 ___________________________________________________________________
1711 Deleted: svn:executable
1712    - *
1713
1714
1715 Property changes on: testcases/README
1716 ___________________________________________________________________
1717 Deleted: svn:executable
1718    - *
1719
1720
1721 Property changes on: .
1722 ___________________________________________________________________
1723 Added: svn:ignore
1724    + .project
1725
1726
This page took 0.308402 seconds and 3 git commands to generate.