~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-03 03:26:27 UTC
  • mfrom: (2940.3.1 memorytransport)
  • Revision ID: pqm@pqm.ubuntu.com-20071103032627-fvl5prorhuns0t4o
MemoryTransport._abspath: fix handling of '..' and other strangeness

Show diffs side-by-side

added added

removed removed

Lines of Context:
64
64
    hashcache,
65
65
    ignores,
66
66
    merge,
 
67
    osutils,
67
68
    revision as _mod_revision,
68
69
    revisiontree,
69
70
    repository,
89
90
from bzrlib.lockdir import LockDir
90
91
import bzrlib.mutabletree
91
92
from bzrlib.mutabletree import needs_tree_write_lock
92
 
from bzrlib import osutils
93
93
from bzrlib.osutils import (
94
94
    compact_date,
95
95
    file_kind,
111
111
        deprecated_method,
112
112
        deprecated_function,
113
113
        DEPRECATED_PARAMETER,
 
114
        zero_eight,
 
115
        zero_eleven,
 
116
        zero_thirteen,
114
117
        )
115
118
 
116
119
 
120
123
ERROR_PATH_NOT_FOUND = 3    # WindowsError errno code, equivalent to ENOENT
121
124
 
122
125
 
 
126
@deprecated_function(zero_thirteen)
 
127
def gen_file_id(name):
 
128
    """Return new file id for the basename 'name'.
 
129
 
 
130
    Use bzrlib.generate_ids.gen_file_id() instead
 
131
    """
 
132
    return generate_ids.gen_file_id(name)
 
133
 
 
134
 
 
135
@deprecated_function(zero_thirteen)
 
136
def gen_root_id():
 
137
    """Return a new tree-root file id.
 
138
 
 
139
    This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
 
140
    """
 
141
    return generate_ids.gen_root_id()
 
142
 
 
143
 
123
144
class TreeEntry(object):
124
145
    """An entry that implements the minimum interface used by commands.
125
146
 
201
222
        if not _internal:
202
223
            raise errors.BzrError("Please use bzrdir.open_workingtree or "
203
224
                "WorkingTree.open() to obtain a WorkingTree.")
 
225
        assert isinstance(basedir, basestring), \
 
226
            "base directory %r is not a string" % basedir
204
227
        basedir = safe_unicode(basedir)
205
228
        mutter("opening working tree %r", basedir)
206
229
        if deprecated_passed(branch):
214
237
            self._control_files = self.branch.control_files
215
238
        else:
216
239
            # assume all other formats have their own control files.
 
240
            assert isinstance(_control_files, LockableFiles), \
 
241
                    "_control_files must be a LockableFiles, not %r" \
 
242
                    % _control_files
217
243
            self._control_files = _control_files
218
 
        self._transport = self._control_files._transport
219
244
        # update the whole cache up front and write to disk if anything changed;
220
245
        # in the future we might want to do this more selectively
221
246
        # two possible ways offer themselves : in self._unlock, write the cache
225
250
        wt_trans = self.bzrdir.get_workingtree_transport(None)
226
251
        cache_filename = wt_trans.local_abspath('stat-cache')
227
252
        self._hashcache = hashcache.HashCache(basedir, cache_filename,
228
 
            self.bzrdir._get_file_mode())
 
253
                                              self._control_files._file_mode)
229
254
        hc = self._hashcache
230
255
        hc.read()
231
256
        # is this scan needed ? it makes things kinda slow.
245
270
            # the Format factory and creation methods that are
246
271
            # permitted to do this.
247
272
            self._set_inventory(_inventory, dirty=False)
248
 
        self._detect_case_handling()
249
 
        self._rules_searcher = None
250
 
 
251
 
    def _detect_case_handling(self):
252
 
        wt_trans = self.bzrdir.get_workingtree_transport(None)
253
 
        try:
254
 
            wt_trans.stat("FoRMaT")
255
 
        except errors.NoSuchFile:
256
 
            self.case_sensitive = True
257
 
        else:
258
 
            self.case_sensitive = False
259
 
 
260
 
        self._setup_directory_is_tree_reference()
261
273
 
262
274
    branch = property(
263
275
        fget=lambda self: self._branch,
294
306
            False then the inventory is the same as that on disk and any
295
307
            serialisation would be unneeded overhead.
296
308
        """
 
309
        assert inv.root is not None
297
310
        self._inventory = inv
298
311
        self._inventory_is_modified = dirty
299
312
 
334
347
        """
335
348
        return WorkingTree.open(path, _unsupported=True)
336
349
 
337
 
    @staticmethod
338
 
    def find_trees(location):
339
 
        def list_current(transport):
340
 
            return [d for d in transport.list_dir('') if d != '.bzr']
341
 
        def evaluate(bzrdir):
342
 
            try:
343
 
                tree = bzrdir.open_workingtree()
344
 
            except errors.NoWorkingTree:
345
 
                return True, None
346
 
            else:
347
 
                return True, tree
348
 
        transport = get_transport(location)
349
 
        iterator = bzrdir.BzrDir.find_bzrdirs(transport, evaluate=evaluate,
350
 
                                              list_current=list_current)
351
 
        return [t for t in iterator if t is not None]
352
 
 
353
350
    # should be deprecated - this is slow and in any case treating them as a
354
351
    # container is (we now know) bad style -- mbp 20070302
355
352
    ## @deprecated_method(zero_fifteen)
364
361
            if osutils.lexists(self.abspath(path)):
365
362
                yield ie.file_id
366
363
 
367
 
    def all_file_ids(self):
368
 
        """See Tree.iter_all_file_ids"""
369
 
        return set(self.inventory)
370
 
 
371
364
    def __repr__(self):
372
365
        return "<%s of %s>" % (self.__class__.__name__,
373
366
                               getattr(self, 'basedir', None))
397
390
        # at this point ?
398
391
        try:
399
392
            return self.branch.repository.revision_tree(revision_id)
400
 
        except (errors.RevisionNotPresent, errors.NoSuchRevision):
 
393
        except errors.RevisionNotPresent:
401
394
            # the basis tree *may* be a ghost or a low level error may have
402
395
            # occured. If the revision is present, its a problem, if its not
403
396
            # its a ghost.
409
402
    def _cleanup(self):
410
403
        self._flush_ignore_list_cache()
411
404
 
 
405
    @staticmethod
 
406
    @deprecated_method(zero_eight)
 
407
    def create(branch, directory):
 
408
        """Create a workingtree for branch at directory.
 
409
 
 
410
        If existing_directory already exists it must have a .bzr directory.
 
411
        If it does not exist, it will be created.
 
412
 
 
413
        This returns a new WorkingTree object for the new checkout.
 
414
 
 
415
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
416
        should accept an optional revisionid to checkout [and reject this if
 
417
        checking out into the same dir as a pre-checkout-aware branch format.]
 
418
 
 
419
        XXX: When BzrDir is present, these should be created through that 
 
420
        interface instead.
 
421
        """
 
422
        warnings.warn('delete WorkingTree.create', stacklevel=3)
 
423
        transport = get_transport(directory)
 
424
        if branch.bzrdir.root_transport.base == transport.base:
 
425
            # same dir 
 
426
            return branch.bzrdir.create_workingtree()
 
427
        # different directory, 
 
428
        # create a branch reference
 
429
        # and now a working tree.
 
430
        raise NotImplementedError
 
431
 
 
432
    @staticmethod
 
433
    @deprecated_method(zero_eight)
 
434
    def create_standalone(directory):
 
435
        """Create a checkout and a branch and a repo at directory.
 
436
 
 
437
        Directory must exist and be empty.
 
438
 
 
439
        please use BzrDir.create_standalone_workingtree
 
440
        """
 
441
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
 
442
 
412
443
    def relpath(self, path):
413
444
        """Return the local path portion from a given path.
414
445
        
445
476
        basis = self.basis_tree()
446
477
        basis.lock_read()
447
478
        try:
448
 
            changes = self.iter_changes(basis, True, [self.id2path(file_id)],
 
479
            changes = self._iter_changes(basis, True, [self.id2path(file_id)],
449
480
                require_versioned=True).next()
450
481
            changed_content, kind = changes[2], changes[6]
451
482
            if not changed_content:
487
518
        else:
488
519
            parents = [last_rev]
489
520
        try:
490
 
            merges_file = self._transport.get('pending-merges')
 
521
            merges_file = self._control_files.get('pending-merges')
491
522
        except errors.NoSuchFile:
492
523
            pass
493
524
        else:
520
551
            and this one merged in.
521
552
        """
522
553
        # assumes the target bzr dir format is compatible.
523
 
        result = to_bzrdir.create_workingtree()
 
554
        result = self._format.initialize(to_bzrdir)
524
555
        self.copy_content_into(result, revision_id)
525
556
        return result
526
557
 
555
586
    __contains__ = has_id
556
587
 
557
588
    def get_file_size(self, file_id):
558
 
        """See Tree.get_file_size"""
559
 
        try:
560
 
            return os.path.getsize(self.id2abspath(file_id))
561
 
        except OSError, e:
562
 
            if e.errno != errno.ENOENT:
563
 
                raise
564
 
            else:
565
 
                return None
 
589
        return os.path.getsize(self.id2abspath(file_id))
566
590
 
567
591
    @needs_read_lock
568
592
    def get_file_sha1(self, file_id, path=None, stat_value=None):
608
632
        # function - they should be part of lock_write and unlock.
609
633
        inv = self.inventory
610
634
        for f, file_id, kind in zip(files, ids, kinds):
 
635
            assert kind is not None
611
636
            if file_id is None:
612
637
                inv.add_path(f, kind=kind)
613
638
            else:
678
703
        if updated:
679
704
            self.set_parent_ids(parents, allow_leftmost_as_ghost=True)
680
705
 
681
 
    def path_content_summary(self, path, _lstat=os.lstat,
 
706
    def path_content_summary(self, path, _lstat=osutils.lstat,
682
707
        _mapper=osutils.file_kind_from_stat_mode):
683
708
        """See Tree.path_content_summary."""
684
709
        abspath = self.abspath(path)
708
733
        else:
709
734
            return (kind, None, None, None)
710
735
 
 
736
    @deprecated_method(zero_eleven)
 
737
    @needs_read_lock
 
738
    def pending_merges(self):
 
739
        """Return a list of pending merges.
 
740
 
 
741
        These are revisions that have been merged into the working
 
742
        directory but not yet committed.
 
743
 
 
744
        As of 0.11 this is deprecated. Please see WorkingTree.get_parent_ids()
 
745
        instead - which is available on all tree objects.
 
746
        """
 
747
        return self.get_parent_ids()[1:]
 
748
 
711
749
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
712
750
        """Common ghost checking functionality from set_parent_*.
713
751
 
722
760
 
723
761
    def _set_merges_from_parent_ids(self, parent_ids):
724
762
        merges = parent_ids[1:]
725
 
        self._transport.put_bytes('pending-merges', '\n'.join(merges),
726
 
            mode=self._control_files._file_mode)
727
 
 
728
 
    def _filter_parent_ids_by_ancestry(self, revision_ids):
729
 
        """Check that all merged revisions are proper 'heads'.
730
 
 
731
 
        This will always return the first revision_id, and any merged revisions
732
 
        which are 
733
 
        """
734
 
        if len(revision_ids) == 0:
735
 
            return revision_ids
736
 
        graph = self.branch.repository.get_graph()
737
 
        heads = graph.heads(revision_ids)
738
 
        new_revision_ids = revision_ids[:1]
739
 
        for revision_id in revision_ids[1:]:
740
 
            if revision_id in heads and revision_id not in new_revision_ids:
741
 
                new_revision_ids.append(revision_id)
742
 
        if new_revision_ids != revision_ids:
743
 
            trace.mutter('requested to set revision_ids = %s,'
744
 
                         ' but filtered to %s', revision_ids, new_revision_ids)
745
 
        return new_revision_ids
 
763
        self._control_files.put_bytes('pending-merges', '\n'.join(merges))
746
764
 
747
765
    @needs_tree_write_lock
748
766
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
762
780
        for revision_id in revision_ids:
763
781
            _mod_revision.check_not_reserved_id(revision_id)
764
782
 
765
 
        revision_ids = self._filter_parent_ids_by_ancestry(revision_ids)
766
 
 
767
783
        if len(revision_ids) > 0:
768
784
            self.set_last_revision(revision_ids[0])
769
785
        else:
781
797
        self._check_parents_for_ghosts(parent_ids,
782
798
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
783
799
 
784
 
        parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
785
 
 
786
800
        if len(parent_ids) == 0:
787
801
            leftmost_parent_id = _mod_revision.NULL_REVISION
788
802
            leftmost_parent_tree = None
828
842
    def _put_rio(self, filename, stanzas, header):
829
843
        self._must_be_locked()
830
844
        my_file = rio_file(stanzas, header)
831
 
        self._transport.put_file(filename, my_file,
832
 
            mode=self._control_files._file_mode)
 
845
        self._control_files.put(filename, my_file)
833
846
 
834
847
    @needs_write_lock # because merge pulls data into the branch.
835
848
    def merge_from_branch(self, branch, to_revision=None, from_revision=None,
894
907
        still in the working inventory and have that text hash.
895
908
        """
896
909
        try:
897
 
            hashfile = self._transport.get('merge-hashes')
 
910
            hashfile = self._control_files.get('merge-hashes')
898
911
        except errors.NoSuchFile:
899
912
            return {}
900
913
        merge_hashes = {}
967
980
            other_tree.unlock()
968
981
        other_tree.bzrdir.retire_bzrdir()
969
982
 
970
 
    def _setup_directory_is_tree_reference(self):
971
 
        if self._branch.repository._format.supports_tree_reference:
972
 
            self._directory_is_tree_reference = \
973
 
                self._directory_may_be_tree_reference
974
 
        else:
975
 
            self._directory_is_tree_reference = \
976
 
                self._directory_is_never_tree_reference
977
 
 
978
 
    def _directory_is_never_tree_reference(self, relpath):
979
 
        return False
980
 
 
981
 
    def _directory_may_be_tree_reference(self, relpath):
 
983
    def _directory_is_tree_reference(self, relpath):
982
984
        # as a special case, if a directory contains control files then 
983
985
        # it's a tree reference, except that the root of the tree is not
984
986
        return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
1011
1013
        sub_path = self.id2path(file_id)
1012
1014
        branch_transport = mkdirs(sub_path)
1013
1015
        if format is None:
1014
 
            format = self.bzrdir.cloning_metadir()
 
1016
            format = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
1015
1017
        branch_transport.ensure_base()
1016
1018
        branch_bzrdir = format.initialize_on_transport(branch_transport)
1017
1019
        try:
1018
1020
            repo = branch_bzrdir.find_repository()
1019
1021
        except errors.NoRepositoryPresent:
1020
1022
            repo = branch_bzrdir.create_repository()
1021
 
        if not repo.supports_rich_root():
1022
 
            raise errors.RootNotRich()
 
1023
            assert repo.supports_rich_root()
 
1024
        else:
 
1025
            if not repo.supports_rich_root():
 
1026
                raise errors.RootNotRich()
1023
1027
        new_branch = branch_bzrdir.create_branch()
1024
1028
        new_branch.pull(self.branch)
1025
1029
        for parent_id in self.get_parent_ids():
1057
1061
        sio = StringIO()
1058
1062
        self._serialize(self._inventory, sio)
1059
1063
        sio.seek(0)
1060
 
        self._transport.put_file('inventory', sio,
1061
 
            mode=self._control_files._file_mode)
 
1064
        self._control_files.put('inventory', sio)
1062
1065
        self._inventory_is_modified = False
1063
1066
 
1064
1067
    def _kind(self, relpath):
1225
1228
                                       DeprecationWarning)
1226
1229
 
1227
1230
        # check destination directory
1228
 
        if isinstance(from_paths, basestring):
1229
 
            raise ValueError()
 
1231
        assert not isinstance(from_paths, basestring)
1230
1232
        inv = self.inventory
1231
1233
        to_abs = self.abspath(to_dir)
1232
1234
        if not isdir(to_abs):
1316
1318
                only_change_inv = True
1317
1319
            elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1318
1320
                only_change_inv = False
1319
 
            elif (not self.case_sensitive
1320
 
                  and from_rel.lower() == to_rel.lower()
1321
 
                  and self.has_filename(from_rel)):
1322
 
                only_change_inv = False
1323
1321
            else:
1324
1322
                # something is wrong, so lets determine what exactly
1325
1323
                if not self.has_filename(from_rel) and \
1328
1326
                        errors.PathsDoNotExist(paths=(str(from_rel),
1329
1327
                        str(to_rel))))
1330
1328
                else:
1331
 
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
 
1329
                    raise errors.RenameFailedFilesExist(from_rel, to_rel,
 
1330
                        extra="(Use --after to update the Bazaar id)")
1332
1331
            rename_entry.only_change_inv = only_change_inv
1333
1332
        return rename_entries
1334
1333
 
1496
1495
            # - RBC 20060907
1497
1496
            self._write_inventory(self._inventory)
1498
1497
    
 
1498
    @deprecated_method(zero_eight)
 
1499
    def iter_conflicts(self):
 
1500
        """List all files in the tree that have text or content conflicts.
 
1501
        DEPRECATED.  Use conflicts instead."""
 
1502
        return self._iter_conflicts()
 
1503
 
1499
1504
    def _iter_conflicts(self):
1500
1505
        conflicted = set()
1501
1506
        for info in self.list_files():
1536
1541
                                change_reporter=change_reporter)
1537
1542
                    if (basis_tree.inventory.root is None and
1538
1543
                        new_basis_tree.inventory.root is not None):
1539
 
                        self.set_root_id(new_basis_tree.get_root_id())
 
1544
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1540
1545
                finally:
1541
1546
                    pb.finished()
1542
1547
                    basis_tree.unlock()
1592
1597
                if subf == '.bzr':
1593
1598
                    continue
1594
1599
                if subf not in dir_entry.children:
1595
 
                    try:
1596
 
                        (subf_norm,
1597
 
                         can_access) = osutils.normalized_filename(subf)
1598
 
                    except UnicodeDecodeError:
1599
 
                        path_os_enc = path.encode(osutils._fs_enc)
1600
 
                        relpath = path_os_enc + '/' + subf
1601
 
                        raise errors.BadFilenameEncoding(relpath,
1602
 
                                                         osutils._fs_enc)
 
1600
                    subf_norm, can_access = osutils.normalized_filename(subf)
1603
1601
                    if subf_norm != subf and can_access:
1604
1602
                        if subf_norm not in dir_entry.children:
1605
1603
                            fl.append(subf_norm)
1660
1658
    def kind(self, file_id):
1661
1659
        return file_kind(self.id2abspath(file_id))
1662
1660
 
1663
 
    def stored_kind(self, file_id):
1664
 
        """See Tree.stored_kind"""
1665
 
        return self.inventory[file_id].kind
1666
 
 
1667
1661
    def _comparison_data(self, entry, path):
1668
1662
        abspath = self.abspath(path)
1669
1663
        try:
1751
1745
    def _reset_data(self):
1752
1746
        """Reset transient data that cannot be revalidated."""
1753
1747
        self._inventory_is_modified = False
1754
 
        result = self._deserialize(self._transport.get('inventory'))
 
1748
        result = self._deserialize(self._control_files.get('inventory'))
1755
1749
        self._set_inventory(result, dirty=False)
1756
1750
 
1757
1751
    @needs_tree_write_lock
1778
1772
 
1779
1773
    def _write_basis_inventory(self, xml):
1780
1774
        """Write the basis inventory XML to the basis-inventory file"""
 
1775
        assert isinstance(xml, str), 'serialised xml must be bytestring.'
1781
1776
        path = self._basis_inventory_name()
1782
1777
        sio = StringIO(xml)
1783
 
        self._transport.put_file(path, sio,
1784
 
            mode=self._control_files._file_mode)
 
1778
        self._control_files.put(path, sio)
1785
1779
 
1786
1780
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
1787
1781
        """Create the text that will be saved in basis-inventory"""
1818
1812
    def read_basis_inventory(self):
1819
1813
        """Read the cached basis inventory."""
1820
1814
        path = self._basis_inventory_name()
1821
 
        return self._transport.get_bytes(path)
 
1815
        return self._control_files.get(path).read()
1822
1816
        
1823
1817
    @needs_read_lock
1824
1818
    def read_working_inventory(self):
1833
1827
        # binary.
1834
1828
        if self._inventory_is_modified:
1835
1829
            raise errors.InventoryModified(self)
1836
 
        result = self._deserialize(self._transport.get('inventory'))
 
1830
        result = self._deserialize(self._control_files.get('inventory'))
1837
1831
        self._set_inventory(result, dirty=False)
1838
1832
        return result
1839
1833
 
1859
1853
            # Recurse directory and add all files
1860
1854
            # so we can check if they have changed.
1861
1855
            for parent_info, file_infos in\
1862
 
                self.walkdirs(directory):
1863
 
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
 
1856
                osutils.walkdirs(self.abspath(directory),
 
1857
                    directory):
 
1858
                for relpath, basename, kind, lstat, abspath in file_infos:
1864
1859
                    # Is it versioned or ignored?
1865
1860
                    if self.path2id(relpath) or self.is_ignored(relpath):
1866
1861
                        # Add nested content for deletion.
1876
1871
            filename = self.relpath(abspath)
1877
1872
            if len(filename) > 0:
1878
1873
                new_files.add(filename)
1879
 
                recurse_directory_to_add_files(filename)
 
1874
                if osutils.isdir(abspath):
 
1875
                    recurse_directory_to_add_files(filename)
1880
1876
 
1881
1877
        files = list(new_files)
1882
1878
 
1891
1887
            has_changed_files = len(unknown_nested_files) > 0
1892
1888
            if not has_changed_files:
1893
1889
                for (file_id, path, content_change, versioned, parent_id, name,
1894
 
                     kind, executable) in self.iter_changes(self.basis_tree(),
 
1890
                     kind, executable) in self._iter_changes(self.basis_tree(),
1895
1891
                         include_unchanged=True, require_versioned=False,
1896
1892
                         want_unversioned=True, specific_files=files):
1897
 
                    if versioned == (False, False):
1898
 
                        # The record is unknown ...
1899
 
                        if not self.is_ignored(path[1]):
1900
 
                            # ... but not ignored
1901
 
                            has_changed_files = True
1902
 
                            break
1903
 
                    elif content_change and (kind[1] is not None):
1904
 
                        # Versioned and changed, but not deleted
 
1893
                    # Check if it's an unknown (but not ignored) OR
 
1894
                    # changed (but not deleted) :
 
1895
                    if not self.is_ignored(path[1]) and (
 
1896
                        versioned == (False, False) or
 
1897
                        content_change and kind[1] != None):
1905
1898
                        has_changed_files = True
1906
1899
                        break
1907
1900
 
1986
1979
                self.set_parent_trees(parent_trees)
1987
1980
                resolve(self)
1988
1981
            else:
1989
 
                resolve(self, filenames, ignore_misses=True, recursive=True)
 
1982
                resolve(self, filenames, ignore_misses=True)
1990
1983
        finally:
1991
1984
            if basis_tree is not None:
1992
1985
                basis_tree.unlock()
2046
2039
        """Set the root id for this tree."""
2047
2040
        # for compatability 
2048
2041
        if file_id is None:
2049
 
            raise ValueError(
2050
 
                'WorkingTree.set_root_id with fileid=None')
2051
 
        file_id = osutils.safe_file_id(file_id)
 
2042
            symbol_versioning.warn(symbol_versioning.zero_twelve
 
2043
                % 'WorkingTree.set_root_id with fileid=None',
 
2044
                DeprecationWarning,
 
2045
                stacklevel=3)
 
2046
            file_id = ROOT_ID
 
2047
        else:
 
2048
            file_id = osutils.safe_file_id(file_id)
2052
2049
        self._set_root_id(file_id)
2053
2050
 
2054
2051
    def _set_root_id(self, file_id):
2113
2110
          basis.
2114
2111
        - Do a 'normal' merge of the old branch basis if it is relevant.
2115
2112
        """
2116
 
        if self.branch.get_bound_location() is not None:
 
2113
        if self.branch.get_master_branch(possible_transports) is not None:
2117
2114
            self.lock_write()
2118
2115
            update_branch = True
2119
2116
        else:
2157
2154
            try:
2158
2155
                to_tree = self.branch.basis_tree()
2159
2156
                if basis.inventory.root is None:
2160
 
                    self.set_root_id(to_tree.get_root_id())
 
2157
                    self.set_root_id(to_tree.inventory.root.file_id)
2161
2158
                    self.flush()
2162
2159
                result += merge.merge_inner(
2163
2160
                                      self.branch,
2305
2302
            current_inv = None
2306
2303
            inv_finished = True
2307
2304
        while not inv_finished or not disk_finished:
2308
 
            if current_disk:
2309
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
2310
 
                    cur_disk_dir_content) = current_disk
2311
 
            else:
2312
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
2313
 
                    cur_disk_dir_content) = ((None, None), None)
2314
2305
            if not disk_finished:
2315
2306
                # strip out .bzr dirs
2316
 
                if (cur_disk_dir_path_from_top[top_strip_len:] == '' and
2317
 
                    len(cur_disk_dir_content) > 0):
2318
 
                    # osutils.walkdirs can be made nicer -
 
2307
                if current_disk[0][1][top_strip_len:] == '':
 
2308
                    # osutils.walkdirs can be made nicer - 
2319
2309
                    # yield the path-from-prefix rather than the pathjoined
2320
2310
                    # value.
2321
 
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
2322
 
                        ('.bzr', '.bzr'))
2323
 
                    if cur_disk_dir_content[bzrdir_loc][0] == '.bzr':
 
2311
                    bzrdir_loc = bisect_left(current_disk[1], ('.bzr', '.bzr'))
 
2312
                    if current_disk[1][bzrdir_loc][0] == '.bzr':
2324
2313
                        # we dont yield the contents of, or, .bzr itself.
2325
 
                        del cur_disk_dir_content[bzrdir_loc]
 
2314
                        del current_disk[1][bzrdir_loc]
2326
2315
            if inv_finished:
2327
2316
                # everything is unknown
2328
2317
                direction = 1
2330
2319
                # everything is missing
2331
2320
                direction = -1
2332
2321
            else:
2333
 
                direction = cmp(current_inv[0][0], cur_disk_dir_relpath)
 
2322
                direction = cmp(current_inv[0][0], current_disk[0][0])
2334
2323
            if direction > 0:
2335
2324
                # disk is before inventory - unknown
2336
2325
                dirblock = [(relpath, basename, kind, stat, None, None) for
2337
 
                    relpath, basename, kind, stat, top_path in
2338
 
                    cur_disk_dir_content]
2339
 
                yield (cur_disk_dir_relpath, None), dirblock
 
2326
                    relpath, basename, kind, stat, top_path in current_disk[1]]
 
2327
                yield (current_disk[0][0], None), dirblock
2340
2328
                try:
2341
2329
                    current_disk = disk_iterator.next()
2342
2330
                except StopIteration:
2344
2332
            elif direction < 0:
2345
2333
                # inventory is before disk - missing.
2346
2334
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
2347
 
                    for relpath, basename, dkind, stat, fileid, kind in
 
2335
                    for relpath, basename, dkind, stat, fileid, kind in 
2348
2336
                    current_inv[1]]
2349
2337
                yield (current_inv[0][0], current_inv[0][1]), dirblock
2350
2338
                try:
2356
2344
                # merge the inventory and disk data together
2357
2345
                dirblock = []
2358
2346
                for relpath, subiterator in itertools.groupby(sorted(
2359
 
                    current_inv[1] + cur_disk_dir_content,
2360
 
                    key=operator.itemgetter(0)), operator.itemgetter(1)):
 
2347
                    current_inv[1] + current_disk[1], key=operator.itemgetter(0)), operator.itemgetter(1)):
2361
2348
                    path_elements = list(subiterator)
2362
2349
                    if len(path_elements) == 2:
2363
2350
                        inv_row, disk_row = path_elements
2416
2403
                relroot = ""
2417
2404
            # FIXME: stash the node in pending
2418
2405
            entry = inv[top_id]
2419
 
            if entry.kind == 'directory':
2420
 
                for name, child in entry.sorted_children():
2421
 
                    dirblock.append((relroot + name, name, child.kind, None,
2422
 
                        child.file_id, child.kind
2423
 
                        ))
 
2406
            for name, child in entry.sorted_children():
 
2407
                dirblock.append((relroot + name, name, child.kind, None,
 
2408
                    child.file_id, child.kind
 
2409
                    ))
2424
2410
            yield (currentdir[0], entry.file_id), dirblock
2425
2411
            # push the user specified dirs from dirblock
2426
2412
            for dir in reversed(dirblock):
2459
2445
        self.set_conflicts(un_resolved)
2460
2446
        return un_resolved, resolved
2461
2447
 
2462
 
    @needs_read_lock
2463
 
    def _check(self):
2464
 
        tree_basis = self.basis_tree()
2465
 
        tree_basis.lock_read()
2466
 
        try:
2467
 
            repo_basis = self.branch.repository.revision_tree(
2468
 
                self.last_revision())
2469
 
            if len(list(repo_basis.iter_changes(tree_basis))) > 0:
2470
 
                raise errors.BzrCheckError(
2471
 
                    "Mismatched basis inventory content.")
2472
 
            self._validate()
2473
 
        finally:
2474
 
            tree_basis.unlock()
2475
 
 
2476
2448
    def _validate(self):
2477
2449
        """Validate internal structures.
2478
2450
 
2484
2456
        """
2485
2457
        return
2486
2458
 
2487
 
    @needs_read_lock
2488
 
    def _get_rules_searcher(self, default_searcher):
2489
 
        """See Tree._get_rules_searcher."""
2490
 
        if self._rules_searcher is None:
2491
 
            self._rules_searcher = super(WorkingTree,
2492
 
                self)._get_rules_searcher(default_searcher)
2493
 
        return self._rules_searcher
2494
 
 
2495
2459
 
2496
2460
class WorkingTree2(WorkingTree):
2497
2461
    """This is the Format 2 working tree.
2557
2521
    def _last_revision(self):
2558
2522
        """See Mutable.last_revision."""
2559
2523
        try:
2560
 
            return self._transport.get_bytes('last-revision')
 
2524
            return self._control_files.get('last-revision').read()
2561
2525
        except errors.NoSuchFile:
2562
2526
            return _mod_revision.NULL_REVISION
2563
2527
 
2565
2529
        """See WorkingTree._change_last_revision."""
2566
2530
        if revision_id is None or revision_id == NULL_REVISION:
2567
2531
            try:
2568
 
                self._transport.delete('last-revision')
 
2532
                self._control_files._transport.delete('last-revision')
2569
2533
            except errors.NoSuchFile:
2570
2534
                pass
2571
2535
            return False
2572
2536
        else:
2573
 
            self._transport.put_bytes('last-revision', revision_id,
2574
 
                mode=self._control_files._file_mode)
 
2537
            self._control_files.put_bytes('last-revision', revision_id)
2575
2538
            return True
2576
2539
 
2577
2540
    @needs_tree_write_lock
2589
2552
    @needs_read_lock
2590
2553
    def conflicts(self):
2591
2554
        try:
2592
 
            confile = self._transport.get('conflicts')
 
2555
            confile = self._control_files.get('conflicts')
2593
2556
        except errors.NoSuchFile:
2594
2557
            return _mod_conflicts.ConflictList()
2595
2558
        try:
2620
2583
            return path[:-len(suffix)]
2621
2584
 
2622
2585
 
 
2586
@deprecated_function(zero_eight)
 
2587
def is_control_file(filename):
 
2588
    """See WorkingTree.is_control_filename(filename)."""
 
2589
    ## FIXME: better check
 
2590
    filename = normpath(filename)
 
2591
    while filename != '':
 
2592
        head, tail = os.path.split(filename)
 
2593
        ## mutter('check %r for control file' % ((head, tail),))
 
2594
        if tail == '.bzr':
 
2595
            return True
 
2596
        if filename == head:
 
2597
            break
 
2598
        filename = head
 
2599
    return False
 
2600
 
 
2601
 
2623
2602
class WorkingTreeFormat(object):
2624
2603
    """An encapsulation of the initialization and open routines for a format.
2625
2604
 
2658
2637
        except errors.NoSuchFile:
2659
2638
            raise errors.NoWorkingTree(base=transport.base)
2660
2639
        except KeyError:
2661
 
            raise errors.UnknownFormatError(format=format_string,
2662
 
                                            kind="working tree")
 
2640
            raise errors.UnknownFormatError(format=format_string)
2663
2641
 
2664
2642
    def __eq__(self, other):
2665
2643
        return self.__class__ is other.__class__
2699
2677
 
2700
2678
    @classmethod
2701
2679
    def unregister_format(klass, format):
 
2680
        assert klass._formats[format.get_format_string()] is format
2702
2681
        del klass._formats[format.get_format_string()]
2703
2682
 
2704
2683
 
2714
2693
        """See WorkingTreeFormat.get_format_description()."""
2715
2694
        return "Working tree format 2"
2716
2695
 
2717
 
    def _stub_initialize_on_transport(self, transport, file_mode):
2718
 
        """Workaround: create control files for a remote working tree.
2719
 
 
 
2696
    def stub_initialize_remote(self, control_files):
 
2697
        """As a special workaround create critical control files for a remote working tree
 
2698
        
2720
2699
        This ensures that it can later be updated and dealt with locally,
2721
 
        since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
 
2700
        since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with 
2722
2701
        no working tree.  (See bug #43064).
2723
2702
        """
2724
2703
        sio = StringIO()
2725
2704
        inv = Inventory()
2726
2705
        xml5.serializer_v5.write_inventory(inv, sio, working=True)
2727
2706
        sio.seek(0)
2728
 
        transport.put_file('inventory', sio, file_mode)
2729
 
        transport.put_bytes('pending-merges', '', file_mode)
2730
 
 
2731
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
2732
 
                   accelerator_tree=None, hardlink=False):
 
2707
        control_files.put('inventory', sio)
 
2708
 
 
2709
        control_files.put_bytes('pending-merges', '')
 
2710
        
 
2711
 
 
2712
    def initialize(self, a_bzrdir, revision_id=None):
2733
2713
        """See WorkingTreeFormat.initialize()."""
2734
2714
        if not isinstance(a_bzrdir.transport, LocalTransport):
2735
2715
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2736
 
        if from_branch is not None:
2737
 
            branch = from_branch
2738
 
        else:
2739
 
            branch = a_bzrdir.open_branch()
 
2716
        branch = a_bzrdir.open_branch()
2740
2717
        if revision_id is None:
2741
2718
            revision_id = _mod_revision.ensure_null(branch.last_revision())
2742
2719
        branch.lock_write()
2753
2730
                         _bzrdir=a_bzrdir)
2754
2731
        basis_tree = branch.repository.revision_tree(revision_id)
2755
2732
        if basis_tree.inventory.root is not None:
2756
 
            wt.set_root_id(basis_tree.get_root_id())
 
2733
            wt.set_root_id(basis_tree.inventory.root.file_id)
2757
2734
        # set the parent list and cache the basis tree.
2758
2735
        if _mod_revision.is_null(revision_id):
2759
2736
            parent_trees = []
2821
2798
        return LockableFiles(transport, self._lock_file_name, 
2822
2799
                             self._lock_class)
2823
2800
 
2824
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
2825
 
                   accelerator_tree=None, hardlink=False):
 
2801
    def initialize(self, a_bzrdir, revision_id=None):
2826
2802
        """See WorkingTreeFormat.initialize().
2827
2803
        
2828
 
        :param revision_id: if supplied, create a working tree at a different
2829
 
            revision than the branch is at.
2830
 
        :param accelerator_tree: A tree which can be used for retrieving file
2831
 
            contents more quickly than the revision tree, i.e. a workingtree.
2832
 
            The revision tree will be used for cases where accelerator_tree's
2833
 
            content is different.
2834
 
        :param hardlink: If true, hard-link files from accelerator_tree,
2835
 
            where possible.
 
2804
        revision_id allows creating a working tree at a different
 
2805
        revision than the branch is at.
2836
2806
        """
2837
2807
        if not isinstance(a_bzrdir.transport, LocalTransport):
2838
2808
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2840
2810
        control_files = self._open_control_files(a_bzrdir)
2841
2811
        control_files.create_lock()
2842
2812
        control_files.lock_write()
2843
 
        transport.put_bytes('format', self.get_format_string(),
2844
 
            mode=control_files._file_mode)
2845
 
        if from_branch is not None:
2846
 
            branch = from_branch
2847
 
        else:
2848
 
            branch = a_bzrdir.open_branch()
 
2813
        control_files.put_utf8('format', self.get_format_string())
 
2814
        branch = a_bzrdir.open_branch()
2849
2815
        if revision_id is None:
2850
2816
            revision_id = _mod_revision.ensure_null(branch.last_revision())
2851
2817
        # WorkingTree3 can handle an inventory which has a unique root id.
2866
2832
            basis_tree = branch.repository.revision_tree(revision_id)
2867
2833
            # only set an explicit root id if there is one to set.
2868
2834
            if basis_tree.inventory.root is not None:
2869
 
                wt.set_root_id(basis_tree.get_root_id())
 
2835
                wt.set_root_id(basis_tree.inventory.root.file_id)
2870
2836
            if revision_id == NULL_REVISION:
2871
2837
                wt.set_parent_trees([])
2872
2838
            else: