Fix bug in revid caching.
[jelmer/subvertpy.git] / fetch.py
index a87412b5efac1b36b4914ab5c3b75a41dfbadc9e..2723f97fb4fb2239e7ee4a62de5317c2985dcc62 100644 (file)
--- a/fetch.py
+++ b/fetch.py
@@ -13,6 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""Fetching revisions from Subversion repositories in batches."""
 
 import bzrlib
 from bzrlib.inventory import Inventory
@@ -20,19 +21,20 @@ import bzrlib.osutils as osutils
 from bzrlib.revision import Revision
 from bzrlib.repository import InterRepository
 from bzrlib.trace import mutter
-from bzrlib.ui import ui_factory
+import bzrlib.ui as ui
 
 from copy import copy
 from cStringIO import StringIO
 import md5
 import os
 
-from svn.core import SubversionException, Pool
+from svn.core import Pool
 import svn.core
 
 from fileids import generate_file_id
 from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
-                SVN_PROP_BZR_REVPROP_PREFIX, SvnRepositoryFormat)
+                SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO, 
+                SvnRepositoryFormat, parse_revision_metadata)
 from tree import apply_txdelta_handler
 
 
@@ -43,12 +45,14 @@ def md5_strings(strings):
 
 
 class RevisionBuildEditor(svn.delta.Editor):
+    """Implementation of the Subversion commit editor interface that builds a 
+    Bazaar revision.
+    """
     def __init__(self, source, target, branch_path, prev_inventory, revid, 
                  svn_revprops, id_map):
         self.branch_path = branch_path
         self.old_inventory = prev_inventory
         self.inventory = copy(prev_inventory)
-        assert self.inventory.root is None or revnum > 0
         self.revid = revid
         self.id_map = id_map
         self.source = source
@@ -57,11 +61,15 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.weave_store = target.weave_store
         self.dir_baserev = {}
         self._parent_ids = None
-        self._revprops = {}
+        self._revinfo = None
         self._svn_revprops = svn_revprops
         self.pool = Pool()
 
     def _get_revision(self, revid):
+        """Creates the revision object.
+
+        :param revid: Revision id of the revision to create.
+        """
         if self._parent_ids is None:
             self._parent_ids = ""
 
@@ -70,8 +78,11 @@ class RevisionBuildEditor(svn.delta.Editor):
         # Commit SVN revision properties to a Revision object
         rev = Revision(revision_id=revid, parent_ids=parent_ids)
 
-        rev.timestamp = 1.0 * svn.core.secs_from_timestr(
-            self._svn_revprops[2], None) #date
+        if self._svn_revprops[2] is not None:
+            rev.timestamp = 1.0 * svn.core.secs_from_timestr(
+                self._svn_revprops[2], None) #date
+        else:
+            rev.timestamp = 0 # FIXME: Obtain repository creation time
         rev.timezone = None
 
         rev.committer = self._svn_revprops[0] # author
@@ -79,20 +90,30 @@ class RevisionBuildEditor(svn.delta.Editor):
             rev.committer = ""
         rev.message = self._svn_revprops[1] # message
 
-        rev.properties = self._revprops
+        if self._revinfo:
+            parse_revision_metadata(self._revinfo, rev)
+
         return rev
 
     def open_root(self, base_revnum, baton):
-        file_id, revision_id = self.id_map[""]
-        if self.inventory.root is None:
+        if self.old_inventory.root is None:
+            # First time the root is set
+            file_id = generate_file_id(self.source, self.revid, "")
             self.dir_baserev[file_id] = []
-            ie = self.inventory.add_path("", 'directory', file_id)
         else:
-            self.dir_baserev[file_id] = [self.inventory.revision_id]
-            ie = self.inventory[file_id]
+            assert self.old_inventory.root.revision is not None
+            if self.id_map.has_key(""):
+                file_id = self.id_map[""]
+            else:
+                file_id = self.old_inventory.root.file_id
+            self.dir_baserev[file_id] = [self.old_inventory.root.revision]
 
-        if ie is not None:
-            ie.revision = revision_id
+        if self.inventory.root is not None and \
+                file_id == self.inventory.root.file_id:
+            ie = self.inventory.root
+        else:
+            ie = self.inventory.add_path("", 'directory', file_id)
+        ie.revision = self.revid
         return file_id
 
     def _get_existing_id(self, parent_id, path):
@@ -106,7 +127,7 @@ class RevisionBuildEditor(svn.delta.Editor):
     def _get_new_id(self, parent_id, new_path):
         if self.id_map.has_key(new_path):
             return self.id_map[new_path]
-        return generate_file_id(self.revid, new_path)
+        return generate_file_id(self.source, self.revid, new_path)
 
     def delete_entry(self, path, revnum, parent_id, pool):
         path = path.decode("utf-8")
@@ -119,7 +140,8 @@ class RevisionBuildEditor(svn.delta.Editor):
         if not file_weave.has_version(self.revid):
             file_weave.add_lines(self.revid, self.dir_baserev[id], [])
 
-    def add_directory(self, path, parent_id, copyfrom_path, copyfrom_revnum, pool):
+    def add_directory(self, path, parent_id, copyfrom_path, copyfrom_revnum, 
+                      pool):
         path = path.decode("utf-8")
         file_id = self._get_new_id(parent_id, path)
 
@@ -153,7 +175,7 @@ class RevisionBuildEditor(svn.delta.Editor):
 
     def change_dir_prop(self, id, name, value, pool):
         if name == SVN_PROP_BZR_MERGE:
-            if id != self.id_map[""][0]:
+            if id != self.inventory.root.file_id:
                 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_MERGE)
                 return
             
@@ -163,8 +185,12 @@ class RevisionBuildEditor(svn.delta.Editor):
                 # Only set parents using svk:merge if no 
                 # bzr:merge set.
                 pass # FIXME 
-        elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
-            self._revprops[name[len(SVN_PROP_BZR_REVPROP_PREFIX):]] = value
+        elif name == SVN_PROP_BZR_REVISION_INFO:
+            if id != self.inventory.root.file_id:
+                mutter('rogue %r on non-root directory' % SVN_PROP_BZR_REVISION_INFO)
+                return
+            self._revinfo = value
         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
                       svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
@@ -174,7 +200,8 @@ class RevisionBuildEditor(svn.delta.Editor):
             pass
         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
             pass
-        else:
+        elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
+              name.startswith(SVN_PROP_BZR_PREFIX)):
             mutter('unsupported file property %r' % name)
 
     def change_file_prop(self, id, name, value, pool):
@@ -196,7 +223,8 @@ class RevisionBuildEditor(svn.delta.Editor):
             pass
         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
             pass
-        else:
+        elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
+              name.startswith(SVN_PROP_BZR_PREFIX)):
             mutter('unsupported file property %r' % name)
 
     def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
@@ -215,7 +243,8 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.file_id = self._get_existing_id(parent_id, path)
         self.is_executable = None
         self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
-        file_weave = self.weave_store.get_weave_or_empty(base_file_id, self.transact)
+        file_weave = self.weave_store.get_weave_or_empty(base_file_id, 
+                                                         self.transact)
         self.file_data = file_weave.get_text(base_revid)
         self.file_stream = None
         if self.file_id == base_file_id:
@@ -237,7 +266,8 @@ class RevisionBuildEditor(svn.delta.Editor):
         actual_checksum = md5_strings(lines)
         assert checksum is None or checksum == actual_checksum
 
-        file_weave = self.weave_store.get_weave_or_empty(self.file_id, self.transact)
+        file_weave = self.weave_store.get_weave_or_empty(self.file_id, 
+                                                         self.transact)
         if not file_weave.has_version(self.revid):
             file_weave.add_lines(self.revid, self.file_parents, lines)
 
@@ -277,12 +307,14 @@ class RevisionBuildEditor(svn.delta.Editor):
     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))
+            "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)
+        return apply_txdelta_handler(StringIO(self.file_data), 
+                                     self.file_stream, self.pool)
 
 
-class InterSvnRepository(InterRepository):
+class InterFromSvnRepository(InterRepository):
     """Svn to any repository actions."""
 
     _matching_repo_format = SvnRepositoryFormat()
@@ -296,8 +328,10 @@ class InterSvnRepository(InterRepository):
         parents = {}
         for (branch, revnum) in self.source.follow_history(
                                                 self.source._latest_revnum):
+            mutter('br, revnum: %r, %r' % (branch, revnum))
             revid = self.source.generate_revision_id(revnum, branch)
-            parents[revid] = self.source._mainline_revision_parent(branch, revnum)
+            parents[revid] = self.source._mainline_revision_parent(branch, 
+                                                                   revnum)
 
             if not self.target.has_revision(revid):
                 needed.append(revid)
@@ -306,7 +340,7 @@ class InterSvnRepository(InterRepository):
     def _find_until(self, revision_id):
         needed = []
         parents = {}
-        (path, until_revnum) = self.source.parse_revision_id(revision_id)
+        (path, until_revnum) = self.source.lookup_revision_id(revision_id)
 
         prev_revid = None
         for (branch, revnum) in self.source.follow_branch(path, 
@@ -326,6 +360,7 @@ class InterSvnRepository(InterRepository):
 
     def copy_content(self, revision_id=None, basis=None, pb=None):
         """See InterRepository.copy_content."""
+        # FIXME: Use basis
         # Dictionary with paths as keys, revnums as values
 
         # Loop over all the revnums until revision_id
@@ -353,23 +388,25 @@ class InterSvnRepository(InterRepository):
         transport = self.source.transport
         self.target.lock_write()
         if pb is None:
-            pb = ui_factory.nested_progress_bar()
+            pb = ui.ui_factory.nested_progress_bar()
             nested_pb = pb
         else:
             nested_pb = None
         num = 0
+        prev_inv = None
         try:
             for revid in needed:
-                (branch, revnum) = self.source.parse_revision_id(revid)
+                (branch, revnum) = self.source.lookup_revision_id(revid)
                 pb.update('copying revision', num, len(needed))
 
                 parent_revid = parents[revid]
 
                 if parent_revid is None:
-                    parent_inv = Inventory()
+                    parent_inv = Inventory(root_id=None)
                 elif prev_revid != parent_revid:
                     parent_inv = self.target.get_inventory(parent_revid)
                 else:
+                    assert prev_inv is not None
                     parent_inv = prev_inv
 
                 changes = self.source._log.get_revision_paths(revnum, branch)
@@ -378,9 +415,9 @@ class InterSvnRepository(InterRepository):
                                             revnum, branch, changes, renames)
 
                 editor = RevisionBuildEditor(self.source, self.target, branch, 
-                                             parent_inv, revid, 
-                                         self.source._log.get_revision_info(revnum),
-                                         id_map)
+                             parent_inv, revid, 
+                             self.source._log.get_revision_info(revnum),
+                             id_map)
 
                 pool = Pool()
                 edit, edit_baton = svn.delta.make_editor(editor, pool)
@@ -393,7 +430,8 @@ class InterSvnRepository(InterRepository):
                     # Report status of existing paths
                     reporter.set_path("", revnum, True, None, pool)
                 else:
-                    (parent_branch, parent_revnum) = self.source.parse_revision_id(parent_revid)
+                    (parent_branch, parent_revnum) = \
+                            self.source.lookup_revision_id(parent_revid)
                     transport.reparent("%s/%s" % (repos_root, parent_branch))
 
                     if parent_branch != branch:
@@ -429,6 +467,7 @@ class InterSvnRepository(InterRepository):
     @staticmethod
     def is_compatible(source, target):
         """Be compatible with SvnRepository."""
+        mutter("Checking from %r %r" % (source, target))
         # FIXME: Also check target uses VersionedFile
         return isinstance(source, SvnRepository)