]>
Commit | Line | Data |
---|---|---|
9c3cf735 | 1 | --- svn_post_commit_hook.rb (.../trunk) (revision 0) |
ca77e894 ER |
2 | +++ svn_post_commit_hook.rb (.../branches/svn_support) (revision 269) |
3 | @@ -0,0 +1,412 @@ | |
9c3cf735 ER |
4 | +#!/usr/bin/ruby -w |
5 | + | |
6 | +$svnlook_exe = "svnlook" # default assumes the program is in $PATH | |
7 | + | |
8 | +def usage(msg) | |
9 | + $stderr.puts(msg) | |
10 | + exit(1) | |
11 | +end | |
12 | + | |
13 | +def blah(msg) | |
14 | + if $debug | |
15 | + $stderr.puts "svn_post_commit_hook.rb: #{msg}" | |
16 | + end | |
17 | +end | |
18 | + | |
ca77e894 | 19 | +$debug = false |
9c3cf735 ER |
20 | +$tmpdir = ENV["TMPDIR"] || "/tmp" |
21 | +$dirtemplate = "#svnspam.#{Process.getpgrp}.#{Process.uid}" | |
22 | +# arguments to pass though to 'cvsspam.rb' | |
23 | +$passthrough_args = [] | |
24 | + | |
25 | +def make_data_dir | |
26 | + dir = "#{$tmpdir}/#{$dirtemplate}-#{rand(99999999)}" | |
27 | + Dir.mkdir(dir, 0700) | |
28 | + dir | |
29 | +end | |
30 | + | |
31 | +def init | |
32 | + $datadir = make_data_dir | |
33 | + | |
34 | + # set PWD so that svnlook can create its .svnlook directory | |
35 | + Dir.chdir($datadir) | |
36 | +end | |
37 | + | |
38 | +def cleanup | |
39 | + unless $debug | |
40 | + File.unlink("#{$datadir}/logfile") | |
41 | + Dir.rmdir($datadir) | |
42 | + end | |
43 | +end | |
44 | + | |
45 | +def send_email | |
46 | + cmd = File.dirname($0) + "/cvsspam.rb" | |
47 | + unless system(cmd,"--svn","#{$datadir}/logfile", *$passthrough_args) | |
48 | + fail "problem running '#{cmd}'" | |
49 | + end | |
50 | +end | |
51 | + | |
52 | +# Like IO.popen, but accepts multiple arguments like Kernel.exec | |
53 | +# (So no need to escape shell metacharacters) | |
54 | +def safer_popen(*args) | |
55 | + IO.popen("-") do |pipe| | |
56 | + if pipe==nil | |
57 | + exec(*args) | |
58 | + else | |
59 | + yield pipe | |
60 | + end | |
61 | + end | |
62 | +end | |
63 | + | |
64 | + | |
65 | +# Process the command-line arguments in the given list | |
66 | +def process_args | |
67 | + require 'getoptlong' | |
68 | + | |
69 | + opts = GetoptLong.new( | |
70 | + [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ], | |
71 | + [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], | |
72 | + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], | |
73 | + [ "--from", "-u", GetoptLong::REQUIRED_ARGUMENT ], | |
74 | + [ "--charset", GetoptLong::REQUIRED_ARGUMENT ] | |
75 | + ) | |
76 | + | |
77 | + opts.each do |opt, arg| | |
78 | + if ["--to", "--config", "--from", "--charset"].include?(opt) | |
79 | + $passthrough_args << opt << arg | |
80 | + end | |
81 | + if ["--debug"].include?(opt) | |
82 | + $passthrough_args << opt | |
83 | + end | |
84 | + $config = arg if opt=="--config" | |
85 | + $debug = true if opt == "--debug" | |
86 | + end | |
87 | + | |
88 | + $repository = ARGV[0] | |
89 | + $revision = ARGV[1] | |
90 | + | |
91 | + unless $revision =~ /^\d+$/ | |
92 | + usage("revision must be an integer: #{revision.inspect}") | |
93 | + end | |
94 | + $revision = $revision.to_i | |
95 | + | |
96 | + unless FileTest.directory?($repository) | |
97 | + usage("no such directory: #{$repository.inspect}") | |
98 | + end | |
99 | + $repository =~ /([^\/]+$)/ | |
100 | + $shortrepo = $1 | |
101 | +end | |
102 | + | |
103 | +# runs the given svnlook subcommand | |
104 | +def svnlook(cmd, revision, *args) | |
105 | + rev = revision.to_s | |
106 | + safer_popen($svnlook_exe, cmd, $repository, "-r", rev, *args) do |io| | |
107 | + yield io | |
108 | + end | |
109 | +end | |
110 | + | |
111 | +class Change | |
112 | + def initialize(filechange, propchange, path) | |
113 | + @filechange = filechange | |
114 | + @propchange = propchange | |
115 | + @path = path | |
116 | + end | |
117 | + | |
118 | + attr_accessor :filechange, :propchange, :path | |
119 | + | |
120 | + def property_change? | |
121 | + @propchange != " " | |
122 | + end | |
123 | + | |
124 | + def file_change? | |
125 | + @filechange != "_" | |
126 | + end | |
127 | + | |
128 | + def addition? | |
129 | + @filechange == "A" | |
130 | + end | |
131 | + | |
132 | + def deletion? | |
133 | + @filechange == "D" | |
134 | + end | |
135 | +end | |
136 | + | |
137 | + | |
138 | + | |
139 | +# Line-oriented access to an underlying IO object. Remembers 'current' line | |
140 | +# for lookahead during parsing. | |
141 | +class LineReader | |
142 | + def initialize(io) | |
143 | + @io = io | |
144 | + end | |
145 | + | |
146 | + def current | |
147 | + @line | |
148 | + end | |
149 | + | |
150 | + def next_line | |
151 | + (@line = @io.gets) != nil | |
152 | + end | |
153 | + | |
154 | + def assert_current(re) | |
155 | + raise "unexpected #{current.inspect}" unless @line =~ re | |
156 | + $~ | |
157 | + end | |
158 | + | |
159 | + def assert_next(re=nil) | |
160 | + raise "unexpected end of text" unless next_line | |
161 | + unless re.nil? | |
162 | + raise "unexpected #{current.inspect}" unless @line =~ re | |
163 | + end | |
164 | + $~ | |
165 | + end | |
166 | +end | |
167 | + | |
168 | + | |
169 | +def read_modified_diff(out, lines, path) | |
170 | + lines.assert_next(/^=+$/) | |
171 | + lines.assert_next | |
172 | + if lines.current =~ /\(Binary files differ\)/ | |
173 | + process_modified_binary_diff(out, lines, path) | |
174 | + else | |
175 | + process_modified_text_diff(out, lines, path) | |
176 | + end | |
177 | +end | |
178 | + | |
179 | + | |
180 | +def process_modified_binary_diff(out, lines, path) | |
181 | + prev_rev= $revision-1 | |
182 | + next_rev= $revision | |
183 | + out.puts "#V #{prev_rev},#{next_rev}" | |
184 | + out.puts "#M #{$shortrepo}/#{path}" | |
185 | + out.puts "#U diff x x" | |
186 | + out.puts "#U Binary files x and y differ" | |
187 | +end | |
188 | + | |
189 | + | |
190 | +def process_modified_text_diff(out, lines, path) | |
191 | + m = lines.assert_current(/^---.*\(rev (\d+)\)$/) | |
192 | + prev_rev = m[1].to_i | |
193 | + diff1 = lines.current | |
194 | + m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) | |
195 | + next_rev = m[1].to_i | |
196 | + diff2 = lines.current | |
197 | + out.puts "#V #{prev_rev},#{next_rev}" | |
198 | + out.puts "#M #{$shortrepo}/#{path}" | |
199 | + out.puts "#U #{diff1}" | |
200 | + out.puts "#U #{diff2}" | |
201 | + while lines.next_line && lines.current =~ /^[-\+ @\\]/ | |
202 | + out.puts "#U #{lines.current}" | |
203 | + end | |
204 | +end | |
205 | + | |
206 | +def read_added_diff(out, lines, path) | |
207 | + lines.assert_next(/^=+$/) | |
208 | + lines.assert_next | |
209 | + if lines.current =~ /\(Binary files differ\)/ | |
210 | + process_added_binary_diff(out, lines, path) | |
211 | + else | |
212 | + process_added_text_diff(out, lines, path) | |
213 | + end | |
214 | +end | |
215 | + | |
216 | +def process_added_binary_diff(out, lines, path) | |
217 | + next_rev= $revision | |
218 | + out.puts "#V NONE,#{next_rev}" | |
219 | + out.puts "#A #{$shortrepo}/#{path}" | |
220 | + out.puts "#U diff x x" | |
221 | + out.puts "#U Binary file x added" | |
222 | +end | |
223 | + | |
224 | +def process_added_text_diff(out, lines, path) | |
225 | + m = lines.assert_current(/^---.*\(rev (\d+)\)$/) | |
226 | + prev_rev = m[1].to_i | |
227 | + diff1 = lines.current | |
228 | + m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) | |
229 | + next_rev = m[1].to_i | |
230 | + diff2 = lines.current | |
231 | + out.puts "#V NONE,#{next_rev}" | |
232 | + out.puts "#A #{$shortrepo}/#{path}" | |
233 | + out.puts "#U #{diff1}" | |
234 | + out.puts "#U #{diff2}" | |
235 | + while lines.next_line && lines.current =~ /^[-\+ @\\]/ | |
236 | + out.puts "#U #{lines.current}" | |
237 | + end | |
238 | +end | |
239 | + | |
240 | +def read_deleted_diff(out, lines, path) | |
241 | + lines.assert_next(/^=+$/) | |
242 | + m = lines.assert_next(/^---.*\(rev (\d+)\)$/) | |
243 | + prev_rev = m[1].to_i | |
244 | + diff1 = lines.current | |
245 | + m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) | |
246 | + next_rev = m[1].to_i | |
247 | + diff2 = lines.current | |
248 | + out.puts "#V #{prev_rev},NONE" | |
249 | + out.puts "#R #{$shortrepo}/#{path}" | |
250 | + out.puts "#U #{diff1}" | |
251 | + out.puts "#U #{diff2}" | |
252 | + while lines.next_line && lines.current =~ /^[-\+ @\\]/ | |
253 | + out.puts "#U #{lines.current}" | |
254 | + end | |
255 | +end | |
256 | + | |
257 | +def read_property_lines(path, prop_name, revision) | |
258 | + lines = [] | |
259 | + svnlook("propget", revision, prop_name, path) do |io| | |
260 | + io.each_line do |line| | |
261 | + lines << line.chomp | |
262 | + end | |
263 | + end | |
264 | + lines | |
265 | +end | |
266 | + | |
267 | +def assert_prop_match(a, b) | |
268 | + if !b.nil? && a != b | |
269 | + raise "property mismatch: #{a.inspect}!=#{b.inspect}" | |
270 | + end | |
271 | +end | |
272 | + | |
273 | +# We need to read the property change from the output of svnlook, but have | |
274 | +# a difficulty in that there's no unambiguous delimiter marking the end of | |
275 | +# a potentially multi-line property value. Therefore, we do a seperate | |
276 | +# svn propget on the given file to get the value of the property on its own, | |
277 | +# and then use that value as a guide as to how much data to read from the | |
278 | +# svnlook output. | |
279 | +def munch_prop_text(path, prop_name, revision, lines, line0) | |
280 | + prop = read_property_lines(path, prop_name, revision) | |
281 | + if prop.empty? | |
282 | + assert_prop_match(line0, "") | |
283 | + return | |
284 | + end | |
285 | + assert_prop_match(line0, prop.shift) | |
286 | + prop.each do |prop_line| | |
287 | + lines.assert_next | |
288 | + assert_prop_match(lines.current.chomp, prop_line) | |
289 | + end | |
290 | +end | |
291 | + | |
292 | +def read_properties_changed(out, lines, path) | |
293 | + prev_rev= $revision-1 | |
294 | + next_rev= $revision | |
295 | + lines.assert_next(/^_+$/) | |
296 | + return unless lines.next_line | |
297 | + out.puts "#V #{prev_rev},#{next_rev}" | |
298 | + out.puts "#P #{$shortrepo}/#{path}" | |
299 | +# The first three get consumed and not highlighted | |
300 | + out.puts "#U " | |
301 | + out.puts "#U Property changes:" | |
302 | + out.puts "#U " | |
ca77e894 | 303 | + |
9c3cf735 | 304 | + while true |
ca77e894 ER |
305 | + break unless lines.current =~ /^(?:Name|Added|Deleted): (.+)$/ |
306 | + | |
9c3cf735 ER |
307 | + prop_name = $1 |
308 | + m = lines.assert_next(/^ ([-+]) (.*)/) | |
309 | + op = m[1] | |
310 | + line0 = m[2] | |
311 | + if op == "-" | |
312 | + munch_prop_text(path, prop_name, $revision-1, lines, line0) | |
313 | + if lines.next_line && lines.current =~ /^ \+ (.*)/ | |
314 | + munch_prop_text(path, prop_name, $revision, lines, $1) | |
315 | + lines.next_line | |
316 | + end | |
317 | + else # op == "+" | |
318 | + munch_prop_text(path, prop_name, $revision, lines, line0) | |
319 | + lines.next_line | |
320 | + end | |
321 | + out.puts "#U #{m[1]} #{prop_name}:#{m[2]}" | |
322 | + end | |
323 | + out.puts "#U " | |
324 | +end | |
325 | + | |
326 | +def handle_copy(out, lines, path, from_ref, from_file) | |
327 | + prev_rev= $revision-1 | |
328 | + next_rev= $revision | |
329 | + out.puts "#V #{$shortrepo}/#{from_file}:#{prev_rev},#{next_rev}" | |
330 | + out.puts "#C #{$shortrepo}/#{path}" | |
331 | + if lines.next_line && lines.current =~ /^=+$/ | |
332 | + m = lines.assert_next(/^---.*\(rev (\d+)\)$/) | |
333 | + prev_rev = m[1].to_i | |
334 | + diff1 = lines.current | |
335 | + m = lines.assert_next(/^\+\+\+.*\(rev (\d+)\)$/) | |
336 | + next_rev = m[1].to_i | |
337 | + diff2 = lines.current | |
338 | + out.puts "#U #{diff1}" | |
339 | + out.puts "#U #{diff2}" | |
340 | + while lines.next_line && lines.current =~ /^[-\+ @\\]/ | |
341 | + out.puts "#U #{lines.current}" | |
342 | + end | |
343 | + else | |
344 | + out.puts "#U " | |
345 | + out.puts "#U Copied from #{$shortrepo}/#{from_file}:#{from_ref}" | |
346 | + out.puts "#U " | |
347 | + end | |
348 | +end | |
349 | + | |
350 | +def svnlook_author | |
351 | + svnlook("author", $revision) do |io| | |
352 | + return io.readline.chomp | |
353 | + end | |
354 | + nil | |
355 | +end | |
356 | + | |
357 | +def find_author | |
358 | + return if $passthrough_args.include?("--from") | |
359 | + author = svnlook_author | |
360 | + if author | |
361 | + blah("Author from svnlook: '#{author}'") | |
362 | + $passthrough_args << "--from" << author | |
363 | + end | |
364 | +end | |
365 | + | |
366 | +def process_svnlook_log(file) | |
367 | + svnlook("log", $revision) do |io| | |
368 | + io.each_line do |line| | |
369 | + file.puts("#> #{line}") | |
370 | + end | |
371 | + end | |
372 | +end | |
373 | + | |
374 | +def process_svnlook_diff(file) | |
375 | + svnlook("diff", $revision) do |io| | |
376 | + lines = LineReader.new(io) | |
377 | + while lines.next_line | |
378 | + if lines.current =~ /^Modified:\s+(.*)/ | |
379 | + read_modified_diff(file, lines, $1) | |
380 | + elsif lines.current =~ /^Added:\s+(.*)/ | |
381 | + read_added_diff(file, lines, $1) | |
382 | + elsif lines.current =~ /^Copied:\s+(.*) \(from rev (\d+), (.*)\)$/ | |
383 | + handle_copy(file, lines, $1, $2, $3) | |
384 | + elsif lines.current =~ /^Deleted:\s+(.*)/ | |
385 | + read_deleted_diff(file, lines, $1) | |
386 | + elsif lines.current =~ /^Property changes on:\s+(.*)/ | |
387 | + read_properties_changed(file, lines, $1) | |
388 | + elsif lines.current == "\n" | |
389 | + # ignore | |
390 | + else | |
ca77e894 | 391 | + raise "unable to parse line '#{lines.current.inspect}'" |
9c3cf735 ER |
392 | + end |
393 | + end | |
394 | + end | |
395 | +end | |
396 | + | |
397 | +def process_commit() | |
398 | + File.open("#{$datadir}/logfile", File::WRONLY|File::CREAT) do |file| | |
399 | + process_svnlook_log(file) | |
400 | + process_svnlook_diff(file) | |
401 | + end | |
402 | +end | |
403 | + | |
404 | + | |
405 | +def main | |
406 | + init() | |
407 | + process_args() | |
408 | + find_author() | |
409 | + process_commit() | |
410 | + send_email() | |
411 | + cleanup() | |
412 | +end | |
413 | + | |
414 | + | |
415 | +main | |
ca77e894 ER |
416 | --- cvsspam.rb (.../trunk) (revision 269) |
417 | +++ cvsspam.rb (.../branches/svn_support) (revision 269) | |
9c3cf735 | 418 | @@ -398,7 +398,7 @@ |
3eeef56f ER |
419 | |
420 | # the full path and filename within the repository | |
421 | attr_accessor :path | |
422 | - # the type of change committed 'M'=modified, 'A'=added, 'R'=removed | |
423 | + # the type of change committed 'M'=modified, 'A'=added, 'R'=removed, 'P'=properties, 'C'=copied | |
424 | attr_accessor :type | |
425 | # records number of 'addition' lines in diff output, once counted | |
426 | attr_accessor :lineAdditions | |
9c3cf735 | 427 | @@ -453,17 +453,28 @@ |
3eeef56f ER |
428 | def removal? |
429 | @type == "R" | |
430 | end | |
431 | - | |
432 | + | |
433 | # was this file added during the commit? | |
434 | def addition? | |
435 | @type == "A" | |
436 | end | |
437 | ||
438 | + # was this file copied during the commit? | |
439 | + def copied? | |
440 | + @type == "C" | |
441 | + end | |
442 | + | |
443 | # was this file simply modified during the commit? | |
444 | def modification? | |
445 | @type == "M" | |
446 | end | |
447 | + | |
448 | + # was this file simply modified during the commit? | |
449 | + def modifiedprops? | |
450 | + @type == "P" | |
451 | + end | |
3eeef56f | 452 | |
9c3cf735 | 453 | + |
3eeef56f ER |
454 | # passing true, this object remembers that a diff will appear in the email, |
455 | # passing false, this object remembers that no diff will appear in the email. | |
9c3cf735 ER |
456 | # Once the value is set, it will not be changed |
457 | @@ -889,6 +900,15 @@ | |
3eeef56f ER |
458 | end |
459 | end | |
460 | ||
461 | +# Note when LogReader finds record of a file that was copied in this commit | |
462 | +class CopiedFileHandler < FileHandler | |
463 | + def handleFile(file) | |
464 | + file.type="C" | |
465 | + file.fromVer=$fromVer | |
466 | + file.toVer=$toVer | |
467 | + end | |
468 | +end | |
469 | + | |
470 | # Note when LogReader finds record of a file that was modified in this commit | |
471 | class ModifiedFileHandler < FileHandler | |
472 | def handleFile(file) | |
9c3cf735 | 473 | @@ -898,7 +918,16 @@ |
3eeef56f ER |
474 | end |
475 | end | |
476 | ||
477 | +# Note when LogReader finds record of a file whose properties were modified in this commit | |
478 | +class ModifiedPropsFileHandler < FileHandler | |
479 | + def handleFile(file) | |
480 | + file.type="P" | |
481 | + file.fromVer=$fromVer | |
482 | + file.toVer=$toVer | |
483 | + end | |
484 | +end | |
3eeef56f | 485 | |
9c3cf735 | 486 | + |
3eeef56f ER |
487 | # Used by UnifiedDiffHandler to record the number of added and removed lines |
488 | # appearing in a unidiff. | |
9c3cf735 ER |
489 | class UnifiedDiffStats |
490 | @@ -1064,11 +1093,21 @@ | |
3eeef56f ER |
491 | print($frontend.path($file.basedir, $file.tag)) |
492 | println("</span><br />") | |
493 | 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>") | |
494 | + when "C" | |
495 | + print("<span class=\"pathname\" id=\"copied\">") | |
496 | + print($frontend.path($file.basedir, $file.tag)) | |
497 | + println("</span><br />") | |
498 | + 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>") | |
499 | when "M" | |
500 | print("<span class=\"pathname\">") | |
501 | print($frontend.path($file.basedir, $file.tag)) | |
502 | println("</span><br />") | |
503 | 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>") | |
504 | + when "P" | |
505 | + print("<span class=\"pathname\">") | |
506 | + print($frontend.path($file.basedir, $file.tag)) | |
507 | + println("</span><br />") | |
508 | + 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>") | |
509 | end | |
510 | print("<pre class=\"diff\"><small id=\"info\">") | |
511 | lines.each do |line| | |
9c3cf735 ER |
512 | @@ -1329,6 +1368,7 @@ |
513 | $users_file_charset = nil | |
3eeef56f ER |
514 | |
515 | $debug = false | |
516 | +$svn = false | |
517 | $recipients = Array.new | |
518 | $sendmail_prog = "/usr/sbin/sendmail" | |
9c3cf735 ER |
519 | $hostname = ENV['HOSTNAME'] || 'localhost' |
520 | @@ -1366,6 +1406,7 @@ | |
3eeef56f ER |
521 | [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ], |
522 | [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], | |
523 | [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], | |
524 | + [ "--svn", "-s", GetoptLong::NO_ARGUMENT ], | |
525 | [ "--from", "-u", GetoptLong::REQUIRED_ARGUMENT ], | |
526 | [ "--charset", GetoptLong::REQUIRED_ARGUMENT ] | |
527 | ) | |
9c3cf735 | 528 | @@ -1374,6 +1415,7 @@ |
3eeef56f ER |
529 | $recipients << EmailAddress.new(arg) if opt=="--to" |
530 | $config = arg if opt=="--config" | |
531 | $debug = true if opt=="--debug" | |
532 | + $svn = true if opt=="--svn" | |
533 | $from_address = EmailAddress.new(arg) if opt=="--from" | |
534 | # must use different variable as the config is readed later. | |
535 | $arg_charset = arg if opt == "--charset" | |
9c3cf735 | 536 | @@ -1386,7 +1428,7 @@ |
3eeef56f ER |
537 | else |
538 | $stderr.puts "missing required file argument" | |
539 | end | |
540 | - puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>" | |
541 | + puts "Usage: cvsspam.rb [ --svn ] [ --to <email> ] [ --config <file> ] <collect_diffs file>" | |
542 | exit(-1) | |
543 | end | |
544 | ||
9c3cf735 | 545 | @@ -1495,12 +1537,16 @@ |
3eeef56f ER |
546 | "T" => tagHandler, |
547 | "A" => AddedFileHandler.new, | |
548 | "R" => RemovedFileHandler.new, | |
549 | + "C" => CopiedFileHandler.new, | |
550 | "M" => ModifiedFileHandler.new, | |
551 | + "P" => ModifiedPropsFileHandler.new, | |
552 | "V" => VersionHandler.new] | |
553 | ||
554 | $handlers["A"].setTagHandler(tagHandler) | |
555 | $handlers["R"].setTagHandler(tagHandler) | |
556 | +$handlers["C"].setTagHandler(tagHandler) | |
557 | $handlers["M"].setTagHandler(tagHandler) | |
558 | +$handlers["P"].setTagHandler(tagHandler) | |
559 | ||
560 | $fileEntries = Array.new | |
561 | $task_list = Array.new | |
9c3cf735 | 562 | @@ -1525,7 +1571,11 @@ |
3eeef56f ER |
563 | end |
564 | ||
565 | if $subjectPrefix == nil | |
9c3cf735 | 566 | - $subjectPrefix = "[CVS #{Repository.array.join(',')}]" |
3eeef56f ER |
567 | + if $svn |
568 | + $subjectPrefix = "[SVN #{Repository.array.join(',')}]" | |
569 | + else | |
570 | + $subjectPrefix = "[CVS #{Repository.array.join(',')}]" | |
571 | + end | |
572 | end | |
573 | ||
574 | if $files_in_subject | |
9c3cf735 | 575 | @@ -1572,6 +1622,8 @@ |
3eeef56f ER |
576 | #removed {background-color:#ffdddd;} |
577 | #removedchars {background-color:#ff9999;font-weight:bolder;} | |
578 | tr.alt #removed {background-color:#f7cccc;} | |
579 | + #copied {background-color:#ccccff;} | |
580 | + tr.alt #copied {background-color:#bbbbf7;} | |
581 | #info {color:#888888;} | |
582 | #context {background-color:#eeeeee;} | |
583 | td {padding-left:.3em;padding-right:.3em;} | |
9c3cf735 | 584 | @@ -1604,7 +1656,9 @@ |
3eeef56f ER |
585 | |
586 | filesAdded = 0 | |
587 | filesRemoved = 0 | |
588 | + filesCopied = 0 | |
589 | filesModified = 0 | |
590 | + filesModifiedProps = 0 | |
591 | totalLinesAdded = 0 | |
592 | totalLinesRemoved = 0 | |
593 | file_count = 0 | |
9c3cf735 | 594 | @@ -1613,24 +1667,26 @@ |
3eeef56f ER |
595 | $fileEntries.each do |file| |
596 | unless file.repository == last_repository | |
597 | last_repository = file.repository | |
598 | - mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 5 : 4}\">") | |
599 | + mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 6 : 5}\">") | |
600 | if last_repository.has_multiple_tags | |
601 | mail.print("Mixed-tag commit") | |
602 | else | |
603 | mail.print("Commit") | |
604 | end | |
605 | mail.print(" in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>") | |
606 | - if last_repository.trunk_only? | |
607 | - mail.print("<span id=\"info\"> on MAIN</span>") | |
608 | - else | |
609 | - mail.print(" on ") | |
610 | - tagCount = 0 | |
611 | - last_repository.each_tag do |tag| | |
612 | - tagCount += 1 | |
613 | - if tagCount > 1 | |
614 | - mail.print tagCount<last_repository.tag_count ? ", " : " & " | |
615 | + if !$svn | |
616 | + if last_repository.trunk_only? | |
617 | + mail.print("<span id=\"info\"> on MAIN</span>") | |
618 | + else | |
619 | + mail.print(" on ") | |
620 | + tagCount = 0 | |
621 | + last_repository.each_tag do |tag| | |
622 | + tagCount += 1 | |
623 | + if tagCount > 1 | |
624 | + mail.print tagCount<last_repository.tag_count ? ", " : " & " | |
625 | + end | |
626 | + mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>" | |
627 | end | |
628 | - mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>" | |
629 | end | |
630 | end | |
631 | mail.puts("</td></tr>") | |
9c3cf735 | 632 | @@ -1645,8 +1701,12 @@ |
3eeef56f ER |
633 | filesAdded += 1 |
634 | elsif file.removal? | |
635 | filesRemoved += 1 | |
636 | + elsif file.copied? | |
637 | + filesCopied += 1 | |
638 | elsif file.modification? | |
639 | filesModified += 1 | |
640 | + elsif file.modifiedprops? | |
641 | + filesModifiedProps += 1 | |
642 | end | |
643 | name = htmlEncode(file.name_after_common_prefix) | |
644 | slashPos = name.rindex("/") | |
9c3cf735 | 645 | @@ -1666,6 +1726,8 @@ |
3eeef56f ER |
646 | name = "<span id=\"added\">#{name}</span>" |
647 | elsif file.removal? | |
648 | name = "<span id=\"removed\">#{name}</span>" | |
649 | + elsif file.copied? | |
650 | + name = "<span id=\"copied\">#{name}</span>" | |
651 | end | |
9c3cf735 | 652 | mail.print("<td>") |
3eeef56f | 653 | if file.has_diff? |
9c3cf735 | 654 | @@ -1675,11 +1737,18 @@ |
3eeef56f | 655 | end |
9c3cf735 ER |
656 | mail.print(" #{$frontend.log(file)}") |
657 | mail.print("</td>") | |
3eeef56f ER |
658 | - if file.isEmpty |
659 | - mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>") | |
3eeef56f ER |
660 | + if file.copied? |
661 | + mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[copied]</small></td>") | |
662 | + elsif file.isEmpty | |
663 | + mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[empty]</small></td>") | |
664 | elsif file.isBinary | |
665 | - mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>") | |
666 | + mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[binary]</small></td>") | |
667 | else | |
668 | + if file.modifiedprops? | |
669 | + mail.print("<td align=\"right\"><small id=\"info\">[props]</small></td>") | |
670 | + else | |
671 | + mail.print("<td></td>") | |
672 | + end | |
673 | if file.lineAdditions>0 | |
674 | totalLinesAdded += file.lineAdditions | |
675 | mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>") | |
9c3cf735 | 676 | @@ -1706,15 +1775,19 @@ |
3eeef56f ER |
677 | mail.print("<td nowrap=\"nowrap\" align=\"right\">added #{$frontend.version(file.path,file.toVer)}</td>") |
678 | elsif file.removal? | |
679 | mail.print("<td nowrap=\"nowrap\">#{$frontend.version(file.path,file.fromVer)} removed</td>") | |
680 | + elsif file.copied? | |
681 | + mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>") | |
682 | elsif file.modification? | |
683 | mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>") | |
684 | + elsif file.modifiedprops? | |
685 | + mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>") | |
686 | end | |
687 | ||
688 | mail.puts("</tr>") | |
689 | end | |
690 | if $fileEntries.size>1 && (totalLinesAdded+totalLinesRemoved)>0 | |
691 | # give total number of lines added/removed accross all files | |
692 | - mail.print("<tr><td></td>") | |
693 | + mail.print("<tr><td></td><td></td>") | |
694 | if totalLinesAdded>0 | |
695 | mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>") | |
696 | else | |
9c3cf735 | 697 | @@ -1731,7 +1804,7 @@ |
3eeef56f ER |
698 | |
699 | mail.puts("</table>") | |
700 | ||
701 | - totalFilesChanged = filesAdded+filesRemoved+filesModified | |
702 | + totalFilesChanged = filesAdded+filesRemoved+filesCopied+filesModified+filesModifiedProps | |
703 | if totalFilesChanged > 1 | |
704 | mail.print("<small id=\"info\">") | |
705 | changeKind = 0 | |
9c3cf735 | 706 | @@ -1744,11 +1817,21 @@ |
3eeef56f ER |
707 | mail.print("#{filesRemoved} removed") |
708 | changeKind += 1 | |
709 | end | |
710 | + if filesCopied>0 | |
711 | + mail.print(" + ") if changeKind>0 | |
712 | + mail.print("#{filesCopied} copied") | |
713 | + changeKind += 1 | |
714 | + end | |
715 | if filesModified>0 | |
716 | mail.print(" + ") if changeKind>0 | |
717 | mail.print("#{filesModified} modified") | |
718 | changeKind += 1 | |
719 | end | |
720 | + if filesModifiedProps>0 | |
721 | + mail.print(" + ") if changeKind>0 | |
722 | + mail.print("#{filesModifiedProps} modified properties") | |
723 | + changeKind += 1 | |
724 | + end | |
725 | mail.print(", total #{totalFilesChanged}") if changeKind > 1 | |
726 | mail.puts(" files</small><br />") | |
727 | end |