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