/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-09 16:31:49 UTC
  • mto: This revision was merged to the branch mainline in revision 6673.
  • Revision ID: gzlist@googlemail.com-20170609163149-liveiasey25480q6
Make InventoryDeltaError use string formatting, and repr for fileids

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