~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-03-07 10:45:44 UTC
  • mfrom: (2321.1.2 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070307104544-59e3e6358e4bdb29
(robertc) Merge dirstate and subtrees. (Robert Collins, Martin Pool, Aaaron Bentley, John A Meinel, James Westby)

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:
876
894
        return file_id
877
895
 
878
896
    def get_symlink_target(self, file_id):
 
897
        file_id = osutils.safe_file_id(file_id)
879
898
        return os.readlink(self.id2abspath(file_id))
880
899
 
881
 
    def file_class(self, filename):
882
 
        if self.path2id(filename):
883
 
            return 'V'
884
 
        elif self.is_ignored(filename):
885
 
            return 'I'
886
 
        else:
887
 
            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)
888
1003
 
889
1004
    def flush(self):
890
1005
        """Write the in memory inventory to disk."""
892
1007
        if self._control_files._lock_mode != 'w':
893
1008
            raise errors.NotWriteLocked(self)
894
1009
        sio = StringIO()
895
 
        xml5.serializer_v5.write_inventory(self._inventory, sio)
 
1010
        self._serialize(self._inventory, sio)
896
1011
        sio.seek(0)
897
1012
        self._control_files.put('inventory', sio)
898
1013
        self._inventory_is_modified = False
907
1022
 
908
1023
        Skips the control directory.
909
1024
        """
910
 
        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
911
1031
        if include_root is True:
912
1032
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
913
1033
        # Convert these into local objects to save lookup times
1295
1415
        These are files in the working directory that are not versioned or
1296
1416
        control files or ignored.
1297
1417
        """
1298
 
        for subp in self.extras():
1299
 
            if not self.is_ignored(subp):
1300
 
                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)])
1301
1422
    
1302
1423
    @needs_tree_write_lock
1303
1424
    def unversion(self, file_ids):
1359
1480
                pp.next_phase()
1360
1481
                repository = self.branch.repository
1361
1482
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1483
                basis_tree.lock_read()
1362
1484
                try:
1363
1485
                    new_basis_tree = self.branch.basis_tree()
1364
1486
                    merge.merge_inner(
1373
1495
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1374
1496
                finally:
1375
1497
                    pb.finished()
 
1498
                    basis_tree.unlock()
1376
1499
                # TODO - dedup parents list with things merged by pull ?
1377
1500
                # reuse the revisiontree we merged against to set the new
1378
1501
                # tree data.
1380
1503
                # we have to pull the merge trees out again, because 
1381
1504
                # merge_inner has set the ids. - this corner is not yet 
1382
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.
1383
1508
                merges = self.get_parent_ids()[1:]
1384
1509
                parent_trees.extend([
1385
1510
                    (parent, repository.revision_tree(parent)) for
1402
1527
        # TODO: update the hashcache here ?
1403
1528
 
1404
1529
    def extras(self):
1405
 
        """Yield all unknown files in this WorkingTree.
 
1530
        """Yield all unversioned files in this WorkingTree.
1406
1531
 
1407
 
        If there are any unknown directories then only the directory is
1408
 
        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
1409
1534
        under a versioned subdirectory, they are returned.
1410
1535
 
1411
1536
        Currently returned depth-first, sorted by name within directories.
 
1537
        This is the same order used by 'osutils.walkdirs'.
1412
1538
        """
1413
1539
        ## TODO: Work from given directory downwards
1414
1540
        for path, dir_entry in self.inventory.directories():
1435
1561
                subp = pathjoin(path, subf)
1436
1562
                yield subp
1437
1563
 
1438
 
 
1439
1564
    def ignored_files(self):
1440
1565
        """Yield list of PATH, IGNORE_PATTERN"""
1441
1566
        for subp in self.extras():
1532
1657
 
1533
1658
    def lock_read(self):
1534
1659
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1660
        if not self.is_locked():
 
1661
            self._reset_data()
1535
1662
        self.branch.lock_read()
1536
1663
        try:
1537
1664
            return self._control_files.lock_read()
1541
1668
 
1542
1669
    def lock_tree_write(self):
1543
1670
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
1671
        if not self.is_locked():
 
1672
            self._reset_data()
1544
1673
        self.branch.lock_read()
1545
1674
        try:
1546
1675
            return self._control_files.lock_write()
1550
1679
 
1551
1680
    def lock_write(self):
1552
1681
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
1682
        if not self.is_locked():
 
1683
            self._reset_data()
1553
1684
        self.branch.lock_write()
1554
1685
        try:
1555
1686
            return self._control_files.lock_write()
1563
1694
    def _basis_inventory_name(self):
1564
1695
        return 'basis-inventory-cache'
1565
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
 
1566
1703
    @needs_tree_write_lock
1567
1704
    def set_last_revision(self, new_revision):
1568
1705
        """Change the last revision in the working tree."""
1599
1736
        #       as all callers should have already converted the revision_id to
1600
1737
        #       utf8
1601
1738
        inventory.revision_id = osutils.safe_revision_id(revision_id)
1602
 
        return xml6.serializer_v6.write_inventory_to_string(inventory)
 
1739
        return xml7.serializer_v7.write_inventory_to_string(inventory)
1603
1740
 
1604
1741
    def _cache_basis_inventory(self, new_revision):
1605
1742
        """Cache new_revision as the basis inventory."""
1620
1757
            xml = self.branch.repository.get_inventory_xml(new_revision)
1621
1758
            firstline = xml.split('\n', 1)[0]
1622
1759
            if (not 'revision_id="' in firstline or 
1623
 
                'format="6"' not in firstline):
 
1760
                'format="7"' not in firstline):
1624
1761
                inv = self.branch.repository.deserialise_inventory(
1625
1762
                    new_revision, xml)
1626
1763
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
1646
1783
        # binary.
1647
1784
        if self._inventory_is_modified:
1648
1785
            raise errors.InventoryModified(self)
1649
 
        result = xml5.serializer_v5.read_inventory(
1650
 
            self._control_files.get('inventory'))
 
1786
        result = self._deserialize(self._control_files.get('inventory'))
1651
1787
        self._set_inventory(result, dirty=False)
1652
1788
        return result
1653
1789
 
1706
1842
            resolve(self, filenames, ignore_misses=True)
1707
1843
        return conflicts
1708
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
 
1709
1869
    # XXX: This method should be deprecated in favour of taking in a proper
1710
1870
    # new Inventory object.
1711
1871
    @needs_tree_write_lock
1743
1903
            file_id = ROOT_ID
1744
1904
        else:
1745
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
        """
1746
1915
        inv = self._inventory
1747
1916
        orig_root_id = inv.root.file_id
1748
1917
        # TODO: it might be nice to exit early if there was nothing
1838
2007
        if last_rev != self.branch.last_revision():
1839
2008
            # merge tree state up to new branch tip.
1840
2009
            basis = self.basis_tree()
1841
 
            to_tree = self.branch.basis_tree()
1842
 
            if basis.inventory.root is None:
1843
 
                self.set_root_id(to_tree.inventory.root.file_id)
1844
 
            result += merge.merge_inner(
1845
 
                                  self.branch,
1846
 
                                  to_tree,
1847
 
                                  basis,
1848
 
                                  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()
1849
2023
            # TODO - dedup parents list with things merged by pull ?
1850
2024
            # reuse the tree we've updated to to set the basis:
1851
2025
            parent_trees = [(self.branch.last_revision(), to_tree)]
1874
2048
            # and we have converted that last revision to a pending merge.
1875
2049
            # base is somewhere between the branch tip now
1876
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()
1877
2059
            from bzrlib.revision import common_ancestor
1878
2060
            try:
1879
2061
                base_rev_id = common_ancestor(self.branch.last_revision(),
1942
2124
                             file_id=self.path2id(conflicted)))
1943
2125
        return conflicts
1944
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
 
1945
2260
    @needs_tree_write_lock
1946
2261
    def auto_resolve(self):
1947
2262
        """Automatically resolve text conflicts according to contents.
2085
2400
        if path.endswith(suffix):
2086
2401
            return path[:-len(suffix)]
2087
2402
 
 
2403
 
2088
2404
@deprecated_function(zero_eight)
2089
2405
def is_control_file(filename):
2090
2406
    """See WorkingTree.is_control_filename(filename)."""
2125
2441
    _formats = {}
2126
2442
    """The known formats."""
2127
2443
 
 
2444
    requires_rich_root = False
 
2445
 
2128
2446
    @classmethod
2129
2447
    def find_format(klass, a_bzrdir):
2130
2448
        """Return the format for the working tree object in a_bzrdir."""
2137
2455
        except KeyError:
2138
2456
            raise errors.UnknownFormatError(format=format_string)
2139
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
 
2140
2464
    @classmethod
2141
2465
    def get_default_format(klass):
2142
2466
        """Return the current default format."""
2277
2601
    _lock_file_name = 'lock'
2278
2602
    _lock_class = LockDir
2279
2603
 
 
2604
    _tree_class = WorkingTree3
 
2605
 
 
2606
    def __get_matchingbzrdir(self):
 
2607
        return bzrdir.BzrDirMetaFormat1()
 
2608
 
 
2609
    _matchingbzrdir = property(__get_matchingbzrdir)
 
2610
 
2280
2611
    def _open_control_files(self, a_bzrdir):
2281
2612
        transport = a_bzrdir.get_workingtree_transport(None)
2282
2613
        return LockableFiles(transport, self._lock_file_name, 
2305
2636
        # those trees. And because there isn't a format bump inbetween, we
2306
2637
        # are maintaining compatibility with older clients.
2307
2638
        # inv = Inventory(root_id=gen_root_id())
2308
 
        inv = Inventory()
2309
 
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
2639
        inv = self._initial_inventory()
 
2640
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
2310
2641
                         branch,
2311
2642
                         inv,
2312
2643
                         _internal=True,
2331
2662
            wt.unlock()
2332
2663
        return wt
2333
2664
 
 
2665
    def _initial_inventory(self):
 
2666
        return Inventory()
 
2667
 
2334
2668
    def __init__(self):
2335
2669
        super(WorkingTreeFormat3, self).__init__()
2336
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2337
2670
 
2338
2671
    def open(self, a_bzrdir, _found=False):
2339
2672
        """Return the WorkingTree object for a_bzrdir
2354
2687
        :param a_bzrdir: the dir for the tree.
2355
2688
        :param control_files: the control files for the tree.
2356
2689
        """
2357
 
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2358
 
                           _internal=True,
2359
 
                           _format=self,
2360
 
                           _bzrdir=a_bzrdir,
2361
 
                           _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)
2362
2695
 
2363
2696
    def __str__(self):
2364
2697
        return self.get_format_string()
2365
2698
 
2366
2699
 
 
2700
__default_format = WorkingTreeFormat4()
 
2701
WorkingTreeFormat.register_format(__default_format)
 
2702
WorkingTreeFormat.register_format(WorkingTreeFormat3())
 
2703
WorkingTreeFormat.set_default_format(__default_format)
2367
2704
# formats which have no format string are not discoverable
2368
2705
# and not independently creatable, so are not registered.
2369
 
__default_format = WorkingTreeFormat3()
2370
 
WorkingTreeFormat.register_format(__default_format)
2371
 
WorkingTreeFormat.set_default_format(__default_format)
2372
2706
_legacy_formats = [WorkingTreeFormat2(),
2373
2707
                   ]
2374
2708