1 # Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 from bzrlib.decorators import needs_write_lock
19 from bzrlib.inventory import Inventory, ROOT_ID
20 import bzrlib.osutils as osutils
21 from bzrlib.progress import ProgressBar
22 from bzrlib.revision import Revision
23 from bzrlib.repository import InterRepository
24 from bzrlib.trace import mutter
27 from cStringIO import StringIO
31 from svn.core import SubversionException, Pool
32 import svn.core, svn.ra
34 from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
35 SVN_PROP_BZR_REVPROP_PREFIX, SvnRepositoryFormat)
36 from tree import apply_txdelta_handler
39 def md5_strings(strings):
41 map(s.update, strings)
44 class RevisionBuildEditor(svn.delta.Editor):
45 def __init__(self, source, target, branch_path, revnum, prev_inventory, revid, svn_revprops, id_map, parent_branch):
46 self.branch_path = branch_path
47 self.old_inventory = prev_inventory
48 self.inventory = copy(prev_inventory)
52 self.parent_branch = parent_branch
55 self.transact = target.get_transaction()
56 self.weave_store = target.weave_store
60 self._parent_ids = None
62 self._svn_revprops = svn_revprops
66 def _get_revision(self, revid):
67 if self._parent_ids is None:
70 parent_ids = self.source.revision_parents(revid, self._parent_ids)
72 # Commit SVN revision properties to a Revision object
73 rev = Revision(revision_id=revid, parent_ids=parent_ids)
75 rev.timestamp = 1.0 * svn.core.secs_from_timestr(
76 self._svn_revprops[2], None) #date
79 rev.committer = self._svn_revprops[0] # author
80 if rev.committer is None:
82 rev.message = self._svn_revprops[1] # message
84 rev.properties = self._revprops
87 def open_root(self, base_revnum, baton):
88 if self.inventory.revision_id is None:
89 self.dir_baserev[ROOT_ID] = []
91 self.dir_baserev[ROOT_ID] = [self.inventory.revision_id]
92 self.inventory.revision_id = self.revid
95 def relpath(self, path):
96 return path.strip("/")
98 def delete_entry(self, path, revnum, parent_baton, pool):
99 del self.inventory[self.inventory.path2id(path)]
101 def close_directory(self, id):
105 self.inventory[id].revision = revid
107 file_weave = self.weave_store.get_weave_or_empty(id, self.transact)
108 if not file_weave.has_version(revid):
109 file_weave.add_lines(revid, self.dir_baserev[id], [])
111 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
112 file_id, revision_id = self.id_map[path]
114 self.dir_baserev[file_id] = []
115 ie = self.inventory.add_path(path, 'directory', file_id)
116 ie.revision = revision_id
120 def open_directory(self, path, parent_baton, base_revnum, pool):
121 file_id, revision_id = self.id_map[path]
122 assert base_revnum >= 0
123 base_file_id = self.old_inventory.path2id(path)
124 base_revid = self.old_inventory[base_file_id].revision
125 if file_id == base_file_id:
126 self.dir_baserev[file_id] = [base_revid]
127 ie = self.inventory[file_id]
129 # Replace if original was inside this branch
130 # change id of base_file_id to file_id
131 ie = self.inventory[base_file_id]
132 for name in ie.children:
133 ie.children[name].parent_id = file_id
134 # FIXME: Don't touch inventory internals
135 del self.inventory._byid[base_file_id]
136 self.inventory._byid[file_id] = ie
138 self.dir_baserev[file_id] = []
139 ie.revision = revision_id
142 def change_dir_prop(self, id, name, value, pool):
143 if name == SVN_PROP_BZR_MERGE:
145 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_MERGE)
148 self._parent_ids = value.splitlines()[-1]
149 elif name == SVN_PROP_SVK_MERGE:
150 if self._parent_ids is None:
151 # Only set parents using svk:merge if no
154 elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
155 self._revprops[name[len(SVN_PROP_BZR_REVPROP_PREFIX):]] = value
156 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
157 svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
158 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
159 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
160 svn.core.SVN_PROP_ENTRY_UUID,
161 svn.core.SVN_PROP_EXECUTABLE):
163 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
166 mutter('unsupported file property %r' % name)
168 def change_file_prop(self, id, name, value, pool):
169 if name == svn.core.SVN_PROP_EXECUTABLE:
170 # Strange, you'd expect executable to match svn.core.SVN_PROP_EXECUTABLE_VALUE, but that's not how SVN behaves.
171 self.is_executable = (value != None)
172 elif (name == svn.core.SVN_PROP_SPECIAL):
173 self.is_symlink = (value != None)
174 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
175 self.last_file_rev = int(value)
176 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
177 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
178 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
179 svn.core.SVN_PROP_ENTRY_UUID,
180 svn.core.SVN_PROP_MIME_TYPE):
182 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
185 mutter('unsupported file property %r' % name)
187 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
188 self.is_symlink = False
189 self.is_executable = None
191 self.file_parents = []
192 self.file_stream = None
195 def open_file(self, path, parent_id, base_revnum, pool):
196 base_file_id = self.old_inventory.path2id(path)
197 base_revid = self.old_inventory[base_file_id].revision
198 file_id, revid = self.id_map[path]
199 self.is_executable = None
200 self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
201 file_weave = self.weave_store.get_weave_or_empty(base_file_id, self.transact)
202 self.file_data = file_weave.get_text(base_revid)
203 self.file_stream = None
204 if file_id == base_file_id:
205 self.file_parents = [base_revid]
208 del self.inventory[base_file_id]
209 self.file_parents = []
212 def close_file(self, path, checksum):
213 if self.file_stream is not None:
214 self.file_stream.seek(0)
215 lines = osutils.split_lines(self.file_stream.read())
217 # Data didn't change or file is new
218 lines = osutils.split_lines(self.file_data)
220 actual_checksum = md5_strings(lines)
221 assert checksum is None or checksum == actual_checksum
223 file_id, revision_id = self.id_map[path]
224 file_weave = self.weave_store.get_weave_or_empty(file_id, self.transact)
225 if not file_weave.has_version(revision_id):
226 file_weave.add_lines(revision_id, self.file_parents, lines)
228 if file_id in self.inventory:
229 ie = self.inventory[file_id]
230 elif self.is_symlink:
231 ie = self.inventory.add_path(path, 'symlink', file_id)
233 ie = self.inventory.add_path(path, 'file', file_id)
234 ie.revision = revision_id
237 ie.symlink_target = lines[0][len("link "):]
242 ie.text_sha1 = osutils.sha_strings(lines)
243 ie.text_size = sum(map(len, lines))
244 if self.is_executable is not None:
245 ie.executable = self.is_executable
247 self.file_stream = None
249 def close_edit(self):
250 rev = self._get_revision(self.revid)
251 self.inventory.revision_id = self.revid
252 rev.inventory_sha1 = osutils.sha_string(
253 bzrlib.xml5.serializer_v5.write_inventory_to_string(
255 self.target.add_revision(self.revid, rev, self.inventory)
258 def abort_edit(self):
261 def apply_textdelta(self, file_id, base_checksum):
262 actual_checksum = md5.new(self.file_data).hexdigest(),
263 assert (base_checksum is None or base_checksum == actual_checksum,
264 "base checksum mismatch: %r != %r" % (base_checksum, actual_checksum))
265 self.file_stream = StringIO()
266 return apply_txdelta_handler(StringIO(self.file_data), self.file_stream, self.pool)
269 class InterSvnRepository(InterRepository):
270 """Svn to any repository actions."""
272 _matching_repo_format = SvnRepositoryFormat()
273 """The format to test with."""
276 def copy_content(self, revision_id=None, basis=None, pb=ProgressBar()):
277 """See InterRepository.copy_content."""
278 # Dictionary with paths as keys, revnums as values
280 # Loop over all the revnums until revision_id
281 # (or youngest_revnum) and call self.target.add_revision()
282 # or self.target.add_inventory() each time
283 if revision_id is None:
285 until_revnum = self.source._latest_revnum
287 (path, until_revnum) = self.source.parse_revision_id(revision_id)
289 repos_root = self.source.transport.get_repos_root()
295 it = self.source.follow_history(until_revnum)
297 it = self.source.follow_branch_history(path, until_revnum)
298 for (branch, changes, revnum) in it:
299 revid = self.source.generate_revision_id(revnum, branch)
301 if prev_revid is not None:
302 parents[prev_revid] = revid
306 if not self.target.has_revision(revid):
307 needed.append((branch, revnum, revid, changes))
309 parents[prev_revid] = None
314 transport = self.source.transport
315 for (branch, revnum, revid, changes) in needed:
317 pb.update('copying revision', num+1, len(needed)+1)
320 parent_revid = parents[revid]
322 if parent_revid is not None:
323 (parent_branch, parent_revnum) = self.source.parse_revision_id(parent_revid)
328 if parent_revid is None:
329 id_map = self.source.get_fileid_map(revnum, branch)
330 parent_inv = Inventory(ROOT_ID)
331 elif prev_revid != parent_revid:
332 id_map = self.source.get_fileid_map(revnum, branch)
333 parent_inv = self.target.get_inventory(parent_revid)
335 self.source.transform_fileid_map(self.source.uuid,
338 parent_inv = prev_inv
341 editor = RevisionBuildEditor(self.source, self.target, branch,
342 revnum, parent_inv, revid,
343 self.source._log.get_revision_info(revnum),
344 id_map, parent_branch)
347 edit, edit_baton = svn.delta.make_editor(editor, pool)
349 if parent_branch is None:
350 transport.reparent(repos_root)
352 transport.reparent("%s/%s" % (repos_root, parent_branch))
353 if parent_branch != branch:
354 switch_url = "%s/%s" % (repos_root, branch)
355 reporter, reporter_baton = transport.do_switch(
357 switch_url, edit, edit_baton, pool)
359 reporter, reporter_baton = transport.do_update(
360 revnum, "", True, edit, edit_baton, pool)
362 # Report status of existing paths
363 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton,
364 "", parent_revnum, False, None, pool)
367 svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton, pool)
370 prev_inv = editor.inventory
378 self.source.transport.reparent(repos_root)
381 def fetch(self, revision_id=None, pb=ProgressBar()):
382 """Fetch revisions. """
383 self.copy_content(revision_id=revision_id, pb=pb)
386 def is_compatible(source, target):
387 """Be compatible with SvnRepository."""
388 # FIXME: Also check target uses VersionedFile
389 mutter('test %r' % source)
390 return isinstance(source, SvnRepository)