Merge 0.4.
[jelmer/subvertpy.git] / fetch.py
index aa8dcd1bb86ed4427264302346ff9bace959e2c3..f78aa4e374900f0b10e439d71afb9b5ee167cd73 100644 (file)
--- a/fetch.py
+++ b/fetch.py
@@ -25,9 +25,7 @@ from bzrlib.trace import mutter
 from cStringIO import StringIO
 import md5
 
-from svn.core import Pool
-import svn.core
-
+from bzrlib.plugins.svn.delta import apply_txdelta_handler
 from bzrlib.plugins.svn import properties
 from bzrlib.plugins.svn.errors import InvalidFileName
 from bzrlib.plugins.svn.logwalker import lazy_dict
@@ -39,7 +37,7 @@ from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_MERGE,
                      parse_revision_metadata)
 from bzrlib.plugins.svn.repository import SvnRepository, SvnRepositoryFormat
 from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE
-from bzrlib.plugins.svn.tree import (apply_txdelta_handler, parse_externals_description, 
+from bzrlib.plugins.svn.tree import (parse_externals_description, 
                   inventory_add_external)
 
 
@@ -86,7 +84,7 @@ def check_filename(path):
         raise InvalidFileName(path)
 
 
-class RevisionBuildEditor(svn.delta.Editor):
+class RevisionBuildEditor:
     """Implementation of the Subversion commit editor interface that builds a 
     Bazaar revision.
     """
@@ -95,6 +93,9 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.source = source
         self.transact = target.get_transaction()
 
+    def set_target_revision(self, target_revision):
+        pass
+
     def start_revision(self, revid, prev_inventory, revmeta):
         self.revid = revid
         (self.branch_path, self.revnum, self.mapping) = self.source.lookup_revision_id(revid)
@@ -103,7 +104,6 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.dir_baserev = {}
         self._revinfo = None
         self._premature_deletes = set()
-        self.pool = Pool()
         self.old_inventory = prev_inventory
         self.inventory = prev_inventory.copy()
         self._start_revision()
@@ -134,17 +134,17 @@ class RevisionBuildEditor(svn.delta.Editor):
 
         return (rev, signature)
 
-    def open_root(self, base_revnum, baton):
+    def open_root(self, base_revnum):
         if self.old_inventory.root is None:
             # First time the root is set
             old_file_id = None
             file_id = self.mapping.generate_file_id(self.source.uuid, self.revnum, self.branch_path, u"")
-            self.dir_baserev[file_id] = []
+            file_parents = []
         else:
             assert self.old_inventory.root.revision is not None
             old_file_id = self.old_inventory.root.file_id
             file_id = self._get_id_map().get("", old_file_id)
-            self.dir_baserev[file_id] = [self.old_inventory.root.revision]
+            file_parents = [self.old_inventory.root.revision]
 
         if self.inventory.root is not None and \
                 file_id == self.inventory.root.file_id:
@@ -152,7 +152,28 @@ class RevisionBuildEditor(svn.delta.Editor):
         else:
             ie = self.inventory.add_path("", 'directory', file_id)
         ie.revision = self.revid
-        return (old_file_id, file_id)
+        return DirectoryBuildEditor(self, old_file_id, file_id, file_parents)
+
+    def close(self):
+        pass
+
+    def _store_directory(self, file_id, parents):
+        raise NotImplementedError(self._store_directory)
+
+    def _get_file_data(self, file_id, revid):
+        raise NotImplementedError(self._get_file_data)
+
+    def _finish_commit(self):
+        raise NotImplementedError(self._finish_commit)
+
+    def abort(self):
+        pass
+
+    def _start_revision(self):
+        pass
+
+    def _store_file(self, file_id, lines, parents):
+        raise NotImplementedError(self._store_file)
 
     def _get_existing_id(self, old_parent_id, new_parent_id, path):
         assert isinstance(path, unicode)
@@ -186,82 +207,78 @@ class RevisionBuildEditor(svn.delta.Editor):
             return
         self.inventory.rename(file_id, parent_id, urlutils.basename(path))
 
-    def delete_entry(self, path, revnum, (old_parent_id, new_parent_id), pool):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        if path in self._premature_deletes:
-            # Delete recursively
-            self._premature_deletes.remove(path)
-            for p in self._premature_deletes.copy():
-                if p.startswith("%s/" % path):
-                    self._premature_deletes.remove(p)
-        else:
-            self.inventory.remove_recursive_id(self._get_old_id(old_parent_id, path))
+class DirectoryBuildEditor:
+    def __init__(self, editor, old_id, new_id, parent_revids=[]):
+        self.editor = editor
+        self.old_id = old_id
+        self.new_id = new_id
+        self.parent_revids = parent_revids
 
-    def close_directory(self, (old_id, new_id)):
-        self.inventory[new_id].revision = self.revid
+    def close(self):
+        self.editor.inventory[self.new_id].revision = self.editor.revid
+        self.editor._store_directory(self.new_id, self.parent_revids)
 
-        # Only record root if the target repository supports it
-        self._store_directory(new_id, self.dir_baserev[new_id])
+        if self.new_id == self.editor.inventory.root.file_id:
+            assert len(self.editor._premature_deletes) == 0
+            self.editor._finish_commit()
 
-    def add_directory(self, path, (old_parent_id, new_parent_id), copyfrom_path, copyfrom_revnum, 
-                      pool):
+    def add_directory(self, path, copyfrom_path=None, copyfrom_revnum=-1):
         assert isinstance(path, str)
         path = path.decode("utf-8")
         check_filename(path)
-        file_id = self._get_new_id(new_parent_id, path)
+        file_id = self.editor._get_new_id(self.new_id, path)
 
-        self.dir_baserev[file_id] = []
-        if file_id in self.inventory:
+        if file_id in self.editor.inventory:
             # This directory was moved here from somewhere else, but the 
             # other location hasn't been removed yet. 
             if copyfrom_path is None:
                 # This should ideally never happen!
-                copyfrom_path = self.old_inventory.id2path(file_id)
+                copyfrom_path = self.editor.old_inventory.id2path(file_id)
                 mutter('no copyfrom path set, assuming %r', copyfrom_path)
-            assert copyfrom_path == self.old_inventory.id2path(file_id)
-            assert copyfrom_path not in self._premature_deletes
-            self._premature_deletes.add(copyfrom_path)
-            self._rename(file_id, new_parent_id, path)
-            ie = self.inventory[file_id]
+            assert copyfrom_path == self.editor.old_inventory.id2path(file_id)
+            assert copyfrom_path not in self.editor._premature_deletes
+            self.editor._premature_deletes.add(copyfrom_path)
+            self.editor._rename(file_id, self.new_id, path)
+            ie = self.editor.inventory[file_id]
             old_file_id = file_id
         else:
             old_file_id = None
-            ie = self.inventory.add_path(path, 'directory', file_id)
-        ie.revision = self.revid
+            ie = self.editor.inventory.add_path(path, 'directory', file_id)
+        ie.revision = self.editor.revid
 
-        return (old_file_id, file_id)
+        return DirectoryBuildEditor(self.editor, old_file_id, file_id)
 
-    def open_directory(self, path, (old_parent_id, new_parent_id), base_revnum, pool):
+    def open_directory(self, path, base_revnum):
         assert isinstance(path, str)
         path = path.decode("utf-8")
-        assert base_revnum >= 0
-        base_file_id = self._get_old_id(old_parent_id, path)
-        base_revid = self.old_inventory[base_file_id].revision
-        file_id = self._get_existing_id(old_parent_id, new_parent_id, path)
+        assert isinstance(base_revnum, int)
+        base_file_id = self.editor._get_old_id(self.old_id, path)
+        base_revid = self.editor.old_inventory[base_file_id].revision
+        file_id = self.editor._get_existing_id(self.old_id, self.new_id, path)
         if file_id == base_file_id:
-            self.dir_baserev[file_id] = [base_revid]
-            ie = self.inventory[file_id]
+            file_parents = [base_revid]
+            ie = self.editor.inventory[file_id]
         else:
             # Replace if original was inside this branch
             # change id of base_file_id to file_id
-            ie = self.inventory[base_file_id]
+            ie = self.editor.inventory[base_file_id]
             for name in ie.children:
                 ie.children[name].parent_id = file_id
             # FIXME: Don't touch inventory internals
-            del self.inventory._byid[base_file_id]
-            self.inventory._byid[file_id] = ie
+            del self.editor.inventory._byid[base_file_id]
+            self.editor.inventory._byid[file_id] = ie
             ie.file_id = file_id
-            self.dir_baserev[file_id] = []
-        ie.revision = self.revid
-        return (base_file_id, file_id)
+            file_parents = []
+        ie.revision = self.editor.revid
+        return DirectoryBuildEditor(self.editor, base_file_id, file_id, 
+                                    file_parents)
 
-    def change_dir_prop(self, (old_id, new_id), name, value, pool):
-        if new_id == self.inventory.root.file_id:
+    def change_prop(self, name, value):
+        if self.new_id == self.editor.inventory.root.file_id:
             # Replay lazy_dict, since it may be more expensive
-            if type(self.revmeta.fileprops) != dict:
-                self.revmeta.fileprops = {}
-            self.revmeta.fileprops[name] = value
+            if type(self.editor.revmeta.fileprops) != dict:
+                self.editor.revmeta.fileprops = {}
+            self.editor.revmeta.fileprops[name] = value
 
         if name in (properties.PROP_ENTRY_COMMITTED_DATE,
                     properties.PROP_ENTRY_COMMITTED_REV,
@@ -275,10 +292,78 @@ class RevisionBuildEditor(svn.delta.Editor):
         elif name.startswith(properties.PROP_PREFIX):
             mutter('unsupported dir property %r', name)
 
-    def change_file_prop(self, id, name, value, pool):
+    def add_file(self, path, copyfrom_path=None, copyfrom_revnum=-1):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        check_filename(path)
+        file_id = self.editor._get_new_id(self.new_id, path)
+        if file_id in self.editor.inventory:
+            # This file was moved here from somewhere else, but the 
+            # other location hasn't been removed yet. 
+            if copyfrom_path is None:
+                # This should ideally never happen
+                copyfrom_path = self.editor.old_inventory.id2path(file_id)
+                mutter('no copyfrom path set, assuming %r' % copyfrom_path)
+            assert copyfrom_path == self.editor.old_inventory.id2path(file_id)
+            assert copyfrom_path not in self.editor._premature_deletes
+            self.editor._premature_deletes.add(copyfrom_path)
+            # No need to rename if it's already in the right spot
+            self.editor._rename(file_id, self.new_id, path)
+        return FileBuildEditor(self.editor, path, file_id)
+
+    def open_file(self, path, base_revnum):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        base_file_id = self.editor._get_old_id(self.old_id, path)
+        base_revid = self.editor.old_inventory[base_file_id].revision
+        file_id = self.editor._get_existing_id(self.old_id, self.new_id, path)
+        is_symlink = (self.editor.inventory[base_file_id].kind == 'symlink')
+        file_data = self.editor._get_file_data(base_file_id, base_revid)
+        if file_id == base_file_id:
+            file_parents = [base_revid]
+        else:
+            # Replace
+            del self.editor.inventory[base_file_id]
+            file_parents = []
+        return FileBuildEditor(self.editor, path, file_id, 
+                               file_parents, file_data, is_symlink=is_symlink)
+
+    def delete_entry(self, path, revnum):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        if path in self.editor._premature_deletes:
+            # Delete recursively
+            self.editor._premature_deletes.remove(path)
+            for p in self.editor._premature_deletes.copy():
+                if p.startswith("%s/" % path):
+                    self.editor._premature_deletes.remove(p)
+        else:
+            self.editor.inventory.remove_recursive_id(self.editor._get_old_id(self.old_id, path))
+
+class FileBuildEditor:
+    def __init__(self, editor, path, file_id, file_parents=[], data="", 
+                 is_symlink=False):
+        self.path = path
+        self.editor = editor
+        self.file_id = file_id
+        self.file_data = data
+        self.is_symlink = is_symlink
+        self.file_parents = file_parents
+        self.is_executable = None
+        self.file_stream = None
+
+    def apply_textdelta(self, base_checksum=None):
+        actual_checksum = md5.new(self.file_data).hexdigest()
+        assert (base_checksum is None or base_checksum == actual_checksum,
+            "base checksum mismatch: %r != %r" % (base_checksum, 
+                                                  actual_checksum))
+        self.file_stream = StringIO()
+        return apply_txdelta_handler(self.file_data, self.file_stream)
+
+    def change_prop(self, name, value):
         if name == properties.PROP_EXECUTABLE: 
             # You'd expect executable to match 
-            # properties.PROP_EXECUTABLE_VALUE, but that's not 
+            # constants.PROP_EXECUTABLE_VALUE, but that's not 
             # how SVN behaves. It appears to consider the presence 
             # of the property sufficient to mark it executable.
             self.is_executable = (value != None)
@@ -286,8 +371,6 @@ class RevisionBuildEditor(svn.delta.Editor):
             self.is_symlink = (value != None)
         elif name == properties.PROP_ENTRY_COMMITTED_REV:
             self.last_file_rev = int(value)
-        elif name == properties.PROP_EXTERNALS:
-            mutter('svn:externals property on file!')
         elif name in (properties.PROP_ENTRY_COMMITTED_DATE,
                       properties.PROP_ENTRY_LAST_AUTHOR,
                       properties.PROP_ENTRY_LOCK_TOKEN,
@@ -296,54 +379,14 @@ class RevisionBuildEditor(svn.delta.Editor):
             pass
         elif name.startswith(properties.PROP_WC_PREFIX):
             pass
+        elif name == properties.PROP_EXTERNALS:
+            mutter('svn:externals property on file!')
         elif (name.startswith(properties.PROP_PREFIX) or
               name.startswith(SVN_PROP_BZR_PREFIX)):
             mutter('unsupported file property %r', name)
 
-    def add_file(self, path, (old_parent_id, new_parent_id), copyfrom_path, copyfrom_revnum, baton):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        check_filename(path)
-        self.is_symlink = False
-        self.is_executable = None
-        self.file_data = ""
-        self.file_parents = []
-        self.file_stream = None
-        self.file_id = self._get_new_id(new_parent_id, path)
-        if self.file_id in self.inventory:
-            # This file was moved here from somewhere else, but the 
-            # other location hasn't been removed yet. 
-            if copyfrom_path is None:
-                # This should ideally never happen
-                copyfrom_path = self.old_inventory.id2path(self.file_id)
-                mutter('no copyfrom path set, assuming %r', copyfrom_path)
-            assert copyfrom_path == self.old_inventory.id2path(self.file_id)
-            assert copyfrom_path not in self._premature_deletes
-            self._premature_deletes.add(copyfrom_path)
-            # No need to rename if it's already in the right spot
-            self._rename(self.file_id, new_parent_id, path)
-        return path
-
-    def open_file(self, path, (old_parent_id, new_parent_id), base_revnum, pool):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        base_file_id = self._get_old_id(old_parent_id, path)
-        base_revid = self.old_inventory[base_file_id].revision
-        self.file_id = self._get_existing_id(old_parent_id, new_parent_id, path)
-        self.is_executable = None
-        self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
-        self.file_data = self._get_file_data(base_file_id, base_revid)
-        self.file_stream = None
-        if self.file_id == base_file_id:
-            self.file_parents = [base_revid]
-        else:
-            # Replace
-            del self.inventory[base_file_id]
-            self.file_parents = []
-        return path
-
-    def close_file(self, path, checksum):
-        assert isinstance(path, unicode)
+    def close(self, checksum=None):
+        assert isinstance(self.path, unicode)
         if self.file_stream is not None:
             self.file_stream.seek(0)
             lines = osutils.split_lines(self.file_stream.read())
@@ -354,23 +397,23 @@ class RevisionBuildEditor(svn.delta.Editor):
         actual_checksum = md5_strings(lines)
         assert checksum is None or checksum == actual_checksum
 
-        self._store_file(self.file_id, lines, self.file_parents)
+        self.editor._store_file(self.file_id, lines, self.file_parents)
 
         assert self.is_symlink in (True, False)
 
-        if self.file_id in self.inventory:
-            del self.inventory[self.file_id]
+        if self.file_id in self.editor.inventory:
+            del self.editor.inventory[self.file_id]
 
         if self.is_symlink:
-            ie = self.inventory.add_path(path, 'symlink', self.file_id)
+            ie = self.editor.inventory.add_path(self.path, 'symlink', self.file_id)
             ie.symlink_target = lines[0][len("link "):]
             ie.text_sha1 = None
             ie.text_size = None
             ie.executable = False
-            ie.revision = self.revid
+            ie.revision = self.editor.revid
         else:
-            ie = self.inventory.add_path(path, 'file', self.file_id)
-            ie.revision = self.revid
+            ie = self.editor.inventory.add_path(self.path, 'file', self.file_id)
+            ie.revision = self.editor.revid
             ie.kind = 'file'
             ie.symlink_target = None
             ie.text_sha1 = osutils.sha_strings(lines)
@@ -379,41 +422,8 @@ class RevisionBuildEditor(svn.delta.Editor):
             if self.is_executable is not None:
                 ie.executable = self.is_executable
 
-
         self.file_stream = None
 
-    def close_edit(self):
-        assert len(self._premature_deletes) == 0
-        self._finish_commit()
-        self.pool.destroy()
-
-    def apply_textdelta(self, file_id, base_checksum):
-        actual_checksum = md5.new(self.file_data).hexdigest(),
-        assert (base_checksum is None or base_checksum == actual_checksum,
-            "base checksum mismatch: %r != %r" % (base_checksum, 
-                                                  actual_checksum))
-        self.file_stream = StringIO()
-        return apply_txdelta_handler(StringIO(self.file_data), 
-                                     self.file_stream, self.pool)
-
-    def _store_file(self, file_id, lines, parents):
-        raise NotImplementedError(self._store_file)
-
-    def _store_directory(self, file_id, parents):
-        raise NotImplementedError(self._store_directory)
-
-    def _get_file_data(self, file_id, revid):
-        raise NotImplementedError(self._get_file_data)
-
-    def _finish_commit(self):
-        raise NotImplementedError(self._finish_commit)
-
-    def abort_edit(self):
-        pass
-
-    def _start_revision(self):
-        pass
-
 
 class WeaveRevisionBuildEditor(RevisionBuildEditor):
     """Subversion commit editor that can write to a weave-based repository.
@@ -452,7 +462,7 @@ class WeaveRevisionBuildEditor(RevisionBuildEditor):
         self.target.commit_write_group()
         self._write_group_active = False
 
-    def abort_edit(self):
+    def abort(self):
         if self._write_group_active:
             self.target.abort_write_group()
             self._write_group_active = False
@@ -587,14 +597,6 @@ class InterFromSvnRepository(InterRepository):
         """See InterRepository.copy_content."""
         self.fetch(revision_id, pb, find_ghosts=False)
 
-    def _fetch_replay(self, revids, pb=None):
-        """Copy a set of related revisions using svn.ra.replay.
-
-        :param revids: Revision ids to copy.
-        :param pb: Optional progress bar
-        """
-        raise NotImplementedError(self._copy_revisions_replay)
-
     def _fetch_switch(self, repos_root, revids, pb=None):
         """Copy a set of related revisions using svn.ra.switch.
 
@@ -638,14 +640,14 @@ class InterFromSvnRepository(InterRepository):
                                                        editor.branch_path)
 
                             conn = self.source.transport.connections.get(branch_url)
-                            reporter = conn.do_update(editor.revnum, True, 
+                            reporter = conn.do_update(editor.revnum, "", True, 
                                                            editor)
 
                             try:
                                 # Report status of existing paths
                                 reporter.set_path("", editor.revnum, True, None)
                             except:
-                                reporter.abort_report()
+                                reporter.abort()
                                 raise
                         else:
                             (parent_branch, parent_revnum, mapping) = \
@@ -653,25 +655,25 @@ class InterFromSvnRepository(InterRepository):
                             conn = self.source.transport.connections.get(urlutils.join(repos_root, parent_branch))
 
                             if parent_branch != editor.branch_path:
-                                reporter = conn.do_switch(editor.revnum, True, 
+                                reporter = conn.do_switch(editor.revnum, "", True, 
                                     urlutils.join(repos_root, editor.branch_path), 
                                     editor)
                             else:
-                                reporter = conn.do_update(editor.revnum, True, editor)
+                                reporter = conn.do_update(editor.revnum, "", True, editor)
 
                             try:
                                 # Report status of existing paths
                                 reporter.set_path("", parent_revnum, False, None)
                             except:
-                                reporter.abort_report()
+                                reporter.abort()
                                 raise
 
-                        reporter.finish_report()
+                        reporter.finish()
                     finally:
                         if conn is not None:
                             self.source.transport.add_connection(conn)
                 except:
-                    editor.abort_edit()
+                    editor.abort()
                     raise
 
                 prev_inv = editor.inventory
@@ -687,6 +689,8 @@ class InterFromSvnRepository(InterRepository):
         """Fetch revisions. """
         if revision_id == NULL_REVISION:
             return
+
+        self._supports_replay = True # assume replay supported by default
         # Dictionary with paths as keys, revnums as values
 
         if pb: