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