1 # Copyright (C) 2005-2007 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.inventory import Inventory, ROOT_ID
19 import bzrlib.osutils as osutils
20 from bzrlib.revision import Revision
21 from bzrlib.repository import InterRepository
22 from bzrlib.trace import mutter
23 import bzrlib.ui as ui
26 from cStringIO import StringIO
30 from svn.core import SubversionException, Pool
31 import svn.core, svn.ra
33 from fileids import generate_file_id
34 from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
35 SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVPROP_PREFIX,
37 from tree import apply_txdelta_handler
40 def md5_strings(strings):
42 map(s.update, strings)
46 class RevisionBuildEditor(svn.delta.Editor):
47 def __init__(self, source, target, branch_path, prev_inventory, revid,
48 svn_revprops, id_map):
49 self.branch_path = branch_path
50 self.old_inventory = prev_inventory
51 self.inventory = copy(prev_inventory)
56 self.transact = target.get_transaction()
57 self.weave_store = target.weave_store
59 self._parent_ids = None
61 self._svn_revprops = svn_revprops
64 def _get_revision(self, revid):
65 if self._parent_ids is None:
68 parent_ids = self.source.revision_parents(revid, self._parent_ids)
70 # Commit SVN revision properties to a Revision object
71 rev = Revision(revision_id=revid, parent_ids=parent_ids)
73 rev.timestamp = 1.0 * svn.core.secs_from_timestr(
74 self._svn_revprops[2], None) #date
77 rev.committer = self._svn_revprops[0] # author
78 if rev.committer is None:
80 rev.message = self._svn_revprops[1] # message
82 rev.properties = self._revprops
85 def open_root(self, base_revnum, baton):
86 if self.inventory.revision_id is None:
87 self.dir_baserev[ROOT_ID] = []
89 self.dir_baserev[ROOT_ID] = [self.inventory.revision_id]
90 self.inventory.revision_id = self.revid
93 def _get_existing_id(self, parent_id, path):
94 if self.id_map.has_key(path):
95 return self.id_map[path]
96 return self._get_old_id(parent_id, path)
98 def _get_old_id(self, parent_id, old_path):
99 return self.old_inventory[parent_id].children[os.path.basename(old_path)].file_id
101 def _get_new_id(self, parent_id, new_path):
102 if self.id_map.has_key(new_path):
103 return self.id_map[new_path]
104 return generate_file_id(self.revid, new_path)
106 def delete_entry(self, path, revnum, parent_id, pool):
107 path = path.decode("utf-8")
108 del self.inventory[self._get_old_id(parent_id, path)]
110 def close_directory(self, id):
112 self.inventory[id].revision = self.revid
114 file_weave = self.weave_store.get_weave_or_empty(id, self.transact)
115 if not file_weave.has_version(self.revid):
116 file_weave.add_lines(self.revid, self.dir_baserev[id], [])
118 def add_directory(self, path, parent_id, copyfrom_path, copyfrom_revnum, pool):
119 path = path.decode("utf-8")
120 file_id = self._get_new_id(parent_id, path)
122 self.dir_baserev[file_id] = []
123 ie = self.inventory.add_path(path, 'directory', file_id)
124 ie.revision = self.revid
128 def open_directory(self, path, parent_id, base_revnum, pool):
129 assert base_revnum >= 0
130 base_file_id = self._get_old_id(parent_id, path)
131 base_revid = self.old_inventory[base_file_id].revision
132 file_id = self._get_existing_id(parent_id, path)
133 if file_id == base_file_id:
134 self.dir_baserev[file_id] = [base_revid]
135 ie = self.inventory[file_id]
137 # Replace if original was inside this branch
138 # change id of base_file_id to file_id
139 ie = self.inventory[base_file_id]
140 for name in ie.children:
141 ie.children[name].parent_id = file_id
142 # FIXME: Don't touch inventory internals
143 del self.inventory._byid[base_file_id]
144 self.inventory._byid[file_id] = ie
146 self.dir_baserev[file_id] = []
147 ie.revision = self.revid
150 def change_dir_prop(self, id, name, value, pool):
151 if name == SVN_PROP_BZR_MERGE:
153 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_MERGE)
156 self._parent_ids = value.splitlines()[-1]
157 elif name == SVN_PROP_SVK_MERGE:
158 if self._parent_ids is None:
159 # Only set parents using svk:merge if no
162 elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
163 self._revprops[name[len(SVN_PROP_BZR_REVPROP_PREFIX):]] = value
164 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
165 svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
166 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
167 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
168 svn.core.SVN_PROP_ENTRY_UUID,
169 svn.core.SVN_PROP_EXECUTABLE):
171 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
173 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
174 name.startswith(SVN_PROP_BZR_PREFIX)):
175 mutter('unsupported file property %r' % name)
177 def change_file_prop(self, id, name, value, pool):
178 if name == svn.core.SVN_PROP_EXECUTABLE:
179 # You'd expect executable to match
180 # svn.core.SVN_PROP_EXECUTABLE_VALUE, but that's not
181 # how SVN behaves. It appears to consider the presence
182 # of the property sufficient to mark it executable.
183 self.is_executable = (value != None)
184 elif (name == svn.core.SVN_PROP_SPECIAL):
185 self.is_symlink = (value != None)
186 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
187 self.last_file_rev = int(value)
188 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
189 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
190 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
191 svn.core.SVN_PROP_ENTRY_UUID,
192 svn.core.SVN_PROP_MIME_TYPE):
194 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
196 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
197 name.startswith(SVN_PROP_BZR_PREFIX)):
198 mutter('unsupported file property %r' % name)
200 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
201 path = path.decode("utf-8")
202 self.is_symlink = False
203 self.is_executable = None
205 self.file_parents = []
206 self.file_stream = None
207 self.file_id = self._get_new_id(parent_id, path)
210 def open_file(self, path, parent_id, base_revnum, pool):
211 base_file_id = self._get_old_id(parent_id, path)
212 base_revid = self.old_inventory[base_file_id].revision
213 self.file_id = self._get_existing_id(parent_id, path)
214 self.is_executable = None
215 self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
216 file_weave = self.weave_store.get_weave_or_empty(base_file_id, self.transact)
217 self.file_data = file_weave.get_text(base_revid)
218 self.file_stream = None
219 if self.file_id == base_file_id:
220 self.file_parents = [base_revid]
223 del self.inventory[base_file_id]
224 self.file_parents = []
227 def close_file(self, path, checksum):
228 if self.file_stream is not None:
229 self.file_stream.seek(0)
230 lines = osutils.split_lines(self.file_stream.read())
232 # Data didn't change or file is new
233 lines = osutils.split_lines(self.file_data)
235 actual_checksum = md5_strings(lines)
236 assert checksum is None or checksum == actual_checksum
238 file_weave = self.weave_store.get_weave_or_empty(self.file_id, self.transact)
239 if not file_weave.has_version(self.revid):
240 file_weave.add_lines(self.revid, self.file_parents, lines)
242 if self.file_id in self.inventory:
243 ie = self.inventory[self.file_id]
244 elif self.is_symlink:
245 ie = self.inventory.add_path(path, 'symlink', self.file_id)
247 ie = self.inventory.add_path(path, 'file', self.file_id)
248 ie.revision = self.revid
251 ie.symlink_target = lines[0][len("link "):]
256 ie.text_sha1 = osutils.sha_strings(lines)
257 ie.text_size = sum(map(len, lines))
258 if self.is_executable is not None:
259 ie.executable = self.is_executable
261 self.file_stream = None
263 def close_edit(self):
264 rev = self._get_revision(self.revid)
265 self.inventory.revision_id = self.revid
266 rev.inventory_sha1 = osutils.sha_string(
267 bzrlib.xml5.serializer_v5.write_inventory_to_string(
269 self.target.add_revision(self.revid, rev, self.inventory)
272 def abort_edit(self):
275 def apply_textdelta(self, file_id, base_checksum):
276 actual_checksum = md5.new(self.file_data).hexdigest(),
277 assert (base_checksum is None or base_checksum == actual_checksum,
278 "base checksum mismatch: %r != %r" % (base_checksum, actual_checksum))
279 self.file_stream = StringIO()
280 return apply_txdelta_handler(StringIO(self.file_data), self.file_stream, self.pool)
283 class InterSvnRepository(InterRepository):
284 """Svn to any repository actions."""
286 _matching_repo_format = SvnRepositoryFormat()
289 def _get_repo_format_to_test():
295 for (branch, revnum) in self.source.follow_history(
296 self.source._latest_revnum):
297 revid = self.source.generate_revision_id(revnum, branch)
298 parents[revid] = self.source._mainline_revision_parent(branch, revnum)
300 if not self.target.has_revision(revid):
302 return (needed, parents)
304 def _find_until(self, revision_id):
307 (path, until_revnum) = self.source.parse_revision_id(revision_id)
310 for (branch, revnum) in self.source.follow_branch(path,
312 revid = self.source.generate_revision_id(revnum, branch)
314 if prev_revid is not None:
315 parents[prev_revid] = revid
319 if not self.target.has_revision(revid):
322 parents[prev_revid] = None
323 return (needed, parents)
325 def copy_content(self, revision_id=None, basis=None, pb=None):
326 """See InterRepository.copy_content."""
327 # Dictionary with paths as keys, revnums as values
329 # Loop over all the revnums until revision_id
330 # (or youngest_revnum) and call self.target.add_revision()
331 # or self.target.add_inventory() each time
334 self.target.lock_read()
336 if revision_id is None:
337 (needed, parents) = self._find_all()
339 (needed, parents) = self._find_until(revision_id)
347 repos_root = self.source.transport.get_repos_root()
351 transport = self.source.transport
352 self.target.lock_write()
354 pb = ui.ui_factory.nested_progress_bar()
361 (branch, revnum) = self.source.parse_revision_id(revid)
362 pb.update('copying revision', num, len(needed))
364 parent_revid = parents[revid]
366 if parent_revid is None:
367 parent_inv = Inventory()
368 elif prev_revid != parent_revid:
369 parent_inv = self.target.get_inventory(parent_revid)
371 parent_inv = prev_inv
373 changes = self.source._log.get_revision_paths(revnum, branch)
374 renames = self.source.revision_fileid_renames(revid)
375 id_map = self.source.transform_fileid_map(self.source.uuid,
376 revnum, branch, changes, renames)
378 editor = RevisionBuildEditor(self.source, self.target, branch,
380 self.source._log.get_revision_info(revnum),
384 edit, edit_baton = svn.delta.make_editor(editor, pool)
386 if parent_revid is None:
387 transport.reparent("%s/%s" % (repos_root, branch))
388 reporter, reporter_baton = transport.do_update(
389 revnum, "", True, edit, edit_baton, pool)
391 # Report status of existing paths
392 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton,
393 "", revnum, True, None, pool)
395 (parent_branch, parent_revnum) = self.source.parse_revision_id(parent_revid)
396 transport.reparent("%s/%s" % (repos_root, parent_branch))
398 if parent_branch != branch:
399 switch_url = "%s/%s" % (repos_root, branch)
400 reporter, reporter_baton = transport.do_switch(
402 switch_url, edit, edit_baton, pool)
404 reporter, reporter_baton = transport.do_update(
405 revnum, "", True, edit, edit_baton, pool)
407 # Report status of existing paths
408 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton,
409 "", parent_revnum, False, None, pool)
412 svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton, pool)
415 prev_inv = editor.inventory
421 if nested_pb is not None:
423 self.source.transport.reparent(repos_root)
425 def fetch(self, revision_id=None, pb=None):
426 """Fetch revisions. """
427 self.copy_content(revision_id=revision_id, pb=pb)
430 def is_compatible(source, target):
431 """Be compatible with SvnRepository."""
432 # FIXME: Also check target uses VersionedFile
433 return isinstance(source, SvnRepository)