/[cvs]/jonen/ruby/CVSspam/cvsspam.rb
ViewVC logotype

Contents of /jonen/ruby/CVSspam/cvsspam.rb

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.14 - (show annotations)
Sun Jan 19 03:57:36 2003 UTC (21 years, 11 months ago) by jonen
Branch: MAIN
CVS Tags: HEAD
Changes since 1.13: +3 -2 lines
+ clean up

1 #!/usr/bin/ruby
2
3 # collect_diffs.rb expects to find this script in the same directory as it
4 #
5
6
7
8 # TODO: exemplify syntax for 'cvs admin -m' when log message is missing
9 # TODO: make max-line limit on diff output configurable
10 # TODO: put max size limit on whole email
11 # TODO: support non-html mail too (text/plain, multipart/alternative)
12
13 $maxSubjectLength = 200
14 $maxLinesPerDiff = 1000
15 $charset = nil # nil implies 'don't specify a charset'
16
17 def blah(text)
18 puts("cvsspam.rb: #{text}") if $debug
19 end
20
21 def min(a, b)
22 a<b ? a : b
23 end
24
25
26 # the regexps given as keys must not use capturing subexpressions '(...)'
27 class MultiSub
28 def initialize(hash)
29 @mash = Array.new
30 expr = nil
31 hash.each do |key,val|
32 if expr == nil ; expr="(" else expr<<"|(" end
33 expr << key << ")"
34 @mash << val
35 end
36 @re = Regexp.new(expr)
37 end
38
39 def gsub!(text)
40 text.gsub!(@re) { |match|
41 idx = -1
42 $~.to_a.each { |subexp|
43 break unless idx==-1 || subexp==nil
44 idx += 1
45 }
46 idx==-1 ? match : @mash[idx].call(match)
47 }
48 end
49 end
50
51 class LogReader
52 def initialize(logIO)
53 @io = logIO
54 advance
55 end
56
57 def currentLineCode ; @line[1,1] end
58
59
60 class ConstrainedIO
61 def initialize(reader)
62 @reader = reader
63 @linecode = reader.currentLineCode
64 end
65
66 def each
67 return if @reader == nil
68 while true
69 yield @reader.currentLine
70 break unless @reader.advance && currentValid?
71 end
72 @reader = nil
73 end
74
75 def gets
76 return nil if @reader == nil
77 line = @reader.currentLine
78 return nil if line==nil || !currentValid?
79 @reader.advance
80 return line
81 end
82
83 def currentValid?
84 @linecode == @reader.currentLineCode
85 end
86 end
87
88 def getLines
89 ConstrainedIO.new(self)
90 end
91
92 def eof ; @line==nil end
93
94 def advance
95 @line = @io.gets
96 return false if @line == nil
97 unless @line[0,1] == "#"
98 raise "#{$logfile}:#{@io.lineno} line did not begin with '#': #{@line}"
99 end
100 return true
101 end
102
103 def currentLine
104 @line==nil ? nil : @line[3, @line.length-4]
105 end
106 end
107
108
109 def htmlEncode(text)
110 text.gsub(/./) do
111 case $&
112 when "&" then "&amp;"
113 when "<" then "&lt;"
114 when ">" then "&gt;"
115 when "\"" then "&quot;"
116 else $&
117 end
118 end
119 end
120
121 # actually, allows '/' to appear
122 def urlEncode(text)
123 text.sub(/[^a-zA-Z0-9\-,.*_\/]/) do
124 "%#{sprintf('%2X', $&[0])}"
125 end
126 end
127
128 # a top-level directory under the $CVSROOT
129 class Repository
130 @@repositories = Hash.new
131
132 def initialize(name)
133 @name = name
134 @common_prefix = nil
135 end
136
137 # calculate the path prefix shared by all files commited to this
138 # reposotory
139 def merge_common_prefix(path)
140 if @common_prefix == nil
141 @common_prefix = path.dup
142 else
143 path = path.dup
144 until @common_prefix == path
145 if @common_prefix.size>path.size
146 if @common_prefix.sub!(/(.*)\/.*$/, '\1').nil?
147 raise "unable to merge '#{path}' in to '#{@common_prefix}': prefix totally different"
148 end
149 else
150 if path.sub!(/(.*)\/.*$/, '\1').nil?
151 raise "unable to merge '#{path}' in to '#{@common_prefix}': prefix totally different"
152 end
153 end
154 end
155 end
156 end
157
158 attr_reader :name, :common_prefix
159
160 def Repository.get(name)
161 name =~ /^[^\/]+/
162 name = $&
163 rep = @@repositories[name]
164 if rep.nil?
165 rep = Repository.new(name)
166 @@repositories[name] = rep
167 end
168 rep
169 end
170
171 def Repository.count
172 @@repositories.size
173 end
174
175 def Repository.each
176 @@repositories.each_value do |rep|
177 yield rep
178 end
179 end
180
181 def Repository.array
182 @@repositories.values
183 end
184
185 def to_s
186 @name
187 end
188 end
189
190 class FileEntry
191 def initialize(path)
192 @path = path
193 @lineAdditions = @lineRemovals = 0
194 @repository = Repository.get(path)
195 @repository.merge_common_prefix(basedir())
196 end
197
198 attr_accessor :path, :type, :lineAdditions, :lineRemovals, :isBinary, :isEmpty, :tag, :fromVer, :toVer
199
200 def file
201 @path =~ /.*\/(.*)/
202 $1
203 end
204
205 def basedir
206 @path =~ /(.*)\/.*/
207 $1
208 end
209
210 def repository
211 @repository
212 end
213
214 def name_after_common_prefix
215 @path.slice(@repository.common_prefix.size+1,@path.size-@repository.common_prefix.size-1)
216 end
217
218 def removal?
219 @type == "R"
220 end
221
222 def addition?
223 @type == "A"
224 end
225
226 def modification?
227 @type == "M"
228 end
229 end
230
231
232 class LineConsumer
233 def handleLines(lines, emailIO)
234 @emailIO = emailIO
235 @lineCount = 0
236 setup
237 lines.each do |line|
238 @lineCount += 1
239 consume(line)
240 end
241 teardown
242 end
243
244 def setup
245 end
246
247 def teardown
248 end
249
250 def lineno
251 @lineCount
252 end
253
254 def println(text)
255 @emailIO.puts(text)
256 end
257
258 def print(text)
259 @emailIO.print(text)
260 end
261 end
262
263
264 mailSub = proc { |match| "<a href=\"mailto:#{match}\">#{match}</a>" }
265 urlSub = proc { |match| "<a href=\"#{match}\">#{match}</a>" }
266 bugSub = proc { |match|
267 match =~ /([0-9]+)/
268 "<a href=\"#{$bugzillaURL.sub(/%s/, $1)}\">#{match}</a>"
269 }
270 commentSubstitutions = {
271 '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
272 '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub}
273
274
275 class CommentHandler < LineConsumer
276 def setup
277 @haveBlank = false
278 @comment = ""
279 end
280
281 def consume(line)
282 if line =~ /^\s*$/
283 @haveBlank = true
284 else
285 if @haveBlank
286 @comment += "\n"
287 @haveBlank = false
288 end
289 $mailSubject = line if $mailSubject == nil
290 @comment += line += "\n"
291 end
292 end
293
294 def teardown
295 unless @comment == @lastComment
296 println("<pre class=\"comment\">")
297 encoded = htmlEncode(@comment)
298 $commentEncoder.gsub!(encoded)
299 println(encoded)
300 println("</pre>")
301 @lastComment = @comment
302 end
303 end
304 end
305
306
307 class TagHandler < LineConsumer
308 def consume(line)
309 # TODO: check there is only one line
310 @tag = line
311 $haveTag = true
312 end
313
314 def getLastTag
315 tmp = @tag
316 @tag = nil
317 tmp
318 end
319 end
320
321 class VersionHandler < LineConsumer
322 def consume(line)
323 # TODO: check there is only one line
324 $fromVer,$toVer = line.split(/,/)
325 end
326 end
327
328
329 class FileHandler < LineConsumer
330 def setTagHandler(handler)
331 @tagHandler = handler
332 end
333
334 def setup
335 $fileHeaderHtml = ''
336 println("<hr noshade=\"nodhade\" size=\"1\" /><a name=\"file#{$fileEntries.size+1}\" /><div class=\"file\">")
337 end
338
339 def consume(line)
340 $file = FileEntry.new(line)
341 $fileEntries << $file
342 $file.tag = getTag
343 handleFile($file)
344 end
345
346 protected
347 def getTag
348 @tagHandler.getLastTag
349 end
350
351 def println(text)
352 $fileHeaderHtml << text << "\n"
353 end
354
355 def print(text)
356 $fileHeaderHtml << text
357 end
358 end
359
360 # if using viewcvs, create a link to the named path's dir listing
361 def viewcvsPath(path)
362 if $viewcvsURL == nil
363 htmlEncode(path)
364 else
365 "<a href=\"#{$viewcvsURL}#{urlEncode(path)}\">#{htmlEncode(path)}</a>"
366 end
367 end
368
369 def viewcvsFile(path, version)
370 if $viewcvsURL == nil
371 version
372 else
373 "<a href=\"#{$viewcvsURL}#{urlEncode(path)}?rev=#{version}&content-type=text/vnd.viewcvs-markup\">#{version}</a>"
374 end
375 end
376
377
378 # in need of refactoring...
379
380 class AddedFileHandler < FileHandler
381 def handleFile(file)
382 file.type="A"
383 file.toVer=$toVer
384 print("<span class=\"pathname\" id=\"added\">")
385 print(viewcvsPath(file.basedir))
386 println("<br /></span>")
387 println("<div class=\"fileheader\" id=\"added\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">added at #{viewcvsFile(file.path,file.toVer)}</small></div>")
388 end
389 end
390
391 class RemovedFileHandler < FileHandler
392 def handleFile(file)
393 file.type="R"
394 file.fromVer=$fromVer
395 print("<span class=\"pathname\" id=\"removed\">")
396 print(viewcvsPath(file.basedir))
397 println("<br /></span>")
398 println("<div class=\"fileheader\" id=\"removed\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">removed after #{viewcvsFile(file.path,file.fromVer)}</small></div>")
399 end
400 end
401
402 def viewcvsDiff(file)
403 if $viewcvsURL == nil || file.isBinary
404 "-&gt;"
405 else
406 "<a href=\"#{$viewcvsURL}#{urlEncode(file.path)}.diff?r1=#{file.fromVer}&r2=#{file.toVer}\">-&gt;</a>"
407 end
408 end
409
410 class ModifiedFileHandler < FileHandler
411 def handleFile(file)
412 file.type="M"
413 file.fromVer=$fromVer
414 file.toVer=$toVer
415 print("<span class=\"pathname\">")
416 print(viewcvsPath(file.basedir))
417 println("<br /></span>")
418 println("<div class=\"fileheader\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">#{viewcvsFile(file.path,file.fromVer)} #{viewcvsDiff(file)} #{viewcvsFile(file.path,file.toVer)}</small></div>")
419 end
420 end
421
422
423 class UnifiedDiffStats
424 def initialize
425 @diffLines=3 # the three initial lines in the unidiff
426 end
427
428 def diffLines
429 @diffLines
430 end
431
432 def consume(line)
433 @diffLines += 1
434 case line[0,1]
435 when "+" then $file.lineAdditions += 1
436 when "-" then $file.lineRemovals += 1
437 end
438 end
439 end
440
441 # TODO: change-within-line colourisation should really be comparing the
442 # set of lines just removed with the set of lines just added, but
443 # it currently considers just a single line
444
445 class UnifiedDiffColouriser < LineConsumer
446 def initialize
447 @currentState = "@"
448 @currentStyle = "info"
449 end
450
451 def output=(io)
452 @emailIO = io
453 end
454
455 def consume(line)
456 initial = line[0,1]
457 if initial != @currentState
458 prefixLen = 1
459 suffixLen = 0
460 if initial=="+" && @currentState=="-" && @lineJustDeleted!=nil
461 # may be an edit, try to highlight the changes part of the line
462 a = line[1,line.length-1]
463 b = @lineJustDeleted[1,@lineJustDeleted.length-1]
464 prefixLen = commonPrefixLength(a, b)+1
465 suffixLen = commonPrefixLength(a.reverse, b.reverse)
466 # prevent prefix/suffux having overlap,
467 suffixLen = min(suffixLen, min(line.length,@lineJustDeleted.length)-prefixLen)
468 deleteInfixSize = @lineJustDeleted.length - (prefixLen+suffixLen)
469 addInfixSize = line.length - (prefixLen+suffixLen)
470 oversize_change = deleteInfixSize*100/@lineJustDeleted.length>33 || addInfixSize*100/line.length>33
471
472 if prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
473 println(htmlEncode(@lineJustDeleted))
474 else
475 print(htmlEncode(@lineJustDeleted[0,prefixLen]))
476 print("<span id=\"removedchars\">")
477 print(formatChange(@lineJustDeleted[prefixLen,deleteInfixSize]))
478 print("</span>")
479 println(htmlEncode(@lineJustDeleted[@lineJustDeleted.length-suffixLen,suffixLen]))
480 end
481 @lineJustDeleted = nil
482 end
483 if initial=="-"
484 @lineJustDeleted=line
485 shift(initial)
486 # we'll print it next time (fingers crossed)
487 return
488 elsif @lineJustDeleted!=nil
489 println(htmlEncode(@lineJustDeleted))
490 @lineJustDeleted = nil
491 end
492 shift(initial)
493 if prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
494 encoded = htmlEncode(line)
495 else
496 encoded = htmlEncode(line[0,prefixLen]) +
497 "<span id=\"addedchars\">" +
498 formatChange(line[prefixLen,addInfixSize]) +
499 "</span>" +
500 htmlEncode(line[line.length-suffixLen,suffixLen])
501 end
502 else
503 encoded = htmlEncode(line)
504 end
505 if initial=="-"
506 unless @lineJustDeleted==nil
507 println(htmlEncode(@lineJustDeleted))
508 @lineJustDeleted=nil
509 end
510 end
511 if initial=="+"
512 if line =~ /\b(TODO\b.*)/
513 $todoList << $1
514 encoded.sub!(/\bTODO\b/, "<span id=\"TODO\">TODO</span>")
515 encoded = "<a name=\"todo#{$todoList.size}\" />#{encoded}"
516 elsif line =~ /\b(FIXME\b.*)/
517 $todoList << $1
518 encoded.sub!(/\FIXME\b/, "<span id=\"TODO\">FIXME</span>")
519 encoded = "<a name=\"todo#{$todoList.size}\" />#{encoded}"
520 end
521 # won't bother with /XXX/ (false positives more likely?)
522 end
523 println(encoded)
524 end
525
526 def teardown
527 unless @lineJustDeleted==nil
528 println(htmlEncode(@lineJustDeleted))
529 @lineJustDeleted = nil
530 end
531 shift(nil)
532 end
533
534 private
535
536 def formatChange(text)
537 return '<small id="info">^M</small>' if text=="\r"
538 htmlEncode(text).gsub(/ /, '&nbsp;')
539 end
540
541 def shift(nextState)
542 unless @currentState == nil
543 if @currentStyle == "info"
544 print("</small></pre>")
545 else
546 print("</pre>")
547 end
548 @currentStyle = case nextState
549 when "\\" then "info" # as in '\ No newline at end of file'
550 when "@" then "info"
551 when " " then "context"
552 when "+" then "added"
553 when "-" then "removed"
554 end
555 unless nextState == nil
556 if @currentStyle=='info'
557 print("<pre class=\"diff\"><small id=\"info\">")
558 else
559 print("<pre class=\"diff\" id=\"#{@currentStyle}\">")
560 end
561 end
562 end
563 @currentState = nextState
564 end
565
566 def commonPrefixLength(a, b)
567 length = 0
568 a.each_byte do |char|
569 break unless b[length]==char
570 length = length + 1
571 end
572 return length
573 end
574 end
575
576
577 class UnifiedDiffHandler < LineConsumer
578 def setup
579 @stats = UnifiedDiffStats.new
580 if $file.removal? && $no_removed_file_diff
581 def self.consume(line)
582 @stats.consume(line) if lineno() > 3
583 end
584 return
585 end
586 @colour = UnifiedDiffColouriser.new
587 @colour.output = @emailIO
588 @lookahead = nil
589 # evil here:
590 def self.consume(line)
591 case lineno()
592 when 1
593 @diffline = line
594 when 2
595 @lookahead = line
596 when 3
597 println($fileHeaderHtml)
598 # TODO: move to UnifiedDiffColouriser
599 print("<pre class=\"diff\"><small id=\"info\">")
600 println(htmlEncode(@diffline)) # 'diff ...'
601 println(htmlEncode(@lookahead)) # '--- ...'
602 println(htmlEncode(line)) # '+++ ...'
603
604 # now, redefine this method for the subsequent lines of input
605 def self.consume(line)
606 @stats.consume(line)
607 if @stats.diffLines < $maxLinesPerDiff
608 @colour.consume(line)
609 elsif @stats.diffLines == $maxLinesPerDiff
610 @colour.consume(line)
611 @colour.teardown
612 end
613 end
614 end
615 end
616 end
617
618 def teardown
619 if @lookahead == nil
620 $file.isEmpty = true
621 elsif @lookahead =~ /Binary files .* and .* differ/
622 $file.isBinary = true
623 else
624 unless $file.removal? && $no_removed_file_diff
625 if @stats.diffLines > $maxLinesPerDiff
626 println("</pre>")
627 println("<strong class=\"error\">[truncated at #{$maxLinesPerDiff} lines; #{@stats.diffLines-$maxLinesPerDiff} more skipped]</strong>")
628 else
629 @colour.teardown
630 end
631 println("</div>") # end of "file" div
632 end
633 end
634 end
635 end
636
637
638
639
640
641
642
643
644
645 $config = "#{ENV['CVSROOT']}/CVSROOT/cvsspam.conf"
646
647 $debug = false
648 $recipients = Array.new
649 $sendmail_prog = "/usr/sbin/sendmail"
650 $no_removed_file_diff = false
651
652 require 'getoptlong'
653
654 opts = GetoptLong.new(
655 [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ],
656 [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
657 [ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
658 )
659
660 opts.each do |opt, arg|
661 $recipients << arg if opt=="--to"
662 $config = arg if opt=="--config"
663 $debug = true if opt=="--debug"
664 end
665
666
667 if ARGV.length != 1
668 if ARGV.length > 1
669 $stderr.puts "extra arguments not needed: #{ARGV[1, ARGV.length-1].join(', ')}"
670 else
671 $stderr.puts "missing required file argument"
672 end
673 puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>"
674 exit(-1)
675 end
676
677 $logfile = ARGV[0]
678
679
680 $additionalHeaders = Array.new
681 $problemHeaders = Array.new
682
683 # helper functions called from the 'config file'
684 def addHeader(name, value)
685 if name =~ /^[!-9;-~]+$/
686 $additionalHeaders << [name, value]
687 else
688 $problemHeaders << [name, value]
689 end
690 end
691 def addRecipient(email)
692 $recipients << email
693 end
694
695 if FileTest.exists?($config)
696 load $config
697 else
698 blah("Config file '#{$config}' not found, ignoring")
699 end
700
701 if $recipients.empty?
702 fail "No email recipients defined"
703 end
704
705 $viewcvsURL << "/" if $viewcvsURL!=nil && $viewcvsURL!~/\/$/
706
707
708 unless $bugzillaURL == nil
709 commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugSub
710 end
711 $commentEncoder = MultiSub.new(commentSubstitutions)
712
713
714 tagHandler = TagHandler.new
715
716 $handlers = Hash[">" => CommentHandler.new,
717 "U" => UnifiedDiffHandler.new,
718 "T" => tagHandler,
719 "A" => AddedFileHandler.new,
720 "R" => RemovedFileHandler.new,
721 "M" => ModifiedFileHandler.new,
722 "V" => VersionHandler.new]
723
724 $handlers["A"].setTagHandler(tagHandler)
725 $handlers["R"].setTagHandler(tagHandler)
726 $handlers["M"].setTagHandler(tagHandler)
727
728 $fileEntries = Array.new
729 $todoList = Array.new
730
731 File.open("#{$logfile}.emailtmp", File::RDWR|File::CREAT|File::TRUNC) do |mail|
732
733 File.open($logfile) do |log|
734 reader = LogReader.new(log)
735
736 until reader.eof
737 handler = $handlers[reader.currentLineCode]
738 if handler == nil
739 raise "No handler file lines marked '##{reader.currentLineCode}'"
740 end
741 handler.handleLines(reader.getLines, mail)
742 end
743 end
744 end
745
746 if $subjectPrefix == nil
747 $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
748 end
749
750 # HACK: put all file names at mail subject
751 all_files = ""
752 $fileEntries.each do |file|
753 name = htmlEncode(file.name_after_common_prefix)
754 if all_files != ""
755 all_files = all_files + ";" + name
756 else
757 all_files = name
758 end
759 end
760 all_files = all_files + ":"
761
762 mailSubject = "#{$subjectPrefix} #{all_files} #{$mailSubject}"
763 if mailSubject.length > $maxSubjectLength
764 mailSubject = mailSubject[0, $maxSubjectLength]
765 end
766
767 blah("invoking #{$sendmail_prog} -t")
768 IO.popen("#{$sendmail_prog} -t", "w") do |mail|
769 mail.puts("To: #{$recipients.join(',')}")
770 mail.puts("Subject: #{mailSubject}")
771 mail.puts("Content-Type: text/html" + ($charset.nil? ? "" : "; charset=\"#{$charset}\""))
772 unless ($additionalHeaders.empty?)
773 $additionalHeaders.each do |header|
774 mail.puts("#{header[0]}: #{header[1]}")
775 end
776 end
777 mail.puts # end-of-headers
778
779 mail.puts(<<HEAD)
780 <html>
781 <head>
782 <style><!--
783 body {background-color:#ffffff;}
784 .file {border:1px solid #eeeeee;margin-top:1em;margin-bottom:1em;}
785 .pathname {font-family:monospace; float:right;}
786 .fileheader {margin-bottom:.5em;}
787 .diff {margin:0;}
788 .todolist {padding:4px;border:1px dashed #000000;margin-top:1em;}
789 .todolist ul {margin-top:0;margin-bottom:0;}
790 tr.alt {background-color:#eeeeee}
791 #added {background-color:#ddffdd;}
792 #addedchars {background-color:#99ff99;font-weight:bolder;}
793 tr.alt #added {background-color:#ccf7cc;}
794 #removed {background-color:#ffdddd;}
795 #removedchars {background-color:#ff9999;font-weight:bolder;}
796 tr.alt #removed {background-color:#f7cccc;}
797 #info {color:#888888;}
798 #context {background-color:#eeeeee;}
799 td {padding-left:.3em;padding-right:.3em;}
800 tr.head {border-bottom-width:1px;border-bottom-style:solid;}
801 tr.head td {padding:0;padding-top:.2em;}
802 #todo {background-color:#ffff00;}
803 .comment {padding:4px;border:1px dashed #000000;background-color:#ffffdd}
804 .error {color:red;}
805 --></style>
806 </head>
807 <body>
808 HEAD
809
810 unless ($problemHeaders.empty?)
811 mail.puts("<strong class=\"error\">Bad header format in '#{$config}':<ul>")
812 $stderr.puts("Bad header format in '#{$config}':")
813 $problemHeaders.each do |header|
814 mail.puts("<li><pre>#{htmlEncode(header[0])}</pre></li>")
815 $stderr.puts(" - #{header[0]}")
816 end
817 mail.puts("</ul></strong>")
818 end
819 mail.puts("<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"cols\">")
820 filesAdded = 0
821 filesRemoved = 0
822 filesModified = 0
823 totalLinesAdded = 0
824 totalLinesRemoved = 0
825 file_count = 0
826 lastPath = ""
827 last_repository = nil
828 $fileEntries.each do |file|
829 unless file.repository == last_repository
830 last_repository = file.repository
831 mail.print("<tr class=\"head\"><td colspan=\"#{$haveTag ? 5 : 4}\">")
832 mail.print("Commit in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
833 mail.puts("</td></tr>")
834 end
835 file_count += 1
836 if (file_count%2==0)
837 mail.print("<tr class=\"alt\">")
838 else
839 mail.print("<tr>")
840 end
841 if file.addition?
842 filesAdded += 1
843 elsif file.removal?
844 filesRemoved += 1
845 elsif file.modification?
846 filesModified += 1
847 end
848 name = htmlEncode(file.name_after_common_prefix)
849 slashPos = name.rindex("/")
850 if slashPos==nil
851 prefix = ""
852 else
853 thisPath = name[0,slashPos]
854 name = name[slashPos+1,name.length]
855 if thisPath == lastPath
856 prefix = "&nbsp;"*(slashPos) + "/"
857 else
858 prefix = thisPath + "/"
859 end
860 lastPath = thisPath
861 end
862 if all_files == nil
863 all_files = name
864 else
865 all_files = all_files + " " + name
866 end
867 if file.addition?
868 name = "<span id=\"added\">#{name}</span>"
869 elsif file.removal?
870 name = "<span id=\"removed\">#{name}</span>"
871 end
872 if file.isEmpty || file.isBinary || (file.removal? && $no_removed_file_diff)
873 mail.print("<td><tt>#{prefix}#{name}&nbsp;</tt></td>")
874 else
875 mail.print("<td><tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a>&nbsp;</tt></td>")
876 end
877 if file.isEmpty
878 mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
879 elsif file.isBinary
880 mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
881 else
882 if file.lineAdditions>0
883 totalLinesAdded += file.lineAdditions
884 mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
885 else
886 mail.print("<td>&nbsp;</td>")
887 end
888 if file.lineRemovals>0
889 totalLinesRemoved += file.lineRemovals
890 mail.print("<td align=\"right\" id=\"removed\">-#{file.lineRemovals}</td>")
891 else
892 mail.print("<td>&nbsp;</td>")
893 end
894 end
895 if file.tag
896 mail.print("<td>#{htmlEncode(file.tag)}</td>")
897 elsif $haveTag
898 mail.print("<td>&nbsp;</td>")
899 end
900 if file.addition?
901 mail.print("<td align=\"right\">added #{viewcvsFile(file.path,file.toVer)}</td>")
902 elsif file.removal?
903 mail.print("<td>#{viewcvsFile(file.path,file.fromVer)} removed</td>")
904 elsif file.modification?
905 mail.print("<td align=\"center\">#{viewcvsFile(file.path,file.fromVer)} #{viewcvsDiff(file)} #{viewcvsFile(file.path,file.toVer)}</td>")
906 end
907
908 mail.puts("</tr>")
909 end
910
911 if $fileEntries.size>1
912 # give total number of lines added/removed accross all files
913 mail.print("<tr><td>&nbsp;</td>")
914 if totalLinesAdded>0
915 mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
916 else
917 mail.print("<td>&nbsp;</td>")
918 end
919 if totalLinesRemoved>0
920 mail.print("<td align=\"right\" id=\"removed\">-#{totalLinesRemoved}</td>")
921 else
922 mail.print("<td>&nbsp;</td>")
923 end
924 mail.print("<td>&nbsp;</td>") if $haveTag
925 mail.puts("<td>&nbsp;</td></tr>")
926 end
927
928 mail.puts("</table>")
929
930 totalFilesChanged = filesAdded+filesRemoved+filesModified
931 if totalFilesChanged > 1
932 mail.print("<small id=\"info\">")
933 changeKind = 0
934 if filesAdded>0
935 mail.print("#{filesAdded} added")
936 changeKind += 1
937 end
938 if filesRemoved>0
939 mail.print(" + ") if changeKind>0
940 mail.print("#{filesRemoved} removed")
941 changeKind += 1
942 end
943 if filesModified>0
944 mail.print(" + ") if changeKind>0
945 mail.print("#{filesModified} modified")
946 changeKind += 1
947 end
948 mail.print(", total #{totalFilesChanged}") if changeKind > 1
949 mail.puts(" files</small><br />")
950 end
951
952 todo_count = 0
953 $todoList.each do |item|
954 if todo_count == 0
955 mail.puts("<div class=\"todolist\"><ul>")
956 end
957 todo_count += 1
958 item = htmlEncode(item)
959 mail.puts("<li><a href=\"#todo#{todo_count}\">#{item}</a></li>")
960 end
961 mail.puts("</ul></div>") if todo_count>0
962
963
964 File.open("#{$logfile}.emailtmp") do |input|
965 input.each do |line|
966 mail.puts(line)
967 end
968 end
969 if $debug
970 blah("leaving file #{$logfile}.emailtmp")
971 else
972 File.unlink("#{$logfile}.emailtmp")
973 end
974
975 mail.puts("</body></html>")
976
977 end
978

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed