/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

  • Committer: Jelmer Vernooij
  • Date: 2018-05-06 11:48:54 UTC
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180506114854-h4qd9ojaqy8wxjsd
Move .mailmap to root.

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 (
69
 
    text_type,
70
69
    viewitems,
71
70
    viewvalues,
72
71
    )
73
72
from .tree import (
74
 
    InterTree,
75
 
    TreeChange,
 
73
    find_previous_path,
76
74
    )
77
75
 
78
76
 
79
77
ROOT_PARENT = "root-parent"
80
78
 
81
 
 
82
79
def unique_add(map, key, value):
83
80
    if key in map:
84
81
        raise DuplicateKey(key=key)
85
82
    map[key] = value
86
83
 
87
84
 
 
85
 
88
86
class _TransformResults(object):
89
87
    def __init__(self, modified_paths, rename_count):
90
88
        object.__init__(self)
134
132
        # Mapping trans_id -> path in old tree
135
133
        self._tree_id_paths = {}
136
134
        # The trans_id that will be used as the tree root
137
 
        if tree.is_versioned(''):
 
135
        root_id = tree.get_root_id()
 
136
        if root_id is not None:
138
137
            self._new_root = self.trans_id_tree_path('')
139
138
        else:
140
139
            self._new_root = None
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
 
            b'_id_number': self._id_number,
1132
 
            b'_new_name': new_name,
1133
 
            b'_new_parent': new_parent,
1134
 
            b'_new_executability': new_executability,
1135
 
            b'_new_id': new_id,
1136
 
            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,
 
1109
            '_id_number': self._id_number,
 
1110
            '_new_name': new_name,
 
1111
            '_new_parent': self._new_parent,
 
1112
            '_new_executability': new_executability,
 
1113
            '_new_id': self._new_id,
 
1114
            '_tree_path_ids': tree_path_ids,
 
1115
            '_removed_id': list(self._removed_id),
 
1116
            '_removed_contents': list(self._removed_contents),
 
1117
            '_non_present_ids': self._non_present_ids,
1140
1118
            }
1141
1119
        yield serializer.bytes_record(bencode.bencode(attribs),
1142
 
                                      ((b'attribs',),))
1143
 
        for trans_id, kind in sorted(viewitems(self._new_contents)):
 
1120
                                      (('attribs',),))
 
1121
        for trans_id, kind in viewitems(self._new_contents):
1144
1122
            if kind == 'file':
1145
 
                with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
 
                    lines = cur_file.readlines()
 
1123
                lines = osutils.chunks_to_lines(
 
1124
                    self._read_file_chunks(trans_id))
1147
1125
                parents = self._get_parents_lines(trans_id)
1148
1126
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1149
 
                content = b''.join(mpdiff.to_patch())
 
1127
                content = ''.join(mpdiff.to_patch())
1150
1128
            if kind == 'directory':
1151
 
                content = b''
 
1129
                content = ''
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.
1164
1139
        """
1165
1140
        names, content = next(records)
1166
1141
        attribs = bencode.bdecode(content)
1167
 
        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)}
 
1142
        self._id_number = attribs['_id_number']
 
1143
        self._new_name = dict((k, v.decode('utf-8'))
 
1144
                              for k, v in viewitems(attribs['_new_name']))
 
1145
        self._new_parent = attribs['_new_parent']
 
1146
        self._new_executability = dict((k, bool(v))
 
1147
            for k, v in viewitems(attribs['_new_executability']))
 
1148
        self._new_id = attribs['_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
 
        for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
 
1152
        for bytepath, trans_id in viewitems(attribs['_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['_removed_id'])
 
1157
        self._removed_contents = set(attribs['_removed_contents'])
 
1158
        self._non_present_ids = attribs['_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)
1385
1311
            We can use it to prevent future sha1 computations.
1386
1312
        """
1387
1313
        name = self._limbo_name(trans_id)
1388
 
        with open(name, 'wb') as f:
 
1314
        f = open(name, 'wb')
 
1315
        try:
1389
1316
            unique_add(self._new_contents, trans_id, 'file')
1390
1317
            f.writelines(contents)
 
1318
        finally:
 
1319
            f.close()
1391
1320
        self._set_mtime(name)
1392
1321
        self._set_mode(trans_id, mode_id, S_ISREG)
1393
1322
        # It is unfortunate we have to use lstat instead of fstat, but we just
1396
1325
        if sha1 is not None:
1397
1326
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1398
1327
 
 
1328
    def _read_file_chunks(self, trans_id):
 
1329
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1330
        try:
 
1331
            return cur_file.readlines()
 
1332
        finally:
 
1333
            cur_file.close()
 
1334
 
1399
1335
    def _read_symlink_target(self, trans_id):
1400
1336
        return os.readlink(self._limbo_name(trans_id))
1401
1337
 
1419
1355
            raise errors.HardLinkNotSupported(path)
1420
1356
        try:
1421
1357
            unique_add(self._new_contents, trans_id, 'file')
1422
 
        except BaseException:
 
1358
        except:
1423
1359
            # Clean up the file, it never got registered so
1424
1360
            # TreeTransform.finalize() won't clean it up.
1425
1361
            os.unlink(name)
1439
1375
        target is a bytestring.
1440
1376
        See also new_symlink.
1441
1377
        """
1442
 
        if self._create_symlinks:
 
1378
        if has_symlinks():
1443
1379
            os.symlink(target, self._limbo_name(trans_id))
 
1380
            unique_add(self._new_contents, trans_id, 'symlink')
1444
1381
        else:
1445
1382
            try:
1446
1383
                path = FinalPaths(self).get_path(trans_id)
1447
1384
            except KeyError:
1448
1385
                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')
 
1386
            raise UnableCreateSymlink(path=path)
1455
1387
 
1456
1388
    def cancel_creation(self, trans_id):
1457
1389
        """Cancel the creation of new file contents."""
1530
1462
 
1531
1463
orphaning_registry = registry.Registry()
1532
1464
orphaning_registry.register(
1533
 
    u'conflict', refuse_orphan,
 
1465
    'conflict', refuse_orphan,
1534
1466
    'Leave orphans in place and create a conflict on the directory.')
1535
1467
orphaning_registry.register(
1536
 
    u'move', move_orphan,
 
1468
    'move', move_orphan,
1537
1469
    'Move orphans into the brz-orphans directory.')
1538
 
orphaning_registry._set_default_key(u'conflict')
 
1470
orphaning_registry._set_default_key('conflict')
1539
1471
 
1540
1472
 
1541
1473
opt_transform_orphan = _mod_config.RegistryOption(
1609
1541
    FileMover does not delete files until it is sure that a rollback will not
1610
1542
    happen.
1611
1543
    """
1612
 
 
1613
1544
    def __init__(self, tree, pb=None):
1614
1545
        """Note: a tree_write lock is taken on the tree.
1615
1546
 
1628
1559
            osutils.ensure_empty_directory_exists(
1629
1560
                deletiondir,
1630
1561
                errors.ExistingPendingDeletion)
1631
 
        except BaseException:
 
1562
        except:
1632
1563
            tree.unlock()
1633
1564
            raise
1634
1565
 
1707
1638
        try:
1708
1639
            children = os.listdir(self._tree.abspath(path))
1709
1640
        except OSError as e:
1710
 
            if not (osutils._is_error_enotdir(e) or
1711
 
                    e.errno in (errno.ENOENT, errno.ESRCH)):
 
1641
            if not (osutils._is_error_enotdir(e)
 
1642
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
1712
1643
                raise
1713
1644
            return
1714
1645
 
1745
1676
                # if it is already associated with this trans_id.
1746
1677
                elif self._case_sensitive_target:
1747
1678
                    if (self._limbo_children_names[parent].get(filename)
1748
 
                            in (trans_id, None)):
 
1679
                        in (trans_id, None)):
1749
1680
                        use_direct_path = True
1750
1681
                else:
1751
1682
                    for l_filename, l_trans_id in viewitems(
1765
1696
        self._limbo_children_names[parent][filename] = trans_id
1766
1697
        return limbo_name
1767
1698
 
 
1699
 
1768
1700
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1769
1701
        """Apply all changes to the inventory and filesystem.
1770
1702
 
1800
1732
                self._apply_removals(mover)
1801
1733
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1802
1734
                modified_paths = self._apply_insertions(mover)
1803
 
            except BaseException:
 
1735
            except:
1804
1736
                mover.rollback()
1805
1737
                raise
1806
1738
            else:
1821
1753
        with ui.ui_factory.nested_progress_bar() as child_pb:
1822
1754
            for num, trans_id in enumerate(self._removed_id):
1823
1755
                if (num % 10) == 0:
1824
 
                    child_pb.update(gettext('removing file'),
1825
 
                                    num, total_entries)
 
1756
                    child_pb.update(gettext('removing file'), num, total_entries)
1826
1757
                if trans_id == self._new_root:
1827
 
                    file_id = self._tree.path2id('')
 
1758
                    file_id = self._tree.get_root_id()
1828
1759
                else:
1829
1760
                    file_id = self.tree_file_id(trans_id)
1830
1761
                # File-id isn't really being deleted, just moved
1834
1765
                inventory_delta.append((path, None, file_id, None))
1835
1766
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1836
1767
                                     new_paths)
 
1768
            final_kinds = {}
1837
1769
            for num, (path, trans_id) in enumerate(new_paths):
1838
1770
                if (num % 10) == 0:
1839
1771
                    child_pb.update(gettext('adding file'),
1841
1773
                file_id = new_path_file_ids[trans_id]
1842
1774
                if file_id is None:
1843
1775
                    continue
 
1776
                needs_entry = False
1844
1777
                kind = self.final_kind(trans_id)
1845
1778
                if kind is None:
1846
 
                    kind = self._tree.stored_kind(self._tree.id2path(file_id))
 
1779
                    kind = self._tree.stored_kind(
 
1780
                            self._tree.id2path(file_id), file_id)
1847
1781
                parent_trans_id = self.final_parent(trans_id)
1848
1782
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1849
1783
                if parent_file_id is None:
1856
1790
                        None, self._new_reference_revision[trans_id])
1857
1791
                else:
1858
1792
                    new_entry = inventory.make_entry(kind,
1859
 
                                                     self.final_name(trans_id),
1860
 
                                                     parent_file_id, file_id)
 
1793
                        self.final_name(trans_id),
 
1794
                        parent_file_id, file_id)
1861
1795
                try:
1862
1796
                    old_path = self._tree.id2path(new_entry.file_id)
1863
1797
                except errors.NoSuchId:
1889
1823
                if trans_id in self._removed_contents:
1890
1824
                    delete_path = os.path.join(self._deletiondir, trans_id)
1891
1825
                    mover.pre_delete(full_path, delete_path)
1892
 
                elif (trans_id in self._new_name or
1893
 
                      trans_id in self._new_parent):
 
1826
                elif (trans_id in self._new_name
 
1827
                      or trans_id in self._new_parent):
1894
1828
                    try:
1895
1829
                        mover.rename(full_path, self._limbo_name(trans_id))
1896
1830
                    except errors.TransformRenameFailed as e:
1911
1845
        """
1912
1846
        new_paths = self.new_paths(filesystem_only=True)
1913
1847
        modified_paths = []
 
1848
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1849
                                 new_paths)
1914
1850
        with ui.ui_factory.nested_progress_bar() as child_pb:
1915
1851
            for num, (path, trans_id) in enumerate(new_paths):
1916
1852
                if (num % 10) == 0:
1917
 
                    child_pb.update(gettext('adding file'),
1918
 
                                    num, len(new_paths))
 
1853
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1919
1854
                full_path = self._tree.abspath(path)
1920
1855
                if trans_id in self._needs_rename:
1921
1856
                    try:
1929
1864
                    # TODO: if trans_id in self._observed_sha1s, we should
1930
1865
                    #       re-stat the final target, since ctime will be
1931
1866
                    #       updated by the change.
1932
 
                if (trans_id in self._new_contents
1933
 
                        or self.path_changed(trans_id)):
 
1867
                if (trans_id in self._new_contents or
 
1868
                    self.path_changed(trans_id)):
1934
1869
                    if trans_id in self._new_contents:
1935
1870
                        modified_paths.append(full_path)
1936
1871
                if trans_id in self._new_executability:
1965
1900
        paths = FinalPaths(self)
1966
1901
        for trans_id, observed in viewitems(self._observed_sha1s):
1967
1902
            path = paths.get_path(trans_id)
1968
 
            self._tree._observed_sha1(path, observed)
 
1903
            # We could get the file_id, but dirstate prefers to use the path
 
1904
            # anyway, and it is 'cheaper' to determine.
 
1905
            # file_id = self._new_id[trans_id]
 
1906
            self._tree._observed_sha1(None, path, observed)
1969
1907
 
1970
1908
 
1971
1909
class TransformPreview(DiskTreeTransform):
2008
1946
            path = self._tree_id_paths[parent_id]
2009
1947
        except KeyError:
2010
1948
            return
2011
 
        try:
2012
 
            entry = next(self._tree.iter_entries_by_dir(
 
1949
        entry = next(self._tree.iter_entries_by_dir(
2013
1950
                specific_files=[path]))[1]
2014
 
        except StopIteration:
2015
 
            return
2016
1951
        children = getattr(entry, 'children', {})
2017
1952
        for child in children:
2018
1953
            childpath = joinpath(path, child)
2033
1968
        self._all_children_cache = {}
2034
1969
        self._path2trans_id_cache = {}
2035
1970
        self._final_name_cache = {}
2036
 
        self._iter_changes_cache = dict((c.file_id, c) for c in
 
1971
        self._iter_changes_cache = dict((c[0], c) for c in
2037
1972
                                        self._transform.iter_changes())
2038
1973
 
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
1974
    def _content_change(self, file_id):
2045
1975
        """Return True if the content of this file changed"""
2046
1976
        changes = self._iter_changes_cache.get(file_id)
2047
 
        return (changes is not None and changes.changed_content)
 
1977
        # changes[2] is true if the file content changed.  See
 
1978
        # InterTree.iter_changes.
 
1979
        return (changes is not None and changes[2])
2048
1980
 
2049
1981
    def _get_repository(self):
2050
1982
        repo = getattr(self._transform._tree, '_repository', None)
2061
1993
 
2062
1994
    def _get_file_revision(self, path, file_id, vf, tree_revision):
2063
1995
        parent_keys = [
2064
 
            (file_id, t.get_file_revision(t.id2path(file_id)))
2065
 
            for t in self._iter_parent_trees()]
 
1996
                (file_id, t.get_file_revision(t.id2path(file_id), file_id))
 
1997
                for t in self._iter_parent_trees()]
2066
1998
        vf.add_lines((file_id, tree_revision), parent_keys,
2067
 
                     self.get_file_lines(path))
 
1999
                     self.get_file_lines(path, file_id))
2068
2000
        repo = self._get_repository()
2069
2001
        base_vf = repo.texts
2070
2002
        if base_vf not in vf.fallback_versionedfiles:
2088
2020
            executable = False
2089
2021
        else:
2090
2022
            file_id = self._transform.final_file_id(self._path2trans_id(path))
2091
 
            executable = self.is_executable(path)
 
2023
            executable = self.is_executable(path, file_id)
2092
2024
        return kind, executable, None
2093
2025
 
2094
2026
    def is_locked(self):
2106
2038
        """This Tree does not use inventory as its backing data."""
2107
2039
        raise NotImplementedError(_PreviewTree.root_inventory)
2108
2040
 
 
2041
    def get_root_id(self):
 
2042
        return self._transform.final_file_id(self._transform.root)
 
2043
 
2109
2044
    def all_file_ids(self):
2110
2045
        tree_ids = set(self._transform._tree.all_file_ids())
2111
2046
        tree_ids.difference_update(self._transform.tree_file_id(t)
2114
2049
        return tree_ids
2115
2050
 
2116
2051
    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
 
2052
        return {self.id2path(fid) for fid in self.all_file_ids()}
 
2053
 
 
2054
    def _has_id(self, file_id, fallback_check):
 
2055
        if file_id in self._transform._r_new_id:
 
2056
            return True
 
2057
        elif file_id in {self._transform.tree_file_id(trans_id) for
 
2058
            trans_id in self._transform._removed_id}:
 
2059
            return False
 
2060
        else:
 
2061
            return fallback_check(file_id)
 
2062
 
 
2063
    def has_id(self, file_id):
 
2064
        return self._has_id(file_id, self._transform._tree.has_id)
 
2065
 
 
2066
    def has_or_had_id(self, file_id):
 
2067
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
2128
2068
 
2129
2069
    def _path2trans_id(self, path):
2130
2070
        # We must not use None here, because that is a valid value to store.
2155
2095
            path = osutils.pathjoin(*path)
2156
2096
        return self._transform.final_file_id(self._path2trans_id(path))
2157
2097
 
2158
 
    def id2path(self, file_id, recurse='down'):
 
2098
    def id2path(self, file_id):
2159
2099
        trans_id = self._transform.trans_id_file_id(file_id)
2160
2100
        try:
2161
2101
            return self._final_paths._determine_path(trans_id)
2173
2113
        self._all_children_cache[trans_id] = children
2174
2114
        return children
2175
2115
 
 
2116
    def _iter_children(self, file_id):
 
2117
        trans_id = self._transform.trans_id_file_id(file_id)
 
2118
        for child_trans_id in self._all_children(trans_id):
 
2119
            yield self._transform.final_file_id(child_trans_id)
 
2120
 
2176
2121
    def extras(self):
2177
2122
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
2178
2123
                              in self._transform._tree.extras())
2187
2132
            file_id = self._transform.final_file_id(trans_id)
2188
2133
            if file_id is None:
2189
2134
                continue
2190
 
            if (specific_files is not None
2191
 
                    and self._final_paths.get_path(trans_id) not in specific_files):
 
2135
            if (specific_files is not None and
 
2136
                unicode(self._final_paths.get_path(trans_id)) not in specific_files):
2192
2137
                continue
2193
2138
            kind = self._transform.final_kind(trans_id)
2194
2139
            if kind is None:
2195
2140
                kind = self._transform._tree.stored_kind(
2196
 
                    self._transform._tree.id2path(file_id))
 
2141
                    self._transform._tree.id2path(file_id),
 
2142
                    file_id)
2197
2143
            new_entry = inventory.make_entry(
2198
2144
                kind,
2199
2145
                self._transform.final_name(trans_id),
2214
2160
                ordered_ids.append((trans_id, parent_file_id))
2215
2161
        return ordered_ids
2216
2162
 
2217
 
    def iter_child_entries(self, path):
 
2163
    def iter_child_entries(self, path, file_id=None):
2218
2164
        trans_id = self._path2trans_id(path)
2219
2165
        if trans_id is None:
2220
2166
            raise errors.NoSuchFile(path)
2223
2169
        for entry, trans_id in self._make_inv_entries(todo):
2224
2170
            yield entry
2225
2171
 
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
 
 
 
2172
    def iter_entries_by_dir(self, specific_files=None):
2231
2173
        # This may not be a maximally efficient implementation, but it is
2232
2174
        # reasonably straightforward.  An implementation that grafts the
2233
2175
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2235
2177
        # position.
2236
2178
        ordered_ids = self._list_files_by_dir()
2237
2179
        for entry, trans_id in self._make_inv_entries(ordered_ids,
2238
 
                                                      specific_files):
2239
 
            yield self._final_paths.get_path(trans_id), entry
 
2180
            specific_files):
 
2181
            yield unicode(self._final_paths.get_path(trans_id)), entry
2240
2182
 
2241
2183
    def _iter_entries_for_dir(self, dir_path):
2242
2184
        """Return path, entry for items in a directory without recursing down."""
2245
2187
        dir_id = self._transform.final_file_id(dir_trans_id)
2246
2188
        for child_trans_id in self._all_children(dir_trans_id):
2247
2189
            ordered_ids.append((child_trans_id, dir_id))
2248
 
        path_entries = []
2249
2190
        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
 
2191
            yield unicode(self._final_paths.get_path(trans_id)), entry
2253
2192
 
2254
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
2255
 
                   recurse_nested=False):
 
2193
    def list_files(self, include_root=False, from_dir=None, recursive=True):
2256
2194
        """See WorkingTree.list_files."""
2257
 
        if recurse_nested:
2258
 
            raise NotImplementedError(
2259
 
                'follow tree references not yet supported')
2260
 
 
2261
2195
        # XXX This should behave like WorkingTree.list_files, but is really
2262
2196
        # more like RevisionTree.list_files.
2263
 
        if from_dir == '.':
2264
 
            from_dir = None
2265
2197
        if recursive:
2266
2198
            prefix = None
2267
2199
            if from_dir:
2274
2206
                    if not path.startswith(prefix):
2275
2207
                        continue
2276
2208
                    path = path[len(prefix):]
2277
 
                yield path, 'V', entry.kind, entry
 
2209
                yield path, 'V', entry.kind, entry.file_id, entry
2278
2210
        else:
2279
2211
            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
 
2212
                root_entry = inventory.make_entry('directory', '',
 
2213
                    ROOT_PARENT, self.get_root_id())
 
2214
                yield '', 'V', 'directory', root_entry.file_id, root_entry
2283
2215
            entries = self._iter_entries_for_dir(from_dir or '')
2284
2216
            for path, entry in entries:
2285
 
                yield path, 'V', entry.kind, entry
 
2217
                yield path, 'V', entry.kind, entry.file_id, entry
2286
2218
 
2287
 
    def kind(self, path):
 
2219
    def kind(self, path, file_id=None):
2288
2220
        trans_id = self._path2trans_id(path)
2289
2221
        if trans_id is None:
2290
2222
            raise errors.NoSuchFile(path)
2291
2223
        return self._transform.final_kind(trans_id)
2292
2224
 
2293
 
    def stored_kind(self, path):
 
2225
    def stored_kind(self, path, file_id=None):
2294
2226
        trans_id = self._path2trans_id(path)
2295
2227
        if trans_id is None:
2296
2228
            raise errors.NoSuchFile(path)
2297
2229
        try:
2298
2230
            return self._transform._new_contents[trans_id]
2299
2231
        except KeyError:
2300
 
            return self._transform._tree.stored_kind(path)
 
2232
            return self._transform._tree.stored_kind(path, file_id)
2301
2233
 
2302
 
    def get_file_mtime(self, path):
 
2234
    def get_file_mtime(self, path, file_id=None):
2303
2235
        """See Tree.get_file_mtime"""
2304
 
        file_id = self.path2id(path)
 
2236
        if file_id is None:
 
2237
            file_id = self.path2id(path)
2305
2238
        if file_id is None:
2306
2239
            raise errors.NoSuchFile(path)
2307
2240
        if not self._content_change(file_id):
2308
2241
            return self._transform._tree.get_file_mtime(
2309
 
                self._transform._tree.id2path(file_id))
 
2242
                    self._transform._tree.id2path(file_id), file_id)
2310
2243
        trans_id = self._path2trans_id(path)
2311
2244
        return self._stat_limbo_file(trans_id).st_mtime
2312
2245
 
2313
 
    def get_file_size(self, path):
 
2246
    def get_file_size(self, path, file_id=None):
2314
2247
        """See Tree.get_file_size"""
2315
2248
        trans_id = self._path2trans_id(path)
2316
2249
        if trans_id is None:
2320
2253
            return None
2321
2254
        if trans_id in self._transform._new_contents:
2322
2255
            return self._stat_limbo_file(trans_id).st_size
2323
 
        if self.kind(path) == 'file':
2324
 
            return self._transform._tree.get_file_size(path)
 
2256
        if self.kind(path, file_id) == 'file':
 
2257
            return self._transform._tree.get_file_size(path, file_id)
2325
2258
        else:
2326
2259
            return None
2327
2260
 
2328
 
    def get_file_verifier(self, path, stat_value=None):
 
2261
    def get_file_verifier(self, path, file_id=None, stat_value=None):
2329
2262
        trans_id = self._path2trans_id(path)
2330
2263
        if trans_id is None:
2331
2264
            raise errors.NoSuchFile(path)
2332
2265
        kind = self._transform._new_contents.get(trans_id)
2333
2266
        if kind is None:
2334
 
            return self._transform._tree.get_file_verifier(path)
 
2267
            return self._transform._tree.get_file_verifier(path, file_id)
2335
2268
        if kind == 'file':
2336
 
            with self.get_file(path) as fileobj:
 
2269
            fileobj = self.get_file(path, file_id)
 
2270
            try:
2337
2271
                return ("SHA1", sha_file(fileobj))
 
2272
            finally:
 
2273
                fileobj.close()
2338
2274
 
2339
 
    def get_file_sha1(self, path, stat_value=None):
 
2275
    def get_file_sha1(self, path, file_id=None, stat_value=None):
2340
2276
        trans_id = self._path2trans_id(path)
2341
2277
        if trans_id is None:
2342
2278
            raise errors.NoSuchFile(path)
2343
2279
        kind = self._transform._new_contents.get(trans_id)
2344
2280
        if kind is None:
2345
 
            return self._transform._tree.get_file_sha1(path)
 
2281
            return self._transform._tree.get_file_sha1(path, file_id)
2346
2282
        if kind == 'file':
2347
 
            with self.get_file(path) as fileobj:
 
2283
            fileobj = self.get_file(path, file_id)
 
2284
            try:
2348
2285
                return sha_file(fileobj)
2349
 
 
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):
 
2286
            finally:
 
2287
                fileobj.close()
 
2288
 
 
2289
    def is_executable(self, path, file_id=None):
2360
2290
        trans_id = self._path2trans_id(path)
2361
2291
        if trans_id is None:
2362
2292
            return False
2364
2294
            return self._transform._new_executability[trans_id]
2365
2295
        except KeyError:
2366
2296
            try:
2367
 
                return self._transform._tree.is_executable(path)
 
2297
                return self._transform._tree.is_executable(path, file_id)
2368
2298
            except OSError as e:
2369
2299
                if e.errno == errno.ENOENT:
2370
2300
                    return False
2407
2337
                size = None
2408
2338
                executable = None
2409
2339
            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)
 
2340
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2413
2341
        executable = tt._new_executability.get(trans_id, executable)
2414
2342
        return kind, size, executable, link_or_sha1
2415
2343
 
2416
2344
    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):
 
2345
                      specific_files=None, pb=None, extra_trees=None,
 
2346
                      require_versioned=True, want_unversioned=False):
2419
2347
        """See InterTree.iter_changes.
2420
2348
 
2421
2349
        This has a fast path that is only used when the from_tree matches
2422
2350
        the transform tree, and no fancy options are supplied.
2423
2351
        """
2424
 
        if (from_tree is not self._transform._tree or include_unchanged
2425
 
                or specific_files or want_unversioned):
 
2352
        if (from_tree is not self._transform._tree or include_unchanged or
 
2353
            specific_files or want_unversioned):
2426
2354
            return tree.InterTree(from_tree, self).iter_changes(
2427
2355
                include_unchanged=include_unchanged,
2428
2356
                specific_files=specific_files,
2434
2362
            raise ValueError('want_unversioned is not supported')
2435
2363
        return self._transform.iter_changes()
2436
2364
 
2437
 
    def get_file(self, path):
 
2365
    def get_file(self, path, file_id=None):
2438
2366
        """See Tree.get_file"""
2439
 
        file_id = self.path2id(path)
 
2367
        if file_id is None:
 
2368
            file_id = self.path2id(path)
2440
2369
        if not self._content_change(file_id):
2441
 
            return self._transform._tree.get_file(path)
 
2370
            return self._transform._tree.get_file(path, file_id)
2442
2371
        trans_id = self._path2trans_id(path)
2443
2372
        name = self._transform._limbo_name(trans_id)
2444
2373
        return open(name, 'rb')
2445
2374
 
2446
 
    def get_file_with_stat(self, path):
2447
 
        return self.get_file(path), None
 
2375
    def get_file_with_stat(self, path, file_id=None):
 
2376
        return self.get_file(path, file_id), None
2448
2377
 
2449
 
    def annotate_iter(self, path,
 
2378
    def annotate_iter(self, path, file_id=None,
2450
2379
                      default_revision=_mod_revision.CURRENT_REVISION):
2451
 
        file_id = self.path2id(path)
 
2380
        if file_id is None:
 
2381
            file_id = self.path2id(path)
2452
2382
        changes = self._iter_changes_cache.get(file_id)
2453
2383
        if changes is None:
2454
2384
            get_old = True
2455
2385
        else:
2456
 
            changed_content, versioned, kind = (
2457
 
                changes.changed_content, changes.versioned, changes.kind)
 
2386
            changed_content, versioned, kind = (changes[2], changes[3],
 
2387
                                                changes[6])
2458
2388
            if kind[1] is None:
2459
2389
                return None
2460
2390
            get_old = (kind[0] == 'file' and versioned[0])
2461
2391
        if get_old:
2462
2392
            old_annotation = self._transform._tree.annotate_iter(
2463
 
                path, default_revision=default_revision)
 
2393
                    path, file_id=file_id, default_revision=default_revision)
2464
2394
        else:
2465
2395
            old_annotation = []
2466
2396
        if changes is None:
2475
2405
        #       It would be nice to be able to use the new Annotator based
2476
2406
        #       approach, as well.
2477
2407
        return annotate.reannotate([old_annotation],
2478
 
                                   self.get_file(path).readlines(),
 
2408
                                   self.get_file(path, file_id).readlines(),
2479
2409
                                   default_revision)
2480
2410
 
2481
 
    def get_symlink_target(self, path):
 
2411
    def get_symlink_target(self, path, file_id=None):
2482
2412
        """See Tree.get_symlink_target"""
2483
 
        file_id = self.path2id(path)
 
2413
        if file_id is None:
 
2414
            file_id = self.path2id(path)
2484
2415
        if not self._content_change(file_id):
2485
2416
            return self._transform._tree.get_symlink_target(path)
2486
2417
        trans_id = self._path2trans_id(path)
2500
2431
                path_from_root = self._final_paths.get_path(child_id)
2501
2432
                basename = self._transform.final_name(child_id)
2502
2433
                file_id = self._transform.final_file_id(child_id)
2503
 
                kind = self._transform.final_kind(child_id)
 
2434
                kind  = self._transform.final_kind(child_id)
2504
2435
                if kind is not None:
2505
2436
                    versioned_kind = kind
2506
2437
                else:
2507
2438
                    kind = 'unknown'
2508
2439
                    versioned_kind = self._transform._tree.stored_kind(
2509
 
                        self._transform._tree.id2path(file_id))
 
2440
                            self._transform._tree.id2path(file_id),
 
2441
                            file_id)
2510
2442
                if versioned_kind == 'directory':
2511
2443
                    subdirs.append(child_id)
2512
2444
                children.append((path_from_root, basename, kind, None,
2541
2473
    The underlying tree must not be manipulated between calls, or else
2542
2474
    the results will likely be incorrect.
2543
2475
    """
2544
 
 
2545
2476
    def __init__(self, transform):
2546
2477
        object.__init__(self)
2547
2478
        self._known_paths = {}
2549
2480
 
2550
2481
    def _determine_path(self, trans_id):
2551
2482
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2552
 
            return u""
 
2483
            return ""
2553
2484
        name = self.transform.final_name(trans_id)
2554
2485
        parent_id = self.transform.final_parent(trans_id)
2555
2486
        if parent_id == self.transform.root:
2567
2498
        return [(self.get_path(t), t) for t in trans_ids]
2568
2499
 
2569
2500
 
 
2501
 
 
2502
def topology_sorted_ids(tree):
 
2503
    """Determine the topological order of the ids in a tree"""
 
2504
    file_ids = list(tree)
 
2505
    file_ids.sort(key=tree.id2path)
 
2506
    return file_ids
 
2507
 
 
2508
 
2570
2509
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2571
2510
               delta_from_tree=False):
2572
2511
    """Create working tree for a branch, using a TreeTransform.
2593
2532
    :param delta_from_tree: If true, build_tree may use the input Tree to
2594
2533
        generate the inventory delta.
2595
2534
    """
2596
 
    with cleanup.ExitStack() as exit_stack:
2597
 
        exit_stack.enter_context(wt.lock_tree_write())
2598
 
        exit_stack.enter_context(tree.lock_read())
 
2535
    with wt.lock_tree_write(), tree.lock_read():
2599
2536
        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)
 
2537
            accelerator_tree.lock_read()
 
2538
        try:
 
2539
            return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2540
                               delta_from_tree)
 
2541
        finally:
 
2542
            if accelerator_tree is not None:
 
2543
                accelerator_tree.unlock()
2603
2544
 
2604
2545
 
2605
2546
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2610
2551
    file_trans_id = {}
2611
2552
    top_pb = ui.ui_factory.nested_progress_bar()
2612
2553
    pp = ProgressPhase("Build phase", 2, top_pb)
2613
 
    if tree.path2id('') is not None:
 
2554
    if tree.get_root_id() is not None:
2614
2555
        # This is kind of a hack: we should be altering the root
2615
2556
        # as part of the regular tree shape diff logic.
2616
2557
        # The conditional test here is to avoid doing an
2618
2559
        # is set within the tree, nor setting the root and thus
2619
2560
        # marking the tree as dirty, because we use two different
2620
2561
        # idioms here: tree interfaces and inventory interfaces.
2621
 
        if wt.path2id('') != tree.path2id(''):
2622
 
            wt.set_root_id(tree.path2id(''))
 
2562
        if wt.get_root_id() != tree.get_root_id():
 
2563
            wt.set_root_id(tree.get_root_id())
2623
2564
            wt.flush()
2624
 
    tt = wt.get_transform()
 
2565
    tt = TreeTransform(wt)
2625
2566
    divert = set()
2626
2567
    try:
2627
2568
        pp.next_phase()
2628
 
        file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
 
2569
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2629
2570
        with ui.ui_factory.nested_progress_bar() as pb:
2630
2571
            deferred_contents = []
2631
2572
            num = 0
2644
2585
                for dir, files in wt.walkdirs():
2645
2586
                    existing_files.update(f[0] for f in files)
2646
2587
            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)
 
2588
                enumerate(tree.iter_entries_by_dir()):
 
2589
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2650
2590
                if entry.parent_id is None:
2651
2591
                    continue
2652
2592
                reparent = False
2663
2603
                            pass
2664
2604
                        else:
2665
2605
                            divert.add(file_id)
2666
 
                    if (file_id not in divert
2667
 
                        and _content_match(
2668
 
                            tree, entry, tree_path, kind, target_path)):
 
2606
                    if (file_id not in divert and
 
2607
                        _content_match(tree, entry, tree_path, file_id, kind,
 
2608
                        target_path)):
2669
2609
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2670
2610
                        if kind == 'directory':
2671
2611
                            reparent = True
2676
2616
                    trans_id = tt.create_path(entry.name, parent_id)
2677
2617
                    file_trans_id[file_id] = trans_id
2678
2618
                    tt.version_file(file_id, trans_id)
2679
 
                    executable = tree.is_executable(tree_path)
 
2619
                    executable = tree.is_executable(tree_path, file_id)
2680
2620
                    if executable:
2681
2621
                        tt.set_executability(executable, trans_id)
2682
 
                    trans_data = (trans_id, file_id,
2683
 
                                  tree_path, entry.text_sha1)
 
2622
                    trans_data = (trans_id, file_id, tree_path, entry.text_sha1)
2684
2623
                    deferred_contents.append((tree_path, trans_data))
2685
2624
                else:
2686
2625
                    file_trans_id[file_id] = new_by_entry(
2687
 
                        tree_path, tt, entry, parent_id, tree)
 
2626
                            tree_path, tt, entry, parent_id, tree)
2688
2627
                if reparent:
2689
2628
                    new_trans_id = file_trans_id[file_id]
2690
2629
                    old_parent = tt.trans_id_tree_path(tree_path)
2694
2633
                          accelerator_tree, hardlink)
2695
2634
        pp.next_phase()
2696
2635
        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)
 
2636
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2700
2637
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2701
2638
        if len(raw_conflicts) > 0:
2702
2639
            precomputed_delta = None
2703
2640
        conflicts = cook_conflicts(raw_conflicts, tt)
2704
2641
        for conflict in conflicts:
2705
 
            trace.warning(text_type(conflict))
 
2642
            trace.warning(unicode(conflict))
2706
2643
        try:
2707
2644
            wt.add_conflicts(conflicts)
2708
2645
        except errors.UnsupportedOperation:
2723
2660
        new_desired_files = desired_files
2724
2661
    else:
2725
2662
        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])]
 
2663
        unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
 
2664
                     in iter if not (c or e[0] != e[1])]
2729
2665
        if accelerator_tree.supports_content_filtering():
2730
2666
            unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
2667
                         if not next(accelerator_tree.iter_search_rules([ap]))]
2736
2672
            accelerator_path = unchanged.get(tree_path)
2737
2673
            if accelerator_path is None:
2738
2674
                new_desired_files.append((tree_path,
2739
 
                                          (trans_id, file_id, tree_path, text_sha1)))
 
2675
                    (trans_id, file_id, tree_path, text_sha1)))
2740
2676
                continue
2741
2677
            pb.update(gettext('Adding file contents'), count + offset, total)
2742
2678
            if hardlink:
2743
2679
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2744
2680
                                   trans_id)
2745
2681
            else:
2746
 
                with accelerator_tree.get_file(accelerator_path) as f:
2747
 
                    chunks = osutils.file_iterator(f)
2748
 
                    if wt.supports_content_filtering():
2749
 
                        filters = wt._content_filter_stack(tree_path)
2750
 
                        chunks = filtered_output_bytes(chunks, filters,
2751
 
                                                       ContentFilterContext(tree_path, tree))
2752
 
                    tt.create_file(chunks, trans_id, sha1=text_sha1)
 
2682
                contents = accelerator_tree.get_file(accelerator_path, file_id)
 
2683
                if wt.supports_content_filtering():
 
2684
                    filters = wt._content_filter_stack(tree_path)
 
2685
                    contents = filtered_output_bytes(contents, filters,
 
2686
                        ContentFilterContext(tree_path, tree))
 
2687
                try:
 
2688
                    tt.create_file(contents, trans_id, sha1=text_sha1)
 
2689
                finally:
 
2690
                    try:
 
2691
                        contents.close()
 
2692
                    except AttributeError:
 
2693
                        # after filtering, contents may no longer be file-like
 
2694
                        pass
2753
2695
            count += 1
2754
2696
        offset += count
2755
2697
    for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2757
2699
        if wt.supports_content_filtering():
2758
2700
            filters = wt._content_filter_stack(tree_path)
2759
2701
            contents = filtered_output_bytes(contents, filters,
2760
 
                                             ContentFilterContext(tree_path, tree))
 
2702
                ContentFilterContext(tree_path, tree))
2761
2703
        tt.create_file(contents, trans_id, sha1=text_sha1)
2762
2704
        pb.update(gettext('Adding file contents'), count + offset, total)
2763
2705
 
2774
2716
    return by_parent[old_parent]
2775
2717
 
2776
2718
 
2777
 
def _content_match(tree, entry, tree_path, kind, target_path):
 
2719
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
2778
2720
    if entry.kind != kind:
2779
2721
        return False
2780
2722
    if entry.kind == "directory":
2781
2723
        return True
2782
2724
    if entry.kind == "file":
2783
 
        with open(target_path, 'rb') as f1, \
2784
 
                tree.get_file(tree_path) as f2:
2785
 
            if osutils.compare_files(f1, f2):
 
2725
        f = file(target_path, 'rb')
 
2726
        try:
 
2727
            if tree.get_file_text(tree_path, file_id) == f.read():
2786
2728
                return True
 
2729
        finally:
 
2730
            f.close()
2787
2731
    elif entry.kind == "symlink":
2788
 
        if tree.get_symlink_target(tree_path) == os.readlink(target_path):
 
2732
        if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
2789
2733
            return True
2790
2734
    return False
2791
2735
 
2808
2752
        # resolved
2809
2753
        final_parent = tt.final_parent(old_file)
2810
2754
        if new_file in divert:
2811
 
            new_name = tt.final_name(old_file) + '.diverted'
 
2755
            new_name = tt.final_name(old_file)+'.diverted'
2812
2756
            tt.adjust_path(new_name, final_parent, new_file)
2813
2757
            new_conflicts.add((c_type, 'Diverted to',
2814
2758
                               new_file, old_file))
2815
2759
        else:
2816
 
            new_name = tt.final_name(old_file) + '.moved'
 
2760
            new_name = tt.final_name(old_file)+'.moved'
2817
2761
            tt.adjust_path(new_name, final_parent, old_file)
2818
2762
            new_conflicts.add((c_type, 'Moved existing file to',
2819
2763
                               old_file, new_file))
2825
2769
    name = entry.name
2826
2770
    kind = entry.kind
2827
2771
    if kind == 'file':
2828
 
        with tree.get_file(path) as f:
2829
 
            executable = tree.is_executable(path)
2830
 
            return tt.new_file(
2831
 
                name, parent_id, osutils.file_iterator(f), entry.file_id,
2832
 
                executable)
 
2772
        contents = tree.get_file(path, entry.file_id).readlines()
 
2773
        executable = tree.is_executable(path, entry.file_id)
 
2774
        return tt.new_file(name, parent_id, contents, entry.file_id,
 
2775
                           executable)
2833
2776
    elif kind in ('directory', 'tree-reference'):
2834
2777
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
2835
2778
        if kind == 'tree-reference':
2836
2779
            tt.set_tree_reference(entry.reference_revision, trans_id)
2837
2780
        return trans_id
2838
2781
    elif kind == 'symlink':
2839
 
        target = tree.get_symlink_target(path)
 
2782
        target = tree.get_symlink_target(path, entry.file_id)
2840
2783
        return tt.new_symlink(name, parent_id, target, entry.file_id)
2841
2784
    else:
2842
2785
        raise errors.BadFileKindError(name, kind)
2843
2786
 
2844
2787
 
2845
 
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2846
 
                     filter_tree_path=None):
 
2788
def create_from_tree(tt, trans_id, tree, path, file_id=None, bytes=None,
 
2789
    filter_tree_path=None):
2847
2790
    """Create new file contents according to tree contents.
2848
2791
 
2849
2792
    :param filter_tree_path: the tree path to use to lookup
2850
2793
      content filters to apply to the bytes output in the working tree.
2851
2794
      This only applies if the working tree supports content filtering.
2852
2795
    """
2853
 
    kind = tree.kind(path)
 
2796
    kind = tree.kind(path, file_id)
2854
2797
    if kind == 'directory':
2855
2798
        tt.create_directory(trans_id)
2856
2799
    elif kind == "file":
2857
 
        if chunks is None:
2858
 
            f = tree.get_file(path)
2859
 
            chunks = osutils.file_iterator(f)
2860
 
        else:
2861
 
            f = None
2862
 
        try:
2863
 
            wt = tt._tree
2864
 
            if wt.supports_content_filtering() and filter_tree_path is not None:
2865
 
                filters = wt._content_filter_stack(filter_tree_path)
2866
 
                chunks = filtered_output_bytes(
2867
 
                    chunks, filters,
2868
 
                    ContentFilterContext(filter_tree_path, tree))
2869
 
            tt.create_file(chunks, trans_id)
2870
 
        finally:
2871
 
            if f is not None:
2872
 
                f.close()
 
2800
        if bytes is None:
 
2801
            tree_file = tree.get_file(path, file_id)
 
2802
            try:
 
2803
                bytes = tree_file.readlines()
 
2804
            finally:
 
2805
                tree_file.close()
 
2806
        wt = tt._tree
 
2807
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2808
            filters = wt._content_filter_stack(filter_tree_path)
 
2809
            bytes = filtered_output_bytes(bytes, filters,
 
2810
                ContentFilterContext(filter_tree_path, tree))
 
2811
        tt.create_file(bytes, trans_id)
2873
2812
    elif kind == "symlink":
2874
 
        tt.create_symlink(tree.get_symlink_target(path), trans_id)
 
2813
        tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
2875
2814
    else:
2876
2815
        raise AssertionError('Unknown kind %r' % kind)
2877
2816
 
2885
2824
def revert(working_tree, target_tree, filenames, backups=False,
2886
2825
           pb=None, change_reporter=None):
2887
2826
    """Revert a working tree's contents to those of a target tree."""
 
2827
    target_tree.lock_read()
2888
2828
    pb = ui.ui_factory.nested_progress_bar()
 
2829
    tt = TreeTransform(working_tree, pb)
2889
2830
    try:
2890
 
        with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2891
 
            pp = ProgressPhase("Revert phase", 3, pb)
2892
 
            conflicts, merge_modified = _prepare_revert_transform(
2893
 
                working_tree, target_tree, tt, filenames, backups, pp)
2894
 
            if change_reporter:
2895
 
                change_reporter = delta._ChangeReporter(
2896
 
                    unversioned_filter=working_tree.is_ignored)
2897
 
                delta.report_changes(tt.iter_changes(), change_reporter)
2898
 
            for conflict in conflicts:
2899
 
                trace.warning(text_type(conflict))
2900
 
            pp.next_phase()
2901
 
            tt.apply()
2902
 
            if working_tree.supports_merge_modified():
2903
 
                working_tree.set_merge_modified(merge_modified)
 
2831
        pp = ProgressPhase("Revert phase", 3, pb)
 
2832
        conflicts, merge_modified = _prepare_revert_transform(
 
2833
            working_tree, target_tree, tt, filenames, backups, pp)
 
2834
        if change_reporter:
 
2835
            change_reporter = delta._ChangeReporter(
 
2836
                unversioned_filter=working_tree.is_ignored)
 
2837
            delta.report_changes(tt.iter_changes(), change_reporter)
 
2838
        for conflict in conflicts:
 
2839
            trace.warning(unicode(conflict))
 
2840
        pp.next_phase()
 
2841
        tt.apply()
 
2842
        if working_tree.supports_merge_modified():
 
2843
            working_tree.set_merge_modified(merge_modified)
2904
2844
    finally:
 
2845
        target_tree.unlock()
 
2846
        tt.finalize()
2905
2847
        pb.clear()
2906
2848
    return conflicts
2907
2849
 
2916
2858
                                      child_pb, filenames, backups,
2917
2859
                                      merge_modified, basis_tree)
2918
2860
    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))
 
2861
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2862
            lambda t, c: conflict_pass(t, c, target_tree))
2921
2863
    conflicts = cook_conflicts(raw_conflicts, tt)
2922
2864
    return conflicts, merge_modified
2923
2865
 
2930
2872
    # than the target changes relative to the working tree. Because WT4 has an
2931
2873
    # optimizer to compare itself to a target, but no optimizer for the
2932
2874
    # reverse.
2933
 
    change_list = working_tree.iter_changes(
2934
 
        target_tree, specific_files=specific_files, pb=pb)
2935
 
    if not target_tree.is_versioned(u''):
 
2875
    change_list = working_tree.iter_changes(target_tree,
 
2876
        specific_files=specific_files, pb=pb)
 
2877
    if target_tree.get_root_id() is None:
2936
2878
        skip_root = True
2937
2879
    else:
2938
2880
        skip_root = False
2939
2881
    try:
2940
2882
        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
 
2883
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
2884
                kind, executable) in enumerate(change_list):
 
2885
            target_path, wt_path = path
 
2886
            target_versioned, wt_versioned = versioned
 
2887
            target_parent, wt_parent = parent
 
2888
            target_name, wt_name = name
 
2889
            target_kind, wt_kind = kind
 
2890
            target_executable, wt_executable = executable
2949
2891
            if skip_root and wt_parent is None:
2950
2892
                continue
2951
2893
            trans_id = tt.trans_id_file_id(file_id)
2952
2894
            mode_id = None
2953
 
            if change.changed_content:
 
2895
            if changed_content:
2954
2896
                keep_content = False
2955
2897
                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:
 
2898
                    wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
 
2899
                    if merge_modified.get(file_id) != wt_sha1:
2958
2900
                        # acquire the basis tree lazily to prevent the
2959
2901
                        # expense of accessing it when it's not needed ?
2960
2902
                        # (Guessing, RBC, 200702)
2961
2903
                        if basis_tree is None:
2962
2904
                            basis_tree = working_tree.basis_tree()
2963
2905
                            basis_tree.lock_read()
2964
 
                        basis_inter = InterTree.get(basis_tree, working_tree)
2965
 
                        basis_path = basis_inter.find_source_path(wt_path)
 
2906
                        basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2966
2907
                        if basis_path is None:
2967
2908
                            if target_kind is None and not target_versioned:
2968
2909
                                keep_content = True
2969
2910
                        else:
2970
 
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path):
 
2911
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2971
2912
                                keep_content = True
2972
2913
                if wt_kind is not None:
2973
2914
                    if not keep_content:
2989
2930
                    tt.create_directory(trans_id)
2990
2931
                    if target_kind == 'tree-reference':
2991
2932
                        revision = target_tree.get_reference_revision(
2992
 
                            target_path)
 
2933
                                target_path, file_id)
2993
2934
                        tt.set_tree_reference(revision, trans_id)
2994
2935
                elif target_kind == 'symlink':
2995
2936
                    tt.create_symlink(target_tree.get_symlink_target(
2996
 
                        target_path), trans_id)
 
2937
                            target_path, file_id), trans_id)
2997
2938
                elif target_kind == 'file':
2998
 
                    deferred_files.append(
2999
 
                        (target_path, (trans_id, mode_id, file_id)))
 
2939
                    deferred_files.append((target_path, (trans_id, mode_id, file_id)))
3000
2940
                    if basis_tree is None:
3001
2941
                        basis_tree = working_tree.basis_tree()
3002
2942
                        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)
 
2943
                    new_sha1 = target_tree.get_file_sha1(target_path, file_id)
 
2944
                    basis_path = find_previous_path(target_tree, basis_tree, target_path)
3006
2945
                    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]
 
2946
                        new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
 
2947
                        if file_id in merge_modified:
 
2948
                            del merge_modified[file_id]
3012
2949
                    else:
3013
 
                        merge_modified[target_path] = new_sha1
 
2950
                        merge_modified[file_id] = new_sha1
3014
2951
 
3015
2952
                    # preserve the execute bit when backing up
3016
2953
                    if keep_content and wt_executable == target_executable:
3021
2958
                tt.version_file(file_id, trans_id)
3022
2959
            if wt_versioned and not target_versioned:
3023
2960
                tt.unversion_file(trans_id)
3024
 
            if (target_name is not None
3025
 
                    and (wt_name != target_name or wt_parent != target_parent)):
 
2961
            if (target_name is not None and
 
2962
                (wt_name != target_name or wt_parent != target_parent)):
3026
2963
                if target_name == '' and target_parent is None:
3027
2964
                    parent_trans = ROOT_PARENT
3028
2965
                else:
3035
2972
                tt.set_executability(target_executable, trans_id)
3036
2973
        if working_tree.supports_content_filtering():
3037
2974
            for (trans_id, mode_id, file_id), bytes in (
3038
 
                    target_tree.iter_files_bytes(deferred_files)):
 
2975
                target_tree.iter_files_bytes(deferred_files)):
3039
2976
                # We're reverting a tree to the target tree so using the
3040
2977
                # target tree to find the file path seems the best choice
3041
2978
                # here IMO - Ian C 27/Oct/2009
3042
2979
                filter_tree_path = target_tree.id2path(file_id)
3043
2980
                filters = working_tree._content_filter_stack(filter_tree_path)
3044
 
                bytes = filtered_output_bytes(
3045
 
                    bytes, filters,
 
2981
                bytes = filtered_output_bytes(bytes, filters,
3046
2982
                    ContentFilterContext(filter_tree_path, working_tree))
3047
2983
                tt.create_file(bytes, trans_id, mode_id)
3048
2984
        else:
3049
2985
            for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3050
 
                    deferred_files):
 
2986
                deferred_files):
3051
2987
                tt.create_file(bytes, trans_id, mode_id)
3052
2988
        tt.fixup_new_roots()
3053
2989
    finally:
3063
2999
    new_conflicts = set()
3064
3000
    with ui.ui_factory.nested_progress_bar() as pb:
3065
3001
        for n in range(10):
3066
 
            pb.update(gettext('Resolution pass'), n + 1, 10)
 
3002
            pb.update(gettext('Resolution pass'), n+1, 10)
3067
3003
            conflicts = tt.find_conflicts()
3068
3004
            if len(conflicts) == 0:
3069
3005
                return new_conflicts
3161
3097
            file_id = tt.inactive_file_id(conflict[1])
3162
3098
            # special-case the other tree root (move its children instead)
3163
3099
            if path_tree and path_tree.path2id('') == file_id:
3164
 
                # This is the root entry, skip it
3165
 
                continue
 
3100
                    # This is the root entry, skip it
 
3101
                    continue
3166
3102
            tt.version_file(file_id, conflict[1])
3167
3103
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
3168
3104
        elif c_type == 'non-directory parent':
3171
3107
            parent_name = tt.final_name(parent_id)
3172
3108
            parent_file_id = tt.final_file_id(parent_id)
3173
3109
            new_parent_id = tt.new_directory(parent_name + '.new',
3174
 
                                             parent_parent, parent_file_id)
 
3110
                parent_parent, parent_file_id)
3175
3111
            _reparent_transform_children(tt, parent_id, new_parent_id)
3176
3112
            if parent_file_id is not None:
3177
3113
                tt.unversion_file(parent_id)
3245
3181
            except OSError as e:
3246
3182
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3247
3183
        # after rollback, don't reuse _FileMover
3248
 
        self.past_renames = None
3249
 
        self.pending_deletions = None
 
3184
        past_renames = None
 
3185
        pending_deletions = None
3250
3186
 
3251
3187
    def apply_deletions(self):
3252
3188
        """Apply all marked deletions"""
3253
3189
        for path in self.pending_deletions:
3254
3190
            delete_any(path)
3255
3191
        # after apply_deletions, don't reuse _FileMover
3256
 
        self.past_renames = None
3257
 
        self.pending_deletions = None
 
3192
        past_renames = None
 
3193
        pending_deletions = None
3258
3194
 
3259
3195
 
3260
3196
def link_tree(target_tree, source_tree):
3263
3199
    :param target_tree: Tree to change
3264
3200
    :param source_tree: Tree to hard-link from
3265
3201
    """
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])
 
3202
    tt = TreeTransform(target_tree)
 
3203
    try:
 
3204
        for (file_id, paths, changed_content, versioned, parent, name, kind,
 
3205
             executable) in target_tree.iter_changes(source_tree,
 
3206
             include_unchanged=True):
 
3207
            if changed_content:
 
3208
                continue
 
3209
            if kind != ('file', 'file'):
 
3210
                continue
 
3211
            if executable[0] != executable[1]:
 
3212
                continue
 
3213
            trans_id = tt.trans_id_tree_path(paths[1])
3275
3214
            tt.delete_contents(trans_id)
3276
 
            tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
 
3215
            tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)
3277
3216
        tt.apply()
 
3217
    finally:
 
3218
        tt.finalize()