~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-08 00:21:20 UTC
  • mfrom: (3453.2.11 fast-checkout2)
  • Revision ID: pqm@pqm.ubuntu.com-20080608002120-r3kcq0kxq24lhnak
Improve build_tree performance (igc, abentley)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import os
18
18
import errno
19
 
from stat import S_ISREG, S_IEXEC
 
19
from stat import S_ISREG
 
20
import tempfile
20
21
 
21
22
from bzrlib.lazy_import import lazy_import
22
23
lazy_import(globals(), """
23
24
from bzrlib import (
24
 
    annotate,
25
25
    bzrdir,
26
26
    delta,
27
27
    errors,
28
28
    inventory,
29
 
    osutils,
30
29
    revision as _mod_revision,
31
30
    )
32
31
""")
35
34
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
36
35
                           UnableCreateSymlink)
37
36
from bzrlib.inventory import InventoryEntry
38
 
from bzrlib.osutils import (
39
 
    delete_any,
40
 
    file_kind,
41
 
    has_symlinks,
42
 
    lexists,
43
 
    pathjoin,
44
 
    sha_file,
45
 
    splitpath,
46
 
    supports_executable,
47
 
)
 
37
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
 
38
                            delete_any, has_symlinks)
48
39
from bzrlib.progress import DummyProgress, ProgressPhase
49
40
from bzrlib.symbol_versioning import (
50
41
        deprecated_function,
51
 
        deprecated_in,
52
42
        )
53
43
from bzrlib.trace import mutter, warning
54
44
from bzrlib import tree
130
120
        # Cache of relpath results, to speed up canonical_path
131
121
        self._relpaths = {}
132
122
        # The trans_id that will be used as the tree root
133
 
        root_id = tree.get_root_id()
134
 
        if root_id is not None:
135
 
            self._new_root = self.trans_id_tree_file_id(root_id)
136
 
        else:
137
 
            self._new_root = None
 
123
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
138
124
        # Indictor of whether the transform has been applied
139
125
        self._done = False
140
126
        # A progress bar
201
187
        previous_name = self._new_name.get(trans_id)
202
188
        self._new_name[trans_id] = name
203
189
        self._new_parent[trans_id] = parent
204
 
        if parent == ROOT_PARENT:
205
 
            if self._new_root is not None:
206
 
                raise ValueError("Cannot have multiple roots.")
207
 
            self._new_root = trans_id
208
190
        if (trans_id in self._limbo_files and
209
191
            trans_id not in self._needs_rename):
210
192
            self._rename_in_limbo([trans_id])
267
249
        This reflects only files that already exist, not ones that will be
268
250
        added by transactions.
269
251
        """
270
 
        if inventory_id is None:
271
 
            raise ValueError('None is not a valid file id')
272
252
        path = self._tree.id2path(inventory_id)
273
253
        return self.trans_id_tree_path(path)
274
254
 
278
258
        a transaction has been unversioned, it is deliberately still returned.
279
259
        (this will likely lead to an unversioned parent conflict.)
280
260
        """
281
 
        if file_id is None:
282
 
            raise ValueError('None is not a valid file id')
283
261
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
284
262
            return self._r_new_id[file_id]
 
263
        elif file_id in self._tree.inventory:
 
264
            return self.trans_id_tree_file_id(file_id)
 
265
        elif file_id in self._non_present_ids:
 
266
            return self._non_present_ids[file_id]
285
267
        else:
286
 
            try:
287
 
                self._tree.iter_entries_by_dir([file_id]).next()
288
 
            except StopIteration:
289
 
                if file_id in self._non_present_ids:
290
 
                    return self._non_present_ids[file_id]
291
 
                else:
292
 
                    trans_id = self._assign_id()
293
 
                    self._non_present_ids[file_id] = trans_id
294
 
                    return trans_id
295
 
            else:
296
 
                return self.trans_id_tree_file_id(file_id)
 
268
            trans_id = self._assign_id()
 
269
            self._non_present_ids[file_id] = trans_id
 
270
            return trans_id
297
271
 
298
272
    def canonical_path(self, path):
299
273
        """Get the canonical tree-relative path"""
372
346
        try:
373
347
            mode = os.stat(self._tree.abspath(old_path)).st_mode
374
348
        except OSError, e:
375
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
376
 
                # Either old_path doesn't exist, or the parent of the
377
 
                # target is not a directory (but will be one eventually)
378
 
                # Either way, we know it doesn't exist *right now*
379
 
                # See also bug #248448
 
349
            if e.errno == errno.ENOENT:
380
350
                return
381
351
            else:
382
352
                raise
488
458
        """
489
459
        new_ids = set()
490
460
        if filesystem_only:
491
 
            stale_ids = self._needs_rename.difference(self._new_name)
492
 
            stale_ids.difference_update(self._new_parent)
493
 
            stale_ids.difference_update(self._new_contents)
494
 
            stale_ids.difference_update(self._new_id)
495
 
            needs_rename = self._needs_rename.difference(stale_ids)
496
 
            id_sets = (needs_rename, self._new_executability)
 
461
            id_sets = (self._needs_rename, self._new_executability)
497
462
        else:
498
463
            id_sets = (self._new_name, self._new_parent, self._new_contents,
499
464
                       self._new_id, self._new_executability)
501
466
            new_ids.update(id_set)
502
467
        return sorted(FinalPaths(self).get_paths(new_ids))
503
468
 
504
 
    def _inventory_altered(self):
505
 
        """Get the trans_ids and paths of files needing new inv entries."""
506
 
        new_ids = set()
507
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
508
 
                       self._new_executability]:
509
 
            new_ids.update(id_set)
510
 
        changed_kind = set(self._removed_contents)
511
 
        changed_kind.intersection_update(self._new_contents)
512
 
        changed_kind.difference_update(new_ids)
513
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
514
 
                        self.final_kind(t))
515
 
        new_ids.update(changed_kind)
516
 
        return sorted(FinalPaths(self).get_paths(new_ids))
517
 
 
518
469
    def tree_kind(self, trans_id):
519
470
        """Determine the file kind in the working tree.
520
471
 
555
506
        # the file is old; the old id is still valid
556
507
        if self._new_root == trans_id:
557
508
            return self._tree.get_root_id()
558
 
        return self._tree.path2id(path)
 
509
        return self._tree.inventory.path2id(path)
559
510
 
560
511
    def final_file_id(self, trans_id):
561
512
        """Determine the file id after any changes are applied, or None.
673
624
        try:
674
625
            children = os.listdir(self._tree.abspath(path))
675
626
        except OSError, e:
676
 
            if not (osutils._is_error_enotdir(e)
677
 
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
627
            if e.errno not in (errno.ENOENT, errno.ESRCH, errno.ENOTDIR):
678
628
                raise
679
629
            return
680
 
 
 
630
            
681
631
        for child in children:
682
632
            childpath = joinpath(path, child)
683
633
            if self._tree.is_control_filename(childpath):
908
858
        self._limbo_files[trans_id] = limbo_name
909
859
        return limbo_name
910
860
 
911
 
    def _set_executability(self, path, trans_id):
 
861
    def _set_executability(self, path, entry, trans_id):
912
862
        """Set the executability of versioned files """
 
863
        new_executability = self._new_executability[trans_id]
 
864
        if entry is not None:
 
865
            entry.executable = new_executability
913
866
        if supports_executable():
914
 
            new_executability = self._new_executability[trans_id]
915
867
            abspath = self._tree.abspath(path)
916
868
            current_mode = os.stat(abspath).st_mode
917
869
            if new_executability:
1012
964
        from_path = self._tree_id_paths.get(from_trans_id)
1013
965
        if from_versioned:
1014
966
            # get data from working tree if versioned
1015
 
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
967
            from_entry = self._tree.inventory[file_id]
1016
968
            from_name = from_entry.name
1017
969
            from_parent = from_entry.parent_id
1018
970
        else:
1239
1191
            conflicts = self.find_conflicts()
1240
1192
            if len(conflicts) != 0:
1241
1193
                raise MalformedTransform(conflicts=conflicts)
 
1194
        if precomputed_delta is None:
 
1195
            new_inventory_delta = []
 
1196
            inventory_delta = new_inventory_delta
 
1197
        else:
 
1198
            new_inventory_delta = None
 
1199
            inventory_delta = precomputed_delta
1242
1200
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1243
1201
        try:
1244
 
            if precomputed_delta is None:
1245
 
                child_pb.update('Apply phase', 0, 2)
1246
 
                inventory_delta = self._generate_inventory_delta()
1247
 
                offset = 1
1248
 
            else:
1249
 
                inventory_delta = precomputed_delta
1250
 
                offset = 0
1251
1202
            if _mover is None:
1252
1203
                mover = _FileMover()
1253
1204
            else:
1254
1205
                mover = _mover
1255
1206
            try:
1256
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1257
 
                self._apply_removals(mover)
1258
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1259
 
                modified_paths = self._apply_insertions(mover)
 
1207
                child_pb.update('Apply phase', 0, 2)
 
1208
                self._apply_removals(new_inventory_delta, mover)
 
1209
                child_pb.update('Apply phase', 1, 2)
 
1210
                modified_paths = self._apply_insertions(new_inventory_delta,
 
1211
                                                        mover)
1260
1212
            except:
1261
1213
                mover.rollback()
1262
1214
                raise
1269
1221
        self.finalize()
1270
1222
        return _TransformResults(modified_paths, self.rename_count)
1271
1223
 
1272
 
    def _generate_inventory_delta(self):
1273
 
        """Generate an inventory delta for the current transform."""
1274
 
        inventory_delta = []
1275
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1276
 
        new_paths = self._inventory_altered()
1277
 
        total_entries = len(new_paths) + len(self._removed_id)
1278
 
        try:
1279
 
            for num, trans_id in enumerate(self._removed_id):
1280
 
                if (num % 10) == 0:
1281
 
                    child_pb.update('removing file', num, total_entries)
1282
 
                if trans_id == self._new_root:
1283
 
                    file_id = self._tree.get_root_id()
1284
 
                else:
1285
 
                    file_id = self.tree_file_id(trans_id)
1286
 
                # File-id isn't really being deleted, just moved
1287
 
                if file_id in self._r_new_id:
1288
 
                    continue
1289
 
                path = self._tree_id_paths[trans_id]
1290
 
                inventory_delta.append((path, None, file_id, None))
1291
 
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1292
 
                                     new_paths)
1293
 
            entries = self._tree.iter_entries_by_dir(
1294
 
                new_path_file_ids.values())
1295
 
            old_paths = dict((e.file_id, p) for p, e in entries)
1296
 
            final_kinds = {}
1297
 
            for num, (path, trans_id) in enumerate(new_paths):
1298
 
                if (num % 10) == 0:
1299
 
                    child_pb.update('adding file',
1300
 
                                    num + len(self._removed_id), total_entries)
1301
 
                file_id = new_path_file_ids[trans_id]
1302
 
                if file_id is None:
1303
 
                    continue
1304
 
                needs_entry = False
1305
 
                try:
1306
 
                    kind = self.final_kind(trans_id)
1307
 
                except NoSuchFile:
1308
 
                    kind = self._tree.stored_kind(file_id)
1309
 
                parent_trans_id = self.final_parent(trans_id)
1310
 
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1311
 
                if parent_file_id is None:
1312
 
                    parent_file_id = self.final_file_id(parent_trans_id)
1313
 
                if trans_id in self._new_reference_revision:
1314
 
                    new_entry = inventory.TreeReference(
1315
 
                        file_id,
1316
 
                        self._new_name[trans_id],
1317
 
                        self.final_file_id(self._new_parent[trans_id]),
1318
 
                        None, self._new_reference_revision[trans_id])
1319
 
                else:
1320
 
                    new_entry = inventory.make_entry(kind,
1321
 
                        self.final_name(trans_id),
1322
 
                        parent_file_id, file_id)
1323
 
                old_path = old_paths.get(new_entry.file_id)
1324
 
                new_executability = self._new_executability.get(trans_id)
1325
 
                if new_executability is not None:
1326
 
                    new_entry.executable = new_executability
1327
 
                inventory_delta.append(
1328
 
                    (old_path, path, new_entry.file_id, new_entry))
1329
 
        finally:
1330
 
            child_pb.finished()
1331
 
        return inventory_delta
1332
 
 
1333
 
    def _apply_removals(self, mover):
 
1224
    def _apply_removals(self, inventory_delta, mover):
1334
1225
        """Perform tree operations that remove directory/inventory names.
1335
1226
 
1336
1227
        That is, delete files that are to be deleted, and put any files that
1359
1250
                            raise
1360
1251
                    else:
1361
1252
                        self.rename_count += 1
 
1253
                if (trans_id in self._removed_id
 
1254
                    and inventory_delta is not None):
 
1255
                    if trans_id == self._new_root:
 
1256
                        file_id = self._tree.get_root_id()
 
1257
                    else:
 
1258
                        file_id = self.tree_file_id(trans_id)
 
1259
                    # File-id isn't really being deleted, just moved
 
1260
                    if file_id in self._r_new_id:
 
1261
                        continue
 
1262
                    inventory_delta.append((path, None, file_id, None))
1362
1263
        finally:
1363
1264
            child_pb.finished()
1364
1265
 
1365
 
    def _apply_insertions(self, mover):
 
1266
    def _apply_insertions(self, inventory_delta, mover):
1366
1267
        """Perform tree operations that insert directory/inventory names.
1367
1268
 
1368
1269
        That is, create any files that need to be created, and restore from
1372
1273
        If inventory_delta is None, no inventory delta is calculated, and
1373
1274
        no list of modified paths is returned.
1374
1275
        """
1375
 
        new_paths = self.new_paths(filesystem_only=True)
 
1276
        new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
1376
1277
        modified_paths = []
 
1278
        completed_new = []
1377
1279
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1378
1280
                                 new_paths)
 
1281
        if inventory_delta is not None:
 
1282
            entries = self._tree.iter_entries_by_dir(
 
1283
                new_path_file_ids.values())
 
1284
            old_paths = dict((e.file_id, p) for p, e in entries)
1379
1285
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1380
1286
        try:
1381
1287
            for num, (path, trans_id) in enumerate(new_paths):
 
1288
                new_entry = None
1382
1289
                if (num % 10) == 0:
1383
1290
                    child_pb.update('adding file', num, len(new_paths))
1384
1291
                full_path = self._tree.abspath(path)
1391
1298
                            raise
1392
1299
                    else:
1393
1300
                        self.rename_count += 1
1394
 
                if (trans_id in self._new_contents or
1395
 
                    self.path_changed(trans_id)):
1396
 
                    if trans_id in self._new_contents:
1397
 
                        modified_paths.append(full_path)
 
1301
                if inventory_delta is not None:
 
1302
                    if (trans_id in self._new_contents or
 
1303
                        self.path_changed(trans_id)):
 
1304
                        if trans_id in self._new_contents:
 
1305
                            modified_paths.append(full_path)
 
1306
                            completed_new.append(trans_id)
 
1307
                    file_id = new_path_file_ids[trans_id]
 
1308
                    if file_id is not None and (trans_id in self._new_id or
 
1309
                        trans_id in self._new_name or
 
1310
                        trans_id in self._new_parent
 
1311
                        or trans_id in self._new_executability):
 
1312
                        try:
 
1313
                            kind = self.final_kind(trans_id)
 
1314
                        except NoSuchFile:
 
1315
                            kind = self._tree.stored_kind(file_id)
 
1316
                        parent_trans_id = self.final_parent(trans_id)
 
1317
                        parent_file_id = new_path_file_ids.get(parent_trans_id)
 
1318
                        if parent_file_id is None:
 
1319
                            parent_file_id = self.final_file_id(
 
1320
                                parent_trans_id)
 
1321
                        if trans_id in self._new_reference_revision:
 
1322
                            new_entry = inventory.TreeReference(
 
1323
                                file_id,
 
1324
                                self._new_name[trans_id],
 
1325
                                self.final_file_id(self._new_parent[trans_id]),
 
1326
                                None, self._new_reference_revision[trans_id])
 
1327
                        else:
 
1328
                            new_entry = inventory.make_entry(kind,
 
1329
                                self.final_name(trans_id),
 
1330
                                parent_file_id, file_id)
 
1331
                        old_path = old_paths.get(new_entry.file_id)
 
1332
                        inventory_delta.append(
 
1333
                            (old_path, path, new_entry.file_id, new_entry))
 
1334
 
1398
1335
                if trans_id in self._new_executability:
1399
 
                    self._set_executability(path, trans_id)
 
1336
                    self._set_executability(path, new_entry, trans_id)
1400
1337
        finally:
1401
1338
            child_pb.finished()
1402
 
        self._new_contents.clear()
 
1339
        if inventory_delta is None:
 
1340
            self._new_contents.clear()
 
1341
        else:
 
1342
            for trans_id in completed_new:
 
1343
                del self._new_contents[trans_id]
1403
1344
        return modified_paths
1404
1345
 
1405
1346
 
1413
1354
 
1414
1355
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1415
1356
        tree.lock_read()
1416
 
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
 
1357
        limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1417
1358
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1418
1359
 
1419
1360
    def canonical_path(self, path):
1442
1383
        except KeyError:
1443
1384
            return
1444
1385
        file_id = self.tree_file_id(parent_id)
1445
 
        if file_id is None:
1446
 
            return
1447
 
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1448
 
        children = getattr(entry, 'children', {})
1449
 
        for child in children:
 
1386
        for child in self._tree.inventory[file_id].children.iterkeys():
1450
1387
            childpath = joinpath(path, child)
1451
1388
            yield self.trans_id_tree_path(childpath)
1452
1389
 
1457
1394
    def __init__(self, transform):
1458
1395
        self._transform = transform
1459
1396
        self._final_paths = FinalPaths(transform)
1460
 
        self.__by_parent = None
1461
 
        self._parent_ids = []
1462
 
        self._all_children_cache = {}
1463
 
        self._path2trans_id_cache = {}
1464
 
        self._final_name_cache = {}
1465
1397
 
1466
1398
    def _changes(self, file_id):
1467
1399
        for changes in self._transform.iter_changes():
1475
1407
        # InterTree.iter_changes.
1476
1408
        return (changes is not None and changes[2])
1477
1409
 
1478
 
    def _get_repository(self):
1479
 
        repo = getattr(self._transform._tree, '_repository', None)
1480
 
        if repo is None:
1481
 
            repo = self._transform._tree.branch.repository
1482
 
        return repo
1483
 
 
1484
 
    def _iter_parent_trees(self):
1485
 
        for revision_id in self.get_parent_ids():
1486
 
            try:
1487
 
                yield self.revision_tree(revision_id)
1488
 
            except errors.NoSuchRevisionInTree:
1489
 
                yield self._get_repository().revision_tree(revision_id)
1490
 
 
1491
1410
    def _get_file_revision(self, file_id, vf, tree_revision):
1492
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1493
 
                       self._iter_parent_trees()]
1494
 
        vf.add_lines((file_id, tree_revision), parent_keys,
1495
 
                     self.get_file(file_id).readlines())
1496
 
        repo = self._get_repository()
1497
 
        base_vf = repo.texts
1498
 
        if base_vf not in vf.fallback_versionedfiles:
1499
 
            vf.fallback_versionedfiles.append(base_vf)
1500
 
        return tree_revision
 
1411
        return self._transform._tree._get_file_revision(file_id, vf,
 
1412
                                                        tree_revision)
1501
1413
 
1502
1414
    def _stat_limbo_file(self, file_id):
1503
1415
        trans_id = self._transform.trans_id_file_id(file_id)
1504
1416
        name = self._transform._limbo_name(trans_id)
1505
1417
        return os.lstat(name)
1506
1418
 
1507
 
    @property
1508
 
    def _by_parent(self):
1509
 
        if self.__by_parent is None:
1510
 
            self.__by_parent = self._transform.by_parent()
1511
 
        return self.__by_parent
1512
 
 
1513
 
    def _comparison_data(self, entry, path):
1514
 
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1515
 
        if kind == 'missing':
1516
 
            kind = None
1517
 
            executable = False
1518
 
        else:
1519
 
            file_id = self._transform.final_file_id(self._path2trans_id(path))
1520
 
            executable = self.is_executable(file_id, path)
1521
 
        return kind, executable, None
1522
 
 
1523
1419
    def lock_read(self):
1524
1420
        # Perhaps in theory, this should lock the TreeTransform?
1525
1421
        pass
1536
1432
        return self._transform.final_file_id(self._transform.root)
1537
1433
 
1538
1434
    def all_file_ids(self):
1539
 
        tree_ids = set(self._transform._tree.all_file_ids())
1540
 
        tree_ids.difference_update(self._transform.tree_file_id(t)
1541
 
                                   for t in self._transform._removed_id)
1542
 
        tree_ids.update(self._transform._new_id.values())
1543
 
        return tree_ids
 
1435
        return self._transform._tree.all_file_ids()
1544
1436
 
1545
1437
    def __iter__(self):
1546
1438
        return iter(self.all_file_ids())
1547
1439
 
1548
 
    def has_id(self, file_id):
1549
 
        if file_id in self._transform._r_new_id:
1550
 
            return True
1551
 
        elif file_id in self._transform._removed_id:
1552
 
            return False
1553
 
        else:
1554
 
            return self._transform._tree.has_id(file_id)
1555
 
 
1556
 
    def _path2trans_id(self, path):
1557
 
        # We must not use None here, because that is a valid value to store.
1558
 
        trans_id = self._path2trans_id_cache.get(path, object)
1559
 
        if trans_id is not object:
1560
 
            return trans_id
1561
 
        segments = splitpath(path)
1562
 
        cur_parent = self._transform.root
1563
 
        for cur_segment in segments:
1564
 
            for child in self._all_children(cur_parent):
1565
 
                final_name = self._final_name_cache.get(child)
1566
 
                if final_name is None:
1567
 
                    final_name = self._transform.final_name(child)
1568
 
                    self._final_name_cache[child] = final_name
1569
 
                if final_name == cur_segment:
1570
 
                    cur_parent = child
1571
 
                    break
1572
 
            else:
1573
 
                self._path2trans_id_cache[path] = None
1574
 
                return None
1575
 
        self._path2trans_id_cache[path] = cur_parent
1576
 
        return cur_parent
 
1440
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
 
1441
        """See Tree.paths2ids"""
 
1442
        to_find = set(specific_files)
 
1443
        result = set()
 
1444
        for (file_id, paths, changed, versioned, parent, name, kind,
 
1445
             executable) in self._transform.iter_changes():
 
1446
            if paths[1] in to_find:
 
1447
                result.append(file_id)
 
1448
                to_find.remove(paths[1])
 
1449
        result.update(self._transform._tree.paths2ids(to_find,
 
1450
                      trees=[], require_versioned=require_versioned))
 
1451
        return result
1577
1452
 
1578
1453
    def path2id(self, path):
1579
 
        return self._transform.final_file_id(self._path2trans_id(path))
 
1454
        return self._transform._tree.path2id(path)
1580
1455
 
1581
1456
    def id2path(self, file_id):
1582
1457
        trans_id = self._transform.trans_id_file_id(file_id)
1585
1460
        except NoFinalPath:
1586
1461
            raise errors.NoSuchId(self, file_id)
1587
1462
 
1588
 
    def _all_children(self, trans_id):
1589
 
        children = self._all_children_cache.get(trans_id)
1590
 
        if children is not None:
1591
 
            return children
1592
 
        children = set(self._transform.iter_tree_children(trans_id))
1593
 
        # children in the _new_parent set are provided by _by_parent.
1594
 
        children.difference_update(self._transform._new_parent.keys())
1595
 
        children.update(self._by_parent.get(trans_id, []))
1596
 
        self._all_children_cache[trans_id] = children
1597
 
        return children
1598
 
 
1599
 
    def iter_children(self, file_id):
1600
 
        trans_id = self._transform.trans_id_file_id(file_id)
1601
 
        for child_trans_id in self._all_children(trans_id):
1602
 
            yield self._transform.final_file_id(child_trans_id)
1603
 
 
1604
 
    def extras(self):
1605
 
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
1606
 
                              in self._transform._tree.extras())
1607
 
        possible_extras.update(self._transform._new_contents)
1608
 
        possible_extras.update(self._transform._removed_id)
1609
 
        for trans_id in possible_extras:
1610
 
            if self._transform.final_file_id(trans_id) is None:
1611
 
                yield self._final_paths._determine_path(trans_id)
1612
 
 
1613
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
1614
 
        for trans_id, parent_file_id in ordered_entries:
1615
 
            file_id = self._transform.final_file_id(trans_id)
1616
 
            if file_id is None:
1617
 
                continue
1618
 
            if (specific_file_ids is not None
1619
 
                and file_id not in specific_file_ids):
1620
 
                continue
1621
 
            try:
1622
 
                kind = self._transform.final_kind(trans_id)
1623
 
            except NoSuchFile:
1624
 
                kind = self._transform._tree.stored_kind(file_id)
1625
 
            new_entry = inventory.make_entry(
1626
 
                kind,
1627
 
                self._transform.final_name(trans_id),
1628
 
                parent_file_id, file_id)
1629
 
            yield new_entry, trans_id
1630
 
 
1631
 
    def _list_files_by_dir(self):
1632
 
        todo = [ROOT_PARENT]
1633
 
        ordered_ids = []
1634
 
        while len(todo) > 0:
1635
 
            parent = todo.pop()
1636
 
            parent_file_id = self._transform.final_file_id(parent)
1637
 
            children = list(self._all_children(parent))
1638
 
            paths = dict(zip(children, self._final_paths.get_paths(children)))
1639
 
            children.sort(key=paths.get)
1640
 
            todo.extend(reversed(children))
1641
 
            for trans_id in children:
1642
 
                ordered_ids.append((trans_id, parent_file_id))
1643
 
        return ordered_ids
1644
 
 
1645
1463
    def iter_entries_by_dir(self, specific_file_ids=None):
1646
 
        # This may not be a maximally efficient implementation, but it is
1647
 
        # reasonably straightforward.  An implementation that grafts the
1648
 
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1649
 
        # might be more efficient, but requires tricky inferences about stack
1650
 
        # position.
1651
 
        ordered_ids = self._list_files_by_dir()
1652
 
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1653
 
                                                      specific_file_ids):
1654
 
            yield unicode(self._final_paths.get_path(trans_id)), entry
1655
 
 
1656
 
    def list_files(self, include_root=False):
1657
 
        """See Tree.list_files."""
1658
 
        # XXX This should behave like WorkingTree.list_files, but is really
1659
 
        # more like RevisionTree.list_files.
1660
 
        for path, entry in self.iter_entries_by_dir():
1661
 
            if entry.name == '' and not include_root:
1662
 
                continue
1663
 
            yield path, 'V', entry.kind, entry.file_id, entry
 
1464
        return self._transform._tree.iter_entries_by_dir(specific_file_ids)
1664
1465
 
1665
1466
    def kind(self, file_id):
1666
1467
        trans_id = self._transform.trans_id_file_id(file_id)
1667
1468
        return self._transform.final_kind(trans_id)
1668
1469
 
1669
1470
    def stored_kind(self, file_id):
1670
 
        trans_id = self._transform.trans_id_file_id(file_id)
1671
 
        try:
1672
 
            return self._transform._new_contents[trans_id]
1673
 
        except KeyError:
1674
 
            return self._transform._tree.stored_kind(file_id)
 
1471
        return self._transform._tree.stored_kind(file_id)
1675
1472
 
1676
1473
    def get_file_mtime(self, file_id, path=None):
1677
1474
        """See Tree.get_file_mtime"""
1679
1476
            return self._transform._tree.get_file_mtime(file_id, path)
1680
1477
        return self._stat_limbo_file(file_id).st_mtime
1681
1478
 
1682
 
    def _file_size(self, entry, stat_value):
1683
 
        return self.get_file_size(entry.file_id)
1684
 
 
1685
1479
    def get_file_size(self, file_id):
1686
1480
        """See Tree.get_file_size"""
1687
1481
        if self.kind(file_id) == 'file':
1690
1484
            return None
1691
1485
 
1692
1486
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1693
 
        trans_id = self._transform.trans_id_file_id(file_id)
1694
 
        kind = self._transform._new_contents.get(trans_id)
1695
 
        if kind is None:
1696
 
            return self._transform._tree.get_file_sha1(file_id)
1697
 
        if kind == 'file':
1698
 
            fileobj = self.get_file(file_id)
1699
 
            try:
1700
 
                return sha_file(fileobj)
1701
 
            finally:
1702
 
                fileobj.close()
 
1487
        return self._transform._tree.get_file_sha1(file_id)
1703
1488
 
1704
1489
    def is_executable(self, file_id, path=None):
1705
 
        if file_id is None:
1706
 
            return False
1707
 
        trans_id = self._transform.trans_id_file_id(file_id)
1708
 
        try:
1709
 
            return self._transform._new_executability[trans_id]
1710
 
        except KeyError:
1711
 
            try:
1712
 
                return self._transform._tree.is_executable(file_id, path)
1713
 
            except OSError, e:
1714
 
                if e.errno == errno.ENOENT:
1715
 
                    return False
1716
 
                raise
1717
 
            except errors.NoSuchId:
1718
 
                return False
 
1490
        return self._transform._tree.is_executable(file_id, path)
1719
1491
 
1720
1492
    def path_content_summary(self, path):
1721
 
        trans_id = self._path2trans_id(path)
1722
 
        tt = self._transform
1723
 
        tree_path = tt._tree_id_paths.get(trans_id)
1724
 
        kind = tt._new_contents.get(trans_id)
1725
 
        if kind is None:
1726
 
            if tree_path is None or trans_id in tt._removed_contents:
1727
 
                return 'missing', None, None, None
1728
 
            summary = tt._tree.path_content_summary(tree_path)
1729
 
            kind, size, executable, link_or_sha1 = summary
1730
 
        else:
1731
 
            link_or_sha1 = None
1732
 
            limbo_name = tt._limbo_name(trans_id)
1733
 
            if trans_id in tt._new_reference_revision:
1734
 
                kind = 'tree-reference'
1735
 
            if kind == 'file':
1736
 
                statval = os.lstat(limbo_name)
1737
 
                size = statval.st_size
1738
 
                if not supports_executable():
1739
 
                    executable = None
1740
 
                else:
1741
 
                    executable = statval.st_mode & S_IEXEC
1742
 
            else:
1743
 
                size = None
1744
 
                executable = None
1745
 
            if kind == 'symlink':
1746
 
                link_or_sha1 = os.readlink(limbo_name)
1747
 
        if supports_executable():
1748
 
            executable = tt._new_executability.get(trans_id, executable)
1749
 
        return kind, size, executable, link_or_sha1
 
1493
        return self._transform._tree.path_content_summary(path)
1750
1494
 
1751
1495
    def iter_changes(self, from_tree, include_unchanged=False,
1752
1496
                      specific_files=None, pb=None, extra_trees=None,
1753
1497
                      require_versioned=True, want_unversioned=False):
1754
1498
        """See InterTree.iter_changes.
1755
1499
 
1756
 
        This has a fast path that is only used when the from_tree matches
1757
 
        the transform tree, and no fancy options are supplied.
 
1500
        This implementation does not support include_unchanged, specific_files,
 
1501
        or want_unversioned.  extra_trees, require_versioned, and pb are
 
1502
        ignored.
1758
1503
        """
1759
 
        if (from_tree is not self._transform._tree or include_unchanged or
1760
 
            specific_files or want_unversioned):
1761
 
            return tree.InterTree(from_tree, self).iter_changes(
1762
 
                include_unchanged=include_unchanged,
1763
 
                specific_files=specific_files,
1764
 
                pb=pb,
1765
 
                extra_trees=extra_trees,
1766
 
                require_versioned=require_versioned,
1767
 
                want_unversioned=want_unversioned)
 
1504
        if from_tree is not self._transform._tree:
 
1505
            raise ValueError('from_tree must be transform source tree.')
 
1506
        if include_unchanged:
 
1507
            raise ValueError('include_unchanged is not supported')
 
1508
        if specific_files is not None:
 
1509
            raise ValueError('specific_files is not supported')
1768
1510
        if want_unversioned:
1769
1511
            raise ValueError('want_unversioned is not supported')
1770
1512
        return self._transform.iter_changes()
1777
1519
        name = self._transform._limbo_name(trans_id)
1778
1520
        return open(name, 'rb')
1779
1521
 
 
1522
    def get_file_text(self, file_id):
 
1523
        text_file = self.get_file(file_id)
 
1524
        try:
 
1525
            return text_file.read()
 
1526
        finally:
 
1527
            text_file.close()
 
1528
 
1780
1529
    def annotate_iter(self, file_id,
1781
1530
                      default_revision=_mod_revision.CURRENT_REVISION):
1782
 
        changes = self._changes(file_id)
1783
 
        if changes is None:
1784
 
            get_old = True
1785
 
        else:
1786
 
            changed_content, versioned, kind = (changes[2], changes[3],
1787
 
                                                changes[6])
1788
 
            if kind[1] is None:
1789
 
                return None
1790
 
            get_old = (kind[0] == 'file' and versioned[0])
1791
 
        if get_old:
1792
 
            old_annotation = self._transform._tree.annotate_iter(file_id,
1793
 
                default_revision=default_revision)
1794
 
        else:
1795
 
            old_annotation = []
1796
 
        if changes is None:
1797
 
            return old_annotation
1798
 
        if not changed_content:
1799
 
            return old_annotation
1800
 
        return annotate.reannotate([old_annotation],
1801
 
                                   self.get_file(file_id).readlines(),
1802
 
                                   default_revision)
 
1531
        return self._transform._tree.annotate_iter(file_id,
 
1532
            default_revision=default_revision)
1803
1533
 
1804
1534
    def get_symlink_target(self, file_id):
1805
1535
        """See Tree.get_symlink_target"""
1809
1539
        name = self._transform._limbo_name(trans_id)
1810
1540
        return os.readlink(name)
1811
1541
 
1812
 
    def walkdirs(self, prefix=''):
1813
 
        pending = [self._transform.root]
1814
 
        while len(pending) > 0:
1815
 
            parent_id = pending.pop()
1816
 
            children = []
1817
 
            subdirs = []
1818
 
            prefix = prefix.rstrip('/')
1819
 
            parent_path = self._final_paths.get_path(parent_id)
1820
 
            parent_file_id = self._transform.final_file_id(parent_id)
1821
 
            for child_id in self._all_children(parent_id):
1822
 
                path_from_root = self._final_paths.get_path(child_id)
1823
 
                basename = self._transform.final_name(child_id)
1824
 
                file_id = self._transform.final_file_id(child_id)
1825
 
                try:
1826
 
                    kind = self._transform.final_kind(child_id)
1827
 
                    versioned_kind = kind
1828
 
                except NoSuchFile:
1829
 
                    kind = 'unknown'
1830
 
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1831
 
                if versioned_kind == 'directory':
1832
 
                    subdirs.append(child_id)
1833
 
                children.append((path_from_root, basename, kind, None,
1834
 
                                 file_id, versioned_kind))
1835
 
            children.sort()
1836
 
            if parent_path.startswith(prefix):
1837
 
                yield (parent_path, parent_file_id), children
1838
 
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1839
 
                                  reverse=True))
 
1542
    def list_files(self, include_root=False):
 
1543
        return self._transform._tree.list_files(include_root)
 
1544
 
 
1545
    def walkdirs(self, prefix=""):
 
1546
        return self._transform._tree.walkdirs(prefix)
1840
1547
 
1841
1548
    def get_parent_ids(self):
1842
 
        return self._parent_ids
1843
 
 
1844
 
    def set_parent_ids(self, parent_ids):
1845
 
        self._parent_ids = parent_ids
 
1549
        return self._transform._tree.get_parent_ids()
1846
1550
 
1847
1551
    def get_revision_tree(self, revision_id):
1848
1552
        return self._transform._tree.get_revision_tree(revision_id)
1971
1675
        try:
1972
1676
            deferred_contents = []
1973
1677
            num = 0
1974
 
            total = len(tree.inventory)
1975
1678
            if delta_from_tree:
1976
1679
                precomputed_delta = []
1977
1680
            else:
1978
1681
                precomputed_delta = None
1979
1682
            for num, (tree_path, entry) in \
1980
1683
                enumerate(tree.inventory.iter_entries_by_dir()):
1981
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
1684
                pb.update("Building tree", num - len(deferred_contents),
 
1685
                          len(tree.inventory))
1982
1686
                if entry.parent_id is None:
1983
1687
                    continue
1984
1688
                reparent = False
2001
1705
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2002
1706
                        if kind == 'directory':
2003
1707
                            reparent = True
 
1708
                if entry.parent_id not in file_trans_id:
 
1709
                    raise AssertionError(
 
1710
                        'entry %s parent id %r is not in file_trans_id %r'
 
1711
                        % (entry, entry.parent_id, file_trans_id))
2004
1712
                parent_id = file_trans_id[entry.parent_id]
2005
1713
                if entry.kind == 'file':
2006
1714
                    # We *almost* replicate new_by_entry, so that we can defer
2007
1715
                    # getting the file text, and get them all at once.
2008
1716
                    trans_id = tt.create_path(entry.name, parent_id)
2009
1717
                    file_trans_id[file_id] = trans_id
2010
 
                    tt.version_file(file_id, trans_id)
2011
 
                    executable = tree.is_executable(file_id, tree_path)
 
1718
                    tt.version_file(entry.file_id, trans_id)
 
1719
                    executable = tree.is_executable(entry.file_id, tree_path)
2012
1720
                    if executable:
2013
1721
                        tt.set_executability(executable, trans_id)
2014
 
                    deferred_contents.append((file_id, trans_id))
 
1722
                    deferred_contents.append((entry.file_id, trans_id))
2015
1723
                else:
2016
1724
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2017
1725
                                                          tree)
2154
1862
        raise errors.BadFileKindError(name, kind)
2155
1863
 
2156
1864
 
2157
 
@deprecated_function(deprecated_in((1, 9, 0)))
2158
1865
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2159
 
    """Create new file contents according to an inventory entry.
2160
 
 
2161
 
    DEPRECATED.  Use create_from_tree instead.
2162
 
    """
 
1866
    """Create new file contents according to an inventory entry."""
2163
1867
    if entry.kind == "file":
2164
1868
        if lines is None:
2165
1869
            lines = tree.get_file(entry.file_id).readlines()
2170
1874
        tt.create_directory(trans_id)
2171
1875
 
2172
1876
 
2173
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2174
 
    """Create new file contents according to tree contents."""
2175
 
    kind = tree.kind(file_id)
2176
 
    if kind == 'directory':
2177
 
        tt.create_directory(trans_id)
2178
 
    elif kind == "file":
2179
 
        if bytes is None:
2180
 
            tree_file = tree.get_file(file_id)
2181
 
            try:
2182
 
                bytes = tree_file.readlines()
2183
 
            finally:
2184
 
                tree_file.close()
2185
 
        tt.create_file(bytes, trans_id)
2186
 
    elif kind == "symlink":
2187
 
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2188
 
    else:
2189
 
        raise AssertionError('Unknown kind %r' % kind)
2190
 
 
2191
 
 
2192
1877
def create_entry_executability(tt, entry, trans_id):
2193
1878
    """Set the executability of a trans_id according to an inventory entry"""
2194
1879
    if entry.kind == "file":
2245
1930
    tt = TreeTransform(working_tree, pb)
2246
1931
    try:
2247
1932
        pp = ProgressPhase("Revert phase", 3, pb)
2248
 
        conflicts, merge_modified = _prepare_revert_transform(
2249
 
            working_tree, target_tree, tt, filenames, backups, pp)
 
1933
        pp.next_phase()
 
1934
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1935
        try:
 
1936
            merge_modified = _alter_files(working_tree, target_tree, tt,
 
1937
                                          child_pb, filenames, backups)
 
1938
        finally:
 
1939
            child_pb.finished()
 
1940
        pp.next_phase()
 
1941
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1942
        try:
 
1943
            raw_conflicts = resolve_conflicts(tt, child_pb,
 
1944
                lambda t, c: conflict_pass(t, c, target_tree))
 
1945
        finally:
 
1946
            child_pb.finished()
 
1947
        conflicts = cook_conflicts(raw_conflicts, tt)
2250
1948
        if change_reporter:
2251
1949
            change_reporter = delta._ChangeReporter(
2252
1950
                unversioned_filter=working_tree.is_ignored)
2263
1961
    return conflicts
2264
1962
 
2265
1963
 
2266
 
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2267
 
                              backups, pp, basis_tree=None,
2268
 
                              merge_modified=None):
2269
 
    pp.next_phase()
2270
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2271
 
    try:
2272
 
        if merge_modified is None:
2273
 
            merge_modified = working_tree.merge_modified()
2274
 
        merge_modified = _alter_files(working_tree, target_tree, tt,
2275
 
                                      child_pb, filenames, backups,
2276
 
                                      merge_modified, basis_tree)
2277
 
    finally:
2278
 
        child_pb.finished()
2279
 
    pp.next_phase()
2280
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2281
 
    try:
2282
 
        raw_conflicts = resolve_conflicts(tt, child_pb,
2283
 
            lambda t, c: conflict_pass(t, c, target_tree))
2284
 
    finally:
2285
 
        child_pb.finished()
2286
 
    conflicts = cook_conflicts(raw_conflicts, tt)
2287
 
    return conflicts, merge_modified
2288
 
 
2289
 
 
2290
1964
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
2291
 
                 backups, merge_modified, basis_tree=None):
2292
 
    if basis_tree is not None:
2293
 
        basis_tree.lock_read()
 
1965
                 backups):
 
1966
    merge_modified = working_tree.merge_modified()
2294
1967
    change_list = target_tree.iter_changes(working_tree,
2295
1968
        specific_files=specific_files, pb=pb)
2296
 
    if target_tree.get_root_id() is None:
 
1969
    if target_tree.inventory.root is None:
2297
1970
        skip_root = True
2298
1971
    else:
2299
1972
        skip_root = False
 
1973
    basis_tree = None
2300
1974
    try:
2301
1975
        deferred_files = []
2302
1976
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2338
2012
                        # contents
2339
2013
                        mode_id = trans_id
2340
2014
                        trans_id = new_trans_id
2341
 
                if kind[1] in ('directory', 'tree-reference'):
 
2015
                if kind[1] == 'directory':
2342
2016
                    tt.create_directory(trans_id)
2343
 
                    if kind[1] == 'tree-reference':
2344
 
                        revision = target_tree.get_reference_revision(file_id,
2345
 
                                                                      path[1])
2346
 
                        tt.set_tree_reference(revision, trans_id)
2347
2017
                elif kind[1] == 'symlink':
2348
2018
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2349
2019
                                      trans_id)
2369
2039
                tt.version_file(file_id, trans_id)
2370
2040
            if versioned == (True, False):
2371
2041
                tt.unversion_file(trans_id)
2372
 
            if (name[1] is not None and
 
2042
            if (name[1] is not None and 
2373
2043
                (name[0] != name[1] or parent[0] != parent[1])):
2374
 
                if name[1] == '' and parent[1] is None:
2375
 
                    parent_trans = ROOT_PARENT
2376
 
                else:
2377
 
                    parent_trans = tt.trans_id_file_id(parent[1])
2378
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2044
                tt.adjust_path(
 
2045
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
2379
2046
            if executable[0] != executable[1] and kind[1] == "file":
2380
2047
                tt.set_executability(executable[1], trans_id)
2381
2048
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2489
2156
            if parent_file_id is not None:
2490
2157
                tt.unversion_file(parent_id)
2491
2158
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
2492
 
        elif c_type == 'versioning no contents':
2493
 
            tt.cancel_versioning(conflict[1])
2494
2159
    return new_conflicts
2495
2160
 
2496
2161