##// END OF EJS Templates
Fixed that changesets parents associations are not deleted when deleting a repository....
Jean-Philippe Lang -
r8727:8f921216067c
parent child
Show More
@@ -1,412 +1,418
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 class ScmFetchError < Exception; end
19 19
20 20 class Repository < ActiveRecord::Base
21 21 include Redmine::Ciphering
22 22
23 23 belongs_to :project
24 24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 25 has_many :changes, :through => :changesets
26 26
27 27 serialize :extra_info
28 28
29 29 before_save :check_default
30 30
31 31 # Raw SQL to delete changesets and changes in the database
32 32 # has_many :changesets, :dependent => :destroy is too slow for big repositories
33 33 before_destroy :clear_changesets
34 34
35 35 validates_length_of :password, :maximum => 255, :allow_nil => true
36 36 validates_length_of :identifier, :maximum => 255, :allow_blank => true
37 37 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
38 38 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
39 39 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
40 40 # donwcase letters, digits, dashes but not digits only
41 41 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :allow_blank => true
42 42 # Checks if the SCM is enabled when creating a repository
43 43 validate :repo_create_validation, :on => :create
44 44
45 45 def repo_create_validation
46 46 unless Setting.enabled_scm.include?(self.class.name.demodulize)
47 47 errors.add(:type, :invalid)
48 48 end
49 49 end
50 50
51 51 def self.human_attribute_name(attribute_key_name, *args)
52 52 attr_name = attribute_key_name
53 53 if attr_name == "log_encoding"
54 54 attr_name = "commit_logs_encoding"
55 55 end
56 56 super(attr_name, *args)
57 57 end
58 58
59 59 alias :attributes_without_extra_info= :attributes=
60 60 def attributes=(new_attributes, guard_protected_attributes = true)
61 61 return if new_attributes.nil?
62 62 attributes = new_attributes.dup
63 63 attributes.stringify_keys!
64 64
65 65 p = {}
66 66 p_extra = {}
67 67 attributes.each do |k, v|
68 68 if k =~ /^extra_/
69 69 p_extra[k] = v
70 70 else
71 71 p[k] = v
72 72 end
73 73 end
74 74
75 75 send :attributes_without_extra_info=, p, guard_protected_attributes
76 76 if p_extra.keys.any?
77 77 merge_extra_info(p_extra)
78 78 end
79 79 end
80 80
81 81 # Removes leading and trailing whitespace
82 82 def url=(arg)
83 83 write_attribute(:url, arg ? arg.to_s.strip : nil)
84 84 end
85 85
86 86 # Removes leading and trailing whitespace
87 87 def root_url=(arg)
88 88 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
89 89 end
90 90
91 91 def password
92 92 read_ciphered_attribute(:password)
93 93 end
94 94
95 95 def password=(arg)
96 96 write_ciphered_attribute(:password, arg)
97 97 end
98 98
99 99 def scm_adapter
100 100 self.class.scm_adapter_class
101 101 end
102 102
103 103 def scm
104 104 unless @scm
105 105 @scm = self.scm_adapter.new(url, root_url,
106 106 login, password, path_encoding)
107 107 if root_url.blank? && @scm.root_url.present?
108 108 update_attribute(:root_url, @scm.root_url)
109 109 end
110 110 end
111 111 @scm
112 112 end
113 113
114 114 def scm_name
115 115 self.class.scm_name
116 116 end
117 117
118 118 def name
119 119 if identifier.present?
120 120 identifier
121 121 elsif is_default?
122 122 l(:field_repository_is_default)
123 123 else
124 124 scm_name
125 125 end
126 126 end
127 127
128 128 def identifier_param
129 129 if is_default?
130 130 nil
131 131 elsif identifier.present?
132 132 identifier
133 133 else
134 134 id.to_s
135 135 end
136 136 end
137 137
138 138 def <=>(repository)
139 139 if is_default?
140 140 -1
141 141 elsif repository.is_default?
142 142 1
143 143 else
144 144 identifier <=> repository.identifier
145 145 end
146 146 end
147 147
148 148 def self.find_by_identifier_param(param)
149 149 if param.to_s =~ /^\d+$/
150 150 find_by_id(param)
151 151 else
152 152 find_by_identifier(param)
153 153 end
154 154 end
155 155
156 156 def merge_extra_info(arg)
157 157 h = extra_info || {}
158 158 return h if arg.nil?
159 159 h.merge!(arg)
160 160 write_attribute(:extra_info, h)
161 161 end
162 162
163 163 def report_last_commit
164 164 true
165 165 end
166 166
167 167 def supports_cat?
168 168 scm.supports_cat?
169 169 end
170 170
171 171 def supports_annotate?
172 172 scm.supports_annotate?
173 173 end
174 174
175 175 def supports_all_revisions?
176 176 true
177 177 end
178 178
179 179 def supports_directory_revisions?
180 180 false
181 181 end
182 182
183 183 def supports_revision_graph?
184 184 false
185 185 end
186 186
187 187 def entry(path=nil, identifier=nil)
188 188 scm.entry(path, identifier)
189 189 end
190 190
191 191 def entries(path=nil, identifier=nil)
192 192 scm.entries(path, identifier)
193 193 end
194 194
195 195 def branches
196 196 scm.branches
197 197 end
198 198
199 199 def tags
200 200 scm.tags
201 201 end
202 202
203 203 def default_branch
204 204 nil
205 205 end
206 206
207 207 def properties(path, identifier=nil)
208 208 scm.properties(path, identifier)
209 209 end
210 210
211 211 def cat(path, identifier=nil)
212 212 scm.cat(path, identifier)
213 213 end
214 214
215 215 def diff(path, rev, rev_to)
216 216 scm.diff(path, rev, rev_to)
217 217 end
218 218
219 219 def diff_format_revisions(cs, cs_to, sep=':')
220 220 text = ""
221 221 text << cs_to.format_identifier + sep if cs_to
222 222 text << cs.format_identifier if cs
223 223 text
224 224 end
225 225
226 226 # Returns a path relative to the url of the repository
227 227 def relative_path(path)
228 228 path
229 229 end
230 230
231 231 # Finds and returns a revision with a number or the beginning of a hash
232 232 def find_changeset_by_name(name)
233 233 return nil if name.blank?
234 234 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
235 235 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
236 236 end
237 237
238 238 def latest_changeset
239 239 @latest_changeset ||= changesets.find(:first)
240 240 end
241 241
242 242 # Returns the latest changesets for +path+
243 243 # Default behaviour is to search in cached changesets
244 244 def latest_changesets(path, rev, limit=10)
245 245 if path.blank?
246 246 changesets.find(
247 247 :all,
248 248 :include => :user,
249 249 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
250 250 :limit => limit)
251 251 else
252 252 changes.find(
253 253 :all,
254 254 :include => {:changeset => :user},
255 255 :conditions => ["path = ?", path.with_leading_slash],
256 256 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
257 257 :limit => limit
258 258 ).collect(&:changeset)
259 259 end
260 260 end
261 261
262 262 def scan_changesets_for_issue_ids
263 263 self.changesets.each(&:scan_comment_for_issue_ids)
264 264 end
265 265
266 266 # Returns an array of committers usernames and associated user_id
267 267 def committers
268 268 @committers ||= Changeset.connection.select_rows(
269 269 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
270 270 end
271 271
272 272 # Maps committers username to a user ids
273 273 def committer_ids=(h)
274 274 if h.is_a?(Hash)
275 275 committers.each do |committer, user_id|
276 276 new_user_id = h[committer]
277 277 if new_user_id && (new_user_id.to_i != user_id.to_i)
278 278 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
279 279 Changeset.update_all(
280 280 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
281 281 ["repository_id = ? AND committer = ?", id, committer])
282 282 end
283 283 end
284 284 @committers = nil
285 285 @found_committer_users = nil
286 286 true
287 287 else
288 288 false
289 289 end
290 290 end
291 291
292 292 # Returns the Redmine User corresponding to the given +committer+
293 293 # It will return nil if the committer is not yet mapped and if no User
294 294 # with the same username or email was found
295 295 def find_committer_user(committer)
296 296 unless committer.blank?
297 297 @found_committer_users ||= {}
298 298 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
299 299
300 300 user = nil
301 301 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
302 302 if c && c.user
303 303 user = c.user
304 304 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
305 305 username, email = $1.strip, $3
306 306 u = User.find_by_login(username)
307 307 u ||= User.find_by_mail(email) unless email.blank?
308 308 user = u
309 309 end
310 310 @found_committer_users[committer] = user
311 311 user
312 312 end
313 313 end
314 314
315 315 def repo_log_encoding
316 316 encoding = log_encoding.to_s.strip
317 317 encoding.blank? ? 'UTF-8' : encoding
318 318 end
319 319
320 320 # Fetches new changesets for all repositories of active projects
321 321 # Can be called periodically by an external script
322 322 # eg. ruby script/runner "Repository.fetch_changesets"
323 323 def self.fetch_changesets
324 324 Project.active.has_module(:repository).all.each do |project|
325 325 project.repositories.each do |repository|
326 326 begin
327 327 repository.fetch_changesets
328 328 rescue Redmine::Scm::Adapters::CommandFailed => e
329 329 logger.error "scm: error during fetching changesets: #{e.message}"
330 330 end
331 331 end
332 332 end
333 333 end
334 334
335 335 # scan changeset comments to find related and fixed issues for all repositories
336 336 def self.scan_changesets_for_issue_ids
337 337 find(:all).each(&:scan_changesets_for_issue_ids)
338 338 end
339 339
340 340 def self.scm_name
341 341 'Abstract'
342 342 end
343 343
344 344 def self.available_scm
345 345 subclasses.collect {|klass| [klass.scm_name, klass.name]}
346 346 end
347 347
348 348 def self.factory(klass_name, *args)
349 349 klass = "Repository::#{klass_name}".constantize
350 350 klass.new(*args)
351 351 rescue
352 352 nil
353 353 end
354 354
355 355 def self.scm_adapter_class
356 356 nil
357 357 end
358 358
359 359 def self.scm_command
360 360 ret = ""
361 361 begin
362 362 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
363 363 rescue Exception => e
364 364 logger.error "scm: error during get command: #{e.message}"
365 365 end
366 366 ret
367 367 end
368 368
369 369 def self.scm_version_string
370 370 ret = ""
371 371 begin
372 372 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
373 373 rescue Exception => e
374 374 logger.error "scm: error during get version string: #{e.message}"
375 375 end
376 376 ret
377 377 end
378 378
379 379 def self.scm_available
380 380 ret = false
381 381 begin
382 382 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
383 383 rescue Exception => e
384 384 logger.error "scm: error during get scm available: #{e.message}"
385 385 end
386 386 ret
387 387 end
388 388
389 389 def set_as_default?
390 390 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
391 391 end
392 392
393 393 protected
394 394
395 395 def check_default
396 396 if !is_default? && set_as_default?
397 397 self.is_default = true
398 398 end
399 399 if is_default? && is_default_changed?
400 400 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
401 401 end
402 402 end
403 403
404 404 private
405 405
406 # Deletes repository data
406 407 def clear_changesets
407 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
408 cs = Changeset.table_name
409 ch = Change.table_name
410 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
411 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
412
408 413 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
409 414 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
415 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
410 416 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
411 417 end
412 418 end
@@ -1,269 +1,278
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoryTest < ActiveSupport::TestCase
21 21 fixtures :projects,
22 22 :trackers,
23 23 :projects_trackers,
24 24 :enabled_modules,
25 25 :repositories,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_categories,
29 29 :changesets,
30 30 :changes,
31 31 :users,
32 32 :members,
33 33 :member_roles,
34 34 :roles,
35 35 :enumerations
36 36
37 37 def setup
38 38 @repository = Project.find(1).repository
39 39 end
40 40
41 41 def test_create
42 42 repository = Repository::Subversion.new(:project => Project.find(3))
43 43 assert !repository.save
44 44
45 45 repository.url = "svn://localhost"
46 46 assert repository.save
47 47 repository.reload
48 48
49 49 project = Project.find(3)
50 50 assert_equal repository, project.repository
51 51 end
52 52
53 53 def test_first_repository_should_be_set_as_default
54 54 repository1 = Repository::Subversion.new(:project => Project.find(3), :identifier => 'svn1', :url => 'file:///svn1')
55 55 assert repository1.save
56 56 assert repository1.is_default?
57 57
58 58 repository2 = Repository::Subversion.new(:project => Project.find(3), :identifier => 'svn2', :url => 'file:///svn2')
59 59 assert repository2.save
60 60 assert !repository2.is_default?
61 61
62 62 assert_equal repository1, Project.find(3).repository
63 63 assert_equal [repository1, repository2], Project.find(3).repositories.sort
64 64 end
65 65
66 66 def test_destroy
67 67 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
68 68 changes = Change.count(:all, :conditions => "repository_id = 10",
69 69 :include => :changeset)
70 70 assert_difference 'Changeset.count', -changesets do
71 71 assert_difference 'Change.count', -changes do
72 72 Repository.find(10).destroy
73 73 end
74 74 end
75 75 end
76 76
77 def test_destroy_should_delete_parents_associations
78 changeset = Changeset.find(102)
79 changeset.parents = Changeset.find_all_by_id([100, 101])
80
81 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
82 Repository.find(10).destroy
83 end
84 end
85
77 86 def test_should_not_create_with_disabled_scm
78 87 # disable Subversion
79 88 with_settings :enabled_scm => ['Darcs', 'Git'] do
80 89 repository = Repository::Subversion.new(
81 90 :project => Project.find(3), :url => "svn://localhost")
82 91 assert !repository.save
83 92 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
84 93 repository.errors[:type].to_s
85 94 end
86 95 end
87 96
88 97 def test_scan_changesets_for_issue_ids
89 98 Setting.default_language = 'en'
90 99 Setting.notified_events = ['issue_added','issue_updated']
91 100
92 101 # choosing a status to apply to fix issues
93 102 Setting.commit_fix_status_id = IssueStatus.find(
94 103 :first,
95 104 :conditions => ["is_closed = ?", true]).id
96 105 Setting.commit_fix_done_ratio = "90"
97 106 Setting.commit_ref_keywords = 'refs , references, IssueID'
98 107 Setting.commit_fix_keywords = 'fixes , closes'
99 108 Setting.default_language = 'en'
100 109 ActionMailer::Base.deliveries.clear
101 110
102 111 # make sure issue 1 is not already closed
103 112 fixed_issue = Issue.find(1)
104 113 assert !fixed_issue.status.is_closed?
105 114 old_status = fixed_issue.status
106 115
107 116 Repository.scan_changesets_for_issue_ids
108 117 assert_equal [101, 102], Issue.find(3).changeset_ids
109 118
110 119 # fixed issues
111 120 fixed_issue.reload
112 121 assert fixed_issue.status.is_closed?
113 122 assert_equal 90, fixed_issue.done_ratio
114 123 assert_equal [101], fixed_issue.changeset_ids
115 124
116 125 # issue change
117 126 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
118 127 assert_equal User.find_by_login('dlopper'), journal.user
119 128 assert_equal 'Applied in changeset r2.', journal.notes
120 129
121 130 # 2 email notifications
122 131 assert_equal 2, ActionMailer::Base.deliveries.size
123 132 mail = ActionMailer::Base.deliveries.first
124 133 assert_kind_of TMail::Mail, mail
125 134 assert mail.subject.starts_with?(
126 135 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
127 136 assert mail.body.include?(
128 137 "Status changed from #{old_status} to #{fixed_issue.status}")
129 138
130 139 # ignoring commits referencing an issue of another project
131 140 assert_equal [], Issue.find(4).changesets
132 141 end
133 142
134 143 def test_for_changeset_comments_strip
135 144 repository = Repository::Mercurial.create(
136 145 :project => Project.find( 4 ),
137 146 :url => '/foo/bar/baz' )
138 147 comment = <<-COMMENT
139 148 This is a loooooooooooooooooooooooooooong comment
140 149
141 150
142 151 COMMENT
143 152 changeset = Changeset.new(
144 153 :comments => comment, :commit_date => Time.now,
145 154 :revision => 0, :scmid => 'f39b7922fb3c',
146 155 :committer => 'foo <foo@example.com>',
147 156 :committed_on => Time.now, :repository => repository )
148 157 assert( changeset.save )
149 158 assert_not_equal( comment, changeset.comments )
150 159 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
151 160 changeset.comments )
152 161 end
153 162
154 163 def test_for_urls_strip_cvs
155 164 repository = Repository::Cvs.create(
156 165 :project => Project.find(4),
157 166 :url => ' :pserver:login:password@host:/path/to/the/repository',
158 167 :root_url => 'foo ',
159 168 :log_encoding => 'UTF-8')
160 169 assert repository.save
161 170 repository.reload
162 171 assert_equal ':pserver:login:password@host:/path/to/the/repository',
163 172 repository.url
164 173 assert_equal 'foo', repository.root_url
165 174 end
166 175
167 176 def test_for_urls_strip_subversion
168 177 repository = Repository::Subversion.create(
169 178 :project => Project.find(4),
170 179 :url => ' file:///dummy ')
171 180 assert repository.save
172 181 repository.reload
173 182 assert_equal 'file:///dummy', repository.url
174 183 end
175 184
176 185 def test_for_urls_strip_git
177 186 repository = Repository::Git.create(
178 187 :project => Project.find(4),
179 188 :url => ' c:\dummy ')
180 189 assert repository.save
181 190 repository.reload
182 191 assert_equal 'c:\dummy', repository.url
183 192 end
184 193
185 194 def test_manual_user_mapping
186 195 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
187 196 c = Changeset.create!(
188 197 :repository => @repository,
189 198 :committer => 'foo',
190 199 :committed_on => Time.now,
191 200 :revision => 100,
192 201 :comments => 'Committed by foo.'
193 202 )
194 203 assert_nil c.user
195 204 @repository.committer_ids = {'foo' => '2'}
196 205 assert_equal User.find(2), c.reload.user
197 206 # committer is now mapped
198 207 c = Changeset.create!(
199 208 :repository => @repository,
200 209 :committer => 'foo',
201 210 :committed_on => Time.now,
202 211 :revision => 101,
203 212 :comments => 'Another commit by foo.'
204 213 )
205 214 assert_equal User.find(2), c.user
206 215 end
207 216 end
208 217
209 218 def test_auto_user_mapping_by_username
210 219 c = Changeset.create!(
211 220 :repository => @repository,
212 221 :committer => 'jsmith',
213 222 :committed_on => Time.now,
214 223 :revision => 100,
215 224 :comments => 'Committed by john.'
216 225 )
217 226 assert_equal User.find(2), c.user
218 227 end
219 228
220 229 def test_auto_user_mapping_by_email
221 230 c = Changeset.create!(
222 231 :repository => @repository,
223 232 :committer => 'john <jsmith@somenet.foo>',
224 233 :committed_on => Time.now,
225 234 :revision => 100,
226 235 :comments => 'Committed by john.'
227 236 )
228 237 assert_equal User.find(2), c.user
229 238 end
230 239
231 240 def test_filesystem_avaialbe
232 241 klass = Repository::Filesystem
233 242 assert klass.scm_adapter_class
234 243 assert_equal true, klass.scm_available
235 244 end
236 245
237 246 def test_merge_extra_info
238 247 repo = Repository::Subversion.new(:project => Project.find(3))
239 248 assert !repo.save
240 249 repo.url = "svn://localhost"
241 250 assert repo.save
242 251 repo.reload
243 252 project = Project.find(3)
244 253 assert_equal repo, project.repository
245 254 assert_nil repo.extra_info
246 255 h1 = {"test_1" => {"test_11" => "test_value_11"}}
247 256 repo.merge_extra_info(h1)
248 257 assert_equal h1, repo.extra_info
249 258 h2 = {"test_2" => {
250 259 "test_21" => "test_value_21",
251 260 "test_22" => "test_value_22",
252 261 }}
253 262 repo.merge_extra_info(h2)
254 263 assert_equal (h = {"test_11" => "test_value_11"}),
255 264 repo.extra_info["test_1"]
256 265 assert_equal "test_value_21",
257 266 repo.extra_info["test_2"]["test_21"]
258 267 h3 = {"test_2" => {
259 268 "test_23" => "test_value_23",
260 269 "test_24" => "test_value_24",
261 270 }}
262 271 repo.merge_extra_info(h3)
263 272 assert_equal (h = {"test_11" => "test_value_11"}),
264 273 repo.extra_info["test_1"]
265 274 assert_nil repo.extra_info["test_2"]["test_21"]
266 275 assert_equal "test_value_23",
267 276 repo.extra_info["test_2"]["test_23"]
268 277 end
269 278 end
General Comments 0
You need to be logged in to leave comments. Login now