/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import os
18
18
import errno
19
19
from stat import S_ISREG, S_IEXEC
 
20
import time
20
21
 
21
22
from bzrlib.lazy_import import lazy_import
22
23
lazy_import(globals(), """
30
31
    multiparent,
31
32
    osutils,
32
33
    revision as _mod_revision,
 
34
    ui,
33
35
    )
34
36
""")
35
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
38
                           ReusingTransform, CantMoveRoot,
37
39
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
40
                           UnableCreateSymlink)
39
41
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
48
50
    splitpath,
49
51
    supports_executable,
50
52
)
51
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
53
from bzrlib.progress import ProgressPhase
52
54
from bzrlib.symbol_versioning import (
53
55
        deprecated_function,
54
56
        deprecated_in,
78
80
class TreeTransformBase(object):
79
81
    """The base class for TreeTransform and its kin."""
80
82
 
81
 
    def __init__(self, tree, pb=DummyProgress(),
 
83
    def __init__(self, tree, pb=None,
82
84
                 case_sensitive=True):
83
85
        """Constructor.
84
86
 
85
87
        :param tree: The tree that will be transformed, but not necessarily
86
88
            the output tree.
87
 
        :param pb: A ProgressBar indicating how much progress is being made
 
89
        :param pb: ignored
88
90
        :param case_sensitive: If True, the target of the transform is
89
91
            case sensitive, not just case preserving.
90
92
        """
161
163
 
162
164
    def adjust_path(self, name, parent, trans_id):
163
165
        """Change the path that is assigned to a transaction id."""
 
166
        if parent is None:
 
167
            raise ValueError("Parent trans-id may not be None")
164
168
        if trans_id == self._new_root:
165
169
            raise CantMoveRoot
166
170
        self._new_name[trans_id] = name
167
171
        self._new_parent[trans_id] = parent
168
 
        if parent == ROOT_PARENT:
169
 
            if self._new_root is not None:
170
 
                raise ValueError("Cannot have multiple roots.")
171
 
            self._new_root = trans_id
172
172
 
173
173
    def adjust_root_path(self, name, parent):
174
174
        """Emulate moving the root by moving all children, instead.
202
202
        self.version_file(old_root_file_id, old_root)
203
203
        self.unversion_file(self._new_root)
204
204
 
 
205
    def fixup_new_roots(self):
 
206
        """Reinterpret requests to change the root directory
 
207
 
 
208
        Instead of creating a root directory, or moving an existing directory,
 
209
        all the attributes and children of the new root are applied to the
 
210
        existing root directory.
 
211
 
 
212
        This means that the old root trans-id becomes obsolete, so it is
 
213
        recommended only to invoke this after the root trans-id has become
 
214
        irrelevant.
 
215
        """
 
216
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
217
                     ROOT_PARENT]
 
218
        if len(new_roots) < 1:
 
219
            return
 
220
        if len(new_roots) != 1:
 
221
            raise ValueError('A tree cannot have two roots!')
 
222
        if self._new_root is None:
 
223
            self._new_root = new_roots[0]
 
224
            return
 
225
        old_new_root = new_roots[0]
 
226
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
227
        #       not listed as being removed? This code explicitly unversions
 
228
        #       the old root and versions it with the new file_id. Though that
 
229
        #       seems like an incomplete delta
 
230
 
 
231
        # unversion the new root's directory.
 
232
        file_id = self.final_file_id(old_new_root)
 
233
        if old_new_root in self._new_id:
 
234
            self.cancel_versioning(old_new_root)
 
235
        else:
 
236
            self.unversion_file(old_new_root)
 
237
        # if, at this stage, root still has an old file_id, zap it so we can
 
238
        # stick a new one in.
 
239
        if (self.tree_file_id(self._new_root) is not None and
 
240
            self._new_root not in self._removed_id):
 
241
            self.unversion_file(self._new_root)
 
242
        self.version_file(file_id, self._new_root)
 
243
 
 
244
        # Now move children of new root into old root directory.
 
245
        # Ensure all children are registered with the transaction, but don't
 
246
        # use directly-- some tree children have new parents
 
247
        list(self.iter_tree_children(old_new_root))
 
248
        # Move all children of new root into old root directory.
 
249
        for child in self.by_parent().get(old_new_root, []):
 
250
            self.adjust_path(self.final_name(child), self._new_root, child)
 
251
 
 
252
        # Ensure old_new_root has no directory.
 
253
        if old_new_root in self._new_contents:
 
254
            self.cancel_creation(old_new_root)
 
255
        else:
 
256
            self.delete_contents(old_new_root)
 
257
 
 
258
        # prevent deletion of root directory.
 
259
        if self._new_root in self._removed_contents:
 
260
            self.cancel_deletion(self._new_root)
 
261
 
 
262
        # destroy path info for old_new_root.
 
263
        del self._new_parent[old_new_root]
 
264
        del self._new_name[old_new_root]
 
265
 
205
266
    def trans_id_tree_file_id(self, inventory_id):
206
267
        """Determine the transaction id of a working tree file.
207
268
 
253
314
 
254
315
    def delete_contents(self, trans_id):
255
316
        """Schedule the contents of a path entry for deletion"""
 
317
        # Ensure that the object exists in the WorkingTree, this will raise an
 
318
        # exception if there is a problem
256
319
        self.tree_kind(trans_id)
257
320
        self._removed_contents.add(trans_id)
258
321
 
442
505
        conflicts.extend(self._overwrite_conflicts())
443
506
        return conflicts
444
507
 
 
508
    def _check_malformed(self):
 
509
        conflicts = self.find_conflicts()
 
510
        if len(conflicts) != 0:
 
511
            raise MalformedTransform(conflicts=conflicts)
 
512
 
445
513
    def _add_tree_children(self):
446
514
        """Add all the children of all active parents to the known paths.
447
515
 
854
922
    def get_preview_tree(self):
855
923
        """Return a tree representing the result of the transform.
856
924
 
857
 
        This tree only supports the subset of Tree functionality required
858
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
925
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
926
        it.
859
927
        """
860
928
        return _PreviewTree(self)
861
929
 
 
930
    def commit(self, branch, message, merge_parents=None, strict=False):
 
931
        """Commit the result of this TreeTransform to a branch.
 
932
 
 
933
        :param branch: The branch to commit to.
 
934
        :param message: The message to attach to the commit.
 
935
        :param merge_parents: Additional parents specified by pending merges.
 
936
        :return: The revision_id of the revision committed.
 
937
        """
 
938
        self._check_malformed()
 
939
        if strict:
 
940
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
941
            for trans_id in unversioned:
 
942
                if self.final_file_id(trans_id) is None:
 
943
                    raise errors.StrictCommitFailed()
 
944
 
 
945
        revno, last_rev_id = branch.last_revision_info()
 
946
        if last_rev_id == _mod_revision.NULL_REVISION:
 
947
            if merge_parents is not None:
 
948
                raise ValueError('Cannot supply merge parents for first'
 
949
                                 ' commit.')
 
950
            parent_ids = []
 
951
        else:
 
952
            parent_ids = [last_rev_id]
 
953
            if merge_parents is not None:
 
954
                parent_ids.extend(merge_parents)
 
955
        if self._tree.get_revision_id() != last_rev_id:
 
956
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
957
                             self._tree.get_revision_id())
 
958
        builder = branch.get_commit_builder(parent_ids)
 
959
        preview = self.get_preview_tree()
 
960
        list(builder.record_iter_changes(preview, last_rev_id,
 
961
                                         self.iter_changes()))
 
962
        builder.finish_inventory()
 
963
        revision_id = builder.commit(message)
 
964
        branch.set_last_revision_info(revno + 1, revision_id)
 
965
        return revision_id
 
966
 
862
967
    def _text_parent(self, trans_id):
863
968
        file_id = self.tree_file_id(trans_id)
864
969
        try:
958
1063
class DiskTreeTransform(TreeTransformBase):
959
1064
    """Tree transform storing its contents on disk."""
960
1065
 
961
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
1066
    def __init__(self, tree, limbodir, pb=None,
962
1067
                 case_sensitive=True):
963
1068
        """Constructor.
964
1069
        :param tree: The tree that will be transformed, but not necessarily
965
1070
            the output tree.
966
1071
        :param limbodir: A directory where new files can be stored until
967
1072
            they are installed in their proper places
968
 
        :param pb: A ProgressBar indicating how much progress is being made
 
1073
        :param pb: ignored
969
1074
        :param case_sensitive: If True, the target of the transform is
970
1075
            case sensitive, not just case preserving.
971
1076
        """
981
1086
        self._limbo_children_names = {}
982
1087
        # List of transform ids that need to be renamed from limbo into place
983
1088
        self._needs_rename = set()
 
1089
        self._creation_mtime = None
984
1090
 
985
1091
    def finalize(self):
986
1092
        """Release the working tree lock, if held, clean up limbo dir.
995
1101
                       self._new_contents.iteritems()]
996
1102
            entries.sort(reverse=True)
997
1103
            for path, trans_id, kind in entries:
998
 
                if kind == "directory":
999
 
                    os.rmdir(path)
1000
 
                else:
1001
 
                    os.unlink(path)
 
1104
                delete_any(path)
1002
1105
            try:
1003
 
                os.rmdir(self._limbodir)
 
1106
                delete_any(self._limbodir)
1004
1107
            except OSError:
1005
1108
                # We don't especially care *why* the dir is immortal.
1006
1109
                raise ImmortalLimbo(self._limbodir)
1007
1110
            try:
1008
1111
                if self._deletiondir is not None:
1009
 
                    os.rmdir(self._deletiondir)
 
1112
                    delete_any(self._deletiondir)
1010
1113
            except OSError:
1011
1114
                raise errors.ImmortalPendingDeletion(self._deletiondir)
1012
1115
        finally:
1015
1118
    def _limbo_name(self, trans_id):
1016
1119
        """Generate the limbo name of a file"""
1017
1120
        limbo_name = self._limbo_files.get(trans_id)
1018
 
        if limbo_name is not None:
1019
 
            return limbo_name
1020
 
        parent = self._new_parent.get(trans_id)
1021
 
        # if the parent directory is already in limbo (e.g. when building a
1022
 
        # tree), choose a limbo name inside the parent, to reduce further
1023
 
        # renames.
1024
 
        use_direct_path = False
1025
 
        if self._new_contents.get(parent) == 'directory':
1026
 
            filename = self._new_name.get(trans_id)
1027
 
            if filename is not None:
1028
 
                if parent not in self._limbo_children:
1029
 
                    self._limbo_children[parent] = set()
1030
 
                    self._limbo_children_names[parent] = {}
1031
 
                    use_direct_path = True
1032
 
                # the direct path can only be used if no other file has
1033
 
                # already taken this pathname, i.e. if the name is unused, or
1034
 
                # if it is already associated with this trans_id.
1035
 
                elif self._case_sensitive_target:
1036
 
                    if (self._limbo_children_names[parent].get(filename)
1037
 
                        in (trans_id, None)):
1038
 
                        use_direct_path = True
1039
 
                else:
1040
 
                    for l_filename, l_trans_id in\
1041
 
                        self._limbo_children_names[parent].iteritems():
1042
 
                        if l_trans_id == trans_id:
1043
 
                            continue
1044
 
                        if l_filename.lower() == filename.lower():
1045
 
                            break
1046
 
                    else:
1047
 
                        use_direct_path = True
1048
 
 
1049
 
        if use_direct_path:
1050
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
 
            self._limbo_children[parent].add(trans_id)
1052
 
            self._limbo_children_names[parent][filename] = trans_id
1053
 
        else:
1054
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1055
 
            self._needs_rename.add(trans_id)
1056
 
        self._limbo_files[trans_id] = limbo_name
 
1121
        if limbo_name is None:
 
1122
            limbo_name = self._generate_limbo_path(trans_id)
 
1123
            self._limbo_files[trans_id] = limbo_name
1057
1124
        return limbo_name
1058
1125
 
 
1126
    def _generate_limbo_path(self, trans_id):
 
1127
        """Generate a limbo path using the trans_id as the relative path.
 
1128
 
 
1129
        This is suitable as a fallback, and when the transform should not be
 
1130
        sensitive to the path encoding of the limbo directory.
 
1131
        """
 
1132
        self._needs_rename.add(trans_id)
 
1133
        return pathjoin(self._limbodir, trans_id)
 
1134
 
1059
1135
    def adjust_path(self, name, parent, trans_id):
1060
1136
        previous_parent = self._new_parent.get(trans_id)
1061
1137
        previous_name = self._new_name.get(trans_id)
1063
1139
        if (trans_id in self._limbo_files and
1064
1140
            trans_id not in self._needs_rename):
1065
1141
            self._rename_in_limbo([trans_id])
1066
 
            self._limbo_children[previous_parent].remove(trans_id)
1067
 
            del self._limbo_children_names[previous_parent][previous_name]
 
1142
            if previous_parent != parent:
 
1143
                self._limbo_children[previous_parent].remove(trans_id)
 
1144
            if previous_parent != parent or previous_name != name:
 
1145
                del self._limbo_children_names[previous_parent][previous_name]
1068
1146
 
1069
1147
    def _rename_in_limbo(self, trans_ids):
1070
1148
        """Fix limbo names so that the right final path is produced.
1082
1160
            if trans_id not in self._new_contents:
1083
1161
                continue
1084
1162
            new_path = self._limbo_name(trans_id)
1085
 
            os.rename(old_path, new_path)
 
1163
            osutils.rename(old_path, new_path)
 
1164
            for descendant in self._limbo_descendants(trans_id):
 
1165
                desc_path = self._limbo_files[descendant]
 
1166
                desc_path = new_path + desc_path[len(old_path):]
 
1167
                self._limbo_files[descendant] = desc_path
 
1168
 
 
1169
    def _limbo_descendants(self, trans_id):
 
1170
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1171
        descendants = set(self._limbo_children.get(trans_id, []))
 
1172
        for descendant in list(descendants):
 
1173
            descendants.update(self._limbo_descendants(descendant))
 
1174
        return descendants
1086
1175
 
1087
1176
    def create_file(self, contents, trans_id, mode_id=None):
1088
1177
        """Schedule creation of a new file.
1110
1199
            f.writelines(contents)
1111
1200
        finally:
1112
1201
            f.close()
 
1202
        self._set_mtime(name)
1113
1203
        self._set_mode(trans_id, mode_id, S_ISREG)
1114
1204
 
1115
1205
    def _read_file_chunks(self, trans_id):
1122
1212
    def _read_symlink_target(self, trans_id):
1123
1213
        return os.readlink(self._limbo_name(trans_id))
1124
1214
 
 
1215
    def _set_mtime(self, path):
 
1216
        """All files that are created get the same mtime.
 
1217
 
 
1218
        This time is set by the first object to be created.
 
1219
        """
 
1220
        if self._creation_mtime is None:
 
1221
            self._creation_mtime = time.time()
 
1222
        os.utime(path, (self._creation_mtime, self._creation_mtime))
 
1223
 
1125
1224
    def create_hardlink(self, path, trans_id):
1126
1225
        """Schedule creation of a hard link"""
1127
1226
        name = self._limbo_name(trans_id)
1241
1340
    FileMover does not delete files until it is sure that a rollback will not
1242
1341
    happen.
1243
1342
    """
1244
 
    def __init__(self, tree, pb=DummyProgress()):
 
1343
    def __init__(self, tree, pb=None):
1245
1344
        """Note: a tree_write lock is taken on the tree.
1246
1345
 
1247
1346
        Use TreeTransform.finalize() to release the lock (can be omitted if
1357
1456
                continue
1358
1457
            yield self.trans_id_tree_path(childpath)
1359
1458
 
 
1459
    def _generate_limbo_path(self, trans_id):
 
1460
        """Generate a limbo path using the final path if possible.
 
1461
 
 
1462
        This optimizes the performance of applying the tree transform by
 
1463
        avoiding renames.  These renames can be avoided only when the parent
 
1464
        directory is already scheduled for creation.
 
1465
 
 
1466
        If the final path cannot be used, falls back to using the trans_id as
 
1467
        the relpath.
 
1468
        """
 
1469
        parent = self._new_parent.get(trans_id)
 
1470
        # if the parent directory is already in limbo (e.g. when building a
 
1471
        # tree), choose a limbo name inside the parent, to reduce further
 
1472
        # renames.
 
1473
        use_direct_path = False
 
1474
        if self._new_contents.get(parent) == 'directory':
 
1475
            filename = self._new_name.get(trans_id)
 
1476
            if filename is not None:
 
1477
                if parent not in self._limbo_children:
 
1478
                    self._limbo_children[parent] = set()
 
1479
                    self._limbo_children_names[parent] = {}
 
1480
                    use_direct_path = True
 
1481
                # the direct path can only be used if no other file has
 
1482
                # already taken this pathname, i.e. if the name is unused, or
 
1483
                # if it is already associated with this trans_id.
 
1484
                elif self._case_sensitive_target:
 
1485
                    if (self._limbo_children_names[parent].get(filename)
 
1486
                        in (trans_id, None)):
 
1487
                        use_direct_path = True
 
1488
                else:
 
1489
                    for l_filename, l_trans_id in\
 
1490
                        self._limbo_children_names[parent].iteritems():
 
1491
                        if l_trans_id == trans_id:
 
1492
                            continue
 
1493
                        if l_filename.lower() == filename.lower():
 
1494
                            break
 
1495
                    else:
 
1496
                        use_direct_path = True
 
1497
 
 
1498
        if not use_direct_path:
 
1499
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1500
 
 
1501
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1502
        self._limbo_children[parent].add(trans_id)
 
1503
        self._limbo_children_names[parent][filename] = trans_id
 
1504
        return limbo_name
 
1505
 
1360
1506
 
1361
1507
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1362
1508
        """Apply all changes to the inventory and filesystem.
1373
1519
        :param _mover: Supply an alternate FileMover, for testing
1374
1520
        """
1375
1521
        if not no_conflicts:
1376
 
            conflicts = self.find_conflicts()
1377
 
            if len(conflicts) != 0:
1378
 
                raise MalformedTransform(conflicts=conflicts)
 
1522
            self._check_malformed()
1379
1523
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1380
1524
        try:
1381
1525
            if precomputed_delta is None:
1485
1629
                child_pb.update('removing file', num, len(tree_paths))
1486
1630
                full_path = self._tree.abspath(path)
1487
1631
                if trans_id in self._removed_contents:
1488
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1489
 
                                     trans_id))
1490
 
                elif trans_id in self._new_name or trans_id in \
1491
 
                    self._new_parent:
 
1632
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1633
                    mover.pre_delete(full_path, delete_path)
 
1634
                elif (trans_id in self._new_name
 
1635
                      or trans_id in self._new_parent):
1492
1636
                    try:
1493
1637
                        mover.rename(full_path, self._limbo_name(trans_id))
1494
1638
                    except OSError, e:
1548
1692
    unversioned files in the input tree.
1549
1693
    """
1550
1694
 
1551
 
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
 
1695
    def __init__(self, tree, pb=None, case_sensitive=True):
1552
1696
        tree.lock_read()
1553
1697
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1554
1698
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1599
1743
        self._all_children_cache = {}
1600
1744
        self._path2trans_id_cache = {}
1601
1745
        self._final_name_cache = {}
1602
 
 
1603
 
    def _changes(self, file_id):
1604
 
        for changes in self._transform.iter_changes():
1605
 
            if changes[0] == file_id:
1606
 
                return changes
 
1746
        self._iter_changes_cache = dict((c[0], c) for c in
 
1747
                                        self._transform.iter_changes())
1607
1748
 
1608
1749
    def _content_change(self, file_id):
1609
1750
        """Return True if the content of this file changed"""
1610
 
        changes = self._changes(file_id)
 
1751
        changes = self._iter_changes_cache.get(file_id)
1611
1752
        # changes[2] is true if the file content changed.  See
1612
1753
        # InterTree.iter_changes.
1613
1754
        return (changes is not None and changes[2])
1682
1823
    def __iter__(self):
1683
1824
        return iter(self.all_file_ids())
1684
1825
 
1685
 
    def has_id(self, file_id):
 
1826
    def _has_id(self, file_id, fallback_check):
1686
1827
        if file_id in self._transform._r_new_id:
1687
1828
            return True
1688
1829
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1689
1830
            trans_id in self._transform._removed_id]):
1690
1831
            return False
1691
1832
        else:
1692
 
            return self._transform._tree.has_id(file_id)
 
1833
            return fallback_check(file_id)
 
1834
 
 
1835
    def has_id(self, file_id):
 
1836
        return self._has_id(file_id, self._transform._tree.has_id)
 
1837
 
 
1838
    def has_or_had_id(self, file_id):
 
1839
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
1693
1840
 
1694
1841
    def _path2trans_id(self, path):
1695
1842
        # We must not use None here, because that is a valid value to store.
1748
1895
            if self._transform.final_file_id(trans_id) is None:
1749
1896
                yield self._final_paths._determine_path(trans_id)
1750
1897
 
1751
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
1898
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
1899
        yield_parents=False):
1752
1900
        for trans_id, parent_file_id in ordered_entries:
1753
1901
            file_id = self._transform.final_file_id(trans_id)
1754
1902
            if file_id is None:
1780
1928
                ordered_ids.append((trans_id, parent_file_id))
1781
1929
        return ordered_ids
1782
1930
 
1783
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1931
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1784
1932
        # This may not be a maximally efficient implementation, but it is
1785
1933
        # reasonably straightforward.  An implementation that grafts the
1786
1934
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1788
1936
        # position.
1789
1937
        ordered_ids = self._list_files_by_dir()
1790
1938
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1791
 
                                                      specific_file_ids):
 
1939
            specific_file_ids, yield_parents=yield_parents):
1792
1940
            yield unicode(self._final_paths.get_path(trans_id)), entry
1793
1941
 
1794
1942
    def _iter_entries_for_dir(self, dir_path):
1841
1989
    def get_file_mtime(self, file_id, path=None):
1842
1990
        """See Tree.get_file_mtime"""
1843
1991
        if not self._content_change(file_id):
1844
 
            return self._transform._tree.get_file_mtime(file_id, path)
 
1992
            return self._transform._tree.get_file_mtime(file_id)
1845
1993
        return self._stat_limbo_file(file_id).st_mtime
1846
1994
 
1847
1995
    def _file_size(self, entry, stat_value):
1901
2049
                statval = os.lstat(limbo_name)
1902
2050
                size = statval.st_size
1903
2051
                if not supports_executable():
1904
 
                    executable = None
 
2052
                    executable = False
1905
2053
                else:
1906
2054
                    executable = statval.st_mode & S_IEXEC
1907
2055
            else:
1909
2057
                executable = None
1910
2058
            if kind == 'symlink':
1911
2059
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1912
 
        if supports_executable():
1913
 
            executable = tt._new_executability.get(trans_id, executable)
 
2060
        executable = tt._new_executability.get(trans_id, executable)
1914
2061
        return kind, size, executable, link_or_sha1
1915
2062
 
1916
2063
    def iter_changes(self, from_tree, include_unchanged=False,
1947
2094
 
1948
2095
    def annotate_iter(self, file_id,
1949
2096
                      default_revision=_mod_revision.CURRENT_REVISION):
1950
 
        changes = self._changes(file_id)
 
2097
        changes = self._iter_changes_cache.get(file_id)
1951
2098
        if changes is None:
1952
2099
            get_old = True
1953
2100
        else:
1965
2112
            return old_annotation
1966
2113
        if not changed_content:
1967
2114
            return old_annotation
 
2115
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2116
        #       doing, however it fails slightly because it doesn't know what
 
2117
        #       the *other* revision_id is, so it doesn't know how to give the
 
2118
        #       other as the origin for some lines, they all get
 
2119
        #       'default_revision'
 
2120
        #       It would be nice to be able to use the new Annotator based
 
2121
        #       approach, as well.
1968
2122
        return annotate.reannotate([old_annotation],
1969
2123
                                   self.get_file(file_id).readlines(),
1970
2124
                                   default_revision)
2036
2190
        self.transform = transform
2037
2191
 
2038
2192
    def _determine_path(self, trans_id):
2039
 
        if trans_id == self.transform.root:
 
2193
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2040
2194
            return ""
2041
2195
        name = self.transform.final_name(trans_id)
2042
2196
        parent_id = self.transform.final_parent(trans_id)
2222
2376
        new_desired_files = desired_files
2223
2377
    else:
2224
2378
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2225
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2226
 
                         in iter if not (c or e[0] != e[1]))
 
2379
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2380
                     in iter if not (c or e[0] != e[1])]
 
2381
        if accelerator_tree.supports_content_filtering():
 
2382
            unchanged = [(f, p) for (f, p) in unchanged
 
2383
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2384
        unchanged = dict(unchanged)
2227
2385
        new_desired_files = []
2228
2386
        count = 0
2229
2387
        for file_id, (trans_id, tree_path) in desired_files:
2352
2510
        tt.create_directory(trans_id)
2353
2511
 
2354
2512
 
2355
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2356
 
    """Create new file contents according to tree contents."""
 
2513
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2514
    filter_tree_path=None):
 
2515
    """Create new file contents according to tree contents.
 
2516
    
 
2517
    :param filter_tree_path: the tree path to use to lookup
 
2518
      content filters to apply to the bytes output in the working tree.
 
2519
      This only applies if the working tree supports content filtering.
 
2520
    """
2357
2521
    kind = tree.kind(file_id)
2358
2522
    if kind == 'directory':
2359
2523
        tt.create_directory(trans_id)
2364
2528
                bytes = tree_file.readlines()
2365
2529
            finally:
2366
2530
                tree_file.close()
 
2531
        wt = tt._tree
 
2532
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2533
            filters = wt._content_filter_stack(filter_tree_path)
 
2534
            bytes = filtered_output_bytes(bytes, filters,
 
2535
                ContentFilterContext(filter_tree_path, tree))
2367
2536
        tt.create_file(bytes, trans_id)
2368
2537
    elif kind == "symlink":
2369
2538
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2421
2590
 
2422
2591
 
2423
2592
def revert(working_tree, target_tree, filenames, backups=False,
2424
 
           pb=DummyProgress(), change_reporter=None):
 
2593
           pb=None, change_reporter=None):
2425
2594
    """Revert a working tree's contents to those of a target tree."""
2426
2595
    target_tree.lock_read()
 
2596
    pb = ui.ui_factory.nested_progress_bar()
2427
2597
    tt = TreeTransform(working_tree, pb)
2428
2598
    try:
2429
2599
        pp = ProgressPhase("Revert phase", 3, pb)
2448
2618
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2449
2619
                              backups, pp, basis_tree=None,
2450
2620
                              merge_modified=None):
2451
 
    pp.next_phase()
2452
2621
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2453
2622
    try:
2454
2623
        if merge_modified is None:
2458
2627
                                      merge_modified, basis_tree)
2459
2628
    finally:
2460
2629
        child_pb.finished()
2461
 
    pp.next_phase()
2462
2630
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2463
2631
    try:
2464
2632
        raw_conflicts = resolve_conflicts(tt, child_pb,
2557
2725
                    parent_trans = ROOT_PARENT
2558
2726
                else:
2559
2727
                    parent_trans = tt.trans_id_file_id(parent[1])
2560
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2728
                if parent[0] is None and versioned[0]:
 
2729
                    tt.adjust_root_path(name[1], parent_trans)
 
2730
                else:
 
2731
                    tt.adjust_path(name[1], parent_trans, trans_id)
2561
2732
            if executable[0] != executable[1] and kind[1] == "file":
2562
2733
                tt.set_executability(executable[1], trans_id)
2563
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2564
 
            deferred_files):
2565
 
            tt.create_file(bytes, trans_id, mode_id)
 
2734
        if working_tree.supports_content_filtering():
 
2735
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2736
                target_tree.iter_files_bytes(deferred_files)):
 
2737
                file_id = deferred_files[index][0]
 
2738
                # We're reverting a tree to the target tree so using the
 
2739
                # target tree to find the file path seems the best choice
 
2740
                # here IMO - Ian C 27/Oct/2009
 
2741
                filter_tree_path = target_tree.id2path(file_id)
 
2742
                filters = working_tree._content_filter_stack(filter_tree_path)
 
2743
                bytes = filtered_output_bytes(bytes, filters,
 
2744
                    ContentFilterContext(filter_tree_path, working_tree))
 
2745
                tt.create_file(bytes, trans_id, mode_id)
 
2746
        else:
 
2747
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2748
                deferred_files):
 
2749
                tt.create_file(bytes, trans_id, mode_id)
 
2750
        tt.fixup_new_roots()
2566
2751
    finally:
2567
2752
        if basis_tree is not None:
2568
2753
            basis_tree.unlock()
2569
2754
    return merge_modified
2570
2755
 
2571
2756
 
2572
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
2757
def resolve_conflicts(tt, pb=None, pass_func=None):
2573
2758
    """Make many conflict-resolution attempts, but die if they fail"""
2574
2759
    if pass_func is None:
2575
2760
        pass_func = conflict_pass
2576
2761
    new_conflicts = set()
 
2762
    pb = ui.ui_factory.nested_progress_bar()
2577
2763
    try:
2578
2764
        for n in range(10):
2579
2765
            pb.update('Resolution pass', n+1, 10)
2583
2769
            new_conflicts.update(pass_func(tt, conflicts))
2584
2770
        raise MalformedTransform(conflicts=conflicts)
2585
2771
    finally:
2586
 
        pb.clear()
 
2772
        pb.finished()
2587
2773
 
2588
2774
 
2589
2775
def conflict_pass(tt, conflicts, path_tree=None):
2638
2824
                        # special-case the other tree root (move its
2639
2825
                        # children to current root)
2640
2826
                        if entry.parent_id is None:
2641
 
                            create=False
 
2827
                            create = False
2642
2828
                            moved = _reparent_transform_children(
2643
2829
                                tt, trans_id, tt.root)
2644
2830
                            for child in moved:
2712
2898
        self.pending_deletions = []
2713
2899
 
2714
2900
    def rename(self, from_, to):
2715
 
        """Rename a file from one path to another.  Functions like os.rename"""
 
2901
        """Rename a file from one path to another."""
2716
2902
        try:
2717
 
            os.rename(from_, to)
 
2903
            osutils.rename(from_, to)
2718
2904
        except OSError, e:
2719
2905
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2720
2906
                raise errors.FileExists(to, str(e))
2734
2920
    def rollback(self):
2735
2921
        """Reverse all renames that have been performed"""
2736
2922
        for from_, to in reversed(self.past_renames):
2737
 
            os.rename(to, from_)
 
2923
            osutils.rename(to, from_)
2738
2924
        # after rollback, don't reuse _FileMover
2739
2925
        past_renames = None
2740
2926
        pending_deletions = None