~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-06-18 05:22:35 UTC
  • mfrom: (1551.15.27 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070618052235-mvns8j28szyzscy0
Turn list-weave into list-versionedfile

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
""")
30
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
31
                           ReusingTransform, NotVersionedError, CantMoveRoot,
32
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
33
 
                           UnableCreateSymlink)
 
32
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
34
33
from bzrlib.inventory import InventoryEntry
35
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
36
 
                            delete_any, has_symlinks)
 
35
                            delete_any)
37
36
from bzrlib.progress import DummyProgress, ProgressPhase
38
 
from bzrlib.symbol_versioning import (
39
 
        deprecated_function,
40
 
        zero_fifteen,
41
 
        zero_ninety,
42
 
        )
 
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
43
38
from bzrlib.trace import mutter, warning
44
39
from bzrlib import tree
45
40
import bzrlib.ui
90
85
     * create_file or create_directory or create_symlink
91
86
     * version_file
92
87
     * set_executability
93
 
 
94
 
    Transform/Transaction ids
95
 
    -------------------------
96
 
    trans_ids are temporary ids assigned to all files involved in a transform.
97
 
    It's possible, even common, that not all files in the Tree have trans_ids.
98
 
 
99
 
    trans_ids are used because filenames and file_ids are not good enough
100
 
    identifiers; filenames change, and not all files have file_ids.  File-ids
101
 
    are also associated with trans-ids, so that moving a file moves its
102
 
    file-id.
103
 
 
104
 
    trans_ids are only valid for the TreeTransform that generated them.
105
 
 
106
 
    Limbo
107
 
    -----
108
 
    Limbo is a temporary directory use to hold new versions of files.
109
 
    Files are added to limbo by create_file, create_directory, create_symlink,
110
 
    and their convenience variants (new_*).  Files may be removed from limbo
111
 
    using cancel_creation.  Files are renamed from limbo into their final
112
 
    location as part of TreeTransform.apply
113
 
 
114
 
    Limbo must be cleaned up, by either calling TreeTransform.apply or
115
 
    calling TreeTransform.finalize.
116
 
 
117
 
    Files are placed into limbo inside their parent directories, where
118
 
    possible.  This reduces subsequent renames, and makes operations involving
119
 
    lots of files faster.  This optimization is only possible if the parent
120
 
    directory is created *before* creating any of its children, so avoid
121
 
    creating children before parents, where possible.
122
 
 
123
 
    Pending-deletion
124
 
    ----------------
125
 
    This temporary directory is used by _FileMover for storing files that are
126
 
    about to be deleted.  In case of rollback, the files will be restored.
127
 
    FileMover does not delete files until it is sure that a rollback will not
128
 
    happen.  
129
88
    """
130
89
    def __init__(self, tree, pb=DummyProgress()):
131
90
        """Note: a tree_write lock is taken on the tree.
145
104
            except OSError, e:
146
105
                if e.errno == errno.EEXIST:
147
106
                    raise ExistingLimbo(self._limbodir)
148
 
            self._deletiondir = urlutils.local_path_from_url(
149
 
                control_files.controlfilename('pending-deletion'))
150
 
            try:
151
 
                os.mkdir(self._deletiondir)
152
 
            except OSError, e:
153
 
                if e.errno == errno.EEXIST:
154
 
                    raise errors.ExistingPendingDeletion(self._deletiondir)
155
 
 
156
107
        except: 
157
108
            self._tree.unlock()
158
109
            raise
159
110
 
160
 
        # counter used to generate trans-ids (which are locally unique)
161
111
        self._id_number = 0
162
 
        # mapping of trans_id -> new basename
163
112
        self._new_name = {}
164
 
        # mapping of trans_id -> new parent trans_id
165
113
        self._new_parent = {}
166
 
        # mapping of trans_id with new contents -> new file_kind
167
114
        self._new_contents = {}
168
115
        # A mapping of transform ids to their limbo filename
169
116
        self._limbo_files = {}
174
121
        self._limbo_children_names = {}
175
122
        # List of transform ids that need to be renamed from limbo into place
176
123
        self._needs_rename = set()
177
 
        # Set of trans_ids whose contents will be removed
178
124
        self._removed_contents = set()
179
 
        # Mapping of trans_id -> new execute-bit value
180
125
        self._new_executability = {}
181
 
        # Mapping of trans_id -> new tree-reference value
182
126
        self._new_reference_revision = {}
183
 
        # Mapping of trans_id -> new file_id
184
127
        self._new_id = {}
185
 
        # Mapping of old file-id -> trans_id
186
128
        self._non_present_ids = {}
187
 
        # Mapping of new file_id -> trans_id
188
129
        self._r_new_id = {}
189
 
        # Set of file_ids that will be removed
190
130
        self._removed_id = set()
191
 
        # Mapping of path in old tree -> trans_id
192
131
        self._tree_path_ids = {}
193
 
        # Mapping trans_id -> path in old tree
194
132
        self._tree_id_paths = {}
195
133
        # Cache of realpath results, to speed up canonical_path
196
134
        self._realpaths = {}
197
135
        # Cache of relpath results, to speed up canonical_path
198
136
        self._relpaths = {}
199
 
        # The trans_id that will be used as the tree root
200
137
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
201
 
        # Indictor of whether the transform has been applied
202
138
        self.__done = False
203
 
        # A progress bar
204
139
        self._pb = pb
205
 
        # A counter of how many files have been renamed
206
140
        self.rename_count = 0
207
141
 
208
142
    def __get_root(self):
232
166
            except OSError:
233
167
                # We don't especially care *why* the dir is immortal.
234
168
                raise ImmortalLimbo(self._limbodir)
235
 
            try:
236
 
                os.rmdir(self._deletiondir)
237
 
            except OSError:
238
 
                raise errors.ImmortalPendingDeletion(self._deletiondir)
239
169
        finally:
240
170
            self._tree.unlock()
241
171
            self._tree = None
441
371
        target is a bytestring.
442
372
        See also new_symlink.
443
373
        """
444
 
        if has_symlinks():
445
 
            os.symlink(target, self._limbo_name(trans_id))
446
 
            unique_add(self._new_contents, trans_id, 'symlink')
447
 
        else:
448
 
            try:
449
 
                path = FinalPaths(self).get_path(trans_id)
450
 
            except KeyError:
451
 
                path = None
452
 
            raise UnableCreateSymlink(path=path)
 
374
        os.symlink(target, self._limbo_name(trans_id))
 
375
        unique_add(self._new_contents, trans_id, 'symlink')
453
376
 
454
377
    def cancel_creation(self, trans_id):
455
378
        """Cancel the creation of new file contents."""
556
479
            return None
557
480
        # the file is old; the old id is still valid
558
481
        if self._new_root == trans_id:
559
 
            return self._tree.get_root_id()
 
482
            return self._tree.inventory.root.file_id
560
483
        return self._tree.inventory.path2id(path)
561
484
 
562
485
    def final_file_id(self, trans_id):
794
717
    def _duplicate_entries(self, by_parent):
795
718
        """No directory may have two entries with the same name."""
796
719
        conflicts = []
797
 
        if (self._new_name, self._new_parent) == ({}, {}):
798
 
            return conflicts
799
720
        for children in by_parent.itervalues():
800
721
            name_ids = [(self.final_name(t), t) for t in children]
801
 
            if not self._tree.case_sensitive:
802
 
                name_ids = [(n.lower(), t) for n, t in name_ids]
803
722
            name_ids.sort()
804
723
            last_name = None
805
724
            last_trans_id = None
864
783
            return True
865
784
        return False
866
785
            
867
 
    def apply(self, no_conflicts=False, _mover=None):
 
786
    def apply(self):
868
787
        """Apply all changes to the inventory and filesystem.
869
788
        
870
789
        If filesystem or inventory conflicts are present, MalformedTransform
871
790
        will be thrown.
872
791
 
873
792
        If apply succeeds, finalize is not necessary.
874
 
 
875
 
        :param no_conflicts: if True, the caller guarantees there are no
876
 
            conflicts, so no check is made.
877
 
        :param _mover: Supply an alternate FileMover, for testing
878
793
        """
879
 
        if not no_conflicts:
880
 
            conflicts = self.find_conflicts()
881
 
            if len(conflicts) != 0:
882
 
                raise MalformedTransform(conflicts=conflicts)
 
794
        conflicts = self.find_conflicts()
 
795
        if len(conflicts) != 0:
 
796
            raise MalformedTransform(conflicts=conflicts)
883
797
        inv = self._tree.inventory
884
798
        inventory_delta = []
885
799
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
886
800
        try:
887
 
            if _mover is None:
888
 
                mover = _FileMover()
889
 
            else:
890
 
                mover = _mover
891
 
            try:
892
 
                child_pb.update('Apply phase', 0, 2)
893
 
                self._apply_removals(inv, inventory_delta, mover)
894
 
                child_pb.update('Apply phase', 1, 2)
895
 
                modified_paths = self._apply_insertions(inv, inventory_delta,
896
 
                                                        mover)
897
 
            except:
898
 
                mover.rollback()
899
 
                raise
900
 
            else:
901
 
                mover.apply_deletions()
 
801
            child_pb.update('Apply phase', 0, 2)
 
802
            self._apply_removals(inv, inventory_delta)
 
803
            child_pb.update('Apply phase', 1, 2)
 
804
            modified_paths = self._apply_insertions(inv, inventory_delta)
902
805
        finally:
903
806
            child_pb.finished()
904
807
        self._tree.apply_inventory_delta(inventory_delta)
926
829
                # the direct path can only be used if no other file has
927
830
                # already taken this pathname, i.e. if the name is unused, or
928
831
                # if it is already associated with this trans_id.
929
 
                elif self._tree.case_sensitive:
930
 
                    if (self._limbo_children_names[parent].get(filename)
931
 
                        in (trans_id, None)):
932
 
                        use_direct_path = True
933
 
                else:
934
 
                    for l_filename, l_trans_id in\
935
 
                        self._limbo_children_names[parent].iteritems():
936
 
                        if l_trans_id == trans_id:
937
 
                            continue
938
 
                        if l_filename.lower() == filename.lower():
939
 
                            break
940
 
                    else:
941
 
                        use_direct_path = True
942
 
 
 
832
                elif (self._limbo_children_names[parent].get(filename)
 
833
                      in (trans_id, None)):
 
834
                    use_direct_path = True
943
835
        if use_direct_path:
944
836
            limbo_name = pathjoin(self._limbo_files[parent], filename)
945
837
            self._limbo_children[parent].add(trans_id)
950
842
        self._limbo_files[trans_id] = limbo_name
951
843
        return limbo_name
952
844
 
953
 
    def _apply_removals(self, inv, inventory_delta, mover):
 
845
    def _apply_removals(self, inv, inventory_delta):
954
846
        """Perform tree operations that remove directory/inventory names.
955
847
        
956
848
        That is, delete files that are to be deleted, and put any files that
966
858
                child_pb.update('removing file', num, len(tree_paths))
967
859
                full_path = self._tree.abspath(path)
968
860
                if trans_id in self._removed_contents:
969
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
970
 
                                     trans_id))
 
861
                    delete_any(full_path)
971
862
                elif trans_id in self._new_name or trans_id in \
972
863
                    self._new_parent:
973
864
                    try:
974
 
                        mover.rename(full_path, self._limbo_name(trans_id))
 
865
                        os.rename(full_path, self._limbo_name(trans_id))
975
866
                    except OSError, e:
976
867
                        if e.errno != errno.ENOENT:
977
868
                            raise
979
870
                        self.rename_count += 1
980
871
                if trans_id in self._removed_id:
981
872
                    if trans_id == self._new_root:
982
 
                        file_id = self._tree.get_root_id()
 
873
                        file_id = self._tree.inventory.root.file_id
983
874
                    else:
984
875
                        file_id = self.tree_file_id(trans_id)
985
876
                    assert file_id is not None
987
878
        finally:
988
879
            child_pb.finished()
989
880
 
990
 
    def _apply_insertions(self, inv, inventory_delta, mover):
 
881
    def _apply_insertions(self, inv, inventory_delta):
991
882
        """Perform tree operations that insert directory/inventory names.
992
883
        
993
884
        That is, create any files that need to be created, and restore from
997
888
        new_paths = self.new_paths()
998
889
        modified_paths = []
999
890
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1000
 
        completed_new = []
1001
891
        try:
1002
892
            for num, (path, trans_id) in enumerate(new_paths):
1003
893
                new_entry = None
1011
901
                    full_path = self._tree.abspath(path)
1012
902
                    if trans_id in self._needs_rename:
1013
903
                        try:
1014
 
                            mover.rename(self._limbo_name(trans_id), full_path)
 
904
                            os.rename(self._limbo_name(trans_id), full_path)
1015
905
                        except OSError, e:
1016
906
                            # We may be renaming a dangling inventory id
1017
907
                            if e.errno != errno.ENOENT:
1020
910
                            self.rename_count += 1
1021
911
                    if trans_id in self._new_contents:
1022
912
                        modified_paths.append(full_path)
1023
 
                        completed_new.append(trans_id)
 
913
                        del self._new_contents[trans_id]
1024
914
 
1025
915
                if trans_id in self._new_id:
1026
916
                    if kind is None:
1065
955
                                            new_entry))
1066
956
        finally:
1067
957
            child_pb.finished()
1068
 
        for trans_id in completed_new:
1069
 
            del self._new_contents[trans_id]
1070
958
        return modified_paths
1071
959
 
1072
960
    def _set_executability(self, path, entry, trans_id):
1314
1202
            self._known_paths[trans_id] = self._determine_path(trans_id)
1315
1203
        return self._known_paths[trans_id]
1316
1204
 
1317
 
 
1318
1205
def topology_sorted_ids(tree):
1319
1206
    """Determine the topological order of the ids in a tree"""
1320
1207
    file_ids = list(tree)
1322
1209
    return file_ids
1323
1210
 
1324
1211
 
1325
 
def build_tree(tree, wt, accelerator_tree=None):
 
1212
def build_tree(tree, wt):
1326
1213
    """Create working tree for a branch, using a TreeTransform.
1327
1214
    
1328
1215
    This function should be used on empty trees, having a tree root at most.
1335
1222
    - Otherwise, if the content on disk matches the content we are building,
1336
1223
      it is silently replaced.
1337
1224
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1338
 
 
1339
 
    :param tree: The tree to convert wt into a copy of
1340
 
    :param wt: The working tree that files will be placed into
1341
 
    :param accelerator_tree: A tree which can be used for retrieving file
1342
 
        contents more quickly than tree itself, i.e. a workingtree.  tree
1343
 
        will be used for cases where accelerator_tree's content is different.
1344
1225
    """
1345
1226
    wt.lock_tree_write()
1346
1227
    try:
1347
1228
        tree.lock_read()
1348
1229
        try:
1349
 
            if accelerator_tree is not None:
1350
 
                accelerator_tree.lock_read()
1351
 
            try:
1352
 
                return _build_tree(tree, wt, accelerator_tree)
1353
 
            finally:
1354
 
                if accelerator_tree is not None:
1355
 
                    accelerator_tree.unlock()
 
1230
            return _build_tree(tree, wt)
1356
1231
        finally:
1357
1232
            tree.unlock()
1358
1233
    finally:
1359
1234
        wt.unlock()
1360
1235
 
1361
 
 
1362
 
def _build_tree(tree, wt, accelerator_tree):
 
1236
def _build_tree(tree, wt):
1363
1237
    """See build_tree."""
1364
1238
    if len(wt.inventory) > 1:  # more than just a root
1365
1239
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1374
1248
        # is set within the tree, nor setting the root and thus
1375
1249
        # marking the tree as dirty, because we use two different
1376
1250
        # idioms here: tree interfaces and inventory interfaces.
1377
 
        if wt.get_root_id() != tree.get_root_id():
1378
 
            wt.set_root_id(tree.get_root_id())
 
1251
        if wt.path2id('') != tree.inventory.root.file_id:
 
1252
            wt.set_root_id(tree.inventory.root.file_id)
1379
1253
            wt.flush()
1380
1254
    tt = TreeTransform(wt)
1381
1255
    divert = set()
1385
1259
            tt.trans_id_tree_file_id(wt.get_root_id())
1386
1260
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1387
1261
        try:
1388
 
            deferred_contents = []
1389
1262
            for num, (tree_path, entry) in \
1390
1263
                enumerate(tree.inventory.iter_entries_by_dir()):
1391
 
                pb.update("Building tree", num - len(deferred_contents),
1392
 
                          len(tree.inventory))
 
1264
                pb.update("Building tree", num, len(tree.inventory))
1393
1265
                if entry.parent_id is None:
1394
1266
                    continue
1395
1267
                reparent = False
1418
1290
                        'entry %s parent id %r is not in file_trans_id %r'
1419
1291
                        % (entry, entry.parent_id, file_trans_id))
1420
1292
                parent_id = file_trans_id[entry.parent_id]
1421
 
                if entry.kind == 'file':
1422
 
                    # We *almost* replicate new_by_entry, so that we can defer
1423
 
                    # getting the file text, and get them all at once.
1424
 
                    trans_id = tt.create_path(entry.name, parent_id)
1425
 
                    file_trans_id[file_id] = trans_id
1426
 
                    tt.version_file(entry.file_id, trans_id)
1427
 
                    executable = tree.is_executable(entry.file_id, tree_path)
1428
 
                    if executable is not None:
1429
 
                        tt.set_executability(executable, trans_id)
1430
 
                    deferred_contents.append((entry.file_id, trans_id))
1431
 
                else:
1432
 
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1433
 
                                                          tree)
 
1293
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
1294
                                                      tree)
1434
1295
                if reparent:
1435
1296
                    new_trans_id = file_trans_id[file_id]
1436
1297
                    old_parent = tt.trans_id_tree_path(tree_path)
1437
1298
                    _reparent_children(tt, old_parent, new_trans_id)
1438
 
            for num, (trans_id, bytes) in enumerate(
1439
 
                _iter_files_bytes_accelerated(tree, accelerator_tree,
1440
 
                                              deferred_contents)):
1441
 
                tt.create_file(bytes, trans_id)
1442
 
                pb.update('Adding file contents',
1443
 
                          (num + len(tree.inventory) - len(deferred_contents)),
1444
 
                          len(tree.inventory))
1445
1299
        finally:
1446
1300
            pb.finished()
1447
1301
        pp.next_phase()
1455
1309
            wt.add_conflicts(conflicts)
1456
1310
        except errors.UnsupportedOperation:
1457
1311
            pass
1458
 
        result = tt.apply(no_conflicts=True)
 
1312
        result = tt.apply()
1459
1313
    finally:
1460
1314
        tt.finalize()
1461
1315
        top_pb.finished()
1462
1316
    return result
1463
1317
 
1464
1318
 
1465
 
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
1466
 
    if accelerator_tree is None:
1467
 
        new_desired_files = desired_files
1468
 
    else:
1469
 
        iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1470
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1471
 
                         in iter if not c)
1472
 
        new_desired_files = []
1473
 
        for file_id, identifier in desired_files:
1474
 
            accelerator_path = unchanged.get(file_id)
1475
 
            if accelerator_path is None:
1476
 
                new_desired_files.append((file_id, identifier))
1477
 
                continue
1478
 
            contents = accelerator_tree.get_file(file_id, accelerator_path)
1479
 
            try:
1480
 
                want_new = False
1481
 
                contents_bytes = (contents.read(),)
1482
 
            finally:
1483
 
                contents.close()
1484
 
            yield identifier, contents_bytes
1485
 
    for result in tree.iter_files_bytes(new_desired_files):
1486
 
        yield result
1487
 
 
1488
 
 
1489
1319
def _reparent_children(tt, old_parent, new_parent):
1490
1320
    for child in tt.iter_tree_children(old_parent):
1491
1321
        tt.adjust_path(tt.final_name(child), new_parent, child)
1554
1384
    else:
1555
1385
        raise errors.BadFileKindError(name, kind)
1556
1386
 
1557
 
 
1558
1387
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1559
1388
    """Create new file contents according to an inventory entry."""
1560
1389
    if entry.kind == "file":
1566
1395
    elif entry.kind == "directory":
1567
1396
        tt.create_directory(trans_id)
1568
1397
 
1569
 
 
1570
1398
def create_entry_executability(tt, entry, trans_id):
1571
1399
    """Set the executability of a trans_id according to an inventory entry"""
1572
1400
    if entry.kind == "file":
1590
1418
        working_tree.unlock()
1591
1419
 
1592
1420
 
1593
 
@deprecated_function(zero_ninety)
1594
1421
def change_entry(tt, file_id, working_tree, target_tree, 
1595
1422
                 trans_id_file_id, backups, trans_id, by_parent):
1596
1423
    """Replace a file_id's contents with those from a target tree."""
1597
 
    if file_id is None and target_tree is None:
1598
 
        # skip the logic altogether in the deprecation test
1599
 
        return
1600
1424
    e_trans_id = trans_id_file_id(file_id)
1601
1425
    entry = target_tree.inventory[file_id]
1602
1426
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1694
1518
        pp.next_phase()
1695
1519
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1696
1520
        try:
1697
 
            raw_conflicts = resolve_conflicts(tt, child_pb,
1698
 
                lambda t, c: conflict_pass(t, c, target_tree))
 
1521
            raw_conflicts = resolve_conflicts(tt, child_pb)
1699
1522
        finally:
1700
1523
            child_pb.finished()
1701
1524
        conflicts = cook_conflicts(raw_conflicts, tt)
1726
1549
        skip_root = False
1727
1550
    basis_tree = None
1728
1551
    try:
1729
 
        deferred_files = []
1730
1552
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1731
1553
                kind, executable) in enumerate(change_list):
1732
1554
            if skip_root and file_id[0] is not None and parent[0] is None:
1772
1594
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1773
1595
                                      trans_id)
1774
1596
                elif kind[1] == 'file':
1775
 
                    deferred_files.append((file_id, (trans_id, mode_id)))
 
1597
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1598
                                   trans_id, mode_id)
1776
1599
                    if basis_tree is None:
1777
1600
                        basis_tree = working_tree.basis_tree()
1778
1601
                        basis_tree.lock_read()
1799
1622
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
1800
1623
            if executable[0] != executable[1] and kind[1] == "file":
1801
1624
                tt.set_executability(executable[1], trans_id)
1802
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1803
 
            deferred_files):
1804
 
            tt.create_file(bytes, trans_id, mode_id)
1805
1625
    finally:
1806
1626
        if basis_tree is not None:
1807
1627
            basis_tree.unlock()
1825
1645
        pb.clear()
1826
1646
 
1827
1647
 
1828
 
def conflict_pass(tt, conflicts, path_tree=None):
1829
 
    """Resolve some classes of conflicts.
1830
 
 
1831
 
    :param tt: The transform to resolve conflicts in
1832
 
    :param conflicts: The conflicts to resolve
1833
 
    :param path_tree: A Tree to get supplemental paths from
1834
 
    """
 
1648
def conflict_pass(tt, conflicts):
 
1649
    """Resolve some classes of conflicts."""
1835
1650
    new_conflicts = set()
1836
1651
    for c_type, conflict in ((c[0], c) for c in conflicts):
1837
1652
        if c_type == 'duplicate id':
1840
1655
                               conflict[1], conflict[2], ))
1841
1656
        elif c_type == 'duplicate':
1842
1657
            # files that were renamed take precedence
 
1658
            new_name = tt.final_name(conflict[1])+'.moved'
1843
1659
            final_parent = tt.final_parent(conflict[1])
1844
1660
            if tt.path_changed(conflict[1]):
1845
 
                existing_file, new_file = conflict[2], conflict[1]
 
1661
                tt.adjust_path(new_name, final_parent, conflict[2])
 
1662
                new_conflicts.add((c_type, 'Moved existing file to', 
 
1663
                                   conflict[2], conflict[1]))
1846
1664
            else:
1847
 
                existing_file, new_file = conflict[1], conflict[2]
1848
 
            new_name = tt.final_name(existing_file)+'.moved'
1849
 
            tt.adjust_path(new_name, final_parent, existing_file)
1850
 
            new_conflicts.add((c_type, 'Moved existing file to', 
1851
 
                               existing_file, new_file))
 
1665
                tt.adjust_path(new_name, final_parent, conflict[1])
 
1666
                new_conflicts.add((c_type, 'Moved existing file to', 
 
1667
                                  conflict[1], conflict[2]))
1852
1668
        elif c_type == 'parent loop':
1853
1669
            # break the loop by undoing one of the ops that caused the loop
1854
1670
            cur = conflict[1]
1867
1683
            except KeyError:
1868
1684
                tt.create_directory(trans_id)
1869
1685
                new_conflicts.add((c_type, 'Created directory', trans_id))
1870
 
                try:
1871
 
                    tt.final_name(trans_id)
1872
 
                except NoFinalPath:
1873
 
                    if path_tree is not None:
1874
 
                        file_id = tt.final_file_id(trans_id)
1875
 
                        entry = path_tree.inventory[file_id]
1876
 
                        parent_trans_id = tt.trans_id_file_id(entry.parent_id)
1877
 
                        tt.adjust_path(entry.name, parent_trans_id, trans_id)
1878
1686
        elif c_type == 'unversioned parent':
1879
1687
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1880
1688
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1907
1715
                                   file_id=modified_id, 
1908
1716
                                   conflict_path=conflicting_path,
1909
1717
                                   conflict_file_id=conflicting_id)
1910
 
 
1911
 
 
1912
 
class _FileMover(object):
1913
 
    """Moves and deletes files for TreeTransform, tracking operations"""
1914
 
 
1915
 
    def __init__(self):
1916
 
        self.past_renames = []
1917
 
        self.pending_deletions = []
1918
 
 
1919
 
    def rename(self, from_, to):
1920
 
        """Rename a file from one path to another.  Functions like os.rename"""
1921
 
        try:
1922
 
            os.rename(from_, to)
1923
 
        except OSError, e:
1924
 
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1925
 
                raise errors.FileExists(to, str(e))
1926
 
            raise
1927
 
        self.past_renames.append((from_, to))
1928
 
 
1929
 
    def pre_delete(self, from_, to):
1930
 
        """Rename a file out of the way and mark it for deletion.
1931
 
 
1932
 
        Unlike os.unlink, this works equally well for files and directories.
1933
 
        :param from_: The current file path
1934
 
        :param to: A temporary path for the file
1935
 
        """
1936
 
        self.rename(from_, to)
1937
 
        self.pending_deletions.append(to)
1938
 
 
1939
 
    def rollback(self):
1940
 
        """Reverse all renames that have been performed"""
1941
 
        for from_, to in reversed(self.past_renames):
1942
 
            os.rename(to, from_)
1943
 
        # after rollback, don't reuse _FileMover
1944
 
        past_renames = None
1945
 
        pending_deletions = None
1946
 
 
1947
 
    def apply_deletions(self):
1948
 
        """Apply all marked deletions"""
1949
 
        for path in self.pending_deletions:
1950
 
            delete_any(path)
1951
 
        # after apply_deletions, don't reuse _FileMover
1952
 
        past_renames = None
1953
 
        pending_deletions = None