/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/workingtree_4.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-22 00:56:52 UTC
  • mfrom: (6621.2.26 py3_pokes)
  • Revision ID: jelmer@jelmer.uk-20170522005652-yjahcr9hwmjkno7n
Merge Python3 porting work ('py3 pokes')

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2012 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 cStringIO import StringIO
 
25
from __future__ import absolute_import
 
26
 
26
27
import os
27
28
import sys
28
29
 
29
 
from bzrlib.lazy_import import lazy_import
 
30
from .lazy_import import lazy_import
30
31
lazy_import(globals(), """
31
32
import errno
32
33
import stat
33
34
 
34
 
import bzrlib
35
 
from bzrlib import (
 
35
from breezy import (
36
36
    bzrdir,
37
37
    cache_utf8,
 
38
    cleanup,
 
39
    config,
 
40
    conflicts as _mod_conflicts,
 
41
    controldir,
38
42
    debug,
39
43
    dirstate,
40
44
    errors,
 
45
    filters as _mod_filters,
41
46
    generate_ids,
42
47
    osutils,
43
48
    revision as _mod_revision,
46
51
    transform,
47
52
    views,
48
53
    )
49
 
import bzrlib.branch
50
 
import bzrlib.ui
51
54
""")
52
55
 
53
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
54
 
from bzrlib.filters import filtered_input_file, internal_size_sha_file_byname
55
 
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
56
 
from bzrlib.lock import LogicalLockResult
57
 
from bzrlib.mutabletree import needs_tree_write_lock
58
 
from bzrlib.osutils import (
 
56
from .decorators import needs_read_lock, needs_write_lock
 
57
from .inventory import Inventory, ROOT_ID, entry_factory
 
58
from .lock import LogicalLockResult
 
59
from .lockable_files import LockableFiles
 
60
from .lockdir import LockDir
 
61
from .mutabletree import (
 
62
    MutableTree,
 
63
    needs_tree_write_lock,
 
64
    )
 
65
from .osutils import (
59
66
    file_kind,
60
67
    isdir,
61
68
    pathjoin,
62
69
    realpath,
63
70
    safe_unicode,
64
71
    )
65
 
from bzrlib.trace import mutter
66
 
from bzrlib.transport.local import LocalTransport
67
 
from bzrlib.tree import InterTree
68
 
from bzrlib.tree import Tree
69
 
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
70
 
 
71
 
 
72
 
class DirStateWorkingTree(WorkingTree3):
 
72
from .sixish import (
 
73
    BytesIO,
 
74
    )
 
75
from .symbol_versioning import (
 
76
    deprecated_in,
 
77
    deprecated_method,
 
78
    )
 
79
from .transport.local import LocalTransport
 
80
from .tree import (
 
81
    InterTree,
 
82
    InventoryTree,
 
83
    )
 
84
from .workingtree import (
 
85
    InventoryWorkingTree,
 
86
    WorkingTree,
 
87
    WorkingTreeFormatMetaDir,
 
88
    )
 
89
 
 
90
 
 
91
class DirStateWorkingTree(InventoryWorkingTree):
 
92
 
73
93
    def __init__(self, basedir,
74
94
                 branch,
75
95
                 _control_files=None,
85
105
        self._format = _format
86
106
        self.bzrdir = _bzrdir
87
107
        basedir = safe_unicode(basedir)
88
 
        mutter("opening working tree %r", basedir)
 
108
        trace.mutter("opening working tree %r", basedir)
89
109
        self._branch = branch
90
110
        self.basedir = realpath(basedir)
91
111
        # if branch is at our basedir and is a format 6 or less
125
145
            state.add(f, file_id, kind, None, '')
126
146
        self._make_dirty(reset_inventory=True)
127
147
 
 
148
    def _get_check_refs(self):
 
149
        """Return the references needed to perform a check of this tree."""
 
150
        return [('trees', self.last_revision())]
 
151
 
128
152
    def _make_dirty(self, reset_inventory):
129
153
        """Make the tree state dirty.
130
154
 
182
206
 
183
207
    def _comparison_data(self, entry, path):
184
208
        kind, executable, stat_value = \
185
 
            WorkingTree3._comparison_data(self, entry, path)
 
209
            WorkingTree._comparison_data(self, entry, path)
186
210
        # it looks like a plain directory, but it's really a reference -- see
187
211
        # also kind()
188
212
        if (self._repo_supports_tree_reference and kind == 'directory'
194
218
    def commit(self, message=None, revprops=None, *args, **kwargs):
195
219
        # mark the tree as dirty post commit - commit
196
220
        # can change the current versioned list by doing deletes.
197
 
        result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
 
221
        result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
198
222
        self._make_dirty(reset_inventory=True)
199
223
        return result
200
224
 
219
243
        local_path = self.bzrdir.get_workingtree_transport(None
220
244
            ).local_abspath('dirstate')
221
245
        self._dirstate = dirstate.DirState.on_file(local_path,
222
 
            self._sha1_provider())
 
246
            self._sha1_provider(), self._worth_saving_limit())
223
247
        return self._dirstate
224
248
 
225
249
    def _sha1_provider(self):
234
258
        else:
235
259
            return None
236
260
 
 
261
    def _worth_saving_limit(self):
 
262
        """How many hash changes are ok before we must save the dirstate.
 
263
 
 
264
        :return: an integer. -1 means never save.
 
265
        """
 
266
        conf = self.get_config_stack()
 
267
        return conf.get('bzr.workingtree.worth_saving_limit')
 
268
 
237
269
    def filter_unversioned_files(self, paths):
238
270
        """Filter out paths that are versioned.
239
271
 
369
401
        state = self.current_dirstate()
370
402
        if stat_value is None:
371
403
            try:
372
 
                stat_value = os.lstat(file_abspath)
373
 
            except OSError, e:
 
404
                stat_value = osutils.lstat(file_abspath)
 
405
            except OSError as e:
374
406
                if e.errno == errno.ENOENT:
375
407
                    return None
376
408
                else:
390
422
                return link_or_sha1
391
423
        return None
392
424
 
393
 
    def _get_inventory(self):
 
425
    def _get_root_inventory(self):
394
426
        """Get the inventory for the tree. This is only valid within a lock."""
395
427
        if 'evil' in debug.debug_flags:
396
428
            trace.mutter_callsite(2,
401
433
        self._generate_inventory()
402
434
        return self._inventory
403
435
 
 
436
    @deprecated_method(deprecated_in((2, 5, 0)))
 
437
    def _get_inventory(self):
 
438
        return self.root_inventory
 
439
 
404
440
    inventory = property(_get_inventory,
405
441
                         doc="Inventory of this Tree")
406
442
 
 
443
    root_inventory = property(_get_root_inventory,
 
444
        "Root inventory of this tree")
 
445
 
407
446
    @needs_read_lock
408
447
    def get_parent_ids(self):
409
448
        """See Tree.get_parent_ids.
456
495
            return False # Missing entries are not executable
457
496
        return entry[1][0][3] # Executable?
458
497
 
459
 
    if not osutils.supports_executable():
460
 
        def is_executable(self, file_id, path=None):
461
 
            """Test if a file is executable or not.
 
498
    def is_executable(self, file_id, path=None):
 
499
        """Test if a file is executable or not.
462
500
 
463
 
            Note: The caller is expected to take a read-lock before calling this.
464
 
            """
 
501
        Note: The caller is expected to take a read-lock before calling this.
 
502
        """
 
503
        if not self._supports_executable():
465
504
            entry = self._get_entry(file_id=file_id, path=path)
466
505
            if entry == (None, None):
467
506
                return False
468
507
            return entry[1][0][3]
469
 
 
470
 
        _is_executable_from_path_and_stat = \
471
 
            _is_executable_from_path_and_stat_from_basis
472
 
    else:
473
 
        def is_executable(self, file_id, path=None):
474
 
            """Test if a file is executable or not.
475
 
 
476
 
            Note: The caller is expected to take a read-lock before calling this.
477
 
            """
 
508
        else:
478
509
            self._must_be_locked()
479
510
            if not path:
480
511
                path = self.id2path(file_id)
481
 
            mode = os.lstat(self.abspath(path)).st_mode
 
512
            mode = osutils.lstat(self.abspath(path)).st_mode
482
513
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
483
514
 
484
515
    def all_file_ids(self):
528
559
                # path is missing on disk.
529
560
                continue
530
561
 
531
 
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
 
562
    def _observed_sha1(self, file_id, path, sha_and_stat):
532
563
        """See MutableTree._observed_sha1."""
533
564
        state = self.current_dirstate()
534
565
        entry = self._get_entry(file_id=file_id, path=path)
535
 
        state._observed_sha1(entry, sha1, statvalue)
 
566
        state._observed_sha1(entry, *sha_and_stat)
536
567
 
537
568
    def kind(self, file_id):
538
569
        """Return the kind of a file.
570
601
    def lock_read(self):
571
602
        """See Branch.lock_read, and WorkingTree.unlock.
572
603
 
573
 
        :return: A bzrlib.lock.LogicalLockResult.
 
604
        :return: A breezy.lock.LogicalLockResult.
574
605
        """
575
606
        self.branch.lock_read()
576
607
        try:
616
647
    def lock_tree_write(self):
617
648
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
618
649
 
619
 
        :return: A bzrlib.lock.LogicalLockResult.
 
650
        :return: A breezy.lock.LogicalLockResult.
620
651
        """
621
652
        self.branch.lock_read()
622
653
        return self._lock_self_write()
624
655
    def lock_write(self):
625
656
        """See MutableTree.lock_write, and WorkingTree.unlock.
626
657
 
627
 
        :return: A bzrlib.lock.LogicalLockResult.
 
658
        :return: A breezy.lock.LogicalLockResult.
628
659
        """
629
660
        self.branch.lock_write()
630
661
        return self._lock_self_write()
664
695
 
665
696
        if self._inventory is not None:
666
697
            update_inventory = True
667
 
            inv = self.inventory
 
698
            inv = self.root_inventory
668
699
            to_dir_id = to_entry[0][2]
669
700
            to_dir_ie = inv[to_dir_id]
670
701
        else:
671
702
            update_inventory = False
672
703
 
673
 
        rollbacks = []
 
704
        # GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
 
705
        # missing those added here, but there's also no test coverage for this.
 
706
        rollbacks = cleanup.ObjectWithCleanups()
674
707
        def move_one(old_entry, from_path_utf8, minikind, executable,
675
708
                     fingerprint, packed_stat, size,
676
709
                     to_block, to_key, to_path_utf8):
677
710
            state._make_absent(old_entry)
678
711
            from_key = old_entry[0]
679
 
            rollbacks.append(
680
 
                lambda:state.update_minimal(from_key,
681
 
                    minikind,
682
 
                    executable=executable,
683
 
                    fingerprint=fingerprint,
684
 
                    packed_stat=packed_stat,
685
 
                    size=size,
686
 
                    path_utf8=from_path_utf8))
 
712
            rollbacks.add_cleanup(
 
713
                state.update_minimal,
 
714
                from_key,
 
715
                minikind,
 
716
                executable=executable,
 
717
                fingerprint=fingerprint,
 
718
                packed_stat=packed_stat,
 
719
                size=size,
 
720
                path_utf8=from_path_utf8)
687
721
            state.update_minimal(to_key,
688
722
                    minikind,
689
723
                    executable=executable,
693
727
                    path_utf8=to_path_utf8)
694
728
            added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
695
729
            new_entry = to_block[1][added_entry_index]
696
 
            rollbacks.append(lambda:state._make_absent(new_entry))
 
730
            rollbacks.add_cleanup(state._make_absent, new_entry)
697
731
 
698
732
        for from_rel in from_paths:
699
733
            # from_rel is 'pathinroot/foo/bar'
738
772
                elif not after:
739
773
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
740
774
 
741
 
            rollbacks = []
742
 
            def rollback_rename():
743
 
                """A single rename has failed, roll it back."""
744
 
                # roll back everything, even if we encounter trouble doing one
745
 
                # of them.
746
 
                #
747
 
                # TODO: at least log the other exceptions rather than just
748
 
                # losing them mbp 20070307
749
 
                exc_info = None
750
 
                for rollback in reversed(rollbacks):
751
 
                    try:
752
 
                        rollback()
753
 
                    except Exception, e:
754
 
                        exc_info = sys.exc_info()
755
 
                if exc_info:
756
 
                    raise exc_info[0], exc_info[1], exc_info[2]
757
 
 
758
775
            # perform the disk move first - its the most likely failure point.
759
776
            if move_file:
760
777
                from_rel_abs = self.abspath(from_rel)
761
778
                to_rel_abs = self.abspath(to_rel)
762
779
                try:
763
780
                    osutils.rename(from_rel_abs, to_rel_abs)
764
 
                except OSError, e:
 
781
                except OSError as e:
765
782
                    raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
766
 
                rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
 
783
                rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
767
784
            try:
768
785
                # perform the rename in the inventory next if needed: its easy
769
786
                # to rollback
772
789
                    from_entry = inv[from_id]
773
790
                    current_parent = from_entry.parent_id
774
791
                    inv.rename(from_id, to_dir_id, from_tail)
775
 
                    rollbacks.append(
776
 
                        lambda: inv.rename(from_id, current_parent, from_tail))
 
792
                    rollbacks.add_cleanup(
 
793
                        inv.rename, from_id, current_parent, from_tail)
777
794
                # finally do the rename in the dirstate, which is a little
778
795
                # tricky to rollback, but least likely to need it.
779
796
                old_block_index, old_entry_index, dir_present, file_present = \
847
864
                                                to_path_utf8)
848
865
                    update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
849
866
            except:
850
 
                rollback_rename()
 
867
                rollbacks.cleanup_now()
851
868
                raise
852
869
            result.append((from_rel, to_rel))
853
 
            state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
 
870
            state._mark_modified()
854
871
            self._make_dirty(reset_inventory=False)
855
872
 
856
873
        return result
866
883
    @needs_read_lock
867
884
    def path2id(self, path):
868
885
        """Return the id for path in this tree."""
 
886
        if isinstance(path, list):
 
887
            if path == []:
 
888
                path = [""]
 
889
            path = osutils.pathjoin(*path)
869
890
        path = path.strip('/')
870
891
        entry = self._get_entry(path=path)
871
892
        if entry == (None, None):
949
970
                    all_versioned = False
950
971
                    break
951
972
            if not all_versioned:
952
 
                raise errors.PathsNotVersionedError(paths)
 
973
                raise errors.PathsNotVersionedError(
 
974
                    [p.decode('utf-8') for p in paths])
953
975
        # -- remove redundancy in supplied paths to prevent over-scanning --
954
976
        search_paths = osutils.minimum_path_selection(paths)
955
977
        # sketch:
1004
1026
            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
1005
1027
            for dir_name in split_paths:
1006
1028
                if dir_name not in found_dir_names:
1007
 
                    raise errors.PathsNotVersionedError(paths)
 
1029
                    raise errors.PathsNotVersionedError(
 
1030
                        [p.decode('utf-8') for p in paths])
1008
1031
 
1009
1032
        for dir_name_id, trees_info in found.iteritems():
1010
1033
            for index in search_indexes:
1017
1040
 
1018
1041
        This is a meaningless operation for dirstate, but we obey it anyhow.
1019
1042
        """
1020
 
        return self.inventory
 
1043
        return self.root_inventory
1021
1044
 
1022
1045
    @needs_read_lock
1023
1046
    def revision_tree(self, revision_id):
1113
1136
                        _mod_revision.NULL_REVISION)))
1114
1137
                ghosts.append(rev_id)
1115
1138
            accepted_revisions.add(rev_id)
1116
 
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
 
1139
        updated = False
 
1140
        if (len(real_trees) == 1
 
1141
            and not ghosts
 
1142
            and self.branch.repository._format.fast_deltas
 
1143
            and isinstance(real_trees[0][1],
 
1144
                revisiontree.InventoryRevisionTree)
 
1145
            and self.get_parent_ids()):
 
1146
            rev_id, rev_tree = real_trees[0]
 
1147
            basis_id = self.get_parent_ids()[0]
 
1148
            # There are times when basis_tree won't be in
 
1149
            # self.branch.repository, (switch, for example)
 
1150
            try:
 
1151
                basis_tree = self.branch.repository.revision_tree(basis_id)
 
1152
            except errors.NoSuchRevision:
 
1153
                # Fall back to the set_parent_trees(), since we can't use
 
1154
                # _make_delta if we can't get the RevisionTree
 
1155
                pass
 
1156
            else:
 
1157
                delta = rev_tree.root_inventory._make_delta(
 
1158
                    basis_tree.root_inventory)
 
1159
                dirstate.update_basis_by_delta(delta, rev_id)
 
1160
                updated = True
 
1161
        if not updated:
 
1162
            dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1117
1163
        self._make_dirty(reset_inventory=False)
1118
1164
 
1119
1165
    def _set_root_id(self, file_id):
1139
1185
 
1140
1186
    def unlock(self):
1141
1187
        """Unlock in format 4 trees needs to write the entire dirstate."""
1142
 
        # do non-implementation specific cleanup
1143
 
        self._cleanup()
1144
 
 
1145
1188
        if self._control_files._lock_count == 1:
 
1189
            # do non-implementation specific cleanup
 
1190
            self._cleanup()
 
1191
 
1146
1192
            # eventually we should do signature checking during read locks for
1147
1193
            # dirstate updates.
1148
1194
            if self._control_files._lock_mode == 'w':
1247
1293
        # have to change the legacy inventory too.
1248
1294
        if self._inventory is not None:
1249
1295
            for file_id in file_ids:
1250
 
                self._inventory.remove_recursive_id(file_id)
 
1296
                if self._inventory.has_id(file_id):
 
1297
                    self._inventory.remove_recursive_id(file_id)
1251
1298
 
1252
1299
    @needs_tree_write_lock
1253
1300
    def rename_one(self, from_rel, to_rel, after=False):
1254
1301
        """See WorkingTree.rename_one"""
1255
1302
        self.flush()
1256
 
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1303
        super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1257
1304
 
1258
1305
    @needs_tree_write_lock
1259
1306
    def apply_inventory_delta(self, changes):
1285
1332
        # being created.
1286
1333
        self._inventory = None
1287
1334
        # generate a delta,
1288
 
        delta = inv._make_delta(self.inventory)
 
1335
        delta = inv._make_delta(self.root_inventory)
1289
1336
        # and apply it.
1290
1337
        self.apply_inventory_delta(delta)
1291
1338
        if had_inventory:
1292
1339
            self._inventory = inv
1293
1340
        self.flush()
1294
1341
 
 
1342
    @needs_tree_write_lock
 
1343
    def reset_state(self, revision_ids=None):
 
1344
        """Reset the state of the working tree.
 
1345
 
 
1346
        This does a hard-reset to a last-known-good state. This is a way to
 
1347
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
 
1348
        """
 
1349
        if revision_ids is None:
 
1350
            revision_ids = self.get_parent_ids()
 
1351
        if not revision_ids:
 
1352
            base_tree = self.branch.repository.revision_tree(
 
1353
                _mod_revision.NULL_REVISION)
 
1354
            trees = []
 
1355
        else:
 
1356
            trees = zip(revision_ids,
 
1357
                        self.branch.repository.revision_trees(revision_ids))
 
1358
            base_tree = trees[0][1]
 
1359
        state = self.current_dirstate()
 
1360
        # We don't support ghosts yet
 
1361
        state.set_state_from_scratch(base_tree.root_inventory, trees, [])
 
1362
 
1295
1363
 
1296
1364
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1297
1365
 
1302
1370
        """See dirstate.SHA1Provider.sha1()."""
1303
1371
        filters = self.tree._content_filter_stack(
1304
1372
            self.tree.relpath(osutils.safe_unicode(abspath)))
1305
 
        return internal_size_sha_file_byname(abspath, filters)[1]
 
1373
        return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1306
1374
 
1307
1375
    def stat_and_sha1(self, abspath):
1308
1376
        """See dirstate.SHA1Provider.stat_and_sha1()."""
1312
1380
        try:
1313
1381
            statvalue = os.fstat(file_obj.fileno())
1314
1382
            if filters:
1315
 
                file_obj = filtered_input_file(file_obj, filters)
 
1383
                file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1316
1384
            sha1 = osutils.size_sha_file(file_obj)[1]
1317
1385
        finally:
1318
1386
            file_obj.close()
1329
1397
    def _file_content_summary(self, path, stat_result):
1330
1398
        # This is to support the somewhat obsolete path_content_summary method
1331
1399
        # with content filtering: see
1332
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
 
1400
        # <https://bugs.launchpad.net/bzr/+bug/415508>.
1333
1401
        #
1334
1402
        # If the dirstate cache is up to date and knows the hash and size,
1335
1403
        # return that.
1348
1416
class WorkingTree4(DirStateWorkingTree):
1349
1417
    """This is the Format 4 working tree.
1350
1418
 
1351
 
    This differs from WorkingTree3 by:
 
1419
    This differs from WorkingTree by:
1352
1420
     - Having a consolidated internal dirstate, stored in a
1353
1421
       randomly-accessible sorted file on disk.
1354
1422
     - Not having a regular inventory attribute.  One can be synthesized
1382
1450
        return views.PathBasedViews(self)
1383
1451
 
1384
1452
 
1385
 
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
 
1453
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
 
1454
 
 
1455
    missing_parent_conflicts = True
 
1456
 
 
1457
    supports_versioned_directories = True
 
1458
 
 
1459
    _lock_class = LockDir
 
1460
    _lock_file_name = 'lock'
 
1461
 
 
1462
    def _open_control_files(self, a_bzrdir):
 
1463
        transport = a_bzrdir.get_workingtree_transport(None)
 
1464
        return LockableFiles(transport, self._lock_file_name,
 
1465
                             self._lock_class)
1386
1466
 
1387
1467
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1388
1468
                   accelerator_tree=None, hardlink=False):
1389
1469
        """See WorkingTreeFormat.initialize().
1390
1470
 
1391
1471
        :param revision_id: allows creating a working tree at a different
1392
 
        revision than the branch is at.
 
1472
            revision than the branch is at.
1393
1473
        :param accelerator_tree: A tree which can be used for retrieving file
1394
1474
            contents more quickly than the revision tree, i.e. a workingtree.
1395
1475
            The revision tree will be used for cases where accelerator_tree's
1406
1486
        control_files = self._open_control_files(a_bzrdir)
1407
1487
        control_files.create_lock()
1408
1488
        control_files.lock_write()
1409
 
        transport.put_bytes('format', self.get_format_string(),
 
1489
        transport.put_bytes('format', self.as_string(),
1410
1490
            mode=a_bzrdir._get_file_mode())
1411
1491
        if from_branch is not None:
1412
1492
            branch = from_branch
1472
1552
                transform.build_tree(basis, wt, accelerator_tree,
1473
1553
                                     hardlink=hardlink,
1474
1554
                                     delta_from_tree=delta_from_tree)
 
1555
                for hook in MutableTree.hooks['post_build_tree']:
 
1556
                    hook(wt)
1475
1557
            finally:
1476
1558
                basis.unlock()
1477
1559
        finally:
1488
1570
        :param wt: the WorkingTree object
1489
1571
        """
1490
1572
 
 
1573
    def open(self, a_bzrdir, _found=False):
 
1574
        """Return the WorkingTree object for a_bzrdir
 
1575
 
 
1576
        _found is a private parameter, do not use it. It is used to indicate
 
1577
               if format probing has already been done.
 
1578
        """
 
1579
        if not _found:
 
1580
            # we are being called directly and must probe.
 
1581
            raise NotImplementedError
 
1582
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1583
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1584
        wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1585
        return wt
 
1586
 
1491
1587
    def _open(self, a_bzrdir, control_files):
1492
1588
        """Open the tree itself.
1493
1589
 
1506
1602
    def _get_matchingbzrdir(self):
1507
1603
        """Overrideable method to get a bzrdir for testing."""
1508
1604
        # please test against something that will let us do tree references
1509
 
        return bzrdir.format_registry.make_bzrdir(
1510
 
            'dirstate-with-subtree')
 
1605
        return controldir.format_registry.make_bzrdir(
 
1606
            'development-subtree')
1511
1607
 
1512
1608
    _matchingbzrdir = property(__get_matchingbzrdir)
1513
1609
 
1518
1614
    This format:
1519
1615
        - exists within a metadir controlling .bzr
1520
1616
        - includes an explicit version marker for the workingtree control
1521
 
          files, separate from the BzrDir format
 
1617
          files, separate from the ControlDir format
1522
1618
        - modifies the hash cache format
1523
1619
        - is new in bzr 0.15
1524
1620
        - uses a LockDir to guard access to it.
1528
1624
 
1529
1625
    _tree_class = WorkingTree4
1530
1626
 
1531
 
    def get_format_string(self):
 
1627
    @classmethod
 
1628
    def get_format_string(cls):
1532
1629
        """See WorkingTreeFormat.get_format_string()."""
1533
1630
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1534
1631
 
1545
1642
 
1546
1643
    _tree_class = WorkingTree5
1547
1644
 
1548
 
    def get_format_string(self):
 
1645
    @classmethod
 
1646
    def get_format_string(cls):
1549
1647
        """See WorkingTreeFormat.get_format_string()."""
1550
1648
        return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1551
1649
 
1565
1663
 
1566
1664
    _tree_class = WorkingTree6
1567
1665
 
1568
 
    def get_format_string(self):
 
1666
    @classmethod
 
1667
    def get_format_string(cls):
1569
1668
        """See WorkingTreeFormat.get_format_string()."""
1570
1669
        return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1571
1670
 
1583
1682
    def supports_views(self):
1584
1683
        return True
1585
1684
 
1586
 
 
1587
 
class DirStateRevisionTree(Tree):
 
1685
    def _get_matchingbzrdir(self):
 
1686
        """Overrideable method to get a bzrdir for testing."""
 
1687
        # We use 'development-subtree' instead of '2a', because we have a
 
1688
        # few tests that want to test tree references
 
1689
        return bzrdir.format_registry.make_bzrdir('development-subtree')
 
1690
 
 
1691
 
 
1692
class DirStateRevisionTree(InventoryTree):
1588
1693
    """A revision tree pulling the inventory from a dirstate.
1589
1694
    
1590
1695
    Note that this is one of the historical (ie revision) trees cached in the
1609
1714
    def annotate_iter(self, file_id,
1610
1715
                      default_revision=_mod_revision.CURRENT_REVISION):
1611
1716
        """See Tree.annotate_iter"""
1612
 
        text_key = (file_id, self.inventory[file_id].revision)
 
1717
        text_key = (file_id, self.get_file_revision(file_id))
1613
1718
        annotations = self._repository.texts.annotate(text_key)
1614
1719
        return [(key[-1], line) for (key, line) in annotations]
1615
1720
 
1616
 
    def _get_ancestors(self, default_revision):
1617
 
        return set(self._repository.get_ancestry(self._revision_id,
1618
 
                                                 topo_sorted=False))
1619
1721
    def _comparison_data(self, entry, path):
1620
1722
        """See Tree._comparison_data."""
1621
1723
        if entry is None:
1674
1776
        if path is not None:
1675
1777
            path = path.encode('utf8')
1676
1778
        parent_index = self._get_parent_index()
1677
 
        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
 
1779
        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id,
 
1780
            path_utf8=path)
1678
1781
 
1679
1782
    def _generate_inventory(self):
1680
1783
        """Create and set self.inventory from the dirstate object.
1737
1840
                elif kind == 'directory':
1738
1841
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1739
1842
                elif kind == 'symlink':
1740
 
                    inv_entry.executable = False
1741
 
                    inv_entry.text_size = None
1742
1843
                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1743
1844
                elif kind == 'tree-reference':
1744
1845
                    inv_entry.reference_revision = fingerprint or None
1764
1865
        # Make sure the file exists
1765
1866
        entry = self._get_entry(file_id, path=path)
1766
1867
        if entry == (None, None): # do we raise?
1767
 
            return None
 
1868
            raise errors.NoSuchId(self, file_id)
1768
1869
        parent_index = self._get_parent_index()
1769
1870
        last_changed_revision = entry[1][parent_index][4]
1770
1871
        try:
1781
1882
            return parent_details[1]
1782
1883
        return None
1783
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
 
1784
1890
    def get_file(self, file_id, path=None):
1785
 
        return StringIO(self.get_file_text(file_id))
 
1891
        return BytesIO(self.get_file_text(file_id))
1786
1892
 
1787
1893
    def get_file_size(self, file_id):
1788
1894
        """See Tree.get_file_size"""
1789
 
        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
1790
1897
 
1791
1898
    def get_file_text(self, file_id, path=None):
1792
 
        _, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1793
 
        return ''.join(content)
 
1899
        content = None
 
1900
        for _, content_iter in self.iter_files_bytes([(file_id, None)]):
 
1901
            if content is not None:
 
1902
                raise AssertionError('iter_files_bytes returned'
 
1903
                    ' too many entries')
 
1904
            # For each entry returned by iter_files_bytes, we must consume the
 
1905
            # content_iter before we step the files iterator.
 
1906
            content = ''.join(content_iter)
 
1907
        if content is None:
 
1908
            raise AssertionError('iter_files_bytes did not return'
 
1909
                ' the requested data')
 
1910
        return content
1794
1911
 
1795
1912
    def get_reference_revision(self, file_id, path=None):
1796
 
        return self.inventory[file_id].reference_revision
 
1913
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1914
        return inv[inv_file_id].reference_revision
1797
1915
 
1798
1916
    def iter_files_bytes(self, desired_files):
1799
1917
        """See Tree.iter_files_bytes.
1809
1927
                                       identifier))
1810
1928
        return self._repository.iter_files_bytes(repo_desired_files)
1811
1929
 
1812
 
    def get_symlink_target(self, file_id):
 
1930
    def get_symlink_target(self, file_id, path=None):
1813
1931
        entry = self._get_entry(file_id=file_id)
1814
1932
        parent_index = self._get_parent_index()
1815
1933
        if entry[1][parent_index][0] != 'l':
1823
1941
        """Return the revision id for this tree."""
1824
1942
        return self._revision_id
1825
1943
 
1826
 
    def _get_inventory(self):
 
1944
    def _get_root_inventory(self):
1827
1945
        if self._inventory is not None:
1828
1946
            return self._inventory
1829
1947
        self._must_be_locked()
1830
1948
        self._generate_inventory()
1831
1949
        return self._inventory
1832
1950
 
 
1951
    root_inventory = property(_get_root_inventory,
 
1952
                         doc="Inventory of this Tree")
 
1953
 
 
1954
    @deprecated_method(deprecated_in((2, 5, 0)))
 
1955
    def _get_inventory(self):
 
1956
        return self.root_inventory
 
1957
 
1833
1958
    inventory = property(_get_inventory,
1834
1959
                         doc="Inventory of this Tree")
1835
1960
 
1853
1978
 
1854
1979
    def path_content_summary(self, path):
1855
1980
        """See Tree.path_content_summary."""
1856
 
        id = self.inventory.path2id(path)
1857
 
        if id is None:
 
1981
        inv, inv_file_id = self._path2inv_file_id(path)
 
1982
        if inv_file_id is None:
1858
1983
            return ('missing', None, None, None)
1859
 
        entry = self._inventory[id]
 
1984
        entry = inv[inv_file_id]
1860
1985
        kind = entry.kind
1861
1986
        if kind == 'file':
1862
1987
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
1866
1991
            return (kind, None, None, None)
1867
1992
 
1868
1993
    def is_executable(self, file_id, path=None):
1869
 
        ie = self.inventory[file_id]
 
1994
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1995
        ie = inv[inv_file_id]
1870
1996
        if ie.kind != "file":
1871
 
            return None
 
1997
            return False
1872
1998
        return ie.executable
1873
1999
 
1874
2000
    def is_locked(self):
1877
2003
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1878
2004
        # We use a standard implementation, because DirStateRevisionTree is
1879
2005
        # dealing with one of the parents of the current state
1880
 
        inv = self._get_inventory()
1881
2006
        if from_dir is None:
 
2007
            inv = self.root_inventory
1882
2008
            from_dir_id = None
1883
2009
        else:
1884
 
            from_dir_id = inv.path2id(from_dir)
 
2010
            inv, from_dir_id = self._path2inv_file_id(from_dir)
1885
2011
            if from_dir_id is None:
1886
2012
                # Directory not versioned
1887
2013
                return
 
2014
        # FIXME: Support nested trees
1888
2015
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1889
2016
        if inv.root is not None and not include_root and from_dir is None:
1890
2017
            entries.next()
1894
2021
    def lock_read(self):
1895
2022
        """Lock the tree for a set of operations.
1896
2023
 
1897
 
        :return: A bzrlib.lock.LogicalLockResult.
 
2024
        :return: A breezy.lock.LogicalLockResult.
1898
2025
        """
1899
2026
        if not self._locked:
1900
2027
            self._repository.lock_read()
1912
2039
    def path2id(self, path):
1913
2040
        """Return the id for path in this tree."""
1914
2041
        # lookup by path: faster than splitting and walking the ivnentory.
 
2042
        if isinstance(path, list):
 
2043
            if path == []:
 
2044
                path = [""]
 
2045
            path = osutils.pathjoin(*path)
1915
2046
        entry = self._get_entry(path=path)
1916
2047
        if entry == (None, None):
1917
2048
            return None
1940
2071
        # So for now, we just build up the parent inventory, and extract
1941
2072
        # it the same way RevisionTree does.
1942
2073
        _directory = 'directory'
1943
 
        inv = self._get_inventory()
 
2074
        inv = self._get_root_inventory()
1944
2075
        top_id = inv.path2id(prefix)
1945
2076
        if top_id is None:
1946
2077
            pending = []
1981
2112
    def __init__(self, source, target):
1982
2113
        super(InterDirStateTree, self).__init__(source, target)
1983
2114
        if not InterDirStateTree.is_compatible(source, target):
1984
 
            raise Exception, "invalid source %r and target %r" % (source, target)
 
2115
            raise Exception("invalid source %r and target %r" % (source, target))
1985
2116
 
1986
2117
    @staticmethod
1987
2118
    def make_source_parent_tree(source, target):
1988
2119
        """Change the source tree into a parent of the target."""
1989
2120
        revid = source.commit('record tree')
1990
 
        target.branch.repository.fetch(source.branch.repository, revid)
 
2121
        target.branch.fetch(source.branch, revid)
1991
2122
        target.set_parent_ids([revid])
1992
2123
        return target.basis_tree(), target
1993
2124
 
2000
2131
    @classmethod
2001
2132
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
2002
2133
                                                  target):
2003
 
        from bzrlib.tests.test__dirstate_helpers import \
 
2134
        from .tests.test__dirstate_helpers import \
2004
2135
            compiled_dirstate_helpers_feature
2005
2136
        test_case.requireFeature(compiled_dirstate_helpers_feature)
2006
 
        from bzrlib._dirstate_helpers_pyx import ProcessEntryC
 
2137
        from ._dirstate_helpers_pyx import ProcessEntryC
2007
2138
        result = klass.make_source_parent_tree(source, target)
2008
2139
        result[1]._iter_changes = ProcessEntryC
2009
2140
        return result
2072
2203
                specific_files_utf8.add(path.encode('utf8'))
2073
2204
            specific_files = specific_files_utf8
2074
2205
        else:
2075
 
            specific_files = set([''])
 
2206
            specific_files = {''}
2076
2207
        # -- specific_files is now a utf8 path set --
2077
2208
 
2078
2209
        # -- get the state object and prepare it.
2085
2216
                path_entries = state._entries_for_path(path)
2086
2217
                if not path_entries:
2087
2218
                    # this specified path is not present at all: error
2088
 
                    not_versioned.append(path)
 
2219
                    not_versioned.append(path.decode('utf-8'))
2089
2220
                    continue
2090
2221
                found_versioned = False
2091
2222
                # for each id at this path
2099
2230
                if not found_versioned:
2100
2231
                    # none of the indexes was not 'absent' at all ids for this
2101
2232
                    # path.
2102
 
                    not_versioned.append(path)
 
2233
                    not_versioned.append(path.decode('utf-8'))
2103
2234
            if len(not_versioned) > 0:
2104
2235
                raise errors.PathsNotVersionedError(not_versioned)
2105
2236
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
2172
2303
    def update_format(self, tree):
2173
2304
        """Change the format marker."""
2174
2305
        tree._transport.put_bytes('format',
2175
 
            self.target_format.get_format_string(),
 
2306
            self.target_format.as_string(),
2176
2307
            mode=tree.bzrdir._get_file_mode())
2177
2308
 
2178
2309
 
2195
2326
    def update_format(self, tree):
2196
2327
        """Change the format marker."""
2197
2328
        tree._transport.put_bytes('format',
2198
 
            self.target_format.get_format_string(),
 
2329
            self.target_format.as_string(),
2199
2330
            mode=tree.bzrdir._get_file_mode())
2200
2331
 
2201
2332
 
2224
2355
    def update_format(self, tree):
2225
2356
        """Change the format marker."""
2226
2357
        tree._transport.put_bytes('format',
2227
 
            self.target_format.get_format_string(),
 
2358
            self.target_format.as_string(),
2228
2359
            mode=tree.bzrdir._get_file_mode())