8 class Error < StandardError
11 class NotInitialized < Error
12 attr_reader :destination_url
13 def initialize(destination_url)
14 @destination_url = destination_url
15 super("#{@destination_url} is not initialized yet.")
19 class ConsistencyError < Error
20 attr_reader :destination_url, :destination_latest_revision, :next_revision
21 def initialize(destination_url, destination_latest_revision, next_revision)
22 @destination_url = destination_url
23 @destination_latest_revision = destination_latest_revision
24 @next_revision = next_revision
25 super("the latest revision (#{@destination_latest_revision}) of " +
26 "the destination repository (#{@destination_url}) " +
27 "should be #{@next_revision - 1}.")
32 Svn::Ext::Core.constants.each do |name|
34 when /\ASVNSYNC_PROP_/
35 const_set($POSTMATCH, Svn::Ext::Core.const_get(name))
40 def initialize(destination_url, callbacks=nil)
41 @destination = Ra::Session.open(destination_url, nil, callbacks)
42 if @destination.repos_root != destination_url
44 "URL '#{destination_url}' is not repository root " +
45 "(#{@destination.repos_root})."
50 @destination[Property::FROM_URL, 0] = source.repos_root
51 @destination[Property::FROM_UUID, 0] = source.uuid
52 @destination[Property::LAST_MERGED_REV, 0] = "0"
54 sync_property_re = /\A#{Property::PREFIX}/
55 source.properties(0).each do |key, value|
56 next if sync_property_re =~ key
57 @destination[key, 0] = value
61 def sync(callbacks=nil)
62 last_merged_rev = @destination[Property::LAST_MERGED_REV, 0]
63 raise NotInitialized.new(@destination.repos_root) if last_merged_rev.nil?
65 last_merged_rev = Integer(last_merged_rev)
66 source = Ra::Session.open(@destination[Property::FROM_URL, 0], nil,
68 (last_merged_rev + 1).upto(source.latest_revision) do |revision|
69 unless @destination.latest_revision + 1 == revision
70 raise ConsistencyError.new(@destination.repos_root,
71 @destination.latest_revision,
74 editor = sync_editor(source, revision)
75 source.replay(revision, 0, editor)
77 sync_svn_revision_properties(source, revision)
78 @destination[Property::LAST_MERGED_REV, 0] = revision.to_s
83 def sync_editor(source, base_revision)
84 properties = source.properties(base_revision)
85 commit_editor = @destination.commit_editor2(properties)
86 Editor.new(commit_editor, @destination.repos_root, base_revision)
89 def sync_svn_revision_properties(source, revision)
90 [Core::PROP_REVISION_AUTHOR, Core::PROP_REVISION_DATE].each do |key|
91 @destination[key, revision] = source[key, revision]
95 class Editor < Delta::WrapEditor
96 def initialize(wrapped_editor, dest_url, base_revision)
99 @base_revision = base_revision
103 def open_root(base_revision)
108 def add_directory(path, parent_baton, copy_from_path, copy_from_rev)
110 copy_from_path = "#{@dest_url}#{CGI.escape(copy_from_path)}"
112 super(path, parent_baton, copy_from_path, copy_from_rev)
115 def add_file(path, parent_baton, copy_from_path, copy_from_rev)
117 copy_from_path = "#{@dest_url}#{CGI.escape(copy_from_path)}"
119 super(path, parent_baton, copy_from_path, copy_from_rev)
124 baton = @wrapped_editor.open_root(@base_revision)
125 @wrapped_editor.close_directory(baton)