From AGibson at carrieraccess.com Thu Sep 13 23:31:02 2007 From: AGibson at carrieraccess.com (Gibson, Gabbie) Date: Thu, 13 Sep 2007 17:31:02 -0600 Subject: [cvsspam-devel] Possible to receive entire file? Message-ID: <74C09F030C328B40823FD24005E0664E78A710@camailsvr01.carrieraccess.com> Hi, I was wondering if it was possible to configure CVSSpam to email the entire file with the changes in it. And if so, how? Thanks, -Gabbie -------------- next part -------------- An HTML attachment was scrubbed... URL: http://lists.badgers-in-foil.co.uk/pipermail/cvsspam-devel/attachments/20070913/6b208981/attachment.htm From jot3 at cs.stanford.edu Fri Sep 14 00:33:50 2007 From: jot3 at cs.stanford.edu (Jerry Talton) Date: Thu, 13 Sep 2007 17:33:50 -0700 Subject: [cvsspam-devel] CVSSPAM and SVN Message-ID: <81fd61630709131733j8ed317foec98a6643232b4ef@mail.gmail.com> I recently installed CVSSPAM in a subversion repository that I'm helping maintain, with the usual post-commit script to call svn_post_commit_hook.rb. For the most part, it works great, but any time I add or change a binary file I get the following: Warning: 'post-commit' hook failed with error output: /web/svn/elysium/hooks/svn_post_commit_hook.rb:155:in `assert_next': undefined local variable or method `lines' for # (NameError) from /web/svn/elysium/hooks/svn_post_commit_hook.rb:164:in `read_modified_diff' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:278:in `process_svnlook_diff' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:274:in `svnlook' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:94:in `safer_popen' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:45:in `popen' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:45:in `safer_popen' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:94:in `svnlook' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:274:in `process_svnlook_diff' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:299:in `process_commit' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:297:in `open' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:297:in `process_commit' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:307:in `main' from /web/svn/elysium/hooks/svn_post_commit_hook.rb:313 I am really only a novice Ruby programmer: can someone suggest a workaround? It seems as if it shouldn't try to read_modified_diff on binary files, right? Any help would be *much* appreciated!! -- Jerry Talton jtalton at cs.stanford.edu http://www.stanford.edu/~jtalton From jot3 at cs.stanford.edu Mon Sep 17 02:43:31 2007 From: jot3 at cs.stanford.edu (Jerry Talton) Date: Sun, 16 Sep 2007 19:43:31 -0700 Subject: [cvsspam-devel] CVSSPAM and SVN In-Reply-To: <81fd61630709131733j8ed317foec98a6643232b4ef@mail.gmail.com> References: <81fd61630709131733j8ed317foec98a6643232b4ef@mail.gmail.com> Message-ID: <81fd61630709161943u6ac12163n65e7d90d8e068e7b@mail.gmail.com> I finally hacked together the following to fix my problem. Perhaps someone could clean it up and include it in the next release? It was pretty simple to prevent the system from crashing by detecting binary files in the diff, but since you don't get version numbers from SVN (that I can tell) the output could certainly be better. --- svn_post_commit_hook.rb.old 2007-09-16 19:38:22.810211671 -0700 +++ svn_post_commit_hook.rb.new 2007-09-16 19:38:29.748493645 -0700 @@ -152,16 +152,25 @@ def assert_next(re=nil) raise "unexpected end of text" unless next_line unless re.nil? - raise "unexpected #{lines.current.inspect}" unless @line =~ re + raise "unexpected #{lines.current.inspect}" unless @line =~ re || @line =~ /^\(Binary.*$/ end $~ end + + def catch_binary + @line =~ /^\(Binary.*$/ ? true : false + end end def read_modified_diff(out, lines, path) lines.assert_next(/^=+$/) m = lines.assert_next(/^---.*\(rev (\d+)\)$/) + if lines.catch_binary + out.puts "#V NA,NA" + out.puts "#M #{path}" + return + end prev_rev = m[1].to_i diff1 = lines.current m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) @@ -179,6 +188,11 @@ def read_added_diff(out, lines, path) lines.assert_next(/^=+$/) m = lines.assert_next(/^---.*\(rev (\d+)\)$/) + if lines.catch_binary + out.puts "#V NA,NA" + out.puts "#A #{path}" + return + end prev_rev = m[1].to_i diff1 = lines.current m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) @@ -196,6 +210,11 @@ def read_deleted_diff(out, lines, path) lines.assert_next(/^=+$/) m = lines.assert_next(/^---.*\(rev (\d+)\)$/) + if lines.catch_binary + out.puts "#V NA,NA" + out.puts "#R #{path}" + return + end prev_rev = m[1].to_i diff1 = lines.current m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) On 9/13/07, Jerry Talton wrote: > I recently installed CVSSPAM in a subversion repository that I'm > helping maintain, with the usual post-commit script to call > svn_post_commit_hook.rb. For the most part, it works great, but any > time I add or change a binary file I get the following: > > Warning: 'post-commit' hook failed with error output: > /web/svn/elysium/hooks/svn_post_commit_hook.rb:155:in `assert_next': > undefined local variable or method `lines' for > # (NameError) > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:164:in > `read_modified_diff' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:278:in > `process_svnlook_diff' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:274:in `svnlook' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:94:in `safer_popen' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:45:in `popen' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:45:in `safer_popen' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:94:in `svnlook' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:274:in > `process_svnlook_diff' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:299:in > `process_commit' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:297:in `open' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:297:in > `process_commit' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:307:in `main' > from /web/svn/elysium/hooks/svn_post_commit_hook.rb:313 > > I am really only a novice Ruby programmer: can someone suggest a > workaround? It seems as if it shouldn't try to read_modified_diff on > binary files, right? > > Any help would be *much* appreciated!! > -- Jerry Talton jtalton at cs.stanford.edu http://www.stanford.edu/~jtalton From jfoy at btiphotonics.com Thu Sep 20 16:04:29 2007 From: jfoy at btiphotonics.com (Jeff Foy) Date: Thu, 20 Sep 2007 12:04:29 -0400 Subject: [cvsspam-devel] $USER in the --to argument. Message-ID: <980B0091141F8143A0F6465E8571199F404AE7@BTIExchange.gridway.bti.local> Hi, I was wondering if there is a way to use the $USER variable in the --to argument similar to the --from, to send an e-mail to the user who is committing the change, simply to have a record or what they've changed. Thanks, jeff. From tomk at gentoo.org Fri Sep 21 13:27:29 2007 From: tomk at gentoo.org (Tom Knight) Date: Fri, 21 Sep 2007 14:27:29 +0100 Subject: [cvsspam-devel] Hack to get plain text diffs Message-ID: <20070921132728.GA4139@squark.bogus> Hi, I knocked together this quick hack to get cvsspam to send both a plain text and HTML diffs out as they switched to using cvsspam at work. I don't really know much ruby so I'm sure it could be done much better but it works for now. I saw that this was one of the things on the TODO list so thought I'd pass it on in case it's of any use. All the changes are in cvsspam.rb based on the 0.2.12 version, the rand_string function was borrowed from here (not 100% sure what the license is but looks like it's public domain): http://snippets.dzone.com/posts/show/491 Anything I've added is GPL2. Hopefully it'll be of use, if not throw it away and do it properly, I don't really mind :) Tom -- Tom Knight tomk at gentoo.org GPG Public Key: http://dev.gentoo.org/~tomk/tomk.asc -------------- next part -------------- --- ../cvsspam2/cvsspam-0.2.12/cvsspam.rb 2005-07-11 16:53:29.000000000 +0100 +++ cvsspam.rb 2007-09-21 11:08:17.000000000 +0100 @@ -590,6 +590,42 @@ end end +# outputs commit log comment text supplied by LogReader as preformatted Text +class TextCommentHandler < LineConsumer + def initialize + @lastComment = nil + end + + def setup + @haveBlank = false + @comment = "" + end + + def consume(line) + if line =~ /^\s*$/ + @haveBlank = true + else + if @haveBlank + @comment += "\n" + @haveBlank = false + end + $mailSubject = line unless $mailSubject.length > 0 + @comment += line += "\n" + end + end + + def teardown + unless @comment == @lastComment + #println("
")
+      encoded = @comment
+      $commentEncoder.gsub!(encoded)
+      println(encoded)
+      #println("
") + @lastComment = @comment + end + end +end + # Handle lines from LogReader that represent the name of the branch tag for # the next file in the log. When files are committed to the trunk, the log @@ -649,6 +685,31 @@ end end +# Reads a line giving the path and name of the current file being considered +# from our log of all files changed in this commit. Subclasses make different +# records depending on whether this commit adds, removes, or just modifies this +# file +class TextFileHandler < LineConsumer + def setTagHandler(handler) + @tagHandler = handler + end + + def consume(line) + $file = FileEntry.new(line) + if $diff_output_limiter.choose_to_limit? + $file.has_diff = false + end + #$fileEntries << $file + $file.tag = getTag + handleFile($file) + end + + protected + def getTag + @tagHandler.getLastTag + end +end + # A do-nothing superclass for objects that know how to create hyperlinks to # web CVS interfaces (e.g. CVSweb). Subclasses overide these methods to # wrap HTML link tags arround the text that this classes methods generate. @@ -659,6 +720,11 @@ htmlEncode(path) end + # text path + def textpath(path, tag) + path + end + # Just returns the value of the 'version' argument. Subclasses should change # this into a link to the given version of the file. def version(path, version) @@ -670,6 +736,11 @@ def diff(file) '->' end + + # text diff + def textdiff(file) + '->' + end end # Superclass for objects that can link to CVS frontends on the web (ViewCVS, @@ -810,6 +881,31 @@ end end +# Note when LogReader finds record of a file that was added in this commit +class TextAddedFileHandler < TextFileHandler + def handleFile(file) + file.type="A" + file.toVer=$toVer + end +end + +# Note when LogReader finds record of a file that was removed in this commit +class TextRemovedFileHandler < TextFileHandler + def handleFile(file) + file.type="R" + file.fromVer=$fromVer + end +end + +# Note when LogReader finds record of a file that was modified in this commit +class TextModifiedFileHandler < TextFileHandler + def handleFile(file) + file.type="M" + file.fromVer=$fromVer + file.toVer=$toVer + end +end + # Used by UnifiedDiffHandler to record the number of added and removed lines # appearing in a unidiff. @@ -1030,6 +1126,160 @@ end end +# Used by TextUnifiedDiffHandler to produce an Text +class TextUnifiedDiffColouriser < LineConsumer + def initialize + @currentState = "@" + @currentStyle = "info" + @lineJustDeleted = nil + @lineJustDeletedSuperlong = false + @truncatedLineCount = 0 + end + + def output=(io) + @emailIO = io + end + + def consume(line) + initial = line[0,1] + superlong_line = false + if $maxDiffLineLength && line.length > $maxDiffLineLength+1 + line = line[0, $maxDiffLineLength+1] + superlong_line = true + @truncatedLineCount += 1 + end + if initial != @currentState + prefixLen = 1 + suffixLen = 0 + if initial=="+" && @currentState=="-" && @lineJustDeleted!=nil + # may be an edit, try to highlight the changes part of the line + a = line[1,line.length-1] + b = @lineJustDeleted[1, at lineJustDeleted.length-1] + prefixLen = commonPrefixLength(a, b)+1 + suffixLen = commonPrefixLength(a.reverse, b.reverse) + # prevent prefix/suffux having overlap, + suffixLen = min(suffixLen, min(line.length, at lineJustDeleted.length)-prefixLen) + deleteInfixSize = @lineJustDeleted.length - (prefixLen+suffixLen) + 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 + print(@lineJustDeleted) + else + print(@lineJustDeleted[0,prefixLen]) + print(@lineJustDeleted[prefixLen,deleteInfixSize]) + print(@lineJustDeleted[@lineJustDeleted.length-suffixLen,suffixLen]) + end + if superlong_line + println("[...]") + else + println("") + end + @lineJustDeleted = nil + end + if initial=="-" + @lineJustDeleted=line + @lineJustDeletedSuperlong = superlong_line + shift(initial) + # we'll print it next time (fingers crossed) + return + elsif @lineJustDeleted!=nil + print(@lineJustDeleted) + if @lineJustDeletedSuperlong + println("[...]") + else + println("") + end + @lineJustDeleted = nil + end + shift(initial) + if prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change + encoded = line + else + encoded = line[0,prefixLen] + + line[prefixLen,addInfixSize] + + line[line.length-suffixLen,suffixLen] + end + else + encoded = line + end + if initial=="-" + unless @lineJustDeleted==nil + print(@lineJustDeleted) + if @lineJustDeletedSuperlong + println("[...]") + else + println("") + end + @lineJustDeleted=nil + end + end + print(encoded) + if superlong_line + println("[...]") + else + println("") + end + end + + def teardown + unless @lineJustDeleted==nil + print(@lineJustDeleted) + if @lineJustDeletedSuperlong + println("[...]") + else + println("") + end + @lineJustDeleted = nil + end + shift(nil) + if @truncatedLineCount>0 + println("[Note: Some over-long lines of diff output only partialy shown]") + end + end + + # start the diff output, using the given lines as the 'preamble' bit + def start_output(*lines) + println("--------------------------------------------------------------------------------") + case $file.type + when "A" + print($frontend.textpath($file.basedir, $file.tag)) + println("\n") + println("#{$file.file} added at #{$frontend.version($file.path,$file.toVer)}") + when "R" + print($frontend.textpath($file.basedir, $file.tag)) + println("\n") + println("#{$file.file} removed after #{$frontend.version($file.path,$file.fromVer)}") + when "M" + print($frontend.textpath($file.basedir, $file.tag)) + println("\n") + println("#{$file.file} #{$frontend.version($file.path,$file.fromVer)} #{$frontend.textdiff($file)} #{$frontend.version($file.path,$file.toVer)}") + end + lines.each do |line| + println(line) + end + end + + private + + def formatChange(text) + return '^M' if text=="\r" + end + + def shift(nextState) + @currentState = nextState + end + + def commonPrefixLength(a, b) + length = 0 + a.each_byte do |char| + break unless b[length]==char + length = length + 1 + end + return length + end +end + # Handle lines from LogReader that are the output from 'cvs diff -u' for the # particular file under consideration @@ -1084,6 +1334,57 @@ end end +# Handle lines from LogReader that are the output from 'cvs diff -u' for the +# particular file under consideration for Text +class TextUnifiedDiffHandler < LineConsumer + def setup + @stats = UnifiedDiffStats.new + @colour = TextUnifiedDiffColouriser.new + @colour.output = @emailIO + @lookahead = nil + end + + def consume(line) + case lineno() + when 1 + @diffline = line + when 2 + @lookahead = line + when 3 + if $file.wants_diff_in_mail? + @colour.start_output(@diffline, @lookahead, line) + end + else + @stats.consume(line) + if $file.wants_diff_in_mail? + if $maxLinesPerDiff.nil? || @stats.diffLines < $maxLinesPerDiff + @colour.consume(line) + elsif @stats.diffLines == $maxLinesPerDiff + @colour.consume(line) + @colour.teardown + end + end + end + end + + def teardown + if @lookahead == nil + $file.isEmpty = true + elsif @lookahead =~ /Binary files .* and .* differ/ + $file.isBinary = true + else + if $file.wants_diff_in_mail? + if $maxLinesPerDiff && @stats.diffLines > $maxLinesPerDiff + println("[truncated at #{$maxLinesPerDiff} lines; #{@stats.diffLines-$maxLinesPerDiff} more skipped]") + else + @colour.teardown + end + $file.has_diff = true + end + end + end +end + # a filter that counts the number of characters output to the underlying object class OutputCounter @@ -1381,6 +1682,18 @@ $handlers["R"].setTagHandler(tagHandler) $handlers["M"].setTagHandler(tagHandler) +$texthandlers = Hash[">" => TextCommentHandler.new, + "U" => TextUnifiedDiffHandler.new, + "T" => tagHandler, + "A" => TextAddedFileHandler.new, + "R" => TextRemovedFileHandler.new, + "M" => TextModifiedFileHandler.new, + "V" => VersionHandler.new] + +$texthandlers["A"].setTagHandler(tagHandler) +$texthandlers["R"].setTagHandler(tagHandler) +$texthandlers["M"].setTagHandler(tagHandler) + $fileEntries = Array.new $task_list = Array.new $allTags = Hash.new @@ -1403,6 +1716,24 @@ end +File.open("#{$logfile}.emailtexttmp", File::RDWR|File::CREAT|File::TRUNC) do |mail| + + $diff_output_limiter = OutputSizeLimiter.new(mail, $mail_size_limit) + + File.open($logfile) do |log| + reader = LogReader.new(log) + + until reader.eof + handler = $texthandlers[reader.currentLineCode] + if handler == nil + raise "No handler file lines marked '##{reader.currentLineCode}'" + end + handler.handleLines(reader.getLines, $diff_output_limiter) + end + end + +end + if $subjectPrefix == nil $subjectPrefix = "[CVS #{Repository.array.join(',')}]" end @@ -1432,7 +1763,10 @@ # generate the email header (and footer) having already generated the diffs # for the email body to a temp file (which is simply included in the middle) -def make_html_email(mail) +def make_html_email(mail, boundary) + mail.puts("--#{boundary}") + mail.puts("Content-Type: text/html;" + ($charset.nil? ? "" : "; charset=\"#{$charset}\"")) + mail.puts("Content-Disposition: inline\n\n"); mail.puts(< @@ -1660,7 +1994,7 @@ mail.puts("
CVSspam #{$version}
") mail.puts("") - + mail.puts("\n\n--#{boundary}--\n\n") end # Tries to look up an 'alias' email address for the given string in the @@ -1818,11 +2152,188 @@ $from_address = sender_alias($from_address) unless $from_address.nil? +def rand_string(len) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + newpass = "" + 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } + return newpass +end + +def make_text_email(mail, boundary) + mail.puts(< 1 + mail.print tagCount0 + totalLinesAdded += file.lineAdditions + mail.print("+#{file.lineAdditions} ") + end + if file.lineRemovals>0 + totalLinesRemoved += file.lineRemovals + mail.print("-#{file.lineRemovals} ") + end + end + if last_repository.has_multiple_tags + if file.tag + mail.print("#{file.tag} ") + else + mail.print("MAIN ") + end + end + if file.addition? + mail.print("added #{$frontend.version(file.path,file.toVer)} ") + elsif file.removal? + mail.print("#{$frontend.version(file.path,file.fromVer)} removed ") + elsif file.modification? + mail.print("#{$frontend.version(file.path,file.fromVer)} #{$frontend.textdiff(file)} #{$frontend.version(file.path,file.toVer)} ") + end + + mail.puts("\n") + end + if $fileEntries.size>1 && (totalLinesAdded+totalLinesRemoved)>0 + if totalLinesAdded>0 + mail.print("+#{totalLinesAdded} ") + end + if totalLinesRemoved>0 + mail.print("-#{totalLinesRemoved} ") + end + mail.puts("\n") + end + + totalFilesChanged = filesAdded+filesRemoved+filesModified + if totalFilesChanged > 1 + changeKind = 0 + if filesAdded>0 + mail.print("#{filesAdded} added") + changeKind += 1 + end + if filesRemoved>0 + mail.print(" + ") if changeKind>0 + mail.print("#{filesRemoved} removed") + changeKind += 1 + end + if filesModified>0 + mail.print(" + ") if changeKind>0 + mail.print("#{filesModified} modified") + changeKind += 1 + end + mail.print(", total #{totalFilesChanged}") if changeKind > 1 + mail.puts(" files\n") + end + + if $task_list.size > 0 + task_count = 0 + $task_list.each do |item| + task_count += 1 + item = htmlEncode(item) + mail.puts("* #{item}\n") + end + end + + + File.open("#{$logfile}.emailtexttmp") do |input| + input.each do |line| + mail.puts(line.chomp) + end + end + if $diff_output_limiter.choose_to_limit? + mail.puts("[Reached #{$diff_output_limiter.written_count} bytes of diffs.") + mail.puts("Since the limit is about #{$mail_size_limit} bytes,") + mail.puts("a further #{$diff_output_limiter.total_count-$diff_output_limiter.written_count} were skipped.]") + end + if $debug + blah("leaving file #{$logfile}.emailtexttmp") + else + File.unlink("#{$logfile}.emailtexttmp") + end + + mail.puts("CVSspam #{$version}") +end + mailer.send($from_address, $recipients) do |mail| mail.header("Subject", mailSubject) inject_threading_headers(mail) mail.header("MIME-Version", "1.0") - mail.header("Content-Type", "text/html" + ($charset.nil? ? "" : "; charset=\"#{$charset}\"")) + boundary = rand_string(32) + mail.header("Content-Type", "multipart/alternative; boundary=\"#{boundary}\"") + mail.header("Content-Disposition", "inline") if ENV['REMOTE_HOST'] # TODO: I think this will always be an IP address. If a hostname is # possible, it may need encoding of some kind, @@ -1836,6 +2347,7 @@ mail.header("X-Mailer", "CVSspam #{$version} ") mail.body do |body| - make_html_email(body) + make_text_email(body, boundary) + make_html_email(body, boundary) end end -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 191 bytes Desc: not available Url : http://lists.badgers-in-foil.co.uk/pipermail/cvsspam-devel/attachments/20070921/5764afe2/attachment.pgp From glen at delfi.ee Thu Sep 27 14:33:59 2007 From: glen at delfi.ee (Elan =?iso-8859-15?q?Ruusam=E4e?=) Date: Thu, 27 Sep 2007 17:33:59 +0300 Subject: [cvsspam-devel] Hack to get plain text diffs In-Reply-To: <20070921132728.GA4139@squark.bogus> References: <20070921132728.GA4139@squark.bogus> Message-ID: <200709271733.59889.glen@delfi.ee> On Friday 21 September 2007 16:27:29 Tom Knight wrote: > Hi, > > I knocked together this quick hack to get cvsspam to send both a plain text > and HTML diffs out as they switched to using cvsspam at work. I don't > really know much ruby so I'm sure it could be done much better but it works > for now. I saw that this was one of the things on the TODO list so thought > I'd pass it on in case it's of any use. > > All the changes are in cvsspam.rb based on the 0.2.12 version, the > rand_string function was borrowed from here (not 100% sure what the license > is but looks like it's public domain): > > http://snippets.dzone.com/posts/show/491 > > Anything I've added is GPL2. > > Hopefully it'll be of use, if not throw it away and do it properly, I don't > really mind :) nice (looks working) but shouldn't you also trim html links from the header in case $cvswebURL (and other $*URL) are defined? Commit in test/utf8 on MAIN xxx +1 -4 1.2 -> 1.3 - check textdiff -------------------------------------------------------------------------------- test/utf8 xxx 1.2 -> 1.3 diff -u -r1.2 -r1.3 > Tom -- glen