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