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