/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

1st cut merge of bzr.dev r3907

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
import os
18
18
import errno
19
19
from stat import S_ISREG, S_IEXEC
20
 
import tempfile
21
20
 
22
21
from bzrlib.lazy_import import lazy_import
23
22
lazy_import(globals(), """
27
26
    delta,
28
27
    errors,
29
28
    inventory,
 
29
    multiparent,
 
30
    osutils,
30
31
    revision as _mod_revision,
31
32
    )
 
33
from bzrlib.util import bencode
32
34
""")
33
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
34
36
                           ReusingTransform, NotVersionedError, CantMoveRoot,
42
44
    has_symlinks,
43
45
    lexists,
44
46
    pathjoin,
 
47
    sha_file,
45
48
    splitpath,
46
49
    supports_executable,
47
50
)
48
51
from bzrlib.progress import DummyProgress, ProgressPhase
49
52
from bzrlib.symbol_versioning import (
50
53
        deprecated_function,
 
54
        deprecated_in,
51
55
        )
52
56
from bzrlib.trace import mutter, warning
53
57
from bzrlib import tree
129
133
        # Cache of relpath results, to speed up canonical_path
130
134
        self._relpaths = {}
131
135
        # The trans_id that will be used as the tree root
132
 
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
 
136
        root_id = tree.get_root_id()
 
137
        if root_id is not None:
 
138
            self._new_root = self.trans_id_tree_file_id(root_id)
 
139
        else:
 
140
            self._new_root = None
133
141
        # Indictor of whether the transform has been applied
134
142
        self._done = False
135
143
        # A progress bar
196
204
        previous_name = self._new_name.get(trans_id)
197
205
        self._new_name[trans_id] = name
198
206
        self._new_parent[trans_id] = parent
 
207
        if parent == ROOT_PARENT:
 
208
            if self._new_root is not None:
 
209
                raise ValueError("Cannot have multiple roots.")
 
210
            self._new_root = trans_id
199
211
        if (trans_id in self._limbo_files and
200
212
            trans_id not in self._needs_rename):
201
213
            self._rename_in_limbo([trans_id])
258
270
        This reflects only files that already exist, not ones that will be
259
271
        added by transactions.
260
272
        """
 
273
        if inventory_id is None:
 
274
            raise ValueError('None is not a valid file id')
261
275
        path = self._tree.id2path(inventory_id)
262
276
        return self.trans_id_tree_path(path)
263
277
 
267
281
        a transaction has been unversioned, it is deliberately still returned.
268
282
        (this will likely lead to an unversioned parent conflict.)
269
283
        """
 
284
        if file_id is None:
 
285
            raise ValueError('None is not a valid file id')
270
286
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
271
287
            return self._r_new_id[file_id]
272
 
        elif file_id in self._tree.inventory:
273
 
            return self.trans_id_tree_file_id(file_id)
274
 
        elif file_id in self._non_present_ids:
275
 
            return self._non_present_ids[file_id]
276
288
        else:
277
 
            trans_id = self._assign_id()
278
 
            self._non_present_ids[file_id] = trans_id
279
 
            return trans_id
 
289
            try:
 
290
                self._tree.iter_entries_by_dir([file_id]).next()
 
291
            except StopIteration:
 
292
                if file_id in self._non_present_ids:
 
293
                    return self._non_present_ids[file_id]
 
294
                else:
 
295
                    trans_id = self._assign_id()
 
296
                    self._non_present_ids[file_id] = trans_id
 
297
                    return trans_id
 
298
            else:
 
299
                return self.trans_id_tree_file_id(file_id)
280
300
 
281
301
    def canonical_path(self, path):
282
302
        """Get the canonical tree-relative path"""
471
491
        """
472
492
        new_ids = set()
473
493
        if filesystem_only:
474
 
            id_sets = (self._needs_rename, self._new_executability)
 
494
            stale_ids = self._needs_rename.difference(self._new_name)
 
495
            stale_ids.difference_update(self._new_parent)
 
496
            stale_ids.difference_update(self._new_contents)
 
497
            stale_ids.difference_update(self._new_id)
 
498
            needs_rename = self._needs_rename.difference(stale_ids)
 
499
            id_sets = (needs_rename, self._new_executability)
475
500
        else:
476
501
            id_sets = (self._new_name, self._new_parent, self._new_contents,
477
502
                       self._new_id, self._new_executability)
479
504
            new_ids.update(id_set)
480
505
        return sorted(FinalPaths(self).get_paths(new_ids))
481
506
 
 
507
    def _inventory_altered(self):
 
508
        """Get the trans_ids and paths of files needing new inv entries."""
 
509
        new_ids = set()
 
510
        for id_set in [self._new_name, self._new_parent, self._new_id,
 
511
                       self._new_executability]:
 
512
            new_ids.update(id_set)
 
513
        changed_kind = set(self._removed_contents)
 
514
        changed_kind.intersection_update(self._new_contents)
 
515
        changed_kind.difference_update(new_ids)
 
516
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
 
517
                        self.final_kind(t))
 
518
        new_ids.update(changed_kind)
 
519
        return sorted(FinalPaths(self).get_paths(new_ids))
 
520
 
482
521
    def tree_kind(self, trans_id):
483
522
        """Determine the file kind in the working tree.
484
523
 
519
558
        # the file is old; the old id is still valid
520
559
        if self._new_root == trans_id:
521
560
            return self._tree.get_root_id()
522
 
        return self._tree.inventory.path2id(path)
 
561
        return self._tree.path2id(path)
523
562
 
524
563
    def final_file_id(self, trans_id):
525
564
        """Determine the file id after any changes are applied, or None.
637
676
        try:
638
677
            children = os.listdir(self._tree.abspath(path))
639
678
        except OSError, e:
640
 
            if e.errno not in (errno.ENOENT, errno.ESRCH, errno.ENOTDIR):
 
679
            if not (osutils._is_error_enotdir(e)
 
680
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
641
681
                raise
642
682
            return
643
 
            
 
683
 
644
684
        for child in children:
645
685
            childpath = joinpath(path, child)
646
686
            if self._tree.is_control_filename(childpath):
871
911
        self._limbo_files[trans_id] = limbo_name
872
912
        return limbo_name
873
913
 
874
 
    def _set_executability(self, path, entry, trans_id):
 
914
    def _set_executability(self, path, trans_id):
875
915
        """Set the executability of versioned files """
876
 
        new_executability = self._new_executability[trans_id]
877
 
        if entry is not None:
878
 
            entry.executable = new_executability
879
916
        if supports_executable():
 
917
            new_executability = self._new_executability[trans_id]
880
918
            abspath = self._tree.abspath(path)
881
919
            current_mode = os.stat(abspath).st_mode
882
920
            if new_executability:
977
1015
        from_path = self._tree_id_paths.get(from_trans_id)
978
1016
        if from_versioned:
979
1017
            # get data from working tree if versioned
980
 
            from_entry = self._tree.inventory[file_id]
 
1018
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
981
1019
            from_name = from_entry.name
982
1020
            from_parent = from_entry.parent_id
983
1021
        else:
1089
1127
        """
1090
1128
        return _PreviewTree(self)
1091
1129
 
 
1130
    def _text_parent(self, trans_id):
 
1131
        file_id = self.tree_file_id(trans_id)
 
1132
        try:
 
1133
            if file_id is None or self._tree.kind(file_id) != 'file':
 
1134
                return None
 
1135
        except errors.NoSuchFile:
 
1136
            return None
 
1137
        return file_id
 
1138
 
 
1139
    def _get_parents_texts(self, trans_id):
 
1140
        """Get texts for compression parents of this file."""
 
1141
        file_id = self._text_parent(trans_id)
 
1142
        if file_id is None:
 
1143
            return ()
 
1144
        return (self._tree.get_file_text(file_id),)
 
1145
 
 
1146
    def _get_parents_lines(self, trans_id):
 
1147
        """Get lines for compression parents of this file."""
 
1148
        file_id = self._text_parent(trans_id)
 
1149
        if file_id is None:
 
1150
            return ()
 
1151
        return (self._tree.get_file_lines(file_id),)
 
1152
 
 
1153
    def serialize(self, serializer):
 
1154
        """Serialize this TreeTransform.
 
1155
 
 
1156
        :param serializer: A Serialiser like pack.ContainerSerializer.
 
1157
        """
 
1158
        new_name = dict((k, v.encode('utf-8')) for k, v in
 
1159
                        self._new_name.items())
 
1160
        new_executability = dict((k, int(v)) for k, v in
 
1161
                                 self._new_executability.items())
 
1162
        tree_path_ids = dict((k.encode('utf-8'), v)
 
1163
                             for k, v in self._tree_path_ids.items())
 
1164
        attribs = {
 
1165
            '_id_number': self._id_number,
 
1166
            '_new_name': new_name,
 
1167
            '_new_parent': self._new_parent,
 
1168
            '_new_executability': new_executability,
 
1169
            '_new_id': self._new_id,
 
1170
            '_tree_path_ids': tree_path_ids,
 
1171
            '_removed_id': list(self._removed_id),
 
1172
            '_removed_contents': list(self._removed_contents),
 
1173
            '_non_present_ids': self._non_present_ids,
 
1174
            }
 
1175
        yield serializer.bytes_record(bencode.bencode(attribs),
 
1176
                                      (('attribs',),))
 
1177
        for trans_id, kind in self._new_contents.items():
 
1178
            if kind == 'file':
 
1179
                cur_file = open(self._limbo_name(trans_id), 'rb')
 
1180
                try:
 
1181
                    lines = osutils.chunks_to_lines(cur_file.readlines())
 
1182
                finally:
 
1183
                    cur_file.close()
 
1184
                parents = self._get_parents_lines(trans_id)
 
1185
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
 
1186
                content = ''.join(mpdiff.to_patch())
 
1187
            if kind == 'directory':
 
1188
                content = ''
 
1189
            if kind == 'symlink':
 
1190
                content = os.readlink(self._limbo_name(trans_id))
 
1191
            yield serializer.bytes_record(content, ((trans_id, kind),))
 
1192
 
 
1193
 
 
1194
    def deserialize(self, records):
 
1195
        """Deserialize a stored TreeTransform.
 
1196
 
 
1197
        :param records: An iterable of (names, content) tuples, as per
 
1198
            pack.ContainerPushParser.
 
1199
        """
 
1200
        names, content = records.next()
 
1201
        attribs = bencode.bdecode(content)
 
1202
        self._id_number = attribs['_id_number']
 
1203
        self._new_name = dict((k, v.decode('utf-8'))
 
1204
                            for k, v in attribs['_new_name'].items())
 
1205
        self._new_parent = attribs['_new_parent']
 
1206
        self._new_executability = dict((k, bool(v)) for k, v in
 
1207
            attribs['_new_executability'].items())
 
1208
        self._new_id = attribs['_new_id']
 
1209
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
 
1210
        self._tree_path_ids = {}
 
1211
        self._tree_id_paths = {}
 
1212
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
 
1213
            path = bytepath.decode('utf-8')
 
1214
            self._tree_path_ids[path] = trans_id
 
1215
            self._tree_id_paths[trans_id] = path
 
1216
        self._removed_id = set(attribs['_removed_id'])
 
1217
        self._removed_contents = set(attribs['_removed_contents'])
 
1218
        self._non_present_ids = attribs['_non_present_ids']
 
1219
        for ((trans_id, kind),), content in records:
 
1220
            if kind == 'file':
 
1221
                mpdiff = multiparent.MultiParent.from_patch(content)
 
1222
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
 
1223
                self.create_file(lines, trans_id)
 
1224
            if kind == 'directory':
 
1225
                self.create_directory(trans_id)
 
1226
            if kind == 'symlink':
 
1227
                self.create_symlink(content.decode('utf-8'), trans_id)
 
1228
 
1092
1229
 
1093
1230
class TreeTransform(TreeTransformBase):
1094
1231
    """Represent a tree transformation.
1204
1341
            conflicts = self.find_conflicts()
1205
1342
            if len(conflicts) != 0:
1206
1343
                raise MalformedTransform(conflicts=conflicts)
1207
 
        if precomputed_delta is None:
1208
 
            new_inventory_delta = []
1209
 
            inventory_delta = new_inventory_delta
1210
 
        else:
1211
 
            new_inventory_delta = None
1212
 
            inventory_delta = precomputed_delta
1213
1344
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1214
1345
        try:
 
1346
            if precomputed_delta is None:
 
1347
                child_pb.update('Apply phase', 0, 2)
 
1348
                inventory_delta = self._generate_inventory_delta()
 
1349
                offset = 1
 
1350
            else:
 
1351
                inventory_delta = precomputed_delta
 
1352
                offset = 0
1215
1353
            if _mover is None:
1216
1354
                mover = _FileMover()
1217
1355
            else:
1218
1356
                mover = _mover
1219
1357
            try:
1220
 
                child_pb.update('Apply phase', 0, 2)
1221
 
                self._apply_removals(new_inventory_delta, mover)
1222
 
                child_pb.update('Apply phase', 1, 2)
1223
 
                modified_paths = self._apply_insertions(new_inventory_delta,
1224
 
                                                        mover)
 
1358
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1359
                self._apply_removals(mover)
 
1360
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1361
                modified_paths = self._apply_insertions(mover)
1225
1362
            except:
1226
1363
                mover.rollback()
1227
1364
                raise
1234
1371
        self.finalize()
1235
1372
        return _TransformResults(modified_paths, self.rename_count)
1236
1373
 
1237
 
    def _apply_removals(self, inventory_delta, mover):
 
1374
    def _generate_inventory_delta(self):
 
1375
        """Generate an inventory delta for the current transform."""
 
1376
        inventory_delta = []
 
1377
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1378
        new_paths = self._inventory_altered()
 
1379
        total_entries = len(new_paths) + len(self._removed_id)
 
1380
        try:
 
1381
            for num, trans_id in enumerate(self._removed_id):
 
1382
                if (num % 10) == 0:
 
1383
                    child_pb.update('removing file', num, total_entries)
 
1384
                if trans_id == self._new_root:
 
1385
                    file_id = self._tree.get_root_id()
 
1386
                else:
 
1387
                    file_id = self.tree_file_id(trans_id)
 
1388
                # File-id isn't really being deleted, just moved
 
1389
                if file_id in self._r_new_id:
 
1390
                    continue
 
1391
                path = self._tree_id_paths[trans_id]
 
1392
                inventory_delta.append((path, None, file_id, None))
 
1393
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1394
                                     new_paths)
 
1395
            entries = self._tree.iter_entries_by_dir(
 
1396
                new_path_file_ids.values())
 
1397
            old_paths = dict((e.file_id, p) for p, e in entries)
 
1398
            final_kinds = {}
 
1399
            for num, (path, trans_id) in enumerate(new_paths):
 
1400
                if (num % 10) == 0:
 
1401
                    child_pb.update('adding file',
 
1402
                                    num + len(self._removed_id), total_entries)
 
1403
                file_id = new_path_file_ids[trans_id]
 
1404
                if file_id is None:
 
1405
                    continue
 
1406
                needs_entry = False
 
1407
                try:
 
1408
                    kind = self.final_kind(trans_id)
 
1409
                except NoSuchFile:
 
1410
                    kind = self._tree.stored_kind(file_id)
 
1411
                parent_trans_id = self.final_parent(trans_id)
 
1412
                parent_file_id = new_path_file_ids.get(parent_trans_id)
 
1413
                if parent_file_id is None:
 
1414
                    parent_file_id = self.final_file_id(parent_trans_id)
 
1415
                if trans_id in self._new_reference_revision:
 
1416
                    new_entry = inventory.TreeReference(
 
1417
                        file_id,
 
1418
                        self._new_name[trans_id],
 
1419
                        self.final_file_id(self._new_parent[trans_id]),
 
1420
                        None, self._new_reference_revision[trans_id])
 
1421
                else:
 
1422
                    new_entry = inventory.make_entry(kind,
 
1423
                        self.final_name(trans_id),
 
1424
                        parent_file_id, file_id)
 
1425
                old_path = old_paths.get(new_entry.file_id)
 
1426
                new_executability = self._new_executability.get(trans_id)
 
1427
                if new_executability is not None:
 
1428
                    new_entry.executable = new_executability
 
1429
                inventory_delta.append(
 
1430
                    (old_path, path, new_entry.file_id, new_entry))
 
1431
        finally:
 
1432
            child_pb.finished()
 
1433
        return inventory_delta
 
1434
 
 
1435
    def _apply_removals(self, mover):
1238
1436
        """Perform tree operations that remove directory/inventory names.
1239
1437
 
1240
1438
        That is, delete files that are to be deleted, and put any files that
1263
1461
                            raise
1264
1462
                    else:
1265
1463
                        self.rename_count += 1
1266
 
                if (trans_id in self._removed_id
1267
 
                    and inventory_delta is not None):
1268
 
                    if trans_id == self._new_root:
1269
 
                        file_id = self._tree.get_root_id()
1270
 
                    else:
1271
 
                        file_id = self.tree_file_id(trans_id)
1272
 
                    # File-id isn't really being deleted, just moved
1273
 
                    if file_id in self._r_new_id:
1274
 
                        continue
1275
 
                    inventory_delta.append((path, None, file_id, None))
1276
1464
        finally:
1277
1465
            child_pb.finished()
1278
1466
 
1279
 
    def _apply_insertions(self, inventory_delta, mover):
 
1467
    def _apply_insertions(self, mover):
1280
1468
        """Perform tree operations that insert directory/inventory names.
1281
1469
 
1282
1470
        That is, create any files that need to be created, and restore from
1286
1474
        If inventory_delta is None, no inventory delta is calculated, and
1287
1475
        no list of modified paths is returned.
1288
1476
        """
1289
 
        new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
 
1477
        new_paths = self.new_paths(filesystem_only=True)
1290
1478
        modified_paths = []
1291
 
        completed_new = []
1292
1479
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1293
1480
                                 new_paths)
1294
 
        if inventory_delta is not None:
1295
 
            entries = self._tree.iter_entries_by_dir(
1296
 
                new_path_file_ids.values())
1297
 
            old_paths = dict((e.file_id, p) for p, e in entries)
1298
1481
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1299
1482
        try:
1300
1483
            for num, (path, trans_id) in enumerate(new_paths):
1301
 
                new_entry = None
1302
1484
                if (num % 10) == 0:
1303
1485
                    child_pb.update('adding file', num, len(new_paths))
1304
1486
                full_path = self._tree.abspath(path)
1311
1493
                            raise
1312
1494
                    else:
1313
1495
                        self.rename_count += 1
1314
 
                if inventory_delta is not None:
1315
 
                    if (trans_id in self._new_contents or
1316
 
                        self.path_changed(trans_id)):
1317
 
                        if trans_id in self._new_contents:
1318
 
                            modified_paths.append(full_path)
1319
 
                            completed_new.append(trans_id)
1320
 
                    file_id = new_path_file_ids[trans_id]
1321
 
                    if file_id is not None and (trans_id in self._new_id or
1322
 
                        trans_id in self._new_name or
1323
 
                        trans_id in self._new_parent
1324
 
                        or trans_id in self._new_executability):
1325
 
                        try:
1326
 
                            kind = self.final_kind(trans_id)
1327
 
                        except NoSuchFile:
1328
 
                            kind = self._tree.stored_kind(file_id)
1329
 
                        parent_trans_id = self.final_parent(trans_id)
1330
 
                        parent_file_id = new_path_file_ids.get(parent_trans_id)
1331
 
                        if parent_file_id is None:
1332
 
                            parent_file_id = self.final_file_id(
1333
 
                                parent_trans_id)
1334
 
                        if trans_id in self._new_reference_revision:
1335
 
                            new_entry = inventory.TreeReference(
1336
 
                                file_id,
1337
 
                                self._new_name[trans_id],
1338
 
                                self.final_file_id(self._new_parent[trans_id]),
1339
 
                                None, self._new_reference_revision[trans_id])
1340
 
                        else:
1341
 
                            new_entry = inventory.make_entry(kind,
1342
 
                                self.final_name(trans_id),
1343
 
                                parent_file_id, file_id)
1344
 
                        old_path = old_paths.get(new_entry.file_id)
1345
 
                        inventory_delta.append(
1346
 
                            (old_path, path, new_entry.file_id, new_entry))
1347
 
 
 
1496
                if (trans_id in self._new_contents or
 
1497
                    self.path_changed(trans_id)):
 
1498
                    if trans_id in self._new_contents:
 
1499
                        modified_paths.append(full_path)
1348
1500
                if trans_id in self._new_executability:
1349
 
                    self._set_executability(path, new_entry, trans_id)
 
1501
                    self._set_executability(path, trans_id)
1350
1502
        finally:
1351
1503
            child_pb.finished()
1352
 
        if inventory_delta is None:
1353
 
            self._new_contents.clear()
1354
 
        else:
1355
 
            for trans_id in completed_new:
1356
 
                del self._new_contents[trans_id]
 
1504
        self._new_contents.clear()
1357
1505
        return modified_paths
1358
1506
 
1359
1507
 
1367
1515
 
1368
1516
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1369
1517
        tree.lock_read()
1370
 
        limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
 
1518
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1371
1519
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1372
1520
 
1373
1521
    def canonical_path(self, path):
1398
1546
        file_id = self.tree_file_id(parent_id)
1399
1547
        if file_id is None:
1400
1548
            return
1401
 
        children = getattr(self._tree.inventory[file_id], 'children', {})
 
1549
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
1550
        children = getattr(entry, 'children', {})
1402
1551
        for child in children:
1403
1552
            childpath = joinpath(path, child)
1404
1553
            yield self.trans_id_tree_path(childpath)
1412
1561
        self._final_paths = FinalPaths(transform)
1413
1562
        self.__by_parent = None
1414
1563
        self._parent_ids = []
 
1564
        self._all_children_cache = {}
 
1565
        self._path2trans_id_cache = {}
 
1566
        self._final_name_cache = {}
1415
1567
 
1416
1568
    def _changes(self, file_id):
1417
1569
        for changes in self._transform.iter_changes():
1460
1612
            self.__by_parent = self._transform.by_parent()
1461
1613
        return self.__by_parent
1462
1614
 
 
1615
    def _comparison_data(self, entry, path):
 
1616
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
 
1617
        if kind == 'missing':
 
1618
            kind = None
 
1619
            executable = False
 
1620
        else:
 
1621
            file_id = self._transform.final_file_id(self._path2trans_id(path))
 
1622
            executable = self.is_executable(file_id, path)
 
1623
        return kind, executable, None
 
1624
 
1463
1625
    def lock_read(self):
1464
1626
        # Perhaps in theory, this should lock the TreeTransform?
1465
1627
        pass
1485
1647
    def __iter__(self):
1486
1648
        return iter(self.all_file_ids())
1487
1649
 
1488
 
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
1489
 
        """See Tree.paths2ids"""
1490
 
        to_find = set(specific_files)
1491
 
        result = set()
1492
 
        for (file_id, paths, changed, versioned, parent, name, kind,
1493
 
             executable) in self._transform.iter_changes():
1494
 
            if paths[1] in to_find:
1495
 
                result.append(file_id)
1496
 
                to_find.remove(paths[1])
1497
 
        result.update(self._transform._tree.paths2ids(to_find,
1498
 
                      trees=[], require_versioned=require_versioned))
1499
 
        return result
 
1650
    def has_id(self, file_id):
 
1651
        if file_id in self._transform._r_new_id:
 
1652
            return True
 
1653
        elif file_id in self._transform._removed_id:
 
1654
            return False
 
1655
        else:
 
1656
            return self._transform._tree.has_id(file_id)
1500
1657
 
1501
1658
    def _path2trans_id(self, path):
 
1659
        # We must not use None here, because that is a valid value to store.
 
1660
        trans_id = self._path2trans_id_cache.get(path, object)
 
1661
        if trans_id is not object:
 
1662
            return trans_id
1502
1663
        segments = splitpath(path)
1503
1664
        cur_parent = self._transform.root
1504
1665
        for cur_segment in segments:
1505
1666
            for child in self._all_children(cur_parent):
1506
 
                if self._transform.final_name(child) == cur_segment:
 
1667
                final_name = self._final_name_cache.get(child)
 
1668
                if final_name is None:
 
1669
                    final_name = self._transform.final_name(child)
 
1670
                    self._final_name_cache[child] = final_name
 
1671
                if final_name == cur_segment:
1507
1672
                    cur_parent = child
1508
1673
                    break
1509
1674
            else:
 
1675
                self._path2trans_id_cache[path] = None
1510
1676
                return None
 
1677
        self._path2trans_id_cache[path] = cur_parent
1511
1678
        return cur_parent
1512
1679
 
1513
1680
    def path2id(self, path):
1521
1688
            raise errors.NoSuchId(self, file_id)
1522
1689
 
1523
1690
    def _all_children(self, trans_id):
 
1691
        children = self._all_children_cache.get(trans_id)
 
1692
        if children is not None:
 
1693
            return children
1524
1694
        children = set(self._transform.iter_tree_children(trans_id))
1525
1695
        # children in the _new_parent set are provided by _by_parent.
1526
1696
        children.difference_update(self._transform._new_parent.keys())
1527
1697
        children.update(self._by_parent.get(trans_id, []))
 
1698
        self._all_children_cache[trans_id] = children
1528
1699
        return children
1529
1700
 
 
1701
    def iter_children(self, file_id):
 
1702
        trans_id = self._transform.trans_id_file_id(file_id)
 
1703
        for child_trans_id in self._all_children(trans_id):
 
1704
            yield self._transform.final_file_id(child_trans_id)
 
1705
 
 
1706
    def extras(self):
 
1707
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
 
1708
                              in self._transform._tree.extras())
 
1709
        possible_extras.update(self._transform._new_contents)
 
1710
        possible_extras.update(self._transform._removed_id)
 
1711
        for trans_id in possible_extras:
 
1712
            if self._transform.final_file_id(trans_id) is None:
 
1713
                yield self._final_paths._determine_path(trans_id)
 
1714
 
1530
1715
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
1531
1716
        for trans_id, parent_file_id in ordered_entries:
1532
1717
            file_id = self._transform.final_file_id(trans_id)
1545
1730
                parent_file_id, file_id)
1546
1731
            yield new_entry, trans_id
1547
1732
 
1548
 
    def iter_entries_by_dir(self, specific_file_ids=None):
1549
 
        # This may not be a maximally efficient implementation, but it is
1550
 
        # reasonably straightforward.  An implementation that grafts the
1551
 
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1552
 
        # might be more efficient, but requires tricky inferences about stack
1553
 
        # position.
 
1733
    def _list_files_by_dir(self):
1554
1734
        todo = [ROOT_PARENT]
1555
1735
        ordered_ids = []
1556
1736
        while len(todo) > 0:
1562
1742
            todo.extend(reversed(children))
1563
1743
            for trans_id in children:
1564
1744
                ordered_ids.append((trans_id, parent_file_id))
 
1745
        return ordered_ids
 
1746
 
 
1747
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1748
        # This may not be a maximally efficient implementation, but it is
 
1749
        # reasonably straightforward.  An implementation that grafts the
 
1750
        # TreeTransform changes onto the tree's iter_entries_by_dir results
 
1751
        # might be more efficient, but requires tricky inferences about stack
 
1752
        # position.
 
1753
        ordered_ids = self._list_files_by_dir()
1565
1754
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1566
1755
                                                      specific_file_ids):
1567
1756
            yield unicode(self._final_paths.get_path(trans_id)), entry
1568
1757
 
 
1758
    def list_files(self, include_root=False):
 
1759
        """See Tree.list_files."""
 
1760
        # XXX This should behave like WorkingTree.list_files, but is really
 
1761
        # more like RevisionTree.list_files.
 
1762
        for path, entry in self.iter_entries_by_dir():
 
1763
            if entry.name == '' and not include_root:
 
1764
                continue
 
1765
            yield path, 'V', entry.kind, entry.file_id, entry
 
1766
 
1569
1767
    def kind(self, file_id):
1570
1768
        trans_id = self._transform.trans_id_file_id(file_id)
1571
1769
        return self._transform.final_kind(trans_id)
1583
1781
            return self._transform._tree.get_file_mtime(file_id, path)
1584
1782
        return self._stat_limbo_file(file_id).st_mtime
1585
1783
 
 
1784
    def _file_size(self, entry, stat_value):
 
1785
        return self.get_file_size(entry.file_id)
 
1786
 
1586
1787
    def get_file_size(self, file_id):
1587
1788
        """See Tree.get_file_size"""
1588
1789
        if self.kind(file_id) == 'file':
1591
1792
            return None
1592
1793
 
1593
1794
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1594
 
        return self._transform._tree.get_file_sha1(file_id)
 
1795
        trans_id = self._transform.trans_id_file_id(file_id)
 
1796
        kind = self._transform._new_contents.get(trans_id)
 
1797
        if kind is None:
 
1798
            return self._transform._tree.get_file_sha1(file_id)
 
1799
        if kind == 'file':
 
1800
            fileobj = self.get_file(file_id)
 
1801
            try:
 
1802
                return sha_file(fileobj)
 
1803
            finally:
 
1804
                fileobj.close()
1595
1805
 
1596
1806
    def is_executable(self, file_id, path=None):
 
1807
        if file_id is None:
 
1808
            return False
1597
1809
        trans_id = self._transform.trans_id_file_id(file_id)
1598
1810
        try:
1599
1811
            return self._transform._new_executability[trans_id]
1600
1812
        except KeyError:
1601
 
            return self._transform._tree.is_executable(file_id, path)
 
1813
            try:
 
1814
                return self._transform._tree.is_executable(file_id, path)
 
1815
            except OSError, e:
 
1816
                if e.errno == errno.ENOENT:
 
1817
                    return False
 
1818
                raise
 
1819
            except errors.NoSuchId:
 
1820
                return False
1602
1821
 
1603
1822
    def path_content_summary(self, path):
1604
1823
        trans_id = self._path2trans_id(path)
1636
1855
                      require_versioned=True, want_unversioned=False):
1637
1856
        """See InterTree.iter_changes.
1638
1857
 
1639
 
        This implementation does not support include_unchanged, specific_files,
1640
 
        or want_unversioned.  extra_trees, require_versioned, and pb are
1641
 
        ignored.
 
1858
        This has a fast path that is only used when the from_tree matches
 
1859
        the transform tree, and no fancy options are supplied.
1642
1860
        """
1643
 
        if from_tree is not self._transform._tree:
1644
 
            raise ValueError('from_tree must be transform source tree.')
1645
 
        if include_unchanged:
1646
 
            raise ValueError('include_unchanged is not supported')
1647
 
        if specific_files is not None:
1648
 
            raise ValueError('specific_files is not supported')
 
1861
        if (from_tree is not self._transform._tree or include_unchanged or
 
1862
            specific_files or want_unversioned):
 
1863
            return tree.InterTree(from_tree, self).iter_changes(
 
1864
                include_unchanged=include_unchanged,
 
1865
                specific_files=specific_files,
 
1866
                pb=pb,
 
1867
                extra_trees=extra_trees,
 
1868
                require_versioned=require_versioned,
 
1869
                want_unversioned=want_unversioned)
1649
1870
        if want_unversioned:
1650
1871
            raise ValueError('want_unversioned is not supported')
1651
1872
        return self._transform.iter_changes()
1658
1879
        name = self._transform._limbo_name(trans_id)
1659
1880
        return open(name, 'rb')
1660
1881
 
1661
 
    def get_file_text(self, file_id):
1662
 
        text_file = self.get_file(file_id)
1663
 
        try:
1664
 
            return text_file.read()
1665
 
        finally:
1666
 
            text_file.close()
1667
 
 
1668
1882
    def annotate_iter(self, file_id,
1669
1883
                      default_revision=_mod_revision.CURRENT_REVISION):
1670
1884
        changes = self._changes(file_id)
1697
1911
        name = self._transform._limbo_name(trans_id)
1698
1912
        return os.readlink(name)
1699
1913
 
1700
 
    def list_files(self, include_root=False):
1701
 
        return self._transform._tree.list_files(include_root)
1702
 
 
1703
 
    def walkdirs(self, prefix=""):
1704
 
        return self._transform._tree.walkdirs(prefix)
 
1914
    def walkdirs(self, prefix=''):
 
1915
        pending = [self._transform.root]
 
1916
        while len(pending) > 0:
 
1917
            parent_id = pending.pop()
 
1918
            children = []
 
1919
            subdirs = []
 
1920
            prefix = prefix.rstrip('/')
 
1921
            parent_path = self._final_paths.get_path(parent_id)
 
1922
            parent_file_id = self._transform.final_file_id(parent_id)
 
1923
            for child_id in self._all_children(parent_id):
 
1924
                path_from_root = self._final_paths.get_path(child_id)
 
1925
                basename = self._transform.final_name(child_id)
 
1926
                file_id = self._transform.final_file_id(child_id)
 
1927
                try:
 
1928
                    kind = self._transform.final_kind(child_id)
 
1929
                    versioned_kind = kind
 
1930
                except NoSuchFile:
 
1931
                    kind = 'unknown'
 
1932
                    versioned_kind = self._transform._tree.stored_kind(file_id)
 
1933
                if versioned_kind == 'directory':
 
1934
                    subdirs.append(child_id)
 
1935
                children.append((path_from_root, basename, kind, None,
 
1936
                                 file_id, versioned_kind))
 
1937
            children.sort()
 
1938
            if parent_path.startswith(prefix):
 
1939
                yield (parent_path, parent_file_id), children
 
1940
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
 
1941
                                  reverse=True))
1705
1942
 
1706
1943
    def get_parent_ids(self):
1707
1944
        return self._parent_ids
2030
2267
        raise errors.BadFileKindError(name, kind)
2031
2268
 
2032
2269
 
 
2270
@deprecated_function(deprecated_in((1, 9, 0)))
2033
2271
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2034
 
    """Create new file contents according to an inventory entry."""
 
2272
    """Create new file contents according to an inventory entry.
 
2273
 
 
2274
    DEPRECATED.  Use create_from_tree instead.
 
2275
    """
2035
2276
    if entry.kind == "file":
2036
2277
        if lines is None:
2037
2278
            lines = tree.get_file(entry.file_id).readlines()
2042
2283
        tt.create_directory(trans_id)
2043
2284
 
2044
2285
 
 
2286
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
 
2287
    """Create new file contents according to tree contents."""
 
2288
    kind = tree.kind(file_id)
 
2289
    if kind == 'directory':
 
2290
        tt.create_directory(trans_id)
 
2291
    elif kind == "file":
 
2292
        if bytes is None:
 
2293
            tree_file = tree.get_file(file_id)
 
2294
            try:
 
2295
                bytes = tree_file.readlines()
 
2296
            finally:
 
2297
                tree_file.close()
 
2298
        tt.create_file(bytes, trans_id)
 
2299
    elif kind == "symlink":
 
2300
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
 
2301
    else:
 
2302
        raise AssertionError('Unknown kind %r' % kind)
 
2303
 
 
2304
 
2045
2305
def create_entry_executability(tt, entry, trans_id):
2046
2306
    """Set the executability of a trans_id according to an inventory entry"""
2047
2307
    if entry.kind == "file":
2098
2358
    tt = TreeTransform(working_tree, pb)
2099
2359
    try:
2100
2360
        pp = ProgressPhase("Revert phase", 3, pb)
2101
 
        pp.next_phase()
2102
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2103
 
        try:
2104
 
            merge_modified = _alter_files(working_tree, target_tree, tt,
2105
 
                                          child_pb, filenames, backups)
2106
 
        finally:
2107
 
            child_pb.finished()
2108
 
        pp.next_phase()
2109
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2110
 
        try:
2111
 
            raw_conflicts = resolve_conflicts(tt, child_pb,
2112
 
                lambda t, c: conflict_pass(t, c, target_tree))
2113
 
        finally:
2114
 
            child_pb.finished()
2115
 
        conflicts = cook_conflicts(raw_conflicts, tt)
 
2361
        conflicts, merge_modified = _prepare_revert_transform(
 
2362
            working_tree, target_tree, tt, filenames, backups, pp)
2116
2363
        if change_reporter:
2117
2364
            change_reporter = delta._ChangeReporter(
2118
2365
                unversioned_filter=working_tree.is_ignored)
2129
2376
    return conflicts
2130
2377
 
2131
2378
 
 
2379
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
 
2380
                              backups, pp, basis_tree=None,
 
2381
                              merge_modified=None):
 
2382
    pp.next_phase()
 
2383
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2384
    try:
 
2385
        if merge_modified is None:
 
2386
            merge_modified = working_tree.merge_modified()
 
2387
        merge_modified = _alter_files(working_tree, target_tree, tt,
 
2388
                                      child_pb, filenames, backups,
 
2389
                                      merge_modified, basis_tree)
 
2390
    finally:
 
2391
        child_pb.finished()
 
2392
    pp.next_phase()
 
2393
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2394
    try:
 
2395
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2396
            lambda t, c: conflict_pass(t, c, target_tree))
 
2397
    finally:
 
2398
        child_pb.finished()
 
2399
    conflicts = cook_conflicts(raw_conflicts, tt)
 
2400
    return conflicts, merge_modified
 
2401
 
 
2402
 
2132
2403
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2133
 
                 backups):
2134
 
    merge_modified = working_tree.merge_modified()
 
2404
                 backups, merge_modified, basis_tree=None):
 
2405
    if basis_tree is not None:
 
2406
        basis_tree.lock_read()
2135
2407
    change_list = target_tree.iter_changes(working_tree,
2136
2408
        specific_files=specific_files, pb=pb)
2137
 
    if target_tree.inventory.root is None:
 
2409
    if target_tree.get_root_id() is None:
2138
2410
        skip_root = True
2139
2411
    else:
2140
2412
        skip_root = False
2141
 
    basis_tree = None
2142
2413
    try:
2143
2414
        deferred_files = []
2144
2415
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2180
2451
                        # contents
2181
2452
                        mode_id = trans_id
2182
2453
                        trans_id = new_trans_id
2183
 
                if kind[1] == 'directory':
 
2454
                if kind[1] in ('directory', 'tree-reference'):
2184
2455
                    tt.create_directory(trans_id)
 
2456
                    if kind[1] == 'tree-reference':
 
2457
                        revision = target_tree.get_reference_revision(file_id,
 
2458
                                                                      path[1])
 
2459
                        tt.set_tree_reference(revision, trans_id)
2185
2460
                elif kind[1] == 'symlink':
2186
2461
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2187
2462
                                      trans_id)
2207
2482
                tt.version_file(file_id, trans_id)
2208
2483
            if versioned == (True, False):
2209
2484
                tt.unversion_file(trans_id)
2210
 
            if (name[1] is not None and 
 
2485
            if (name[1] is not None and
2211
2486
                (name[0] != name[1] or parent[0] != parent[1])):
2212
 
                tt.adjust_path(
2213
 
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
2487
                if name[1] == '' and parent[1] is None:
 
2488
                    parent_trans = ROOT_PARENT
 
2489
                else:
 
2490
                    parent_trans = tt.trans_id_file_id(parent[1])
 
2491
                tt.adjust_path(name[1], parent_trans, trans_id)
2214
2492
            if executable[0] != executable[1] and kind[1] == "file":
2215
2493
                tt.set_executability(executable[1], trans_id)
2216
2494
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2324
2602
            if parent_file_id is not None:
2325
2603
                tt.unversion_file(parent_id)
2326
2604
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
 
2605
        elif c_type == 'versioning no contents':
 
2606
            tt.cancel_versioning(conflict[1])
2327
2607
    return new_conflicts
2328
2608
 
2329
2609