---- cvsspam-0.2.12/CREDITS 2005-07-11 18:53:29.000000000 +0300
-+++ cvsspam/CREDITS 2006-12-21 11:44:26.837358000 +0200
-@@ -29,3 +29,10 @@
- Elan Ruusamäe
- Steve Fox
- Christopher Petro
-+ Robin Getz
-+ Glen Starrett
-+ Jonathan Rafkind
-+ Ryan Dlugosz
-+ Steve Woodcock
-+ Andy Selle
-+ Charles Duffy
---- cvsspam-0.2.12/collect_diffs.rb 2005-07-11 18:53:29.000000000 +0300
-+++ cvsspam/collect_diffs.rb 2006-12-21 11:44:26.827358000 +0200
-@@ -27,6 +27,13 @@
+Index: cvsspam.conf
+===================================================================
+--- cvsspam.conf (.../tags/RELEASE-0_2_12) (revision 277)
++++ cvsspam.conf (.../trunk) (revision 277)
+@@ -34,11 +34,19 @@
+ #
+ # When $jiraURL is given, text of the form 'project-1234' will be linked
+ # to this issue in JIRA.
++#
++# When $xplannerStoryURL, $xplannerIterationURL and $xplannerProjectURL are
++# given, text of the form XS1234 will be linked to XPlanner stories; text of
++# the form XI1234 will be linked to XPlanner iterations; and text of the form
++# XP1234 will be linked to XPlanner projects.
+
+ #$bugzillaURL = "http://bugzilla.mozilla.org/show_bug.cgi?id=%s"
+
+ #$jiraURL = "http://jira.atlassian.com/secure/ViewIssue.jspa?key=%s"
+
++#$xplannerStoryURL = "http://www.example.com/xplanner/do/view/userstory?oid=%s"
++#$xplannerIterationURL = "http://www.example.com/xplanner/do/view/iteration?oid=%s"
++#$xplannerProjectURL = "http://www.example.com/xplanner/do/view/project?oid=%s"
+
+ # Link to Wiki systems
+ #
+@@ -71,6 +79,7 @@
+
+ #$cvswebURL = "http://localhost/cgi-bin/cvsweb.cgi"
+
++#$tracURL = "http://localhost/trac/project"
+
+
+ # Additional SMTP Headers (Optional)
+@@ -116,15 +125,32 @@
+
+
+
+-# cvsdiff keyword ignoring (Default: show changes in keywords)
++# cvsdiff keyword ignoring (Default: show changes in keywords)
+ #
+ # Changes in CVS keywords can be distracting. For instance, the
+-# $Revision: 1.12 $ keyword will change on each commit. Set this value to true
++# $Revision$ keyword will change on each commit. Set this value to true
+ # to exclude changes in keyword fields (adds the -kk option to cvs diff).
+
+ #$diff_ignore_keywords = true
+
+
++# cvsdiff whitespace ignoring (Default: show whitespace-only changes)
++#
++# Whitespace-only changes can distract from the rest of a diff. Set this
++# value to true to exclude changes in the amount of whitespace (adds the -b
++# option to cvs diff).
++
++$diff_ignore_whitespace = true
++
++
++# cvs diff files ignoring (Default: empty)
++#
++# Make CVSspam ignore certain files.
++#
++# Can contain file masks, separated by whitespace.
++
++#$ignore_files = "*.al *.gif"
++
+ # $no_removed_file_diff and $no_added_file_diff
+ #
+ # Set both these options, and emails will only include diffs for files
+@@ -132,7 +158,7 @@
+ # deleted...
+
+
+-# Don't show diff for removed files (Default: show file's contents)
++# Don't show diff for removed files (Default: show file's contents)
+ #
+ # If you aren't interested in seeing the contents of a file that was
+ # removed, set this option to true. The files will still appear in the index
+@@ -166,14 +192,46 @@
+ # Allows the specification of a character set for all generated emails.
+ # The files CVS is dealing with should already be in the character set you
+ # specify -- no transcoding is done.
++#
++# Note that you can override this with --charset argument per module, etc.
+
+ #$charset="ISO-8859-1"
+
+
+
++# Users file (Default: $CVSROOT/CVSROOT/users)
++#
++# Specify users file to lookup From addresses for commites
++
++#$users_file = "/srv/svn/users"
++
++# Users file charset (Default: $charset)
++#
++# If the users file is encoded differently than $charset, You can override
++# it here. Especially useful if you use --charset argument. See above.
++
++#$users_file_charset = "ISO-8859-1"
++
++
+ # File names in Subject (Default: no filenames in Subject)
+ #
+ # Some people like file names to appear in the email subject. To make
+ # them happy, you can say $files_in_subject = true here.
+
+ #$files_in_subject = false
++
++
++# Module Path email header (Default: no X-CVSspam-Module-Path header)
++#
++# Sets 'X-CVSspam-Module-Path' header to contain common path of files commited.
++# Useful for server side mail filtering.
++
++#$cvsroot_email_header = true
++
++# Email size limit (Default: around 2MB)
++#
++# When large changes are committed, large CVSspam emails can result. Here
++# you can set the size of email that CVSspam is not allowed to append any
++# more diffs onto. Specify the number of bytes.
++
++#$mail_size_limit = 2097152
+
+Property changes on: cvsspam.conf
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+Modified: svn:keywords
+ - Author Date Id Revision
+ + Author Date Id
+
+Index: collect_diffs.rb
+===================================================================
+--- collect_diffs.rb (.../tags/RELEASE-0_2_12) (revision 277)
++++ collect_diffs.rb (.../trunk) (revision 277)
+@@ -26,7 +26,18 @@
+ $tmpdir = ENV["TMPDIR"] || "/tmp"
$dirtemplate = "#cvsspam.#{Process.getpgrp}.#{Process.uid}"
++def shell_mask2regex(mask)
++ '^' + mask.gsub('.', '\.').gsub('?', '.').gsub('*', '.*') + '$'
++end
++
def find_data_dir
+ if $from_address
+ safe_from = make_fromaddr_safe_for_filename($from_address)
Dir["#{$tmpdir}/#{$dirtemplate}-*"].each do |dir|
stat = File.stat(dir)
return dir if stat.owned?
-@@ -35,6 +42,14 @@
+@@ -35,6 +46,14 @@
end
def blah(msg)
if $debug
$stderr.puts "collect_diffs.rb: #{msg}"
-@@ -129,7 +144,14 @@
+@@ -81,11 +100,11 @@
+ File.open("#{$datadir}/commitinfo-tags") do |file|
+ $commitinfo_tags = Hash.new
+ file.each_line do |line|
+- line =~ /([^\t]+)\t(.+)/
+- key = $2
+- val = $1
+- key.sub!(/^#{ENV['CVSROOT']}\//, '')
+- $commitinfo_tags[key] = val
++ line =~ /([^\t]+)\t(.+)/
++ key = $2
++ val = $1
++ key.sub!(/^#{ENV['CVSROOT']}\//, '')
++ $commitinfo_tags[key] = val
+ end
+ end
+ end
+@@ -129,7 +148,14 @@
changes = Array.new
i = 0
while i < cvs_info.length
i+=1
end
return changes
-@@ -222,6 +244,7 @@
+@@ -194,6 +220,8 @@
+
+ changes.each do |change|
+
++ next if $ignore_file_regexes and $ignore_file_regexes.any?{|r| change.file =~ /#{r}/}
++
+ # record version information
+ file.puts "#V #{change.fromVer},#{change.toVer}"
+
+@@ -202,7 +230,7 @@
+ # note if the file is on a branch
+ tag = nil
+ if change.isRemoval
+- tag = get_commitinfo_tag("#{$repository_path}/#{change.file}")
++ tag = get_commitinfo_tag("#{$repository_path}/#{change.file}")
+ else
+ status = nil
+ safer_popen($cvs_prog, "-nq", "status", change.file) do |io|
+@@ -210,18 +238,19 @@
+ end
+ fail "couldn't get cvs status: #{$!} (exited with #{$?})" unless ($?>>8)==0
+
+- if status =~ /^\s*Sticky Tag:\s*(.+) \(branch: +/m
+- tag = $1
+- end
++ if status =~ /^\s*Sticky Tag:\s*(.+) \(branch: +/m
++ tag = $1
++ end
+
+- if status =~ /^\s*Sticky Options:\s*-kb/m
+- binary_file = true
+- end
++ if status =~ /^\s*Sticky Options:\s*-kb/m
++ binary_file = true
++ end
+ end
+ file.puts "#T #{tag}" unless tag.nil?
diff_cmd = Array.new << $cvs_prog << "-nq" << "diff" << "-Nu"
diff_cmd << "-kk" if $diff_ignore_keywords
if change.isAddition
file.write "#A "
-@@ -333,9 +356,11 @@
+@@ -240,24 +269,24 @@
+ file.puts "#{$repository_path}/#{change.file}"
+ diff_cmd << change.file
+ if binary_file
+- blah("not diffing #{change.file}; has -kb set")
+- # fake diff lines that will cause cvsspam.rb to consider this a binary
+- # file,
+- file.puts "#U diff x x"
+- file.puts "#U Binary files x and y differ"
++ blah("not diffing #{change.file}; has -kb set")
++ # fake diff lines that will cause cvsspam.rb to consider this a binary
++ # file,
++ file.puts "#U diff x x"
++ file.puts "#U Binary files x and y differ"
+ else
+- # do a cvs diff and place the output into our temp file
+- blah("about to run #{diff_cmd.join(' ')}")
+- safer_popen(*diff_cmd) do |pipe|
+- # skip over cvs-diff's preamble
+- pipe.each do |line|
+- break if line =~ /^diff /
+- end
+- file.puts "#U #{line}"
+- pipe.each do |line|
+- file.puts "#U #{line}"
+- end
+- end
++ # do a cvs diff and place the output into our temp file
++ blah("about to run #{diff_cmd.join(' ')}")
++ safer_popen(*diff_cmd) do |pipe|
++ # skip over cvs-diff's preamble
++ pipe.each do |line|
++ break if line =~ /^diff /
++ end
++ file.puts "#U #{line}"
++ pipe.each do |line|
++ file.puts "#U #{line}"
++ end
++ end
+ end
+ # TODO: don't how to do this reliably on different systems...
+ #fail "cvsdiff did not give exit status 1 for invocation: #{diff_cmd.join(' ')}" unless ($?>>8)==1
+@@ -333,10 +362,13 @@
end
$config = nil
$diff_ignore_keywords = false
+$diff_ignore_whitespace = false
$task_keywords = []
++$ignore_file_regexes = nil
unless ENV.has_key?('CVSROOT')
-@@ -387,6 +412,7 @@
+ fail "$CVSROOT not defined. It should be when I am invoked from CVSROOT/loginfo"
+@@ -387,6 +419,7 @@
end
$config = arg if opt=="--config"
$debug = true if opt == "--debug"
end
blah("CVSROOT is #{ENV['CVSROOT']}")
---- cvsspam-0.2.12/cvsspam-doc.xml 2005-07-11 18:53:29.000000000 +0300
-+++ cvsspam/cvsspam-doc.xml 2006-12-21 11:44:26.837358000 +0200
+@@ -426,6 +459,9 @@
+ class GUESS
+ end
+ load $config
++ if $ignore_files
++ $ignore_file_regexes = $ignore_files.split(/\s+/).map{|i| shell_mask2regex(i)}
++ end
+ else
+ blah("Config file '#{$config}' not found, ignoring")
+ end
+@@ -447,3 +483,5 @@
+ process_log(ARGV[0])
+ end
+ mailtest
++
++# vim:et:ts=2:sw=2
+Index: record_lastdir.rb
+===================================================================
+--- record_lastdir.rb (.../tags/RELEASE-0_2_12) (revision 277)
++++ record_lastdir.rb (.../trunk) (revision 277)
+@@ -4,7 +4,6 @@
+ # http://www.badgers-in-foil.co.uk/projects/cvsspam/
+ # Copyright (c) David Holroyd
+
+-$repositorydir = ARGV.shift
+
+ $tmpdir = ENV["TMPDIR"] || "/tmp"
+
+@@ -19,6 +18,36 @@
+ nil
+ end
+
++
++# transform any special / unexpected characters appearing in the argument to
++# --from so that they will not cause problems if the value is inserted into
++# a file or directory name
++def make_fromaddr_safe_for_filename(addr)
++ addr.gsub(/[^a-zA-Z0-1.,_-]/, "_")
++end
++
++# Option processing doesn't use GetoptLong (for the moment) bacause arguments
++# given to this script by CVS include the names of committed files. It
++# seems quite possible that one of those file names could begin with a '-'
++# and therefore be treated by GetoptLong as a value which requires processing.
++# This would probably result in an error.
++#
++# [That could be worked around by placing a '--' option (which tells GetoptLong
++# to stop processing option arguments) at the very end of the arguments to
++# record_lastdir.rb in commitinfo, but that's very easily forgotten, and isn't
++# really backwards compatable with the behaviour of older CVSspam releases.]
++if ARGV.first == "--from"
++ # we could, of course, be tricked, if the first committed file in the list
++ # happened to be named '--from' :S
++
++ # drop the "--from"
++ ARGV.shift
++ # and use the value which was given following the option,
++ $dirtemplate << "." << make_fromaddr_safe_for_filename(ARGV.shift)
++end
++
++$repositorydir = ARGV.shift
++
+ $datadir = find_data_dir()
+
+ if $datadir==nil
+@@ -78,5 +107,7 @@
+ # email yet.
+
+ File.open("#{$datadir}/lastdir", "w") { |file|
+- file.write $repositorydir
++ file.write $repositorydir
+ }
++
++# vim:et:ts=2:sw=2
+
+Property changes on: TODO
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+Index: project.xml
+===================================================================
+Index: svn_post_commit_hook.rb
+===================================================================
+--- svn_post_commit_hook.rb (.../tags/RELEASE-0_2_12) (revision 0)
++++ svn_post_commit_hook.rb (.../trunk) (revision 277)
+@@ -0,0 +1,412 @@
++#!/usr/bin/ruby -w
++
++$svnlook_exe = "svnlook" # default assumes the program is in $PATH
++
++def usage(msg)
++ $stderr.puts(msg)
++ exit(1)
++end
++
++def blah(msg)
++ if $debug
++ $stderr.puts "svn_post_commit_hook.rb: #{msg}"
++ end
++end
++
++$debug = false
++$tmpdir = ENV["TMPDIR"] || "/tmp"
++$dirtemplate = "#svnspam.#{Process.getpgrp}.#{Process.uid}"
++# arguments to pass though to 'cvsspam.rb'
++$passthrough_args = []
++
++def make_data_dir
++ dir = "#{$tmpdir}/#{$dirtemplate}-#{rand(99999999)}"
++ Dir.mkdir(dir, 0700)
++ dir
++end
++
++def init
++ $datadir = make_data_dir
++
++ # set PWD so that svnlook can create its .svnlook directory
++ Dir.chdir($datadir)
++end
++
++def cleanup
++ unless $debug
++ File.unlink("#{$datadir}/logfile")
++ Dir.rmdir($datadir)
++ end
++end
++
++def send_email
++ cmd = File.dirname($0) + "/cvsspam.rb"
++ unless system(cmd,"--svn","#{$datadir}/logfile", *$passthrough_args)
++ fail "problem running '#{cmd}'"
++ end
++end
++
++# Like IO.popen, but accepts multiple arguments like Kernel.exec
++# (So no need to escape shell metacharacters)
++def safer_popen(*args)
++ IO.popen("-") do |pipe|
++ if pipe==nil
++ exec(*args)
++ else
++ yield pipe
++ end
++ end
++end
++
++
++# Process the command-line arguments in the given list
++def process_args
++ require 'getoptlong'
++
++ opts = GetoptLong.new(
++ [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ],
++ [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
++ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
++ [ "--from", "-u", GetoptLong::REQUIRED_ARGUMENT ],
++ [ "--charset", GetoptLong::REQUIRED_ARGUMENT ]
++ )
++
++ opts.each do |opt, arg|
++ if ["--to", "--config", "--from", "--charset"].include?(opt)
++ $passthrough_args << opt << arg
++ end
++ if ["--debug"].include?(opt)
++ $passthrough_args << opt
++ end
++ $config = arg if opt=="--config"
++ $debug = true if opt == "--debug"
++ end
++
++ $repository = ARGV[0]
++ $revision = ARGV[1]
++
++ unless $revision =~ /^\d+$/
++ usage("revision must be an integer: #{revision.inspect}")
++ end
++ $revision = $revision.to_i
++
++ unless FileTest.directory?($repository)
++ usage("no such directory: #{$repository.inspect}")
++ end
++end
++
++# runs the given svnlook subcommand
++def svnlook(cmd, revision, *args)
++ rev = revision.to_s
++ safer_popen($svnlook_exe, cmd, $repository, "-r", rev, *args) do |io|
++ yield io
++ end
++end
++
++class Change
++ def initialize(filechange, propchange, path)
++ @filechange = filechange
++ @propchange = propchange
++ @path = path
++ end
++
++ attr_accessor :filechange, :propchange, :path
++
++ def property_change?
++ @propchange != " "
++ end
++
++ def file_change?
++ @filechange != "_"
++ end
++
++ def addition?
++ @filechange == "A"
++ end
++
++ def deletion?
++ @filechange == "D"
++ end
++end
++
++
++
++# Line-oriented access to an underlying IO object. Remembers 'current' line
++# for lookahead during parsing.
++class LineReader
++ def initialize(io)
++ @io = io
++ end
++
++ def current
++ @line
++ end
++
++ def next_line
++ (@line = @io.gets) != nil
++ end
++
++ def assert_current(re)
++ raise "unexpected #{current.inspect}" unless @line =~ re
++ $~
++ end
++
++ def assert_next(re=nil)
++ raise "unexpected end of text" unless next_line
++ unless re.nil?
++ raise "unexpected #{current.inspect}" unless @line =~ re
++ end
++ $~
++ end
++end
++
++
++def read_modified_diff(out, lines, path)
++ lines.assert_next(/^=+$/)
++ lines.assert_next
++ if lines.current =~ /\(Binary files differ\)/
++ process_modified_binary_diff(out, lines, path)
++ else
++ process_modified_text_diff(out, lines, path)
++ end
++end
++
++
++def process_modified_binary_diff(out, lines, path)
++ prev_rev= $revision-1
++ next_rev= $revision
++ out.puts "#V #{prev_rev},#{next_rev}"
++ out.puts "#M #{path}"
++ out.puts "#U diff x x"
++ out.puts "#U Binary files x and y differ"
++end
++
++
++def process_modified_text_diff(out, lines, path)
++ m = lines.assert_current(/^---.*\(rev (\d+)\)$/)
++ prev_rev = m[1].to_i
++ diff1 = lines.current
++ m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
++ next_rev = m[1].to_i
++ diff2 = lines.current
++ out.puts "#V #{prev_rev},#{next_rev}"
++ out.puts "#M #{path}"
++ out.puts "#U #{diff1}"
++ out.puts "#U #{diff2}"
++ while lines.next_line && lines.current =~ /^[-\+ @\\]/
++ out.puts "#U #{lines.current}"
++ end
++end
++
++def read_added_diff(out, lines, path)
++ lines.assert_next(/^=+$/)
++ lines.assert_next
++ if lines.current =~ /\(Binary files differ\)/
++ process_added_binary_diff(out, lines, path)
++ else
++ process_added_text_diff(out, lines, path)
++ end
++end
++
++def process_added_binary_diff(out, lines, path)
++ next_rev= $revision
++ out.puts "#V NONE,#{next_rev}"
++ out.puts "#A #{path}"
++ out.puts "#U diff x x"
++ out.puts "#U Binary file x added"
++end
++
++def process_added_text_diff(out, lines, path)
++ m = lines.assert_current(/^---.*\(rev (\d+)\)$/)
++ prev_rev = m[1].to_i
++ diff1 = lines.current
++ m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
++ next_rev = m[1].to_i
++ diff2 = lines.current
++ out.puts "#V NONE,#{next_rev}"
++ out.puts "#A #{path}"
++ out.puts "#U #{diff1}"
++ out.puts "#U #{diff2}"
++ while lines.next_line && lines.current =~ /^[-\+ @\\]/
++ out.puts "#U #{lines.current}"
++ end
++end
++
++def read_deleted_diff(out, lines, path)
++ lines.assert_next(/^=+$/)
++ m = lines.assert_next(/^---.*\(rev (\d+)\)$/)
++ prev_rev = m[1].to_i
++ diff1 = lines.current
++ m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
++ next_rev = m[1].to_i
++ diff2 = lines.current
++ out.puts "#V #{prev_rev},NONE"
++ out.puts "#R #{path}"
++ out.puts "#U #{diff1}"
++ out.puts "#U #{diff2}"
++ while lines.next_line && lines.current =~ /^[-\+ @\\]/
++ out.puts "#U #{lines.current}"
++ end
++end
++
++def read_property_lines(path, prop_name, revision)
++ lines = []
++ svnlook("propget", revision, prop_name, path) do |io|
++ io.each_line do |line|
++ lines << line.chomp
++ end
++ end
++ lines
++end
++
++def assert_prop_match(a, b)
++ if !b.nil? && a != b
++ raise "property mismatch: #{a.inspect}!=#{b.inspect}"
++ end
++end
++
++# We need to read the property change from the output of svnlook, but have
++# a difficulty in that there's no unambiguous delimiter marking the end of
++# a potentially multi-line property value. Therefore, we do a seperate
++# svn propget on the given file to get the value of the property on its own,
++# and then use that value as a guide as to how much data to read from the
++# svnlook output.
++def munch_prop_text(path, prop_name, revision, lines, line0)
++ prop = read_property_lines(path, prop_name, revision)
++ if prop.empty?
++ assert_prop_match(line0, "")
++ return
++ end
++ assert_prop_match(line0, prop.shift)
++ prop.each do |prop_line|
++ lines.assert_next
++ assert_prop_match(lines.current.chomp, prop_line)
++ end
++end
++
++def read_properties_changed(out, lines, path)
++ prev_rev= $revision-1
++ next_rev= $revision
++ lines.assert_next(/^_+$/)
++ return unless lines.next_line
++ out.puts "#V #{prev_rev},#{next_rev}"
++ out.puts "#P #{path}"
++# The first three get consumed and not highlighted
++ out.puts "#U "
++ out.puts "#U Property changes:"
++ out.puts "#U "
++
++ while true
++ break unless lines.current =~ /^(?:Name|Added|Deleted): (.+)$/
++
++ prop_name = $1
++ m = lines.assert_next(/^ ([-+]) (.*)/)
++ op = m[1]
++ line0 = m[2]
++ if op == "-"
++ munch_prop_text(path, prop_name, $revision-1, lines, line0)
++ if lines.next_line && lines.current =~ /^ \+ (.*)/
++ munch_prop_text(path, prop_name, $revision, lines, $1)
++ lines.next_line
++ end
++ else # op == "+"
++ munch_prop_text(path, prop_name, $revision, lines, line0)
++ lines.next_line
++ end
++ out.puts "#U #{m[1]} #{prop_name}:#{m[2]}"
++ end
++ out.puts "#U "
++end
++
++def handle_copy(out, lines, path, from_ref, from_file)
++ prev_rev= $revision-1
++ next_rev= $revision
++ out.puts "#V #{from_file}:#{prev_rev},#{next_rev}"
++ out.puts "#C #{path}"
++ if lines.next_line && lines.current =~ /^=+$/
++ m = lines.assert_next(/^---.*\(rev (\d+)\)$/)
++ prev_rev = m[1].to_i
++ diff1 = lines.current
++ m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/)
++ next_rev = m[1].to_i
++ diff2 = lines.current
++ out.puts "#U #{diff1}"
++ out.puts "#U #{diff2}"
++ while lines.next_line && lines.current =~ /^[-\+ @\\]/
++ out.puts "#U #{lines.current}"
++ end
++ else
++ out.puts "#U "
++ out.puts "#U Copied from #{from_file}:#{from_ref}"
++ out.puts "#U "
++ end
++end
++
++def svnlook_author
++ svnlook("author", $revision) do |io|
++ return io.readline.chomp
++ end
++ nil
++end
++
++def find_author
++ return if $passthrough_args.include?("--from")
++ author = svnlook_author
++ if author
++ blah("Author from svnlook: '#{author}'")
++ $passthrough_args << "--from" << author
++ end
++end
++
++def process_svnlook_log(file)
++ svnlook("log", $revision) do |io|
++ io.each_line do |line|
++ file.puts("#> #{line}")
++ end
++ end
++end
++
++def process_svnlook_diff(file)
++ svnlook("diff", $revision) do |io|
++ lines = LineReader.new(io)
++ while lines.next_line
++ if lines.current =~ /^Modified:\s+(.*)/
++ read_modified_diff(file, lines, $1)
++ elsif lines.current =~ /^Added:\s+(.*)/
++ read_added_diff(file, lines, $1)
++ elsif lines.current =~ /^Copied:\s+(.*) \(from rev (\d+), (.*)\)$/
++ handle_copy(file, lines, $1, $2, $3)
++ elsif lines.current =~ /^Deleted:\s+(.*)/
++ read_deleted_diff(file, lines, $1)
++ elsif lines.current =~ /^Property changes on:\s+(.*)/
++ read_properties_changed(file, lines, $1)
++ elsif lines.current == "\n"
++ # ignore
++ else
++ raise "unable to parse line '#{lines.current.inspect}'"
++ end
++ end
++ end
++end
++
++def process_commit()
++ File.open("#{$datadir}/logfile", File::WRONLY|File::CREAT) do |file|
++ process_svnlook_log(file)
++ process_svnlook_diff(file)
++ end
++end
++
++
++def main
++ init()
++ process_args()
++ find_author()
++ process_commit()
++ send_email()
++ cleanup()
++end
++
++
++main
++
++# vim:et:ts=2:sw=2
+
+Property changes on: svn_post_commit_hook.rb
+___________________________________________________________________
+Added: svn:mergeinfo
+Added: svn:executable
+ + *
+
+
+Property changes on: COPYING
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+Index: CREDITS
+===================================================================
+--- CREDITS (.../tags/RELEASE-0_2_12) (revision 277)
++++ CREDITS (.../trunk) (revision 277)
+@@ -29,3 +29,10 @@
+ Elan Ruusamäe
+ Steve Fox
+ Christopher Petro
++ Robin Getz
++ Glen Starrett
++ Jonathan Rafkind
++ Ryan Dlugosz
++ Steve Woodcock
++ Andy Selle
++ Charles Duffy
+Index: cvsspam-doc.xml
+===================================================================
+--- cvsspam-doc.xml (.../tags/RELEASE-0_2_12) (revision 277)
++++ cvsspam-doc.xml (.../trunk) (revision 277)
@@ -452,6 +452,23 @@
</screen></informalexample>
</para>
</section>
<section><title>CVS Web Frontends</title>
---- cvsspam-0.2.12/cvsspam.conf 2005-07-11 18:53:30.000000000 +0300
-+++ cvsspam/cvsspam.conf 2006-12-21 11:44:26.827358000 +0200
-@@ -34,11 +34,19 @@
- #
- # When $jiraURL is given, text of the form 'project-1234' will be linked
- # to this issue in JIRA.
-+#
-+# When $xplannerStoryURL, $xplannerIterationURL and $xplannerProjectURL are
-+# given, text of the form XS1234 will be linked to XPlanner stories; text of
-+# the form XI1234 will be linked to XPlanner iterations; and text of the form
-+# XP1234 will be linked to XPlanner projects.
-
- #$bugzillaURL = "http://bugzilla.mozilla.org/show_bug.cgi?id=%s"
-
- #$jiraURL = "http://jira.atlassian.com/secure/ViewIssue.jspa?key=%s"
-
-+#$xplannerStoryURL = "http://www.example.com/xplanner/do/view/userstory?oid=%s"
-+#$xplannerIterationURL = "http://www.example.com/xplanner/do/view/iteration?oid=%s"
-+#$xplannerProjectURL = "http://www.example.com/xplanner/do/view/project?oid=%s"
-
- # Link to Wiki systems
- #
-@@ -119,12 +127,21 @@
- # cvsdiff keyword ignoring (Default: show changes in keywords)
- #
- # Changes in CVS keywords can be distracting. For instance, the
--# $Revision$ keyword will change on each commit. Set this value to true
-+# $Revision$ keyword will change on each commit. Set this value to true
- # to exclude changes in keyword fields (adds the -kk option to cvs diff).
-
- #$diff_ignore_keywords = true
-
-
-+# cvsdiff whitespace ignoring (Default: show whitespace-only changes)
-+#
-+# Whitespace-only changes can distract from the rest of a diff. Set this
-+# value to true to exclude changes in the amount of whitespace (adds the -b
-+# option to cvs diff).
-+
-+$diff_ignore_whitespace = true
-+
-+
- # $no_removed_file_diff and $no_added_file_diff
- #
- # Set both these options, and emails will only include diffs for files
-@@ -177,3 +194,13 @@
- # them happy, you can say $files_in_subject = true here.
-
- #$files_in_subject = false
-+
-+
-+
-+# Email size limit (Default: around 2MB)
-+#
-+# When large changes are committed, large CVSspam emails can result. Here
-+# you can set the size of email that CVSspam is not allowed to append any
-+# more diffs onto. Specify the number of bytes.
-+
-+#$mail_size_limit = 2097152
---- cvsspam-0.2.12/cvsspam.rb 2005-07-11 18:53:29.000000000 +0300
-+++ cvsspam/cvsspam.rb 2006-12-21 17:36:44.342608880 +0200
-@@ -20,6 +20,7 @@
+
+Property changes on: cvsspam-doc.xml
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+Index: cvsspam.rb
+===================================================================
+--- cvsspam.rb (.../tags/RELEASE-0_2_12) (revision 277)
++++ cvsspam.rb (.../trunk) (revision 277)
+@@ -20,11 +20,14 @@
$version = "0.2.12"
$maxSubjectLength = 200
$maxLinesPerDiff = 1000
-@@ -35,10 +36,6 @@
+-$maxDiffLineLength = 1000 # may be set to nil for no limit
+-$charset = nil # nil implies 'don't specify a charset'
++# may be set to nil for no limit
++$maxDiffLineLength = 1000
++# nil implies 'don't specify a charset'
++$charset = nil
+ $mailSubject = ''
+
+ def blah(text)
+@@ -35,10 +38,6 @@
a<b ? a : b
end
# Perform (possibly) multiple global substitutions on a string.
# the regexps given as keys must not use capturing subexpressions '(...)'
class MultiSub
-@@ -116,6 +113,8 @@
+@@ -48,7 +47,7 @@
+ @mash = Array.new
+ expr = nil
+ hash.each do |key,val|
+- if expr == nil ; expr="(" else expr<<"|(" end
++ if expr == nil ; expr="(" else expr << "|(" end
+ expr << key << ")"
+ @mash << val
+ end
+@@ -116,6 +115,8 @@
UNDERSCORE = chr("_")
SPACE = chr(" ")
TAB = chr("\t")
# encode a header value according to the RFC-2047 quoted-printable spec,
# allowing non-ASCII characters to appear in header values, and wrapping
-@@ -137,7 +136,7 @@
+@@ -137,8 +138,8 @@
# return a string representing the given character-code in quoted-printable
# format
def quoted_encode_char(b)
- if b>126 || b==UNDERSCORE || b==TAB
+- sprintf("=%02x", b)
+ if b>126 || b==UNDERSCORE || b==TAB || b==HOOK || b==EQUALS
- sprintf("=%02x", b)
++ sprintf("=%02X", b)
elsif b == SPACE
"_"
-@@ -388,6 +387,7 @@
+ else
+@@ -163,8 +164,9 @@
+
+ # gives a string starting "=?", and including a charset specification, that
+ # marks the start of a quoted-printable character sequence
+- def marker_start_quoted
+- "=?#{@charset}?#{@encoding}?"
++ def marker_start_quoted(charset=nil)
++ charset = @charset if charset.nil?
++ "=?#{charset}?#{@encoding}?"
+ end
+
+ # test to see of the given string contains non-ASCII characters
+@@ -388,6 +390,7 @@
class FileEntry
def initialize(path)
@path = path
@lineAdditions = @lineRemovals = 0
@repository = Repository.get(path)
@repository.merge_common_prefix(basedir())
-@@ -533,6 +533,14 @@
+@@ -397,7 +400,7 @@
+
+ # the full path and filename within the repository
+ attr_accessor :path
+- # the type of change committed 'M'=modified, 'A'=added, 'R'=removed
++ # the type of change committed 'M'=modified, 'A'=added, 'R'=removed, 'P'=properties, 'C'=copied
+ attr_accessor :type
+ # records number of 'addition' lines in diff output, once counted
+ attr_accessor :lineAdditions
+@@ -452,17 +455,28 @@
+ def removal?
+ @type == "R"
+ end
+-
++
+ # was this file added during the commit?
+ def addition?
+ @type == "A"
+ end
+
++ # was this file copied during the commit?
++ def copied?
++ @type == "C"
++ end
++
+ # was this file simply modified during the commit?
+ def modification?
+ @type == "M"
+ end
++
++ # was this file simply modified during the commit?
++ def modifiedprops?
++ @type == "P"
++ end
+
++
+ # passing true, this object remembers that a diff will appear in the email,
+ # passing false, this object remembers that no diff will appear in the email.
+ # Once the value is set, it will not be changed
+@@ -533,6 +547,14 @@
# TODO: consolidate these into a nicer framework,
mailSub = proc { |match| "<a href=\"mailto:#{match}\">#{match}</a>" }
urlSub = proc { |match| "<a href=\"#{match}\">#{match}</a>" }
bugzillaSub = proc { |match|
match =~ /([0-9]+)/
"<a href=\"#{$bugzillaURL.sub(/%s/, $1)}\">#{match}</a>"
-@@ -544,11 +552,27 @@
+@@ -544,15 +566,31 @@
match =~ /([0-9]+)/
"<a href=\"#{$ticketURL.sub(/%s/, $1)}\">#{match}</a>"
}
+ "<a href=\"#{$xplannerStoryURL.sub(/%s/, $1)}\">#{match}</a>"
+}
commentSubstitutions = {
- '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
- '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
-@@ -670,6 +694,12 @@
+- '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
+- '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
+- }
++ '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
++ '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
++}
+
+ # outputs commit log comment text supplied by LogReader as preformatted HTML
+ class CommentHandler < LineConsumer
+@@ -670,6 +708,12 @@
def diff(file)
'->'
end
end
# Superclass for objects that can link to CVS frontends on the web (ViewCVS,
-@@ -710,6 +740,14 @@
+@@ -710,6 +754,14 @@
"<a href=\"#{diff_url(file)}\">#{super(file)}</a>"
end
protected
def add_repo(url)
if @repository_name
-@@ -722,6 +760,10 @@
+@@ -722,6 +774,10 @@
url
end
end
end
# Link to ViewCVS
-@@ -745,6 +787,15 @@
+@@ -745,6 +801,15 @@
def diff_url(file)
add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=#{file.fromVer}&r2=#{file.toVer}")
end
end
# Link to Chora, from the Horde framework
-@@ -767,9 +818,9 @@
+@@ -767,9 +832,9 @@
class CVSwebFrontend < WebFrontend
def path_url(path, tag)
if tag == nil
end
end
-@@ -780,6 +831,17 @@
+@@ -780,9 +845,45 @@
def diff_url(file)
add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=text&tr1=#{file.fromVer}&r2=text&tr2=#{file.toVer}&f=h")
end
+ end
end
++# Link to Trac
++class TracFrontend < WebFrontend
++ def path_url(path, tag)
++ add_repo("#{@base_url}browser/#{urlEncode(path)}")
++ end
-@@ -958,7 +1020,7 @@
++ def version_url(path, version)
++ add_repo("#{@base_url}browser/#{urlEncode(path)}?rev=#{version}")
++ end
++
++ def diff_url(file)
++ add_repo("#{@base_url}changeset/#{file.toVer}")
++ end
++
++ protected
++
++ def log_url(file)
++ if file.toVer
++ log_anchor = "?rev=#{file.toVer}"
++ else
++ log_anchor = ""
++ end
++ add_repo("#{@base_url}log/#{urlEncode(file.path)}#{log_anchor}")
++ end
++end
++
+ # in need of refactoring...
+
+ # Note when LogReader finds record of a file that was added in this commit
+@@ -801,6 +902,15 @@
+ end
+ end
+
++# Note when LogReader finds record of a file that was copied in this commit
++class CopiedFileHandler < FileHandler
++ def handleFile(file)
++ file.type="C"
++ file.fromVer=$fromVer
++ file.toVer=$toVer
++ end
++end
++
+ # Note when LogReader finds record of a file that was modified in this commit
+ class ModifiedFileHandler < FileHandler
+ def handleFile(file)
+@@ -810,7 +920,16 @@
+ end
+ end
+
++# Note when LogReader finds record of a file whose properties were modified in this commit
++class ModifiedPropsFileHandler < FileHandler
++ def handleFile(file)
++ file.type="P"
++ file.fromVer=$fromVer
++ file.toVer=$toVer
++ end
++end
+
++
+ # Used by UnifiedDiffHandler to record the number of added and removed lines
+ # appearing in a unidiff.
+ class UnifiedDiffStats
+@@ -873,7 +992,10 @@
+ addInfixSize = line.length - (prefixLen+suffixLen)
+ oversize_change = deleteInfixSize*100/@lineJustDeleted.length>33 || addInfixSize*100/line.length>33
+
+- if prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
++ # avoid doing 'within-a-line highlighting' if a multibyte encoding
++ # is suspected, as all the suffix/prefix stuff above is byte, not
++ # character based
++ if multibyte_encoding? || prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
+ print(htmlEncode(@lineJustDeleted))
+ else
+ print(htmlEncode(@lineJustDeleted[0,prefixLen]))
+@@ -905,7 +1027,7 @@
+ @lineJustDeleted = nil
+ end
+ shift(initial)
+- if prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
++ if multibyte_encoding? || prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
+ encoded = htmlEncode(line)
+ else
+ encoded = htmlEncode(line[0,prefixLen]) +
+@@ -958,7 +1080,7 @@
end
shift(nil)
if @truncatedLineCount>0
end
end
-@@ -1247,10 +1309,16 @@
+@@ -976,11 +1098,21 @@
+ print($frontend.path($file.basedir, $file.tag))
+ println("</span><br />")
+ 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>")
++ when "C"
++ print("<span class=\"pathname\" id=\"copied\">")
++ print($frontend.path($file.basedir, $file.tag))
++ println("</span><br />")
++ 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>")
+ when "M"
+ print("<span class=\"pathname\">")
+ print($frontend.path($file.basedir, $file.tag))
+ println("</span><br />")
+ 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>")
++ when "P"
++ print("<span class=\"pathname\">")
++ print($frontend.path($file.basedir, $file.tag))
++ println("</span><br />")
++ 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>")
+ end
+ print("<pre class=\"diff\"><small id=\"info\">")
+ lines.each do |line|
+@@ -1078,7 +1210,7 @@
+ @colour.teardown
+ end
+ println("</div>") # end of "file" div
+- $file.has_diff = true
++ $file.has_diff = true
+ end
+ end
+ end
+@@ -1181,7 +1313,7 @@
+
+ # an RFC 822 email address
+ class EmailAddress
+- def initialize(text)
++ def initialize(text, charset=nil)
+ if text =~ /^\s*([^<]+?)\s*<\s*([^>]+?)\s*>\s*$/
+ @personal_name = $1
+ @address = $2
+@@ -1189,9 +1321,10 @@
+ @personal_name = nil
+ @address = text
+ end
++ @charset=charset
+ end
+
+- attr_accessor :personal_name, :address
++ attr_accessor :personal_name, :address, :charset
+
+ def has_personal_name?
+ return !@personal_name.nil?
+@@ -1222,9 +1355,9 @@
+ # rfc2047 encode the word, if it contains non-ASCII characters
+ def encode_word(word)
+ if $encoder.requires_rfc2047?(word)
+- encoded = $encoder.marker_start_quoted
++ encoded = $encoder.marker_start_quoted(@charset)
+ $encoder.each_char_encoded(word) do |code|
+- encoded << code
++ encoded << code
+ end
+ encoded << $encoder.marker_end_quoted
+ return encoded
+@@ -1233,26 +1366,40 @@
+ end
+ end
+
++# guess if the users selected encoding is multibyte, since some CVSspam code
++# isn't multibyte-safe, and needs to be disabled.
++def multibyte_encoding?
++ $charset && ["utf-8", "utf-16"].include?($charset.downcase)
++end
+
+ cvsroot_dir = "#{ENV['CVSROOT']}/CVSROOT"
+ $config = "#{cvsroot_dir}/cvsspam.conf"
+ $users_file = "#{cvsroot_dir}/users"
++$users_file_charset = nil
+
+ $debug = false
++$svn = false
+ $recipients = Array.new
+ $sendmail_prog = "/usr/sbin/sendmail"
+ $hostname = ENV['HOSTNAME'] || 'localhost'
+ $no_removed_file_diff = false
+ $no_added_file_diff = false
$no_diff = false
- $task_keywords = ['TODO', 'FIXME']
+-$task_keywords = ['TODO', 'FIXME']
++$task_keywords = ['TODO', 'FIXME', 'FIXIT', 'todo']
$bugzillaURL = nil
+$gforgeBugURL = nil
+$gforgeTaskURL = nil
+$xplannerStoryURL = nil
$choraURL = nil
$cvswebURL = nil
++$tracURL = nil
$from_address = nil
-@@ -1261,6 +1329,7 @@
+ $subjectPrefix = nil
+ $files_in_subject = false;
+@@ -1261,6 +1408,7 @@
# 2MiB limit on attached diffs,
$mail_size_limit = 1024 * 1024 * 2
$arg_charset = nil
require 'getoptlong'
-@@ -1353,7 +1422,13 @@
+@@ -1268,6 +1416,7 @@
+ [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
++ [ "--svn", "-s", GetoptLong::NO_ARGUMENT ],
+ [ "--from", "-u", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--charset", GetoptLong::REQUIRED_ARGUMENT ]
+ )
+@@ -1276,6 +1425,7 @@
+ $recipients << EmailAddress.new(arg) if opt=="--to"
+ $config = arg if opt=="--config"
+ $debug = true if opt=="--debug"
++ $svn = true if opt=="--svn"
+ $from_address = EmailAddress.new(arg) if opt=="--from"
+ # must use different variable as the config is readed later.
+ $arg_charset = arg if opt == "--charset"
+@@ -1288,7 +1438,7 @@
+ else
+ $stderr.puts "missing required file argument"
+ end
+- puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>"
++ puts "Usage: cvsspam.rb [ --svn ] [ --to <email> ] [ --config <file> ] <collect_diffs file>"
+ exit(-1)
+ end
+
+@@ -1321,6 +1471,8 @@
+ blah("Config file '#{$config}' not found, ignoring")
+ end
+
++blah("Users file: '#{$users_file}'")
++
+ unless $arg_charset.nil?
+ $charset = $arg_charset
+ end
+@@ -1337,6 +1489,9 @@
+ elsif $cvswebURL !=nil
+ $cvswebURL << "/" unless $cvswebURL =~ /\/$/
+ $frontend = CVSwebFrontend.new($cvswebURL)
++elsif $tracURL !=nil
++ $tracURL << "/" unless $tracURL =~ /\/$/
++ $frontend = TracFrontend.new($tracURL)
+ else
+ $frontend = NoFrontend.new
+ end
+@@ -1353,33 +1508,57 @@
if $bugzillaURL != nil
- commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugzillaSub
-+ commentSubstitutions['\b[Bb]([Uu][Gg])?\s*[#:]?\s*\[?[0-9]+\]?'] = bugzillaSub
-+end
++ commentSubstitutions['\b[Bb](?:[Uu][Gg])?\s*[#:]?\s*\[?[0-9]+\]?'] = bugzillaSub
+ end
+if $gforgeBugURL != nil
+ commentSubstitutions['\B\[#[0-9]+\]'] = gforgeBugSub
+end
+if $gforgeTaskURL != nil
+ commentSubstitutions['\B\[[Tt][0-9]+\]'] = gforgeTaskSub
- end
++end
if $jiraURL != nil
commentSubstitutions['\b[a-zA-Z]+-[0-9]+\b'] = jiraSub
-@@ -1361,9 +1436,21 @@
+ end
if $ticketURL != nil
commentSubstitutions['\b[Tt][Ii][Cc][Kk][Ee][Tt]\s*#?[0-9]+\b'] = ticketSub
end
$commentEncoder = MultiSub.new(commentSubstitutions)
-@@ -1546,11 +1633,14 @@
+ tagHandler = TagHandler.new
+
+-$handlers = Hash[">" => CommentHandler.new,
+- "U" => UnifiedDiffHandler.new,
+- "T" => tagHandler,
+- "A" => AddedFileHandler.new,
+- "R" => RemovedFileHandler.new,
+- "M" => ModifiedFileHandler.new,
+- "V" => VersionHandler.new]
++$handlers = Hash[
++ ">" => CommentHandler.new,
++ "U" => UnifiedDiffHandler.new,
++ "T" => tagHandler,
++ "A" => AddedFileHandler.new,
++ "R" => RemovedFileHandler.new,
++ "C" => CopiedFileHandler.new,
++ "M" => ModifiedFileHandler.new,
++ "P" => ModifiedPropsFileHandler.new,
++ "V" => VersionHandler.new
++]
+
+ $handlers["A"].setTagHandler(tagHandler)
+ $handlers["R"].setTagHandler(tagHandler)
++$handlers["C"].setTagHandler(tagHandler)
+ $handlers["M"].setTagHandler(tagHandler)
++$handlers["P"].setTagHandler(tagHandler)
+
+ $fileEntries = Array.new
+ $task_list = Array.new
+@@ -1404,7 +1583,11 @@
+ end
+
+ if $subjectPrefix == nil
+- $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
++ if $svn
++ $subjectPrefix = "[SVN #{Repository.array.join(',')}]"
++ else
++ $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
++ end
+ end
+
+ if $files_in_subject
+@@ -1451,6 +1634,8 @@
+ #removed {background-color:#ffdddd;}
+ #removedchars {background-color:#ff9999;font-weight:bolder;}
+ tr.alt #removed {background-color:#f7cccc;}
++ #copied {background-color:#ccccff;}
++ tr.alt #copied {background-color:#bbbbf7;}
+ #info {color:#888888;}
+ #context {background-color:#eeeeee;}
+ td {padding-left:.3em;padding-right:.3em;}
+@@ -1483,7 +1668,9 @@
+
+ filesAdded = 0
+ filesRemoved = 0
++ filesCopied = 0
+ filesModified = 0
++ filesModifiedProps = 0
+ totalLinesAdded = 0
+ totalLinesRemoved = 0
+ file_count = 0
+@@ -1492,24 +1679,26 @@
+ $fileEntries.each do |file|
+ unless file.repository == last_repository
+ last_repository = file.repository
+- mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 5 : 4}\">")
++ mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 6 : 5}\">")
+ if last_repository.has_multiple_tags
+ mail.print("Mixed-tag commit")
+ else
+ mail.print("Commit")
+ end
+ mail.print(" in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
+- if last_repository.trunk_only?
+- mail.print("<span id=\"info\"> on MAIN</span>")
+- else
+- mail.print(" on ")
+- tagCount = 0
+- last_repository.each_tag do |tag|
+- tagCount += 1
+- if tagCount > 1
+- mail.print tagCount<last_repository.tag_count ? ", " : " & "
++ if !$svn
++ if last_repository.trunk_only?
++ mail.print("<span id=\"info\"> on MAIN</span>")
++ else
++ mail.print(" on ")
++ tagCount = 0
++ last_repository.each_tag do |tag|
++ tagCount += 1
++ if tagCount > 1
++ mail.print tagCount<last_repository.tag_count ? ", " : " & "
++ end
++ mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
+ end
+- mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
+ end
+ end
+ mail.puts("</td></tr>")
+@@ -1524,8 +1713,12 @@
+ filesAdded += 1
+ elsif file.removal?
+ filesRemoved += 1
++ elsif file.copied?
++ filesCopied += 1
+ elsif file.modification?
+ filesModified += 1
++ elsif file.modifiedprops?
++ filesModifiedProps += 1
+ end
+ name = htmlEncode(file.name_after_common_prefix)
+ slashPos = name.rindex("/")
+@@ -1545,17 +1738,29 @@
+ name = "<span id=\"added\">#{name}</span>"
elsif file.removal?
name = "<span id=\"removed\">#{name}</span>"
++ elsif file.copied?
++ name = "<span id=\"copied\">#{name}</span>"
end
+ mail.print("<td>")
if file.has_diff?
- mail.print("<td><tt>#{prefix}#{name}</tt></td>")
+ mail.print("<tt>#{prefix}#{name}</tt>")
end
+- if file.isEmpty
+- mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
+ mail.print(" #{$frontend.log(file)}")
+ mail.print("</td>")
- if file.isEmpty
- mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
++ if file.copied?
++ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[copied]</small></td>")
++ elsif file.isEmpty
++ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[empty]</small></td>")
elsif file.isBinary
-@@ -1686,6 +1776,8 @@
+- mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
++ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[binary]</small></td>")
+ else
++ if file.modifiedprops?
++ mail.print("<td align=\"right\"><small id=\"info\">[props]</small></td>")
++ else
++ mail.print("<td></td>")
++ end
+ if file.lineAdditions>0
+ totalLinesAdded += file.lineAdditions
+ mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
+@@ -1582,15 +1787,19 @@
+ mail.print("<td nowrap=\"nowrap\" align=\"right\">added #{$frontend.version(file.path,file.toVer)}</td>")
+ elsif file.removal?
+ mail.print("<td nowrap=\"nowrap\">#{$frontend.version(file.path,file.fromVer)} removed</td>")
++ elsif file.copied?
++ mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
+ elsif file.modification?
+ mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
++ elsif file.modifiedprops?
++ mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
+ end
+
+ mail.puts("</tr>")
+ end
+ if $fileEntries.size>1 && (totalLinesAdded+totalLinesRemoved)>0
+ # give total number of lines added/removed accross all files
+- mail.print("<tr><td></td>")
++ mail.print("<tr><td></td><td></td>")
+ if totalLinesAdded>0
+ mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
+ else
+@@ -1607,7 +1816,7 @@
+
+ mail.puts("</table>")
+
+- totalFilesChanged = filesAdded+filesRemoved+filesModified
++ totalFilesChanged = filesAdded+filesRemoved+filesCopied+filesModified+filesModifiedProps
+ if totalFilesChanged > 1
+ mail.print("<small id=\"info\">")
+ changeKind = 0
+@@ -1620,11 +1829,21 @@
+ mail.print("#{filesRemoved} removed")
+ changeKind += 1
+ end
++ if filesCopied>0
++ mail.print(" + ") if changeKind>0
++ mail.print("#{filesCopied} copied")
++ changeKind += 1
++ end
+ if filesModified>0
+ mail.print(" + ") if changeKind>0
+ mail.print("#{filesModified} modified")
+ changeKind += 1
+ end
++ if filesModifiedProps>0
++ mail.print(" + ") if changeKind>0
++ mail.print("#{filesModifiedProps} modified properties")
++ changeKind += 1
++ end
+ mail.print(", total #{totalFilesChanged}") if changeKind > 1
+ mail.puts(" files</small><br />")
+ end
+@@ -1667,12 +1886,13 @@
+ # CVSROOT/users file, if the file exists. The argument is returned unchanged
+ # if no alias is found.
+ def sender_alias(email)
++ blah("Lookup '#{email}' from users file")
+ if File.exists?($users_file)
+ File.open($users_file) do |io|
+ io.each_line do |line|
+ if line =~ /^([^:]+)\s*:\s*(['"]?)([^\n\r]+)(\2)/
+ if email.address == $1
+- return EmailAddress.new($3)
++ return EmailAddress.new($3, $users_file_charset)
+ end
+ end
+ end
+@@ -1686,6 +1906,8 @@
# sensible header formatting, and for ensuring that the body is seperated
# from the message headers by a blank line (as it is required to be).
class MailContext
def initialize(io)
@done_headers = false
@io = io
-@@ -1695,8 +1787,8 @@
+@@ -1695,8 +1917,8 @@
# called
def header(name, value)
raise "headers already commited" if @done_headers
else
@io.puts("#{name}: #{value}")
end
-@@ -1769,7 +1861,7 @@
+@@ -1769,7 +1991,7 @@
ctx.header("To", recipients.map{|addr| addr.encoded}.join(','))
blah("Mail From: <#{from}>")
ctx.header("From", from.encoded) if from
yield ctx
end
end
-@@ -1800,10 +1892,10 @@
+@@ -1800,10 +2022,10 @@
return unless $fileEntries.length == 1
file = $fileEntries[0]
name = zap_header_special_chars(file.path)
mail.header("Message-ID", make_msg_id("#{name}.#{file.toVer}", $hostname))
end
end
-@@ -1834,6 +1926,14 @@
+@@ -1834,8 +2056,18 @@
end
end
mail.header("X-Mailer", "CVSspam #{$version} <http://www.badgers-in-foil.co.uk/projects/cvsspam/>")
mail.body do |body|
make_html_email(body)
---- cvsspam-0.2.12/record_lastdir.rb 2005-07-11 18:53:29.000000000 +0300
-+++ cvsspam/record_lastdir.rb 2006-12-21 11:44:26.827358000 +0200
-@@ -4,7 +4,6 @@
- # http://www.badgers-in-foil.co.uk/projects/cvsspam/
- # Copyright (c) David Holroyd
-
--$repositorydir = ARGV.shift
-
- $tmpdir = ENV["TMPDIR"] || "/tmp"
-
-@@ -19,6 +18,36 @@
- nil
+ end
end
-
+
-+# transform any special / unexpected characters appearing in the argument to
-+# --from so that they will not cause problems if the value is inserted into
-+# a file or directory name
-+def make_fromaddr_safe_for_filename(addr)
-+ addr.gsub(/[^a-zA-Z0-1.,_-]/, "_")
-+end
-+
-+# Option processing doesn't use GetoptLong (for the moment) bacause arguments
-+# given to this script by CVS include the names of committed files. It
-+# seems quite possible that one of those file names could begin with a '-'
-+# and therefore be treated by GetoptLong as a value which requires processing.
-+# This would probably result in an error.
-+#
-+# [That could be worked around by placing a '--' option (which tells GetoptLong
-+# to stop processing option arguments) at the very end of the arguments to
-+# record_lastdir.rb in commitinfo, but that's very easily forgotten, and isn't
-+# really backwards compatable with the behaviour of older CVSspam releases.]
-+if ARGV.first == "--from"
-+ # we could, of course, be tricked, if the first committed file in the list
-+ # happened to be named '--from' :S
-+
-+ # drop the "--from"
-+ ARGV.shift
-+ # and use the value which was given following the option,
-+ $dirtemplate << "." << make_fromaddr_safe_for_filename(ARGV.shift)
-+end
-+
-+$repositorydir = ARGV.shift
-+
- $datadir = find_data_dir()
-
- if $datadir==nil
++# vim:et:ts=2:sw=2
+
+Property changes on: testcases/data/remove.png
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+
+Property changes on: testcases/data/fiddlyedits.after
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+
+Property changes on: testcases/data/fiddlyedits.before
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+
+Property changes on: testcases/data/add.png
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+
+Property changes on: testcases/README
+___________________________________________________________________
+Deleted: svn:executable
+ - *
+
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:ignore
+ + .project
+
+