/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Ian Clatworthy
  • Date: 2008-12-15 06:18:29 UTC
  • mfrom: (3905 +trunk)
  • mto: (3586.1.23 views-ui)
  • mto: This revision was merged to the branch mainline in revision 4030.
  • Revision ID: ian.clatworthy@canonical.com-20081215061829-c8qwa93g71u9fsh5
merge bzr.dev 3905

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