##// END OF EJS Templates
Log SCM stderr when log level is set to debug only (#13541)....
Jean-Philippe Lang -
r11450:d8769cb27cc5
parent child
Show More
@@ -1,429 +1,429
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'cgi'
19 19
20 20 if RUBY_VERSION < '1.9'
21 21 require 'iconv'
22 22 end
23 23
24 24 module Redmine
25 25 module Scm
26 26 module Adapters
27 27 class CommandFailed < StandardError #:nodoc:
28 28 end
29 29
30 30 class AbstractAdapter #:nodoc:
31 31
32 32 # raised if scm command exited with error, e.g. unknown revision.
33 33 class ScmCommandAborted < CommandFailed; end
34 34
35 35 class << self
36 36 def client_command
37 37 ""
38 38 end
39 39
40 40 def shell_quote_command
41 41 if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
42 42 client_command
43 43 else
44 44 shell_quote(client_command)
45 45 end
46 46 end
47 47
48 48 # Returns the version of the scm client
49 49 # Eg: [1, 5, 0] or [] if unknown
50 50 def client_version
51 51 []
52 52 end
53 53
54 54 # Returns the version string of the scm client
55 55 # Eg: '1.5.0' or 'Unknown version' if unknown
56 56 def client_version_string
57 57 v = client_version || 'Unknown version'
58 58 v.is_a?(Array) ? v.join('.') : v.to_s
59 59 end
60 60
61 61 # Returns true if the current client version is above
62 62 # or equals the given one
63 63 # If option is :unknown is set to true, it will return
64 64 # true if the client version is unknown
65 65 def client_version_above?(v, options={})
66 66 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
67 67 end
68 68
69 69 def client_available
70 70 true
71 71 end
72 72
73 73 def shell_quote(str)
74 74 if Redmine::Platform.mswin?
75 75 '"' + str.gsub(/"/, '\\"') + '"'
76 76 else
77 77 "'" + str.gsub(/'/, "'\"'\"'") + "'"
78 78 end
79 79 end
80 80 end
81 81
82 82 def initialize(url, root_url=nil, login=nil, password=nil,
83 83 path_encoding=nil)
84 84 @url = url
85 85 @login = login if login && !login.empty?
86 86 @password = (password || "") if @login
87 87 @root_url = root_url.blank? ? retrieve_root_url : root_url
88 88 end
89 89
90 90 def adapter_name
91 91 'Abstract'
92 92 end
93 93
94 94 def supports_cat?
95 95 true
96 96 end
97 97
98 98 def supports_annotate?
99 99 respond_to?('annotate')
100 100 end
101 101
102 102 def root_url
103 103 @root_url
104 104 end
105 105
106 106 def url
107 107 @url
108 108 end
109 109
110 110 def path_encoding
111 111 nil
112 112 end
113 113
114 114 # get info about the svn repository
115 115 def info
116 116 return nil
117 117 end
118 118
119 119 # Returns the entry identified by path and revision identifier
120 120 # or nil if entry doesn't exist in the repository
121 121 def entry(path=nil, identifier=nil)
122 122 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
123 123 search_path = parts[0..-2].join('/')
124 124 search_name = parts[-1]
125 125 if search_path.blank? && search_name.blank?
126 126 # Root entry
127 127 Entry.new(:path => '', :kind => 'dir')
128 128 else
129 129 # Search for the entry in the parent directory
130 130 es = entries(search_path, identifier)
131 131 es ? es.detect {|e| e.name == search_name} : nil
132 132 end
133 133 end
134 134
135 135 # Returns an Entries collection
136 136 # or nil if the given path doesn't exist in the repository
137 137 def entries(path=nil, identifier=nil, options={})
138 138 return nil
139 139 end
140 140
141 141 def branches
142 142 return nil
143 143 end
144 144
145 145 def tags
146 146 return nil
147 147 end
148 148
149 149 def default_branch
150 150 return nil
151 151 end
152 152
153 153 def properties(path, identifier=nil)
154 154 return nil
155 155 end
156 156
157 157 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
158 158 return nil
159 159 end
160 160
161 161 def diff(path, identifier_from, identifier_to=nil)
162 162 return nil
163 163 end
164 164
165 165 def cat(path, identifier=nil)
166 166 return nil
167 167 end
168 168
169 169 def with_leading_slash(path)
170 170 path ||= ''
171 171 (path[0,1]!="/") ? "/#{path}" : path
172 172 end
173 173
174 174 def with_trailling_slash(path)
175 175 path ||= ''
176 176 (path[-1,1] == "/") ? path : "#{path}/"
177 177 end
178 178
179 179 def without_leading_slash(path)
180 180 path ||= ''
181 181 path.gsub(%r{^/+}, '')
182 182 end
183 183
184 184 def without_trailling_slash(path)
185 185 path ||= ''
186 186 (path[-1,1] == "/") ? path[0..-2] : path
187 187 end
188 188
189 189 def shell_quote(str)
190 190 self.class.shell_quote(str)
191 191 end
192 192
193 193 private
194 194 def retrieve_root_url
195 195 info = self.info
196 196 info ? info.root_url : nil
197 197 end
198 198
199 199 def target(path, sq=true)
200 200 path ||= ''
201 201 base = path.match(/^\//) ? root_url : url
202 202 str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
203 203 if sq
204 204 str = shell_quote(str)
205 205 end
206 206 str
207 207 end
208 208
209 209 def logger
210 210 self.class.logger
211 211 end
212 212
213 213 def shellout(cmd, options = {}, &block)
214 214 self.class.shellout(cmd, options, &block)
215 215 end
216 216
217 217 def self.logger
218 218 Rails.logger
219 219 end
220 220
221 221 # Path to the file where scm stderr output is logged
222 222 def self.stderr_log_file
223 223 @stderr_log_path ||=
224 224 Redmine::Configuration['scm_stderr_log_file'].presence ||
225 225 Rails.root.join("log/#{Rails.env}.scm.stderr.log").to_s
226 226 end
227 227
228 228 def self.shellout(cmd, options = {}, &block)
229 229 if logger && logger.debug?
230 230 logger.debug "Shelling out: #{strip_credential(cmd)}"
231 # Capture stderr in a log file
232 cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}"
231 233 end
232 # Capture stderr in a log file
233 cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}"
234 234 begin
235 235 mode = "r+"
236 236 IO.popen(cmd, mode) do |io|
237 237 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
238 238 io.close_write unless options[:write_stdin]
239 239 block.call(io) if block_given?
240 240 end
241 241 ## If scm command does not exist,
242 242 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
243 243 ## in production environment.
244 244 # rescue Errno::ENOENT => e
245 245 rescue Exception => e
246 246 msg = strip_credential(e.message)
247 247 # The command failed, log it and re-raise
248 248 logmsg = "SCM command failed, "
249 249 logmsg += "make sure that your SCM command (e.g. svn) is "
250 250 logmsg += "in PATH (#{ENV['PATH']})\n"
251 251 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
252 252 logmsg += "#{strip_credential(cmd)}\n"
253 253 logmsg += "with: #{msg}"
254 254 logger.error(logmsg)
255 255 raise CommandFailed.new(msg)
256 256 end
257 257 end
258 258
259 259 # Hides username/password in a given command
260 260 def self.strip_credential(cmd)
261 261 q = (Redmine::Platform.mswin? ? '"' : "'")
262 262 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
263 263 end
264 264
265 265 def strip_credential(cmd)
266 266 self.class.strip_credential(cmd)
267 267 end
268 268
269 269 def scm_iconv(to, from, str)
270 270 return nil if str.nil?
271 271 return str if to == from
272 272 if str.respond_to?(:force_encoding)
273 273 str.force_encoding(from)
274 274 begin
275 275 str.encode(to)
276 276 rescue Exception => err
277 277 logger.error("failed to convert from #{from} to #{to}. #{err}")
278 278 nil
279 279 end
280 280 else
281 281 begin
282 282 Iconv.conv(to, from, str)
283 283 rescue Iconv::Failure => err
284 284 logger.error("failed to convert from #{from} to #{to}. #{err}")
285 285 nil
286 286 end
287 287 end
288 288 end
289 289
290 290 def parse_xml(xml)
291 291 if RUBY_PLATFORM == 'java'
292 292 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
293 293 end
294 294 ActiveSupport::XmlMini.parse(xml)
295 295 end
296 296 end
297 297
298 298 class Entries < Array
299 299 def sort_by_name
300 300 dup.sort! {|x,y|
301 301 if x.kind == y.kind
302 302 x.name.to_s <=> y.name.to_s
303 303 else
304 304 x.kind <=> y.kind
305 305 end
306 306 }
307 307 end
308 308
309 309 def revisions
310 310 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
311 311 end
312 312 end
313 313
314 314 class Info
315 315 attr_accessor :root_url, :lastrev
316 316 def initialize(attributes={})
317 317 self.root_url = attributes[:root_url] if attributes[:root_url]
318 318 self.lastrev = attributes[:lastrev]
319 319 end
320 320 end
321 321
322 322 class Entry
323 323 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
324 324
325 325 def initialize(attributes={})
326 326 self.name = attributes[:name] if attributes[:name]
327 327 self.path = attributes[:path] if attributes[:path]
328 328 self.kind = attributes[:kind] if attributes[:kind]
329 329 self.size = attributes[:size].to_i if attributes[:size]
330 330 self.lastrev = attributes[:lastrev]
331 331 end
332 332
333 333 def is_file?
334 334 'file' == self.kind
335 335 end
336 336
337 337 def is_dir?
338 338 'dir' == self.kind
339 339 end
340 340
341 341 def is_text?
342 342 Redmine::MimeType.is_type?('text', name)
343 343 end
344 344
345 345 def author
346 346 if changeset
347 347 changeset.author.to_s
348 348 elsif lastrev
349 349 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
350 350 end
351 351 end
352 352 end
353 353
354 354 class Revisions < Array
355 355 def latest
356 356 sort {|x,y|
357 357 unless x.time.nil? or y.time.nil?
358 358 x.time <=> y.time
359 359 else
360 360 0
361 361 end
362 362 }.last
363 363 end
364 364 end
365 365
366 366 class Revision
367 367 attr_accessor :scmid, :name, :author, :time, :message,
368 368 :paths, :revision, :branch, :identifier,
369 369 :parents
370 370
371 371 def initialize(attributes={})
372 372 self.identifier = attributes[:identifier]
373 373 self.scmid = attributes[:scmid]
374 374 self.name = attributes[:name] || self.identifier
375 375 self.author = attributes[:author]
376 376 self.time = attributes[:time]
377 377 self.message = attributes[:message] || ""
378 378 self.paths = attributes[:paths]
379 379 self.revision = attributes[:revision]
380 380 self.branch = attributes[:branch]
381 381 self.parents = attributes[:parents]
382 382 end
383 383
384 384 # Returns the readable identifier.
385 385 def format_identifier
386 386 self.identifier.to_s
387 387 end
388 388
389 389 def ==(other)
390 390 if other.nil?
391 391 false
392 392 elsif scmid.present?
393 393 scmid == other.scmid
394 394 elsif identifier.present?
395 395 identifier == other.identifier
396 396 elsif revision.present?
397 397 revision == other.revision
398 398 end
399 399 end
400 400 end
401 401
402 402 class Annotate
403 403 attr_reader :lines, :revisions
404 404
405 405 def initialize
406 406 @lines = []
407 407 @revisions = []
408 408 end
409 409
410 410 def add_line(line, revision)
411 411 @lines << line
412 412 @revisions << revision
413 413 end
414 414
415 415 def content
416 416 content = lines.join("\n")
417 417 end
418 418
419 419 def empty?
420 420 lines.empty?
421 421 end
422 422 end
423 423
424 424 class Branch < String
425 425 attr_accessor :revision, :scmid
426 426 end
427 427 end
428 428 end
429 429 end
General Comments 0
You need to be logged in to leave comments. Login now