##// END OF EJS Templates
use String#encode at scm_iconv on Ruby 1.9 (#12228)...
Toshi MARUYAMA -
r10525:5a2c80cb48c4
parent child
Show More
@@ -1,410 +1,420
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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, options = {}, &block)
209 def shellout(cmd, options = {}, &block)
210 self.class.shellout(cmd, options, &block)
210 self.class.shellout(cmd, options, &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, options = {}, &block)
217 def self.shellout(cmd, options = {}, &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>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
223 cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
224 end
224 end
225 begin
225 begin
226 mode = "r+"
226 mode = "r+"
227 IO.popen(cmd, mode) do |io|
227 IO.popen(cmd, mode) do |io|
228 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
228 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
229 io.close_write unless options[:write_stdin]
229 io.close_write unless options[:write_stdin]
230 block.call(io) if block_given?
230 block.call(io) if block_given?
231 end
231 end
232 ## If scm command does not exist,
232 ## If scm command does not exist,
233 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
233 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
234 ## in production environment.
234 ## in production environment.
235 # rescue Errno::ENOENT => e
235 # rescue Errno::ENOENT => e
236 rescue Exception => e
236 rescue Exception => e
237 msg = strip_credential(e.message)
237 msg = strip_credential(e.message)
238 # The command failed, log it and re-raise
238 # The command failed, log it and re-raise
239 logmsg = "SCM command failed, "
239 logmsg = "SCM command failed, "
240 logmsg += "make sure that your SCM command (e.g. svn) is "
240 logmsg += "make sure that your SCM command (e.g. svn) is "
241 logmsg += "in PATH (#{ENV['PATH']})\n"
241 logmsg += "in PATH (#{ENV['PATH']})\n"
242 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
242 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
243 logmsg += "#{strip_credential(cmd)}\n"
243 logmsg += "#{strip_credential(cmd)}\n"
244 logmsg += "with: #{msg}"
244 logmsg += "with: #{msg}"
245 logger.error(logmsg)
245 logger.error(logmsg)
246 raise CommandFailed.new(msg)
246 raise CommandFailed.new(msg)
247 end
247 end
248 end
248 end
249
249
250 # Hides username/password in a given command
250 # Hides username/password in a given command
251 def self.strip_credential(cmd)
251 def self.strip_credential(cmd)
252 q = (Redmine::Platform.mswin? ? '"' : "'")
252 q = (Redmine::Platform.mswin? ? '"' : "'")
253 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
253 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
254 end
254 end
255
255
256 def strip_credential(cmd)
256 def strip_credential(cmd)
257 self.class.strip_credential(cmd)
257 self.class.strip_credential(cmd)
258 end
258 end
259
259
260 def scm_iconv(to, from, str)
260 def scm_iconv(to, from, str)
261 return nil if str.nil?
261 return nil if str.nil?
262 return str if to == from
262 return str if to == from
263 begin
263 if str.respond_to?(:force_encoding)
264 Iconv.conv(to, from, str)
264 str.force_encoding(from)
265 rescue Iconv::Failure => err
265 begin
266 logger.error("failed to convert from #{from} to #{to}. #{err}")
266 s = str.encode(to)
267 nil
267 rescue Exception => e
268 logger.error("failed to convert from #{from} to #{to}. #{err}")
269 nil
270 end
271 else
272 begin
273 Iconv.conv(to, from, str)
274 rescue Iconv::Failure => err
275 logger.error("failed to convert from #{from} to #{to}. #{err}")
276 nil
277 end
268 end
278 end
269 end
279 end
270
280
271 def parse_xml(xml)
281 def parse_xml(xml)
272 if RUBY_PLATFORM == 'java'
282 if RUBY_PLATFORM == 'java'
273 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
283 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
274 end
284 end
275 ActiveSupport::XmlMini.parse(xml)
285 ActiveSupport::XmlMini.parse(xml)
276 end
286 end
277 end
287 end
278
288
279 class Entries < Array
289 class Entries < Array
280 def sort_by_name
290 def sort_by_name
281 dup.sort! {|x,y|
291 dup.sort! {|x,y|
282 if x.kind == y.kind
292 if x.kind == y.kind
283 x.name.to_s <=> y.name.to_s
293 x.name.to_s <=> y.name.to_s
284 else
294 else
285 x.kind <=> y.kind
295 x.kind <=> y.kind
286 end
296 end
287 }
297 }
288 end
298 end
289
299
290 def revisions
300 def revisions
291 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
301 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
292 end
302 end
293 end
303 end
294
304
295 class Info
305 class Info
296 attr_accessor :root_url, :lastrev
306 attr_accessor :root_url, :lastrev
297 def initialize(attributes={})
307 def initialize(attributes={})
298 self.root_url = attributes[:root_url] if attributes[:root_url]
308 self.root_url = attributes[:root_url] if attributes[:root_url]
299 self.lastrev = attributes[:lastrev]
309 self.lastrev = attributes[:lastrev]
300 end
310 end
301 end
311 end
302
312
303 class Entry
313 class Entry
304 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
314 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
305
315
306 def initialize(attributes={})
316 def initialize(attributes={})
307 self.name = attributes[:name] if attributes[:name]
317 self.name = attributes[:name] if attributes[:name]
308 self.path = attributes[:path] if attributes[:path]
318 self.path = attributes[:path] if attributes[:path]
309 self.kind = attributes[:kind] if attributes[:kind]
319 self.kind = attributes[:kind] if attributes[:kind]
310 self.size = attributes[:size].to_i if attributes[:size]
320 self.size = attributes[:size].to_i if attributes[:size]
311 self.lastrev = attributes[:lastrev]
321 self.lastrev = attributes[:lastrev]
312 end
322 end
313
323
314 def is_file?
324 def is_file?
315 'file' == self.kind
325 'file' == self.kind
316 end
326 end
317
327
318 def is_dir?
328 def is_dir?
319 'dir' == self.kind
329 'dir' == self.kind
320 end
330 end
321
331
322 def is_text?
332 def is_text?
323 Redmine::MimeType.is_type?('text', name)
333 Redmine::MimeType.is_type?('text', name)
324 end
334 end
325
335
326 def author
336 def author
327 if changeset
337 if changeset
328 changeset.author.to_s
338 changeset.author.to_s
329 elsif lastrev
339 elsif lastrev
330 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
340 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
331 end
341 end
332 end
342 end
333 end
343 end
334
344
335 class Revisions < Array
345 class Revisions < Array
336 def latest
346 def latest
337 sort {|x,y|
347 sort {|x,y|
338 unless x.time.nil? or y.time.nil?
348 unless x.time.nil? or y.time.nil?
339 x.time <=> y.time
349 x.time <=> y.time
340 else
350 else
341 0
351 0
342 end
352 end
343 }.last
353 }.last
344 end
354 end
345 end
355 end
346
356
347 class Revision
357 class Revision
348 attr_accessor :scmid, :name, :author, :time, :message,
358 attr_accessor :scmid, :name, :author, :time, :message,
349 :paths, :revision, :branch, :identifier,
359 :paths, :revision, :branch, :identifier,
350 :parents
360 :parents
351
361
352 def initialize(attributes={})
362 def initialize(attributes={})
353 self.identifier = attributes[:identifier]
363 self.identifier = attributes[:identifier]
354 self.scmid = attributes[:scmid]
364 self.scmid = attributes[:scmid]
355 self.name = attributes[:name] || self.identifier
365 self.name = attributes[:name] || self.identifier
356 self.author = attributes[:author]
366 self.author = attributes[:author]
357 self.time = attributes[:time]
367 self.time = attributes[:time]
358 self.message = attributes[:message] || ""
368 self.message = attributes[:message] || ""
359 self.paths = attributes[:paths]
369 self.paths = attributes[:paths]
360 self.revision = attributes[:revision]
370 self.revision = attributes[:revision]
361 self.branch = attributes[:branch]
371 self.branch = attributes[:branch]
362 self.parents = attributes[:parents]
372 self.parents = attributes[:parents]
363 end
373 end
364
374
365 # Returns the readable identifier.
375 # Returns the readable identifier.
366 def format_identifier
376 def format_identifier
367 self.identifier.to_s
377 self.identifier.to_s
368 end
378 end
369
379
370 def ==(other)
380 def ==(other)
371 if other.nil?
381 if other.nil?
372 false
382 false
373 elsif scmid.present?
383 elsif scmid.present?
374 scmid == other.scmid
384 scmid == other.scmid
375 elsif identifier.present?
385 elsif identifier.present?
376 identifier == other.identifier
386 identifier == other.identifier
377 elsif revision.present?
387 elsif revision.present?
378 revision == other.revision
388 revision == other.revision
379 end
389 end
380 end
390 end
381 end
391 end
382
392
383 class Annotate
393 class Annotate
384 attr_reader :lines, :revisions
394 attr_reader :lines, :revisions
385
395
386 def initialize
396 def initialize
387 @lines = []
397 @lines = []
388 @revisions = []
398 @revisions = []
389 end
399 end
390
400
391 def add_line(line, revision)
401 def add_line(line, revision)
392 @lines << line
402 @lines << line
393 @revisions << revision
403 @revisions << revision
394 end
404 end
395
405
396 def content
406 def content
397 content = lines.join("\n")
407 content = lines.join("\n")
398 end
408 end
399
409
400 def empty?
410 def empty?
401 lines.empty?
411 lines.empty?
402 end
412 end
403 end
413 end
404
414
405 class Branch < String
415 class Branch < String
406 attr_accessor :revision, :scmid
416 attr_accessor :revision, :scmid
407 end
417 end
408 end
418 end
409 end
419 end
410 end
420 end
General Comments 0
You need to be logged in to leave comments. Login now