##// END OF EJS Templates
Fixes nil error when svn binary version is unknown (#1607)....
Jean-Philippe Lang -
r1638:622b6121f48f
parent child
Show More
@@ -1,287 +1,296
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 class << self
27 class << self
28 # Returns the version of the scm client
28 # Returns the version of the scm client
29 # Eg: [1, 5, 0]
29 # Eg: [1, 5, 0] or [] if unknown
30 def client_version
30 def client_version
31 'Unknown version'
31 []
32 end
32 end
33
33
34 # Returns the version string of the scm client
34 # Returns the version string of the scm client
35 # Eg: '1.5.0'
35 # Eg: '1.5.0' or 'Unknown version' if unknown
36 def client_version_string
36 def client_version_string
37 client_version.is_a?(Array) ? client_version.join('.') : client_version.to_s
37 v = client_version || 'Unknown version'
38 v.is_a?(Array) ? v.join('.') : v.to_s
39 end
40
41 # Returns true if the current client version is above
42 # or equals the given one
43 # If option is :unknown is set to true, it will return
44 # true if the client version is unknown
45 def client_version_above?(v, options={})
46 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
38 end
47 end
39 end
48 end
40
49
41 def initialize(url, root_url=nil, login=nil, password=nil)
50 def initialize(url, root_url=nil, login=nil, password=nil)
42 @url = url
51 @url = url
43 @login = login if login && !login.empty?
52 @login = login if login && !login.empty?
44 @password = (password || "") if @login
53 @password = (password || "") if @login
45 @root_url = root_url.blank? ? retrieve_root_url : root_url
54 @root_url = root_url.blank? ? retrieve_root_url : root_url
46 end
55 end
47
56
48 def adapter_name
57 def adapter_name
49 'Abstract'
58 'Abstract'
50 end
59 end
51
60
52 def supports_cat?
61 def supports_cat?
53 true
62 true
54 end
63 end
55
64
56 def supports_annotate?
65 def supports_annotate?
57 respond_to?('annotate')
66 respond_to?('annotate')
58 end
67 end
59
68
60 def root_url
69 def root_url
61 @root_url
70 @root_url
62 end
71 end
63
72
64 def url
73 def url
65 @url
74 @url
66 end
75 end
67
76
68 # get info about the svn repository
77 # get info about the svn repository
69 def info
78 def info
70 return nil
79 return nil
71 end
80 end
72
81
73 # Returns the entry identified by path and revision identifier
82 # Returns the entry identified by path and revision identifier
74 # or nil if entry doesn't exist in the repository
83 # or nil if entry doesn't exist in the repository
75 def entry(path=nil, identifier=nil)
84 def entry(path=nil, identifier=nil)
76 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
85 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
77 search_path = parts[0..-2].join('/')
86 search_path = parts[0..-2].join('/')
78 search_name = parts[-1]
87 search_name = parts[-1]
79 if search_path.blank? && search_name.blank?
88 if search_path.blank? && search_name.blank?
80 # Root entry
89 # Root entry
81 Entry.new(:path => '', :kind => 'dir')
90 Entry.new(:path => '', :kind => 'dir')
82 else
91 else
83 # Search for the entry in the parent directory
92 # Search for the entry in the parent directory
84 es = entries(search_path, identifier)
93 es = entries(search_path, identifier)
85 es ? es.detect {|e| e.name == search_name} : nil
94 es ? es.detect {|e| e.name == search_name} : nil
86 end
95 end
87 end
96 end
88
97
89 # Returns an Entries collection
98 # Returns an Entries collection
90 # or nil if the given path doesn't exist in the repository
99 # or nil if the given path doesn't exist in the repository
91 def entries(path=nil, identifier=nil)
100 def entries(path=nil, identifier=nil)
92 return nil
101 return nil
93 end
102 end
94
103
95 def properties(path, identifier=nil)
104 def properties(path, identifier=nil)
96 return nil
105 return nil
97 end
106 end
98
107
99 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
108 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
100 return nil
109 return nil
101 end
110 end
102
111
103 def diff(path, identifier_from, identifier_to=nil)
112 def diff(path, identifier_from, identifier_to=nil)
104 return nil
113 return nil
105 end
114 end
106
115
107 def cat(path, identifier=nil)
116 def cat(path, identifier=nil)
108 return nil
117 return nil
109 end
118 end
110
119
111 def with_leading_slash(path)
120 def with_leading_slash(path)
112 path ||= ''
121 path ||= ''
113 (path[0,1]!="/") ? "/#{path}" : path
122 (path[0,1]!="/") ? "/#{path}" : path
114 end
123 end
115
124
116 def with_trailling_slash(path)
125 def with_trailling_slash(path)
117 path ||= ''
126 path ||= ''
118 (path[-1,1] == "/") ? path : "#{path}/"
127 (path[-1,1] == "/") ? path : "#{path}/"
119 end
128 end
120
129
121 def without_leading_slash(path)
130 def without_leading_slash(path)
122 path ||= ''
131 path ||= ''
123 path.gsub(%r{^/+}, '')
132 path.gsub(%r{^/+}, '')
124 end
133 end
125
134
126 def without_trailling_slash(path)
135 def without_trailling_slash(path)
127 path ||= ''
136 path ||= ''
128 (path[-1,1] == "/") ? path[0..-2] : path
137 (path[-1,1] == "/") ? path[0..-2] : path
129 end
138 end
130
139
131 def shell_quote(str)
140 def shell_quote(str)
132 if RUBY_PLATFORM =~ /mswin/
141 if RUBY_PLATFORM =~ /mswin/
133 '"' + str.gsub(/"/, '\\"') + '"'
142 '"' + str.gsub(/"/, '\\"') + '"'
134 else
143 else
135 "'" + str.gsub(/'/, "'\"'\"'") + "'"
144 "'" + str.gsub(/'/, "'\"'\"'") + "'"
136 end
145 end
137 end
146 end
138
147
139 private
148 private
140 def retrieve_root_url
149 def retrieve_root_url
141 info = self.info
150 info = self.info
142 info ? info.root_url : nil
151 info ? info.root_url : nil
143 end
152 end
144
153
145 def target(path)
154 def target(path)
146 path ||= ''
155 path ||= ''
147 base = path.match(/^\//) ? root_url : url
156 base = path.match(/^\//) ? root_url : url
148 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
157 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
149 end
158 end
150
159
151 def logger
160 def logger
152 self.class.logger
161 self.class.logger
153 end
162 end
154
163
155 def shellout(cmd, &block)
164 def shellout(cmd, &block)
156 self.class.shellout(cmd, &block)
165 self.class.shellout(cmd, &block)
157 end
166 end
158
167
159 def self.logger
168 def self.logger
160 RAILS_DEFAULT_LOGGER
169 RAILS_DEFAULT_LOGGER
161 end
170 end
162
171
163 def self.shellout(cmd, &block)
172 def self.shellout(cmd, &block)
164 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
173 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
165 begin
174 begin
166 IO.popen(cmd, "r+") do |io|
175 IO.popen(cmd, "r+") do |io|
167 io.close_write
176 io.close_write
168 block.call(io) if block_given?
177 block.call(io) if block_given?
169 end
178 end
170 rescue Errno::ENOENT => e
179 rescue Errno::ENOENT => e
171 msg = strip_credential(e.message)
180 msg = strip_credential(e.message)
172 # The command failed, log it and re-raise
181 # The command failed, log it and re-raise
173 logger.error("SCM command failed: #{strip_credential(cmd)}\n with: #{msg}")
182 logger.error("SCM command failed: #{strip_credential(cmd)}\n with: #{msg}")
174 raise CommandFailed.new(msg)
183 raise CommandFailed.new(msg)
175 end
184 end
176 end
185 end
177
186
178 # Hides username/password in a given command
187 # Hides username/password in a given command
179 def self.hide_credential(cmd)
188 def self.hide_credential(cmd)
180 q = (RUBY_PLATFORM =~ /mswin/ ? '"' : "'")
189 q = (RUBY_PLATFORM =~ /mswin/ ? '"' : "'")
181 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
190 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
182 end
191 end
183
192
184 def strip_credential(cmd)
193 def strip_credential(cmd)
185 self.class.hide_credential(cmd)
194 self.class.hide_credential(cmd)
186 end
195 end
187 end
196 end
188
197
189 class Entries < Array
198 class Entries < Array
190 def sort_by_name
199 def sort_by_name
191 sort {|x,y|
200 sort {|x,y|
192 if x.kind == y.kind
201 if x.kind == y.kind
193 x.name <=> y.name
202 x.name <=> y.name
194 else
203 else
195 x.kind <=> y.kind
204 x.kind <=> y.kind
196 end
205 end
197 }
206 }
198 end
207 end
199
208
200 def revisions
209 def revisions
201 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
210 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
202 end
211 end
203 end
212 end
204
213
205 class Info
214 class Info
206 attr_accessor :root_url, :lastrev
215 attr_accessor :root_url, :lastrev
207 def initialize(attributes={})
216 def initialize(attributes={})
208 self.root_url = attributes[:root_url] if attributes[:root_url]
217 self.root_url = attributes[:root_url] if attributes[:root_url]
209 self.lastrev = attributes[:lastrev]
218 self.lastrev = attributes[:lastrev]
210 end
219 end
211 end
220 end
212
221
213 class Entry
222 class Entry
214 attr_accessor :name, :path, :kind, :size, :lastrev
223 attr_accessor :name, :path, :kind, :size, :lastrev
215 def initialize(attributes={})
224 def initialize(attributes={})
216 self.name = attributes[:name] if attributes[:name]
225 self.name = attributes[:name] if attributes[:name]
217 self.path = attributes[:path] if attributes[:path]
226 self.path = attributes[:path] if attributes[:path]
218 self.kind = attributes[:kind] if attributes[:kind]
227 self.kind = attributes[:kind] if attributes[:kind]
219 self.size = attributes[:size].to_i if attributes[:size]
228 self.size = attributes[:size].to_i if attributes[:size]
220 self.lastrev = attributes[:lastrev]
229 self.lastrev = attributes[:lastrev]
221 end
230 end
222
231
223 def is_file?
232 def is_file?
224 'file' == self.kind
233 'file' == self.kind
225 end
234 end
226
235
227 def is_dir?
236 def is_dir?
228 'dir' == self.kind
237 'dir' == self.kind
229 end
238 end
230
239
231 def is_text?
240 def is_text?
232 Redmine::MimeType.is_type?('text', name)
241 Redmine::MimeType.is_type?('text', name)
233 end
242 end
234 end
243 end
235
244
236 class Revisions < Array
245 class Revisions < Array
237 def latest
246 def latest
238 sort {|x,y|
247 sort {|x,y|
239 unless x.time.nil? or y.time.nil?
248 unless x.time.nil? or y.time.nil?
240 x.time <=> y.time
249 x.time <=> y.time
241 else
250 else
242 0
251 0
243 end
252 end
244 }.last
253 }.last
245 end
254 end
246 end
255 end
247
256
248 class Revision
257 class Revision
249 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
258 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
250 def initialize(attributes={})
259 def initialize(attributes={})
251 self.identifier = attributes[:identifier]
260 self.identifier = attributes[:identifier]
252 self.scmid = attributes[:scmid]
261 self.scmid = attributes[:scmid]
253 self.name = attributes[:name] || self.identifier
262 self.name = attributes[:name] || self.identifier
254 self.author = attributes[:author]
263 self.author = attributes[:author]
255 self.time = attributes[:time]
264 self.time = attributes[:time]
256 self.message = attributes[:message] || ""
265 self.message = attributes[:message] || ""
257 self.paths = attributes[:paths]
266 self.paths = attributes[:paths]
258 self.revision = attributes[:revision]
267 self.revision = attributes[:revision]
259 self.branch = attributes[:branch]
268 self.branch = attributes[:branch]
260 end
269 end
261
270
262 end
271 end
263
272
264 class Annotate
273 class Annotate
265 attr_reader :lines, :revisions
274 attr_reader :lines, :revisions
266
275
267 def initialize
276 def initialize
268 @lines = []
277 @lines = []
269 @revisions = []
278 @revisions = []
270 end
279 end
271
280
272 def add_line(line, revision)
281 def add_line(line, revision)
273 @lines << line
282 @lines << line
274 @revisions << revision
283 @revisions << revision
275 end
284 end
276
285
277 def content
286 def content
278 content = lines.join("\n")
287 content = lines.join("\n")
279 end
288 end
280
289
281 def empty?
290 def empty?
282 lines.empty?
291 lines.empty?
283 end
292 end
284 end
293 end
285 end
294 end
286 end
295 end
287 end
296 end
@@ -1,205 +1,205
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class MercurialAdapter < AbstractAdapter
23 class MercurialAdapter < AbstractAdapter
24
24
25 # Mercurial executable name
25 # Mercurial executable name
26 HG_BIN = "hg"
26 HG_BIN = "hg"
27 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
27 TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
28 TEMPLATE_NAME = "hg-template"
28 TEMPLATE_NAME = "hg-template"
29 TEMPLATE_EXTENSION = "tmpl"
29 TEMPLATE_EXTENSION = "tmpl"
30
30
31 class << self
31 class << self
32 def client_version
32 def client_version
33 @@client_version ||= (hgversion || 'Unknown version')
33 @@client_version ||= (hgversion || [])
34 end
34 end
35
35
36 def hgversion
36 def hgversion
37 # The hg version is expressed either as a
37 # The hg version is expressed either as a
38 # release number (eg 0.9.5 or 1.0) or as a revision
38 # release number (eg 0.9.5 or 1.0) or as a revision
39 # id composed of 12 hexa characters.
39 # id composed of 12 hexa characters.
40 theversion = hgversion_from_command_line
40 theversion = hgversion_from_command_line
41 if theversion.match(/^\d+(\.\d+)+/)
41 if theversion.match(/^\d+(\.\d+)+/)
42 theversion.split(".").collect(&:to_i)
42 theversion.split(".").collect(&:to_i)
43 end
43 end
44 end
44 end
45
45
46 def hgversion_from_command_line
46 def hgversion_from_command_line
47 %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1]
47 %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1]
48 end
48 end
49
49
50 def template_path
50 def template_path
51 @@template_path ||= template_path_for(client_version)
51 @@template_path ||= template_path_for(client_version)
52 end
52 end
53
53
54 def template_path_for(version)
54 def template_path_for(version)
55 if version.is_a?(String) or ((version <=> [0,9,5]) > 0)
55 if ((version <=> [0,9,5]) > 0) || version.empty?
56 ver = "1.0"
56 ver = "1.0"
57 else
57 else
58 ver = "0.9.5"
58 ver = "0.9.5"
59 end
59 end
60 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
60 "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
61 end
61 end
62 end
62 end
63
63
64 def info
64 def info
65 cmd = "#{HG_BIN} -R #{target('')} root"
65 cmd = "#{HG_BIN} -R #{target('')} root"
66 root_url = nil
66 root_url = nil
67 shellout(cmd) do |io|
67 shellout(cmd) do |io|
68 root_url = io.gets
68 root_url = io.gets
69 end
69 end
70 return nil if $? && $?.exitstatus != 0
70 return nil if $? && $?.exitstatus != 0
71 info = Info.new({:root_url => root_url.chomp,
71 info = Info.new({:root_url => root_url.chomp,
72 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
72 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
73 })
73 })
74 info
74 info
75 rescue CommandFailed
75 rescue CommandFailed
76 return nil
76 return nil
77 end
77 end
78
78
79 def entries(path=nil, identifier=nil)
79 def entries(path=nil, identifier=nil)
80 path ||= ''
80 path ||= ''
81 entries = Entries.new
81 entries = Entries.new
82 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
82 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
83 cmd << " -r " + (identifier ? identifier.to_s : "tip")
83 cmd << " -r " + (identifier ? identifier.to_s : "tip")
84 cmd << " " + shell_quote("path:#{path}") unless path.empty?
84 cmd << " " + shell_quote("path:#{path}") unless path.empty?
85 shellout(cmd) do |io|
85 shellout(cmd) do |io|
86 io.each_line do |line|
86 io.each_line do |line|
87 # HG uses antislashs as separator on Windows
87 # HG uses antislashs as separator on Windows
88 line = line.gsub(/\\/, "/")
88 line = line.gsub(/\\/, "/")
89 if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
89 if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
90 e ||= line
90 e ||= line
91 e = e.chomp.split(%r{[\/\\]})
91 e = e.chomp.split(%r{[\/\\]})
92 entries << Entry.new({:name => e.first,
92 entries << Entry.new({:name => e.first,
93 :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
93 :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
94 :kind => (e.size > 1 ? 'dir' : 'file'),
94 :kind => (e.size > 1 ? 'dir' : 'file'),
95 :lastrev => Revision.new
95 :lastrev => Revision.new
96 }) unless entries.detect{|entry| entry.name == e.first}
96 }) unless entries.detect{|entry| entry.name == e.first}
97 end
97 end
98 end
98 end
99 end
99 end
100 return nil if $? && $?.exitstatus != 0
100 return nil if $? && $?.exitstatus != 0
101 entries.sort_by_name
101 entries.sort_by_name
102 end
102 end
103
103
104 # Fetch the revisions by using a template file that
104 # Fetch the revisions by using a template file that
105 # makes Mercurial produce a xml output.
105 # makes Mercurial produce a xml output.
106 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
106 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
107 revisions = Revisions.new
107 revisions = Revisions.new
108 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{self.class.template_path}"
108 cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{self.class.template_path}"
109 if identifier_from && identifier_to
109 if identifier_from && identifier_to
110 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
110 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
111 elsif identifier_from
111 elsif identifier_from
112 cmd << " -r #{identifier_from.to_i}:"
112 cmd << " -r #{identifier_from.to_i}:"
113 end
113 end
114 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
114 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
115 cmd << " #{path}" if path
115 cmd << " #{path}" if path
116 shellout(cmd) do |io|
116 shellout(cmd) do |io|
117 begin
117 begin
118 # HG doesn't close the XML Document...
118 # HG doesn't close the XML Document...
119 doc = REXML::Document.new(io.read << "</log>")
119 doc = REXML::Document.new(io.read << "</log>")
120 doc.elements.each("log/logentry") do |logentry|
120 doc.elements.each("log/logentry") do |logentry|
121 paths = []
121 paths = []
122 copies = logentry.get_elements('paths/path-copied')
122 copies = logentry.get_elements('paths/path-copied')
123 logentry.elements.each("paths/path") do |path|
123 logentry.elements.each("paths/path") do |path|
124 # Detect if the added file is a copy
124 # Detect if the added file is a copy
125 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
125 if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
126 from_path = c.attributes['copyfrom-path']
126 from_path = c.attributes['copyfrom-path']
127 from_rev = logentry.attributes['revision']
127 from_rev = logentry.attributes['revision']
128 end
128 end
129 paths << {:action => path.attributes['action'],
129 paths << {:action => path.attributes['action'],
130 :path => "/#{path.text}",
130 :path => "/#{path.text}",
131 :from_path => from_path ? "/#{from_path}" : nil,
131 :from_path => from_path ? "/#{from_path}" : nil,
132 :from_revision => from_rev ? from_rev : nil
132 :from_revision => from_rev ? from_rev : nil
133 }
133 }
134 end
134 end
135 paths.sort! { |x,y| x[:path] <=> y[:path] }
135 paths.sort! { |x,y| x[:path] <=> y[:path] }
136
136
137 revisions << Revision.new({:identifier => logentry.attributes['revision'],
137 revisions << Revision.new({:identifier => logentry.attributes['revision'],
138 :scmid => logentry.attributes['node'],
138 :scmid => logentry.attributes['node'],
139 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
139 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
140 :time => Time.parse(logentry.elements['date'].text).localtime,
140 :time => Time.parse(logentry.elements['date'].text).localtime,
141 :message => logentry.elements['msg'].text,
141 :message => logentry.elements['msg'].text,
142 :paths => paths
142 :paths => paths
143 })
143 })
144 end
144 end
145 rescue
145 rescue
146 logger.debug($!)
146 logger.debug($!)
147 end
147 end
148 end
148 end
149 return nil if $? && $?.exitstatus != 0
149 return nil if $? && $?.exitstatus != 0
150 revisions
150 revisions
151 end
151 end
152
152
153 def diff(path, identifier_from, identifier_to=nil)
153 def diff(path, identifier_from, identifier_to=nil)
154 path ||= ''
154 path ||= ''
155 if identifier_to
155 if identifier_to
156 identifier_to = identifier_to.to_i
156 identifier_to = identifier_to.to_i
157 else
157 else
158 identifier_to = identifier_from.to_i - 1
158 identifier_to = identifier_from.to_i - 1
159 end
159 end
160 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
160 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
161 cmd << " -I #{target(path)}" unless path.empty?
161 cmd << " -I #{target(path)}" unless path.empty?
162 diff = []
162 diff = []
163 shellout(cmd) do |io|
163 shellout(cmd) do |io|
164 io.each_line do |line|
164 io.each_line do |line|
165 diff << line
165 diff << line
166 end
166 end
167 end
167 end
168 return nil if $? && $?.exitstatus != 0
168 return nil if $? && $?.exitstatus != 0
169 diff
169 diff
170 end
170 end
171
171
172 def cat(path, identifier=nil)
172 def cat(path, identifier=nil)
173 cmd = "#{HG_BIN} -R #{target('')} cat"
173 cmd = "#{HG_BIN} -R #{target('')} cat"
174 cmd << " -r " + (identifier ? identifier.to_s : "tip")
174 cmd << " -r " + (identifier ? identifier.to_s : "tip")
175 cmd << " #{target(path)}"
175 cmd << " #{target(path)}"
176 cat = nil
176 cat = nil
177 shellout(cmd) do |io|
177 shellout(cmd) do |io|
178 io.binmode
178 io.binmode
179 cat = io.read
179 cat = io.read
180 end
180 end
181 return nil if $? && $?.exitstatus != 0
181 return nil if $? && $?.exitstatus != 0
182 cat
182 cat
183 end
183 end
184
184
185 def annotate(path, identifier=nil)
185 def annotate(path, identifier=nil)
186 path ||= ''
186 path ||= ''
187 cmd = "#{HG_BIN} -R #{target('')}"
187 cmd = "#{HG_BIN} -R #{target('')}"
188 cmd << " annotate -n -u"
188 cmd << " annotate -n -u"
189 cmd << " -r " + (identifier ? identifier.to_s : "tip")
189 cmd << " -r " + (identifier ? identifier.to_s : "tip")
190 cmd << " -r #{identifier.to_i}" if identifier
190 cmd << " -r #{identifier.to_i}" if identifier
191 cmd << " #{target(path)}"
191 cmd << " #{target(path)}"
192 blame = Annotate.new
192 blame = Annotate.new
193 shellout(cmd) do |io|
193 shellout(cmd) do |io|
194 io.each_line do |line|
194 io.each_line do |line|
195 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
195 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
196 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
196 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
197 end
197 end
198 end
198 end
199 return nil if $? && $?.exitstatus != 0
199 return nil if $? && $?.exitstatus != 0
200 blame
200 blame
201 end
201 end
202 end
202 end
203 end
203 end
204 end
204 end
205 end
205 end
@@ -1,228 +1,228
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class SubversionAdapter < AbstractAdapter
24 class SubversionAdapter < AbstractAdapter
25
25
26 # SVN executable name
26 # SVN executable name
27 SVN_BIN = "svn"
27 SVN_BIN = "svn"
28
28
29 class << self
29 class << self
30 def client_version
30 def client_version
31 @@client_version ||= (svn_binary_version || 'Unknown version')
31 @@client_version ||= (svn_binary_version || [])
32 end
32 end
33
33
34 def svn_binary_version
34 def svn_binary_version
35 cmd = "#{SVN_BIN} --version"
35 cmd = "#{SVN_BIN} --version"
36 version = nil
36 version = nil
37 shellout(cmd) do |io|
37 shellout(cmd) do |io|
38 # Read svn version in first returned line
38 # Read svn version in first returned line
39 if m = io.gets.match(%r{((\d+\.)+\d+)})
39 if m = io.gets.match(%r{((\d+\.)+\d+)})
40 version = m[0].scan(%r{\d+}).collect(&:to_i)
40 version = m[0].scan(%r{\d+}).collect(&:to_i)
41 end
41 end
42 end
42 end
43 return nil if $? && $?.exitstatus != 0
43 return nil if $? && $?.exitstatus != 0
44 version
44 version
45 end
45 end
46 end
46 end
47
47
48 # Get info about the svn repository
48 # Get info about the svn repository
49 def info
49 def info
50 cmd = "#{SVN_BIN} info --xml #{target('')}"
50 cmd = "#{SVN_BIN} info --xml #{target('')}"
51 cmd << credentials_string
51 cmd << credentials_string
52 info = nil
52 info = nil
53 shellout(cmd) do |io|
53 shellout(cmd) do |io|
54 begin
54 begin
55 doc = REXML::Document.new(io)
55 doc = REXML::Document.new(io)
56 #root_url = doc.elements["info/entry/repository/root"].text
56 #root_url = doc.elements["info/entry/repository/root"].text
57 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
57 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
58 :lastrev => Revision.new({
58 :lastrev => Revision.new({
59 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
59 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
60 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
60 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
61 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
61 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
62 })
62 })
63 })
63 })
64 rescue
64 rescue
65 end
65 end
66 end
66 end
67 return nil if $? && $?.exitstatus != 0
67 return nil if $? && $?.exitstatus != 0
68 info
68 info
69 rescue CommandFailed
69 rescue CommandFailed
70 return nil
70 return nil
71 end
71 end
72
72
73 # Returns an Entries collection
73 # Returns an Entries collection
74 # or nil if the given path doesn't exist in the repository
74 # or nil if the given path doesn't exist in the repository
75 def entries(path=nil, identifier=nil)
75 def entries(path=nil, identifier=nil)
76 path ||= ''
76 path ||= ''
77 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
77 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
78 entries = Entries.new
78 entries = Entries.new
79 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
79 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
80 cmd << credentials_string
80 cmd << credentials_string
81 shellout(cmd) do |io|
81 shellout(cmd) do |io|
82 output = io.read
82 output = io.read
83 begin
83 begin
84 doc = REXML::Document.new(output)
84 doc = REXML::Document.new(output)
85 doc.elements.each("lists/list/entry") do |entry|
85 doc.elements.each("lists/list/entry") do |entry|
86 # Skip directory if there is no commit date (usually that
86 # Skip directory if there is no commit date (usually that
87 # means that we don't have read access to it)
87 # means that we don't have read access to it)
88 next if entry.attributes['kind'] == 'dir' && entry.elements['commit'].elements['date'].nil?
88 next if entry.attributes['kind'] == 'dir' && entry.elements['commit'].elements['date'].nil?
89 entries << Entry.new({:name => entry.elements['name'].text,
89 entries << Entry.new({:name => entry.elements['name'].text,
90 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
90 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
91 :kind => entry.attributes['kind'],
91 :kind => entry.attributes['kind'],
92 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
92 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
93 :lastrev => Revision.new({
93 :lastrev => Revision.new({
94 :identifier => entry.elements['commit'].attributes['revision'],
94 :identifier => entry.elements['commit'].attributes['revision'],
95 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
95 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
96 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
96 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
97 })
97 })
98 })
98 })
99 end
99 end
100 rescue Exception => e
100 rescue Exception => e
101 logger.error("Error parsing svn output: #{e.message}")
101 logger.error("Error parsing svn output: #{e.message}")
102 logger.error("Output was:\n #{output}")
102 logger.error("Output was:\n #{output}")
103 end
103 end
104 end
104 end
105 return nil if $? && $?.exitstatus != 0
105 return nil if $? && $?.exitstatus != 0
106 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
106 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
107 entries.sort_by_name
107 entries.sort_by_name
108 end
108 end
109
109
110 def properties(path, identifier=nil)
110 def properties(path, identifier=nil)
111 # proplist xml output supported in svn 1.5.0 and higher
111 # proplist xml output supported in svn 1.5.0 and higher
112 return nil if (self.class.client_version <=> [1, 5, 0]) < 0
112 return nil unless self.class.client_version_above?([1, 5, 0])
113
113
114 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
114 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
115 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
115 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
116 cmd << credentials_string
116 cmd << credentials_string
117 properties = {}
117 properties = {}
118 shellout(cmd) do |io|
118 shellout(cmd) do |io|
119 output = io.read
119 output = io.read
120 begin
120 begin
121 doc = REXML::Document.new(output)
121 doc = REXML::Document.new(output)
122 doc.elements.each("properties/target/property") do |property|
122 doc.elements.each("properties/target/property") do |property|
123 properties[ property.attributes['name'] ] = property.text
123 properties[ property.attributes['name'] ] = property.text
124 end
124 end
125 rescue
125 rescue
126 end
126 end
127 end
127 end
128 return nil if $? && $?.exitstatus != 0
128 return nil if $? && $?.exitstatus != 0
129 properties
129 properties
130 end
130 end
131
131
132 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
132 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
133 path ||= ''
133 path ||= ''
134 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
134 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
135 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
135 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
136 revisions = Revisions.new
136 revisions = Revisions.new
137 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
137 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
138 cmd << credentials_string
138 cmd << credentials_string
139 cmd << " --verbose " if options[:with_paths]
139 cmd << " --verbose " if options[:with_paths]
140 cmd << ' ' + target(path)
140 cmd << ' ' + target(path)
141 shellout(cmd) do |io|
141 shellout(cmd) do |io|
142 begin
142 begin
143 doc = REXML::Document.new(io)
143 doc = REXML::Document.new(io)
144 doc.elements.each("log/logentry") do |logentry|
144 doc.elements.each("log/logentry") do |logentry|
145 paths = []
145 paths = []
146 logentry.elements.each("paths/path") do |path|
146 logentry.elements.each("paths/path") do |path|
147 paths << {:action => path.attributes['action'],
147 paths << {:action => path.attributes['action'],
148 :path => path.text,
148 :path => path.text,
149 :from_path => path.attributes['copyfrom-path'],
149 :from_path => path.attributes['copyfrom-path'],
150 :from_revision => path.attributes['copyfrom-rev']
150 :from_revision => path.attributes['copyfrom-rev']
151 }
151 }
152 end
152 end
153 paths.sort! { |x,y| x[:path] <=> y[:path] }
153 paths.sort! { |x,y| x[:path] <=> y[:path] }
154
154
155 revisions << Revision.new({:identifier => logentry.attributes['revision'],
155 revisions << Revision.new({:identifier => logentry.attributes['revision'],
156 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
156 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
157 :time => Time.parse(logentry.elements['date'].text).localtime,
157 :time => Time.parse(logentry.elements['date'].text).localtime,
158 :message => logentry.elements['msg'].text,
158 :message => logentry.elements['msg'].text,
159 :paths => paths
159 :paths => paths
160 })
160 })
161 end
161 end
162 rescue
162 rescue
163 end
163 end
164 end
164 end
165 return nil if $? && $?.exitstatus != 0
165 return nil if $? && $?.exitstatus != 0
166 revisions
166 revisions
167 end
167 end
168
168
169 def diff(path, identifier_from, identifier_to=nil, type="inline")
169 def diff(path, identifier_from, identifier_to=nil, type="inline")
170 path ||= ''
170 path ||= ''
171 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
171 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
172 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
172 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
173
173
174 cmd = "#{SVN_BIN} diff -r "
174 cmd = "#{SVN_BIN} diff -r "
175 cmd << "#{identifier_to}:"
175 cmd << "#{identifier_to}:"
176 cmd << "#{identifier_from}"
176 cmd << "#{identifier_from}"
177 cmd << " #{target(path)}@#{identifier_from}"
177 cmd << " #{target(path)}@#{identifier_from}"
178 cmd << credentials_string
178 cmd << credentials_string
179 diff = []
179 diff = []
180 shellout(cmd) do |io|
180 shellout(cmd) do |io|
181 io.each_line do |line|
181 io.each_line do |line|
182 diff << line
182 diff << line
183 end
183 end
184 end
184 end
185 return nil if $? && $?.exitstatus != 0
185 return nil if $? && $?.exitstatus != 0
186 diff
186 diff
187 end
187 end
188
188
189 def cat(path, identifier=nil)
189 def cat(path, identifier=nil)
190 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
190 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
191 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
191 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
192 cmd << credentials_string
192 cmd << credentials_string
193 cat = nil
193 cat = nil
194 shellout(cmd) do |io|
194 shellout(cmd) do |io|
195 io.binmode
195 io.binmode
196 cat = io.read
196 cat = io.read
197 end
197 end
198 return nil if $? && $?.exitstatus != 0
198 return nil if $? && $?.exitstatus != 0
199 cat
199 cat
200 end
200 end
201
201
202 def annotate(path, identifier=nil)
202 def annotate(path, identifier=nil)
203 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
203 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
204 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
204 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
205 cmd << credentials_string
205 cmd << credentials_string
206 blame = Annotate.new
206 blame = Annotate.new
207 shellout(cmd) do |io|
207 shellout(cmd) do |io|
208 io.each_line do |line|
208 io.each_line do |line|
209 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
209 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
210 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
210 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
211 end
211 end
212 end
212 end
213 return nil if $? && $?.exitstatus != 0
213 return nil if $? && $?.exitstatus != 0
214 blame
214 blame
215 end
215 end
216
216
217 private
217 private
218
218
219 def credentials_string
219 def credentials_string
220 str = ''
220 str = ''
221 str << " --username #{shell_quote(@login)}" unless @login.blank?
221 str << " --username #{shell_quote(@login)}" unless @login.blank?
222 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
222 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
223 str
223 str
224 end
224 end
225 end
225 end
226 end
226 end
227 end
227 end
228 end
228 end
@@ -1,53 +1,53
1 require File.dirname(__FILE__) + '/../test_helper'
1 require File.dirname(__FILE__) + '/../test_helper'
2 begin
2 begin
3 require 'mocha'
3 require 'mocha'
4
4
5 class MercurialAdapterTest < Test::Unit::TestCase
5 class MercurialAdapterTest < Test::Unit::TestCase
6
6
7 TEMPLATES_DIR = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATES_DIR
7 TEMPLATES_DIR = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATES_DIR
8 TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
8 TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
9 TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
9 TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
10
10
11 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
11 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
12
12
13 def test_hgversion
13 def test_hgversion
14 to_test = { "0.9.5" => [0,9,5],
14 to_test = { "0.9.5" => [0,9,5],
15 "1.0" => [1,0],
15 "1.0" => [1,0],
16 "1e4ddc9ac9f7+20080325" => nil,
16 "1e4ddc9ac9f7+20080325" => nil,
17 "1.0.1+20080525" => [1,0,1],
17 "1.0.1+20080525" => [1,0,1],
18 "1916e629a29d" => nil}
18 "1916e629a29d" => nil}
19
19
20 to_test.each do |s, v|
20 to_test.each do |s, v|
21 test_hgversion_for(s, v)
21 test_hgversion_for(s, v)
22 end
22 end
23 end
23 end
24
24
25 def test_template_path
25 def test_template_path
26 to_test = { [0,9,5] => "0.9.5",
26 to_test = { [0,9,5] => "0.9.5",
27 [1,0] => "1.0",
27 [1,0] => "1.0",
28 "Unknown version" => "1.0",
28 [] => "1.0",
29 [1,0,1] => "1.0"}
29 [1,0,1] => "1.0"}
30
30
31 to_test.each do |v, template|
31 to_test.each do |v, template|
32 test_template_path_for(v, template)
32 test_template_path_for(v, template)
33 end
33 end
34 end
34 end
35
35
36 private
36 private
37
37
38 def test_hgversion_for(hgversion, version)
38 def test_hgversion_for(hgversion, version)
39 Redmine::Scm::Adapters::MercurialAdapter.expects(:hgversion_from_command_line).returns(hgversion)
39 Redmine::Scm::Adapters::MercurialAdapter.expects(:hgversion_from_command_line).returns(hgversion)
40 adapter = Redmine::Scm::Adapters::MercurialAdapter
40 adapter = Redmine::Scm::Adapters::MercurialAdapter
41 assert_equal version, adapter.hgversion
41 assert_equal version, adapter.hgversion
42 end
42 end
43
43
44 def test_template_path_for(version, template)
44 def test_template_path_for(version, template)
45 adapter = Redmine::Scm::Adapters::MercurialAdapter
45 adapter = Redmine::Scm::Adapters::MercurialAdapter
46 assert_equal "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", adapter.template_path_for(version)
46 assert_equal "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", adapter.template_path_for(version)
47 assert File.exist?(adapter.template_path_for(version))
47 assert File.exist?(adapter.template_path_for(version))
48 end
48 end
49 end
49 end
50
50
51 rescue LoadError
51 rescue LoadError
52 def test_fake; assert(false, "Requires mocha to run those tests") end
52 def test_fake; assert(false, "Requires mocha to run those tests") end
53 end
53 end
General Comments 0
You need to be logged in to leave comments. Login now