~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-08 14:55:19 UTC
  • mfrom: (3530 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3532.
  • Revision ID: john@arbash-meinel.com-20080708145519-paqg4kjwbpgs2xmq
Merge bzr.dev 3530

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
 
19
from stat import S_ISREG, S_IEXEC
20
20
import tempfile
21
21
 
22
22
from bzrlib.lazy_import import lazy_import
23
23
lazy_import(globals(), """
24
24
from bzrlib import (
 
25
    annotate,
25
26
    bzrdir,
26
27
    delta,
27
28
    errors,
28
 
    inventory
 
29
    inventory,
 
30
    revision as _mod_revision,
29
31
    )
30
32
""")
31
33
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
33
35
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
34
36
                           UnableCreateSymlink)
35
37
from bzrlib.inventory import InventoryEntry
36
 
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
37
 
                            delete_any, has_symlinks)
 
38
from bzrlib.osutils import (
 
39
    delete_any,
 
40
    file_kind,
 
41
    has_symlinks,
 
42
    lexists,
 
43
    pathjoin,
 
44
    splitpath,
 
45
    supports_executable,
 
46
)
38
47
from bzrlib.progress import DummyProgress, ProgressPhase
39
48
from bzrlib.symbol_versioning import (
40
49
        deprecated_function,
41
 
        zero_fifteen,
42
 
        zero_ninety,
43
50
        )
44
51
from bzrlib.trace import mutter, warning
45
52
from bzrlib import tree
440
447
 
441
448
    def version_file(self, file_id, trans_id):
442
449
        """Schedule a file to become versioned."""
443
 
        assert file_id is not None
 
450
        if file_id is None:
 
451
            raise ValueError()
444
452
        unique_add(self._new_id, trans_id, file_id)
445
453
        unique_add(self._r_new_id, file_id, trans_id)
446
454
 
450
458
        del self._new_id[trans_id]
451
459
        del self._r_new_id[file_id]
452
460
 
453
 
    def new_paths(self):
454
 
        """Determine the paths of all new and changed files"""
 
461
    def new_paths(self, filesystem_only=False):
 
462
        """Determine the paths of all new and changed files.
 
463
 
 
464
        :param filesystem_only: if True, only calculate values for files
 
465
            that require renames or execute bit changes.
 
466
        """
455
467
        new_ids = set()
456
 
        fp = FinalPaths(self)
457
 
        for id_set in (self._new_name, self._new_parent, self._new_contents,
458
 
                       self._new_id, self._new_executability):
 
468
        if filesystem_only:
 
469
            id_sets = (self._needs_rename, self._new_executability)
 
470
        else:
 
471
            id_sets = (self._new_name, self._new_parent, self._new_contents,
 
472
                       self._new_id, self._new_executability)
 
473
        for id_set in id_sets:
459
474
            new_ids.update(id_set)
460
 
        new_paths = [(fp.get_path(t), t) for t in new_ids]
461
 
        new_paths.sort()
462
 
        return new_paths
 
475
        return sorted(FinalPaths(self).get_paths(new_ids))
463
476
 
464
477
    def tree_kind(self, trans_id):
465
478
        """Determine the file kind in the working tree.
510
523
        applied.
511
524
        """
512
525
        try:
513
 
            # there is a new id for this file
514
 
            assert self._new_id[trans_id] is not None
515
526
            return self._new_id[trans_id]
516
527
        except KeyError:
517
528
            if trans_id in self._removed_id:
858
869
    def _set_executability(self, path, entry, trans_id):
859
870
        """Set the executability of versioned files """
860
871
        new_executability = self._new_executability[trans_id]
861
 
        entry.executable = new_executability
 
872
        if entry is not None:
 
873
            entry.executable = new_executability
862
874
        if supports_executable():
863
875
            abspath = self._tree.abspath(path)
864
876
            current_mode = os.stat(abspath).st_mode
1147
1159
        tree.lock_tree_write()
1148
1160
 
1149
1161
        try:
1150
 
            control_files = tree._control_files
1151
1162
            limbodir = urlutils.local_path_from_url(
1152
 
                control_files.controlfilename('limbo'))
 
1163
                tree._transport.abspath('limbo'))
1153
1164
            try:
1154
1165
                os.mkdir(limbodir)
1155
1166
            except OSError, e:
1156
1167
                if e.errno == errno.EEXIST:
1157
1168
                    raise ExistingLimbo(limbodir)
1158
1169
            deletiondir = urlutils.local_path_from_url(
1159
 
                control_files.controlfilename('pending-deletion'))
 
1170
                tree._transport.abspath('pending-deletion'))
1160
1171
            try:
1161
1172
                os.mkdir(deletiondir)
1162
1173
            except OSError, e:
1170
1181
                                   tree.case_sensitive)
1171
1182
        self._deletiondir = deletiondir
1172
1183
 
1173
 
    def apply(self, no_conflicts=False, _mover=None):
 
1184
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1174
1185
        """Apply all changes to the inventory and filesystem.
1175
1186
 
1176
1187
        If filesystem or inventory conflicts are present, MalformedTransform
1180
1191
 
1181
1192
        :param no_conflicts: if True, the caller guarantees there are no
1182
1193
            conflicts, so no check is made.
 
1194
        :param precomputed_delta: An inventory delta to use instead of
 
1195
            calculating one.
1183
1196
        :param _mover: Supply an alternate FileMover, for testing
1184
1197
        """
1185
1198
        if not no_conflicts:
1186
1199
            conflicts = self.find_conflicts()
1187
1200
            if len(conflicts) != 0:
1188
1201
                raise MalformedTransform(conflicts=conflicts)
1189
 
        inventory_delta = []
 
1202
        if precomputed_delta is None:
 
1203
            new_inventory_delta = []
 
1204
            inventory_delta = new_inventory_delta
 
1205
        else:
 
1206
            new_inventory_delta = None
 
1207
            inventory_delta = precomputed_delta
1190
1208
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1191
1209
        try:
1192
1210
            if _mover is None:
1195
1213
                mover = _mover
1196
1214
            try:
1197
1215
                child_pb.update('Apply phase', 0, 2)
1198
 
                self._apply_removals(inventory_delta, mover)
 
1216
                self._apply_removals(new_inventory_delta, mover)
1199
1217
                child_pb.update('Apply phase', 1, 2)
1200
 
                modified_paths = self._apply_insertions(inventory_delta, mover)
 
1218
                modified_paths = self._apply_insertions(new_inventory_delta,
 
1219
                                                        mover)
1201
1220
            except:
1202
1221
                mover.rollback()
1203
1222
                raise
1216
1235
        That is, delete files that are to be deleted, and put any files that
1217
1236
        need renaming into limbo.  This must be done in strict child-to-parent
1218
1237
        order.
 
1238
 
 
1239
        If inventory_delta is None, no inventory delta generation is performed.
1219
1240
        """
1220
1241
        tree_paths = list(self._tree_path_ids.iteritems())
1221
1242
        tree_paths.sort(reverse=True)
1237
1258
                            raise
1238
1259
                    else:
1239
1260
                        self.rename_count += 1
1240
 
                if trans_id in self._removed_id:
 
1261
                if (trans_id in self._removed_id
 
1262
                    and inventory_delta is not None):
1241
1263
                    if trans_id == self._new_root:
1242
1264
                        file_id = self._tree.get_root_id()
1243
1265
                    else:
1244
1266
                        file_id = self.tree_file_id(trans_id)
1245
 
                    assert file_id is not None
1246
1267
                    # File-id isn't really being deleted, just moved
1247
1268
                    if file_id in self._r_new_id:
1248
1269
                        continue
1256
1277
        That is, create any files that need to be created, and restore from
1257
1278
        limbo any files that needed renaming.  This must be done in strict
1258
1279
        parent-to-child order.
 
1280
 
 
1281
        If inventory_delta is None, no inventory delta is calculated, and
 
1282
        no list of modified paths is returned.
1259
1283
        """
1260
 
        new_paths = self.new_paths()
 
1284
        new_paths = self.new_paths(filesystem_only=(inventory_delta is None))
1261
1285
        modified_paths = []
1262
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1263
1286
        completed_new = []
 
1287
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1288
                                 new_paths)
 
1289
        if inventory_delta is not None:
 
1290
            entries = self._tree.iter_entries_by_dir(
 
1291
                new_path_file_ids.values())
 
1292
            old_paths = dict((e.file_id, p) for p, e in entries)
 
1293
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1264
1294
        try:
1265
1295
            for num, (path, trans_id) in enumerate(new_paths):
1266
1296
                new_entry = None
1267
 
                child_pb.update('adding file', num, len(new_paths))
1268
 
                if trans_id in self._new_contents or \
1269
 
                    self.path_changed(trans_id):
1270
 
                    full_path = self._tree.abspath(path)
1271
 
                    if trans_id in self._needs_rename:
 
1297
                if (num % 10) == 0:
 
1298
                    child_pb.update('adding file', num, len(new_paths))
 
1299
                full_path = self._tree.abspath(path)
 
1300
                if trans_id in self._needs_rename:
 
1301
                    try:
 
1302
                        mover.rename(self._limbo_name(trans_id), full_path)
 
1303
                    except OSError, e:
 
1304
                        # We may be renaming a dangling inventory id
 
1305
                        if e.errno != errno.ENOENT:
 
1306
                            raise
 
1307
                    else:
 
1308
                        self.rename_count += 1
 
1309
                if inventory_delta is not None:
 
1310
                    if (trans_id in self._new_contents or
 
1311
                        self.path_changed(trans_id)):
 
1312
                        if trans_id in self._new_contents:
 
1313
                            modified_paths.append(full_path)
 
1314
                            completed_new.append(trans_id)
 
1315
                    file_id = new_path_file_ids[trans_id]
 
1316
                    if file_id is not None and (trans_id in self._new_id or
 
1317
                        trans_id in self._new_name or
 
1318
                        trans_id in self._new_parent
 
1319
                        or trans_id in self._new_executability):
1272
1320
                        try:
1273
 
                            mover.rename(self._limbo_name(trans_id), full_path)
1274
 
                        except OSError, e:
1275
 
                            # We may be renaming a dangling inventory id
1276
 
                            if e.errno != errno.ENOENT:
1277
 
                                raise
 
1321
                            kind = self.final_kind(trans_id)
 
1322
                        except NoSuchFile:
 
1323
                            kind = self._tree.stored_kind(file_id)
 
1324
                        parent_trans_id = self.final_parent(trans_id)
 
1325
                        parent_file_id = new_path_file_ids.get(parent_trans_id)
 
1326
                        if parent_file_id is None:
 
1327
                            parent_file_id = self.final_file_id(
 
1328
                                parent_trans_id)
 
1329
                        if trans_id in self._new_reference_revision:
 
1330
                            new_entry = inventory.TreeReference(
 
1331
                                file_id,
 
1332
                                self._new_name[trans_id],
 
1333
                                self.final_file_id(self._new_parent[trans_id]),
 
1334
                                None, self._new_reference_revision[trans_id])
1278
1335
                        else:
1279
 
                            self.rename_count += 1
1280
 
                    if trans_id in self._new_contents:
1281
 
                        modified_paths.append(full_path)
1282
 
                        completed_new.append(trans_id)
1283
 
                file_id = self.final_file_id(trans_id)
1284
 
                if file_id is not None and (trans_id in self._new_id or
1285
 
                    trans_id in self._new_name or trans_id in self._new_parent
1286
 
                    or trans_id in self._new_executability):
1287
 
                    try:
1288
 
                        kind = self.final_kind(trans_id)
1289
 
                    except NoSuchFile:
1290
 
                        kind = self._tree.stored_kind(file_id)
1291
 
                    if trans_id in self._new_reference_revision:
1292
 
                        new_entry = inventory.TreeReference(
1293
 
                            self.final_file_id(trans_id),
1294
 
                            self._new_name[trans_id],
1295
 
                            self.final_file_id(self._new_parent[trans_id]),
1296
 
                            None, self._new_reference_revision[trans_id])
1297
 
                    else:
1298
 
                        new_entry = inventory.make_entry(kind,
1299
 
                            self.final_name(trans_id),
1300
 
                            self.final_file_id(self.final_parent(trans_id)),
1301
 
                            self.final_file_id(trans_id))
1302
 
                    try:
1303
 
                        old_path = self._tree.id2path(new_entry.file_id)
1304
 
                    except errors.NoSuchId:
1305
 
                        old_path = None
1306
 
                    inventory_delta.append((old_path, path, new_entry.file_id,
1307
 
                                            new_entry))
 
1336
                            new_entry = inventory.make_entry(kind,
 
1337
                                self.final_name(trans_id),
 
1338
                                parent_file_id, file_id)
 
1339
                        old_path = old_paths.get(new_entry.file_id)
 
1340
                        inventory_delta.append(
 
1341
                            (old_path, path, new_entry.file_id, new_entry))
1308
1342
 
1309
1343
                if trans_id in self._new_executability:
1310
1344
                    self._set_executability(path, new_entry, trans_id)
1311
1345
        finally:
1312
1346
            child_pb.finished()
1313
 
        for trans_id in completed_new:
1314
 
            del self._new_contents[trans_id]
 
1347
        if inventory_delta is None:
 
1348
            self._new_contents.clear()
 
1349
        else:
 
1350
            for trans_id in completed_new:
 
1351
                del self._new_contents[trans_id]
1315
1352
        return modified_paths
1316
1353
 
1317
1354
 
1354
1391
        except KeyError:
1355
1392
            return
1356
1393
        file_id = self.tree_file_id(parent_id)
1357
 
        for child in self._tree.inventory[file_id].children.iterkeys():
 
1394
        if file_id is None:
 
1395
            return
 
1396
        children = getattr(self._tree.inventory[file_id], 'children', {})
 
1397
        for child in children:
1358
1398
            childpath = joinpath(path, child)
1359
1399
            yield self.trans_id_tree_path(childpath)
1360
1400
 
1361
1401
 
1362
 
class _PreviewTree(object):
 
1402
class _PreviewTree(tree.Tree):
1363
1403
    """Partial implementation of Tree to support show_diff_trees"""
1364
1404
 
1365
1405
    def __init__(self, transform):
1366
1406
        self._transform = transform
 
1407
        self._final_paths = FinalPaths(transform)
 
1408
        self.__by_parent = None
 
1409
 
 
1410
    def _changes(self, file_id):
 
1411
        for changes in self._transform.iter_changes():
 
1412
            if changes[0] == file_id:
 
1413
                return changes
 
1414
 
 
1415
    def _content_change(self, file_id):
 
1416
        """Return True if the content of this file changed"""
 
1417
        changes = self._changes(file_id)
 
1418
        # changes[2] is true if the file content changed.  See
 
1419
        # InterTree.iter_changes.
 
1420
        return (changes is not None and changes[2])
 
1421
 
 
1422
    def _get_file_revision(self, file_id, vf, tree_revision):
 
1423
        return self._transform._tree._get_file_revision(file_id, vf,
 
1424
                                                        tree_revision)
 
1425
 
 
1426
    def _stat_limbo_file(self, file_id):
 
1427
        trans_id = self._transform.trans_id_file_id(file_id)
 
1428
        name = self._transform._limbo_name(trans_id)
 
1429
        return os.lstat(name)
 
1430
 
 
1431
    @property
 
1432
    def _by_parent(self):
 
1433
        if self.__by_parent is None:
 
1434
            self.__by_parent = self._transform.by_parent()
 
1435
        return self.__by_parent
1367
1436
 
1368
1437
    def lock_read(self):
1369
1438
        # Perhaps in theory, this should lock the TreeTransform?
1372
1441
    def unlock(self):
1373
1442
        pass
1374
1443
 
 
1444
    @property
 
1445
    def inventory(self):
 
1446
        """This Tree does not use inventory as its backing data."""
 
1447
        raise NotImplementedError(_PreviewTree.inventory)
 
1448
 
 
1449
    def get_root_id(self):
 
1450
        return self._transform.final_file_id(self._transform.root)
 
1451
 
 
1452
    def all_file_ids(self):
 
1453
        tree_ids = set(self._transform._tree.all_file_ids())
 
1454
        tree_ids.difference_update(self._transform.tree_file_id(t)
 
1455
                                   for t in self._transform._removed_id)
 
1456
        tree_ids.update(self._transform._new_id.values())
 
1457
        return tree_ids
 
1458
 
 
1459
    def __iter__(self):
 
1460
        return iter(self.all_file_ids())
 
1461
 
 
1462
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
 
1463
        """See Tree.paths2ids"""
 
1464
        to_find = set(specific_files)
 
1465
        result = set()
 
1466
        for (file_id, paths, changed, versioned, parent, name, kind,
 
1467
             executable) in self._transform.iter_changes():
 
1468
            if paths[1] in to_find:
 
1469
                result.append(file_id)
 
1470
                to_find.remove(paths[1])
 
1471
        result.update(self._transform._tree.paths2ids(to_find,
 
1472
                      trees=[], require_versioned=require_versioned))
 
1473
        return result
 
1474
 
 
1475
    def _path2trans_id(self, path):
 
1476
        segments = splitpath(path)
 
1477
        cur_parent = self._transform.root
 
1478
        for cur_segment in segments:
 
1479
            for child in self._all_children(cur_parent):
 
1480
                if self._transform.final_name(child) == cur_segment:
 
1481
                    cur_parent = child
 
1482
                    break
 
1483
            else:
 
1484
                return None
 
1485
        return cur_parent
 
1486
 
 
1487
    def path2id(self, path):
 
1488
        return self._transform.final_file_id(self._path2trans_id(path))
 
1489
 
 
1490
    def id2path(self, file_id):
 
1491
        trans_id = self._transform.trans_id_file_id(file_id)
 
1492
        try:
 
1493
            return self._final_paths._determine_path(trans_id)
 
1494
        except NoFinalPath:
 
1495
            raise errors.NoSuchId(self, file_id)
 
1496
 
 
1497
    def _all_children(self, trans_id):
 
1498
        children = set(self._transform.iter_tree_children(trans_id))
 
1499
        # children in the _new_parent set are provided by _by_parent.
 
1500
        children.difference_update(self._transform._new_parent.keys())
 
1501
        children.update(self._by_parent.get(trans_id, []))
 
1502
        return children
 
1503
 
 
1504
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
 
1505
        for trans_id, parent_file_id in ordered_entries:
 
1506
            file_id = self._transform.final_file_id(trans_id)
 
1507
            if file_id is None:
 
1508
                continue
 
1509
            if (specific_file_ids is not None
 
1510
                and file_id not in specific_file_ids):
 
1511
                continue
 
1512
            try:
 
1513
                kind = self._transform.final_kind(trans_id)
 
1514
            except NoSuchFile:
 
1515
                kind = self._transform._tree.stored_kind(file_id)
 
1516
            new_entry = inventory.make_entry(
 
1517
                kind,
 
1518
                self._transform.final_name(trans_id),
 
1519
                parent_file_id, file_id)
 
1520
            yield new_entry, trans_id
 
1521
 
 
1522
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1523
        # This may not be a maximally efficient implementation, but it is
 
1524
        # reasonably straightforward.  An implementation that grafts the
 
1525
        # TreeTransform changes onto the tree's iter_entries_by_dir results
 
1526
        # might be more efficient, but requires tricky inferences about stack
 
1527
        # position.
 
1528
        todo = [ROOT_PARENT]
 
1529
        ordered_ids = []
 
1530
        while len(todo) > 0:
 
1531
            parent = todo.pop()
 
1532
            parent_file_id = self._transform.final_file_id(parent)
 
1533
            children = list(self._all_children(parent))
 
1534
            paths = dict(zip(children, self._final_paths.get_paths(children)))
 
1535
            children.sort(key=paths.get)
 
1536
            todo.extend(reversed(children))
 
1537
            for trans_id in children:
 
1538
                ordered_ids.append((trans_id, parent_file_id))
 
1539
        for entry, trans_id in self._make_inv_entries(ordered_ids,
 
1540
                                                      specific_file_ids):
 
1541
            yield unicode(self._final_paths.get_path(trans_id)), entry
 
1542
 
 
1543
    def kind(self, file_id):
 
1544
        trans_id = self._transform.trans_id_file_id(file_id)
 
1545
        return self._transform.final_kind(trans_id)
 
1546
 
 
1547
    def stored_kind(self, file_id):
 
1548
        trans_id = self._transform.trans_id_file_id(file_id)
 
1549
        try:
 
1550
            return self._transform._new_contents[trans_id]
 
1551
        except KeyError:
 
1552
            return self._transform._tree.stored_kind(file_id)
 
1553
 
 
1554
    def get_file_mtime(self, file_id, path=None):
 
1555
        """See Tree.get_file_mtime"""
 
1556
        if not self._content_change(file_id):
 
1557
            return self._transform._tree.get_file_mtime(file_id, path)
 
1558
        return self._stat_limbo_file(file_id).st_mtime
 
1559
 
 
1560
    def get_file_size(self, file_id):
 
1561
        """See Tree.get_file_size"""
 
1562
        if self.kind(file_id) == 'file':
 
1563
            return self._transform._tree.get_file_size(file_id)
 
1564
        else:
 
1565
            return None
 
1566
 
 
1567
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
1568
        return self._transform._tree.get_file_sha1(file_id)
 
1569
 
 
1570
    def is_executable(self, file_id, path=None):
 
1571
        trans_id = self._transform.trans_id_file_id(file_id)
 
1572
        try:
 
1573
            return self._transform._new_executability[trans_id]
 
1574
        except KeyError:
 
1575
            return self._transform._tree.is_executable(file_id, path)
 
1576
 
 
1577
    def path_content_summary(self, path):
 
1578
        trans_id = self._path2trans_id(path)
 
1579
        tt = self._transform
 
1580
        tree_path = tt._tree_id_paths.get(trans_id)
 
1581
        kind = tt._new_contents.get(trans_id)
 
1582
        if kind is None:
 
1583
            if tree_path is None or trans_id in tt._removed_contents:
 
1584
                return 'missing', None, None, None
 
1585
            summary = tt._tree.path_content_summary(tree_path)
 
1586
            kind, size, executable, link_or_sha1 = summary
 
1587
        else:
 
1588
            link_or_sha1 = None
 
1589
            limbo_name = tt._limbo_name(trans_id)
 
1590
            if trans_id in tt._new_reference_revision:
 
1591
                kind = 'tree-reference'
 
1592
            if kind == 'file':
 
1593
                statval = os.lstat(limbo_name)
 
1594
                size = statval.st_size
 
1595
                if not supports_executable():
 
1596
                    executable = None
 
1597
                else:
 
1598
                    executable = statval.st_mode & S_IEXEC
 
1599
            else:
 
1600
                size = None
 
1601
                executable = None
 
1602
            if kind == 'symlink':
 
1603
                link_or_sha1 = os.readlink(limbo_name)
 
1604
        if supports_executable():
 
1605
            executable = tt._new_executability.get(trans_id, executable)
 
1606
        return kind, size, executable, link_or_sha1
 
1607
 
1375
1608
    def iter_changes(self, from_tree, include_unchanged=False,
1376
1609
                      specific_files=None, pb=None, extra_trees=None,
1377
1610
                      require_versioned=True, want_unversioned=False):
1391
1624
            raise ValueError('want_unversioned is not supported')
1392
1625
        return self._transform.iter_changes()
1393
1626
 
1394
 
    def kind(self, file_id):
1395
 
        trans_id = self._transform.trans_id_file_id(file_id)
1396
 
        return self._transform.final_kind(trans_id)
1397
 
 
1398
 
    def get_file_mtime(self, file_id, path=None):
1399
 
        """See Tree.get_file_mtime"""
1400
 
        trans_id = self._transform.trans_id_file_id(file_id)
1401
 
        name = self._transform._limbo_name(trans_id)
1402
 
        return os.stat(name).st_mtime
1403
 
 
1404
 
    def get_file(self, file_id):
 
1627
    def get_file(self, file_id, path=None):
1405
1628
        """See Tree.get_file"""
 
1629
        if not self._content_change(file_id):
 
1630
            return self._transform._tree.get_file(file_id, path)
1406
1631
        trans_id = self._transform.trans_id_file_id(file_id)
1407
1632
        name = self._transform._limbo_name(trans_id)
1408
1633
        return open(name, 'rb')
1409
1634
 
 
1635
    def get_file_text(self, file_id):
 
1636
        text_file = self.get_file(file_id)
 
1637
        try:
 
1638
            return text_file.read()
 
1639
        finally:
 
1640
            text_file.close()
 
1641
 
 
1642
    def annotate_iter(self, file_id,
 
1643
                      default_revision=_mod_revision.CURRENT_REVISION):
 
1644
        changes = self._changes(file_id)
 
1645
        if changes is None:
 
1646
            get_old = True
 
1647
        else:
 
1648
            changed_content, versioned, kind = (changes[2], changes[3],
 
1649
                                                changes[6])
 
1650
            if kind[1] is None:
 
1651
                return None
 
1652
            get_old = (kind[0] == 'file' and versioned[0])
 
1653
        if get_old:
 
1654
            old_annotation = self._transform._tree.annotate_iter(file_id,
 
1655
                default_revision=default_revision)
 
1656
        else:
 
1657
            old_annotation = []
 
1658
        if changes is None:
 
1659
            return old_annotation
 
1660
        if not changed_content:
 
1661
            return old_annotation
 
1662
        return annotate.reannotate([old_annotation],
 
1663
                                   self.get_file(file_id).readlines(),
 
1664
                                   default_revision)
 
1665
 
1410
1666
    def get_symlink_target(self, file_id):
1411
1667
        """See Tree.get_symlink_target"""
 
1668
        if not self._content_change(file_id):
 
1669
            return self._transform._tree.get_symlink_target(file_id)
1412
1670
        trans_id = self._transform.trans_id_file_id(file_id)
1413
1671
        name = self._transform._limbo_name(trans_id)
1414
1672
        return os.readlink(name)
1415
1673
 
1416
 
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
1417
 
        """See Tree.paths2ids"""
1418
 
        return 'not_empty'
 
1674
    def list_files(self, include_root=False):
 
1675
        return self._transform._tree.list_files(include_root)
 
1676
 
 
1677
    def walkdirs(self, prefix=""):
 
1678
        return self._transform._tree.walkdirs(prefix)
 
1679
 
 
1680
    def get_parent_ids(self):
 
1681
        return self._transform._tree.get_parent_ids()
 
1682
 
 
1683
    def get_revision_tree(self, revision_id):
 
1684
        return self._transform._tree.get_revision_tree(revision_id)
1419
1685
 
1420
1686
 
1421
1687
def joinpath(parent, child):
1453
1719
            self._known_paths[trans_id] = self._determine_path(trans_id)
1454
1720
        return self._known_paths[trans_id]
1455
1721
 
 
1722
    def get_paths(self, trans_ids):
 
1723
        return [(self.get_path(t), t) for t in trans_ids]
 
1724
 
 
1725
 
1456
1726
 
1457
1727
def topology_sorted_ids(tree):
1458
1728
    """Determine the topological order of the ids in a tree"""
1461
1731
    return file_ids
1462
1732
 
1463
1733
 
1464
 
def build_tree(tree, wt, accelerator_tree=None, hardlink=False):
 
1734
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
 
1735
               delta_from_tree=False):
1465
1736
    """Create working tree for a branch, using a TreeTransform.
1466
1737
    
1467
1738
    This function should be used on empty trees, having a tree root at most.
1483
1754
    :param hardlink: If true, hard-link files to accelerator_tree, where
1484
1755
        possible.  accelerator_tree must implement abspath, i.e. be a
1485
1756
        working tree.
 
1757
    :param delta_from_tree: If true, build_tree may use the input Tree to
 
1758
        generate the inventory delta.
1486
1759
    """
1487
1760
    wt.lock_tree_write()
1488
1761
    try:
1491
1764
            if accelerator_tree is not None:
1492
1765
                accelerator_tree.lock_read()
1493
1766
            try:
1494
 
                return _build_tree(tree, wt, accelerator_tree, hardlink)
 
1767
                return _build_tree(tree, wt, accelerator_tree, hardlink,
 
1768
                                   delta_from_tree)
1495
1769
            finally:
1496
1770
                if accelerator_tree is not None:
1497
1771
                    accelerator_tree.unlock()
1501
1775
        wt.unlock()
1502
1776
 
1503
1777
 
1504
 
def _build_tree(tree, wt, accelerator_tree, hardlink):
 
1778
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1505
1779
    """See build_tree."""
1506
1780
    for num, _unused in enumerate(wt.all_file_ids()):
1507
1781
        if num > 0:  # more than just a root
1508
1782
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
1783
    existing_files = set()
 
1784
    for dir, files in wt.walkdirs():
 
1785
        existing_files.update(f[0] for f in files)
1509
1786
    file_trans_id = {}
1510
1787
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1511
1788
    pp = ProgressPhase("Build phase", 2, top_pb)
1530
1807
        try:
1531
1808
            deferred_contents = []
1532
1809
            num = 0
 
1810
            total = len(tree.inventory)
 
1811
            if delta_from_tree:
 
1812
                precomputed_delta = []
 
1813
            else:
 
1814
                precomputed_delta = None
1533
1815
            for num, (tree_path, entry) in \
1534
1816
                enumerate(tree.inventory.iter_entries_by_dir()):
1535
 
                pb.update("Building tree", num - len(deferred_contents),
1536
 
                          len(tree.inventory))
 
1817
                pb.update("Building tree", num - len(deferred_contents), total)
1537
1818
                if entry.parent_id is None:
1538
1819
                    continue
1539
1820
                reparent = False
1540
1821
                file_id = entry.file_id
1541
 
                target_path = wt.abspath(tree_path)
1542
 
                try:
 
1822
                if delta_from_tree:
 
1823
                    precomputed_delta.append((None, tree_path, file_id, entry))
 
1824
                if tree_path in existing_files:
 
1825
                    target_path = wt.abspath(tree_path)
1543
1826
                    kind = file_kind(target_path)
1544
 
                except NoSuchFile:
1545
 
                    pass
1546
 
                else:
1547
1827
                    if kind == "directory":
1548
1828
                        try:
1549
1829
                            bzrdir.BzrDir.open(target_path)
1557
1837
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1558
1838
                        if kind == 'directory':
1559
1839
                            reparent = True
1560
 
                if entry.parent_id not in file_trans_id:
1561
 
                    raise AssertionError(
1562
 
                        'entry %s parent id %r is not in file_trans_id %r'
1563
 
                        % (entry, entry.parent_id, file_trans_id))
1564
1840
                parent_id = file_trans_id[entry.parent_id]
1565
1841
                if entry.kind == 'file':
1566
1842
                    # We *almost* replicate new_by_entry, so that we can defer
1567
1843
                    # getting the file text, and get them all at once.
1568
1844
                    trans_id = tt.create_path(entry.name, parent_id)
1569
1845
                    file_trans_id[file_id] = trans_id
1570
 
                    tt.version_file(entry.file_id, trans_id)
1571
 
                    executable = tree.is_executable(entry.file_id, tree_path)
1572
 
                    if executable is not None:
 
1846
                    tt.version_file(file_id, trans_id)
 
1847
                    executable = tree.is_executable(file_id, tree_path)
 
1848
                    if executable:
1573
1849
                        tt.set_executability(executable, trans_id)
1574
 
                    deferred_contents.append((entry.file_id, trans_id))
 
1850
                    deferred_contents.append((file_id, trans_id))
1575
1851
                else:
1576
1852
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1577
1853
                                                          tree)
1588
1864
        divert_trans = set(file_trans_id[f] for f in divert)
1589
1865
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1590
1866
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
1867
        if len(raw_conflicts) > 0:
 
1868
            precomputed_delta = None
1591
1869
        conflicts = cook_conflicts(raw_conflicts, tt)
1592
1870
        for conflict in conflicts:
1593
1871
            warning(conflict)
1595
1873
            wt.add_conflicts(conflicts)
1596
1874
        except errors.UnsupportedOperation:
1597
1875
            pass
1598
 
        result = tt.apply(no_conflicts=True)
 
1876
        result = tt.apply(no_conflicts=True,
 
1877
                          precomputed_delta=precomputed_delta)
1599
1878
    finally:
1600
1879
        tt.finalize()
1601
1880
        top_pb.finished()
1644
1923
    by_parent = tt.by_parent()
1645
1924
    for child in by_parent[old_parent]:
1646
1925
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
1926
    return by_parent[old_parent]
1647
1927
 
1648
1928
def _content_match(tree, entry, file_id, kind, target_path):
1649
1929
    if entry.kind != kind:
1663
1943
    new_conflicts = set()
1664
1944
    for c_type, conflict in ((c[0], c) for c in conflicts):
1665
1945
        # Anything but a 'duplicate' would indicate programmer error
1666
 
        assert c_type == 'duplicate', c_type
 
1946
        if c_type != 'duplicate':
 
1947
            raise AssertionError(c_type)
1667
1948
        # Now figure out which is new and which is old
1668
1949
        if tt.new_contents(conflict[1]):
1669
1950
            new_file = conflict[1]
1727
2008
        tt.set_executability(entry.executable, trans_id)
1728
2009
 
1729
2010
 
1730
 
@deprecated_function(zero_fifteen)
1731
 
def find_interesting(working_tree, target_tree, filenames):
1732
 
    """Find the ids corresponding to specified filenames.
1733
 
    
1734
 
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1735
 
    """
1736
 
    working_tree.lock_read()
1737
 
    try:
1738
 
        target_tree.lock_read()
1739
 
        try:
1740
 
            return working_tree.paths2ids(filenames, [target_tree])
1741
 
        finally:
1742
 
            target_tree.unlock()
1743
 
    finally:
1744
 
        working_tree.unlock()
1745
 
 
1746
 
 
1747
 
@deprecated_function(zero_ninety)
1748
 
def change_entry(tt, file_id, working_tree, target_tree, 
1749
 
                 trans_id_file_id, backups, trans_id, by_parent):
1750
 
    """Replace a file_id's contents with those from a target tree."""
1751
 
    if file_id is None and target_tree is None:
1752
 
        # skip the logic altogether in the deprecation test
1753
 
        return
1754
 
    e_trans_id = trans_id_file_id(file_id)
1755
 
    entry = target_tree.inventory[file_id]
1756
 
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1757
 
                                                           working_tree)
1758
 
    if contents_mod:
1759
 
        mode_id = e_trans_id
1760
 
        if has_contents:
1761
 
            if not backups:
1762
 
                tt.delete_contents(e_trans_id)
1763
 
            else:
1764
 
                parent_trans_id = trans_id_file_id(entry.parent_id)
1765
 
                backup_name = get_backup_name(entry, by_parent,
1766
 
                                              parent_trans_id, tt)
1767
 
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1768
 
                tt.unversion_file(e_trans_id)
1769
 
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1770
 
                tt.version_file(file_id, e_trans_id)
1771
 
                trans_id[file_id] = e_trans_id
1772
 
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1773
 
        create_entry_executability(tt, entry, e_trans_id)
1774
 
 
1775
 
    elif meta_mod:
1776
 
        tt.set_executability(entry.executable, e_trans_id)
1777
 
    if tt.final_name(e_trans_id) != entry.name:
1778
 
        adjust_path  = True
1779
 
    else:
1780
 
        parent_id = tt.final_parent(e_trans_id)
1781
 
        parent_file_id = tt.final_file_id(parent_id)
1782
 
        if parent_file_id != entry.parent_id:
1783
 
            adjust_path = True
1784
 
        else:
1785
 
            adjust_path = False
1786
 
    if adjust_path:
1787
 
        parent_trans_id = trans_id_file_id(entry.parent_id)
1788
 
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1789
 
 
1790
 
 
1791
2011
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1792
2012
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1793
2013
 
1941
2161
                    # preserve the execute bit when backing up
1942
2162
                    if keep_content and executable[0] == executable[1]:
1943
2163
                        tt.set_executability(executable[1], trans_id)
1944
 
                else:
1945
 
                    assert kind[1] is None
 
2164
                elif kind[1] is not None:
 
2165
                    raise AssertionError(kind[1])
1946
2166
            if versioned == (False, True):
1947
2167
                tt.version_file(file_id, trans_id)
1948
2168
            if versioned == (True, False):
2019
2239
                new_conflicts.add(('deleting parent', 'Not deleting', 
2020
2240
                                   trans_id))
2021
2241
            except KeyError:
2022
 
                tt.create_directory(trans_id)
2023
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
2242
                create = True
2024
2243
                try:
2025
2244
                    tt.final_name(trans_id)
2026
2245
                except NoFinalPath:
2027
2246
                    if path_tree is not None:
2028
2247
                        file_id = tt.final_file_id(trans_id)
 
2248
                        if file_id is None:
 
2249
                            file_id = tt.inactive_file_id(trans_id)
2029
2250
                        entry = path_tree.inventory[file_id]
2030
 
                        parent_trans_id = tt.trans_id_file_id(entry.parent_id)
2031
 
                        tt.adjust_path(entry.name, parent_trans_id, trans_id)
 
2251
                        # special-case the other tree root (move its
 
2252
                        # children to current root)
 
2253
                        if entry.parent_id is None:
 
2254
                            create=False
 
2255
                            moved = _reparent_transform_children(
 
2256
                                tt, trans_id, tt.root)
 
2257
                            for child in moved:
 
2258
                                new_conflicts.add((c_type, 'Moved to root',
 
2259
                                                   child))
 
2260
                        else:
 
2261
                            parent_trans_id = tt.trans_id_file_id(
 
2262
                                entry.parent_id)
 
2263
                            tt.adjust_path(entry.name, parent_trans_id,
 
2264
                                           trans_id)
 
2265
                if create:
 
2266
                    tt.create_directory(trans_id)
 
2267
                    new_conflicts.add((c_type, 'Created directory', trans_id))
2032
2268
        elif c_type == 'unversioned parent':
2033
 
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
 
2269
            file_id = tt.inactive_file_id(conflict[1])
 
2270
            # special-case the other tree root (move its children instead)
 
2271
            if path_tree and file_id in path_tree:
 
2272
                if path_tree.inventory[file_id].parent_id is None:
 
2273
                    continue
 
2274
            tt.version_file(file_id, conflict[1])
2034
2275
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2035
2276
        elif c_type == 'non-directory parent':
2036
2277
            parent_id = conflict[1]