Rev 4385: (abentley) Extract BaseTreeTransform disk code into DiskTreeTransform. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu May 28 09:53:48 BST 2009


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 4385
revision-id: pqm at pqm.ubuntu.com-20090528085340-bfw8729wfm9kmfmd
parent: pqm at pqm.ubuntu.com-20090527193433-rw9zh1l73zjc9knb
parent: aaron at aaronbentley.com-20090528074653-3amj8yr8m5c3yz6o
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2009-05-28 09:53:40 +0100
message:
  (abentley) Extract BaseTreeTransform disk code into DiskTreeTransform.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
    ------------------------------------------------------------
    revno: 4354.5.4
    revision-id: aaron at aaronbentley.com-20090528074653-3amj8yr8m5c3yz6o
    parent: aaron at aaronbentley.com-20090528073625-vzt9289civ2fcwo6
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: disk-transform
    timestamp: Thu 2009-05-28 09:46:53 +0200
    message:
      Update docs
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
    ------------------------------------------------------------
    revno: 4354.5.3
    revision-id: aaron at aaronbentley.com-20090528073625-vzt9289civ2fcwo6
    parent: aaron at aaronbentley.com-20090515210050-d1sp93gylf05scaf
    parent: pqm at pqm.ubuntu.com-20090527193433-rw9zh1l73zjc9knb
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: disk-transform
    timestamp: Thu 2009-05-28 09:36:25 +0200
    message:
      Merge bzr.dev into disk-transform.
    added:
      bzrlib/_rio_py.py              _rio_py.py-20090514104624-ied3d39oju8anmfz-1
      bzrlib/_rio_pyx.pyx            _rio_pyx.pyx-20090514104636-8203jcqvfny56yrd-1
      bzrlib/tests/test__rio.py      test__rio.py-20090514191748-cy74k8yj46gzoeq6-1
    modified:
      .bzrignore                     bzrignore-20050311232317-81f7b71efa2db11a
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzr                            bzr.py-20050313053754-5485f144c7006fa6
      bzrlib/__init__.py             __init__.py-20050309040759-33e65acf91bbcd5d
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/chk_map.py              chk_map.py-20081001014447-ue6kkuhofvdecvxa-1
      bzrlib/commands.py             bzr.py-20050309040720-d10f4714595cf8c3
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
      bzrlib/foreign.py              foreign.py-20081112170002-olsxmandkk8qyfuq-1
      bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
      bzrlib/groupcompress.py        groupcompress.py-20080705181503-ccbxd6xuy1bdnrpu-8
      bzrlib/help_topics/en/eol.txt  eol.txt-20090327060429-todzdjmqt3bpv5r8-3
      bzrlib/knit.py                 knit.py-20051212171256-f056ac8f0fbe1bd9
      bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
      bzrlib/repofmt/groupcompress_repo.py repofmt.py-20080715094215-wp1qfvoo7093c8qr-1
      bzrlib/revisiontree.py         revisiontree.py-20060724012533-bg8xyryhxd0o0i0h-1
      bzrlib/rio.py                  rio.py-20051128032247-770b120b34dfff60
      bzrlib/smart/server.py         server.py-20061110062051-chzu10y32vx8gvur-1
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/blackbox/test_commit.py test_commit.py-20060212094538-ae88fc861d969db0
      bzrlib/tests/blackbox/test_dpush.py test_dpush.py-20090108125928-st1td6le59g0vyv2-1
      bzrlib/tests/blackbox/test_log.py test_log.py-20060112090212-78f6ea560c868e24
      bzrlib/tests/blackbox/test_send.py test_bundle.py-20060616222707-c21c8b7ea5ef57b1
      bzrlib/tests/blackbox/test_serve.py test_serve.py-20060913064329-8t2pvmsikl4s3xhl-1
      bzrlib/tests/blackbox/test_switch.py test_switch.py-20071122111948-0c5en6uz92bwl76h-1
      bzrlib/tests/branch_implementations/test_push.py test_push.py-20070130153159-fhfap8uoifevg30j-1
      bzrlib/tests/per_repository/test_fetch.py test_fetch.py-20070814052151-5cxha9slx4c93uog-1
      bzrlib/tests/test_chk_map.py   test_chk_map.py-20081001014447-ue6kkuhofvdecvxa-2
      bzrlib/tests/test_foreign.py   test_foreign.py-20081125004048-ywb901edgp9lluxo-1
      bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
      bzrlib/tests/test_osutils.py   test_osutils.py-20051201224856-e48ee24c12182989
      bzrlib/tests/test_pack_repository.py test_pack_repository-20080801043947-eaw0e6h2gu75kwmy-1
      bzrlib/tests/test_transform.py test_transaction.py-20060105172520-b3ffb3946550e6c4
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
      bzrlib/transport/__init__.py   transport.py-20050711165921-4978aa7ce1285ad5
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
      bzrlib/win32utils.py           win32console.py-20051021033308-123c6c929d04973d
      bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
      bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
      bzrlib/xml_serializer.py       xml.py-20050309040759-57d51586fdec365d
      doc/en/user-guide/svn_plugin.txt svn_plugin.txt-20080509065016-cjc90f46407vi9a0-2
      setup.py                       setup.py-20050314065409-02f8a0a6e3f9bc70
    ------------------------------------------------------------
    revno: 4354.5.2
    revision-id: aaron at aaronbentley.com-20090515210050-d1sp93gylf05scaf
    parent: aaron at aaronbentley.com-20090515203115-45vb6dp9lm5m6k9l
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: disk-transform
    timestamp: Fri 2009-05-15 17:00:50 -0400
    message:
      Move WorkingTree-specific code out of BaseTreeTransform
    modified:
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
    ------------------------------------------------------------
    revno: 4354.5.1
    revision-id: aaron at aaronbentley.com-20090515203115-45vb6dp9lm5m6k9l
    parent: pqm at pqm.ubuntu.com-20090512090530-v9355ohetp61ltc1
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: disk-transform
    timestamp: Fri 2009-05-15 16:31:15 -0400
    message:
      Split out a DiskTreeTransform class that manages Limbo.
    modified:
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
=== modified file 'NEWS'
--- a/NEWS	2009-05-23 04:55:52 +0000
+++ b/NEWS	2009-05-28 07:46:53 +0000
@@ -34,6 +34,10 @@
 
 * Added osutils.parent_directories(). (Ian Clatworthy)
 
+* TreeTransformBase no longer assumes that limbo is provided via disk.
+  DiskTreeTransform now provides disk functionality.  (Aaron Bentley)
+
+
 Internals
 *********
 

=== modified file 'bzrlib/transform.py'
--- a/bzrlib/transform.py	2009-05-23 04:55:52 +0000
+++ b/bzrlib/transform.py	2009-05-28 07:46:53 +0000
@@ -76,24 +76,20 @@
 
 
 class TreeTransformBase(object):
-    """The base class for TreeTransform and TreeTransformBase"""
+    """The base class for TreeTransform and its kin."""
 
-    def __init__(self, tree, limbodir, pb=DummyProgress(),
+    def __init__(self, tree, pb=DummyProgress(),
                  case_sensitive=True):
         """Constructor.
 
         :param tree: The tree that will be transformed, but not necessarily
             the output tree.
-        :param limbodir: A directory where new files can be stored until
-            they are installed in their proper places
         :param pb: A ProgressBar indicating how much progress is being made
         :param case_sensitive: If True, the target of the transform is
             case sensitive, not just case preserving.
         """
         object.__init__(self)
         self._tree = tree
-        self._limbodir = limbodir
-        self._deletiondir = None
         self._id_number = 0
         # mapping of trans_id -> new basename
         self._new_name = {}
@@ -101,15 +97,6 @@
         self._new_parent = {}
         # mapping of trans_id with new contents -> new file_kind
         self._new_contents = {}
-        # A mapping of transform ids to their limbo filename
-        self._limbo_files = {}
-        # A mapping of transform ids to a set of the transform ids of children
-        # that their limbo directory has
-        self._limbo_children = {}
-        # Map transform ids to maps of child filename to child transform id
-        self._limbo_children_names = {}
-        # List of transform ids that need to be renamed from limbo into place
-        self._needs_rename = set()
         # Set of trans_ids whose contents will be removed
         self._removed_contents = set()
         # Mapping of trans_id -> new execute-bit value
@@ -128,10 +115,6 @@
         self._tree_path_ids = {}
         # Mapping trans_id -> path in old tree
         self._tree_id_paths = {}
-        # Cache of realpath results, to speed up canonical_path
-        self._realpaths = {}
-        # Cache of relpath results, to speed up canonical_path
-        self._relpaths = {}
         # The trans_id that will be used as the tree root
         root_id = tree.get_root_id()
         if root_id is not None:
@@ -147,42 +130,22 @@
         # A counter of how many files have been renamed
         self.rename_count = 0
 
+    def finalize(self):
+        """Release the working tree lock, if held.
+
+        This is required if apply has not been invoked, but can be invoked
+        even after apply.
+        """
+        if self._tree is None:
+            return
+        self._tree.unlock()
+        self._tree = None
+
     def __get_root(self):
         return self._new_root
 
     root = property(__get_root)
 
-    def finalize(self):
-        """Release the working tree lock, if held, clean up limbo dir.
-
-        This is required if apply has not been invoked, but can be invoked
-        even after apply.
-        """
-        if self._tree is None:
-            return
-        try:
-            entries = [(self._limbo_name(t), t, k) for t, k in
-                       self._new_contents.iteritems()]
-            entries.sort(reverse=True)
-            for path, trans_id, kind in entries:
-                if kind == "directory":
-                    os.rmdir(path)
-                else:
-                    os.unlink(path)
-            try:
-                os.rmdir(self._limbodir)
-            except OSError:
-                # We don't especially care *why* the dir is immortal.
-                raise ImmortalLimbo(self._limbodir)
-            try:
-                if self._deletiondir is not None:
-                    os.rmdir(self._deletiondir)
-            except OSError:
-                raise errors.ImmortalPendingDeletion(self._deletiondir)
-        finally:
-            self._tree.unlock()
-            self._tree = None
-
     def _assign_id(self):
         """Produce a new tranform id"""
         new_id = "new-%s" % self._id_number
@@ -200,37 +163,12 @@
         """Change the path that is assigned to a transaction id."""
         if trans_id == self._new_root:
             raise CantMoveRoot
-        previous_parent = self._new_parent.get(trans_id)
-        previous_name = self._new_name.get(trans_id)
         self._new_name[trans_id] = name
         self._new_parent[trans_id] = parent
         if parent == ROOT_PARENT:
             if self._new_root is not None:
                 raise ValueError("Cannot have multiple roots.")
             self._new_root = trans_id
-        if (trans_id in self._limbo_files and
-            trans_id not in self._needs_rename):
-            self._rename_in_limbo([trans_id])
-            self._limbo_children[previous_parent].remove(trans_id)
-            del self._limbo_children_names[previous_parent][previous_name]
-
-    def _rename_in_limbo(self, trans_ids):
-        """Fix limbo names so that the right final path is produced.
-
-        This means we outsmarted ourselves-- we tried to avoid renaming
-        these files later by creating them with their final names in their
-        final parents.  But now the previous name or parent is no longer
-        suitable, so we have to rename them.
-
-        Even for trans_ids that have no new contents, we must remove their
-        entries from _limbo_files, because they are now stale.
-        """
-        for trans_id in trans_ids:
-            old_path = self._limbo_files.pop(trans_id)
-            if trans_id not in self._new_contents:
-                continue
-            new_path = self._limbo_name(trans_id)
-            os.rename(old_path, new_path)
 
     def adjust_root_path(self, name, parent):
         """Emulate moving the root by moving all children, instead.
@@ -298,25 +236,6 @@
             else:
                 return self.trans_id_tree_file_id(file_id)
 
-    def canonical_path(self, path):
-        """Get the canonical tree-relative path"""
-        # don't follow final symlinks
-        abs = self._tree.abspath(path)
-        if abs in self._relpaths:
-            return self._relpaths[abs]
-        dirname, basename = os.path.split(abs)
-        if dirname not in self._realpaths:
-            self._realpaths[dirname] = os.path.realpath(dirname)
-        dirname = self._realpaths[dirname]
-        abs = pathjoin(dirname, basename)
-        if dirname in self._relpaths:
-            relpath = pathjoin(self._relpaths[dirname], basename)
-            relpath = relpath.rstrip('/\\')
-        else:
-            relpath = self._tree.relpath(abs)
-        self._relpaths[abs] = relpath
-        return relpath
-
     def trans_id_tree_path(self, path):
         """Determine (and maybe set) the transaction ID for a tree path."""
         path = self.canonical_path(path)
@@ -332,113 +251,6 @@
             return ROOT_PARENT
         return self.trans_id_tree_path(os.path.dirname(path))
 
-    def create_file(self, contents, trans_id, mode_id=None):
-        """Schedule creation of a new file.
-
-        See also new_file.
-
-        Contents is an iterator of strings, all of which will be written
-        to the target destination.
-
-        New file takes the permissions of any existing file with that id,
-        unless mode_id is specified.
-        """
-        name = self._limbo_name(trans_id)
-        f = open(name, 'wb')
-        try:
-            try:
-                unique_add(self._new_contents, trans_id, 'file')
-            except:
-                # Clean up the file, it never got registered so
-                # TreeTransform.finalize() won't clean it up.
-                f.close()
-                os.unlink(name)
-                raise
-
-            f.writelines(contents)
-        finally:
-            f.close()
-        self._set_mode(trans_id, mode_id, S_ISREG)
-
-    def _set_mode(self, trans_id, mode_id, typefunc):
-        """Set the mode of new file contents.
-        The mode_id is the existing file to get the mode from (often the same
-        as trans_id).  The operation is only performed if there's a mode match
-        according to typefunc.
-        """
-        if mode_id is None:
-            mode_id = trans_id
-        try:
-            old_path = self._tree_id_paths[mode_id]
-        except KeyError:
-            return
-        try:
-            mode = os.stat(self._tree.abspath(old_path)).st_mode
-        except OSError, e:
-            if e.errno in (errno.ENOENT, errno.ENOTDIR):
-                # Either old_path doesn't exist, or the parent of the
-                # target is not a directory (but will be one eventually)
-                # Either way, we know it doesn't exist *right now*
-                # See also bug #248448
-                return
-            else:
-                raise
-        if typefunc(mode):
-            os.chmod(self._limbo_name(trans_id), mode)
-
-    def create_hardlink(self, path, trans_id):
-        """Schedule creation of a hard link"""
-        name = self._limbo_name(trans_id)
-        try:
-            os.link(path, name)
-        except OSError, e:
-            if e.errno != errno.EPERM:
-                raise
-            raise errors.HardLinkNotSupported(path)
-        try:
-            unique_add(self._new_contents, trans_id, 'file')
-        except:
-            # Clean up the file, it never got registered so
-            # TreeTransform.finalize() won't clean it up.
-            os.unlink(name)
-            raise
-
-    def create_directory(self, trans_id):
-        """Schedule creation of a new directory.
-
-        See also new_directory.
-        """
-        os.mkdir(self._limbo_name(trans_id))
-        unique_add(self._new_contents, trans_id, 'directory')
-
-    def create_symlink(self, target, trans_id):
-        """Schedule creation of a new symbolic link.
-
-        target is a bytestring.
-        See also new_symlink.
-        """
-        if has_symlinks():
-            os.symlink(target, self._limbo_name(trans_id))
-            unique_add(self._new_contents, trans_id, 'symlink')
-        else:
-            try:
-                path = FinalPaths(self).get_path(trans_id)
-            except KeyError:
-                path = None
-            raise UnableCreateSymlink(path=path)
-
-    def cancel_creation(self, trans_id):
-        """Cancel the creation of new file contents."""
-        del self._new_contents[trans_id]
-        children = self._limbo_children.get(trans_id)
-        # if this is a limbo directory with children, move them before removing
-        # the directory
-        if children is not None:
-            self._rename_in_limbo(children)
-            del self._limbo_children[trans_id]
-            del self._limbo_children_names[trans_id]
-        delete_any(self._limbo_name(trans_id))
-
     def delete_contents(self, trans_id):
         """Schedule the contents of a path entry for deletion"""
         self.tree_kind(trans_id)
@@ -518,22 +330,6 @@
         new_ids.update(changed_kind)
         return sorted(FinalPaths(self).get_paths(new_ids))
 
-    def tree_kind(self, trans_id):
-        """Determine the file kind in the working tree.
-
-        Raises NoSuchFile if the file does not exist
-        """
-        path = self._tree_id_paths.get(trans_id)
-        if path is None:
-            raise NoSuchFile(None)
-        try:
-            return file_kind(self._tree.abspath(path))
-        except OSError, e:
-            if e.errno != errno.ENOENT:
-                raise
-            else:
-                raise NoSuchFile(path)
-
     def final_kind(self, trans_id):
         """Determine the final file kind, after any changes applied.
 
@@ -667,26 +463,6 @@
             # ensure that all children are registered with the transaction
             list(self.iter_tree_children(parent_id))
 
-    def iter_tree_children(self, parent_id):
-        """Iterate through the entry's tree children, if any"""
-        try:
-            path = self._tree_id_paths[parent_id]
-        except KeyError:
-            return
-        try:
-            children = os.listdir(self._tree.abspath(path))
-        except OSError, e:
-            if not (osutils._is_error_enotdir(e)
-                    or e.errno in (errno.ENOENT, errno.ESRCH)):
-                raise
-            return
-
-        for child in children:
-            childpath = joinpath(path, child)
-            if self._tree.is_control_filename(childpath):
-                continue
-            yield self.trans_id_tree_path(childpath)
-
     def has_named_child(self, by_parent, parent_id, name):
         try:
             children = by_parent[parent_id]
@@ -867,50 +643,6 @@
             return True
         return False
 
-    def _limbo_name(self, trans_id):
-        """Generate the limbo name of a file"""
-        limbo_name = self._limbo_files.get(trans_id)
-        if limbo_name is not None:
-            return limbo_name
-        parent = self._new_parent.get(trans_id)
-        # if the parent directory is already in limbo (e.g. when building a
-        # tree), choose a limbo name inside the parent, to reduce further
-        # renames.
-        use_direct_path = False
-        if self._new_contents.get(parent) == 'directory':
-            filename = self._new_name.get(trans_id)
-            if filename is not None:
-                if parent not in self._limbo_children:
-                    self._limbo_children[parent] = set()
-                    self._limbo_children_names[parent] = {}
-                    use_direct_path = True
-                # the direct path can only be used if no other file has
-                # already taken this pathname, i.e. if the name is unused, or
-                # if it is already associated with this trans_id.
-                elif self._case_sensitive_target:
-                    if (self._limbo_children_names[parent].get(filename)
-                        in (trans_id, None)):
-                        use_direct_path = True
-                else:
-                    for l_filename, l_trans_id in\
-                        self._limbo_children_names[parent].iteritems():
-                        if l_trans_id == trans_id:
-                            continue
-                        if l_filename.lower() == filename.lower():
-                            break
-                    else:
-                        use_direct_path = True
-
-        if use_direct_path:
-            limbo_name = pathjoin(self._limbo_files[parent], filename)
-            self._limbo_children[parent].add(trans_id)
-            self._limbo_children_names[parent][filename] = trans_id
-        else:
-            limbo_name = pathjoin(self._limbodir, trans_id)
-            self._needs_rename.add(trans_id)
-        self._limbo_files[trans_id] = limbo_name
-        return limbo_name
-
     def _set_executability(self, path, trans_id):
         """Set the executability of versioned files """
         if supports_executable():
@@ -1176,21 +908,17 @@
                                       (('attribs',),))
         for trans_id, kind in self._new_contents.items():
             if kind == 'file':
-                cur_file = open(self._limbo_name(trans_id), 'rb')
-                try:
-                    lines = osutils.chunks_to_lines(cur_file.readlines())
-                finally:
-                    cur_file.close()
+                lines = osutils.chunks_to_lines(
+                    self._read_file_chunks(trans_id))
                 parents = self._get_parents_lines(trans_id)
                 mpdiff = multiparent.MultiParent.from_lines(lines, parents)
                 content = ''.join(mpdiff.to_patch())
             if kind == 'directory':
                 content = ''
             if kind == 'symlink':
-                content = os.readlink(self._limbo_name(trans_id))
+                content = self._read_symlink_target(trans_id)
             yield serializer.bytes_record(content, ((trans_id, kind),))
 
-
     def deserialize(self, records):
         """Deserialize a stored TreeTransform.
 
@@ -1227,7 +955,228 @@
                 self.create_symlink(content.decode('utf-8'), trans_id)
 
 
-class TreeTransform(TreeTransformBase):
+class DiskTreeTransform(TreeTransformBase):
+    """Tree transform storing its contents on disk."""
+
+    def __init__(self, tree, limbodir, pb=DummyProgress(),
+                 case_sensitive=True):
+        """Constructor.
+        :param tree: The tree that will be transformed, but not necessarily
+            the output tree.
+        :param limbodir: A directory where new files can be stored until
+            they are installed in their proper places
+        :param pb: A ProgressBar indicating how much progress is being made
+        :param case_sensitive: If True, the target of the transform is
+            case sensitive, not just case preserving.
+        """
+        TreeTransformBase.__init__(self, tree, pb, case_sensitive)
+        self._limbodir = limbodir
+        self._deletiondir = None
+        # A mapping of transform ids to their limbo filename
+        self._limbo_files = {}
+        # A mapping of transform ids to a set of the transform ids of children
+        # that their limbo directory has
+        self._limbo_children = {}
+        # Map transform ids to maps of child filename to child transform id
+        self._limbo_children_names = {}
+        # List of transform ids that need to be renamed from limbo into place
+        self._needs_rename = set()
+
+    def finalize(self):
+        """Release the working tree lock, if held, clean up limbo dir.
+
+        This is required if apply has not been invoked, but can be invoked
+        even after apply.
+        """
+        if self._tree is None:
+            return
+        try:
+            entries = [(self._limbo_name(t), t, k) for t, k in
+                       self._new_contents.iteritems()]
+            entries.sort(reverse=True)
+            for path, trans_id, kind in entries:
+                if kind == "directory":
+                    os.rmdir(path)
+                else:
+                    os.unlink(path)
+            try:
+                os.rmdir(self._limbodir)
+            except OSError:
+                # We don't especially care *why* the dir is immortal.
+                raise ImmortalLimbo(self._limbodir)
+            try:
+                if self._deletiondir is not None:
+                    os.rmdir(self._deletiondir)
+            except OSError:
+                raise errors.ImmortalPendingDeletion(self._deletiondir)
+        finally:
+            TreeTransformBase.finalize(self)
+
+    def _limbo_name(self, trans_id):
+        """Generate the limbo name of a file"""
+        limbo_name = self._limbo_files.get(trans_id)
+        if limbo_name is not None:
+            return limbo_name
+        parent = self._new_parent.get(trans_id)
+        # if the parent directory is already in limbo (e.g. when building a
+        # tree), choose a limbo name inside the parent, to reduce further
+        # renames.
+        use_direct_path = False
+        if self._new_contents.get(parent) == 'directory':
+            filename = self._new_name.get(trans_id)
+            if filename is not None:
+                if parent not in self._limbo_children:
+                    self._limbo_children[parent] = set()
+                    self._limbo_children_names[parent] = {}
+                    use_direct_path = True
+                # the direct path can only be used if no other file has
+                # already taken this pathname, i.e. if the name is unused, or
+                # if it is already associated with this trans_id.
+                elif self._case_sensitive_target:
+                    if (self._limbo_children_names[parent].get(filename)
+                        in (trans_id, None)):
+                        use_direct_path = True
+                else:
+                    for l_filename, l_trans_id in\
+                        self._limbo_children_names[parent].iteritems():
+                        if l_trans_id == trans_id:
+                            continue
+                        if l_filename.lower() == filename.lower():
+                            break
+                    else:
+                        use_direct_path = True
+
+        if use_direct_path:
+            limbo_name = pathjoin(self._limbo_files[parent], filename)
+            self._limbo_children[parent].add(trans_id)
+            self._limbo_children_names[parent][filename] = trans_id
+        else:
+            limbo_name = pathjoin(self._limbodir, trans_id)
+            self._needs_rename.add(trans_id)
+        self._limbo_files[trans_id] = limbo_name
+        return limbo_name
+
+    def adjust_path(self, name, parent, trans_id):
+        previous_parent = self._new_parent.get(trans_id)
+        previous_name = self._new_name.get(trans_id)
+        TreeTransformBase.adjust_path(self, name, parent, trans_id)
+        if (trans_id in self._limbo_files and
+            trans_id not in self._needs_rename):
+            self._rename_in_limbo([trans_id])
+            self._limbo_children[previous_parent].remove(trans_id)
+            del self._limbo_children_names[previous_parent][previous_name]
+
+    def _rename_in_limbo(self, trans_ids):
+        """Fix limbo names so that the right final path is produced.
+
+        This means we outsmarted ourselves-- we tried to avoid renaming
+        these files later by creating them with their final names in their
+        final parents.  But now the previous name or parent is no longer
+        suitable, so we have to rename them.
+
+        Even for trans_ids that have no new contents, we must remove their
+        entries from _limbo_files, because they are now stale.
+        """
+        for trans_id in trans_ids:
+            old_path = self._limbo_files.pop(trans_id)
+            if trans_id not in self._new_contents:
+                continue
+            new_path = self._limbo_name(trans_id)
+            os.rename(old_path, new_path)
+
+    def create_file(self, contents, trans_id, mode_id=None):
+        """Schedule creation of a new file.
+
+        See also new_file.
+
+        Contents is an iterator of strings, all of which will be written
+        to the target destination.
+
+        New file takes the permissions of any existing file with that id,
+        unless mode_id is specified.
+        """
+        name = self._limbo_name(trans_id)
+        f = open(name, 'wb')
+        try:
+            try:
+                unique_add(self._new_contents, trans_id, 'file')
+            except:
+                # Clean up the file, it never got registered so
+                # TreeTransform.finalize() won't clean it up.
+                f.close()
+                os.unlink(name)
+                raise
+
+            f.writelines(contents)
+        finally:
+            f.close()
+        self._set_mode(trans_id, mode_id, S_ISREG)
+
+    def _read_file_chunks(self, trans_id):
+        cur_file = open(self._limbo_name(trans_id), 'rb')
+        try:
+            return cur_file.readlines()
+        finally:
+            cur_file.close()
+
+    def _read_symlink_target(self, trans_id):
+        return os.readlink(self._limbo_name(trans_id))
+
+    def create_hardlink(self, path, trans_id):
+        """Schedule creation of a hard link"""
+        name = self._limbo_name(trans_id)
+        try:
+            os.link(path, name)
+        except OSError, e:
+            if e.errno != errno.EPERM:
+                raise
+            raise errors.HardLinkNotSupported(path)
+        try:
+            unique_add(self._new_contents, trans_id, 'file')
+        except:
+            # Clean up the file, it never got registered so
+            # TreeTransform.finalize() won't clean it up.
+            os.unlink(name)
+            raise
+
+    def create_directory(self, trans_id):
+        """Schedule creation of a new directory.
+
+        See also new_directory.
+        """
+        os.mkdir(self._limbo_name(trans_id))
+        unique_add(self._new_contents, trans_id, 'directory')
+
+    def create_symlink(self, target, trans_id):
+        """Schedule creation of a new symbolic link.
+
+        target is a bytestring.
+        See also new_symlink.
+        """
+        if has_symlinks():
+            os.symlink(target, self._limbo_name(trans_id))
+            unique_add(self._new_contents, trans_id, 'symlink')
+        else:
+            try:
+                path = FinalPaths(self).get_path(trans_id)
+            except KeyError:
+                path = None
+            raise UnableCreateSymlink(path=path)
+
+    def cancel_creation(self, trans_id):
+        """Cancel the creation of new file contents."""
+        del self._new_contents[trans_id]
+        children = self._limbo_children.get(trans_id)
+        # if this is a limbo directory with children, move them before removing
+        # the directory
+        if children is not None:
+            self._rename_in_limbo(children)
+            del self._limbo_children[trans_id]
+            del self._limbo_children_names[trans_id]
+        delete_any(self._limbo_name(trans_id))
+
+
+class TreeTransform(DiskTreeTransform):
     """Represent a tree transformation.
 
     This object is designed to support incremental generation of the transform,
@@ -1319,10 +1268,96 @@
             tree.unlock()
             raise
 
-        TreeTransformBase.__init__(self, tree, limbodir, pb,
+        # Cache of realpath results, to speed up canonical_path
+        self._realpaths = {}
+        # Cache of relpath results, to speed up canonical_path
+        self._relpaths = {}
+        DiskTreeTransform.__init__(self, tree, limbodir, pb,
                                    tree.case_sensitive)
         self._deletiondir = deletiondir
 
+    def canonical_path(self, path):
+        """Get the canonical tree-relative path"""
+        # don't follow final symlinks
+        abs = self._tree.abspath(path)
+        if abs in self._relpaths:
+            return self._relpaths[abs]
+        dirname, basename = os.path.split(abs)
+        if dirname not in self._realpaths:
+            self._realpaths[dirname] = os.path.realpath(dirname)
+        dirname = self._realpaths[dirname]
+        abs = pathjoin(dirname, basename)
+        if dirname in self._relpaths:
+            relpath = pathjoin(self._relpaths[dirname], basename)
+            relpath = relpath.rstrip('/\\')
+        else:
+            relpath = self._tree.relpath(abs)
+        self._relpaths[abs] = relpath
+        return relpath
+
+    def tree_kind(self, trans_id):
+        """Determine the file kind in the working tree.
+
+        Raises NoSuchFile if the file does not exist
+        """
+        path = self._tree_id_paths.get(trans_id)
+        if path is None:
+            raise NoSuchFile(None)
+        try:
+            return file_kind(self._tree.abspath(path))
+        except OSError, e:
+            if e.errno != errno.ENOENT:
+                raise
+            else:
+                raise NoSuchFile(path)
+
+    def _set_mode(self, trans_id, mode_id, typefunc):
+        """Set the mode of new file contents.
+        The mode_id is the existing file to get the mode from (often the same
+        as trans_id).  The operation is only performed if there's a mode match
+        according to typefunc.
+        """
+        if mode_id is None:
+            mode_id = trans_id
+        try:
+            old_path = self._tree_id_paths[mode_id]
+        except KeyError:
+            return
+        try:
+            mode = os.stat(self._tree.abspath(old_path)).st_mode
+        except OSError, e:
+            if e.errno in (errno.ENOENT, errno.ENOTDIR):
+                # Either old_path doesn't exist, or the parent of the
+                # target is not a directory (but will be one eventually)
+                # Either way, we know it doesn't exist *right now*
+                # See also bug #248448
+                return
+            else:
+                raise
+        if typefunc(mode):
+            os.chmod(self._limbo_name(trans_id), mode)
+
+    def iter_tree_children(self, parent_id):
+        """Iterate through the entry's tree children, if any"""
+        try:
+            path = self._tree_id_paths[parent_id]
+        except KeyError:
+            return
+        try:
+            children = os.listdir(self._tree.abspath(path))
+        except OSError, e:
+            if not (osutils._is_error_enotdir(e)
+                    or e.errno in (errno.ENOENT, errno.ESRCH)):
+                raise
+            return
+
+        for child in children:
+            childpath = joinpath(path, child)
+            if self._tree.is_control_filename(childpath):
+                continue
+            yield self.trans_id_tree_path(childpath)
+
+
     def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
         """Apply all changes to the inventory and filesystem.
 
@@ -1505,7 +1540,7 @@
         return modified_paths
 
 
-class TransformPreview(TreeTransformBase):
+class TransformPreview(DiskTreeTransform):
     """A TreeTransform for generating preview trees.
 
     Unlike TreeTransform, this version works when the input tree is a
@@ -1516,7 +1551,7 @@
     def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
         tree.lock_read()
         limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
-        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
+        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
 
     def canonical_path(self, path):
         return path




More information about the bazaar-commits mailing list