##// END OF EJS Templates
code cleanup scm_iconv (#12228)...
Toshi MARUYAMA -
r10527:625eebb72060
parent child
Show More
@@ -1,420 +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 if str.respond_to?(:force_encoding)
263 if str.respond_to?(:force_encoding)
264 str.force_encoding(from)
264 str.force_encoding(from)
265 begin
265 begin
266 s = str.encode(to)
266 str.encode(to)
267 rescue Exception => err
267 rescue Exception => err
268 logger.error("failed to convert from #{from} to #{to}. #{err}")
268 logger.error("failed to convert from #{from} to #{to}. #{err}")
269 nil
269 nil
270 end
270 end
271 else
271 else
272 begin
272 begin
273 Iconv.conv(to, from, str)
273 Iconv.conv(to, from, str)
274 rescue Iconv::Failure => err
274 rescue Iconv::Failure => err
275 logger.error("failed to convert from #{from} to #{to}. #{err}")
275 logger.error("failed to convert from #{from} to #{to}. #{err}")
276 nil
276 nil
277 end
277 end
278 end
278 end
279 end
279 end
280
280
281 def parse_xml(xml)
281 def parse_xml(xml)
282 if RUBY_PLATFORM == 'java'
282 if RUBY_PLATFORM == 'java'
283 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
283 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
284 end
284 end
285 ActiveSupport::XmlMini.parse(xml)
285 ActiveSupport::XmlMini.parse(xml)
286 end
286 end
287 end
287 end
288
288
289 class Entries < Array
289 class Entries < Array
290 def sort_by_name
290 def sort_by_name
291 dup.sort! {|x,y|
291 dup.sort! {|x,y|
292 if x.kind == y.kind
292 if x.kind == y.kind
293 x.name.to_s <=> y.name.to_s
293 x.name.to_s <=> y.name.to_s
294 else
294 else
295 x.kind <=> y.kind
295 x.kind <=> y.kind
296 end
296 end
297 }
297 }
298 end
298 end
299
299
300 def revisions
300 def revisions
301 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
301 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
302 end
302 end
303 end
303 end
304
304
305 class Info
305 class Info
306 attr_accessor :root_url, :lastrev
306 attr_accessor :root_url, :lastrev
307 def initialize(attributes={})
307 def initialize(attributes={})
308 self.root_url = attributes[:root_url] if attributes[:root_url]
308 self.root_url = attributes[:root_url] if attributes[:root_url]
309 self.lastrev = attributes[:lastrev]
309 self.lastrev = attributes[:lastrev]
310 end
310 end
311 end
311 end
312
312
313 class Entry
313 class Entry
314 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
314 attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
315
315
316 def initialize(attributes={})
316 def initialize(attributes={})
317 self.name = attributes[:name] if attributes[:name]
317 self.name = attributes[:name] if attributes[:name]
318 self.path = attributes[:path] if attributes[:path]
318 self.path = attributes[:path] if attributes[:path]
319 self.kind = attributes[:kind] if attributes[:kind]
319 self.kind = attributes[:kind] if attributes[:kind]
320 self.size = attributes[:size].to_i if attributes[:size]
320 self.size = attributes[:size].to_i if attributes[:size]
321 self.lastrev = attributes[:lastrev]
321 self.lastrev = attributes[:lastrev]
322 end
322 end
323
323
324 def is_file?
324 def is_file?
325 'file' == self.kind
325 'file' == self.kind
326 end
326 end
327
327
328 def is_dir?
328 def is_dir?
329 'dir' == self.kind
329 'dir' == self.kind
330 end
330 end
331
331
332 def is_text?
332 def is_text?
333 Redmine::MimeType.is_type?('text', name)
333 Redmine::MimeType.is_type?('text', name)
334 end
334 end
335
335
336 def author
336 def author
337 if changeset
337 if changeset
338 changeset.author.to_s
338 changeset.author.to_s
339 elsif lastrev
339 elsif lastrev
340 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
340 Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
341 end
341 end
342 end
342 end
343 end
343 end
344
344
345 class Revisions < Array
345 class Revisions < Array
346 def latest
346 def latest
347 sort {|x,y|
347 sort {|x,y|
348 unless x.time.nil? or y.time.nil?
348 unless x.time.nil? or y.time.nil?
349 x.time <=> y.time
349 x.time <=> y.time
350 else
350 else
351 0
351 0
352 end
352 end
353 }.last
353 }.last
354 end
354 end
355 end
355 end
356
356
357 class Revision
357 class Revision
358 attr_accessor :scmid, :name, :author, :time, :message,
358 attr_accessor :scmid, :name, :author, :time, :message,
359 :paths, :revision, :branch, :identifier,
359 :paths, :revision, :branch, :identifier,
360 :parents
360 :parents
361
361
362 def initialize(attributes={})
362 def initialize(attributes={})
363 self.identifier = attributes[:identifier]
363 self.identifier = attributes[:identifier]
364 self.scmid = attributes[:scmid]
364 self.scmid = attributes[:scmid]
365 self.name = attributes[:name] || self.identifier
365 self.name = attributes[:name] || self.identifier
366 self.author = attributes[:author]
366 self.author = attributes[:author]
367 self.time = attributes[:time]
367 self.time = attributes[:time]
368 self.message = attributes[:message] || ""
368 self.message = attributes[:message] || ""
369 self.paths = attributes[:paths]
369 self.paths = attributes[:paths]
370 self.revision = attributes[:revision]
370 self.revision = attributes[:revision]
371 self.branch = attributes[:branch]
371 self.branch = attributes[:branch]
372 self.parents = attributes[:parents]
372 self.parents = attributes[:parents]
373 end
373 end
374
374
375 # Returns the readable identifier.
375 # Returns the readable identifier.
376 def format_identifier
376 def format_identifier
377 self.identifier.to_s
377 self.identifier.to_s
378 end
378 end
379
379
380 def ==(other)
380 def ==(other)
381 if other.nil?
381 if other.nil?
382 false
382 false
383 elsif scmid.present?
383 elsif scmid.present?
384 scmid == other.scmid
384 scmid == other.scmid
385 elsif identifier.present?
385 elsif identifier.present?
386 identifier == other.identifier
386 identifier == other.identifier
387 elsif revision.present?
387 elsif revision.present?
388 revision == other.revision
388 revision == other.revision
389 end
389 end
390 end
390 end
391 end
391 end
392
392
393 class Annotate
393 class Annotate
394 attr_reader :lines, :revisions
394 attr_reader :lines, :revisions
395
395
396 def initialize
396 def initialize
397 @lines = []
397 @lines = []
398 @revisions = []
398 @revisions = []
399 end
399 end
400
400
401 def add_line(line, revision)
401 def add_line(line, revision)
402 @lines << line
402 @lines << line
403 @revisions << revision
403 @revisions << revision
404 end
404 end
405
405
406 def content
406 def content
407 content = lines.join("\n")
407 content = lines.join("\n")
408 end
408 end
409
409
410 def empty?
410 def empty?
411 lines.empty?
411 lines.empty?
412 end
412 end
413 end
413 end
414
414
415 class Branch < String
415 class Branch < String
416 attr_accessor :revision, :scmid
416 attr_accessor :revision, :scmid
417 end
417 end
418 end
418 end
419 end
419 end
420 end
420 end
General Comments 0
You need to be logged in to leave comments. Login now