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