3 require "my-assertions"
11 class SvnReposTest < Test::Unit::TestCase
23 assert_equal(Svn::Core.subr_version, Svn::Repos.version)
27 assert_equal(@repos_path, @repos.path)
29 assert_equal(File.join(@repos_path, "db"), @repos.db_env)
31 assert_equal(File.join(@repos_path, "conf"), @repos.conf_dir)
32 assert_equal(File.join(@repos_path, "conf", "svnserve.conf"),
35 locks_dir = File.join(@repos_path, "locks")
36 assert_equal(locks_dir, @repos.lock_dir)
37 assert_equal(File.join(locks_dir, "db.lock"),
39 assert_equal(File.join(locks_dir, "db-logs.lock"),
40 @repos.db_logs_lockfile)
42 hooks_dir = File.join(@repos_path, "hooks")
43 assert_equal(hooks_dir, @repos.hook_dir)
45 assert_equal(File.join(hooks_dir, "start-commit"),
46 @repos.start_commit_hook)
47 assert_equal(File.join(hooks_dir, "pre-commit"),
48 @repos.pre_commit_hook)
49 assert_equal(File.join(hooks_dir, "post-commit"),
50 @repos.post_commit_hook)
52 assert_equal(File.join(hooks_dir, "pre-revprop-change"),
53 @repos.pre_revprop_change_hook)
54 assert_equal(File.join(hooks_dir, "post-revprop-change"),
55 @repos.post_revprop_change_hook)
57 assert_equal(File.join(hooks_dir, "pre-lock"),
59 assert_equal(File.join(hooks_dir, "post-lock"),
60 @repos.post_lock_hook)
62 assert_equal(File.join(hooks_dir, "pre-unlock"),
63 @repos.pre_unlock_hook)
64 assert_equal(File.join(hooks_dir, "post-unlock"),
65 @repos.post_unlock_hook)
68 search_path = @repos_path
69 assert_equal(@repos_path, Svn::Repos.find_root_path(search_path))
70 search_path = "#{@repos_path}/XXX"
71 assert_equal(@repos_path, Svn::Repos.find_root_path(search_path))
73 search_path = "not-found"
74 assert_equal(nil, Svn::Repos.find_root_path(search_path))
78 tmp_repos_path = File.join(@tmp_path, "repos")
79 fs_type = Svn::Fs::TYPE_FSFS
80 fs_config = {Svn::Fs::CONFIG_FS_TYPE => fs_type}
82 Svn::Repos.create(tmp_repos_path, {}, fs_config) do |repos|
83 assert(File.exist?(tmp_repos_path))
84 fs_type_path = File.join(repos.fs.path, Svn::Fs::CONFIG_FS_TYPE)
85 assert_equal(fs_type, File.open(fs_type_path) {|f| f.read.chop})
86 repos.fs.set_warning_func(&warning_func)
90 assert_raises(Svn::Error::ReposAlreadyClose) do
94 Svn::Repos.delete(tmp_repos_path)
95 assert(!File.exist?(tmp_repos_path))
104 props = {"myprop" => "value"}
105 path = File.join(@wc_path, file)
107 ctx = make_context(log1)
108 File.open(path, "w") {|f| f.print(src)}
110 info1 = ctx.ci(@wc_path)
111 start_rev = info1.revision
113 ctx = make_context(log2)
114 File.open(path, "a") {|f| f.print(src)}
115 info2 = ctx.ci(@wc_path)
117 ctx = make_context(log3)
118 File.open(path, "a") {|f| f.print(src)}
119 props.each do |key, value|
120 ctx.prop_set(key, value, path)
122 info3 = ctx.ci(@wc_path)
123 end_rev = info3.revision
125 logs = @repos.logs(file, start_rev, end_rev, end_rev - start_rev + 1)
126 logs = logs.collect do |changed_paths, revision, author, date, message|
128 changed_paths.each do |key, changed_path|
129 paths[key] = changed_path.action
131 [paths, revision, author, date, message]
158 args = [file, start_rev, end_rev]
159 @repos.file_revs(*args) do |path, rev, rev_props, prop_diffs|
160 hashed_prop_diffs = {}
161 prop_diffs.each do |prop|
162 hashed_prop_diffs[prop.name] = prop.value
164 revs << [path, rev, hashed_prop_diffs]
167 ["/#{file}", info1.revision, {}],
168 ["/#{file}", info2.revision, {}],
169 ["/#{file}", info3.revision, props],
174 @repos.file_revs2(*args) do |path, rev, rev_props, prop_diffs|
175 revs << [path, rev, prop_diffs]
178 ["/#{file}", info1.revision, {}],
179 ["/#{file}", info2.revision, {}],
180 ["/#{file}", info3.revision, props],
185 rev, date, author = @repos.fs.root.committed_info("/")
186 assert_equal(info3.revision, rev)
187 assert_equal(info3.date, date)
188 assert_equal(info3.author, author)
194 path = File.join(@wc_path, file)
195 FileUtils.touch(path)
197 ctx = make_context(log)
199 commit_info = ctx.commit(@wc_path)
200 rev = commit_info.revision
202 assert_equal(log, ctx.log_message(path, rev))
204 dest_path = File.join(@tmp_path, "dest")
205 backup_path = File.join(@tmp_path, "back")
209 repos = Svn::Repos.create(dest_path, config, fs_config)
210 repos.fs.set_warning_func(&warning_func)
212 FileUtils.mv(@repos.path, backup_path)
213 FileUtils.mv(repos.path, @repos.path)
215 assert_raises(Svn::Error::FsNoSuchRevision) do
216 assert_equal(log, ctx.log_message(path, rev))
219 FileUtils.rm_r(@repos.path)
220 Svn::Repos.hotcopy(backup_path, @repos.path)
221 assert_equal(log, ctx.log_message(path, rev))
224 def assert_transaction
226 ctx = make_context(log)
227 ctx.checkout(@repos_uri, @wc_path)
228 ctx.mkdir(["#{@wc_path}/new_dir"])
230 prev_rev = @repos.youngest_rev
235 :revision => prev_rev,
237 callback = Proc.new do |txn|
240 yield(:commit, @repos, args, callback)
241 assert_equal(prev_rev, @repos.youngest_rev)
242 assert_equal(prev_rev, @repos.dated_revision(past_date))
244 prev_rev = @repos.youngest_rev
245 @repos.transaction_for_commit(@author, log) do |txn|
247 assert_equal(prev_rev + 1, @repos.youngest_rev)
248 assert_equal(prev_rev, @repos.dated_revision(past_date))
249 assert_equal(prev_rev + 1, @repos.dated_revision(Time.now))
251 prev_rev = @repos.youngest_rev
254 :revision => prev_rev,
256 callback = Proc.new do |txn|
258 yield(:update, @repos, args, callback)
259 assert_equal(prev_rev, @repos.youngest_rev)
263 assert_transaction do |type, repos, args, callback|
266 repos.transaction_for_commit(args[:author], args[:log], &callback)
268 repos.transaction_for_update(args[:author], &callback)
273 def test_transaction_with_revision
274 assert_transaction do |type, repos, args, callback|
277 repos.transaction_for_commit(args[:author], args[:log],
278 args[:revision], &callback)
280 repos.transaction_for_update(args[:author], args[:revision], &callback)
285 def test_transaction2
286 assert_transaction do |type, repos, args, callback|
290 Svn::Core::PROP_REVISION_AUTHOR => args[:author],
291 Svn::Core::PROP_REVISION_LOG => args[:log],
293 repos.transaction_for_commit(props, &callback)
295 repos.transaction_for_update(args[:author], &callback)
300 def test_transaction2_with_revision
301 assert_transaction do |type, repos, args, callback|
305 Svn::Core::PROP_REVISION_AUTHOR => args[:author],
306 Svn::Core::PROP_REVISION_LOG => args[:log],
308 repos.transaction_for_commit(props,
312 repos.transaction_for_update(args[:author],
319 def test_trace_node_locations
323 path1 = File.join(@wc_path, file1)
324 path2 = File.join(@wc_path, file2)
325 path3 = File.join(@wc_path, file3)
327 ctx = make_context(log)
329 FileUtils.touch(path1)
331 rev1 = ctx.ci(@wc_path).revision
334 rev2 = ctx.ci(@wc_path).revision
337 rev3 = ctx.ci(@wc_path).revision
344 @repos.fs.trace_node_locations("/#{file2}",
352 path = File.join(@wc_path, file)
353 path2 = File.join(@wc_path, file2)
354 source = "sample source"
356 ctx = make_context(log)
358 File.open(path, "w") {|f| f.print(source)}
360 rev = ctx.ci(@wc_path).revision
362 assert_equal(Svn::Core::NODE_FILE, @repos.fs.root.stat(file).kind)
364 editor = TestEditor.new
367 :user_name => @author,
372 :text_deltas => true,
374 :ignore_ancestry => false,
376 callback = Proc.new do |baton|
377 baton.link_path(file, file2, rev)
378 baton.delete_path(file)
380 yield(@repos, args, callback)
382 :set_target_revision,
387 editor.sequence.collect{|meth, *args| meth})
391 assert_report do |repos, args, callback|
392 @repos.report(args[:revision], args[:user_name], args[:fs_base],
393 args[:target], args[:target_path], args[:editor],
394 args[:text_deltas], args[:recurse], args[:ignore_ancestry],
400 assert_report do |repos, args, callback|
402 depth = Svn::Core::DEPTH_INFINITY
404 depth = Svn::Core::DEPTH_FILES
406 @repos.report2(args[:revision], args[:fs_base], args[:target],
407 args[:target_path], args[:editor], args[:text_deltas],
408 args[:ignore_ancestry], depth, &callback)
412 def assert_commit_editor
417 source = "sample source"
420 trunk_dir_path = File.join(@wc_path, trunk)
421 tags_dir_path = File.join(@wc_path, tags)
422 tags_sub_dir_path = File.join(tags_dir_path, tags_sub)
423 trunk_path = File.join(trunk_dir_path, file)
424 tags_path = File.join(tags_dir_path, file)
425 tags_sub_path = File.join(tags_sub_dir_path, file)
426 trunk_repos_uri = "#{@repos_uri}/#{trunk}"
427 rev1 = @repos.youngest_rev
429 commit_callback_result = {}
431 :repos_url => @repos_uri,
434 :log_message => log_message,
437 editor = yield(@repos, commit_callback_result, args)
438 root_baton = editor.open_root(rev1)
439 dir_baton = editor.add_directory(trunk, root_baton, nil, rev1)
440 file_baton = editor.add_file("#{trunk}/#{file}", dir_baton, nil, -1)
441 ret = editor.apply_textdelta(file_baton, nil)
445 assert_equal(rev1 + 1, @repos.youngest_rev)
447 :revision => @repos.youngest_rev,
448 :date => @repos.prop(Svn::Core::PROP_REVISION_DATE),
451 commit_callback_result)
452 rev2 = @repos.youngest_rev
454 ctx = make_context("")
456 assert_equal(source, File.open(trunk_path) {|f| f.read})
458 commit_callback_result = {}
459 editor = yield(@repos, commit_callback_result, args)
460 root_baton = editor.open_root(rev2)
461 dir_baton = editor.add_directory(tags, root_baton, nil, rev2)
462 subdir_baton = editor.add_directory("#{tags}/#{tags_sub}",
468 assert_equal(rev2 + 1, @repos.youngest_rev)
470 :revision => @repos.youngest_rev,
471 :date => @repos.prop(Svn::Core::PROP_REVISION_DATE),
474 commit_callback_result)
475 rev3 = @repos.youngest_rev
479 ["/#{tags}/#{tags_sub}/#{file}", rev3],
480 ["/#{trunk}/#{file}", rev2],
482 @repos.fs.history("#{tags}/#{tags_sub}/#{file}",
485 commit_callback_result = {}
486 editor = yield(@repos, commit_callback_result, args)
487 root_baton = editor.open_root(rev3)
488 dir_baton = editor.delete_entry(tags, rev3, root_baton)
492 :revision => @repos.youngest_rev,
493 :date => @repos.prop(Svn::Core::PROP_REVISION_DATE),
496 commit_callback_result)
499 assert(!File.exist?(tags_path))
502 def test_commit_editor
503 assert_commit_editor do |receiver, commit_callback_result, args|
504 commit_callback = Proc.new do |revision, date, author|
505 commit_callback_result[:revision] = revision
506 commit_callback_result[:date] = date
507 commit_callback_result[:author] = author
509 receiver.commit_editor(args[:repos_url], args[:base_path], args[:txn],
510 args[:user], args[:log_message], commit_callback)
514 def test_commit_editor2
515 assert_commit_editor do |receiver, commit_callback_result, args|
516 commit_callback = Proc.new do |info|
517 commit_callback_result[:revision] = info.revision
518 commit_callback_result[:date] = info.date
519 commit_callback_result[:author] = info.author
521 receiver.commit_editor2(args[:repos_url], args[:base_path], args[:txn],
522 args[:user], args[:log_message], commit_callback)
526 def test_commit_editor3
527 assert_commit_editor do |receiver, commit_callback_result, args|
529 Svn::Core::PROP_REVISION_AUTHOR => args[:user],
530 Svn::Core::PROP_REVISION_LOG => args[:log_message],
532 commit_callback = Proc.new do |info|
533 commit_callback_result[:revision] = info.revision
534 commit_callback_result[:date] = info.date
535 commit_callback_result[:author] = info.author
537 receiver.commit_editor3(args[:repos_url], args[:base_path], args[:txn],
538 props, commit_callback)
544 path = File.join(@wc_path, file)
545 source = "sample source"
547 ctx = make_context(log)
549 File.open(path, "w") {|f| f.print(source)}
554 Svn::Core::PROP_REVISION_AUTHOR,
555 Svn::Core::PROP_REVISION_LOG,
556 Svn::Core::PROP_REVISION_DATE,
558 @repos.proplist.keys.sort)
559 assert_equal(log, @repos.prop(Svn::Core::PROP_REVISION_LOG))
560 @repos.set_prop(@author, Svn::Core::PROP_REVISION_LOG, nil)
561 assert_nil(@repos.prop(Svn::Core::PROP_REVISION_LOG))
563 Svn::Core::PROP_REVISION_AUTHOR,
564 Svn::Core::PROP_REVISION_DATE,
566 @repos.proplist.keys.sort)
568 assert_raises(Svn::Error::ReposHookFailure) do
569 @repos.set_prop(@author, Svn::Core::PROP_REVISION_DATE, nil)
571 assert_not_nil(@repos.prop(Svn::Core::PROP_REVISION_DATE))
573 assert_nothing_raised do
574 @repos.set_prop(@author, Svn::Core::PROP_REVISION_DATE, nil, nil, nil,
577 assert_nil(@repos.prop(Svn::Core::PROP_REVISION_DATE))
579 Svn::Core::PROP_REVISION_AUTHOR,
581 @repos.proplist.keys.sort)
586 path = File.join(@wc_path, file)
587 source = "sample source"
589 ctx = make_context(log)
591 File.open(path, "w") {|f| f.print(source)}
593 rev1 = ctx.ci(@wc_path).revision
595 File.open(path, "a") {|f| f.print(source)}
596 rev2 = ctx.ci(@wc_path).revision
598 assert_nothing_raised do
599 @repos.dump_fs(nil, nil, rev1, rev2)
602 dump = StringIO.new("")
603 feedback = StringIO.new("")
604 @repos.dump_fs(dump, feedback, rev1, rev2)
606 dump_unless_feedback = StringIO.new("")
607 @repos.dump_fs(dump_unless_feedback, nil, rev1, rev2)
610 dump_unless_feedback.rewind
611 assert_equal(dump.read, dump_unless_feedback.read)
616 path = File.join(@wc_path, file)
617 source = "sample source"
619 ctx = make_context(log)
621 File.open(path, "w") {|f| f.print(source)}
623 rev1 = ctx.ci(@wc_path).revision
625 File.open(path, "a") {|f| f.print(source)}
626 rev2 = ctx.ci(@wc_path).revision
628 dump = StringIO.new("")
629 @repos.dump_fs(dump, nil, rev1, rev2)
631 dest_path = File.join(@tmp_path, "dest")
632 repos = Svn::Repos.create(dest_path)
633 assert_raises(NoMethodError) do
638 [StringIO.new(""), Svn::Repos::LOAD_UUID_DEFAULT, "/"],
641 ].each_with_index do |args, i|
642 dest_path = File.join(@tmp_path, "dest#{i}")
643 repos = Svn::Repos.create(dest_path)
644 assert_not_equal(@repos.fs.root.committed_info("/"),
645 repos.fs.root.committed_info("/"))
647 repos.load_fs(dump, *args)
648 assert_equal(@repos.fs.root.committed_info("/"),
649 repos.fs.root.committed_info("/"))
658 dir1_path = File.join(@wc_path, dir1)
659 dir2_path = File.join(dir1_path, dir2)
660 dir3_path = File.join(dir2_path, dir3)
661 path = File.join(dir3_path, file)
662 source = "sample source"
665 ctx = make_context(log)
666 FileUtils.mkdir_p(dir3_path)
667 FileUtils.touch(path)
669 rev1 = ctx.ci(@wc_path).revision
672 rev2 = ctx.ci(@wc_path).revision
674 rev1_root = @repos.fs.root(rev1)
675 rev2_root = @repos.fs.root(rev2)
676 editor = @repos.node_editor(rev1_root, rev2_root)
677 rev2_root.replay(editor)
679 tree = editor.baton.node
681 assert_equal("", tree.name)
682 assert_equal(dir1, tree.child.name)
683 assert_equal(dir2, tree.child.child.name)
689 path = File.join(@wc_path, file)
690 path_in_repos = "/#{file}"
691 ctx = make_context(log)
693 FileUtils.touch(path)
695 rev = ctx.ci(@wc_path).revision
697 access = Svn::Fs::Access.new(@author)
698 @repos.fs.access = access
699 lock = @repos.lock(file)
700 locks = @repos.get_locks(file)
701 assert_equal([path_in_repos], locks.keys)
702 assert_equal(lock.token, locks[path_in_repos].token)
703 @repos.unlock(file, lock.token)
704 assert_equal({}, @repos.get_locks(file))
709 conf_path = File.join(@tmp_path, "authz_file")
710 File.open(conf_path, "w") do |f|
717 authz = Svn::Repos::Authz.read(conf_path)
718 assert(authz.can_access?(name, "/", @author, Svn::Repos::AUTHZ_READ))
719 assert(!authz.can_access?(name, "/", @author, Svn::Repos::AUTHZ_WRITE))
720 assert(!authz.can_access?(name, "/", "FOO", Svn::Repos::AUTHZ_READ))
725 Svn::Repos.recover(@repos_path, false) do
735 trunk = File.join(@wc_path, "trunk")
736 branch = File.join(@wc_path, "branch")
737 trunk_path = File.join(trunk, file)
738 branch_path = File.join(branch, file)
739 trunk_path_in_repos = "/trunk/#{file}"
740 branch_path_in_repos = "/branch/#{file}"
742 ctx = make_context(log)
743 ctx.mkdir(trunk, branch)
744 File.open(trunk_path, "w") {}
745 File.open(branch_path, "w") {}
748 original_rev = ctx.commit(@wc_path).revision
750 File.open(branch_path, "w") {|f| f.print(src)}
751 merged_rev = ctx.commit(@wc_path).revision
753 ctx.merge(branch, original_rev, branch, merged_rev, trunk)
756 mergeinfo = Svn::Core::MergeInfo.parse("#{branch_path_in_repos}:#{merged_rev}")
757 assert_equal({trunk_path_in_repos => mergeinfo},
758 @repos.mergeinfo([trunk_path_in_repos]))
759 assert_equal(mergeinfo, @repos.mergeinfo(trunk_path_in_repos))
765 STDERR.puts err if $DEBUG
769 class TestEditor < Svn::Delta::BaseEditor
770 attr_reader :sequence
775 def set_target_revision(target_revision)
776 @sequence << [:set_target_revision, target_revision]
779 def open_root(base_revision)
780 @sequence << [:open_root, base_revision]
783 def delete_entry(path, revision, parent_baton)
784 @sequence << [:delete_entry, path, revision, parent_baton]
787 def add_directory(path, parent_baton,
788 copyfrom_path, copyfrom_revision)
789 @sequence << [:add_directory, path, parent_baton,
790 copyfrom_path, copyfrom_revision]
793 def open_directory(path, parent_baton, base_revision)
794 @sequence << [:open_directory, path, parent_baton, base_revision]
797 def change_dir_prop(dir_baton, name, value)
798 @sequence << [:change_dir_prop, dir_baton, name, value]
801 def close_directory(dir_baton)
802 @sequence << [:close_directory, dir_baton]
805 def absent_directory(path, parent_baton)
806 @sequence << [:absent_directory, path, parent_baton]
809 def add_file(path, parent_baton,
810 copyfrom_path, copyfrom_revision)
811 @sequence << [:add_file, path, parent_baton,
812 copyfrom_path, copyfrom_revision]
815 def open_file(path, parent_baton, base_revision)
816 @sequence << [:open_file, path, parent_baton, base_revision]
819 # return nil or object which has `call' method.
820 def apply_textdelta(file_baton, base_checksum)
821 @sequence << [:apply_textdelta, file_baton, base_checksum]
825 def change_file_prop(file_baton, name, value)
826 @sequence << [:change_file_prop, file_baton, name, value]
829 def close_file(file_baton, text_checksum)
830 @sequence << [:close_file, file_baton, text_checksum]
833 def absent_file(path, parent_baton)
834 @sequence << [:absent_file, path, parent_baton]
837 def close_edit(baton)
838 @sequence << [:close_edit, baton]
841 def abort_edit(baton)
842 @sequence << [:abort_edit, baton]