/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 breezy/transform.py

Merge test-run support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
from breezy import (
34
34
    annotate,
35
35
    bencode,
36
 
    cleanup,
37
36
    controldir,
38
37
    commit,
39
38
    conflicts,
53
52
""")
54
53
from .errors import (DuplicateKey, MalformedTransform,
55
54
                     ReusingTransform, CantMoveRoot,
56
 
                     ImmortalLimbo, NoFinalPath)
 
55
                     ImmortalLimbo, NoFinalPath,
 
56
                     UnableCreateSymlink)
57
57
from .filters import filtered_output_bytes, ContentFilterContext
58
58
from .mutabletree import MutableTree
59
59
from .osutils import (
60
60
    delete_any,
61
61
    file_kind,
 
62
    has_symlinks,
62
63
    pathjoin,
63
64
    sha_file,
64
65
    splitpath,
65
 
    supports_symlinks,
66
66
    )
67
67
from .progress import ProgressPhase
68
68
from .sixish import (
71
71
    viewvalues,
72
72
    )
73
73
from .tree import (
74
 
    InterTree,
75
 
    TreeChange,
 
74
    find_previous_path,
76
75
    )
77
76
 
78
77
 
79
78
ROOT_PARENT = "root-parent"
80
79
 
81
 
 
82
80
def unique_add(map, key, value):
83
81
    if key in map:
84
82
        raise DuplicateKey(key=key)
85
83
    map[key] = value
86
84
 
87
85
 
 
86
 
88
87
class _TransformResults(object):
89
88
    def __init__(self, modified_paths, rename_count):
90
89
        object.__init__(self)
176
175
    def _assign_id(self):
177
176
        """Produce a new tranform id"""
178
177
        new_id = "new-%s" % self._id_number
179
 
        self._id_number += 1
 
178
        self._id_number +=1
180
179
        return new_id
181
180
 
182
181
    def create_path(self, name, parent):
260
259
            self.unversion_file(old_new_root)
261
260
        # if, at this stage, root still has an old file_id, zap it so we can
262
261
        # stick a new one in.
263
 
        if (self.tree_file_id(self._new_root) is not None
264
 
                and self._new_root not in self._removed_id):
 
262
        if (self.tree_file_id(self._new_root) is not None and
 
263
            self._new_root not in self._removed_id):
265
264
            self.unversion_file(self._new_root)
266
265
        if file_id is not None:
267
266
            self.version_file(file_id, self._new_root)
426
425
        changed_ids.update(changed_kind)
427
426
        # To find entries with changed parent_ids, find parents which existed,
428
427
        # but changed file_id.
 
428
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
429
429
        # Now add all their children to the set.
430
430
        for parent_trans_id in new_file_id:
431
431
            changed_ids.update(self.iter_tree_children(parent_trans_id))
456
456
            return None
457
457
        # the file is old; the old id is still valid
458
458
        if self._new_root == trans_id:
459
 
            return self._tree.path2id('')
 
459
            return self._tree.get_root_id()
460
460
        return self._tree.path2id(path)
461
461
 
462
462
    def final_file_id(self, trans_id):
512
512
        by_parent = {}
513
513
        items = list(viewitems(self._new_parent))
514
514
        items.extend((t, self.final_parent(t))
515
 
                     for t in list(self._tree_id_paths))
 
515
            for t in list(self._tree_id_paths))
516
516
        for trans_id, parent_id in items:
517
517
            if parent_id not in by_parent:
518
518
                by_parent[parent_id] = set()
605
605
 
606
606
        :param name: The basename of the file.
607
607
 
608
 
        :param target_id: The directory trans_id where the backup should
 
608
        :param target_id: The directory trans_id where the backup should 
609
609
            be placed.
610
610
        """
611
611
        known_children = self.by_parent().get(target_id, [])
643
643
            for child_id in children:
644
644
                if self.final_file_id(child_id) is not None:
645
645
                    conflicts.append(('unversioned parent', parent_id))
646
 
                    break
 
646
                    break;
647
647
        return conflicts
648
648
 
649
649
    def _improper_versioning(self):
654
654
        conflicts = []
655
655
        for trans_id in self._new_id:
656
656
            kind = self.final_kind(trans_id)
657
 
            if kind == 'symlink' and not self._tree.supports_symlinks():
658
 
                # Ignore symlinks as they are not supported on this platform
659
 
                continue
660
657
            if kind is None:
661
658
                conflicts.append(('versioning no contents', trans_id))
662
659
                continue
689
686
                continue
690
687
            if trans_id not in self._removed_contents:
691
688
                conflicts.append(('overwrite', trans_id,
692
 
                                  self.final_name(trans_id)))
 
689
                                 self.final_name(trans_id)))
693
690
        return conflicts
694
691
 
695
692
    def _duplicate_entries(self, by_parent):
716
713
                    continue
717
714
                if name == last_name:
718
715
                    conflicts.append(('duplicate', last_trans_id, trans_id,
719
 
                                      name))
 
716
                    name))
720
717
                last_name = name
721
718
                last_trans_id = trans_id
722
719
        return conflicts
724
721
    def _duplicate_ids(self):
725
722
        """Each inventory id may only be used once"""
726
723
        conflicts = []
727
 
        try:
728
 
            all_ids = self._tree.all_file_ids()
729
 
        except errors.UnsupportedOperation:
730
 
            # it's okay for non-file-id trees to raise UnsupportedOperation.
731
 
            return []
732
724
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
733
725
                                self._removed_id))
 
726
        all_ids = self._tree.all_file_ids()
734
727
        active_tree_ids = all_ids.difference(removed_tree_ids)
735
728
        for trans_id, file_id in viewitems(self._new_id):
736
729
            if file_id in active_tree_ids:
910
903
        if from_versioned:
911
904
            # get data from working tree if versioned
912
905
            from_entry = next(self._tree.iter_entries_by_dir(
913
 
                specific_files=[from_path]))[1]
 
906
                    specific_files=[from_path]))[1]
914
907
            from_name = from_entry.name
915
908
            from_parent = from_entry.parent_id
916
909
        else:
997
990
            if from_kind != to_kind:
998
991
                modified = True
999
992
            elif to_kind in ('file', 'symlink') and (
1000
 
                    to_trans_id != from_trans_id
1001
 
                    or to_trans_id in self._new_contents):
 
993
                to_trans_id != from_trans_id or
 
994
                to_trans_id in self._new_contents):
1002
995
                modified = True
1003
 
            if (not modified and from_versioned == to_versioned
1004
 
                and from_parent == to_parent and from_name == to_name
1005
 
                    and from_executable == to_executable):
 
996
            if (not modified and from_versioned == to_versioned and
 
997
                from_parent==to_parent and from_name == to_name and
 
998
                from_executable == to_executable):
1006
999
                continue
1007
 
            results.append(
1008
 
                TreeChange(
1009
 
                    file_id, (from_path, to_path), modified,
1010
 
                    (from_versioned, to_versioned),
1011
 
                    (from_parent, to_parent),
1012
 
                    (from_name, to_name),
1013
 
                    (from_kind, to_kind),
1014
 
                    (from_executable, to_executable)))
1015
 
 
1016
 
        def path_key(c):
1017
 
            return (c.path[0] or '', c.path[1] or '')
1018
 
        return iter(sorted(results, key=path_key))
 
1000
            results.append((file_id, (from_path, to_path), modified,
 
1001
                   (from_versioned, to_versioned),
 
1002
                   (from_parent, to_parent),
 
1003
                   (from_name, to_name),
 
1004
                   (from_kind, to_kind),
 
1005
                   (from_executable, to_executable)))
 
1006
        return iter(sorted(results, key=lambda x:x[1]))
1019
1007
 
1020
1008
    def get_preview_tree(self):
1021
1009
        """Return a tree representing the result of the transform.
1067
1055
                parent_ids.extend(merge_parents)
1068
1056
        if self._tree.get_revision_id() != last_rev_id:
1069
1057
            raise ValueError('TreeTransform not based on branch basis: %s' %
1070
 
                             self._tree.get_revision_id().decode('utf-8'))
 
1058
                             self._tree.get_revision_id())
1071
1059
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
1072
1060
        builder = branch.get_commit_builder(parent_ids,
1073
1061
                                            timestamp=timestamp,
1111
1099
 
1112
1100
        :param serializer: A Serialiser like pack.ContainerSerializer.
1113
1101
        """
1114
 
        new_name = {k.encode('utf-8'): v.encode('utf-8')
1115
 
                    for k, v in viewitems(self._new_name)}
1116
 
        new_parent = {k.encode('utf-8'): v.encode('utf-8')
1117
 
                      for k, v in viewitems(self._new_parent)}
1118
 
        new_id = {k.encode('utf-8'): v
1119
 
                  for k, v in viewitems(self._new_id)}
1120
 
        new_executability = {k.encode('utf-8'): int(v)
1121
 
                             for k, v in viewitems(self._new_executability)}
1122
 
        tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1123
 
                         for k, v in viewitems(self._tree_path_ids)}
1124
 
        non_present_ids = {k: v.encode('utf-8')
1125
 
                           for k, v in viewitems(self._non_present_ids)}
1126
 
        removed_contents = [trans_id.encode('utf-8')
1127
 
                            for trans_id in self._removed_contents]
1128
 
        removed_id = [trans_id.encode('utf-8')
1129
 
                      for trans_id in self._removed_id]
 
1102
        new_name = dict((k, v.encode('utf-8')) for k, v in
 
1103
                        viewitems(self._new_name))
 
1104
        new_executability = dict((k, int(v)) for k, v in
 
1105
                                 viewitems(self._new_executability))
 
1106
        tree_path_ids = dict((k.encode('utf-8'), v)
 
1107
                             for k, v in viewitems(self._tree_path_ids))
1130
1108
        attribs = {
1131
1109
            b'_id_number': self._id_number,
1132
1110
            b'_new_name': new_name,
1133
 
            b'_new_parent': new_parent,
 
1111
            b'_new_parent': self._new_parent,
1134
1112
            b'_new_executability': new_executability,
1135
 
            b'_new_id': new_id,
 
1113
            b'_new_id': self._new_id,
1136
1114
            b'_tree_path_ids': tree_path_ids,
1137
 
            b'_removed_id': removed_id,
1138
 
            b'_removed_contents': removed_contents,
1139
 
            b'_non_present_ids': non_present_ids,
 
1115
            b'_removed_id': list(self._removed_id),
 
1116
            b'_removed_contents': list(self._removed_contents),
 
1117
            b'_non_present_ids': self._non_present_ids,
1140
1118
            }
1141
1119
        yield serializer.bytes_record(bencode.bencode(attribs),
1142
1120
                                      ((b'attribs',),))
1143
 
        for trans_id, kind in sorted(viewitems(self._new_contents)):
 
1121
        for trans_id, kind in viewitems(self._new_contents):
1144
1122
            if kind == 'file':
1145
1123
                with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
1124
                    lines = cur_file.readlines()
1151
1129
                content = b''
1152
1130
            if kind == 'symlink':
1153
1131
                content = self._read_symlink_target(trans_id)
1154
 
                if not isinstance(content, bytes):
1155
 
                    content = content.encode('utf-8')
1156
 
            yield serializer.bytes_record(
1157
 
                content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
 
1132
            yield serializer.bytes_record(content, ((trans_id, kind),))
1158
1133
 
1159
1134
    def deserialize(self, records):
1160
1135
        """Deserialize a stored TreeTransform.
1165
1140
        names, content = next(records)
1166
1141
        attribs = bencode.bdecode(content)
1167
1142
        self._id_number = attribs[b'_id_number']
1168
 
        self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1169
 
                          for k, v in viewitems(attribs[b'_new_name'])}
1170
 
        self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1171
 
                            for k, v in viewitems(attribs[b'_new_parent'])}
1172
 
        self._new_executability = {
1173
 
            k.decode('utf-8'): bool(v)
1174
 
            for k, v in viewitems(attribs[b'_new_executability'])}
1175
 
        self._new_id = {k.decode('utf-8'): v
1176
 
                        for k, v in viewitems(attribs[b'_new_id'])}
1177
 
        self._r_new_id = {v: k for k, v in viewitems(self._new_id)}
 
1143
        self._new_name = dict((k, v.decode('utf-8'))
 
1144
                              for k, v in viewitems(attribs[b'_new_name']))
 
1145
        self._new_parent = attribs[b'_new_parent']
 
1146
        self._new_executability = dict((k, bool(v))
 
1147
            for k, v in viewitems(attribs[b'_new_executability']))
 
1148
        self._new_id = attribs[b'_new_id']
 
1149
        self._r_new_id = dict((v, k) for k, v in viewitems(self._new_id))
1178
1150
        self._tree_path_ids = {}
1179
1151
        self._tree_id_paths = {}
1180
1152
        for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
1181
1153
            path = bytepath.decode('utf-8')
1182
 
            trans_id = trans_id.decode('utf-8')
1183
1154
            self._tree_path_ids[path] = trans_id
1184
1155
            self._tree_id_paths[trans_id] = path
1185
 
        self._removed_id = {trans_id.decode('utf-8')
1186
 
                            for trans_id in attribs[b'_removed_id']}
1187
 
        self._removed_contents = set(
1188
 
            trans_id.decode('utf-8')
1189
 
            for trans_id in attribs[b'_removed_contents'])
1190
 
        self._non_present_ids = {
1191
 
            k: v.decode('utf-8')
1192
 
            for k, v in viewitems(attribs[b'_non_present_ids'])}
 
1156
        self._removed_id = set(attribs[b'_removed_id'])
 
1157
        self._removed_contents = set(attribs[b'_removed_contents'])
 
1158
        self._non_present_ids = attribs[b'_non_present_ids']
1193
1159
        for ((trans_id, kind),), content in records:
1194
 
            trans_id = trans_id.decode('utf-8')
1195
 
            kind = kind.decode('ascii')
1196
1160
            if kind == 'file':
1197
1161
                mpdiff = multiparent.MultiParent.from_patch(content)
1198
1162
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1202
1166
            if kind == 'symlink':
1203
1167
                self.create_symlink(content.decode('utf-8'), trans_id)
1204
1168
 
1205
 
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1206
 
        """Schedule creation of a new file.
1207
 
 
1208
 
        :seealso: new_file.
1209
 
 
1210
 
        :param contents: an iterator of strings, all of which will be written
1211
 
            to the target destination.
1212
 
        :param trans_id: TreeTransform handle
1213
 
        :param mode_id: If not None, force the mode of the target file to match
1214
 
            the mode of the object referenced by mode_id.
1215
 
            Otherwise, we will try to preserve mode bits of an existing file.
1216
 
        :param sha1: If the sha1 of this content is already known, pass it in.
1217
 
            We can use it to prevent future sha1 computations.
1218
 
        """
1219
 
        raise NotImplementedError(self.create_file)
1220
 
 
1221
 
    def create_directory(self, trans_id):
1222
 
        """Schedule creation of a new directory.
1223
 
 
1224
 
        See also new_directory.
1225
 
        """
1226
 
        raise NotImplementedError(self.create_directory)
1227
 
 
1228
 
    def create_symlink(self, target, trans_id):
1229
 
        """Schedule creation of a new symbolic link.
1230
 
 
1231
 
        target is a bytestring.
1232
 
        See also new_symlink.
1233
 
        """
1234
 
        raise NotImplementedError(self.create_symlink)
1235
 
 
1236
 
    def create_hardlink(self, path, trans_id):
1237
 
        """Schedule creation of a hard link"""
1238
 
        raise NotImplementedError(self.create_hardlink)
1239
 
 
1240
 
    def cancel_creation(self, trans_id):
1241
 
        """Cancel the creation of new file contents."""
1242
 
        raise NotImplementedError(self.cancel_creation)
1243
 
 
1244
1169
 
1245
1170
class DiskTreeTransform(TreeTransformBase):
1246
1171
    """Tree transform storing its contents on disk."""
1247
1172
 
1248
 
    def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
 
1173
    def __init__(self, tree, limbodir, pb=None,
 
1174
                 case_sensitive=True):
1249
1175
        """Constructor.
1250
1176
        :param tree: The tree that will be transformed, but not necessarily
1251
1177
            the output tree.
1269
1195
        # List of transform ids that need to be renamed from limbo into place
1270
1196
        self._needs_rename = set()
1271
1197
        self._creation_mtime = None
1272
 
        self._create_symlinks = osutils.supports_symlinks(self._limbodir)
1273
1198
 
1274
1199
    def finalize(self):
1275
1200
        """Release the working tree lock, if held, clean up limbo dir.
1307
1232
 
1308
1233
    def _limbo_supports_executable(self):
1309
1234
        """Check if the limbo path supports the executable bit."""
1310
 
        return osutils.supports_executable(self._limbodir)
 
1235
        # FIXME: Check actual file system capabilities of limbodir
 
1236
        return osutils.supports_executable()
1311
1237
 
1312
1238
    def _limbo_name(self, trans_id):
1313
1239
        """Generate the limbo name of a file"""
1330
1256
        previous_parent = self._new_parent.get(trans_id)
1331
1257
        previous_name = self._new_name.get(trans_id)
1332
1258
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
1333
 
        if (trans_id in self._limbo_files
1334
 
                and trans_id not in self._needs_rename):
 
1259
        if (trans_id in self._limbo_files and
 
1260
            trans_id not in self._needs_rename):
1335
1261
            self._rename_in_limbo([trans_id])
1336
1262
            if previous_parent != parent:
1337
1263
                self._limbo_children[previous_parent].remove(trans_id)
1419
1345
            raise errors.HardLinkNotSupported(path)
1420
1346
        try:
1421
1347
            unique_add(self._new_contents, trans_id, 'file')
1422
 
        except BaseException:
 
1348
        except:
1423
1349
            # Clean up the file, it never got registered so
1424
1350
            # TreeTransform.finalize() won't clean it up.
1425
1351
            os.unlink(name)
1439
1365
        target is a bytestring.
1440
1366
        See also new_symlink.
1441
1367
        """
1442
 
        if self._create_symlinks:
 
1368
        if has_symlinks():
1443
1369
            os.symlink(target, self._limbo_name(trans_id))
 
1370
            unique_add(self._new_contents, trans_id, 'symlink')
1444
1371
        else:
1445
1372
            try:
1446
1373
                path = FinalPaths(self).get_path(trans_id)
1447
1374
            except KeyError:
1448
1375
                path = None
1449
 
            trace.warning(
1450
 
                'Unable to create symlink "%s" on this filesystem.' % (path,))
1451
 
        # We add symlink to _new_contents even if they are unsupported
1452
 
        # and not created. These entries are subsequently used to avoid
1453
 
        # conflicts on platforms that don't support symlink
1454
 
        unique_add(self._new_contents, trans_id, 'symlink')
 
1376
            raise UnableCreateSymlink(path=path)
1455
1377
 
1456
1378
    def cancel_creation(self, trans_id):
1457
1379
        """Cancel the creation of new file contents."""
1609
1531
    FileMover does not delete files until it is sure that a rollback will not
1610
1532
    happen.
1611
1533
    """
1612
 
 
1613
1534
    def __init__(self, tree, pb=None):
1614
1535
        """Note: a tree_write lock is taken on the tree.
1615
1536
 
1628
1549
            osutils.ensure_empty_directory_exists(
1629
1550
                deletiondir,
1630
1551
                errors.ExistingPendingDeletion)
1631
 
        except BaseException:
 
1552
        except:
1632
1553
            tree.unlock()
1633
1554
            raise
1634
1555
 
1707
1628
        try:
1708
1629
            children = os.listdir(self._tree.abspath(path))
1709
1630
        except OSError as e:
1710
 
            if not (osutils._is_error_enotdir(e) or
1711
 
                    e.errno in (errno.ENOENT, errno.ESRCH)):
 
1631
            if not (osutils._is_error_enotdir(e)
 
1632
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
1712
1633
                raise
1713
1634
            return
1714
1635
 
1745
1666
                # if it is already associated with this trans_id.
1746
1667
                elif self._case_sensitive_target:
1747
1668
                    if (self._limbo_children_names[parent].get(filename)
1748
 
                            in (trans_id, None)):
 
1669
                        in (trans_id, None)):
1749
1670
                        use_direct_path = True
1750
1671
                else:
1751
1672
                    for l_filename, l_trans_id in viewitems(
1765
1686
        self._limbo_children_names[parent][filename] = trans_id
1766
1687
        return limbo_name
1767
1688
 
 
1689
 
1768
1690
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1769
1691
        """Apply all changes to the inventory and filesystem.
1770
1692
 
1800
1722
                self._apply_removals(mover)
1801
1723
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1802
1724
                modified_paths = self._apply_insertions(mover)
1803
 
            except BaseException:
 
1725
            except:
1804
1726
                mover.rollback()
1805
1727
                raise
1806
1728
            else:
1821
1743
        with ui.ui_factory.nested_progress_bar() as child_pb:
1822
1744
            for num, trans_id in enumerate(self._removed_id):
1823
1745
                if (num % 10) == 0:
1824
 
                    child_pb.update(gettext('removing file'),
1825
 
                                    num, total_entries)
 
1746
                    child_pb.update(gettext('removing file'), num, total_entries)
1826
1747
                if trans_id == self._new_root:
1827
 
                    file_id = self._tree.path2id('')
 
1748
                    file_id = self._tree.get_root_id()
1828
1749
                else:
1829
1750
                    file_id = self.tree_file_id(trans_id)
1830
1751
                # File-id isn't really being deleted, just moved
1834
1755
                inventory_delta.append((path, None, file_id, None))
1835
1756
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1836
1757
                                     new_paths)
 
1758
            final_kinds = {}
1837
1759
            for num, (path, trans_id) in enumerate(new_paths):
1838
1760
                if (num % 10) == 0:
1839
1761
                    child_pb.update(gettext('adding file'),
1841
1763
                file_id = new_path_file_ids[trans_id]
1842
1764
                if file_id is None:
1843
1765
                    continue
 
1766
                needs_entry = False
1844
1767
                kind = self.final_kind(trans_id)
1845
1768
                if kind is None:
1846
 
                    kind = self._tree.stored_kind(self._tree.id2path(file_id))
 
1769
                    kind = self._tree.stored_kind(
 
1770
                            self._tree.id2path(file_id), file_id)
1847
1771
                parent_trans_id = self.final_parent(trans_id)
1848
1772
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1849
1773
                if parent_file_id is None:
1856
1780
                        None, self._new_reference_revision[trans_id])
1857
1781
                else:
1858
1782
                    new_entry = inventory.make_entry(kind,
1859
 
                                                     self.final_name(trans_id),
1860
 
                                                     parent_file_id, file_id)
 
1783
                        self.final_name(trans_id),
 
1784
                        parent_file_id, file_id)
1861
1785
                try:
1862
1786
                    old_path = self._tree.id2path(new_entry.file_id)
1863
1787
                except errors.NoSuchId:
1889
1813
                if trans_id in self._removed_contents:
1890
1814
                    delete_path = os.path.join(self._deletiondir, trans_id)
1891
1815
                    mover.pre_delete(full_path, delete_path)
1892
 
                elif (trans_id in self._new_name or
1893
 
                      trans_id in self._new_parent):
 
1816
                elif (trans_id in self._new_name
 
1817
                      or trans_id in self._new_parent):
1894
1818
                    try:
1895
1819
                        mover.rename(full_path, self._limbo_name(trans_id))
1896
1820
                    except errors.TransformRenameFailed as e:
1911
1835
        """
1912
1836
        new_paths = self.new_paths(filesystem_only=True)
1913
1837
        modified_paths = []
 
1838
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1839
                                 new_paths)
1914
1840
        with ui.ui_factory.nested_progress_bar() as child_pb:
1915
1841
            for num, (path, trans_id) in enumerate(new_paths):
1916
1842
                if (num % 10) == 0:
1917
 
                    child_pb.update(gettext('adding file'),
1918
 
                                    num, len(new_paths))
 
1843
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1919
1844
                full_path = self._tree.abspath(path)
1920
1845
                if trans_id in self._needs_rename:
1921
1846
                    try:
1929
1854
                    # TODO: if trans_id in self._observed_sha1s, we should
1930
1855
                    #       re-stat the final target, since ctime will be
1931
1856
                    #       updated by the change.
1932
 
                if (trans_id in self._new_contents
1933
 
                        or self.path_changed(trans_id)):
 
1857
                if (trans_id in self._new_contents or
 
1858
                    self.path_changed(trans_id)):
1934
1859
                    if trans_id in self._new_contents:
1935
1860
                        modified_paths.append(full_path)
1936
1861
                if trans_id in self._new_executability:
1965
1890
        paths = FinalPaths(self)
1966
1891
        for trans_id, observed in viewitems(self._observed_sha1s):
1967
1892
            path = paths.get_path(trans_id)
1968
 
            self._tree._observed_sha1(path, observed)
 
1893
            # We could get the file_id, but dirstate prefers to use the path
 
1894
            # anyway, and it is 'cheaper' to determine.
 
1895
            # file_id = self._new_id[trans_id]
 
1896
            self._tree._observed_sha1(None, path, observed)
1969
1897
 
1970
1898
 
1971
1899
class TransformPreview(DiskTreeTransform):
2008
1936
            path = self._tree_id_paths[parent_id]
2009
1937
        except KeyError:
2010
1938
            return
2011
 
        try:
2012
 
            entry = next(self._tree.iter_entries_by_dir(
 
1939
        entry = next(self._tree.iter_entries_by_dir(
2013
1940
                specific_files=[path]))[1]
2014
 
        except StopIteration:
2015
 
            return
2016
1941
        children = getattr(entry, 'children', {})
2017
1942
        for child in children:
2018
1943
            childpath = joinpath(path, child)
2033
1958
        self._all_children_cache = {}
2034
1959
        self._path2trans_id_cache = {}
2035
1960
        self._final_name_cache = {}
2036
 
        self._iter_changes_cache = dict((c.file_id, c) for c in
 
1961
        self._iter_changes_cache = dict((c[0], c) for c in
2037
1962
                                        self._transform.iter_changes())
2038
1963
 
2039
 
    def supports_tree_reference(self):
2040
 
        # TODO(jelmer): Support tree references in _PreviewTree.
2041
 
        # return self._transform._tree.supports_tree_reference()
2042
 
        return False
2043
 
 
2044
1964
    def _content_change(self, file_id):
2045
1965
        """Return True if the content of this file changed"""
2046
1966
        changes = self._iter_changes_cache.get(file_id)
2047
 
        return (changes is not None and changes.changed_content)
 
1967
        # changes[2] is true if the file content changed.  See
 
1968
        # InterTree.iter_changes.
 
1969
        return (changes is not None and changes[2])
2048
1970
 
2049
1971
    def _get_repository(self):
2050
1972
        repo = getattr(self._transform._tree, '_repository', None)
2061
1983
 
2062
1984
    def _get_file_revision(self, path, file_id, vf, tree_revision):
2063
1985
        parent_keys = [
2064
 
            (file_id, t.get_file_revision(t.id2path(file_id)))
2065
 
            for t in self._iter_parent_trees()]
 
1986
                (file_id, t.get_file_revision(t.id2path(file_id), file_id))
 
1987
                for t in self._iter_parent_trees()]
2066
1988
        vf.add_lines((file_id, tree_revision), parent_keys,
2067
 
                     self.get_file_lines(path))
 
1989
                     self.get_file_lines(path, file_id))
2068
1990
        repo = self._get_repository()
2069
1991
        base_vf = repo.texts
2070
1992
        if base_vf not in vf.fallback_versionedfiles:
2088
2010
            executable = False
2089
2011
        else:
2090
2012
            file_id = self._transform.final_file_id(self._path2trans_id(path))
2091
 
            executable = self.is_executable(path)
 
2013
            executable = self.is_executable(path, file_id)
2092
2014
        return kind, executable, None
2093
2015
 
2094
2016
    def is_locked(self):
2106
2028
        """This Tree does not use inventory as its backing data."""
2107
2029
        raise NotImplementedError(_PreviewTree.root_inventory)
2108
2030
 
 
2031
    def get_root_id(self):
 
2032
        return self._transform.final_file_id(self._transform.root)
 
2033
 
2109
2034
    def all_file_ids(self):
2110
2035
        tree_ids = set(self._transform._tree.all_file_ids())
2111
2036
        tree_ids.difference_update(self._transform.tree_file_id(t)
2114
2039
        return tree_ids
2115
2040
 
2116
2041
    def all_versioned_paths(self):
2117
 
        tree_paths = set(self._transform._tree.all_versioned_paths())
2118
 
 
2119
 
        tree_paths.difference_update(
2120
 
            self._transform.trans_id_tree_path(t)
2121
 
            for t in self._transform._removed_id)
2122
 
 
2123
 
        tree_paths.update(
2124
 
            self._final_paths._determine_path(t)
2125
 
            for t in self._transform._new_id)
2126
 
 
2127
 
        return tree_paths
 
2042
        return {self.id2path(fid) for fid in self.all_file_ids()}
 
2043
 
 
2044
    def _has_id(self, file_id, fallback_check):
 
2045
        if file_id in self._transform._r_new_id:
 
2046
            return True
 
2047
        elif file_id in {self._transform.tree_file_id(trans_id) for
 
2048
            trans_id in self._transform._removed_id}:
 
2049
            return False
 
2050
        else:
 
2051
            return fallback_check(file_id)
 
2052
 
 
2053
    def has_id(self, file_id):
 
2054
        return self._has_id(file_id, self._transform._tree.has_id)
 
2055
 
 
2056
    def has_or_had_id(self, file_id):
 
2057
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
2128
2058
 
2129
2059
    def _path2trans_id(self, path):
2130
2060
        # We must not use None here, because that is a valid value to store.
2155
2085
            path = osutils.pathjoin(*path)
2156
2086
        return self._transform.final_file_id(self._path2trans_id(path))
2157
2087
 
2158
 
    def id2path(self, file_id, recurse='down'):
 
2088
    def id2path(self, file_id):
2159
2089
        trans_id = self._transform.trans_id_file_id(file_id)
2160
2090
        try:
2161
2091
            return self._final_paths._determine_path(trans_id)
2173
2103
        self._all_children_cache[trans_id] = children
2174
2104
        return children
2175
2105
 
 
2106
    def _iter_children(self, file_id):
 
2107
        trans_id = self._transform.trans_id_file_id(file_id)
 
2108
        for child_trans_id in self._all_children(trans_id):
 
2109
            yield self._transform.final_file_id(child_trans_id)
 
2110
 
2176
2111
    def extras(self):
2177
2112
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
2178
2113
                              in self._transform._tree.extras())
2187
2122
            file_id = self._transform.final_file_id(trans_id)
2188
2123
            if file_id is None:
2189
2124
                continue
2190
 
            if (specific_files is not None
2191
 
                    and self._final_paths.get_path(trans_id) not in specific_files):
 
2125
            if (specific_files is not None and
 
2126
                self._final_paths.get_path(trans_id) not in specific_files):
2192
2127
                continue
2193
2128
            kind = self._transform.final_kind(trans_id)
2194
2129
            if kind is None:
2195
2130
                kind = self._transform._tree.stored_kind(
2196
 
                    self._transform._tree.id2path(file_id))
 
2131
                    self._transform._tree.id2path(file_id),
 
2132
                    file_id)
2197
2133
            new_entry = inventory.make_entry(
2198
2134
                kind,
2199
2135
                self._transform.final_name(trans_id),
2214
2150
                ordered_ids.append((trans_id, parent_file_id))
2215
2151
        return ordered_ids
2216
2152
 
2217
 
    def iter_child_entries(self, path):
 
2153
    def iter_child_entries(self, path, file_id=None):
2218
2154
        trans_id = self._path2trans_id(path)
2219
2155
        if trans_id is None:
2220
2156
            raise errors.NoSuchFile(path)
2223
2159
        for entry, trans_id in self._make_inv_entries(todo):
2224
2160
            yield entry
2225
2161
 
2226
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2227
 
        if recurse_nested:
2228
 
            raise NotImplementedError(
2229
 
                'follow tree references not yet supported')
2230
 
 
 
2162
    def iter_entries_by_dir(self, specific_files=None):
2231
2163
        # This may not be a maximally efficient implementation, but it is
2232
2164
        # reasonably straightforward.  An implementation that grafts the
2233
2165
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2235
2167
        # position.
2236
2168
        ordered_ids = self._list_files_by_dir()
2237
2169
        for entry, trans_id in self._make_inv_entries(ordered_ids,
2238
 
                                                      specific_files):
 
2170
            specific_files):
2239
2171
            yield self._final_paths.get_path(trans_id), entry
2240
2172
 
2241
2173
    def _iter_entries_for_dir(self, dir_path):
2245
2177
        dir_id = self._transform.final_file_id(dir_trans_id)
2246
2178
        for child_trans_id in self._all_children(dir_trans_id):
2247
2179
            ordered_ids.append((child_trans_id, dir_id))
2248
 
        path_entries = []
2249
2180
        for entry, trans_id in self._make_inv_entries(ordered_ids):
2250
 
            path_entries.append((self._final_paths.get_path(trans_id), entry))
2251
 
        path_entries.sort()
2252
 
        return path_entries
 
2181
            yield self._final_paths.get_path(trans_id), entry
2253
2182
 
2254
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
2255
 
                   recurse_nested=False):
 
2183
    def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2184
        """See WorkingTree.list_files."""
2257
 
        if recurse_nested:
2258
 
            raise NotImplementedError(
2259
 
                'follow tree references not yet supported')
2260
 
 
2261
2185
        # XXX This should behave like WorkingTree.list_files, but is really
2262
2186
        # more like RevisionTree.list_files.
2263
 
        if from_dir == '.':
2264
 
            from_dir = None
2265
2187
        if recursive:
2266
2188
            prefix = None
2267
2189
            if from_dir:
2274
2196
                    if not path.startswith(prefix):
2275
2197
                        continue
2276
2198
                    path = path[len(prefix):]
2277
 
                yield path, 'V', entry.kind, entry
 
2199
                yield path, 'V', entry.kind, entry.file_id, entry
2278
2200
        else:
2279
2201
            if from_dir is None and include_root is True:
2280
 
                root_entry = inventory.make_entry(
2281
 
                    'directory', '', ROOT_PARENT, self.path2id(''))
2282
 
                yield '', 'V', 'directory', root_entry
 
2202
                root_entry = inventory.make_entry('directory', '',
 
2203
                    ROOT_PARENT, self.get_root_id())
 
2204
                yield '', 'V', 'directory', root_entry.file_id, root_entry
2283
2205
            entries = self._iter_entries_for_dir(from_dir or '')
2284
2206
            for path, entry in entries:
2285
 
                yield path, 'V', entry.kind, entry
 
2207
                yield path, 'V', entry.kind, entry.file_id, entry
2286
2208
 
2287
 
    def kind(self, path):
 
2209
    def kind(self, path, file_id=None):
2288
2210
        trans_id = self._path2trans_id(path)
2289
2211
        if trans_id is None:
2290
2212
            raise errors.NoSuchFile(path)
2291
2213
        return self._transform.final_kind(trans_id)
2292
2214
 
2293
 
    def stored_kind(self, path):
 
2215
    def stored_kind(self, path, file_id=None):
2294
2216
        trans_id = self._path2trans_id(path)
2295
2217
        if trans_id is None:
2296
2218
            raise errors.NoSuchFile(path)
2297
2219
        try:
2298
2220
            return self._transform._new_contents[trans_id]
2299
2221
        except KeyError:
2300
 
            return self._transform._tree.stored_kind(path)
 
2222
            return self._transform._tree.stored_kind(path, file_id)
2301
2223
 
2302
 
    def get_file_mtime(self, path):
 
2224
    def get_file_mtime(self, path, file_id=None):
2303
2225
        """See Tree.get_file_mtime"""
2304
 
        file_id = self.path2id(path)
 
2226
        if file_id is None:
 
2227
            file_id = self.path2id(path)
2305
2228
        if file_id is None:
2306
2229
            raise errors.NoSuchFile(path)
2307
2230
        if not self._content_change(file_id):
2308
2231
            return self._transform._tree.get_file_mtime(
2309
 
                self._transform._tree.id2path(file_id))
 
2232
                    self._transform._tree.id2path(file_id), file_id)
2310
2233
        trans_id = self._path2trans_id(path)
2311
2234
        return self._stat_limbo_file(trans_id).st_mtime
2312
2235
 
2313
 
    def get_file_size(self, path):
 
2236
    def get_file_size(self, path, file_id=None):
2314
2237
        """See Tree.get_file_size"""
2315
2238
        trans_id = self._path2trans_id(path)
2316
2239
        if trans_id is None:
2320
2243
            return None
2321
2244
        if trans_id in self._transform._new_contents:
2322
2245
            return self._stat_limbo_file(trans_id).st_size
2323
 
        if self.kind(path) == 'file':
2324
 
            return self._transform._tree.get_file_size(path)
 
2246
        if self.kind(path, file_id) == 'file':
 
2247
            return self._transform._tree.get_file_size(path, file_id)
2325
2248
        else:
2326
2249
            return None
2327
2250
 
2328
 
    def get_file_verifier(self, path, stat_value=None):
 
2251
    def get_file_verifier(self, path, file_id=None, stat_value=None):
2329
2252
        trans_id = self._path2trans_id(path)
2330
2253
        if trans_id is None:
2331
2254
            raise errors.NoSuchFile(path)
2332
2255
        kind = self._transform._new_contents.get(trans_id)
2333
2256
        if kind is None:
2334
 
            return self._transform._tree.get_file_verifier(path)
 
2257
            return self._transform._tree.get_file_verifier(path, file_id)
2335
2258
        if kind == 'file':
2336
 
            with self.get_file(path) as fileobj:
 
2259
            with self.get_file(path, file_id) as fileobj:
2337
2260
                return ("SHA1", sha_file(fileobj))
2338
2261
 
2339
 
    def get_file_sha1(self, path, stat_value=None):
 
2262
    def get_file_sha1(self, path, file_id=None, stat_value=None):
2340
2263
        trans_id = self._path2trans_id(path)
2341
2264
        if trans_id is None:
2342
2265
            raise errors.NoSuchFile(path)
2343
2266
        kind = self._transform._new_contents.get(trans_id)
2344
2267
        if kind is None:
2345
 
            return self._transform._tree.get_file_sha1(path)
 
2268
            return self._transform._tree.get_file_sha1(path, file_id)
2346
2269
        if kind == 'file':
2347
 
            with self.get_file(path) as fileobj:
 
2270
            with self.get_file(path, file_id) as fileobj:
2348
2271
                return sha_file(fileobj)
2349
2272
 
2350
 
    def get_reference_revision(self, path):
2351
 
        trans_id = self._path2trans_id(path)
2352
 
        if trans_id is None:
2353
 
            raise errors.NoSuchFile(path)
2354
 
        reference_revision = self._transform._new_reference_revision.get(trans_id)
2355
 
        if reference_revision is None:
2356
 
            return self._transform._tree.get_reference_revision(path)
2357
 
        return reference_revision
2358
 
 
2359
 
    def is_executable(self, path):
 
2273
    def is_executable(self, path, file_id=None):
2360
2274
        trans_id = self._path2trans_id(path)
2361
2275
        if trans_id is None:
2362
2276
            return False
2364
2278
            return self._transform._new_executability[trans_id]
2365
2279
        except KeyError:
2366
2280
            try:
2367
 
                return self._transform._tree.is_executable(path)
 
2281
                return self._transform._tree.is_executable(path, file_id)
2368
2282
            except OSError as e:
2369
2283
                if e.errno == errno.ENOENT:
2370
2284
                    return False
2407
2321
                size = None
2408
2322
                executable = None
2409
2323
            if kind == 'symlink':
2410
 
                link_or_sha1 = os.readlink(limbo_name)
2411
 
                if not isinstance(link_or_sha1, text_type):
2412
 
                    link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
 
2324
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2413
2325
        executable = tt._new_executability.get(trans_id, executable)
2414
2326
        return kind, size, executable, link_or_sha1
2415
2327
 
2416
2328
    def iter_changes(self, from_tree, include_unchanged=False,
2417
 
                     specific_files=None, pb=None, extra_trees=None,
2418
 
                     require_versioned=True, want_unversioned=False):
 
2329
                      specific_files=None, pb=None, extra_trees=None,
 
2330
                      require_versioned=True, want_unversioned=False):
2419
2331
        """See InterTree.iter_changes.
2420
2332
 
2421
2333
        This has a fast path that is only used when the from_tree matches
2422
2334
        the transform tree, and no fancy options are supplied.
2423
2335
        """
2424
 
        if (from_tree is not self._transform._tree or include_unchanged
2425
 
                or specific_files or want_unversioned):
 
2336
        if (from_tree is not self._transform._tree or include_unchanged or
 
2337
            specific_files or want_unversioned):
2426
2338
            return tree.InterTree(from_tree, self).iter_changes(
2427
2339
                include_unchanged=include_unchanged,
2428
2340
                specific_files=specific_files,
2434
2346
            raise ValueError('want_unversioned is not supported')
2435
2347
        return self._transform.iter_changes()
2436
2348
 
2437
 
    def get_file(self, path):
 
2349
    def get_file(self, path, file_id=None):
2438
2350
        """See Tree.get_file"""
2439
 
        file_id = self.path2id(path)
 
2351
        if file_id is None:
 
2352
            file_id = self.path2id(path)
2440
2353
        if not self._content_change(file_id):
2441
 
            return self._transform._tree.get_file(path)
 
2354
            return self._transform._tree.get_file(path, file_id)
2442
2355
        trans_id = self._path2trans_id(path)
2443
2356
        name = self._transform._limbo_name(trans_id)
2444
2357
        return open(name, 'rb')
2445
2358
 
2446
 
    def get_file_with_stat(self, path):
2447
 
        return self.get_file(path), None
 
2359
    def get_file_with_stat(self, path, file_id=None):
 
2360
        return self.get_file(path, file_id), None
2448
2361
 
2449
 
    def annotate_iter(self, path,
 
2362
    def annotate_iter(self, path, file_id=None,
2450
2363
                      default_revision=_mod_revision.CURRENT_REVISION):
2451
 
        file_id = self.path2id(path)
 
2364
        if file_id is None:
 
2365
            file_id = self.path2id(path)
2452
2366
        changes = self._iter_changes_cache.get(file_id)
2453
2367
        if changes is None:
2454
2368
            get_old = True
2455
2369
        else:
2456
 
            changed_content, versioned, kind = (
2457
 
                changes.changed_content, changes.versioned, changes.kind)
 
2370
            changed_content, versioned, kind = (changes[2], changes[3],
 
2371
                                                changes[6])
2458
2372
            if kind[1] is None:
2459
2373
                return None
2460
2374
            get_old = (kind[0] == 'file' and versioned[0])
2461
2375
        if get_old:
2462
2376
            old_annotation = self._transform._tree.annotate_iter(
2463
 
                path, default_revision=default_revision)
 
2377
                    path, file_id=file_id, default_revision=default_revision)
2464
2378
        else:
2465
2379
            old_annotation = []
2466
2380
        if changes is None:
2475
2389
        #       It would be nice to be able to use the new Annotator based
2476
2390
        #       approach, as well.
2477
2391
        return annotate.reannotate([old_annotation],
2478
 
                                   self.get_file(path).readlines(),
 
2392
                                   self.get_file(path, file_id).readlines(),
2479
2393
                                   default_revision)
2480
2394
 
2481
 
    def get_symlink_target(self, path):
 
2395
    def get_symlink_target(self, path, file_id=None):
2482
2396
        """See Tree.get_symlink_target"""
2483
 
        file_id = self.path2id(path)
 
2397
        if file_id is None:
 
2398
            file_id = self.path2id(path)
2484
2399
        if not self._content_change(file_id):
2485
2400
            return self._transform._tree.get_symlink_target(path)
2486
2401
        trans_id = self._path2trans_id(path)
2500
2415
                path_from_root = self._final_paths.get_path(child_id)
2501
2416
                basename = self._transform.final_name(child_id)
2502
2417
                file_id = self._transform.final_file_id(child_id)
2503
 
                kind = self._transform.final_kind(child_id)
 
2418
                kind  = self._transform.final_kind(child_id)
2504
2419
                if kind is not None:
2505
2420
                    versioned_kind = kind
2506
2421
                else:
2507
2422
                    kind = 'unknown'
2508
2423
                    versioned_kind = self._transform._tree.stored_kind(
2509
 
                        self._transform._tree.id2path(file_id))
 
2424
                            self._transform._tree.id2path(file_id),
 
2425
                            file_id)
2510
2426
                if versioned_kind == 'directory':
2511
2427
                    subdirs.append(child_id)
2512
2428
                children.append((path_from_root, basename, kind, None,
2541
2457
    The underlying tree must not be manipulated between calls, or else
2542
2458
    the results will likely be incorrect.
2543
2459
    """
2544
 
 
2545
2460
    def __init__(self, transform):
2546
2461
        object.__init__(self)
2547
2462
        self._known_paths = {}
2567
2482
        return [(self.get_path(t), t) for t in trans_ids]
2568
2483
 
2569
2484
 
 
2485
 
 
2486
def topology_sorted_ids(tree):
 
2487
    """Determine the topological order of the ids in a tree"""
 
2488
    file_ids = list(tree)
 
2489
    file_ids.sort(key=tree.id2path)
 
2490
    return file_ids
 
2491
 
 
2492
 
2570
2493
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2571
2494
               delta_from_tree=False):
2572
2495
    """Create working tree for a branch, using a TreeTransform.
2593
2516
    :param delta_from_tree: If true, build_tree may use the input Tree to
2594
2517
        generate the inventory delta.
2595
2518
    """
2596
 
    with cleanup.ExitStack() as exit_stack:
2597
 
        exit_stack.enter_context(wt.lock_tree_write())
2598
 
        exit_stack.enter_context(tree.lock_read())
 
2519
    with wt.lock_tree_write(), tree.lock_read():
2599
2520
        if accelerator_tree is not None:
2600
 
            exit_stack.enter_context(accelerator_tree.lock_read())
2601
 
        return _build_tree(tree, wt, accelerator_tree, hardlink,
2602
 
                           delta_from_tree)
 
2521
            accelerator_tree.lock_read()
 
2522
        try:
 
2523
            return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2524
                               delta_from_tree)
 
2525
        finally:
 
2526
            if accelerator_tree is not None:
 
2527
                accelerator_tree.unlock()
2603
2528
 
2604
2529
 
2605
2530
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2610
2535
    file_trans_id = {}
2611
2536
    top_pb = ui.ui_factory.nested_progress_bar()
2612
2537
    pp = ProgressPhase("Build phase", 2, top_pb)
2613
 
    if tree.path2id('') is not None:
 
2538
    if tree.get_root_id() is not None:
2614
2539
        # This is kind of a hack: we should be altering the root
2615
2540
        # as part of the regular tree shape diff logic.
2616
2541
        # The conditional test here is to avoid doing an
2618
2543
        # is set within the tree, nor setting the root and thus
2619
2544
        # marking the tree as dirty, because we use two different
2620
2545
        # idioms here: tree interfaces and inventory interfaces.
2621
 
        if wt.path2id('') != tree.path2id(''):
2622
 
            wt.set_root_id(tree.path2id(''))
 
2546
        if wt.get_root_id() != tree.get_root_id():
 
2547
            wt.set_root_id(tree.get_root_id())
2623
2548
            wt.flush()
2624
 
    tt = wt.get_transform()
 
2549
    tt = TreeTransform(wt)
2625
2550
    divert = set()
2626
2551
    try:
2627
2552
        pp.next_phase()
2628
 
        file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
 
2553
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2629
2554
        with ui.ui_factory.nested_progress_bar() as pb:
2630
2555
            deferred_contents = []
2631
2556
            num = 0
2644
2569
                for dir, files in wt.walkdirs():
2645
2570
                    existing_files.update(f[0] for f in files)
2646
2571
            for num, (tree_path, entry) in \
2647
 
                    enumerate(tree.iter_entries_by_dir()):
2648
 
                pb.update(gettext("Building tree"), num
2649
 
                          - len(deferred_contents), total)
 
2572
                enumerate(tree.iter_entries_by_dir()):
 
2573
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2650
2574
                if entry.parent_id is None:
2651
2575
                    continue
2652
2576
                reparent = False
2663
2587
                            pass
2664
2588
                        else:
2665
2589
                            divert.add(file_id)
2666
 
                    if (file_id not in divert
2667
 
                        and _content_match(
2668
 
                            tree, entry, tree_path, kind, target_path)):
 
2590
                    if (file_id not in divert and
 
2591
                        _content_match(tree, entry, tree_path, file_id, kind,
 
2592
                        target_path)):
2669
2593
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2670
2594
                        if kind == 'directory':
2671
2595
                            reparent = True
2676
2600
                    trans_id = tt.create_path(entry.name, parent_id)
2677
2601
                    file_trans_id[file_id] = trans_id
2678
2602
                    tt.version_file(file_id, trans_id)
2679
 
                    executable = tree.is_executable(tree_path)
 
2603
                    executable = tree.is_executable(tree_path, file_id)
2680
2604
                    if executable:
2681
2605
                        tt.set_executability(executable, trans_id)
2682
 
                    trans_data = (trans_id, file_id,
2683
 
                                  tree_path, entry.text_sha1)
 
2606
                    trans_data = (trans_id, file_id, tree_path, entry.text_sha1)
2684
2607
                    deferred_contents.append((tree_path, trans_data))
2685
2608
                else:
2686
2609
                    file_trans_id[file_id] = new_by_entry(
2687
 
                        tree_path, tt, entry, parent_id, tree)
 
2610
                            tree_path, tt, entry, parent_id, tree)
2688
2611
                if reparent:
2689
2612
                    new_trans_id = file_trans_id[file_id]
2690
2613
                    old_parent = tt.trans_id_tree_path(tree_path)
2694
2617
                          accelerator_tree, hardlink)
2695
2618
        pp.next_phase()
2696
2619
        divert_trans = set(file_trans_id[f] for f in divert)
2697
 
 
2698
 
        def resolver(t, c):
2699
 
            return resolve_checkout(t, c, divert_trans)
 
2620
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2700
2621
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2701
2622
        if len(raw_conflicts) > 0:
2702
2623
            precomputed_delta = None
2723
2644
        new_desired_files = desired_files
2724
2645
    else:
2725
2646
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2726
 
        unchanged = [
2727
 
            change.path for change in iter
2728
 
            if not (change.changed_content or change.executable[0] != change.executable[1])]
 
2647
        unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
 
2648
                     in iter if not (c or e[0] != e[1])]
2729
2649
        if accelerator_tree.supports_content_filtering():
2730
2650
            unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2651
                         if not next(accelerator_tree.iter_search_rules([ap]))]
2736
2656
            accelerator_path = unchanged.get(tree_path)
2737
2657
            if accelerator_path is None:
2738
2658
                new_desired_files.append((tree_path,
2739
 
                                          (trans_id, file_id, tree_path, text_sha1)))
 
2659
                    (trans_id, file_id, tree_path, text_sha1)))
2740
2660
                continue
2741
2661
            pb.update(gettext('Adding file contents'), count + offset, total)
2742
2662
            if hardlink:
2743
2663
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2744
2664
                                   trans_id)
2745
2665
            else:
2746
 
                with accelerator_tree.get_file(accelerator_path) as f:
 
2666
                with accelerator_tree.get_file(accelerator_path, file_id) as f:
2747
2667
                    chunks = osutils.file_iterator(f)
2748
2668
                    if wt.supports_content_filtering():
2749
2669
                        filters = wt._content_filter_stack(tree_path)
2750
2670
                        chunks = filtered_output_bytes(chunks, filters,
2751
 
                                                       ContentFilterContext(tree_path, tree))
 
2671
                            ContentFilterContext(tree_path, tree))
2752
2672
                    tt.create_file(chunks, trans_id, sha1=text_sha1)
2753
2673
            count += 1
2754
2674
        offset += count
2757
2677
        if wt.supports_content_filtering():
2758
2678
            filters = wt._content_filter_stack(tree_path)
2759
2679
            contents = filtered_output_bytes(contents, filters,
2760
 
                                             ContentFilterContext(tree_path, tree))
 
2680
                ContentFilterContext(tree_path, tree))
2761
2681
        tt.create_file(contents, trans_id, sha1=text_sha1)
2762
2682
        pb.update(gettext('Adding file contents'), count + offset, total)
2763
2683
 
2774
2694
    return by_parent[old_parent]
2775
2695
 
2776
2696
 
2777
 
def _content_match(tree, entry, tree_path, kind, target_path):
 
2697
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2698
    if entry.kind != kind:
2779
2699
        return False
2780
2700
    if entry.kind == "directory":
2781
2701
        return True
2782
2702
    if entry.kind == "file":
2783
2703
        with open(target_path, 'rb') as f1, \
2784
 
                tree.get_file(tree_path) as f2:
 
2704
             tree.get_file(tree_path, file_id) as f2:
2785
2705
            if osutils.compare_files(f1, f2):
2786
2706
                return True
2787
2707
    elif entry.kind == "symlink":
2788
 
        if tree.get_symlink_target(tree_path) == os.readlink(target_path):
 
2708
        if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
2789
2709
            return True
2790
2710
    return False
2791
2711
 
2808
2728
        # resolved
2809
2729
        final_parent = tt.final_parent(old_file)
2810
2730
        if new_file in divert:
2811
 
            new_name = tt.final_name(old_file) + '.diverted'
 
2731
            new_name = tt.final_name(old_file)+'.diverted'
2812
2732
            tt.adjust_path(new_name, final_parent, new_file)
2813
2733
            new_conflicts.add((c_type, 'Diverted to',
2814
2734
                               new_file, old_file))
2815
2735
        else:
2816
 
            new_name = tt.final_name(old_file) + '.moved'
 
2736
            new_name = tt.final_name(old_file)+'.moved'
2817
2737
            tt.adjust_path(new_name, final_parent, old_file)
2818
2738
            new_conflicts.add((c_type, 'Moved existing file to',
2819
2739
                               old_file, new_file))
2825
2745
    name = entry.name
2826
2746
    kind = entry.kind
2827
2747
    if kind == 'file':
2828
 
        with tree.get_file(path) as f:
2829
 
            executable = tree.is_executable(path)
 
2748
        with tree.get_file(path, entry.file_id) as f:
 
2749
            executable = tree.is_executable(path, entry.file_id)
2830
2750
            return tt.new_file(
2831
 
                name, parent_id, osutils.file_iterator(f), entry.file_id,
2832
 
                executable)
 
2751
                    name, parent_id, osutils.file_iterator(f), entry.file_id,
 
2752
                    executable)
2833
2753
    elif kind in ('directory', 'tree-reference'):
2834
2754
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
2835
2755
        if kind == 'tree-reference':
2836
2756
            tt.set_tree_reference(entry.reference_revision, trans_id)
2837
2757
        return trans_id
2838
2758
    elif kind == 'symlink':
2839
 
        target = tree.get_symlink_target(path)
 
2759
        target = tree.get_symlink_target(path, entry.file_id)
2840
2760
        return tt.new_symlink(name, parent_id, target, entry.file_id)
2841
2761
    else:
2842
2762
        raise errors.BadFileKindError(name, kind)
2843
2763
 
2844
2764
 
2845
 
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2846
 
                     filter_tree_path=None):
 
2765
def create_from_tree(tt, trans_id, tree, path, file_id=None, chunks=None,
 
2766
    filter_tree_path=None):
2847
2767
    """Create new file contents according to tree contents.
2848
2768
 
2849
2769
    :param filter_tree_path: the tree path to use to lookup
2850
2770
      content filters to apply to the bytes output in the working tree.
2851
2771
      This only applies if the working tree supports content filtering.
2852
2772
    """
2853
 
    kind = tree.kind(path)
 
2773
    kind = tree.kind(path, file_id)
2854
2774
    if kind == 'directory':
2855
2775
        tt.create_directory(trans_id)
2856
2776
    elif kind == "file":
2857
2777
        if chunks is None:
2858
 
            f = tree.get_file(path)
 
2778
            f = tree.get_file(path, file_id)
2859
2779
            chunks = osutils.file_iterator(f)
2860
2780
        else:
2861
2781
            f = None
2863
2783
            wt = tt._tree
2864
2784
            if wt.supports_content_filtering() and filter_tree_path is not None:
2865
2785
                filters = wt._content_filter_stack(filter_tree_path)
2866
 
                chunks = filtered_output_bytes(
2867
 
                    chunks, filters,
 
2786
                chunks = filtered_output_bytes(chunks, filters,
2868
2787
                    ContentFilterContext(filter_tree_path, tree))
2869
2788
            tt.create_file(chunks, trans_id)
2870
2789
        finally:
2871
2790
            if f is not None:
2872
2791
                f.close()
2873
2792
    elif kind == "symlink":
2874
 
        tt.create_symlink(tree.get_symlink_target(path), trans_id)
 
2793
        tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
2875
2794
    else:
2876
2795
        raise AssertionError('Unknown kind %r' % kind)
2877
2796
 
2887
2806
    """Revert a working tree's contents to those of a target tree."""
2888
2807
    pb = ui.ui_factory.nested_progress_bar()
2889
2808
    try:
2890
 
        with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
 
2809
        with target_tree.lock_read(), TreeTransform(working_tree, pb) as tt:
2891
2810
            pp = ProgressPhase("Revert phase", 3, pb)
2892
2811
            conflicts, merge_modified = _prepare_revert_transform(
2893
2812
                working_tree, target_tree, tt, filenames, backups, pp)
2916
2835
                                      child_pb, filenames, backups,
2917
2836
                                      merge_modified, basis_tree)
2918
2837
    with ui.ui_factory.nested_progress_bar() as child_pb:
2919
 
        raw_conflicts = resolve_conflicts(
2920
 
            tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
 
2838
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2839
            lambda t, c: conflict_pass(t, c, target_tree))
2921
2840
    conflicts = cook_conflicts(raw_conflicts, tt)
2922
2841
    return conflicts, merge_modified
2923
2842
 
2930
2849
    # than the target changes relative to the working tree. Because WT4 has an
2931
2850
    # optimizer to compare itself to a target, but no optimizer for the
2932
2851
    # reverse.
2933
 
    change_list = working_tree.iter_changes(
2934
 
        target_tree, specific_files=specific_files, pb=pb)
 
2852
    change_list = working_tree.iter_changes(target_tree,
 
2853
        specific_files=specific_files, pb=pb)
2935
2854
    if not target_tree.is_versioned(u''):
2936
2855
        skip_root = True
2937
2856
    else:
2938
2857
        skip_root = False
2939
2858
    try:
2940
2859
        deferred_files = []
2941
 
        for id_num, change in enumerate(change_list):
2942
 
            file_id = change.file_id
2943
 
            target_path, wt_path = change.path
2944
 
            target_versioned, wt_versioned = change.versioned
2945
 
            target_parent, wt_parent = change.parent_id
2946
 
            target_name, wt_name = change.name
2947
 
            target_kind, wt_kind = change.kind
2948
 
            target_executable, wt_executable = change.executable
 
2860
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
2861
                kind, executable) in enumerate(change_list):
 
2862
            target_path, wt_path = path
 
2863
            target_versioned, wt_versioned = versioned
 
2864
            target_parent, wt_parent = parent
 
2865
            target_name, wt_name = name
 
2866
            target_kind, wt_kind = kind
 
2867
            target_executable, wt_executable = executable
2949
2868
            if skip_root and wt_parent is None:
2950
2869
                continue
2951
2870
            trans_id = tt.trans_id_file_id(file_id)
2952
2871
            mode_id = None
2953
 
            if change.changed_content:
 
2872
            if changed_content:
2954
2873
                keep_content = False
2955
2874
                if wt_kind == 'file' and (backups or target_kind is None):
2956
 
                    wt_sha1 = working_tree.get_file_sha1(wt_path)
2957
 
                    if merge_modified.get(wt_path) != wt_sha1:
 
2875
                    wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
 
2876
                    if merge_modified.get(file_id) != wt_sha1:
2958
2877
                        # acquire the basis tree lazily to prevent the
2959
2878
                        # expense of accessing it when it's not needed ?
2960
2879
                        # (Guessing, RBC, 200702)
2961
2880
                        if basis_tree is None:
2962
2881
                            basis_tree = working_tree.basis_tree()
2963
2882
                            basis_tree.lock_read()
2964
 
                        basis_inter = InterTree.get(basis_tree, working_tree)
2965
 
                        basis_path = basis_inter.find_source_path(wt_path)
 
2883
                        basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2966
2884
                        if basis_path is None:
2967
2885
                            if target_kind is None and not target_versioned:
2968
2886
                                keep_content = True
2969
2887
                        else:
2970
 
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path):
 
2888
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2971
2889
                                keep_content = True
2972
2890
                if wt_kind is not None:
2973
2891
                    if not keep_content:
2989
2907
                    tt.create_directory(trans_id)
2990
2908
                    if target_kind == 'tree-reference':
2991
2909
                        revision = target_tree.get_reference_revision(
2992
 
                            target_path)
 
2910
                                target_path, file_id)
2993
2911
                        tt.set_tree_reference(revision, trans_id)
2994
2912
                elif target_kind == 'symlink':
2995
2913
                    tt.create_symlink(target_tree.get_symlink_target(
2996
 
                        target_path), trans_id)
 
2914
                            target_path, file_id), trans_id)
2997
2915
                elif target_kind == 'file':
2998
 
                    deferred_files.append(
2999
 
                        (target_path, (trans_id, mode_id, file_id)))
 
2916
                    deferred_files.append((target_path, (trans_id, mode_id, file_id)))
3000
2917
                    if basis_tree is None:
3001
2918
                        basis_tree = working_tree.basis_tree()
3002
2919
                        basis_tree.lock_read()
3003
 
                    new_sha1 = target_tree.get_file_sha1(target_path)
3004
 
                    basis_inter = InterTree.get(basis_tree, target_tree)
3005
 
                    basis_path = basis_inter.find_source_path(target_path)
 
2920
                    new_sha1 = target_tree.get_file_sha1(target_path, file_id)
 
2921
                    basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2922
                    if (basis_path is not None and
3007
 
                            new_sha1 == basis_tree.get_file_sha1(basis_path)):
3008
 
                        # If the new contents of the file match what is in basis,
3009
 
                        # then there is no need to store in merge_modified.
3010
 
                        if basis_path in merge_modified:
3011
 
                            del merge_modified[basis_path]
 
2923
                        new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
 
2924
                        if file_id in merge_modified:
 
2925
                            del merge_modified[file_id]
3012
2926
                    else:
3013
 
                        merge_modified[target_path] = new_sha1
 
2927
                        merge_modified[file_id] = new_sha1
3014
2928
 
3015
2929
                    # preserve the execute bit when backing up
3016
2930
                    if keep_content and wt_executable == target_executable:
3021
2935
                tt.version_file(file_id, trans_id)
3022
2936
            if wt_versioned and not target_versioned:
3023
2937
                tt.unversion_file(trans_id)
3024
 
            if (target_name is not None
3025
 
                    and (wt_name != target_name or wt_parent != target_parent)):
 
2938
            if (target_name is not None and
 
2939
                (wt_name != target_name or wt_parent != target_parent)):
3026
2940
                if target_name == '' and target_parent is None:
3027
2941
                    parent_trans = ROOT_PARENT
3028
2942
                else:
3035
2949
                tt.set_executability(target_executable, trans_id)
3036
2950
        if working_tree.supports_content_filtering():
3037
2951
            for (trans_id, mode_id, file_id), bytes in (
3038
 
                    target_tree.iter_files_bytes(deferred_files)):
 
2952
                target_tree.iter_files_bytes(deferred_files)):
3039
2953
                # We're reverting a tree to the target tree so using the
3040
2954
                # target tree to find the file path seems the best choice
3041
2955
                # here IMO - Ian C 27/Oct/2009
3042
2956
                filter_tree_path = target_tree.id2path(file_id)
3043
2957
                filters = working_tree._content_filter_stack(filter_tree_path)
3044
 
                bytes = filtered_output_bytes(
3045
 
                    bytes, filters,
 
2958
                bytes = filtered_output_bytes(bytes, filters,
3046
2959
                    ContentFilterContext(filter_tree_path, working_tree))
3047
2960
                tt.create_file(bytes, trans_id, mode_id)
3048
2961
        else:
3049
2962
            for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3050
 
                    deferred_files):
 
2963
                deferred_files):
3051
2964
                tt.create_file(bytes, trans_id, mode_id)
3052
2965
        tt.fixup_new_roots()
3053
2966
    finally:
3063
2976
    new_conflicts = set()
3064
2977
    with ui.ui_factory.nested_progress_bar() as pb:
3065
2978
        for n in range(10):
3066
 
            pb.update(gettext('Resolution pass'), n + 1, 10)
 
2979
            pb.update(gettext('Resolution pass'), n+1, 10)
3067
2980
            conflicts = tt.find_conflicts()
3068
2981
            if len(conflicts) == 0:
3069
2982
                return new_conflicts
3161
3074
            file_id = tt.inactive_file_id(conflict[1])
3162
3075
            # special-case the other tree root (move its children instead)
3163
3076
            if path_tree and path_tree.path2id('') == file_id:
3164
 
                # This is the root entry, skip it
3165
 
                continue
 
3077
                    # This is the root entry, skip it
 
3078
                    continue
3166
3079
            tt.version_file(file_id, conflict[1])
3167
3080
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
3168
3081
        elif c_type == 'non-directory parent':
3171
3084
            parent_name = tt.final_name(parent_id)
3172
3085
            parent_file_id = tt.final_file_id(parent_id)
3173
3086
            new_parent_id = tt.new_directory(parent_name + '.new',
3174
 
                                             parent_parent, parent_file_id)
 
3087
                parent_parent, parent_file_id)
3175
3088
            _reparent_transform_children(tt, parent_id, new_parent_id)
3176
3089
            if parent_file_id is not None:
3177
3090
                tt.unversion_file(parent_id)
3245
3158
            except OSError as e:
3246
3159
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3247
3160
        # after rollback, don't reuse _FileMover
3248
 
        self.past_renames = None
3249
 
        self.pending_deletions = None
 
3161
        past_renames = None
 
3162
        pending_deletions = None
3250
3163
 
3251
3164
    def apply_deletions(self):
3252
3165
        """Apply all marked deletions"""
3253
3166
        for path in self.pending_deletions:
3254
3167
            delete_any(path)
3255
3168
        # after apply_deletions, don't reuse _FileMover
3256
 
        self.past_renames = None
3257
 
        self.pending_deletions = None
 
3169
        past_renames = None
 
3170
        pending_deletions = None
3258
3171
 
3259
3172
 
3260
3173
def link_tree(target_tree, source_tree):
3263
3176
    :param target_tree: Tree to change
3264
3177
    :param source_tree: Tree to hard-link from
3265
3178
    """
3266
 
    with target_tree.get_transform() as tt:
3267
 
        for change in target_tree.iter_changes(source_tree, include_unchanged=True):
3268
 
            if change.changed_content:
3269
 
                continue
3270
 
            if change.kind != ('file', 'file'):
3271
 
                continue
3272
 
            if change.executable[0] != change.executable[1]:
3273
 
                continue
3274
 
            trans_id = tt.trans_id_tree_path(change.path[1])
 
3179
    tt = TreeTransform(target_tree)
 
3180
    try:
 
3181
        for (file_id, paths, changed_content, versioned, parent, name, kind,
 
3182
             executable) in target_tree.iter_changes(source_tree,
 
3183
             include_unchanged=True):
 
3184
            if changed_content:
 
3185
                continue
 
3186
            if kind != ('file', 'file'):
 
3187
                continue
 
3188
            if executable[0] != executable[1]:
 
3189
                continue
 
3190
            trans_id = tt.trans_id_tree_path(paths[1])
3275
3191
            tt.delete_contents(trans_id)
3276
 
            tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
 
3192
            tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)
3277
3193
        tt.apply()
 
3194
    finally:
 
3195
        tt.finalize()