]> git.pld-linux.org Git - packages/cvsspam.git/blame - cvsspam-svnspam-branch.diff
- add config item too
[packages/cvsspam.git] / cvsspam-svnspam-branch.diff
CommitLineData
3eeef56f
ER
1--- cvsspam-0.2.12/svn_cvsspam.rb 2005-07-11 18:53:29.000000000 +0300
2+++ cvsspam-svn/svn_cvsspam.rb 2008-08-07 17:27:52.632725455 +0300
3@@ -18,7 +18,7 @@
4 # to your cvssppam.conf
5
6
7-$version = "0.2.11"
8+$version = "0.2.12"
9
10
11 $maxSubjectLength = 200
12@@ -339,8 +339,11 @@
13
14 # gets the Repository object for the first component of the given path
15 def Repository.get(name)
16- name =~ /^[^\/]+/
17- name = $&
18+ # Leading './' is ignored (for peeps who have done 'cvs checkout .')
19+ # Trailing '/' ensures no match for files in root (we just want dirs)
20+ name =~ /^(?:\.\/)?([^\/]+)\//
21+ name = $1
22+ name = "/" if name.nil? # file at top-level? fake up a name for repo
23 rep = @@repositories[name]
24 if rep.nil?
25 rep = Repository.new(name)
26@@ -385,6 +388,7 @@
27 class FileEntry
28 def initialize(path)
29 @path = path
30+ @fromVer = @toVer = nil
31 @lineAdditions = @lineRemovals = 0
32 @repository = Repository.get(path)
33 @repository.merge_common_prefix(basedir())
34@@ -394,7 +398,7 @@
35
36 # the full path and filename within the repository
37 attr_accessor :path
38- # the type of change committed 'M'=modified, 'A'=added, 'R'=removed
39+ # the type of change committed 'M'=modified, 'A'=added, 'R'=removed, 'P'=properties, 'C'=copied
40 attr_accessor :type
41 # records number of 'addition' lines in diff output, once counted
42 attr_accessor :lineAdditions
43@@ -412,7 +416,7 @@
44 # works out the filename part of #path
45 def file
46 @path =~ /.*\/(.*)/
47- $1 || @path
48+ $1
49 end
50
51 # set the branch on which this change was committed, and add it to the list
52@@ -430,7 +434,7 @@
53 # works out the directory part of #path
54 def basedir
55 @path =~ /(.*)\/.*/
56- $1 || "/"
57+ $1
58 end
59
60 # gives the Repository object this file was automatically associated with
61@@ -449,16 +453,27 @@
62 def removal?
63 @type == "R"
64 end
65-
66+
67 # was this file added during the commit?
68 def addition?
69 @type == "A"
70 end
71
72+ # was this file copied during the commit?
73+ def copied?
74+ @type == "C"
75+ end
76+
77 # was this file simply modified during the commit?
78 def modification?
79 @type == "M"
80 end
81+
82+ # was this file simply modified during the commit?
83+ def modifiedprops?
84+ @type == "P"
85+ end
86+
87
88 # passing true, this object remembers that a diff will appear in the email,
89 # passing false, this object remembers that no diff will appear in the email.
90@@ -530,6 +545,14 @@
91 # TODO: consolidate these into a nicer framework,
92 mailSub = proc { |match| "<a href=\"mailto:#{match}\">#{match}</a>" }
93 urlSub = proc { |match| "<a href=\"#{match}\">#{match}</a>" }
94+gforgeTaskSub = proc { |match|
95+ match =~ /([0-9]+)/
96+ "<a href=\"#{$gforgeTaskURL.sub(/%s/, $1)}\">#{match}</a>"
97+}
98+gforgeBugSub = proc { |match|
99+ match =~ /([0-9]+)/
100+ "<a href=\"#{$gforgeBugURL.sub(/%s/, $1)}\">#{match}</a>"
101+}
102 bugzillaSub = proc { |match|
103 match =~ /([0-9]+)/
104 "<a href=\"#{$bugzillaURL.sub(/%s/, $1)}\">#{match}</a>"
105@@ -541,9 +564,15 @@
106 match =~ /([0-9]+)/
107 "<a href=\"#{$ticketURL.sub(/%s/, $1)}\">#{match}</a>"
108 }
109+wikiSub = proc { |match|
110+ match =~ /\[\[(.*)\]\]/
111+ raw = $1
112+ "<a href=\"#{$wikiURL.sub(/%s/, urlEncode(raw))}\">[[#{raw}]]</a>"
113+}
114 commentSubstitutions = {
115 '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
116- '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub}
117+ '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
118+ }
119
120 # outputs commit log comment text supplied by LogReader as preformatted HTML
121 class CommentHandler < LineConsumer
122@@ -661,6 +690,12 @@
123 def diff(file)
124 '-&gt;'
125 end
126+
127+ # may be overridden by subclasses that are able to make a hyperlink to a
128+ # history log for a file
129+ def log(file)
130+ ''
131+ end
132 end
133
134 # Superclass for objects that can link to CVS frontends on the web (ViewCVS,
135@@ -701,6 +736,14 @@
136 "<a href=\"#{diff_url(file)}\">#{super(file)}</a>"
137 end
138
139+ def log(file)
140+ link = log_url(file)
141+ if link
142+ return "<span id=\"info\">(<a href=\"#{link}\">log</a>)</span>"
143+ end
144+ return nil
145+ end
146+
147 protected
148 def add_repo(url)
149 if @repository_name
150@@ -713,6 +756,10 @@
151 url
152 end
153 end
154+
155+ def log_url(file)
156+ nil
157+ end
158 end
159
160 # Link to ViewCVS
161@@ -771,6 +818,17 @@
162 def diff_url(file)
163 add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=text&amp;tr1=#{file.fromVer}&amp;r2=text&amp;tr2=#{file.toVer}&amp;f=h")
164 end
165+
166+ protected
167+
168+ def log_url(file)
169+ if file.toVer
170+ log_anchor = "#rev#{file.toVer}"
171+ else
172+ log_anchor = ""
173+ end
174+ add_repo("#{@base_url}#{urlEncode(file.path)}#{log_anchor}")
175+ end
176 end
177
178
179@@ -792,6 +850,15 @@
180 end
181 end
182
183+# Note when LogReader finds record of a file that was copied in this commit
184+class CopiedFileHandler < FileHandler
185+ def handleFile(file)
186+ file.type="C"
187+ file.fromVer=$fromVer
188+ file.toVer=$toVer
189+ end
190+end
191+
192 # Note when LogReader finds record of a file that was modified in this commit
193 class ModifiedFileHandler < FileHandler
194 def handleFile(file)
195@@ -801,6 +868,15 @@
196 end
197 end
198
199+# Note when LogReader finds record of a file whose properties were modified in this commit
200+class ModifiedPropsFileHandler < FileHandler
201+ def handleFile(file)
202+ file.type="P"
203+ file.fromVer=$fromVer
204+ file.toVer=$toVer
205+ end
206+end
207+
208
209 # Used by UnifiedDiffHandler to record the number of added and removed lines
210 # appearing in a unidiff.
211@@ -967,11 +1043,21 @@
212 print($frontend.path($file.basedir, $file.tag))
213 println("</span><br />")
214 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>")
215+ when "C"
216+ print("<span class=\"pathname\" id=\"copied\">")
217+ print($frontend.path($file.basedir, $file.tag))
218+ println("</span><br />")
219+ 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>")
220 when "M"
221 print("<span class=\"pathname\">")
222 print($frontend.path($file.basedir, $file.tag))
223 println("</span><br />")
224 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>")
225+ when "P"
226+ print("<span class=\"pathname\">")
227+ print($frontend.path($file.basedir, $file.tag))
228+ println("</span><br />")
229+ 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>")
230 end
231 print("<pre class=\"diff\"><small id=\"info\">")
232 lines.each do |line|
233@@ -1045,7 +1131,7 @@
234 else
235 @stats.consume(line)
236 if $file.wants_diff_in_mail?
237- if @stats.diffLines < $maxLinesPerDiff
238+ if $maxLinesPerDiff.nil? || @stats.diffLines < $maxLinesPerDiff
239 @colour.consume(line)
240 elsif @stats.diffLines == $maxLinesPerDiff
241 @colour.consume(line)
242@@ -1062,7 +1148,7 @@
243 $file.isBinary = true
244 else
245 if $file.wants_diff_in_mail?
246- if @stats.diffLines > $maxLinesPerDiff
247+ if $maxLinesPerDiff && @stats.diffLines > $maxLinesPerDiff
248 println("</pre>")
249 println("<strong class=\"error\">[truncated at #{$maxLinesPerDiff} lines; #{@stats.diffLines-$maxLinesPerDiff} more skipped]</strong>")
250 else
251@@ -1230,13 +1316,18 @@
252 $users_file = "#{cvsroot_dir}/users"
253
254 $debug = false
255+$svn = false
256 $recipients = Array.new
257 $sendmail_prog = "/usr/sbin/sendmail"
258+$hostname = ENV['HOSTNAME'] || 'localhost'
259 $no_removed_file_diff = false
260 $no_added_file_diff = false
261 $no_diff = false
262-$task_keywords = ['TODO', 'FIXME']
263+$task_keywords = ['TODO', 'FIXME', 'FIXIT', 'todo']
264 $bugzillaURL = nil
265+$gforgeBugURL = nil
266+$gforgeTaskURL = nil
267+$wikiURL = nil
268 $jiraURL = nil
269 $ticketURL = nil
270 $viewcvsURL = nil
271@@ -1257,6 +1348,7 @@
272 [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ],
273 [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
274 [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
275+ [ "--svn", "-s", GetoptLong::NO_ARGUMENT ],
276 [ "--from", "-u", GetoptLong::REQUIRED_ARGUMENT ],
277 [ "--charset", GetoptLong::REQUIRED_ARGUMENT ]
278 )
279@@ -1265,6 +1357,7 @@
280 $recipients << EmailAddress.new(arg) if opt=="--to"
281 $config = arg if opt=="--config"
282 $debug = true if opt=="--debug"
283+ $svn = true if opt=="--svn"
284 $from_address = EmailAddress.new(arg) if opt=="--from"
285 # must use different variable as the config is readed later.
286 $arg_charset = arg if opt == "--charset"
287@@ -1277,12 +1370,13 @@
288 else
289 $stderr.puts "missing required file argument"
290 end
291- puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>"
292+ puts "Usage: cvsspam.rb [ --svn ] [ --to <email> ] [ --config <file> ] <collect_diffs file>"
293 exit(-1)
294 end
295
296 $logfile = ARGV[0]
297
298+
299 $additionalHeaders = Array.new
300 $problemHeaders = Array.new
301
302@@ -1343,12 +1437,21 @@
303 if $bugzillaURL != nil
304 commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugzillaSub
305 end
306+if $gforgeBugURL != nil
307+ commentSubstitutions['\B\[#[0-9]+\]'] = gforgeBugSub
308+end
309+if $gforgeTaskURL != nil
310+ commentSubstitutions['\B\[[Tt][0-9]+\]'] = gforgeTaskSub
311+end
312 if $jiraURL != nil
313 commentSubstitutions['\b[a-zA-Z]+-[0-9]+\b'] = jiraSub
314 end
315 if $ticketURL != nil
316 commentSubstitutions['\b[Tt][Ii][Cc][Kk][Ee][Tt]\s*#?[0-9]+\b'] = ticketSub
317 end
318+if $wikiURL != nil
319+ commentSubstitutions['\[\[.+\]\]'] = wikiSub
320+end
321 $commentEncoder = MultiSub.new(commentSubstitutions)
322
323
324@@ -1359,12 +1462,16 @@
325 "T" => tagHandler,
326 "A" => AddedFileHandler.new,
327 "R" => RemovedFileHandler.new,
328+ "C" => CopiedFileHandler.new,
329 "M" => ModifiedFileHandler.new,
330+ "P" => ModifiedPropsFileHandler.new,
331 "V" => VersionHandler.new]
332
333 $handlers["A"].setTagHandler(tagHandler)
334 $handlers["R"].setTagHandler(tagHandler)
335+$handlers["C"].setTagHandler(tagHandler)
336 $handlers["M"].setTagHandler(tagHandler)
337+$handlers["P"].setTagHandler(tagHandler)
338
339 $fileEntries = Array.new
340 $task_list = Array.new
341@@ -1374,7 +1481,8 @@
342
343 $diff_output_limiter = OutputSizeLimiter.new(mail, $mail_size_limit)
344
345- reader = LogReader.new($stdin)
346+ File.open($logfile) do |log|
347+ reader = LogReader.new(log)
348
349 until reader.eof
350 handler = $handlers[reader.currentLineCode]
351@@ -1383,11 +1491,16 @@
352 end
353 handler.handleLines(reader.getLines, $diff_output_limiter)
354 end
355+ end
356
357 end
358
359 if $subjectPrefix == nil
360- $subjectPrefix = "[SVN #{Repository.array.join(',')}]"
361+ if $svn
362+ $subjectPrefix = "[SVN #{Repository.array.join(',')}]"
363+ else
364+ $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
365+ end
366 end
367
368 if $files_in_subject
369@@ -1434,13 +1547,15 @@
370 #removed {background-color:#ffdddd;}
371 #removedchars {background-color:#ff9999;font-weight:bolder;}
372 tr.alt #removed {background-color:#f7cccc;}
373+ #copied {background-color:#ccccff;}
374+ tr.alt #copied {background-color:#bbbbf7;}
375 #info {color:#888888;}
376 #context {background-color:#eeeeee;}
377 td {padding-left:.3em;padding-right:.3em;}
378 tr.head {border-bottom-width:1px;border-bottom-style:solid;}
379 tr.head td {padding:0;padding-top:.2em;}
380 .task {background-color:#ffff00;}
381- .comment {padding:4px;border:1px dashed #000000;background-color:#ffffdd}
382+ .comment {white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;white-space:pre-wrap;word-wrap:break-word;padding:4px;border:1px dashed #000000;background-color:#ffffdd}
383 .error {color:red;}
384 hr {border-width:0px;height:2px;background:black;}
385 --></style>
386@@ -1466,7 +1581,9 @@
387
388 filesAdded = 0
389 filesRemoved = 0
390+ filesCopied = 0
391 filesModified = 0
392+ filesModifiedProps = 0
393 totalLinesAdded = 0
394 totalLinesRemoved = 0
395 file_count = 0
396@@ -1475,24 +1592,26 @@
397 $fileEntries.each do |file|
398 unless file.repository == last_repository
399 last_repository = file.repository
400- mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 5 : 4}\">")
401+ mail.print("<tr class=\"head\"><td colspan=\"#{last_repository.has_multiple_tags ? 6 : 5}\">")
402 if last_repository.has_multiple_tags
403 mail.print("Mixed-tag commit")
404 else
405 mail.print("Commit")
406 end
407 mail.print(" in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
408- if last_repository.trunk_only?
409- mail.print("<span id=\"info\"> on MAIN</span>")
410- else
411- mail.print(" on ")
412- tagCount = 0
413- last_repository.each_tag do |tag|
414- tagCount += 1
415- if tagCount > 1
416- mail.print tagCount<last_repository.tag_count ? ", " : " &amp; "
417+ if !$svn
418+ if last_repository.trunk_only?
419+ mail.print("<span id=\"info\"> on MAIN</span>")
420+ else
421+ mail.print(" on ")
422+ tagCount = 0
423+ last_repository.each_tag do |tag|
424+ tagCount += 1
425+ if tagCount > 1
426+ mail.print tagCount<last_repository.tag_count ? ", " : " &amp; "
427+ end
428+ mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
429 end
430- mail.print tag ? htmlEncode(tag) : "<span id=\"info\">MAIN</span>"
431 end
432 end
433 mail.puts("</td></tr>")
434@@ -1507,8 +1626,12 @@
435 filesAdded += 1
436 elsif file.removal?
437 filesRemoved += 1
438+ elsif file.copied?
439+ filesCopied += 1
440 elsif file.modification?
441 filesModified += 1
442+ elsif file.modifiedprops?
443+ filesModifiedProps += 1
444 end
445 name = htmlEncode(file.name_after_common_prefix)
446 slashPos = name.rindex("/")
447@@ -1528,17 +1651,29 @@
448 name = "<span id=\"added\">#{name}</span>"
449 elsif file.removal?
450 name = "<span id=\"removed\">#{name}</span>"
451+ elsif file.copied?
452+ name = "<span id=\"copied\">#{name}</span>"
453 end
454+ mail.print("<td>")
455 if file.has_diff?
456- mail.print("<td><tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a></tt></td>")
457+ mail.print("<tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a></tt>")
458 else
459- mail.print("<td><tt>#{prefix}#{name}</tt></td>")
460+ mail.print("<tt>#{prefix}#{name}</tt>")
461 end
462- if file.isEmpty
463- mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
464+ mail.print(" #{$frontend.log(file)}")
465+ mail.print("</td>")
466+ if file.copied?
467+ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[copied]</small></td>")
468+ elsif file.isEmpty
469+ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[empty]</small></td>")
470 elsif file.isBinary
471- mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
472+ mail.print("<td colspan=\"3\" align=\"center\"><small id=\"info\">[binary]</small></td>")
473 else
474+ if file.modifiedprops?
475+ mail.print("<td align=\"right\"><small id=\"info\">[props]</small></td>")
476+ else
477+ mail.print("<td></td>")
478+ end
479 if file.lineAdditions>0
480 totalLinesAdded += file.lineAdditions
481 mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
482@@ -1565,15 +1700,19 @@
483 mail.print("<td nowrap=\"nowrap\" align=\"right\">added #{$frontend.version(file.path,file.toVer)}</td>")
484 elsif file.removal?
485 mail.print("<td nowrap=\"nowrap\">#{$frontend.version(file.path,file.fromVer)} removed</td>")
486+ elsif file.copied?
487+ mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
488 elsif file.modification?
489 mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
490+ elsif file.modifiedprops?
491+ mail.print("<td nowrap=\"nowrap\" align=\"center\">#{$frontend.version(file.path,file.fromVer)} #{$frontend.diff(file)} #{$frontend.version(file.path,file.toVer)}</td>")
492 end
493
494 mail.puts("</tr>")
495 end
496 if $fileEntries.size>1 && (totalLinesAdded+totalLinesRemoved)>0
497 # give total number of lines added/removed accross all files
498- mail.print("<tr><td></td>")
499+ mail.print("<tr><td></td><td></td>")
500 if totalLinesAdded>0
501 mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
502 else
503@@ -1590,7 +1729,7 @@
504
505 mail.puts("</table>")
506
507- totalFilesChanged = filesAdded+filesRemoved+filesModified
508+ totalFilesChanged = filesAdded+filesRemoved+filesCopied+filesModified+filesModifiedProps
509 if totalFilesChanged > 1
510 mail.print("<small id=\"info\">")
511 changeKind = 0
512@@ -1603,11 +1742,21 @@
513 mail.print("#{filesRemoved} removed")
514 changeKind += 1
515 end
516+ if filesCopied>0
517+ mail.print(" + ") if changeKind>0
518+ mail.print("#{filesCopied} copied")
519+ changeKind += 1
520+ end
521 if filesModified>0
522 mail.print(" + ") if changeKind>0
523 mail.print("#{filesModified} modified")
524 changeKind += 1
525 end
526+ if filesModifiedProps>0
527+ mail.print(" + ") if changeKind>0
528+ mail.print("#{filesModifiedProps} modified properties")
529+ changeKind += 1
530+ end
531 mail.print(", total #{totalFilesChanged}") if changeKind > 1
532 mail.puts(" files</small><br />")
533 end
534@@ -1742,7 +1891,7 @@
535 from = EmailAddress.new(ENV['USER'] || ENV['USERNAME'] || 'cvsspam')
536 end
537 unless from.address =~ /@/
538- from.address = "#{from.address}@#{ENV['HOSTNAME']||'localhost'}"
539+ from.address = "#{from.address}@#{$hostname}"
540 end
541 smtp = Net::SMTP.new(@smtp_host)
542 blah("connecting to '#{@smtp_host}'")
543@@ -1758,6 +1907,40 @@
544 end
545 end
546
547+
548+def make_msg_id(localpart, hostpart)
549+ "<cvsspam-#{localpart}@#{hostpart}>"
550+end
551+
552+
553+# replaces control characters, and a selection of other characters that
554+# may not appear unquoted in an RFC822 'word', with underscores. (It
555+# doesn't actually zap '.' though.)
556+def zap_header_special_chars(text)
557+ text.gsub(/<>()\[\]@,;:\\[\000-\037\177]/, "_")
558+end
559+
560+
561+# Mail clients will try to 'thread' together a conversation over
562+# several email messages by inspecting the In-Reply-To and References headers,
563+# which should refer to previous emails in the conversation by mentioning
564+# the value of the previous message's Message-Id header. This function invents
565+# values for these headers so that, in the special case where a *single* file
566+# is committed to repeatedly, the emails giving notification of these commits
567+# can be threaded together automatically by the mail client.
568+def inject_threading_headers(mail)
569+ return unless $fileEntries.length == 1
570+ file = $fileEntries[0]
571+ name = zap_header_special_chars(file.path)
572+ if file.fromVer
573+ mail.header("References", make_msg_id("#{name}.#{file.fromVer}", $hostname))
574+ end
575+ if file.toVer
576+ mail.header("Message-ID", make_msg_id("#{name}.#{file.toVer}", $hostname))
577+ end
578+end
579+
580+
581 if $smtp_host
582 require 'net/smtp'
583 mailer = SMTPMailer.new($smtp_host)
584@@ -1769,6 +1952,7 @@
585
586 mailer.send($from_address, $recipients) do |mail|
587 mail.header("Subject", mailSubject)
588+ inject_threading_headers(mail)
589 mail.header("MIME-Version", "1.0")
590 mail.header("Content-Type", "text/html" + ($charset.nil? ? "" : "; charset=\"#{$charset}\""))
591 if ENV['REMOTE_HOST']
This page took 0.12904 seconds and 4 git commands to generate.