~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

Merge bzr.dev and tree-file-ids-as-tuples.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
22
22
WorkingTree.open(dir).
23
23
"""
24
24
 
 
25
from __future__ import absolute_import
 
26
 
25
27
from cStringIO import StringIO
26
28
import os
27
29
import sys
31
33
import errno
32
34
import stat
33
35
 
34
 
import bzrlib
35
36
from bzrlib import (
36
37
    bzrdir,
37
38
    cache_utf8,
 
39
    config,
 
40
    conflicts as _mod_conflicts,
38
41
    debug,
39
42
    dirstate,
40
43
    errors,
 
44
    filters as _mod_filters,
41
45
    generate_ids,
42
46
    osutils,
43
47
    revision as _mod_revision,
46
50
    transform,
47
51
    views,
48
52
    )
49
 
import bzrlib.branch
50
 
import bzrlib.ui
51
53
""")
52
54
 
53
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
54
 
from bzrlib.filters import filtered_input_file, internal_size_sha_file_byname
55
56
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
56
 
from bzrlib.mutabletree import needs_tree_write_lock
 
57
from bzrlib.lock import LogicalLockResult
 
58
from bzrlib.lockable_files import LockableFiles
 
59
from bzrlib.lockdir import LockDir
 
60
from bzrlib.mutabletree import (
 
61
    MutableTree,
 
62
    needs_tree_write_lock,
 
63
    )
57
64
from bzrlib.osutils import (
58
65
    file_kind,
59
66
    isdir,
61
68
    realpath,
62
69
    safe_unicode,
63
70
    )
64
 
from bzrlib.trace import mutter
 
71
from bzrlib.symbol_versioning import (
 
72
    deprecated_in,
 
73
    deprecated_method,
 
74
    )
65
75
from bzrlib.transport.local import LocalTransport
66
 
from bzrlib.tree import InterTree
67
 
from bzrlib.tree import Tree
68
 
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
69
 
 
70
 
 
71
 
class DirStateWorkingTree(WorkingTree3):
 
76
from bzrlib.tree import (
 
77
    InterTree,
 
78
    InventoryTree,
 
79
    )
 
80
from bzrlib.workingtree import (
 
81
    InventoryWorkingTree,
 
82
    WorkingTree,
 
83
    WorkingTreeFormatMetaDir,
 
84
    )
 
85
 
 
86
 
 
87
class DirStateWorkingTree(InventoryWorkingTree):
 
88
 
72
89
    def __init__(self, basedir,
73
90
                 branch,
74
91
                 _control_files=None,
84
101
        self._format = _format
85
102
        self.bzrdir = _bzrdir
86
103
        basedir = safe_unicode(basedir)
87
 
        mutter("opening working tree %r", basedir)
 
104
        trace.mutter("opening working tree %r", basedir)
88
105
        self._branch = branch
89
106
        self.basedir = realpath(basedir)
90
107
        # if branch is at our basedir and is a format 6 or less
124
141
            state.add(f, file_id, kind, None, '')
125
142
        self._make_dirty(reset_inventory=True)
126
143
 
 
144
    def _get_check_refs(self):
 
145
        """Return the references needed to perform a check of this tree."""
 
146
        return [('trees', self.last_revision())]
 
147
 
127
148
    def _make_dirty(self, reset_inventory):
128
149
        """Make the tree state dirty.
129
150
 
181
202
 
182
203
    def _comparison_data(self, entry, path):
183
204
        kind, executable, stat_value = \
184
 
            WorkingTree3._comparison_data(self, entry, path)
 
205
            WorkingTree._comparison_data(self, entry, path)
185
206
        # it looks like a plain directory, but it's really a reference -- see
186
207
        # also kind()
187
208
        if (self._repo_supports_tree_reference and kind == 'directory'
193
214
    def commit(self, message=None, revprops=None, *args, **kwargs):
194
215
        # mark the tree as dirty post commit - commit
195
216
        # can change the current versioned list by doing deletes.
196
 
        result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
 
217
        result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
197
218
        self._make_dirty(reset_inventory=True)
198
219
        return result
199
220
 
218
239
        local_path = self.bzrdir.get_workingtree_transport(None
219
240
            ).local_abspath('dirstate')
220
241
        self._dirstate = dirstate.DirState.on_file(local_path,
221
 
            self._sha1_provider())
 
242
            self._sha1_provider(), self._worth_saving_limit())
222
243
        return self._dirstate
223
244
 
224
245
    def _sha1_provider(self):
233
254
        else:
234
255
            return None
235
256
 
 
257
    def _worth_saving_limit(self):
 
258
        """How many hash changes are ok before we must save the dirstate.
 
259
 
 
260
        :return: an integer. -1 means never save.
 
261
        """
 
262
        # FIXME: We want a WorkingTreeStack here -- vila 20110812
 
263
        conf = config.BranchStack(self.branch)
 
264
        return conf.get('bzr.workingtree.worth_saving_limit')
 
265
 
236
266
    def filter_unversioned_files(self, paths):
237
267
        """Filter out paths that are versioned.
238
268
 
368
398
        state = self.current_dirstate()
369
399
        if stat_value is None:
370
400
            try:
371
 
                stat_value = os.lstat(file_abspath)
 
401
                stat_value = osutils.lstat(file_abspath)
372
402
            except OSError, e:
373
403
                if e.errno == errno.ENOENT:
374
404
                    return None
389
419
                return link_or_sha1
390
420
        return None
391
421
 
392
 
    def _get_inventory(self):
 
422
    def _get_root_inventory(self):
393
423
        """Get the inventory for the tree. This is only valid within a lock."""
394
424
        if 'evil' in debug.debug_flags:
395
425
            trace.mutter_callsite(2,
400
430
        self._generate_inventory()
401
431
        return self._inventory
402
432
 
 
433
    @deprecated_method(deprecated_in((2, 5, 0)))
 
434
    def _get_inventory(self):
 
435
        return self.root_inventory
 
436
 
403
437
    inventory = property(_get_inventory,
404
438
                         doc="Inventory of this Tree")
405
439
 
 
440
    root_inventory = property(_get_root_inventory,
 
441
        "Root inventory of this tree")
 
442
 
406
443
    @needs_read_lock
407
444
    def get_parent_ids(self):
408
445
        """See Tree.get_parent_ids.
455
492
            return False # Missing entries are not executable
456
493
        return entry[1][0][3] # Executable?
457
494
 
458
 
    if not osutils.supports_executable():
459
 
        def is_executable(self, file_id, path=None):
460
 
            """Test if a file is executable or not.
 
495
    def is_executable(self, file_id, path=None):
 
496
        """Test if a file is executable or not.
461
497
 
462
 
            Note: The caller is expected to take a read-lock before calling this.
463
 
            """
 
498
        Note: The caller is expected to take a read-lock before calling this.
 
499
        """
 
500
        if not self._supports_executable():
464
501
            entry = self._get_entry(file_id=file_id, path=path)
465
502
            if entry == (None, None):
466
503
                return False
467
504
            return entry[1][0][3]
468
 
 
469
 
        _is_executable_from_path_and_stat = \
470
 
            _is_executable_from_path_and_stat_from_basis
471
 
    else:
472
 
        def is_executable(self, file_id, path=None):
473
 
            """Test if a file is executable or not.
474
 
 
475
 
            Note: The caller is expected to take a read-lock before calling this.
476
 
            """
 
505
        else:
477
506
            self._must_be_locked()
478
507
            if not path:
479
508
                path = self.id2path(file_id)
480
 
            mode = os.lstat(self.abspath(path)).st_mode
 
509
            mode = osutils.lstat(self.abspath(path)).st_mode
481
510
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
482
511
 
483
512
    def all_file_ids(self):
567
596
            return _mod_revision.NULL_REVISION
568
597
 
569
598
    def lock_read(self):
570
 
        """See Branch.lock_read, and WorkingTree.unlock."""
 
599
        """See Branch.lock_read, and WorkingTree.unlock.
 
600
 
 
601
        :return: A bzrlib.lock.LogicalLockResult.
 
602
        """
571
603
        self.branch.lock_read()
572
604
        try:
573
605
            self._control_files.lock_read()
586
618
        except:
587
619
            self.branch.unlock()
588
620
            raise
 
621
        return LogicalLockResult(self.unlock)
589
622
 
590
623
    def _lock_self_write(self):
591
624
        """This should be called after the branch is locked."""
606
639
        except:
607
640
            self.branch.unlock()
608
641
            raise
 
642
        return LogicalLockResult(self.unlock)
609
643
 
610
644
    def lock_tree_write(self):
611
 
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
645
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
646
 
 
647
        :return: A bzrlib.lock.LogicalLockResult.
 
648
        """
612
649
        self.branch.lock_read()
613
 
        self._lock_self_write()
 
650
        return self._lock_self_write()
614
651
 
615
652
    def lock_write(self):
616
 
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
653
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
654
 
 
655
        :return: A bzrlib.lock.LogicalLockResult.
 
656
        """
617
657
        self.branch.lock_write()
618
 
        self._lock_self_write()
 
658
        return self._lock_self_write()
619
659
 
620
660
    @needs_tree_write_lock
621
661
    def move(self, from_paths, to_dir, after=False):
652
692
 
653
693
        if self._inventory is not None:
654
694
            update_inventory = True
655
 
            inv = self.inventory
 
695
            inv = self.root_inventory
656
696
            to_dir_id = to_entry[0][2]
657
697
            to_dir_ie = inv[to_dir_id]
658
698
        else:
838
878
                rollback_rename()
839
879
                raise
840
880
            result.append((from_rel, to_rel))
841
 
            state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
 
881
            state._mark_modified()
842
882
            self._make_dirty(reset_inventory=False)
843
883
 
844
884
        return result
937
977
                    all_versioned = False
938
978
                    break
939
979
            if not all_versioned:
940
 
                raise errors.PathsNotVersionedError(paths)
 
980
                raise errors.PathsNotVersionedError(
 
981
                    [p.decode('utf-8') for p in paths])
941
982
        # -- remove redundancy in supplied paths to prevent over-scanning --
942
983
        search_paths = osutils.minimum_path_selection(paths)
943
984
        # sketch:
992
1033
            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
993
1034
            for dir_name in split_paths:
994
1035
                if dir_name not in found_dir_names:
995
 
                    raise errors.PathsNotVersionedError(paths)
 
1036
                    raise errors.PathsNotVersionedError(
 
1037
                        [p.decode('utf-8') for p in paths])
996
1038
 
997
1039
        for dir_name_id, trees_info in found.iteritems():
998
1040
            for index in search_indexes:
1005
1047
 
1006
1048
        This is a meaningless operation for dirstate, but we obey it anyhow.
1007
1049
        """
1008
 
        return self.inventory
 
1050
        return self.root_inventory
1009
1051
 
1010
1052
    @needs_read_lock
1011
1053
    def revision_tree(self, revision_id):
1101
1143
                        _mod_revision.NULL_REVISION)))
1102
1144
                ghosts.append(rev_id)
1103
1145
            accepted_revisions.add(rev_id)
1104
 
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
 
1146
        updated = False
 
1147
        if (len(real_trees) == 1
 
1148
            and not ghosts
 
1149
            and self.branch.repository._format.fast_deltas
 
1150
            and isinstance(real_trees[0][1],
 
1151
                revisiontree.InventoryRevisionTree)
 
1152
            and self.get_parent_ids()):
 
1153
            rev_id, rev_tree = real_trees[0]
 
1154
            basis_id = self.get_parent_ids()[0]
 
1155
            # There are times when basis_tree won't be in
 
1156
            # self.branch.repository, (switch, for example)
 
1157
            try:
 
1158
                basis_tree = self.branch.repository.revision_tree(basis_id)
 
1159
            except errors.NoSuchRevision:
 
1160
                # Fall back to the set_parent_trees(), since we can't use
 
1161
                # _make_delta if we can't get the RevisionTree
 
1162
                pass
 
1163
            else:
 
1164
                delta = rev_tree.root_inventory._make_delta(
 
1165
                    basis_tree.root_inventory)
 
1166
                dirstate.update_basis_by_delta(delta, rev_id)
 
1167
                updated = True
 
1168
        if not updated:
 
1169
            dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1105
1170
        self._make_dirty(reset_inventory=False)
1106
1171
 
1107
1172
    def _set_root_id(self, file_id):
1127
1192
 
1128
1193
    def unlock(self):
1129
1194
        """Unlock in format 4 trees needs to write the entire dirstate."""
1130
 
        # do non-implementation specific cleanup
1131
 
        self._cleanup()
1132
 
 
1133
1195
        if self._control_files._lock_count == 1:
 
1196
            # do non-implementation specific cleanup
 
1197
            self._cleanup()
 
1198
 
1134
1199
            # eventually we should do signature checking during read locks for
1135
1200
            # dirstate updates.
1136
1201
            if self._control_files._lock_mode == 'w':
1235
1300
        # have to change the legacy inventory too.
1236
1301
        if self._inventory is not None:
1237
1302
            for file_id in file_ids:
1238
 
                self._inventory.remove_recursive_id(file_id)
 
1303
                if self._inventory.has_id(file_id):
 
1304
                    self._inventory.remove_recursive_id(file_id)
1239
1305
 
1240
1306
    @needs_tree_write_lock
1241
1307
    def rename_one(self, from_rel, to_rel, after=False):
1242
1308
        """See WorkingTree.rename_one"""
1243
1309
        self.flush()
1244
 
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1310
        super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1245
1311
 
1246
1312
    @needs_tree_write_lock
1247
1313
    def apply_inventory_delta(self, changes):
1273
1339
        # being created.
1274
1340
        self._inventory = None
1275
1341
        # generate a delta,
1276
 
        delta = inv._make_delta(self.inventory)
 
1342
        delta = inv._make_delta(self.root_inventory)
1277
1343
        # and apply it.
1278
1344
        self.apply_inventory_delta(delta)
1279
1345
        if had_inventory:
1280
1346
            self._inventory = inv
1281
1347
        self.flush()
1282
1348
 
 
1349
    @needs_tree_write_lock
 
1350
    def reset_state(self, revision_ids=None):
 
1351
        """Reset the state of the working tree.
 
1352
 
 
1353
        This does a hard-reset to a last-known-good state. This is a way to
 
1354
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
 
1355
        """
 
1356
        if revision_ids is None:
 
1357
            revision_ids = self.get_parent_ids()
 
1358
        if not revision_ids:
 
1359
            base_tree = self.branch.repository.revision_tree(
 
1360
                _mod_revision.NULL_REVISION)
 
1361
            trees = []
 
1362
        else:
 
1363
            trees = zip(revision_ids,
 
1364
                        self.branch.repository.revision_trees(revision_ids))
 
1365
            base_tree = trees[0][1]
 
1366
        state = self.current_dirstate()
 
1367
        # We don't support ghosts yet
 
1368
        state.set_state_from_scratch(base_tree.root_inventory, trees, [])
 
1369
 
1283
1370
 
1284
1371
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1285
1372
 
1290
1377
        """See dirstate.SHA1Provider.sha1()."""
1291
1378
        filters = self.tree._content_filter_stack(
1292
1379
            self.tree.relpath(osutils.safe_unicode(abspath)))
1293
 
        return internal_size_sha_file_byname(abspath, filters)[1]
 
1380
        return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1294
1381
 
1295
1382
    def stat_and_sha1(self, abspath):
1296
1383
        """See dirstate.SHA1Provider.stat_and_sha1()."""
1300
1387
        try:
1301
1388
            statvalue = os.fstat(file_obj.fileno())
1302
1389
            if filters:
1303
 
                file_obj = filtered_input_file(file_obj, filters)
 
1390
                file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1304
1391
            sha1 = osutils.size_sha_file(file_obj)[1]
1305
1392
        finally:
1306
1393
            file_obj.close()
1317
1404
    def _file_content_summary(self, path, stat_result):
1318
1405
        # This is to support the somewhat obsolete path_content_summary method
1319
1406
        # with content filtering: see
1320
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
 
1407
        # <https://bugs.launchpad.net/bzr/+bug/415508>.
1321
1408
        #
1322
1409
        # If the dirstate cache is up to date and knows the hash and size,
1323
1410
        # return that.
1336
1423
class WorkingTree4(DirStateWorkingTree):
1337
1424
    """This is the Format 4 working tree.
1338
1425
 
1339
 
    This differs from WorkingTree3 by:
 
1426
    This differs from WorkingTree by:
1340
1427
     - Having a consolidated internal dirstate, stored in a
1341
1428
       randomly-accessible sorted file on disk.
1342
1429
     - Not having a regular inventory attribute.  One can be synthesized
1370
1457
        return views.PathBasedViews(self)
1371
1458
 
1372
1459
 
1373
 
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
 
1460
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
 
1461
 
 
1462
    missing_parent_conflicts = True
 
1463
 
 
1464
    supports_versioned_directories = True
 
1465
 
 
1466
    _lock_class = LockDir
 
1467
    _lock_file_name = 'lock'
 
1468
 
 
1469
    def _open_control_files(self, a_bzrdir):
 
1470
        transport = a_bzrdir.get_workingtree_transport(None)
 
1471
        return LockableFiles(transport, self._lock_file_name,
 
1472
                             self._lock_class)
1374
1473
 
1375
1474
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1376
1475
                   accelerator_tree=None, hardlink=False):
1377
1476
        """See WorkingTreeFormat.initialize().
1378
1477
 
1379
1478
        :param revision_id: allows creating a working tree at a different
1380
 
        revision than the branch is at.
 
1479
            revision than the branch is at.
1381
1480
        :param accelerator_tree: A tree which can be used for retrieving file
1382
1481
            contents more quickly than the revision tree, i.e. a workingtree.
1383
1482
            The revision tree will be used for cases where accelerator_tree's
1394
1493
        control_files = self._open_control_files(a_bzrdir)
1395
1494
        control_files.create_lock()
1396
1495
        control_files.lock_write()
1397
 
        transport.put_bytes('format', self.get_format_string(),
 
1496
        transport.put_bytes('format', self.as_string(),
1398
1497
            mode=a_bzrdir._get_file_mode())
1399
1498
        if from_branch is not None:
1400
1499
            branch = from_branch
1460
1559
                transform.build_tree(basis, wt, accelerator_tree,
1461
1560
                                     hardlink=hardlink,
1462
1561
                                     delta_from_tree=delta_from_tree)
 
1562
                for hook in MutableTree.hooks['post_build_tree']:
 
1563
                    hook(wt)
1463
1564
            finally:
1464
1565
                basis.unlock()
1465
1566
        finally:
1476
1577
        :param wt: the WorkingTree object
1477
1578
        """
1478
1579
 
 
1580
    def open(self, a_bzrdir, _found=False):
 
1581
        """Return the WorkingTree object for a_bzrdir
 
1582
 
 
1583
        _found is a private parameter, do not use it. It is used to indicate
 
1584
               if format probing has already been done.
 
1585
        """
 
1586
        if not _found:
 
1587
            # we are being called directly and must probe.
 
1588
            raise NotImplementedError
 
1589
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1590
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1591
        wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1592
        return wt
 
1593
 
1479
1594
    def _open(self, a_bzrdir, control_files):
1480
1595
        """Open the tree itself.
1481
1596
 
1506
1621
    This format:
1507
1622
        - exists within a metadir controlling .bzr
1508
1623
        - includes an explicit version marker for the workingtree control
1509
 
          files, separate from the BzrDir format
 
1624
          files, separate from the ControlDir format
1510
1625
        - modifies the hash cache format
1511
1626
        - is new in bzr 0.15
1512
1627
        - uses a LockDir to guard access to it.
1516
1631
 
1517
1632
    _tree_class = WorkingTree4
1518
1633
 
1519
 
    def get_format_string(self):
 
1634
    @classmethod
 
1635
    def get_format_string(cls):
1520
1636
        """See WorkingTreeFormat.get_format_string()."""
1521
1637
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1522
1638
 
1533
1649
 
1534
1650
    _tree_class = WorkingTree5
1535
1651
 
1536
 
    def get_format_string(self):
 
1652
    @classmethod
 
1653
    def get_format_string(cls):
1537
1654
        """See WorkingTreeFormat.get_format_string()."""
1538
1655
        return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1539
1656
 
1553
1670
 
1554
1671
    _tree_class = WorkingTree6
1555
1672
 
1556
 
    def get_format_string(self):
 
1673
    @classmethod
 
1674
    def get_format_string(cls):
1557
1675
        """See WorkingTreeFormat.get_format_string()."""
1558
1676
        return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1559
1677
 
1572
1690
        return True
1573
1691
 
1574
1692
 
1575
 
class DirStateRevisionTree(Tree):
 
1693
class DirStateRevisionTree(InventoryTree):
1576
1694
    """A revision tree pulling the inventory from a dirstate.
1577
1695
    
1578
1696
    Note that this is one of the historical (ie revision) trees cached in the
1597
1715
    def annotate_iter(self, file_id,
1598
1716
                      default_revision=_mod_revision.CURRENT_REVISION):
1599
1717
        """See Tree.annotate_iter"""
1600
 
        text_key = (file_id, self.inventory[file_id].revision)
 
1718
        text_key = (file_id, self.get_file_revision(file_id))
1601
1719
        annotations = self._repository.texts.annotate(text_key)
1602
1720
        return [(key[-1], line) for (key, line) in annotations]
1603
1721
 
1604
 
    def _get_ancestors(self, default_revision):
1605
 
        return set(self._repository.get_ancestry(self._revision_id,
1606
 
                                                 topo_sorted=False))
1607
1722
    def _comparison_data(self, entry, path):
1608
1723
        """See Tree._comparison_data."""
1609
1724
        if entry is None:
1725
1840
                elif kind == 'directory':
1726
1841
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1727
1842
                elif kind == 'symlink':
1728
 
                    inv_entry.executable = False
1729
 
                    inv_entry.text_size = None
1730
1843
                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1731
1844
                elif kind == 'tree-reference':
1732
1845
                    inv_entry.reference_revision = fingerprint or None
1752
1865
        # Make sure the file exists
1753
1866
        entry = self._get_entry(file_id, path=path)
1754
1867
        if entry == (None, None): # do we raise?
1755
 
            return None
 
1868
            raise errors.NoSuchId(self, file_id)
1756
1869
        parent_index = self._get_parent_index()
1757
1870
        last_changed_revision = entry[1][parent_index][4]
1758
1871
        try:
1769
1882
            return parent_details[1]
1770
1883
        return None
1771
1884
 
 
1885
    @needs_read_lock
 
1886
    def get_file_revision(self, file_id):
 
1887
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1888
        return inv[inv_file_id].revision
 
1889
 
1772
1890
    def get_file(self, file_id, path=None):
1773
1891
        return StringIO(self.get_file_text(file_id))
1774
1892
 
1775
1893
    def get_file_size(self, file_id):
1776
1894
        """See Tree.get_file_size"""
1777
 
        return self.inventory[file_id].text_size
 
1895
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1896
        return inv[inv_file_id].text_size
1778
1897
 
1779
1898
    def get_file_text(self, file_id, path=None):
1780
1899
        _, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1781
1900
        return ''.join(content)
1782
1901
 
1783
1902
    def get_reference_revision(self, file_id, path=None):
1784
 
        return self.inventory[file_id].reference_revision
 
1903
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1904
        return inv[inv_file_id].reference_revision
1785
1905
 
1786
1906
    def iter_files_bytes(self, desired_files):
1787
1907
        """See Tree.iter_files_bytes.
1797
1917
                                       identifier))
1798
1918
        return self._repository.iter_files_bytes(repo_desired_files)
1799
1919
 
1800
 
    def get_symlink_target(self, file_id):
 
1920
    def get_symlink_target(self, file_id, path=None):
1801
1921
        entry = self._get_entry(file_id=file_id)
1802
1922
        parent_index = self._get_parent_index()
1803
1923
        if entry[1][parent_index][0] != 'l':
1811
1931
        """Return the revision id for this tree."""
1812
1932
        return self._revision_id
1813
1933
 
1814
 
    def _get_inventory(self):
 
1934
    def _get_root_inventory(self):
1815
1935
        if self._inventory is not None:
1816
1936
            return self._inventory
1817
1937
        self._must_be_locked()
1818
1938
        self._generate_inventory()
1819
1939
        return self._inventory
1820
1940
 
 
1941
    root_inventory = property(_get_root_inventory,
 
1942
                         doc="Inventory of this Tree")
 
1943
 
 
1944
    @deprecated_method(deprecated_in((2, 5, 0)))
 
1945
    def _get_inventory(self):
 
1946
        return self.root_inventory
 
1947
 
1821
1948
    inventory = property(_get_inventory,
1822
1949
                         doc="Inventory of this Tree")
1823
1950
 
1841
1968
 
1842
1969
    def path_content_summary(self, path):
1843
1970
        """See Tree.path_content_summary."""
1844
 
        id = self.inventory.path2id(path)
1845
 
        if id is None:
 
1971
        inv, inv_file_id = self._path2inv_file_id(path)
 
1972
        if inv_file_id is None:
1846
1973
            return ('missing', None, None, None)
1847
 
        entry = self._inventory[id]
 
1974
        entry = inv[inv_file_id]
1848
1975
        kind = entry.kind
1849
1976
        if kind == 'file':
1850
1977
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
1854
1981
            return (kind, None, None, None)
1855
1982
 
1856
1983
    def is_executable(self, file_id, path=None):
1857
 
        ie = self.inventory[file_id]
 
1984
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1985
        ie = inv[inv_file_id]
1858
1986
        if ie.kind != "file":
1859
 
            return None
 
1987
            return False
1860
1988
        return ie.executable
1861
1989
 
 
1990
    def is_locked(self):
 
1991
        return self._locked
 
1992
 
1862
1993
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1863
1994
        # We use a standard implementation, because DirStateRevisionTree is
1864
1995
        # dealing with one of the parents of the current state
1865
 
        inv = self._get_inventory()
1866
1996
        if from_dir is None:
 
1997
            inv = self.root_inventory
1867
1998
            from_dir_id = None
1868
1999
        else:
1869
 
            from_dir_id = inv.path2id(from_dir)
 
2000
            inv, from_dir_id = self._path2inv_file_id(from_dir)
1870
2001
            if from_dir_id is None:
1871
2002
                # Directory not versioned
1872
2003
                return
 
2004
        # FIXME: Support nested trees
1873
2005
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1874
2006
        if inv.root is not None and not include_root and from_dir is None:
1875
2007
            entries.next()
1877
2009
            yield path, 'V', entry.kind, entry.file_id, entry
1878
2010
 
1879
2011
    def lock_read(self):
1880
 
        """Lock the tree for a set of operations."""
 
2012
        """Lock the tree for a set of operations.
 
2013
 
 
2014
        :return: A bzrlib.lock.LogicalLockResult.
 
2015
        """
1881
2016
        if not self._locked:
1882
2017
            self._repository.lock_read()
1883
2018
            if self._dirstate._lock_token is None:
1884
2019
                self._dirstate.lock_read()
1885
2020
                self._dirstate_locked = True
1886
2021
        self._locked += 1
 
2022
        return LogicalLockResult(self.unlock)
1887
2023
 
1888
2024
    def _must_be_locked(self):
1889
2025
        if not self._locked:
1921
2057
        # So for now, we just build up the parent inventory, and extract
1922
2058
        # it the same way RevisionTree does.
1923
2059
        _directory = 'directory'
1924
 
        inv = self._get_inventory()
 
2060
        inv = self._get_root_inventory()
1925
2061
        top_id = inv.path2id(prefix)
1926
2062
        if top_id is None:
1927
2063
            pending = []
1968
2104
    def make_source_parent_tree(source, target):
1969
2105
        """Change the source tree into a parent of the target."""
1970
2106
        revid = source.commit('record tree')
1971
 
        target.branch.repository.fetch(source.branch.repository, revid)
 
2107
        target.branch.fetch(source.branch, revid)
1972
2108
        target.set_parent_ids([revid])
1973
2109
        return target.basis_tree(), target
1974
2110
 
2066
2202
                path_entries = state._entries_for_path(path)
2067
2203
                if not path_entries:
2068
2204
                    # this specified path is not present at all: error
2069
 
                    not_versioned.append(path)
 
2205
                    not_versioned.append(path.decode('utf-8'))
2070
2206
                    continue
2071
2207
                found_versioned = False
2072
2208
                # for each id at this path
2080
2216
                if not found_versioned:
2081
2217
                    # none of the indexes was not 'absent' at all ids for this
2082
2218
                    # path.
2083
 
                    not_versioned.append(path)
 
2219
                    not_versioned.append(path.decode('utf-8'))
2084
2220
            if len(not_versioned) > 0:
2085
2221
                raise errors.PathsNotVersionedError(not_versioned)
2086
2222
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
2153
2289
    def update_format(self, tree):
2154
2290
        """Change the format marker."""
2155
2291
        tree._transport.put_bytes('format',
2156
 
            self.target_format.get_format_string(),
 
2292
            self.target_format.as_string(),
2157
2293
            mode=tree.bzrdir._get_file_mode())
2158
2294
 
2159
2295
 
2176
2312
    def update_format(self, tree):
2177
2313
        """Change the format marker."""
2178
2314
        tree._transport.put_bytes('format',
2179
 
            self.target_format.get_format_string(),
 
2315
            self.target_format.as_string(),
2180
2316
            mode=tree.bzrdir._get_file_mode())
2181
2317
 
2182
2318
 
2205
2341
    def update_format(self, tree):
2206
2342
        """Change the format marker."""
2207
2343
        tree._transport.put_bytes('format',
2208
 
            self.target_format.get_format_string(),
 
2344
            self.target_format.as_string(),
2209
2345
            mode=tree.bzrdir._get_file_mode())