Merge upstream fixes.
authorJelmer Vernooij <jelmer@samba.org>
Sat, 23 Dec 2006 20:08:29 +0000 (21:08 +0100)
committerJelmer Vernooij <jelmer@samba.org>
Sat, 23 Dec 2006 20:08:29 +0000 (21:08 +0100)
15 files changed:
.bzrignore
__init__.py
checkout.py
commit.py
fetch.py
fileids.py
logwalker.py
mapping.txt
repository.py
tests/test_commit.py
tests/test_fileids.py
tests/test_repos.py
tests/test_transport.py
tests/test_workingtree.py
tree.py

index 24d53971064471be130fe839eeb638f9a5dfe21d..ac744b56e3c2bea96046f6a4732c85020a936f33 100644 (file)
@@ -1,3 +1,4 @@
 build
 *.pyc
 test????.tmp
+tags
index 99b8a514ae148f37373a913c88c282187bca098b..3850b34cc3678758405408ceee62e78b95bf9d31 100644 (file)
@@ -55,6 +55,10 @@ def check_bzrlib_version(desired):
             raise Exception, 'Version mismatch'
 
 check_bzrlib_version(required_bzr_version)
+try:
+    from bzrlib.workingtree import WorkingTreeFormat4
+except ImportError:
+    warning('this version of bzr-svn requires WorkingTreeFormat4 to be available to work properly')
 
 import branch
 import convert
index 4aea5adb06ce902ce0cff8ce1fa4e2c81782570b..5a96f9418eaba5078baace943912dc1233a65d9b 100644 (file)
@@ -17,8 +17,7 @@
 from binascii import hexlify
 from bzrlib.bzrdir import BzrDirFormat, BzrDir
 from bzrlib.errors import NotBranchError, NoSuchFile, InvalidRevisionId
-from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile,
-                              ROOT_ID)
+from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile)
 from bzrlib.lockable_files import TransportLock, LockableFiles
 from bzrlib.lockdir import LockDir
 from bzrlib.osutils import rand_bytes, fingerprint_file
@@ -54,11 +53,9 @@ class SvnWorkingTree(WorkingTree):
         self.client_ctx.log_msg_baton2 = self.log_message_func
 
         self._set_inventory(self.read_working_inventory(), dirty=False)
-        mutter('working inv: %r' % self.read_working_inventory().entries())
 
         self.base_revid = branch.repository.generate_revision_id(
                     self.base_revnum, branch.branch_path)
-        mutter('basis inv: %r' % self.basis_tree().inventory.entries())
         self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(), 'bzr')
         try:
             os.makedirs(self.controldir)
@@ -171,7 +168,7 @@ class SvnWorkingTree(WorkingTree):
             svn.wc.adm_close(to_wc)
 
     def read_working_inventory(self):
-        inv = Inventory(ROOT_ID)
+        inv = Inventory()
 
         def add_file_to_inv(relpath, id, revid, parent_id):
             """Add a file to the inventory."""
@@ -240,13 +237,7 @@ class SvnWorkingTree(WorkingTree):
             self.base_revnum = max(self.base_revnum, entry.revision)
 
             # First handle directory itself
-            if relpath == "":
-                inv.add_path("", 'directory', ROOT_ID)
-                inv.revision_id = revid
-            else:
-                inventry = InventoryDirectory(id, os.path.basename(relpath), parent_id)
-                inventry.revision = revid
-                inv.add(inventry)
+            inv.add_path(relpath, 'directory', id, parent_id).revision = revid
 
             for name in entries:
                 if name == "":
@@ -342,7 +333,8 @@ class SvnWorkingTree(WorkingTree):
 
         commit_info = svn.client.commit3(specific_files, True, False, self.client_ctx)
 
-        revid = self.branch.repository.generate_revision_id(commit_info.revision, self.branch.branch_path)
+        revid = self.branch.repository.generate_revision_id(
+                commit_info.revision, self.branch.branch_path)
 
         self.base_revid = revid
         self.branch._revision_history.append(revid)
index 21310445ed2646439fa7bed8ccd473b1e95ee905..7e17a7e8a3742117c5b2990a72ff2d17f6caec2e 100644 (file)
--- a/commit.py
+++ b/commit.py
@@ -19,21 +19,20 @@ from svn.core import Pool, SubversionException
 
 from bzrlib.errors import (UnsupportedOperation, BzrError, InvalidRevisionId, 
                            DivergedBranches)
-from bzrlib.inventory import Inventory, ROOT_ID
+from bzrlib.inventory import Inventory
 import bzrlib.osutils as osutils
-from bzrlib.repository import CommitBuilder
+from bzrlib.repository import RootCommitBuilder
 from bzrlib.trace import mutter, warning
 
-from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE, 
-                       SVN_PROP_BZR_REVPROP_PREFIX, revision_id_to_svk_feature)
+from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_BZR_FILEIDS,
+                        SVN_PROP_SVK_MERGE, SVN_PROP_BZR_REVPROP_PREFIX, 
+                        revision_id_to_svk_feature, escape_svn_path)
 
 import os
 
-class SvnCommitBuilder(CommitBuilder):
+class SvnCommitBuilder(RootCommitBuilder):
     """Commit Builder implementation wrapped around svn_delta_editor. """
 
-    record_root_entry = True 
-
     def __init__(self, repository, branch, parents, config, revprops):
         """Instantiate a new SvnCommitBuilder.
 
@@ -87,14 +86,13 @@ class SvnCommitBuilder(CommitBuilder):
                 self.branch.last_revision() in parents)
 
         if self.branch.last_revision() is None:
-            self.old_inv = Inventory(ROOT_ID)
+            self.old_inv = Inventory(root_id=None)
         else:
-            self.old_inv = self.repository.get_inventory(
-                               self.branch.last_revision())
+            self.old_inv = self.repository.get_inventory(self.branch.last_revision())
 
         self.modified_files = {}
         self.modified_dirs = []
-        
+
     def _generate_revision_if_needed(self):
         pass
 
@@ -123,8 +121,9 @@ class SvnCommitBuilder(CommitBuilder):
 
         svn.delta.svn_txdelta_send_string(contents, txdelta, txbaton, self.pool)
 
-    def _dir_process(self, path, file_id, baton):
-        mutter('processing %r' % path)
+    def _dir_process(self, file_id, baton):
+        path = self.new_inventory.id2path(file_id)
+        mutter('processing %r (%r)' % (path, file_id))
         if path == "":
             # Set all the revprops
             for prop, value in self._svnprops.items():
@@ -262,8 +261,7 @@ class SvnCommitBuilder(CommitBuilder):
 
             # Handle this directory
             if child_ie.file_id in self.modified_dirs:
-                self._dir_process(self.new_inventory.id2path(child_ie.file_id), 
-                        child_ie.file_id, child_baton)
+                self._dir_process(child_ie.file_id, child_baton)
 
             svn.delta.editor_invoke_close_directory(self.editor, child_baton, 
                                              self.pool)
@@ -307,7 +305,7 @@ class SvnCommitBuilder(CommitBuilder):
         branch_batons = self.open_branch_batons(root,
                                 self.branch.branch_path.split("/"))
 
-        self._dir_process("", self.new_inventory.root.file_id, branch_batons[-1])
+        self._dir_process(self.new_inventory.root.file_id, branch_batons[-1])
 
         branch_batons.reverse()
         for baton in branch_batons:
@@ -330,6 +328,44 @@ class SvnCommitBuilder(CommitBuilder):
 
         return revid
 
+    def record_entry_contents(self, ie, parent_invs, path, tree):
+        """Record the content of ie from tree into the commit if needed.
+
+        Side effect: sets ie.revision when unchanged
+
+        :param ie: An inventory entry present in the commit.
+        :param parent_invs: The inventories of the parent revisions of the
+            commit.
+        :param path: The path the entry is at in the tree.
+        :param tree: The tree which contains this entry and should be used to 
+        obtain content.
+        """
+        assert self.new_inventory.root is not None or ie.parent_id is None
+        self.new_inventory.add(ie)
+
+        # ie.revision is always None if the InventoryEntry is considered
+        # for committing. ie.snapshot will record the correct revision 
+        # which may be the sole parent if it is untouched.
+        mutter('recording %s' % ie.file_id)
+        if ie.revision is not None:
+            return
+
+        # Make sure that ie.file_id exists in the map
+        if not ie.file_id in self.old_inv:
+            if not self._svnprops.has_key(SVN_PROP_BZR_FILEIDS):
+                self._svnprops[SVN_PROP_BZR_FILEIDS] = ""
+            mutter('adding fileid mapping %s -> %s' % (path, ie.file_id))
+            self._svnprops[SVN_PROP_BZR_FILEIDS] += "%s\t%s\n" % (escape_svn_path(path, "%\t\n"), ie.file_id)
+
+        previous_entries = ie.find_previous_heads(
+            parent_invs,
+            self.repository.weave_store,
+            self.repository.get_transaction())
+
+        # we are creating a new revision for ie in the history store
+        # and inventory.
+        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
+
 
 def push_as_merged(target, source, revision_id):
     rev = source.repository.get_revision(revision_id)
@@ -350,7 +386,7 @@ def push_as_merged(target, source, revision_id):
             continue
 
         id = ie.file_id
-        while id != ROOT_ID:
+        while inv[id].parent_id is not None:
             if inv[id].revision is None:
                 break
             inv[id].revision = None
index b02dc83e5e662d0e513c694af26d0940c363f0a9..e0471e50653a1cac143eed6ae203acb3f0df750b 100644 (file)
--- a/fetch.py
+++ b/fetch.py
@@ -16,7 +16,7 @@
 
 import bzrlib
 from bzrlib.decorators import needs_write_lock
-from bzrlib.inventory import Inventory, ROOT_ID
+from bzrlib.inventory import Inventory
 import bzrlib.osutils as osutils
 from bzrlib.progress import ProgressBar
 from bzrlib.revision import Revision
@@ -42,11 +42,14 @@ def md5_strings(strings):
     return s.hexdigest()
 
 class RevisionBuildEditor(svn.delta.Editor):
-    def __init__(self, source, target, branch_path, revnum, prev_inventory, revid, svn_revprops, id_map, parent_branch, parent_id_map):
+    def __init__(self, source, target, branch_path, revnum, prev_inventory, 
+                 revid, svn_revprops, id_map, parent_branch, parent_id_map):
         self.branch_path = branch_path
         self.inventory = copy(prev_inventory)
+        assert self.inventory.root is None or revnum > 0
         self.revid = revid
         self.revnum = revnum
+        mutter('idmap for %r %r' % (revid, id_map))
         self.id_map = id_map
         self.parent_branch = parent_branch
         self.parent_id_map = parent_id_map
@@ -83,12 +86,17 @@ class RevisionBuildEditor(svn.delta.Editor):
         return rev
 
     def open_root(self, base_revnum, baton):
-        if self.inventory.revision_id is None:
-            self.dir_baserev[ROOT_ID] = []
+        file_id, revision_id = self.id_map[""]
+        if self.inventory.root is None:
+            self.dir_baserev[file_id] = []
+            ie = self.inventory.add_path("", 'directory', file_id)
         else:
-            self.dir_baserev[ROOT_ID] = [self.inventory.revision_id]
-        self.inventory.revision_id = self.revid
-        return ROOT_ID
+            self.dir_baserev[file_id] = [self.inventory.revision_id]
+            ie = self.inventory[file_id]
+
+        if ie is not None:
+            ie.revision = revision_id
+        return file_id
 
     def relpath(self, path):
         return path.strip("/")
@@ -99,7 +107,7 @@ class RevisionBuildEditor(svn.delta.Editor):
     def close_directory(self, id):
         revid = self.revid
 
-        if id != ROOT_ID:
+        if id != self.id_map[""][0]:
             self.inventory[id].revision = revid
 
             file_weave = self.weave_store.get_weave_or_empty(id, self.transact)
@@ -119,8 +127,7 @@ class RevisionBuildEditor(svn.delta.Editor):
 
         self.dir_baserev[file_id] = []
         ie = self.inventory.add_path(path, 'directory', file_id)
-        if ie:
-            ie.revision = revision_id
+        ie.revision = revision_id
 
         return file_id
 
@@ -129,7 +136,7 @@ class RevisionBuildEditor(svn.delta.Editor):
 
     def change_dir_prop(self, id, name, value, pool):
         if name == SVN_PROP_BZR_MERGE:
-            if id != ROOT_ID:
+            if id != self.id_map[""][0]:
                 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_MERGE)
                 return
             
@@ -274,6 +281,7 @@ class InterSvnRepository(InterRepository):
 
         repos_root = self.source.transport.get_repos_root()
         
+        # first, figure out what revisions need to be fetched
         needed = []
         parents = {}
         prev_revid = None
@@ -309,9 +317,11 @@ class InterSvnRepository(InterRepository):
                 parent_branch = None
 
             if parent_revid is None:
-                parent_id_map = {"": (ROOT_ID, None)}
+                # if there is no parent id, that means this is the 
+                # first revision on this branch
                 id_map = self.source.get_fileid_map(revnum, branch)
-                parent_inv = Inventory(ROOT_ID)
+                parent_id_map = None
+                parent_inv = Inventory(root_id=None)
             elif prev_revid != parent_revid:
                 parent_id_map = self.source.get_fileid_map(parent_revnum, parent_branch)
                 id_map = self.source.get_fileid_map(revnum, branch)
@@ -323,7 +333,6 @@ class InterSvnRepository(InterRepository):
                                         changes, id_map)
                 parent_inv = prev_inv
 
-
             editor = RevisionBuildEditor(self.source, self.target, branch, 
                                          revnum, parent_inv, revid, 
                                      self.source._log.get_revision_info(revnum),
@@ -375,7 +384,6 @@ class InterSvnRepository(InterRepository):
     def is_compatible(source, target):
         """Be compatible with SvnRepository."""
         # FIXME: Also check target uses VersionedFile
-        mutter('test %r' % source)
         return isinstance(source, SvnRepository)
 
 
index abc99242c558b4aa67823127201c03be0511fbb9..741693c3b7df75e89feb783106f52bac77198c6e 100644 (file)
@@ -14,9 +14,9 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from bzrlib.errors import RevisionNotPresent
-from bzrlib.inventory import ROOT_ID
+from bzrlib.errors import RevisionNotPresent, NotBranchError
 from bzrlib.progress import ProgressBar
+from bzrlib.revision import NULL_REVISION
 from bzrlib.trace import mutter
 from bzrlib.transport import get_transport
 from bzrlib.knit import KnitVersionedFile
@@ -36,10 +36,9 @@ def generate_svn_file_id(uuid, revnum, branch, path):
     :param branch: Branch path of the branch in which the file was introduced.
     :param path: Original path of the file.
     """
-    if path == "":
-        return ROOT_ID
-    introduced_revision_id = generate_svn_revision_id(uuid, revnum, branch)
-    return "%s-%s" % (introduced_revision_id, escape_svn_path(path))
+    # FIXME: is the branch path required here?
+    return "svn-v%d:%d@%s-%s-%s" % (MAPPING_VERSION, revnum, 
+            uuid, escape_svn_path(branch), escape_svn_path(path))
 
 
 def generate_file_id(revid, path):
@@ -99,7 +98,8 @@ class FileIdMap(object):
 
         return map
 
-    def apply_changes(self, uuid, revnum, branch, global_changes, map):
+    def apply_changes(self, uuid, revnum, branch, global_changes, map, 
+            renames={}):
         """Change file id map to incorporate specified changes.
 
         :param uuid: UUID of repository changes happen in
@@ -117,14 +117,18 @@ class FileIdMap(object):
 
         revid = generate_svn_revision_id(uuid, revnum, branch)
 
-        return self._apply_changes(map, revid, changes, find_children)
+        return self._apply_changes(map, revid, changes, find_children, renames)
 
     def get_map(self, uuid, revnum, branch, pb=None):
         """Make sure the map is up to date until revnum."""
         # First, find the last cached map
         todo = []
         next_parent_revs = []
-        map = {"": (ROOT_ID, None)} # No history -> empty map
+        
+        if revnum == 0:
+            return {}
+
+        # No history -> empty map
         for (bp, paths, rev) in self._log.follow_history(branch, revnum):
             revid = generate_svn_revision_id(uuid, rev, bp)
             map = self.load(revid)
@@ -135,6 +139,12 @@ class FileIdMap(object):
             else:
                 todo.append((revid, paths))
                 continue
+
+        if len(next_parent_revs) == 0:
+            if self._log.scheme.is_branch(""):
+                map = {"": (generate_svn_file_id(uuid, 0, "", ""), NULL_REVISION)}
+            else:
+                map = {}
     
         # target revision was present
         if len(todo) == 0:
@@ -169,9 +179,12 @@ class FileIdMap(object):
 
 class SimpleFileIdMap(FileIdMap):
     @staticmethod
-    def _apply_changes(map, revid, changes, find_children=None):
-        map[""] = (ROOT_ID, revid)
-
+    def _apply_changes(map, revid, changes, find_children=None, renames={}):
+        def new_file_id(path):
+            mutter('new file id for %r. renames: %r' % (path, renames))
+            if renames.has_key(path):
+                return renames[path]
+            return generate_file_id(revid, path)
         sorted_paths = changes.keys()
         sorted_paths.sort()
         for p in sorted_paths:
@@ -185,7 +198,7 @@ class SimpleFileIdMap(FileIdMap):
                         del map[c]
 
             if data[0] in ('A', 'R'):
-                map[p] = generate_file_id(revid, p), revid
+                map[p] = new_file_id(p), revid
 
                 if not data[1] is None:
                     mutter('%r:%s copied from %r:%s' % (p, revid, data[1], data[2]))
@@ -193,11 +206,9 @@ class SimpleFileIdMap(FileIdMap):
                         warn('incomplete data for %r' % p)
                     else:
                         for c in find_children(data[1], data[2]):
-                            map[c.replace(data[1], p, 1)] = generate_file_id(revid, c), revid
+                            map[c.replace(data[1], p, 1)] = new_file_id(c), revid
 
             elif data[0] == 'M':
-                if p == "":
-                    map[p] = (ROOT_ID, "")
                 assert map.has_key(p), "Map has no item %s to modify" % p
                 map[p] = map[p][0], revid
             
index e95b0d216cf0b61231fc5aaa20c35a51be570667..e090a1e5dec1595a7094aa4f705223d24ed31605 100644 (file)
@@ -55,11 +55,12 @@ def _escape_commit_message(message):
 
 
 class NotSvnBranchPath(BzrError):
-    _fmt = """{%(branch_path)s} is not a valid Svn branch path"""
+    _fmt = """{%(branch_path)s}:%(revnum)s is not a valid Svn branch path"""
 
-    def __init__(self, branch_path):
+    def __init__(self, branch_path, revnum=None):
         BzrError.__init__(self)
         self.branch_path = branch_path
+        self.revnum = revnum
 
 
 class LogWalker(object):
@@ -164,7 +165,7 @@ class LogWalker(object):
             return
 
         if not branch_path is None and not self.scheme.is_branch(branch_path):
-            raise NotSvnBranchPath(branch_path)
+            raise NotSvnBranchPath(branch_path, revnum)
 
         if branch_path:
             branch_path = branch_path.strip("/")
index d6685359731c124471ddeece6af05b9b9c465252..8c9fa9fd272d84121c169dcc992f289dab679cc6 100644 (file)
@@ -1,8 +1,9 @@
 This document specifies mapping between Subversion and Bazaar semantics.
 
-Revision: 2
+Revision: 3
 Jelmer Vernooij <jelmer@samba.org>, June 2006.
 Updated October 2006.
+Updated December 2006.
 
 == Branch paths ==
 
@@ -71,12 +72,35 @@ Subversion does not use file ids. It is not possible to know whether a file in
 revision X and a file in revision Y are the same without traversing over all 
 the revisions between X and Y.
 
-Fileids are generated by concatenating a revision id and the path of the file
-from the revision it was added or was copied from another file.
+File ids use the following syntax:
 
-Since / is forbidden in revision ids, the '/', '-', '%' and all whitespace
+svn-v<MAPPING_VERSION>:<REVNO>@<UUID>-<BRANCHPATH>-<PATH>
+
+Since / is forbidden in file ids, the '/', '-', '%' and all whitespace
 characters are urlencoded.
 
+The same rules apply to the roots of branches. This means there is no 
+predefined file id for tree roots.
+
+Alternatively, these file ids can be mapped to more specific file ids. Such 
+a map should be stored in the `bzr:file-ids' property that is set on the 
+branch path.
+
+The bzr:file-ids property should contain a list of mappings. Entries are 
+separated by newlines. The path in the branch and new file-id are separated 
+by a tab.
+
+Given, the path, the revision the mapping was added, the repository uuid 
+and the path the property is set on the (the branch path), the original 
+file id can be determined.  
+
+Tabs, newlines and percent signs in path will be urlencoded.
+
+Neither the original nor the target file id may occur more than once. 
+
+The entries are sorted by revnum (highest revnum last). Within a specific 
+revnum, the order is not specified.
+
 NEXT VERSION: Special rules are applied to make sure that renames are tracked.
 
 == Properties ==
@@ -136,6 +160,8 @@ NEXT VERSION: GPG Signatures for commits will be stored in the SVN revision prop
 
 Revision 1 was the original version of this document.
 
-Revision 2 uses real file ids for the tree root rather than the hardcoded 
-"TREE_ROOT" and enforces UTF-8-valid characters for everything except file 
+Revision 2 enforces UTF-8-valid characters for everything except file 
 contents.
+
+Revision 3 uses real file ids for the tree root rather than the hardcoded 
+"TREE_ROOT" and adds the file id map.
index 8f1b81ae79884d56e4f1e0fd5d79a1e581f9988f..1a044c932705dbae6719c3323f859ef5f41144cf 100644 (file)
@@ -21,11 +21,12 @@ from bzrlib.errors import (BzrError, InvalidRevisionId, NoSuchFile,
                            NoSuchRevision, NotBranchError, 
                            UninitializableFormat)
 from bzrlib.graph import Graph
-from bzrlib.inventory import Inventory, ROOT_ID
+from bzrlib.inventory import Inventory
 from bzrlib.lockable_files import LockableFiles, TransportLock
 import bzrlib.osutils as osutils
 from bzrlib.progress import ProgressBar
 from bzrlib.repository import Repository, RepositoryFormat
+from bzrlib.revisiontree import RevisionTree
 from bzrlib.revision import Revision, NULL_REVISION
 from bzrlib.transport import Transport
 from bzrlib.trace import mutter
@@ -47,13 +48,14 @@ from tree import SvnRevisionTree
 MAPPING_VERSION = 2
 REVISION_ID_PREFIX = "svn-v%d:" % MAPPING_VERSION
 SVN_PROP_BZR_MERGE = 'bzr:merge'
+SVN_PROP_BZR_FILEIDS = 'bzr:file-ids'
 SVN_PROP_SVK_MERGE = 'svk:merge'
 SVN_PROP_BZR_REVPROP_PREFIX = 'bzr:revprop:'
 SVN_REVPROP_BZR_SIGNATURE = 'bzr:gpg-signature'
 
-_unsafe = "%/-\t "
-def escape_svn_path(id):
-    r = [((c in _unsafe) and ('%%%02x' % ord(c)) or c)
+def escape_svn_path(id, unsafe="%/-\t \n"):
+    assert "%" in unsafe
+    r = [((c in unsafe) and ('%%%02x' % ord(c)) or c)
          for c in id]
     return ''.join(r)
 
@@ -256,14 +258,17 @@ class SvnRepository(Repository):
     def get_fileid_map(self, revnum, path, pb=None):
         return self.fileid_map.get_map(self.uuid, revnum, path, pb)
 
-    def transform_fileid_map(self, uuid, revnum, branch, changes, map):
-        return self.fileid_map.apply_changes(uuid, revnum, branch, changes, map)
+    def transform_fileid_map(self, uuid, revnum, branch, changes, map, renames):
+        return self.fileid_map.apply_changes(uuid, revnum, branch, changes, map, renames)
 
     def path_to_file_id(self, revnum, path):
         """Generate a bzr file id from a Subversion file name. 
         
         This implementation DOES NOT track renames.
 
+        Use get_fileid_map() directly instead of calling this function 
+        multiple times if possible.
+
         :param revnum: Revision number.
         :param path: Absolute path.
         :return: Tuple with file id and revision id.
@@ -274,6 +279,10 @@ class SvnRepository(Repository):
 
         path = path.strip("/")
 
+        if revnum == 0:
+            from fileids import generate_svn_file_id
+            return (generate_svn_file_id(self.uuid, 0, "", ""), NULL_REVISION)
+
         (bp, rp) = self.scheme.unprefix(path)
 
         revid = self.generate_revision_id(revnum, bp)
@@ -351,10 +360,18 @@ class SvnRepository(Repository):
             revision_id = NULL_REVISION
 
         if revision_id == NULL_REVISION:
-            inventory = Inventory(ROOT_ID)
+            inventory = Inventory(root_id=None)
+            inventory.revision_id = revision_id
+            return RevisionTree(self, inventory, revision_id)
 
         return SvnRevisionTree(self, revision_id, inventory)
 
+    def revision_fileid_renames(self, revid):
+        (path, revnum) = self.parse_revision_id(revid)
+        items = self._get_branch_prop(path, revnum, 
+                                  SVN_PROP_BZR_FILEIDS, "").splitlines()
+        return dict(map(lambda x: x.split("\t"), items))
+
     def revision_parents(self, revision_id, merged_data=None):
         (path, revnum) = self.parse_revision_id(revision_id)
 
index c45dd6e54ca9053ccde9b2a623fb7d02c4f5895c..fb30f43dd90fed07d124d3e029631655653b48a1 100644 (file)
@@ -14,8 +14,8 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from bzrlib.branch import BranchReferenceFormat
-from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
+from bzrlib.branch import Branch, BranchReferenceFormat
+from bzrlib.bzrdir import BzrDir, BzrDirFormat
 from bzrlib.errors import DivergedBranches
 from bzrlib.inventory import Inventory
 from bzrlib.workingtree import WorkingTree
@@ -23,6 +23,7 @@ from bzrlib.workingtree import WorkingTree
 import os
 import format
 import checkout
+import svn.core
 from repository import MAPPING_VERSION
 from tests import TestCaseWithSubversionRepository
 
@@ -90,7 +91,7 @@ class TestCommitFromBazaar(TestCaseWithSubversionRepository):
         self.repos_url = self.make_repository('d')
         source = BzrDir.open("svn+"+self.repos_url)
         os.mkdir('dc')
-        self.checkout = BzrDirMetaFormat1().initialize('dc')
+        self.checkout = BzrDirFormat.get_default_format().initialize('dc')
         BranchReferenceFormat().initialize(self.checkout, source.open_branch())
 
     def test_simple_commit(self):
@@ -142,6 +143,8 @@ class TestCommitFromBazaar(TestCaseWithSubversionRepository):
         wt.commit(message="data")
         wt.add('foo')
         wt.add('foo/bla')
+        self.assertTrue(wt.inventory.has_filename("bla"))
+        self.assertTrue(wt.inventory.has_filename("foo/bla"))
         wt.set_pending_merges(["some-ghost-revision"])
         wt.commit(message="data")
         self.assertEqual([
@@ -151,6 +154,34 @@ class TestCommitFromBazaar(TestCaseWithSubversionRepository):
         self.assertEqual("some-ghost-revision\n", 
                 self.client_get_prop(self.repos_url, "bzr:merge", 2))
 
+    def test_commit_fileids(self):
+        wt = self.checkout.create_workingtree()
+        self.build_tree({'dc/file': 'data'})
+        wt.add('file')
+        wt.commit(message="Commit from Bzr")
+        self.assertEqual("\t%s\nfile\t%s\n" % (wt.inventory.root.file_id, wt.inventory.path2id("file")), 
+                self.client_get_prop(self.repos_url, "bzr:file-ids", 1))
+
+    def test_commit_fileids_added(self):
+    
+        rev = svn.core.svn_opt_revision_t()
+        rev.kind = svn.core.svn_opt_revision_head
+
+        svn.client.checkout2(self.repos_url, "db", 
+                rev, rev, True, False, self.client_ctx)
+
+        self.build_tree({'dc/file1': 'data', 'db/file2': "otherdata"})
+        self.client_add("db/file2")
+        self.client_commit("db", "amesg")
+        branch = Branch.open(self.repos_url)
+        inv = branch.repository.get_inventory(branch.last_revision())
+        wt = self.checkout.create_workingtree()
+        self.assertEqual(wt.inventory.root.file_id, inv.root.file_id)
+        wt.add('file1')
+        wt.commit(message="Commit from Bzr")
+        self.assertEqual("file1\t%s\n" % wt.inventory.path2id("file1"), 
+                self.client_get_prop(self.repos_url, "bzr:file-ids", 2))
+
     def test_commit_branchnick(self):
         wt = self.checkout.create_workingtree()
         self.build_tree({'dc/foo/bla': "data", 'dc/bla': "otherdata"})
@@ -298,3 +329,4 @@ class TestPush(TestCaseWithSubversionRepository):
 
         self.assertTrue(wt.branch.last_revision() in 
              repos.get_ancestry("svn-v%d:3@%s-" % (MAPPING_VERSION, repos.uuid)))
+
index 660845400dcf250fe2a2dabc14efc62391c52e04..8a3530423a0bbe055dae8b8cd0a23e3433f740c7 100644 (file)
@@ -148,18 +148,20 @@ class TestComplexFileids(TestCaseWithSubversionRepository):
                 revid)
 
 class TestFileMapping(TestCase):
-    def apply_mappings(self, mappings, find_children=None):
-        map = {}
+    def apply_mappings(self, mappings, find_children=None, renames={}):
+        map = {"": ("ROOT", "first-revision") }
         revids = mappings.keys()
         revids.sort()
         for r in revids:
+            if not renames.has_key(r):
+                renames[r] = {}
             map = SimpleFileIdMap._apply_changes(map, r, mappings[r], 
-                                                 find_children)
+                                                 find_children, renames=renames[r])
         return map
 
     def test_simple(self):
         map = self.apply_mappings({"svn-v%d:1@uuid-" % MAPPING_VERSION: {"foo": ('A', None, None)}})
-        self.assertEqual({'': ('TREE_ROOT', "svn-v%d:1@uuid-" % MAPPING_VERSION), 
+        self.assertEqual({'': ('ROOT', "first-revision"),
                                'foo': ("svn-v%d:1@uuid--foo" % MAPPING_VERSION, 
                                        "svn-v%d:1@uuid-" % MAPPING_VERSION)
                          }, map)
@@ -190,3 +192,25 @@ class TestFileMapping(TestCase):
                                    "foo/bla": ('M', None, None)}
                 })
         self.assertEqual("svn-v%d:2@uuid-" % MAPPING_VERSION, map["foo"][1])
+
+    def test_usemap(self):
+        map = self.apply_mappings(
+                {("svn-v%d:1@uuid-" % MAPPING_VERSION): {
+                                   "foo": ('A', None, None), 
+                                   "foo/bla": ('A', None, None)},
+                 ("svn-v%d:2@uuid-" % MAPPING_VERSION): {
+                                   "foo/bla": ('M', None, None)}
+                 }, 
+                renames={("svn-v%d:1@uuid-" % MAPPING_VERSION): {"foo": "myid"}})
+        self.assertEqual("myid", map["foo"][0])
+
+    def test_usemap_later(self):
+        map = self.apply_mappings(
+                {("svn-v%d:1@uuid-" % MAPPING_VERSION): {
+                                   "foo": ('A', None, None), 
+                                   "foo/bla": ('A', None, None)},
+                 ("svn-v%d:2@uuid-" % MAPPING_VERSION): {
+                                   "foo/bla": ('M', None, None)}
+                 }, 
+                renames={("svn-v%d:2@uuid-" % MAPPING_VERSION): {"foo": "myid"}})
+        self.assertEqual("svn-v%d:1@uuid--foo" % MAPPING_VERSION, map["foo"][0])
index 463664a24b10b23fcf6c573fd83f5659df5fc7d4..19139ad1724de1ac3c8c9898764b3c480756d886 100644 (file)
@@ -14,6 +14,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+from bzrlib.branch import Branch
 from bzrlib.bzrdir import BzrDir
 from bzrlib.errors import NoSuchRevision
 from bzrlib.inventory import Inventory
@@ -89,6 +90,17 @@ class TestSubversionRepositoryWorks(TestCaseWithSubversionRepository):
             repository.revision_parents(
                 "svn-v%d:2@%s-" % (MAPPING_VERSION, repository.uuid)))
 
+    def test_revision_fileidmap(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_set_prop("dc", "bzr:file-ids", "foo\tsomeid\n")
+        self.client_commit("dc", "My Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        tree = repository.revision_tree(Branch.open(repos_url).last_revision())
+        self.assertEqual("someid", tree.inventory.path2id("foo"))
+        self.assertFalse("svn-v2:1@%s--foo" % repository.uuid in tree.inventory)
+
     def test_revision_ghost_parents(self):
         repos_url = self.make_client('d', 'dc')
         self.build_tree({'dc/foo': "data"})
@@ -335,6 +347,17 @@ class TestSubversionRepositoryWorks(TestCaseWithSubversionRepository):
         repository = Repository.open("svn+%s" % repos_url)
         self.assertTrue(repository.is_shared())
 
+    def test_revision_fileid_renames(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/test': "data"})
+        self.client_add("dc/test")
+        self.client_set_prop("dc", "bzr:file-ids", "test\tbla\n")
+        self.client_commit("dc", "Msg")
+
+        repos = Repository.open(repos_url)
+        renames = repos.revision_fileid_renames("svn-v%d:1@%s-" % (MAPPING_VERSION, repos.uuid))
+        self.assertEqual({"test": "bla"}, renames)
+
     def test_fetch_local(self):
         repos_url = self.make_client('d', 'dc')
         self.build_tree({'dc/foo/bla': "data"})
@@ -683,6 +706,7 @@ class TestSvnRevisionTree(TestCaseWithSubversionRepository):
 
         self.assertTrue(inventory[inventory.path2id("foo/bla")].executable)
 
+
     def test_symlink(self):
         os.symlink('foo/bla', 'dc/bar')
         self.client_add('dc/bar')
index 243a965e9367e90553780113aab18fe81d384886..104830003ecf48f58210d04f5add486a6886d79e 100644 (file)
@@ -120,7 +120,7 @@ class SvnRaTest(TestCaseWithSubversionRepository):
 
         t = SvnRaTransport(repos_url)
         self.assertEqual("%s/dir" % repos_url, t.clone('dir').base)
-        
+
     def test_get_root(self):
         repos_url = self.make_client('d', 'dc')
         self.build_tree({"dc/dir": None, "dc/bl": "data"})
index 960742a1cc82f5d0a151f623f5807ea8bb406b3b..031874e03faec8660e26fa935d6398bc1d49bf9b 100644 (file)
@@ -17,7 +17,7 @@
 from bzrlib.branch import Branch
 from bzrlib.bzrdir import BzrDir
 from bzrlib.errors import NoSuchRevision, NoSuchFile
-from bzrlib.inventory import Inventory, ROOT_ID
+from bzrlib.inventory import Inventory
 from bzrlib.revision import NULL_REVISION
 from bzrlib.trace import mutter
 from bzrlib.workingtree import WorkingTree
@@ -117,7 +117,7 @@ class TestWorkingTree(TestCaseWithSubversionRepository):
         self.make_client('a', 'dc')
         wt = WorkingTree.open("dc")
         self.assertEqual(NULL_REVISION, wt.basis_tree().inventory.revision_id)
-        self.assertEqual(Inventory(), wt.basis_tree().inventory)
+        self.assertEqual(Inventory(root_id=None), wt.basis_tree().inventory)
 
     def test_basis_tree(self):
         self.make_client('a', 'dc')
@@ -183,7 +183,7 @@ class TestWorkingTree(TestCaseWithSubversionRepository):
         self.client_add("dc/test")
         tree = WorkingTree.open("dc")
         inv = tree.read_working_inventory()
-        self.assertEqual(ROOT_ID, inv.path2id(""))
+        self.assertEqual(inv.path2id(""), inv.root.file_id)
         self.assertTrue(inv.path2id("foo") != "")
         self.assertTrue(inv.has_filename("bl"))
         self.assertTrue(inv.has_filename("foo"))
diff --git a/tree.py b/tree.py
index eff0c3b2caeba0b144c2331751d5094cd23f5875..975864d32251effa058a5d4bcc68018b61349561 100644 (file)
--- a/tree.py
+++ b/tree.py
@@ -17,8 +17,7 @@
 from binascii import hexlify
 from bzrlib.bzrdir import BzrDirFormat
 from bzrlib.errors import NotBranchError, NoSuchFile
-from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile,
-                              ROOT_ID)
+from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile)
 from bzrlib.lockable_files import TransportLock, LockableFiles
 from bzrlib.lockdir import LockDir
 import bzrlib.osutils as osutils
@@ -55,28 +54,24 @@ class SvnRevisionTree(RevisionTree):
      def __init__(self, repository, revision_id, inventory=None):
         self._repository = repository
         self._revision_id = revision_id
-        if revision_id == NULL_REVISION:
-            self._inventory = Inventory(ROOT_ID)
-            self._inventory.revision_id = NULL_REVISION
-        else:
-            (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
-            self._inventory = Inventory(ROOT_ID)
-            self._inventory.revision_id = revision_id
-            self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
-            self.editor = TreeBuildEditor(self)
-            self.file_data = {}
+        (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
+        self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
+        self._inventory = Inventory(root_id=self.id_map[""][0])
+        self._inventory.revision_id = revision_id
+        self.editor = TreeBuildEditor(self)
+        self.file_data = {}
 
-            editor, baton = svn.delta.make_editor(self.editor)
+        editor, baton = svn.delta.make_editor(self.editor)
 
-            root_repos = repository.transport.get_repos_root()
-            mutter('svn checkout -r %r %r' % (self.revnum, self.branch_path))
-            reporter, reporter_baton = repository.transport.do_switch(
-                    self.revnum, "", True, 
-                    os.path.join(root_repos, self.branch_path), editor, baton)
+        root_repos = repository.transport.get_repos_root()
+        mutter('svn checkout -r %r %r' % (self.revnum, self.branch_path))
+        reporter, reporter_baton = repository.transport.do_switch(
+                self.revnum, "", True, 
+                os.path.join(root_repos, self.branch_path), editor, baton)
 
-            svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
+        svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
 
-            svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
+        svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
 
      def get_file_lines(self, file_id):
         return osutils.split_lines(self.file_data[file_id])
@@ -94,7 +89,7 @@ class TreeBuildEditor(svn.delta.Editor):
         self.revnum = revnum
 
     def open_root(self, revnum, baton):
-        return ROOT_ID
+        return self.tree.id_map[""][0]
 
     def relpath(self, path):
         bp, rp = self.tree._repository.scheme.unprefix(path)
@@ -118,7 +113,7 @@ class TreeBuildEditor(svn.delta.Editor):
         elif name == svn.core.SVN_PROP_IGNORE:
             self.dir_ignores[id] = value
         elif name == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
-            if id != ROOT_ID:
+            if id != self.tree.id_map[""][0]:
                 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
                 return
         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,