~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Andrew Bennetts
  • Date: 2007-08-30 08:11:54 UTC
  • mfrom: (2766 +trunk)
  • mto: (2535.3.55 repo-refactor)
  • mto: This revision was merged to the branch mainline in revision 2772.
  • Revision ID: andrew.bennetts@canonical.com-20070830081154-16hebp2xwr15x2hc
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
108
108
            except OSError, e:
109
109
                if e.errno == errno.EEXIST:
110
110
                    raise ExistingLimbo(self._limbodir)
 
111
            self._deletiondir = urlutils.local_path_from_url(
 
112
                control_files.controlfilename('pending-deletion'))
 
113
            try:
 
114
                os.mkdir(self._deletiondir)
 
115
            except OSError, e:
 
116
                if e.errno == errno.EEXIST:
 
117
                    raise errors.ExistingPendingDeletion(self._deletiondir)
 
118
 
111
119
        except: 
112
120
            self._tree.unlock()
113
121
            raise
170
178
            except OSError:
171
179
                # We don't especially care *why* the dir is immortal.
172
180
                raise ImmortalLimbo(self._limbodir)
 
181
            try:
 
182
                os.rmdir(self._deletiondir)
 
183
            except OSError:
 
184
                raise errors.ImmortalPendingDeletion(self._deletiondir)
173
185
        finally:
174
186
            self._tree.unlock()
175
187
            self._tree = None
789
801
            return True
790
802
        return False
791
803
            
792
 
    def apply(self, no_conflicts=False):
 
804
    def apply(self, no_conflicts=False, _mover=None):
793
805
        """Apply all changes to the inventory and filesystem.
794
806
        
795
807
        If filesystem or inventory conflicts are present, MalformedTransform
799
811
 
800
812
        :param no_conflicts: if True, the caller guarantees there are no
801
813
            conflicts, so no check is made.
 
814
        :param _mover: Supply an alternate FileMover, for testing
802
815
        """
803
816
        if not no_conflicts:
804
817
            conflicts = self.find_conflicts()
808
821
        inventory_delta = []
809
822
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
810
823
        try:
811
 
            child_pb.update('Apply phase', 0, 2)
812
 
            self._apply_removals(inv, inventory_delta)
813
 
            child_pb.update('Apply phase', 1, 2)
814
 
            modified_paths = self._apply_insertions(inv, inventory_delta)
 
824
            if _mover is None:
 
825
                mover = _FileMover()
 
826
            else:
 
827
                mover = _mover
 
828
            try:
 
829
                child_pb.update('Apply phase', 0, 2)
 
830
                self._apply_removals(inv, inventory_delta, mover)
 
831
                child_pb.update('Apply phase', 1, 2)
 
832
                modified_paths = self._apply_insertions(inv, inventory_delta,
 
833
                                                        mover)
 
834
            except:
 
835
                mover.rollback()
 
836
                raise
 
837
            else:
 
838
                mover.apply_deletions()
815
839
        finally:
816
840
            child_pb.finished()
817
841
        self._tree.apply_inventory_delta(inventory_delta)
852
876
        self._limbo_files[trans_id] = limbo_name
853
877
        return limbo_name
854
878
 
855
 
    def _apply_removals(self, inv, inventory_delta):
 
879
    def _apply_removals(self, inv, inventory_delta, mover):
856
880
        """Perform tree operations that remove directory/inventory names.
857
881
        
858
882
        That is, delete files that are to be deleted, and put any files that
868
892
                child_pb.update('removing file', num, len(tree_paths))
869
893
                full_path = self._tree.abspath(path)
870
894
                if trans_id in self._removed_contents:
871
 
                    delete_any(full_path)
 
895
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
 
896
                                     trans_id))
872
897
                elif trans_id in self._new_name or trans_id in \
873
898
                    self._new_parent:
874
899
                    try:
875
 
                        os.rename(full_path, self._limbo_name(trans_id))
 
900
                        mover.rename(full_path, self._limbo_name(trans_id))
876
901
                    except OSError, e:
877
902
                        if e.errno != errno.ENOENT:
878
903
                            raise
888
913
        finally:
889
914
            child_pb.finished()
890
915
 
891
 
    def _apply_insertions(self, inv, inventory_delta):
 
916
    def _apply_insertions(self, inv, inventory_delta, mover):
892
917
        """Perform tree operations that insert directory/inventory names.
893
918
        
894
919
        That is, create any files that need to be created, and restore from
911
936
                    full_path = self._tree.abspath(path)
912
937
                    if trans_id in self._needs_rename:
913
938
                        try:
914
 
                            os.rename(self._limbo_name(trans_id), full_path)
 
939
                            mover.rename(self._limbo_name(trans_id), full_path)
915
940
                        except OSError, e:
916
941
                            # We may be renaming a dangling inventory id
917
942
                            if e.errno != errno.ENOENT:
1269
1294
            tt.trans_id_tree_file_id(wt.get_root_id())
1270
1295
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1271
1296
        try:
 
1297
            deferred_contents = []
1272
1298
            for num, (tree_path, entry) in \
1273
1299
                enumerate(tree.inventory.iter_entries_by_dir()):
1274
 
                pb.update("Building tree", num, len(tree.inventory))
 
1300
                pb.update("Building tree", num - len(deferred_contents),
 
1301
                          len(tree.inventory))
1275
1302
                if entry.parent_id is None:
1276
1303
                    continue
1277
1304
                reparent = False
1300
1327
                        'entry %s parent id %r is not in file_trans_id %r'
1301
1328
                        % (entry, entry.parent_id, file_trans_id))
1302
1329
                parent_id = file_trans_id[entry.parent_id]
1303
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1304
 
                                                      tree)
 
1330
                if entry.kind == 'file':
 
1331
                    # We *almost* replicate new_by_entry, so that we can defer
 
1332
                    # getting the file text, and get them all at once.
 
1333
                    trans_id = tt.create_path(entry.name, parent_id)
 
1334
                    file_trans_id[file_id] = trans_id
 
1335
                    tt.version_file(entry.file_id, trans_id)
 
1336
                    executable = tree.is_executable(entry.file_id, tree_path)
 
1337
                    if executable is not None:
 
1338
                        tt.set_executability(executable, trans_id)
 
1339
                    deferred_contents.append((entry.file_id, trans_id))
 
1340
                else:
 
1341
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
1342
                                                          tree)
1305
1343
                if reparent:
1306
1344
                    new_trans_id = file_trans_id[file_id]
1307
1345
                    old_parent = tt.trans_id_tree_path(tree_path)
1308
1346
                    _reparent_children(tt, old_parent, new_trans_id)
 
1347
            for num, (trans_id, bytes) in enumerate(
 
1348
                tree.iter_files_bytes(deferred_contents)):
 
1349
                tt.create_file(bytes, trans_id)
 
1350
                pb.update('Adding file contents',
 
1351
                          (num + len(tree.inventory) - len(deferred_contents)),
 
1352
                          len(tree.inventory))
1309
1353
        finally:
1310
1354
            pb.finished()
1311
1355
        pp.next_phase()
1563
1607
        skip_root = False
1564
1608
    basis_tree = None
1565
1609
    try:
 
1610
        deferred_files = []
1566
1611
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1567
1612
                kind, executable) in enumerate(change_list):
1568
1613
            if skip_root and file_id[0] is not None and parent[0] is None:
1608
1653
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1609
1654
                                      trans_id)
1610
1655
                elif kind[1] == 'file':
1611
 
                    tt.create_file(target_tree.get_file_lines(file_id),
1612
 
                                   trans_id, mode_id)
 
1656
                    deferred_files.append((file_id, (trans_id, mode_id)))
1613
1657
                    if basis_tree is None:
1614
1658
                        basis_tree = working_tree.basis_tree()
1615
1659
                        basis_tree.lock_read()
1636
1680
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
1637
1681
            if executable[0] != executable[1] and kind[1] == "file":
1638
1682
                tt.set_executability(executable[1], trans_id)
 
1683
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
1684
            deferred_files):
 
1685
            tt.create_file(bytes, trans_id, mode_id)
1639
1686
    finally:
1640
1687
        if basis_tree is not None:
1641
1688
            basis_tree.unlock()
1741
1788
                                   file_id=modified_id, 
1742
1789
                                   conflict_path=conflicting_path,
1743
1790
                                   conflict_file_id=conflicting_id)
 
1791
 
 
1792
 
 
1793
class _FileMover(object):
 
1794
    """Moves and deletes files for TreeTransform, tracking operations"""
 
1795
 
 
1796
    def __init__(self):
 
1797
        self.past_renames = []
 
1798
        self.pending_deletions = []
 
1799
 
 
1800
    def rename(self, from_, to):
 
1801
        """Rename a file from one path to another.  Functions like os.rename"""
 
1802
        os.rename(from_, to)
 
1803
        self.past_renames.append((from_, to))
 
1804
 
 
1805
    def pre_delete(self, from_, to):
 
1806
        """Rename a file out of the way and mark it for deletion.
 
1807
 
 
1808
        Unlike os.unlink, this works equally well for files and directories.
 
1809
        :param from_: The current file path
 
1810
        :param to: A temporary path for the file
 
1811
        """
 
1812
        self.rename(from_, to)
 
1813
        self.pending_deletions.append(to)
 
1814
 
 
1815
    def rollback(self):
 
1816
        """Reverse all renames that have been performed"""
 
1817
        for from_, to in reversed(self.past_renames):
 
1818
            os.rename(to, from_)
 
1819
        # after rollback, don't reuse _FileMover
 
1820
        past_renames = None
 
1821
        pending_deletions = None
 
1822
 
 
1823
    def apply_deletions(self):
 
1824
        """Apply all marked deletions"""
 
1825
        for path in self.pending_deletions:
 
1826
            delete_any(path)
 
1827
        # after apply_deletions, don't reuse _FileMover
 
1828
        past_renames = None
 
1829
        pending_deletions = None