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