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