/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.mutabletree import needs_tree_write_lock
57
 
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 (
58
66
    file_kind,
59
67
    isdir,
60
68
    pathjoin,
61
69
    realpath,
62
70
    safe_unicode,
63
71
    )
64
 
from bzrlib.trace import mutter
65
 
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):
 
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
 
72
93
    def __init__(self, basedir,
73
94
                 branch,
74
95
                 _control_files=None,
84
105
        self._format = _format
85
106
        self.bzrdir = _bzrdir
86
107
        basedir = safe_unicode(basedir)
87
 
        mutter("opening working tree %r", basedir)
 
108
        trace.mutter("opening working tree %r", basedir)
88
109
        self._branch = branch
89
110
        self.basedir = realpath(basedir)
90
111
        # if branch is at our basedir and is a format 6 or less
124
145
            state.add(f, file_id, kind, None, '')
125
146
        self._make_dirty(reset_inventory=True)
126
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
 
127
152
    def _make_dirty(self, reset_inventory):
128
153
        """Make the tree state dirty.
129
154
 
181
206
 
182
207
    def _comparison_data(self, entry, path):
183
208
        kind, executable, stat_value = \
184
 
            WorkingTree3._comparison_data(self, entry, path)
 
209
            WorkingTree._comparison_data(self, entry, path)
185
210
        # it looks like a plain directory, but it's really a reference -- see
186
211
        # also kind()
187
212
        if (self._repo_supports_tree_reference and kind == 'directory'
193
218
    def commit(self, message=None, revprops=None, *args, **kwargs):
194
219
        # mark the tree as dirty post commit - commit
195
220
        # can change the current versioned list by doing deletes.
196
 
        result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
 
221
        result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
197
222
        self._make_dirty(reset_inventory=True)
198
223
        return result
199
224
 
218
243
        local_path = self.bzrdir.get_workingtree_transport(None
219
244
            ).local_abspath('dirstate')
220
245
        self._dirstate = dirstate.DirState.on_file(local_path,
221
 
            self._sha1_provider())
 
246
            self._sha1_provider(), self._worth_saving_limit())
222
247
        return self._dirstate
223
248
 
224
249
    def _sha1_provider(self):
233
258
        else:
234
259
            return None
235
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
 
236
269
    def filter_unversioned_files(self, paths):
237
270
        """Filter out paths that are versioned.
238
271
 
368
401
        state = self.current_dirstate()
369
402
        if stat_value is None:
370
403
            try:
371
 
                stat_value = os.lstat(file_abspath)
372
 
            except OSError, e:
 
404
                stat_value = osutils.lstat(file_abspath)
 
405
            except OSError as e:
373
406
                if e.errno == errno.ENOENT:
374
407
                    return None
375
408
                else:
389
422
                return link_or_sha1
390
423
        return None
391
424
 
392
 
    def _get_inventory(self):
 
425
    def _get_root_inventory(self):
393
426
        """Get the inventory for the tree. This is only valid within a lock."""
394
427
        if 'evil' in debug.debug_flags:
395
428
            trace.mutter_callsite(2,
400
433
        self._generate_inventory()
401
434
        return self._inventory
402
435
 
 
436
    @deprecated_method(deprecated_in((2, 5, 0)))
 
437
    def _get_inventory(self):
 
438
        return self.root_inventory
 
439
 
403
440
    inventory = property(_get_inventory,
404
441
                         doc="Inventory of this Tree")
405
442
 
 
443
    root_inventory = property(_get_root_inventory,
 
444
        "Root inventory of this tree")
 
445
 
406
446
    @needs_read_lock
407
447
    def get_parent_ids(self):
408
448
        """See Tree.get_parent_ids.
455
495
            return False # Missing entries are not executable
456
496
        return entry[1][0][3] # Executable?
457
497
 
458
 
    if not osutils.supports_executable():
459
 
        def is_executable(self, file_id, path=None):
460
 
            """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.
461
500
 
462
 
            Note: The caller is expected to take a read-lock before calling this.
463
 
            """
 
501
        Note: The caller is expected to take a read-lock before calling this.
 
502
        """
 
503
        if not self._supports_executable():
464
504
            entry = self._get_entry(file_id=file_id, path=path)
465
505
            if entry == (None, None):
466
506
                return False
467
507
            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
 
            """
 
508
        else:
477
509
            self._must_be_locked()
478
510
            if not path:
479
511
                path = self.id2path(file_id)
480
 
            mode = os.lstat(self.abspath(path)).st_mode
 
512
            mode = osutils.lstat(self.abspath(path)).st_mode
481
513
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
482
514
 
483
515
    def all_file_ids(self):
527
559
                # path is missing on disk.
528
560
                continue
529
561
 
530
 
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
 
562
    def _observed_sha1(self, file_id, path, sha_and_stat):
531
563
        """See MutableTree._observed_sha1."""
532
564
        state = self.current_dirstate()
533
565
        entry = self._get_entry(file_id=file_id, path=path)
534
 
        state._observed_sha1(entry, sha1, statvalue)
 
566
        state._observed_sha1(entry, *sha_and_stat)
535
567
 
536
568
    def kind(self, file_id):
537
569
        """Return the kind of a file.
567
599
            return _mod_revision.NULL_REVISION
568
600
 
569
601
    def lock_read(self):
570
 
        """See Branch.lock_read, and WorkingTree.unlock."""
 
602
        """See Branch.lock_read, and WorkingTree.unlock.
 
603
 
 
604
        :return: A breezy.lock.LogicalLockResult.
 
605
        """
571
606
        self.branch.lock_read()
572
607
        try:
573
608
            self._control_files.lock_read()
586
621
        except:
587
622
            self.branch.unlock()
588
623
            raise
 
624
        return LogicalLockResult(self.unlock)
589
625
 
590
626
    def _lock_self_write(self):
591
627
        """This should be called after the branch is locked."""
606
642
        except:
607
643
            self.branch.unlock()
608
644
            raise
 
645
        return LogicalLockResult(self.unlock)
609
646
 
610
647
    def lock_tree_write(self):
611
 
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
648
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
649
 
 
650
        :return: A breezy.lock.LogicalLockResult.
 
651
        """
612
652
        self.branch.lock_read()
613
 
        self._lock_self_write()
 
653
        return self._lock_self_write()
614
654
 
615
655
    def lock_write(self):
616
 
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
656
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
657
 
 
658
        :return: A breezy.lock.LogicalLockResult.
 
659
        """
617
660
        self.branch.lock_write()
618
 
        self._lock_self_write()
 
661
        return self._lock_self_write()
619
662
 
620
663
    @needs_tree_write_lock
621
664
    def move(self, from_paths, to_dir, after=False):
652
695
 
653
696
        if self._inventory is not None:
654
697
            update_inventory = True
655
 
            inv = self.inventory
 
698
            inv = self.root_inventory
656
699
            to_dir_id = to_entry[0][2]
657
700
            to_dir_ie = inv[to_dir_id]
658
701
        else:
659
702
            update_inventory = False
660
703
 
661
 
        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()
662
707
        def move_one(old_entry, from_path_utf8, minikind, executable,
663
708
                     fingerprint, packed_stat, size,
664
709
                     to_block, to_key, to_path_utf8):
665
710
            state._make_absent(old_entry)
666
711
            from_key = old_entry[0]
667
 
            rollbacks.append(
668
 
                lambda:state.update_minimal(from_key,
669
 
                    minikind,
670
 
                    executable=executable,
671
 
                    fingerprint=fingerprint,
672
 
                    packed_stat=packed_stat,
673
 
                    size=size,
674
 
                    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)
675
721
            state.update_minimal(to_key,
676
722
                    minikind,
677
723
                    executable=executable,
681
727
                    path_utf8=to_path_utf8)
682
728
            added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
683
729
            new_entry = to_block[1][added_entry_index]
684
 
            rollbacks.append(lambda:state._make_absent(new_entry))
 
730
            rollbacks.add_cleanup(state._make_absent, new_entry)
685
731
 
686
732
        for from_rel in from_paths:
687
733
            # from_rel is 'pathinroot/foo/bar'
726
772
                elif not after:
727
773
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
728
774
 
729
 
            rollbacks = []
730
 
            def rollback_rename():
731
 
                """A single rename has failed, roll it back."""
732
 
                # roll back everything, even if we encounter trouble doing one
733
 
                # of them.
734
 
                #
735
 
                # TODO: at least log the other exceptions rather than just
736
 
                # losing them mbp 20070307
737
 
                exc_info = None
738
 
                for rollback in reversed(rollbacks):
739
 
                    try:
740
 
                        rollback()
741
 
                    except Exception, e:
742
 
                        exc_info = sys.exc_info()
743
 
                if exc_info:
744
 
                    raise exc_info[0], exc_info[1], exc_info[2]
745
 
 
746
775
            # perform the disk move first - its the most likely failure point.
747
776
            if move_file:
748
777
                from_rel_abs = self.abspath(from_rel)
749
778
                to_rel_abs = self.abspath(to_rel)
750
779
                try:
751
780
                    osutils.rename(from_rel_abs, to_rel_abs)
752
 
                except OSError, e:
 
781
                except OSError as e:
753
782
                    raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
754
 
                rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
 
783
                rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
755
784
            try:
756
785
                # perform the rename in the inventory next if needed: its easy
757
786
                # to rollback
760
789
                    from_entry = inv[from_id]
761
790
                    current_parent = from_entry.parent_id
762
791
                    inv.rename(from_id, to_dir_id, from_tail)
763
 
                    rollbacks.append(
764
 
                        lambda: inv.rename(from_id, current_parent, from_tail))
 
792
                    rollbacks.add_cleanup(
 
793
                        inv.rename, from_id, current_parent, from_tail)
765
794
                # finally do the rename in the dirstate, which is a little
766
795
                # tricky to rollback, but least likely to need it.
767
796
                old_block_index, old_entry_index, dir_present, file_present = \
835
864
                                                to_path_utf8)
836
865
                    update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
837
866
            except:
838
 
                rollback_rename()
 
867
                rollbacks.cleanup_now()
839
868
                raise
840
869
            result.append((from_rel, to_rel))
841
 
            state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
 
870
            state._mark_modified()
842
871
            self._make_dirty(reset_inventory=False)
843
872
 
844
873
        return result
854
883
    @needs_read_lock
855
884
    def path2id(self, path):
856
885
        """Return the id for path in this tree."""
 
886
        if isinstance(path, list):
 
887
            if path == []:
 
888
                path = [""]
 
889
            path = osutils.pathjoin(*path)
857
890
        path = path.strip('/')
858
891
        entry = self._get_entry(path=path)
859
892
        if entry == (None, None):
937
970
                    all_versioned = False
938
971
                    break
939
972
            if not all_versioned:
940
 
                raise errors.PathsNotVersionedError(paths)
 
973
                raise errors.PathsNotVersionedError(
 
974
                    [p.decode('utf-8') for p in paths])
941
975
        # -- remove redundancy in supplied paths to prevent over-scanning --
942
976
        search_paths = osutils.minimum_path_selection(paths)
943
977
        # sketch:
992
1026
            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
993
1027
            for dir_name in split_paths:
994
1028
                if dir_name not in found_dir_names:
995
 
                    raise errors.PathsNotVersionedError(paths)
 
1029
                    raise errors.PathsNotVersionedError(
 
1030
                        [p.decode('utf-8') for p in paths])
996
1031
 
997
1032
        for dir_name_id, trees_info in found.iteritems():
998
1033
            for index in search_indexes:
1005
1040
 
1006
1041
        This is a meaningless operation for dirstate, but we obey it anyhow.
1007
1042
        """
1008
 
        return self.inventory
 
1043
        return self.root_inventory
1009
1044
 
1010
1045
    @needs_read_lock
1011
1046
    def revision_tree(self, revision_id):
1101
1136
                        _mod_revision.NULL_REVISION)))
1102
1137
                ghosts.append(rev_id)
1103
1138
            accepted_revisions.add(rev_id)
1104
 
        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)
1105
1163
        self._make_dirty(reset_inventory=False)
1106
1164
 
1107
1165
    def _set_root_id(self, file_id):
1127
1185
 
1128
1186
    def unlock(self):
1129
1187
        """Unlock in format 4 trees needs to write the entire dirstate."""
1130
 
        # do non-implementation specific cleanup
1131
 
        self._cleanup()
1132
 
 
1133
1188
        if self._control_files._lock_count == 1:
 
1189
            # do non-implementation specific cleanup
 
1190
            self._cleanup()
 
1191
 
1134
1192
            # eventually we should do signature checking during read locks for
1135
1193
            # dirstate updates.
1136
1194
            if self._control_files._lock_mode == 'w':
1235
1293
        # have to change the legacy inventory too.
1236
1294
        if self._inventory is not None:
1237
1295
            for file_id in file_ids:
1238
 
                self._inventory.remove_recursive_id(file_id)
 
1296
                if self._inventory.has_id(file_id):
 
1297
                    self._inventory.remove_recursive_id(file_id)
1239
1298
 
1240
1299
    @needs_tree_write_lock
1241
1300
    def rename_one(self, from_rel, to_rel, after=False):
1242
1301
        """See WorkingTree.rename_one"""
1243
1302
        self.flush()
1244
 
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1303
        super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1245
1304
 
1246
1305
    @needs_tree_write_lock
1247
1306
    def apply_inventory_delta(self, changes):
1273
1332
        # being created.
1274
1333
        self._inventory = None
1275
1334
        # generate a delta,
1276
 
        delta = inv._make_delta(self.inventory)
 
1335
        delta = inv._make_delta(self.root_inventory)
1277
1336
        # and apply it.
1278
1337
        self.apply_inventory_delta(delta)
1279
1338
        if had_inventory:
1280
1339
            self._inventory = inv
1281
1340
        self.flush()
1282
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
 
1283
1363
 
1284
1364
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1285
1365
 
1290
1370
        """See dirstate.SHA1Provider.sha1()."""
1291
1371
        filters = self.tree._content_filter_stack(
1292
1372
            self.tree.relpath(osutils.safe_unicode(abspath)))
1293
 
        return internal_size_sha_file_byname(abspath, filters)[1]
 
1373
        return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1294
1374
 
1295
1375
    def stat_and_sha1(self, abspath):
1296
1376
        """See dirstate.SHA1Provider.stat_and_sha1()."""
1300
1380
        try:
1301
1381
            statvalue = os.fstat(file_obj.fileno())
1302
1382
            if filters:
1303
 
                file_obj = filtered_input_file(file_obj, filters)
 
1383
                file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1304
1384
            sha1 = osutils.size_sha_file(file_obj)[1]
1305
1385
        finally:
1306
1386
            file_obj.close()
1317
1397
    def _file_content_summary(self, path, stat_result):
1318
1398
        # This is to support the somewhat obsolete path_content_summary method
1319
1399
        # with content filtering: see
1320
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
 
1400
        # <https://bugs.launchpad.net/bzr/+bug/415508>.
1321
1401
        #
1322
1402
        # If the dirstate cache is up to date and knows the hash and size,
1323
1403
        # return that.
1336
1416
class WorkingTree4(DirStateWorkingTree):
1337
1417
    """This is the Format 4 working tree.
1338
1418
 
1339
 
    This differs from WorkingTree3 by:
 
1419
    This differs from WorkingTree by:
1340
1420
     - Having a consolidated internal dirstate, stored in a
1341
1421
       randomly-accessible sorted file on disk.
1342
1422
     - Not having a regular inventory attribute.  One can be synthesized
1370
1450
        return views.PathBasedViews(self)
1371
1451
 
1372
1452
 
1373
 
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)
1374
1466
 
1375
1467
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1376
1468
                   accelerator_tree=None, hardlink=False):
1377
1469
        """See WorkingTreeFormat.initialize().
1378
1470
 
1379
1471
        :param revision_id: allows creating a working tree at a different
1380
 
        revision than the branch is at.
 
1472
            revision than the branch is at.
1381
1473
        :param accelerator_tree: A tree which can be used for retrieving file
1382
1474
            contents more quickly than the revision tree, i.e. a workingtree.
1383
1475
            The revision tree will be used for cases where accelerator_tree's
1394
1486
        control_files = self._open_control_files(a_bzrdir)
1395
1487
        control_files.create_lock()
1396
1488
        control_files.lock_write()
1397
 
        transport.put_bytes('format', self.get_format_string(),
 
1489
        transport.put_bytes('format', self.as_string(),
1398
1490
            mode=a_bzrdir._get_file_mode())
1399
1491
        if from_branch is not None:
1400
1492
            branch = from_branch
1460
1552
                transform.build_tree(basis, wt, accelerator_tree,
1461
1553
                                     hardlink=hardlink,
1462
1554
                                     delta_from_tree=delta_from_tree)
 
1555
                for hook in MutableTree.hooks['post_build_tree']:
 
1556
                    hook(wt)
1463
1557
            finally:
1464
1558
                basis.unlock()
1465
1559
        finally:
1476
1570
        :param wt: the WorkingTree object
1477
1571
        """
1478
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
 
1479
1587
    def _open(self, a_bzrdir, control_files):
1480
1588
        """Open the tree itself.
1481
1589
 
1494
1602
    def _get_matchingbzrdir(self):
1495
1603
        """Overrideable method to get a bzrdir for testing."""
1496
1604
        # please test against something that will let us do tree references
1497
 
        return bzrdir.format_registry.make_bzrdir(
1498
 
            'dirstate-with-subtree')
 
1605
        return controldir.format_registry.make_bzrdir(
 
1606
            'development-subtree')
1499
1607
 
1500
1608
    _matchingbzrdir = property(__get_matchingbzrdir)
1501
1609
 
1506
1614
    This format:
1507
1615
        - exists within a metadir controlling .bzr
1508
1616
        - includes an explicit version marker for the workingtree control
1509
 
          files, separate from the BzrDir format
 
1617
          files, separate from the ControlDir format
1510
1618
        - modifies the hash cache format
1511
1619
        - is new in bzr 0.15
1512
1620
        - uses a LockDir to guard access to it.
1516
1624
 
1517
1625
    _tree_class = WorkingTree4
1518
1626
 
1519
 
    def get_format_string(self):
 
1627
    @classmethod
 
1628
    def get_format_string(cls):
1520
1629
        """See WorkingTreeFormat.get_format_string()."""
1521
1630
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1522
1631
 
1533
1642
 
1534
1643
    _tree_class = WorkingTree5
1535
1644
 
1536
 
    def get_format_string(self):
 
1645
    @classmethod
 
1646
    def get_format_string(cls):
1537
1647
        """See WorkingTreeFormat.get_format_string()."""
1538
1648
        return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1539
1649
 
1553
1663
 
1554
1664
    _tree_class = WorkingTree6
1555
1665
 
1556
 
    def get_format_string(self):
 
1666
    @classmethod
 
1667
    def get_format_string(cls):
1557
1668
        """See WorkingTreeFormat.get_format_string()."""
1558
1669
        return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1559
1670
 
1571
1682
    def supports_views(self):
1572
1683
        return True
1573
1684
 
1574
 
 
1575
 
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):
1576
1693
    """A revision tree pulling the inventory from a dirstate.
1577
1694
    
1578
1695
    Note that this is one of the historical (ie revision) trees cached in the
1597
1714
    def annotate_iter(self, file_id,
1598
1715
                      default_revision=_mod_revision.CURRENT_REVISION):
1599
1716
        """See Tree.annotate_iter"""
1600
 
        text_key = (file_id, self.inventory[file_id].revision)
 
1717
        text_key = (file_id, self.get_file_revision(file_id))
1601
1718
        annotations = self._repository.texts.annotate(text_key)
1602
1719
        return [(key[-1], line) for (key, line) in annotations]
1603
1720
 
1604
 
    def _get_ancestors(self, default_revision):
1605
 
        return set(self._repository.get_ancestry(self._revision_id,
1606
 
                                                 topo_sorted=False))
1607
1721
    def _comparison_data(self, entry, path):
1608
1722
        """See Tree._comparison_data."""
1609
1723
        if entry is None:
1662
1776
        if path is not None:
1663
1777
            path = path.encode('utf8')
1664
1778
        parent_index = self._get_parent_index()
1665
 
        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)
1666
1781
 
1667
1782
    def _generate_inventory(self):
1668
1783
        """Create and set self.inventory from the dirstate object.
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
 
        return StringIO(self.get_file_text(file_id))
 
1891
        return BytesIO(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
 
        _, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1781
 
        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
1782
1911
 
1783
1912
    def get_reference_revision(self, file_id, path=None):
1784
 
        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
1785
1915
 
1786
1916
    def iter_files_bytes(self, desired_files):
1787
1917
        """See Tree.iter_files_bytes.
1797
1927
                                       identifier))
1798
1928
        return self._repository.iter_files_bytes(repo_desired_files)
1799
1929
 
1800
 
    def get_symlink_target(self, file_id):
 
1930
    def get_symlink_target(self, file_id, path=None):
1801
1931
        entry = self._get_entry(file_id=file_id)
1802
1932
        parent_index = self._get_parent_index()
1803
1933
        if entry[1][parent_index][0] != 'l':
1811
1941
        """Return the revision id for this tree."""
1812
1942
        return self._revision_id
1813
1943
 
1814
 
    def _get_inventory(self):
 
1944
    def _get_root_inventory(self):
1815
1945
        if self._inventory is not None:
1816
1946
            return self._inventory
1817
1947
        self._must_be_locked()
1818
1948
        self._generate_inventory()
1819
1949
        return self._inventory
1820
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
 
1821
1958
    inventory = property(_get_inventory,
1822
1959
                         doc="Inventory of this Tree")
1823
1960
 
1841
1978
 
1842
1979
    def path_content_summary(self, path):
1843
1980
        """See Tree.path_content_summary."""
1844
 
        id = self.inventory.path2id(path)
1845
 
        if id is None:
 
1981
        inv, inv_file_id = self._path2inv_file_id(path)
 
1982
        if inv_file_id is None:
1846
1983
            return ('missing', None, None, None)
1847
 
        entry = self._inventory[id]
 
1984
        entry = inv[inv_file_id]
1848
1985
        kind = entry.kind
1849
1986
        if kind == 'file':
1850
1987
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
1854
1991
            return (kind, None, None, None)
1855
1992
 
1856
1993
    def is_executable(self, file_id, path=None):
1857
 
        ie = self.inventory[file_id]
 
1994
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1995
        ie = inv[inv_file_id]
1858
1996
        if ie.kind != "file":
1859
 
            return None
 
1997
            return False
1860
1998
        return ie.executable
1861
1999
 
 
2000
    def is_locked(self):
 
2001
        return self._locked
 
2002
 
1862
2003
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1863
2004
        # We use a standard implementation, because DirStateRevisionTree is
1864
2005
        # dealing with one of the parents of the current state
1865
 
        inv = self._get_inventory()
1866
2006
        if from_dir is None:
 
2007
            inv = self.root_inventory
1867
2008
            from_dir_id = None
1868
2009
        else:
1869
 
            from_dir_id = inv.path2id(from_dir)
 
2010
            inv, from_dir_id = self._path2inv_file_id(from_dir)
1870
2011
            if from_dir_id is None:
1871
2012
                # Directory not versioned
1872
2013
                return
 
2014
        # FIXME: Support nested trees
1873
2015
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1874
2016
        if inv.root is not None and not include_root and from_dir is None:
1875
2017
            entries.next()
1877
2019
            yield path, 'V', entry.kind, entry.file_id, entry
1878
2020
 
1879
2021
    def lock_read(self):
1880
 
        """Lock the tree for a set of operations."""
 
2022
        """Lock the tree for a set of operations.
 
2023
 
 
2024
        :return: A breezy.lock.LogicalLockResult.
 
2025
        """
1881
2026
        if not self._locked:
1882
2027
            self._repository.lock_read()
1883
2028
            if self._dirstate._lock_token is None:
1884
2029
                self._dirstate.lock_read()
1885
2030
                self._dirstate_locked = True
1886
2031
        self._locked += 1
 
2032
        return LogicalLockResult(self.unlock)
1887
2033
 
1888
2034
    def _must_be_locked(self):
1889
2035
        if not self._locked:
1893
2039
    def path2id(self, path):
1894
2040
        """Return the id for path in this tree."""
1895
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)
1896
2046
        entry = self._get_entry(path=path)
1897
2047
        if entry == (None, None):
1898
2048
            return None
1921
2071
        # So for now, we just build up the parent inventory, and extract
1922
2072
        # it the same way RevisionTree does.
1923
2073
        _directory = 'directory'
1924
 
        inv = self._get_inventory()
 
2074
        inv = self._get_root_inventory()
1925
2075
        top_id = inv.path2id(prefix)
1926
2076
        if top_id is None:
1927
2077
            pending = []
1962
2112
    def __init__(self, source, target):
1963
2113
        super(InterDirStateTree, self).__init__(source, target)
1964
2114
        if not InterDirStateTree.is_compatible(source, target):
1965
 
            raise Exception, "invalid source %r and target %r" % (source, target)
 
2115
            raise Exception("invalid source %r and target %r" % (source, target))
1966
2116
 
1967
2117
    @staticmethod
1968
2118
    def make_source_parent_tree(source, target):
1969
2119
        """Change the source tree into a parent of the target."""
1970
2120
        revid = source.commit('record tree')
1971
 
        target.branch.repository.fetch(source.branch.repository, revid)
 
2121
        target.branch.fetch(source.branch, revid)
1972
2122
        target.set_parent_ids([revid])
1973
2123
        return target.basis_tree(), target
1974
2124
 
1981
2131
    @classmethod
1982
2132
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
1983
2133
                                                  target):
1984
 
        from bzrlib.tests.test__dirstate_helpers import \
 
2134
        from .tests.test__dirstate_helpers import \
1985
2135
            compiled_dirstate_helpers_feature
1986
2136
        test_case.requireFeature(compiled_dirstate_helpers_feature)
1987
 
        from bzrlib._dirstate_helpers_pyx import ProcessEntryC
 
2137
        from ._dirstate_helpers_pyx import ProcessEntryC
1988
2138
        result = klass.make_source_parent_tree(source, target)
1989
2139
        result[1]._iter_changes = ProcessEntryC
1990
2140
        return result
2053
2203
                specific_files_utf8.add(path.encode('utf8'))
2054
2204
            specific_files = specific_files_utf8
2055
2205
        else:
2056
 
            specific_files = set([''])
 
2206
            specific_files = {''}
2057
2207
        # -- specific_files is now a utf8 path set --
2058
2208
 
2059
2209
        # -- get the state object and prepare it.
2066
2216
                path_entries = state._entries_for_path(path)
2067
2217
                if not path_entries:
2068
2218
                    # this specified path is not present at all: error
2069
 
                    not_versioned.append(path)
 
2219
                    not_versioned.append(path.decode('utf-8'))
2070
2220
                    continue
2071
2221
                found_versioned = False
2072
2222
                # for each id at this path
2080
2230
                if not found_versioned:
2081
2231
                    # none of the indexes was not 'absent' at all ids for this
2082
2232
                    # path.
2083
 
                    not_versioned.append(path)
 
2233
                    not_versioned.append(path.decode('utf-8'))
2084
2234
            if len(not_versioned) > 0:
2085
2235
                raise errors.PathsNotVersionedError(not_versioned)
2086
2236
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
2153
2303
    def update_format(self, tree):
2154
2304
        """Change the format marker."""
2155
2305
        tree._transport.put_bytes('format',
2156
 
            self.target_format.get_format_string(),
 
2306
            self.target_format.as_string(),
2157
2307
            mode=tree.bzrdir._get_file_mode())
2158
2308
 
2159
2309
 
2176
2326
    def update_format(self, tree):
2177
2327
        """Change the format marker."""
2178
2328
        tree._transport.put_bytes('format',
2179
 
            self.target_format.get_format_string(),
 
2329
            self.target_format.as_string(),
2180
2330
            mode=tree.bzrdir._get_file_mode())
2181
2331
 
2182
2332
 
2205
2355
    def update_format(self, tree):
2206
2356
        """Change the format marker."""
2207
2357
        tree._transport.put_bytes('format',
2208
 
            self.target_format.get_format_string(),
 
2358
            self.target_format.as_string(),
2209
2359
            mode=tree.bzrdir._get_file_mode())