/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: Martin
  • Date: 2017-06-05 20:48:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6658.
  • Revision ID: gzlist@googlemail.com-20170605204831-20accykspjcrx0a8
Apply 2to3 dict fixer and clean up resulting mess using view helpers

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
    viewitems,
 
75
    )
 
76
from .transport.local import LocalTransport
 
77
from .tree import (
 
78
    InterTree,
 
79
    InventoryTree,
 
80
    )
 
81
from .workingtree import (
 
82
    InventoryWorkingTree,
 
83
    WorkingTree,
 
84
    WorkingTreeFormatMetaDir,
 
85
    )
 
86
 
 
87
 
 
88
class DirStateWorkingTree(InventoryWorkingTree):
 
89
 
72
90
    def __init__(self, basedir,
73
91
                 branch,
74
92
                 _control_files=None,
84
102
        self._format = _format
85
103
        self.bzrdir = _bzrdir
86
104
        basedir = safe_unicode(basedir)
87
 
        mutter("opening working tree %r", basedir)
 
105
        trace.mutter("opening working tree %r", basedir)
88
106
        self._branch = branch
89
107
        self.basedir = realpath(basedir)
90
108
        # if branch is at our basedir and is a format 6 or less
124
142
            state.add(f, file_id, kind, None, '')
125
143
        self._make_dirty(reset_inventory=True)
126
144
 
 
145
    def _get_check_refs(self):
 
146
        """Return the references needed to perform a check of this tree."""
 
147
        return [('trees', self.last_revision())]
 
148
 
127
149
    def _make_dirty(self, reset_inventory):
128
150
        """Make the tree state dirty.
129
151
 
181
203
 
182
204
    def _comparison_data(self, entry, path):
183
205
        kind, executable, stat_value = \
184
 
            WorkingTree3._comparison_data(self, entry, path)
 
206
            WorkingTree._comparison_data(self, entry, path)
185
207
        # it looks like a plain directory, but it's really a reference -- see
186
208
        # also kind()
187
209
        if (self._repo_supports_tree_reference and kind == 'directory'
193
215
    def commit(self, message=None, revprops=None, *args, **kwargs):
194
216
        # mark the tree as dirty post commit - commit
195
217
        # can change the current versioned list by doing deletes.
196
 
        result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
 
218
        result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
197
219
        self._make_dirty(reset_inventory=True)
198
220
        return result
199
221
 
218
240
        local_path = self.bzrdir.get_workingtree_transport(None
219
241
            ).local_abspath('dirstate')
220
242
        self._dirstate = dirstate.DirState.on_file(local_path,
221
 
            self._sha1_provider())
 
243
            self._sha1_provider(), self._worth_saving_limit())
222
244
        return self._dirstate
223
245
 
224
246
    def _sha1_provider(self):
233
255
        else:
234
256
            return None
235
257
 
 
258
    def _worth_saving_limit(self):
 
259
        """How many hash changes are ok before we must save the dirstate.
 
260
 
 
261
        :return: an integer. -1 means never save.
 
262
        """
 
263
        conf = self.get_config_stack()
 
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)
372
 
            except OSError, e:
 
401
                stat_value = osutils.lstat(file_abspath)
 
402
            except OSError as e:
373
403
                if e.errno == errno.ENOENT:
374
404
                    return None
375
405
                else:
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
 
403
 
    inventory = property(_get_inventory,
404
 
                         doc="Inventory of this Tree")
 
433
    root_inventory = property(_get_root_inventory,
 
434
        "Root inventory of this tree")
405
435
 
406
436
    @needs_read_lock
407
437
    def get_parent_ids(self):
455
485
            return False # Missing entries are not executable
456
486
        return entry[1][0][3] # Executable?
457
487
 
458
 
    if not osutils.supports_executable():
459
 
        def is_executable(self, file_id, path=None):
460
 
            """Test if a file is executable or not.
 
488
    def is_executable(self, file_id, path=None):
 
489
        """Test if a file is executable or not.
461
490
 
462
 
            Note: The caller is expected to take a read-lock before calling this.
463
 
            """
 
491
        Note: The caller is expected to take a read-lock before calling this.
 
492
        """
 
493
        if not self._supports_executable():
464
494
            entry = self._get_entry(file_id=file_id, path=path)
465
495
            if entry == (None, None):
466
496
                return False
467
497
            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
 
            """
 
498
        else:
477
499
            self._must_be_locked()
478
500
            if not path:
479
501
                path = self.id2path(file_id)
480
 
            mode = os.lstat(self.abspath(path)).st_mode
 
502
            mode = osutils.lstat(self.abspath(path)).st_mode
481
503
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
482
504
 
483
505
    def all_file_ids(self):
527
549
                # path is missing on disk.
528
550
                continue
529
551
 
530
 
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
 
552
    def _observed_sha1(self, file_id, path, sha_and_stat):
531
553
        """See MutableTree._observed_sha1."""
532
554
        state = self.current_dirstate()
533
555
        entry = self._get_entry(file_id=file_id, path=path)
534
 
        state._observed_sha1(entry, sha1, statvalue)
 
556
        state._observed_sha1(entry, *sha_and_stat)
535
557
 
536
558
    def kind(self, file_id):
537
559
        """Return the kind of a file.
567
589
            return _mod_revision.NULL_REVISION
568
590
 
569
591
    def lock_read(self):
570
 
        """See Branch.lock_read, and WorkingTree.unlock."""
 
592
        """See Branch.lock_read, and WorkingTree.unlock.
 
593
 
 
594
        :return: A breezy.lock.LogicalLockResult.
 
595
        """
571
596
        self.branch.lock_read()
572
597
        try:
573
598
            self._control_files.lock_read()
586
611
        except:
587
612
            self.branch.unlock()
588
613
            raise
 
614
        return LogicalLockResult(self.unlock)
589
615
 
590
616
    def _lock_self_write(self):
591
617
        """This should be called after the branch is locked."""
606
632
        except:
607
633
            self.branch.unlock()
608
634
            raise
 
635
        return LogicalLockResult(self.unlock)
609
636
 
610
637
    def lock_tree_write(self):
611
 
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
638
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
639
 
 
640
        :return: A breezy.lock.LogicalLockResult.
 
641
        """
612
642
        self.branch.lock_read()
613
 
        self._lock_self_write()
 
643
        return self._lock_self_write()
614
644
 
615
645
    def lock_write(self):
616
 
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
646
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
647
 
 
648
        :return: A breezy.lock.LogicalLockResult.
 
649
        """
617
650
        self.branch.lock_write()
618
 
        self._lock_self_write()
 
651
        return self._lock_self_write()
619
652
 
620
653
    @needs_tree_write_lock
621
654
    def move(self, from_paths, to_dir, after=False):
652
685
 
653
686
        if self._inventory is not None:
654
687
            update_inventory = True
655
 
            inv = self.inventory
 
688
            inv = self.root_inventory
656
689
            to_dir_id = to_entry[0][2]
657
690
            to_dir_ie = inv[to_dir_id]
658
691
        else:
659
692
            update_inventory = False
660
693
 
661
 
        rollbacks = []
 
694
        # GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
 
695
        # missing those added here, but there's also no test coverage for this.
 
696
        rollbacks = cleanup.ObjectWithCleanups()
662
697
        def move_one(old_entry, from_path_utf8, minikind, executable,
663
698
                     fingerprint, packed_stat, size,
664
699
                     to_block, to_key, to_path_utf8):
665
700
            state._make_absent(old_entry)
666
701
            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))
 
702
            rollbacks.add_cleanup(
 
703
                state.update_minimal,
 
704
                from_key,
 
705
                minikind,
 
706
                executable=executable,
 
707
                fingerprint=fingerprint,
 
708
                packed_stat=packed_stat,
 
709
                size=size,
 
710
                path_utf8=from_path_utf8)
675
711
            state.update_minimal(to_key,
676
712
                    minikind,
677
713
                    executable=executable,
681
717
                    path_utf8=to_path_utf8)
682
718
            added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
683
719
            new_entry = to_block[1][added_entry_index]
684
 
            rollbacks.append(lambda:state._make_absent(new_entry))
 
720
            rollbacks.add_cleanup(state._make_absent, new_entry)
685
721
 
686
722
        for from_rel in from_paths:
687
723
            # from_rel is 'pathinroot/foo/bar'
726
762
                elif not after:
727
763
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
728
764
 
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
765
            # perform the disk move first - its the most likely failure point.
747
766
            if move_file:
748
767
                from_rel_abs = self.abspath(from_rel)
749
768
                to_rel_abs = self.abspath(to_rel)
750
769
                try:
751
770
                    osutils.rename(from_rel_abs, to_rel_abs)
752
 
                except OSError, e:
 
771
                except OSError as e:
753
772
                    raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
754
 
                rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
 
773
                rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
755
774
            try:
756
775
                # perform the rename in the inventory next if needed: its easy
757
776
                # to rollback
760
779
                    from_entry = inv[from_id]
761
780
                    current_parent = from_entry.parent_id
762
781
                    inv.rename(from_id, to_dir_id, from_tail)
763
 
                    rollbacks.append(
764
 
                        lambda: inv.rename(from_id, current_parent, from_tail))
 
782
                    rollbacks.add_cleanup(
 
783
                        inv.rename, from_id, current_parent, from_tail)
765
784
                # finally do the rename in the dirstate, which is a little
766
785
                # tricky to rollback, but least likely to need it.
767
786
                old_block_index, old_entry_index, dir_present, file_present = \
835
854
                                                to_path_utf8)
836
855
                    update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
837
856
            except:
838
 
                rollback_rename()
 
857
                rollbacks.cleanup_now()
839
858
                raise
840
859
            result.append((from_rel, to_rel))
841
 
            state._dirblock_state = dirstate.DirState.IN_MEMORY_MODIFIED
 
860
            state._mark_modified()
842
861
            self._make_dirty(reset_inventory=False)
843
862
 
844
863
        return result
854
873
    @needs_read_lock
855
874
    def path2id(self, path):
856
875
        """Return the id for path in this tree."""
 
876
        if isinstance(path, list):
 
877
            if path == []:
 
878
                path = [""]
 
879
            path = osutils.pathjoin(*path)
857
880
        path = path.strip('/')
858
881
        entry = self._get_entry(path=path)
859
882
        if entry == (None, None):
937
960
                    all_versioned = False
938
961
                    break
939
962
            if not all_versioned:
940
 
                raise errors.PathsNotVersionedError(paths)
 
963
                raise errors.PathsNotVersionedError(
 
964
                    [p.decode('utf-8') for p in paths])
941
965
        # -- remove redundancy in supplied paths to prevent over-scanning --
942
966
        search_paths = osutils.minimum_path_selection(paths)
943
967
        # sketch:
992
1016
            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
993
1017
            for dir_name in split_paths:
994
1018
                if dir_name not in found_dir_names:
995
 
                    raise errors.PathsNotVersionedError(paths)
 
1019
                    raise errors.PathsNotVersionedError(
 
1020
                        [p.decode('utf-8') for p in paths])
996
1021
 
997
 
        for dir_name_id, trees_info in found.iteritems():
 
1022
        for dir_name_id, trees_info in viewitems(found):
998
1023
            for index in search_indexes:
999
1024
                if trees_info[index][0] not in ('r', 'a'):
1000
1025
                    found_ids.add(dir_name_id[2])
1005
1030
 
1006
1031
        This is a meaningless operation for dirstate, but we obey it anyhow.
1007
1032
        """
1008
 
        return self.inventory
 
1033
        return self.root_inventory
1009
1034
 
1010
1035
    @needs_read_lock
1011
1036
    def revision_tree(self, revision_id):
1101
1126
                        _mod_revision.NULL_REVISION)))
1102
1127
                ghosts.append(rev_id)
1103
1128
            accepted_revisions.add(rev_id)
1104
 
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
 
1129
        updated = False
 
1130
        if (len(real_trees) == 1
 
1131
            and not ghosts
 
1132
            and self.branch.repository._format.fast_deltas
 
1133
            and isinstance(real_trees[0][1],
 
1134
                revisiontree.InventoryRevisionTree)
 
1135
            and self.get_parent_ids()):
 
1136
            rev_id, rev_tree = real_trees[0]
 
1137
            basis_id = self.get_parent_ids()[0]
 
1138
            # There are times when basis_tree won't be in
 
1139
            # self.branch.repository, (switch, for example)
 
1140
            try:
 
1141
                basis_tree = self.branch.repository.revision_tree(basis_id)
 
1142
            except errors.NoSuchRevision:
 
1143
                # Fall back to the set_parent_trees(), since we can't use
 
1144
                # _make_delta if we can't get the RevisionTree
 
1145
                pass
 
1146
            else:
 
1147
                delta = rev_tree.root_inventory._make_delta(
 
1148
                    basis_tree.root_inventory)
 
1149
                dirstate.update_basis_by_delta(delta, rev_id)
 
1150
                updated = True
 
1151
        if not updated:
 
1152
            dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1105
1153
        self._make_dirty(reset_inventory=False)
1106
1154
 
1107
1155
    def _set_root_id(self, file_id):
1127
1175
 
1128
1176
    def unlock(self):
1129
1177
        """Unlock in format 4 trees needs to write the entire dirstate."""
1130
 
        # do non-implementation specific cleanup
1131
 
        self._cleanup()
1132
 
 
1133
1178
        if self._control_files._lock_count == 1:
 
1179
            # do non-implementation specific cleanup
 
1180
            self._cleanup()
 
1181
 
1134
1182
            # eventually we should do signature checking during read locks for
1135
1183
            # dirstate updates.
1136
1184
            if self._control_files._lock_mode == 'w':
1230
1278
                ids_to_unversion.remove(entry[0][2])
1231
1279
            block_index += 1
1232
1280
        if ids_to_unversion:
1233
 
            raise errors.NoSuchId(self, iter(ids_to_unversion).next())
 
1281
            raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1234
1282
        self._make_dirty(reset_inventory=False)
1235
1283
        # have to change the legacy inventory too.
1236
1284
        if self._inventory is not None:
1237
1285
            for file_id in file_ids:
1238
 
                self._inventory.remove_recursive_id(file_id)
 
1286
                if self._inventory.has_id(file_id):
 
1287
                    self._inventory.remove_recursive_id(file_id)
1239
1288
 
1240
1289
    @needs_tree_write_lock
1241
1290
    def rename_one(self, from_rel, to_rel, after=False):
1242
1291
        """See WorkingTree.rename_one"""
1243
1292
        self.flush()
1244
 
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1293
        super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1245
1294
 
1246
1295
    @needs_tree_write_lock
1247
1296
    def apply_inventory_delta(self, changes):
1273
1322
        # being created.
1274
1323
        self._inventory = None
1275
1324
        # generate a delta,
1276
 
        delta = inv._make_delta(self.inventory)
 
1325
        delta = inv._make_delta(self.root_inventory)
1277
1326
        # and apply it.
1278
1327
        self.apply_inventory_delta(delta)
1279
1328
        if had_inventory:
1280
1329
            self._inventory = inv
1281
1330
        self.flush()
1282
1331
 
 
1332
    @needs_tree_write_lock
 
1333
    def reset_state(self, revision_ids=None):
 
1334
        """Reset the state of the working tree.
 
1335
 
 
1336
        This does a hard-reset to a last-known-good state. This is a way to
 
1337
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
 
1338
        """
 
1339
        if revision_ids is None:
 
1340
            revision_ids = self.get_parent_ids()
 
1341
        if not revision_ids:
 
1342
            base_tree = self.branch.repository.revision_tree(
 
1343
                _mod_revision.NULL_REVISION)
 
1344
            trees = []
 
1345
        else:
 
1346
            trees = list(zip(revision_ids,
 
1347
                        self.branch.repository.revision_trees(revision_ids)))
 
1348
            base_tree = trees[0][1]
 
1349
        state = self.current_dirstate()
 
1350
        # We don't support ghosts yet
 
1351
        state.set_state_from_scratch(base_tree.root_inventory, trees, [])
 
1352
 
1283
1353
 
1284
1354
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1285
1355
 
1290
1360
        """See dirstate.SHA1Provider.sha1()."""
1291
1361
        filters = self.tree._content_filter_stack(
1292
1362
            self.tree.relpath(osutils.safe_unicode(abspath)))
1293
 
        return internal_size_sha_file_byname(abspath, filters)[1]
 
1363
        return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1294
1364
 
1295
1365
    def stat_and_sha1(self, abspath):
1296
1366
        """See dirstate.SHA1Provider.stat_and_sha1()."""
1300
1370
        try:
1301
1371
            statvalue = os.fstat(file_obj.fileno())
1302
1372
            if filters:
1303
 
                file_obj = filtered_input_file(file_obj, filters)
 
1373
                file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1304
1374
            sha1 = osutils.size_sha_file(file_obj)[1]
1305
1375
        finally:
1306
1376
            file_obj.close()
1317
1387
    def _file_content_summary(self, path, stat_result):
1318
1388
        # This is to support the somewhat obsolete path_content_summary method
1319
1389
        # with content filtering: see
1320
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/415508>.
 
1390
        # <https://bugs.launchpad.net/bzr/+bug/415508>.
1321
1391
        #
1322
1392
        # If the dirstate cache is up to date and knows the hash and size,
1323
1393
        # return that.
1336
1406
class WorkingTree4(DirStateWorkingTree):
1337
1407
    """This is the Format 4 working tree.
1338
1408
 
1339
 
    This differs from WorkingTree3 by:
 
1409
    This differs from WorkingTree by:
1340
1410
     - Having a consolidated internal dirstate, stored in a
1341
1411
       randomly-accessible sorted file on disk.
1342
1412
     - Not having a regular inventory attribute.  One can be synthesized
1370
1440
        return views.PathBasedViews(self)
1371
1441
 
1372
1442
 
1373
 
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
 
1443
class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
 
1444
 
 
1445
    missing_parent_conflicts = True
 
1446
 
 
1447
    supports_versioned_directories = True
 
1448
 
 
1449
    _lock_class = LockDir
 
1450
    _lock_file_name = 'lock'
 
1451
 
 
1452
    def _open_control_files(self, a_bzrdir):
 
1453
        transport = a_bzrdir.get_workingtree_transport(None)
 
1454
        return LockableFiles(transport, self._lock_file_name,
 
1455
                             self._lock_class)
1374
1456
 
1375
1457
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1376
1458
                   accelerator_tree=None, hardlink=False):
1377
1459
        """See WorkingTreeFormat.initialize().
1378
1460
 
1379
1461
        :param revision_id: allows creating a working tree at a different
1380
 
        revision than the branch is at.
 
1462
            revision than the branch is at.
1381
1463
        :param accelerator_tree: A tree which can be used for retrieving file
1382
1464
            contents more quickly than the revision tree, i.e. a workingtree.
1383
1465
            The revision tree will be used for cases where accelerator_tree's
1394
1476
        control_files = self._open_control_files(a_bzrdir)
1395
1477
        control_files.create_lock()
1396
1478
        control_files.lock_write()
1397
 
        transport.put_bytes('format', self.get_format_string(),
 
1479
        transport.put_bytes('format', self.as_string(),
1398
1480
            mode=a_bzrdir._get_file_mode())
1399
1481
        if from_branch is not None:
1400
1482
            branch = from_branch
1460
1542
                transform.build_tree(basis, wt, accelerator_tree,
1461
1543
                                     hardlink=hardlink,
1462
1544
                                     delta_from_tree=delta_from_tree)
 
1545
                for hook in MutableTree.hooks['post_build_tree']:
 
1546
                    hook(wt)
1463
1547
            finally:
1464
1548
                basis.unlock()
1465
1549
        finally:
1476
1560
        :param wt: the WorkingTree object
1477
1561
        """
1478
1562
 
 
1563
    def open(self, a_bzrdir, _found=False):
 
1564
        """Return the WorkingTree object for a_bzrdir
 
1565
 
 
1566
        _found is a private parameter, do not use it. It is used to indicate
 
1567
               if format probing has already been done.
 
1568
        """
 
1569
        if not _found:
 
1570
            # we are being called directly and must probe.
 
1571
            raise NotImplementedError
 
1572
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1573
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1574
        wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1575
        return wt
 
1576
 
1479
1577
    def _open(self, a_bzrdir, control_files):
1480
1578
        """Open the tree itself.
1481
1579
 
1494
1592
    def _get_matchingbzrdir(self):
1495
1593
        """Overrideable method to get a bzrdir for testing."""
1496
1594
        # please test against something that will let us do tree references
1497
 
        return bzrdir.format_registry.make_bzrdir(
1498
 
            'dirstate-with-subtree')
 
1595
        return controldir.format_registry.make_bzrdir(
 
1596
            'development-subtree')
1499
1597
 
1500
1598
    _matchingbzrdir = property(__get_matchingbzrdir)
1501
1599
 
1506
1604
    This format:
1507
1605
        - exists within a metadir controlling .bzr
1508
1606
        - includes an explicit version marker for the workingtree control
1509
 
          files, separate from the BzrDir format
 
1607
          files, separate from the ControlDir format
1510
1608
        - modifies the hash cache format
1511
1609
        - is new in bzr 0.15
1512
1610
        - uses a LockDir to guard access to it.
1516
1614
 
1517
1615
    _tree_class = WorkingTree4
1518
1616
 
1519
 
    def get_format_string(self):
 
1617
    @classmethod
 
1618
    def get_format_string(cls):
1520
1619
        """See WorkingTreeFormat.get_format_string()."""
1521
1620
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1522
1621
 
1533
1632
 
1534
1633
    _tree_class = WorkingTree5
1535
1634
 
1536
 
    def get_format_string(self):
 
1635
    @classmethod
 
1636
    def get_format_string(cls):
1537
1637
        """See WorkingTreeFormat.get_format_string()."""
1538
1638
        return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1539
1639
 
1553
1653
 
1554
1654
    _tree_class = WorkingTree6
1555
1655
 
1556
 
    def get_format_string(self):
 
1656
    @classmethod
 
1657
    def get_format_string(cls):
1557
1658
        """See WorkingTreeFormat.get_format_string()."""
1558
1659
        return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1559
1660
 
1571
1672
    def supports_views(self):
1572
1673
        return True
1573
1674
 
1574
 
 
1575
 
class DirStateRevisionTree(Tree):
 
1675
    def _get_matchingbzrdir(self):
 
1676
        """Overrideable method to get a bzrdir for testing."""
 
1677
        # We use 'development-subtree' instead of '2a', because we have a
 
1678
        # few tests that want to test tree references
 
1679
        return bzrdir.format_registry.make_bzrdir('development-subtree')
 
1680
 
 
1681
 
 
1682
class DirStateRevisionTree(InventoryTree):
1576
1683
    """A revision tree pulling the inventory from a dirstate.
1577
1684
    
1578
1685
    Note that this is one of the historical (ie revision) trees cached in the
1597
1704
    def annotate_iter(self, file_id,
1598
1705
                      default_revision=_mod_revision.CURRENT_REVISION):
1599
1706
        """See Tree.annotate_iter"""
1600
 
        text_key = (file_id, self.inventory[file_id].revision)
 
1707
        text_key = (file_id, self.get_file_revision(file_id))
1601
1708
        annotations = self._repository.texts.annotate(text_key)
1602
1709
        return [(key[-1], line) for (key, line) in annotations]
1603
1710
 
1604
 
    def _get_ancestors(self, default_revision):
1605
 
        return set(self._repository.get_ancestry(self._revision_id,
1606
 
                                                 topo_sorted=False))
1607
1711
    def _comparison_data(self, entry, path):
1608
1712
        """See Tree._comparison_data."""
1609
1713
        if entry is None:
1662
1766
        if path is not None:
1663
1767
            path = path.encode('utf8')
1664
1768
        parent_index = self._get_parent_index()
1665
 
        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
 
1769
        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id,
 
1770
            path_utf8=path)
1666
1771
 
1667
1772
    def _generate_inventory(self):
1668
1773
        """Create and set self.inventory from the dirstate object.
1725
1830
                elif kind == 'directory':
1726
1831
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1727
1832
                elif kind == 'symlink':
1728
 
                    inv_entry.executable = False
1729
 
                    inv_entry.text_size = None
1730
1833
                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1731
1834
                elif kind == 'tree-reference':
1732
1835
                    inv_entry.reference_revision = fingerprint or None
1752
1855
        # Make sure the file exists
1753
1856
        entry = self._get_entry(file_id, path=path)
1754
1857
        if entry == (None, None): # do we raise?
1755
 
            return None
 
1858
            raise errors.NoSuchId(self, file_id)
1756
1859
        parent_index = self._get_parent_index()
1757
1860
        last_changed_revision = entry[1][parent_index][4]
1758
1861
        try:
1769
1872
            return parent_details[1]
1770
1873
        return None
1771
1874
 
 
1875
    @needs_read_lock
 
1876
    def get_file_revision(self, file_id):
 
1877
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1878
        return inv[inv_file_id].revision
 
1879
 
1772
1880
    def get_file(self, file_id, path=None):
1773
 
        return StringIO(self.get_file_text(file_id))
 
1881
        return BytesIO(self.get_file_text(file_id))
1774
1882
 
1775
1883
    def get_file_size(self, file_id):
1776
1884
        """See Tree.get_file_size"""
1777
 
        return self.inventory[file_id].text_size
 
1885
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1886
        return inv[inv_file_id].text_size
1778
1887
 
1779
1888
    def get_file_text(self, file_id, path=None):
1780
 
        _, content = list(self.iter_files_bytes([(file_id, None)]))[0]
1781
 
        return ''.join(content)
 
1889
        content = None
 
1890
        for _, content_iter in self.iter_files_bytes([(file_id, None)]):
 
1891
            if content is not None:
 
1892
                raise AssertionError('iter_files_bytes returned'
 
1893
                    ' too many entries')
 
1894
            # For each entry returned by iter_files_bytes, we must consume the
 
1895
            # content_iter before we step the files iterator.
 
1896
            content = ''.join(content_iter)
 
1897
        if content is None:
 
1898
            raise AssertionError('iter_files_bytes did not return'
 
1899
                ' the requested data')
 
1900
        return 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
 
1821
 
    inventory = property(_get_inventory,
 
1941
    root_inventory = property(_get_root_inventory,
1822
1942
                         doc="Inventory of this Tree")
1823
1943
 
1824
1944
    def get_parent_ids(self):
1841
1961
 
1842
1962
    def path_content_summary(self, path):
1843
1963
        """See Tree.path_content_summary."""
1844
 
        id = self.inventory.path2id(path)
1845
 
        if id is None:
 
1964
        inv, inv_file_id = self._path2inv_file_id(path)
 
1965
        if inv_file_id is None:
1846
1966
            return ('missing', None, None, None)
1847
 
        entry = self._inventory[id]
 
1967
        entry = inv[inv_file_id]
1848
1968
        kind = entry.kind
1849
1969
        if kind == 'file':
1850
1970
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
1854
1974
            return (kind, None, None, None)
1855
1975
 
1856
1976
    def is_executable(self, file_id, path=None):
1857
 
        ie = self.inventory[file_id]
 
1977
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1978
        ie = inv[inv_file_id]
1858
1979
        if ie.kind != "file":
1859
 
            return None
 
1980
            return False
1860
1981
        return ie.executable
1861
1982
 
 
1983
    def is_locked(self):
 
1984
        return self._locked
 
1985
 
1862
1986
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1863
1987
        # We use a standard implementation, because DirStateRevisionTree is
1864
1988
        # dealing with one of the parents of the current state
1865
 
        inv = self._get_inventory()
1866
1989
        if from_dir is None:
 
1990
            inv = self.root_inventory
1867
1991
            from_dir_id = None
1868
1992
        else:
1869
 
            from_dir_id = inv.path2id(from_dir)
 
1993
            inv, from_dir_id = self._path2inv_file_id(from_dir)
1870
1994
            if from_dir_id is None:
1871
1995
                # Directory not versioned
1872
1996
                return
 
1997
        # FIXME: Support nested trees
1873
1998
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1874
1999
        if inv.root is not None and not include_root and from_dir is None:
1875
 
            entries.next()
 
2000
            next(entries)
1876
2001
        for path, entry in entries:
1877
2002
            yield path, 'V', entry.kind, entry.file_id, entry
1878
2003
 
1879
2004
    def lock_read(self):
1880
 
        """Lock the tree for a set of operations."""
 
2005
        """Lock the tree for a set of operations.
 
2006
 
 
2007
        :return: A breezy.lock.LogicalLockResult.
 
2008
        """
1881
2009
        if not self._locked:
1882
2010
            self._repository.lock_read()
1883
2011
            if self._dirstate._lock_token is None:
1884
2012
                self._dirstate.lock_read()
1885
2013
                self._dirstate_locked = True
1886
2014
        self._locked += 1
 
2015
        return LogicalLockResult(self.unlock)
1887
2016
 
1888
2017
    def _must_be_locked(self):
1889
2018
        if not self._locked:
1893
2022
    def path2id(self, path):
1894
2023
        """Return the id for path in this tree."""
1895
2024
        # lookup by path: faster than splitting and walking the ivnentory.
 
2025
        if isinstance(path, list):
 
2026
            if path == []:
 
2027
                path = [""]
 
2028
            path = osutils.pathjoin(*path)
1896
2029
        entry = self._get_entry(path=path)
1897
2030
        if entry == (None, None):
1898
2031
            return None
1921
2054
        # So for now, we just build up the parent inventory, and extract
1922
2055
        # it the same way RevisionTree does.
1923
2056
        _directory = 'directory'
1924
 
        inv = self._get_inventory()
 
2057
        inv = self._get_root_inventory()
1925
2058
        top_id = inv.path2id(prefix)
1926
2059
        if top_id is None:
1927
2060
            pending = []
1962
2095
    def __init__(self, source, target):
1963
2096
        super(InterDirStateTree, self).__init__(source, target)
1964
2097
        if not InterDirStateTree.is_compatible(source, target):
1965
 
            raise Exception, "invalid source %r and target %r" % (source, target)
 
2098
            raise Exception("invalid source %r and target %r" % (source, target))
1966
2099
 
1967
2100
    @staticmethod
1968
2101
    def make_source_parent_tree(source, target):
1969
2102
        """Change the source tree into a parent of the target."""
1970
2103
        revid = source.commit('record tree')
1971
 
        target.branch.repository.fetch(source.branch.repository, revid)
 
2104
        target.branch.fetch(source.branch, revid)
1972
2105
        target.set_parent_ids([revid])
1973
2106
        return target.basis_tree(), target
1974
2107
 
1981
2114
    @classmethod
1982
2115
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
1983
2116
                                                  target):
1984
 
        from bzrlib.tests.test__dirstate_helpers import \
 
2117
        from .tests.test__dirstate_helpers import \
1985
2118
            compiled_dirstate_helpers_feature
1986
2119
        test_case.requireFeature(compiled_dirstate_helpers_feature)
1987
 
        from bzrlib._dirstate_helpers_pyx import ProcessEntryC
 
2120
        from ._dirstate_helpers_pyx import ProcessEntryC
1988
2121
        result = klass.make_source_parent_tree(source, target)
1989
2122
        result[1]._iter_changes = ProcessEntryC
1990
2123
        return result
2053
2186
                specific_files_utf8.add(path.encode('utf8'))
2054
2187
            specific_files = specific_files_utf8
2055
2188
        else:
2056
 
            specific_files = set([''])
 
2189
            specific_files = {''}
2057
2190
        # -- specific_files is now a utf8 path set --
2058
2191
 
2059
2192
        # -- get the state object and prepare it.
2066
2199
                path_entries = state._entries_for_path(path)
2067
2200
                if not path_entries:
2068
2201
                    # this specified path is not present at all: error
2069
 
                    not_versioned.append(path)
 
2202
                    not_versioned.append(path.decode('utf-8'))
2070
2203
                    continue
2071
2204
                found_versioned = False
2072
2205
                # for each id at this path
2080
2213
                if not found_versioned:
2081
2214
                    # none of the indexes was not 'absent' at all ids for this
2082
2215
                    # path.
2083
 
                    not_versioned.append(path)
 
2216
                    not_versioned.append(path.decode('utf-8'))
2084
2217
            if len(not_versioned) > 0:
2085
2218
                raise errors.PathsNotVersionedError(not_versioned)
2086
2219
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
2153
2286
    def update_format(self, tree):
2154
2287
        """Change the format marker."""
2155
2288
        tree._transport.put_bytes('format',
2156
 
            self.target_format.get_format_string(),
 
2289
            self.target_format.as_string(),
2157
2290
            mode=tree.bzrdir._get_file_mode())
2158
2291
 
2159
2292
 
2176
2309
    def update_format(self, tree):
2177
2310
        """Change the format marker."""
2178
2311
        tree._transport.put_bytes('format',
2179
 
            self.target_format.get_format_string(),
 
2312
            self.target_format.as_string(),
2180
2313
            mode=tree.bzrdir._get_file_mode())
2181
2314
 
2182
2315
 
2205
2338
    def update_format(self, tree):
2206
2339
        """Change the format marker."""
2207
2340
        tree._transport.put_bytes('format',
2208
 
            self.target_format.get_format_string(),
 
2341
            self.target_format.as_string(),
2209
2342
            mode=tree.bzrdir._get_file_mode())