/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 bzrlib/workingtree.py

  • Committer: Kent Gibson
  • Date: 2007-03-07 14:49:00 UTC
  • mfrom: (2324 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2350.
  • Revision ID: warthog618@gmail.com-20070307144900-6bt7twg47zul3w0w
merged bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
41
41
 
42
42
from bzrlib.lazy_import import lazy_import
43
43
lazy_import(globals(), """
 
44
from bisect import bisect_left
44
45
import collections
45
46
from copy import deepcopy
46
47
import errno
 
48
import itertools
 
49
import operator
47
50
import stat
48
51
from time import time
49
52
import warnings
51
54
 
52
55
import bzrlib
53
56
from bzrlib import (
 
57
    branch,
54
58
    bzrdir,
55
59
    conflicts as _mod_conflicts,
 
60
    dirstate,
56
61
    errors,
57
62
    generate_ids,
58
63
    globbing,
60
65
    ignores,
61
66
    merge,
62
67
    osutils,
 
68
    revisiontree,
 
69
    repository,
63
70
    textui,
64
71
    transform,
65
72
    urlutils,
66
73
    xml5,
67
74
    xml6,
 
75
    xml7,
68
76
    )
69
77
import bzrlib.branch
70
78
from bzrlib.transport import get_transport
71
79
import bzrlib.ui
 
80
from bzrlib.workingtree_4 import WorkingTreeFormat4
72
81
""")
73
82
 
74
83
from bzrlib import symbol_versioning
75
84
from bzrlib.decorators import needs_read_lock, needs_write_lock
76
 
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
 
85
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, TreeReference
77
86
from bzrlib.lockable_files import LockableFiles, TransportLock
78
87
from bzrlib.lockdir import LockDir
79
88
import bzrlib.mutabletree
92
101
    )
93
102
from bzrlib.trace import mutter, note
94
103
from bzrlib.transport.local import LocalTransport
95
 
import bzrlib.tree
96
104
from bzrlib.progress import DummyProgress, ProgressPhase
97
105
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
98
 
import bzrlib.revisiontree
99
106
from bzrlib.rio import RioReader, rio_file, Stanza
100
107
from bzrlib.symbol_versioning import (deprecated_passed,
101
108
        deprecated_method,
298
305
        self._control_files.break_lock()
299
306
        self.branch.break_lock()
300
307
 
 
308
    def requires_rich_root(self):
 
309
        return self._format.requires_rich_root
 
310
 
 
311
    def supports_tree_reference(self):
 
312
        return False
 
313
 
301
314
    def _set_inventory(self, inv, dirty):
302
315
        """Set the internal cached inventory.
303
316
 
349
362
        """
350
363
        return WorkingTree.open(path, _unsupported=True)
351
364
 
 
365
    # should be deprecated - this is slow and in any case treating them as a
 
366
    # container is (we now know) bad style -- mbp 20070302
 
367
    ## @deprecated_method(zero_fifteen)
352
368
    def __iter__(self):
353
369
        """Iterate through file_ids for this tree.
354
370
 
380
396
            # in the future this should return the tree for
381
397
            # 'empty:' - the implicit root empty tree.
382
398
            return self.branch.repository.revision_tree(None)
383
 
        else:
384
 
            try:
385
 
                xml = self.read_basis_inventory()
386
 
                inv = xml6.serializer_v6.read_inventory_from_string(xml)
387
 
                if inv is not None and inv.revision_id == revision_id:
388
 
                    return bzrlib.revisiontree.RevisionTree(
389
 
                        self.branch.repository, inv, revision_id)
390
 
            except (errors.NoSuchFile, errors.BadInventoryFormat):
391
 
                pass
 
399
        try:
 
400
            return self.revision_tree(revision_id)
 
401
        except errors.NoSuchRevision:
 
402
            pass
392
403
        # No cached copy available, retrieve from the repository.
393
404
        # FIXME? RBC 20060403 should we cache the inventory locally
394
405
        # at this point ?
463
474
    def get_file_byname(self, filename):
464
475
        return file(self.abspath(filename), 'rb')
465
476
 
 
477
    @needs_read_lock
466
478
    def annotate_iter(self, file_id):
467
479
        """See Tree.annotate_iter
468
480
 
475
487
        """
476
488
        file_id = osutils.safe_file_id(file_id)
477
489
        basis = self.basis_tree()
478
 
        changes = self._iter_changes(basis, True, [file_id]).next()
479
 
        changed_content, kind = changes[2], changes[6]
480
 
        if not changed_content:
481
 
            return basis.annotate_iter(file_id)
482
 
        if kind[1] is None:
483
 
            return None
484
 
        import annotate
485
 
        if kind[0] != 'file':
486
 
            old_lines = []
487
 
        else:
488
 
            old_lines = list(basis.annotate_iter(file_id))
489
 
        old = [old_lines]
490
 
        for tree in self.branch.repository.revision_trees(
491
 
            self.get_parent_ids()[1:]):
492
 
            if file_id not in tree:
493
 
                continue
494
 
            old.append(list(tree.annotate_iter(file_id)))
495
 
        return annotate.reannotate(old, self.get_file(file_id).readlines(),
496
 
                                   CURRENT_REVISION)
 
490
        basis.lock_read()
 
491
        try:
 
492
            changes = self._iter_changes(basis, True, [self.id2path(file_id)],
 
493
                require_versioned=True).next()
 
494
            changed_content, kind = changes[2], changes[6]
 
495
            if not changed_content:
 
496
                return basis.annotate_iter(file_id)
 
497
            if kind[1] is None:
 
498
                return None
 
499
            import annotate
 
500
            if kind[0] != 'file':
 
501
                old_lines = []
 
502
            else:
 
503
                old_lines = list(basis.annotate_iter(file_id))
 
504
            old = [old_lines]
 
505
            for tree in self.branch.repository.revision_trees(
 
506
                self.get_parent_ids()[1:]):
 
507
                if file_id not in tree:
 
508
                    continue
 
509
                old.append(list(tree.annotate_iter(file_id)))
 
510
            return annotate.reannotate(old, self.get_file(file_id).readlines(),
 
511
                                       CURRENT_REVISION)
 
512
        finally:
 
513
            basis.unlock()
497
514
 
498
515
    def get_parent_ids(self):
499
516
        """See Tree.get_parent_ids.
568
585
    def has_id(self, file_id):
569
586
        # files that have been deleted are excluded
570
587
        file_id = osutils.safe_file_id(file_id)
571
 
        inv = self._inventory
 
588
        inv = self.inventory
572
589
        if not inv.has_id(file_id):
573
590
            return False
574
591
        path = inv.id2path(file_id)
596
613
    def get_file_mtime(self, file_id, path=None):
597
614
        file_id = osutils.safe_file_id(file_id)
598
615
        if not path:
599
 
            path = self._inventory.id2path(file_id)
 
616
            path = self.inventory.id2path(file_id)
600
617
        return os.lstat(self.abspath(path)).st_mtime
601
618
 
602
619
    if not supports_executable():
607
624
        def is_executable(self, file_id, path=None):
608
625
            if not path:
609
626
                file_id = osutils.safe_file_id(file_id)
610
 
                path = self._inventory.id2path(file_id)
 
627
                path = self.id2path(file_id)
611
628
            mode = os.lstat(self.abspath(path)).st_mode
612
629
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
613
630
 
614
 
    @needs_write_lock
 
631
    @needs_tree_write_lock
615
632
    def _add(self, files, ids, kinds):
616
633
        """See MutableTree._add."""
617
634
        # TODO: Re-adding a file that is removed in the working copy
820
837
            merger.other_basis = merger.other_rev_id
821
838
            merger.other_tree = self.branch.repository.revision_tree(
822
839
                merger.other_rev_id)
 
840
            merger.other_branch = branch
823
841
            merger.pp.next_phase()
824
842
            merger.find_base()
825
843
            if merger.base_rev_id == merger.other_rev_id:
857
875
        except StopIteration:
858
876
            raise errors.MergeModifiedFormatError()
859
877
        for s in RioReader(hashfile):
860
 
            file_id = s.get("file_id")
 
878
            # RioReader reads in Unicode, so convert file_ids back to utf8
 
879
            file_id = osutils.safe_file_id(s.get("file_id"), warn=False)
861
880
            if file_id not in self.inventory:
862
881
                continue
863
882
            text_hash = s.get("hash")
875
894
        return file_id
876
895
 
877
896
    def get_symlink_target(self, file_id):
 
897
        file_id = osutils.safe_file_id(file_id)
878
898
        return os.readlink(self.id2abspath(file_id))
879
899
 
880
 
    def file_class(self, filename):
881
 
        if self.path2id(filename):
882
 
            return 'V'
883
 
        elif self.is_ignored(filename):
884
 
            return 'I'
885
 
        else:
886
 
            return '?'
 
900
    @needs_write_lock
 
901
    def subsume(self, other_tree):
 
902
        def add_children(inventory, entry):
 
903
            for child_entry in entry.children.values():
 
904
                inventory._byid[child_entry.file_id] = child_entry
 
905
                if child_entry.kind == 'directory':
 
906
                    add_children(inventory, child_entry)
 
907
        if other_tree.get_root_id() == self.get_root_id():
 
908
            raise errors.BadSubsumeSource(self, other_tree,
 
909
                                          'Trees have the same root')
 
910
        try:
 
911
            other_tree_path = self.relpath(other_tree.basedir)
 
912
        except errors.PathNotChild:
 
913
            raise errors.BadSubsumeSource(self, other_tree,
 
914
                'Tree is not contained by the other')
 
915
        new_root_parent = self.path2id(osutils.dirname(other_tree_path))
 
916
        if new_root_parent is None:
 
917
            raise errors.BadSubsumeSource(self, other_tree,
 
918
                'Parent directory is not versioned.')
 
919
        # We need to ensure that the result of a fetch will have a
 
920
        # versionedfile for the other_tree root, and only fetching into
 
921
        # RepositoryKnit2 guarantees that.
 
922
        if not self.branch.repository.supports_rich_root():
 
923
            raise errors.SubsumeTargetNeedsUpgrade(other_tree)
 
924
        other_tree.lock_tree_write()
 
925
        try:
 
926
            new_parents = other_tree.get_parent_ids()
 
927
            other_root = other_tree.inventory.root
 
928
            other_root.parent_id = new_root_parent
 
929
            other_root.name = osutils.basename(other_tree_path)
 
930
            self.inventory.add(other_root)
 
931
            add_children(self.inventory, other_root)
 
932
            self._write_inventory(self.inventory)
 
933
            # normally we don't want to fetch whole repositories, but i think
 
934
            # here we really do want to consolidate the whole thing.
 
935
            for parent_id in other_tree.get_parent_ids():
 
936
                self.branch.fetch(other_tree.branch, parent_id)
 
937
                self.add_parent_tree_id(parent_id)
 
938
        finally:
 
939
            other_tree.unlock()
 
940
        other_tree.bzrdir.retire_bzrdir()
 
941
 
 
942
    @needs_tree_write_lock
 
943
    def extract(self, file_id, format=None):
 
944
        """Extract a subtree from this tree.
 
945
        
 
946
        A new branch will be created, relative to the path for this tree.
 
947
        """
 
948
        def mkdirs(path):
 
949
            segments = osutils.splitpath(path)
 
950
            transport = self.branch.bzrdir.root_transport
 
951
            for name in segments:
 
952
                transport = transport.clone(name)
 
953
                try:
 
954
                    transport.mkdir('.')
 
955
                except errors.FileExists:
 
956
                    pass
 
957
            return transport
 
958
            
 
959
        sub_path = self.id2path(file_id)
 
960
        branch_transport = mkdirs(sub_path)
 
961
        if format is None:
 
962
            format = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
 
963
        try:
 
964
            branch_transport.mkdir('.')
 
965
        except errors.FileExists:
 
966
            pass
 
967
        branch_bzrdir = format.initialize_on_transport(branch_transport)
 
968
        try:
 
969
            repo = branch_bzrdir.find_repository()
 
970
        except errors.NoRepositoryPresent:
 
971
            repo = branch_bzrdir.create_repository()
 
972
            assert repo.supports_rich_root()
 
973
        else:
 
974
            if not repo.supports_rich_root():
 
975
                raise errors.RootNotRich()
 
976
        new_branch = branch_bzrdir.create_branch()
 
977
        new_branch.pull(self.branch)
 
978
        for parent_id in self.get_parent_ids():
 
979
            new_branch.fetch(self.branch, parent_id)
 
980
        tree_transport = self.bzrdir.root_transport.clone(sub_path)
 
981
        if tree_transport.base != branch_transport.base:
 
982
            tree_bzrdir = format.initialize_on_transport(tree_transport)
 
983
            branch.BranchReferenceFormat().initialize(tree_bzrdir, new_branch)
 
984
        else:
 
985
            tree_bzrdir = branch_bzrdir
 
986
        wt = tree_bzrdir.create_workingtree(NULL_REVISION)
 
987
        wt.set_parent_ids(self.get_parent_ids())
 
988
        my_inv = self.inventory
 
989
        child_inv = Inventory(root_id=None)
 
990
        new_root = my_inv[file_id]
 
991
        my_inv.remove_recursive_id(file_id)
 
992
        new_root.parent_id = None
 
993
        child_inv.add(new_root)
 
994
        self._write_inventory(my_inv)
 
995
        wt._write_inventory(child_inv)
 
996
        return wt
 
997
 
 
998
    def _serialize(self, inventory, out_file):
 
999
        xml5.serializer_v5.write_inventory(self._inventory, out_file)
 
1000
 
 
1001
    def _deserialize(selt, in_file):
 
1002
        return xml5.serializer_v5.read_inventory(in_file)
887
1003
 
888
1004
    def flush(self):
889
1005
        """Write the in memory inventory to disk."""
891
1007
        if self._control_files._lock_mode != 'w':
892
1008
            raise errors.NotWriteLocked(self)
893
1009
        sio = StringIO()
894
 
        xml5.serializer_v5.write_inventory(self._inventory, sio)
 
1010
        self._serialize(self._inventory, sio)
895
1011
        sio.seek(0)
896
1012
        self._control_files.put('inventory', sio)
897
1013
        self._inventory_is_modified = False
906
1022
 
907
1023
        Skips the control directory.
908
1024
        """
909
 
        inv = self._inventory
 
1025
        # list_files is an iterator, so @needs_read_lock doesn't work properly
 
1026
        # with it. So callers should be careful to always read_lock the tree.
 
1027
        if not self.is_locked():
 
1028
            raise errors.ObjectNotLocked(self)
 
1029
 
 
1030
        inv = self.inventory
910
1031
        if include_root is True:
911
1032
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
912
1033
        # Convert these into local objects to save lookup times
1294
1415
        These are files in the working directory that are not versioned or
1295
1416
        control files or ignored.
1296
1417
        """
1297
 
        for subp in self.extras():
1298
 
            if not self.is_ignored(subp):
1299
 
                yield subp
 
1418
        # force the extras method to be fully executed before returning, to 
 
1419
        # prevent race conditions with the lock
 
1420
        return iter(
 
1421
            [subp for subp in self.extras() if not self.is_ignored(subp)])
1300
1422
    
1301
1423
    @needs_tree_write_lock
1302
1424
    def unversion(self, file_ids):
1358
1480
                pp.next_phase()
1359
1481
                repository = self.branch.repository
1360
1482
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1483
                basis_tree.lock_read()
1361
1484
                try:
1362
1485
                    new_basis_tree = self.branch.basis_tree()
1363
1486
                    merge.merge_inner(
1372
1495
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1373
1496
                finally:
1374
1497
                    pb.finished()
 
1498
                    basis_tree.unlock()
1375
1499
                # TODO - dedup parents list with things merged by pull ?
1376
1500
                # reuse the revisiontree we merged against to set the new
1377
1501
                # tree data.
1379
1503
                # we have to pull the merge trees out again, because 
1380
1504
                # merge_inner has set the ids. - this corner is not yet 
1381
1505
                # layered well enough to prevent double handling.
 
1506
                # XXX TODO: Fix the double handling: telling the tree about
 
1507
                # the already known parent data is wasteful.
1382
1508
                merges = self.get_parent_ids()[1:]
1383
1509
                parent_trees.extend([
1384
1510
                    (parent, repository.revision_tree(parent)) for
1401
1527
        # TODO: update the hashcache here ?
1402
1528
 
1403
1529
    def extras(self):
1404
 
        """Yield all unknown files in this WorkingTree.
 
1530
        """Yield all unversioned files in this WorkingTree.
1405
1531
 
1406
 
        If there are any unknown directories then only the directory is
1407
 
        returned, not all its children.  But if there are unknown files
 
1532
        If there are any unversioned directories then only the directory is
 
1533
        returned, not all its children.  But if there are unversioned files
1408
1534
        under a versioned subdirectory, they are returned.
1409
1535
 
1410
1536
        Currently returned depth-first, sorted by name within directories.
 
1537
        This is the same order used by 'osutils.walkdirs'.
1411
1538
        """
1412
1539
        ## TODO: Work from given directory downwards
1413
1540
        for path, dir_entry in self.inventory.directories():
1434
1561
                subp = pathjoin(path, subf)
1435
1562
                yield subp
1436
1563
 
1437
 
 
1438
1564
    def ignored_files(self):
1439
1565
        """Yield list of PATH, IGNORE_PATTERN"""
1440
1566
        for subp in self.extras():
1531
1657
 
1532
1658
    def lock_read(self):
1533
1659
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1660
        if not self.is_locked():
 
1661
            self._reset_data()
1534
1662
        self.branch.lock_read()
1535
1663
        try:
1536
1664
            return self._control_files.lock_read()
1540
1668
 
1541
1669
    def lock_tree_write(self):
1542
1670
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
1671
        if not self.is_locked():
 
1672
            self._reset_data()
1543
1673
        self.branch.lock_read()
1544
1674
        try:
1545
1675
            return self._control_files.lock_write()
1549
1679
 
1550
1680
    def lock_write(self):
1551
1681
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
1682
        if not self.is_locked():
 
1683
            self._reset_data()
1552
1684
        self.branch.lock_write()
1553
1685
        try:
1554
1686
            return self._control_files.lock_write()
1562
1694
    def _basis_inventory_name(self):
1563
1695
        return 'basis-inventory-cache'
1564
1696
 
 
1697
    def _reset_data(self):
 
1698
        """Reset transient data that cannot be revalidated."""
 
1699
        self._inventory_is_modified = False
 
1700
        result = self._deserialize(self._control_files.get('inventory'))
 
1701
        self._set_inventory(result, dirty=False)
 
1702
 
1565
1703
    @needs_tree_write_lock
1566
1704
    def set_last_revision(self, new_revision):
1567
1705
        """Change the last revision in the working tree."""
1598
1736
        #       as all callers should have already converted the revision_id to
1599
1737
        #       utf8
1600
1738
        inventory.revision_id = osutils.safe_revision_id(revision_id)
1601
 
        return xml6.serializer_v6.write_inventory_to_string(inventory)
 
1739
        return xml7.serializer_v7.write_inventory_to_string(inventory)
1602
1740
 
1603
1741
    def _cache_basis_inventory(self, new_revision):
1604
1742
        """Cache new_revision as the basis inventory."""
1619
1757
            xml = self.branch.repository.get_inventory_xml(new_revision)
1620
1758
            firstline = xml.split('\n', 1)[0]
1621
1759
            if (not 'revision_id="' in firstline or 
1622
 
                'format="6"' not in firstline):
 
1760
                'format="7"' not in firstline):
1623
1761
                inv = self.branch.repository.deserialise_inventory(
1624
1762
                    new_revision, xml)
1625
1763
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
1645
1783
        # binary.
1646
1784
        if self._inventory_is_modified:
1647
1785
            raise errors.InventoryModified(self)
1648
 
        result = xml5.serializer_v5.read_inventory(
1649
 
            self._control_files.get('inventory'))
 
1786
        result = self._deserialize(self._control_files.get('inventory'))
1650
1787
        self._set_inventory(result, dirty=False)
1651
1788
        return result
1652
1789
 
1705
1842
            resolve(self, filenames, ignore_misses=True)
1706
1843
        return conflicts
1707
1844
 
 
1845
    def revision_tree(self, revision_id):
 
1846
        """See Tree.revision_tree.
 
1847
 
 
1848
        WorkingTree can supply revision_trees for the basis revision only
 
1849
        because there is only one cached inventory in the bzr directory.
 
1850
        """
 
1851
        if revision_id == self.last_revision():
 
1852
            try:
 
1853
                xml = self.read_basis_inventory()
 
1854
            except errors.NoSuchFile:
 
1855
                pass
 
1856
            else:
 
1857
                try:
 
1858
                    inv = xml7.serializer_v7.read_inventory_from_string(xml)
 
1859
                    # dont use the repository revision_tree api because we want
 
1860
                    # to supply the inventory.
 
1861
                    if inv.revision_id == revision_id:
 
1862
                        return revisiontree.RevisionTree(self.branch.repository,
 
1863
                            inv, revision_id)
 
1864
                except errors.BadInventoryFormat:
 
1865
                    pass
 
1866
        # raise if there was no inventory, or if we read the wrong inventory.
 
1867
        raise errors.NoSuchRevisionInTree(self, revision_id)
 
1868
 
1708
1869
    # XXX: This method should be deprecated in favour of taking in a proper
1709
1870
    # new Inventory object.
1710
1871
    @needs_tree_write_lock
1742
1903
            file_id = ROOT_ID
1743
1904
        else:
1744
1905
            file_id = osutils.safe_file_id(file_id)
 
1906
        self._set_root_id(file_id)
 
1907
 
 
1908
    def _set_root_id(self, file_id):
 
1909
        """Set the root id for this tree, in a format specific manner.
 
1910
 
 
1911
        :param file_id: The file id to assign to the root. It must not be 
 
1912
            present in the current inventory or an error will occur. It must
 
1913
            not be None, but rather a valid file id.
 
1914
        """
1745
1915
        inv = self._inventory
1746
1916
        orig_root_id = inv.root.file_id
1747
1917
        # TODO: it might be nice to exit early if there was nothing
1837
2007
        if last_rev != self.branch.last_revision():
1838
2008
            # merge tree state up to new branch tip.
1839
2009
            basis = self.basis_tree()
1840
 
            to_tree = self.branch.basis_tree()
1841
 
            if basis.inventory.root is None:
1842
 
                self.set_root_id(to_tree.inventory.root.file_id)
1843
 
            result += merge.merge_inner(
1844
 
                                  self.branch,
1845
 
                                  to_tree,
1846
 
                                  basis,
1847
 
                                  this_tree=self)
 
2010
            basis.lock_read()
 
2011
            try:
 
2012
                to_tree = self.branch.basis_tree()
 
2013
                if basis.inventory.root is None:
 
2014
                    self.set_root_id(to_tree.inventory.root.file_id)
 
2015
                    self.flush()
 
2016
                result += merge.merge_inner(
 
2017
                                      self.branch,
 
2018
                                      to_tree,
 
2019
                                      basis,
 
2020
                                      this_tree=self)
 
2021
            finally:
 
2022
                basis.unlock()
1848
2023
            # TODO - dedup parents list with things merged by pull ?
1849
2024
            # reuse the tree we've updated to to set the basis:
1850
2025
            parent_trees = [(self.branch.last_revision(), to_tree)]
1873
2048
            # and we have converted that last revision to a pending merge.
1874
2049
            # base is somewhere between the branch tip now
1875
2050
            # and the now pending merge
 
2051
 
 
2052
            # Since we just modified the working tree and inventory, flush out
 
2053
            # the current state, before we modify it again.
 
2054
            # TODO: jam 20070214 WorkingTree3 doesn't require this, dirstate
 
2055
            #       requires it only because TreeTransform directly munges the
 
2056
            #       inventory and calls tree._write_inventory(). Ultimately we
 
2057
            #       should be able to remove this extra flush.
 
2058
            self.flush()
1876
2059
            from bzrlib.revision import common_ancestor
1877
2060
            try:
1878
2061
                base_rev_id = common_ancestor(self.branch.last_revision(),
1941
2124
                             file_id=self.path2id(conflicted)))
1942
2125
        return conflicts
1943
2126
 
 
2127
    def walkdirs(self, prefix=""):
 
2128
        """Walk the directories of this tree.
 
2129
 
 
2130
        This API returns a generator, which is only valid during the current
 
2131
        tree transaction - within a single lock_read or lock_write duration.
 
2132
 
 
2133
        If the tree is not locked, it may cause an error to be raised, depending
 
2134
        on the tree implementation.
 
2135
        """
 
2136
        disk_top = self.abspath(prefix)
 
2137
        if disk_top.endswith('/'):
 
2138
            disk_top = disk_top[:-1]
 
2139
        top_strip_len = len(disk_top) + 1
 
2140
        inventory_iterator = self._walkdirs(prefix)
 
2141
        disk_iterator = osutils.walkdirs(disk_top, prefix)
 
2142
        try:
 
2143
            current_disk = disk_iterator.next()
 
2144
            disk_finished = False
 
2145
        except OSError, e:
 
2146
            if e.errno != errno.ENOENT:
 
2147
                raise
 
2148
            current_disk = None
 
2149
            disk_finished = True
 
2150
        try:
 
2151
            current_inv = inventory_iterator.next()
 
2152
            inv_finished = False
 
2153
        except StopIteration:
 
2154
            current_inv = None
 
2155
            inv_finished = True
 
2156
        while not inv_finished or not disk_finished:
 
2157
            if not disk_finished:
 
2158
                # strip out .bzr dirs
 
2159
                if current_disk[0][1][top_strip_len:] == '':
 
2160
                    # osutils.walkdirs can be made nicer - 
 
2161
                    # yield the path-from-prefix rather than the pathjoined
 
2162
                    # value.
 
2163
                    bzrdir_loc = bisect_left(current_disk[1], ('.bzr', '.bzr'))
 
2164
                    if current_disk[1][bzrdir_loc][0] == '.bzr':
 
2165
                        # we dont yield the contents of, or, .bzr itself.
 
2166
                        del current_disk[1][bzrdir_loc]
 
2167
            if inv_finished:
 
2168
                # everything is unknown
 
2169
                direction = 1
 
2170
            elif disk_finished:
 
2171
                # everything is missing
 
2172
                direction = -1
 
2173
            else:
 
2174
                direction = cmp(current_inv[0][0], current_disk[0][0])
 
2175
            if direction > 0:
 
2176
                # disk is before inventory - unknown
 
2177
                dirblock = [(relpath, basename, kind, stat, None, None) for
 
2178
                    relpath, basename, kind, stat, top_path in current_disk[1]]
 
2179
                yield (current_disk[0][0], None), dirblock
 
2180
                try:
 
2181
                    current_disk = disk_iterator.next()
 
2182
                except StopIteration:
 
2183
                    disk_finished = True
 
2184
            elif direction < 0:
 
2185
                # inventory is before disk - missing.
 
2186
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
 
2187
                    for relpath, basename, dkind, stat, fileid, kind in 
 
2188
                    current_inv[1]]
 
2189
                yield (current_inv[0][0], current_inv[0][1]), dirblock
 
2190
                try:
 
2191
                    current_inv = inventory_iterator.next()
 
2192
                except StopIteration:
 
2193
                    inv_finished = True
 
2194
            else:
 
2195
                # versioned present directory
 
2196
                # merge the inventory and disk data together
 
2197
                dirblock = []
 
2198
                for relpath, subiterator in itertools.groupby(sorted(
 
2199
                    current_inv[1] + current_disk[1], key=operator.itemgetter(0)), operator.itemgetter(1)):
 
2200
                    path_elements = list(subiterator)
 
2201
                    if len(path_elements) == 2:
 
2202
                        inv_row, disk_row = path_elements
 
2203
                        # versioned, present file
 
2204
                        dirblock.append((inv_row[0],
 
2205
                            inv_row[1], disk_row[2],
 
2206
                            disk_row[3], inv_row[4],
 
2207
                            inv_row[5]))
 
2208
                    elif len(path_elements[0]) == 5:
 
2209
                        # unknown disk file
 
2210
                        dirblock.append((path_elements[0][0],
 
2211
                            path_elements[0][1], path_elements[0][2],
 
2212
                            path_elements[0][3], None, None))
 
2213
                    elif len(path_elements[0]) == 6:
 
2214
                        # versioned, absent file.
 
2215
                        dirblock.append((path_elements[0][0],
 
2216
                            path_elements[0][1], 'unknown', None,
 
2217
                            path_elements[0][4], path_elements[0][5]))
 
2218
                    else:
 
2219
                        raise NotImplementedError('unreachable code')
 
2220
                yield current_inv[0], dirblock
 
2221
                try:
 
2222
                    current_inv = inventory_iterator.next()
 
2223
                except StopIteration:
 
2224
                    inv_finished = True
 
2225
                try:
 
2226
                    current_disk = disk_iterator.next()
 
2227
                except StopIteration:
 
2228
                    disk_finished = True
 
2229
 
 
2230
    def _walkdirs(self, prefix=""):
 
2231
        _directory = 'directory'
 
2232
        # get the root in the inventory
 
2233
        inv = self.inventory
 
2234
        top_id = inv.path2id(prefix)
 
2235
        if top_id is None:
 
2236
            pending = []
 
2237
        else:
 
2238
            pending = [(prefix, '', _directory, None, top_id, None)]
 
2239
        while pending:
 
2240
            dirblock = []
 
2241
            currentdir = pending.pop()
 
2242
            # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
 
2243
            top_id = currentdir[4]
 
2244
            if currentdir[0]:
 
2245
                relroot = currentdir[0] + '/'
 
2246
            else:
 
2247
                relroot = ""
 
2248
            # FIXME: stash the node in pending
 
2249
            entry = inv[top_id]
 
2250
            for name, child in entry.sorted_children():
 
2251
                dirblock.append((relroot + name, name, child.kind, None,
 
2252
                    child.file_id, child.kind
 
2253
                    ))
 
2254
            yield (currentdir[0], entry.file_id), dirblock
 
2255
            # push the user specified dirs from dirblock
 
2256
            for dir in reversed(dirblock):
 
2257
                if dir[2] == _directory:
 
2258
                    pending.append(dir)
 
2259
 
1944
2260
    @needs_tree_write_lock
1945
2261
    def auto_resolve(self):
1946
2262
        """Automatically resolve text conflicts according to contents.
2084
2400
        if path.endswith(suffix):
2085
2401
            return path[:-len(suffix)]
2086
2402
 
 
2403
 
2087
2404
@deprecated_function(zero_eight)
2088
2405
def is_control_file(filename):
2089
2406
    """See WorkingTree.is_control_filename(filename)."""
2124
2441
    _formats = {}
2125
2442
    """The known formats."""
2126
2443
 
 
2444
    requires_rich_root = False
 
2445
 
2127
2446
    @classmethod
2128
2447
    def find_format(klass, a_bzrdir):
2129
2448
        """Return the format for the working tree object in a_bzrdir."""
2136
2455
        except KeyError:
2137
2456
            raise errors.UnknownFormatError(format=format_string)
2138
2457
 
 
2458
    def __eq__(self, other):
 
2459
        return self.__class__ is other.__class__
 
2460
 
 
2461
    def __ne__(self, other):
 
2462
        return not (self == other)
 
2463
 
2139
2464
    @classmethod
2140
2465
    def get_default_format(klass):
2141
2466
        """Return the current default format."""
2276
2601
    _lock_file_name = 'lock'
2277
2602
    _lock_class = LockDir
2278
2603
 
 
2604
    _tree_class = WorkingTree3
 
2605
 
 
2606
    def __get_matchingbzrdir(self):
 
2607
        return bzrdir.BzrDirMetaFormat1()
 
2608
 
 
2609
    _matchingbzrdir = property(__get_matchingbzrdir)
 
2610
 
2279
2611
    def _open_control_files(self, a_bzrdir):
2280
2612
        transport = a_bzrdir.get_workingtree_transport(None)
2281
2613
        return LockableFiles(transport, self._lock_file_name, 
2304
2636
        # those trees. And because there isn't a format bump inbetween, we
2305
2637
        # are maintaining compatibility with older clients.
2306
2638
        # inv = Inventory(root_id=gen_root_id())
2307
 
        inv = Inventory()
2308
 
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
2639
        inv = self._initial_inventory()
 
2640
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
2309
2641
                         branch,
2310
2642
                         inv,
2311
2643
                         _internal=True,
2330
2662
            wt.unlock()
2331
2663
        return wt
2332
2664
 
 
2665
    def _initial_inventory(self):
 
2666
        return Inventory()
 
2667
 
2333
2668
    def __init__(self):
2334
2669
        super(WorkingTreeFormat3, self).__init__()
2335
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2336
2670
 
2337
2671
    def open(self, a_bzrdir, _found=False):
2338
2672
        """Return the WorkingTree object for a_bzrdir
2353
2687
        :param a_bzrdir: the dir for the tree.
2354
2688
        :param control_files: the control files for the tree.
2355
2689
        """
2356
 
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2357
 
                           _internal=True,
2358
 
                           _format=self,
2359
 
                           _bzrdir=a_bzrdir,
2360
 
                           _control_files=control_files)
 
2690
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
2691
                                _internal=True,
 
2692
                                _format=self,
 
2693
                                _bzrdir=a_bzrdir,
 
2694
                                _control_files=control_files)
2361
2695
 
2362
2696
    def __str__(self):
2363
2697
        return self.get_format_string()
2364
2698
 
2365
2699
 
 
2700
__default_format = WorkingTreeFormat4()
 
2701
WorkingTreeFormat.register_format(__default_format)
 
2702
WorkingTreeFormat.register_format(WorkingTreeFormat3())
 
2703
WorkingTreeFormat.set_default_format(__default_format)
2366
2704
# formats which have no format string are not discoverable
2367
2705
# and not independently creatable, so are not registered.
2368
 
__default_format = WorkingTreeFormat3()
2369
 
WorkingTreeFormat.register_format(__default_format)
2370
 
WorkingTreeFormat.set_default_format(__default_format)
2371
2706
_legacy_formats = [WorkingTreeFormat2(),
2372
2707
                   ]
2373
2708