/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-06-04 19:17:13 UTC
  • mfrom: (0.193.10 trunk)
  • mto: This revision was merged to the branch mainline in revision 6778.
  • Revision ID: jelmer@jelmer.uk-20170604191713-hau7dfsqsl035slm
Bundle the cvs plugin.

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