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