/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

Merge from bzr.dev, resolving the worst of the semantic conflicts, but there's
still a little bit of breakage.

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
 
53
import re
50
54
 
51
55
import bzrlib
52
56
from bzrlib import (
 
57
    branch,
53
58
    bzrdir,
54
59
    conflicts as _mod_conflicts,
 
60
    dirstate,
55
61
    errors,
56
62
    generate_ids,
57
63
    globbing,
59
65
    ignores,
60
66
    merge,
61
67
    osutils,
 
68
    revisiontree,
 
69
    repository,
62
70
    textui,
63
71
    transform,
64
72
    urlutils,
65
73
    xml5,
66
74
    xml6,
 
75
    xml7,
67
76
    )
68
77
import bzrlib.branch
69
78
from bzrlib.transport import get_transport
70
79
import bzrlib.ui
 
80
from bzrlib.workingtree_4 import WorkingTreeFormat4
71
81
""")
72
82
 
73
83
from bzrlib import symbol_versioning
74
84
from bzrlib.decorators import needs_read_lock, needs_write_lock
75
 
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
 
85
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, TreeReference
76
86
from bzrlib.lockable_files import LockableFiles, TransportLock
77
87
from bzrlib.lockdir import LockDir
78
88
import bzrlib.mutabletree
91
101
    )
92
102
from bzrlib.trace import mutter, note
93
103
from bzrlib.transport.local import LocalTransport
94
 
import bzrlib.tree
95
104
from bzrlib.progress import DummyProgress, ProgressPhase
96
105
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
97
 
import bzrlib.revisiontree
98
106
from bzrlib.rio import RioReader, rio_file, Stanza
99
107
from bzrlib.symbol_versioning import (deprecated_passed,
100
108
        deprecated_method,
297
305
        self._control_files.break_lock()
298
306
        self.branch.break_lock()
299
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
 
300
314
    def _set_inventory(self, inv, dirty):
301
315
        """Set the internal cached inventory.
302
316
 
348
362
        """
349
363
        return WorkingTree.open(path, _unsupported=True)
350
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)
351
368
    def __iter__(self):
352
369
        """Iterate through file_ids for this tree.
353
370
 
379
396
            # in the future this should return the tree for
380
397
            # 'empty:' - the implicit root empty tree.
381
398
            return self.branch.repository.revision_tree(None)
382
 
        else:
383
 
            try:
384
 
                xml = self.read_basis_inventory()
385
 
                inv = xml6.serializer_v6.read_inventory_from_string(xml)
386
 
                if inv is not None and inv.revision_id == revision_id:
387
 
                    return bzrlib.revisiontree.RevisionTree(
388
 
                        self.branch.repository, inv, revision_id)
389
 
            except (errors.NoSuchFile, errors.BadInventoryFormat):
390
 
                pass
 
399
        try:
 
400
            return self.revision_tree(revision_id)
 
401
        except errors.NoSuchRevision:
 
402
            pass
391
403
        # No cached copy available, retrieve from the repository.
392
404
        # FIXME? RBC 20060403 should we cache the inventory locally
393
405
        # at this point ?
462
474
    def get_file_byname(self, filename):
463
475
        return file(self.abspath(filename), 'rb')
464
476
 
 
477
    @needs_read_lock
465
478
    def annotate_iter(self, file_id):
466
479
        """See Tree.annotate_iter
467
480
 
474
487
        """
475
488
        file_id = osutils.safe_file_id(file_id)
476
489
        basis = self.basis_tree()
477
 
        changes = self._iter_changes(basis, True, [file_id]).next()
478
 
        changed_content, kind = changes[2], changes[6]
479
 
        if not changed_content:
480
 
            return basis.annotate_iter(file_id)
481
 
        if kind[1] is None:
482
 
            return None
483
 
        import annotate
484
 
        if kind[0] != 'file':
485
 
            old_lines = []
486
 
        else:
487
 
            old_lines = list(basis.annotate_iter(file_id))
488
 
        old = [old_lines]
489
 
        for tree in self.branch.repository.revision_trees(
490
 
            self.get_parent_ids()[1:]):
491
 
            if file_id not in tree:
492
 
                continue
493
 
            old.append(list(tree.annotate_iter(file_id)))
494
 
        return annotate.reannotate(old, self.get_file(file_id).readlines(),
495
 
                                   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()
496
514
 
497
515
    def get_parent_ids(self):
498
516
        """See Tree.get_parent_ids.
567
585
    def has_id(self, file_id):
568
586
        # files that have been deleted are excluded
569
587
        file_id = osutils.safe_file_id(file_id)
570
 
        inv = self._inventory
 
588
        inv = self.inventory
571
589
        if not inv.has_id(file_id):
572
590
            return False
573
591
        path = inv.id2path(file_id)
595
613
    def get_file_mtime(self, file_id, path=None):
596
614
        file_id = osutils.safe_file_id(file_id)
597
615
        if not path:
598
 
            path = self._inventory.id2path(file_id)
 
616
            path = self.inventory.id2path(file_id)
599
617
        return os.lstat(self.abspath(path)).st_mtime
600
618
 
601
619
    if not supports_executable():
606
624
        def is_executable(self, file_id, path=None):
607
625
            if not path:
608
626
                file_id = osutils.safe_file_id(file_id)
609
 
                path = self._inventory.id2path(file_id)
 
627
                path = self.id2path(file_id)
610
628
            mode = os.lstat(self.abspath(path)).st_mode
611
629
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
612
630
 
613
 
    @needs_write_lock
 
631
    @needs_tree_write_lock
614
632
    def _add(self, files, ids, kinds):
615
633
        """See MutableTree._add."""
616
634
        # TODO: Re-adding a file that is removed in the working copy
819
837
            merger.other_basis = merger.other_rev_id
820
838
            merger.other_tree = self.branch.repository.revision_tree(
821
839
                merger.other_rev_id)
 
840
            merger.other_branch = branch
822
841
            merger.pp.next_phase()
823
842
            merger.find_base()
824
843
            if merger.base_rev_id == merger.other_rev_id:
856
875
        except StopIteration:
857
876
            raise errors.MergeModifiedFormatError()
858
877
        for s in RioReader(hashfile):
859
 
            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)
860
880
            if file_id not in self.inventory:
861
881
                continue
862
882
            text_hash = s.get("hash")
874
894
        return file_id
875
895
 
876
896
    def get_symlink_target(self, file_id):
 
897
        file_id = osutils.safe_file_id(file_id)
877
898
        return os.readlink(self.id2abspath(file_id))
878
899
 
879
 
    def file_class(self, filename):
880
 
        if self.path2id(filename):
881
 
            return 'V'
882
 
        elif self.is_ignored(filename):
883
 
            return 'I'
884
 
        else:
885
 
            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
        self.flush()
 
949
        def mkdirs(path):
 
950
            segments = osutils.splitpath(path)
 
951
            transport = self.branch.bzrdir.root_transport
 
952
            for name in segments:
 
953
                transport = transport.clone(name)
 
954
                try:
 
955
                    transport.mkdir('.')
 
956
                except errors.FileExists:
 
957
                    pass
 
958
            return transport
 
959
            
 
960
        sub_path = self.id2path(file_id)
 
961
        branch_transport = mkdirs(sub_path)
 
962
        if format is None:
 
963
            format = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
 
964
        try:
 
965
            branch_transport.mkdir('.')
 
966
        except errors.FileExists:
 
967
            pass
 
968
        branch_bzrdir = format.initialize_on_transport(branch_transport)
 
969
        try:
 
970
            repo = branch_bzrdir.find_repository()
 
971
        except errors.NoRepositoryPresent:
 
972
            repo = branch_bzrdir.create_repository()
 
973
            assert repo.supports_rich_root()
 
974
        else:
 
975
            if not repo.supports_rich_root():
 
976
                raise errors.RootNotRich()
 
977
        new_branch = branch_bzrdir.create_branch()
 
978
        new_branch.pull(self.branch)
 
979
        for parent_id in self.get_parent_ids():
 
980
            new_branch.fetch(self.branch, parent_id)
 
981
        tree_transport = self.bzrdir.root_transport.clone(sub_path)
 
982
        if tree_transport.base != branch_transport.base:
 
983
            tree_bzrdir = format.initialize_on_transport(tree_transport)
 
984
            branch.BranchReferenceFormat().initialize(tree_bzrdir, new_branch)
 
985
        else:
 
986
            tree_bzrdir = branch_bzrdir
 
987
        wt = tree_bzrdir.create_workingtree(NULL_REVISION)
 
988
        wt.set_parent_ids(self.get_parent_ids())
 
989
        my_inv = self.inventory
 
990
        child_inv = Inventory(root_id=None)
 
991
        new_root = my_inv[file_id]
 
992
        my_inv.remove_recursive_id(file_id)
 
993
        new_root.parent_id = None
 
994
        child_inv.add(new_root)
 
995
        self._write_inventory(my_inv)
 
996
        wt._write_inventory(child_inv)
 
997
        return wt
 
998
 
 
999
    def _serialize(self, inventory, out_file):
 
1000
        xml5.serializer_v5.write_inventory(self._inventory, out_file)
 
1001
 
 
1002
    def _deserialize(selt, in_file):
 
1003
        return xml5.serializer_v5.read_inventory(in_file)
886
1004
 
887
1005
    def flush(self):
888
1006
        """Write the in memory inventory to disk."""
890
1008
        if self._control_files._lock_mode != 'w':
891
1009
            raise errors.NotWriteLocked(self)
892
1010
        sio = StringIO()
893
 
        xml5.serializer_v5.write_inventory(self._inventory, sio)
 
1011
        self._serialize(self._inventory, sio)
894
1012
        sio.seek(0)
895
1013
        self._control_files.put('inventory', sio)
896
1014
        self._inventory_is_modified = False
897
1015
 
 
1016
    def _kind(self, relpath):
 
1017
        return osutils.file_kind(self.abspath(relpath))
 
1018
 
898
1019
    def list_files(self, include_root=False):
899
1020
        """Recursively list all files as (path, class, kind, id, entry).
900
1021
 
905
1026
 
906
1027
        Skips the control directory.
907
1028
        """
908
 
        inv = self._inventory
 
1029
        # list_files is an iterator, so @needs_read_lock doesn't work properly
 
1030
        # with it. So callers should be careful to always read_lock the tree.
 
1031
        if not self.is_locked():
 
1032
            raise errors.ObjectNotLocked(self)
 
1033
 
 
1034
        inv = self.inventory
909
1035
        if include_root is True:
910
1036
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
911
1037
        # Convert these into local objects to save lookup times
912
1038
        pathjoin = osutils.pathjoin
913
 
        file_kind = osutils.file_kind
 
1039
        file_kind = self._kind
914
1040
 
915
1041
        # transport.base ends in a slash, we want the piece
916
1042
        # between the last two slashes
976
1102
 
977
1103
                fk = file_kind(fap)
978
1104
 
979
 
                if f_ie:
980
 
                    if f_ie.kind != fk:
981
 
                        raise errors.BzrCheckError(
982
 
                            "file %r entered as kind %r id %r, now of kind %r"
983
 
                            % (fap, f_ie.kind, f_ie.file_id, fk))
984
 
 
985
1105
                # make a last minute entry
986
1106
                if f_ie:
987
1107
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
1293
1413
        These are files in the working directory that are not versioned or
1294
1414
        control files or ignored.
1295
1415
        """
1296
 
        for subp in self.extras():
1297
 
            if not self.is_ignored(subp):
1298
 
                yield subp
 
1416
        # force the extras method to be fully executed before returning, to 
 
1417
        # prevent race conditions with the lock
 
1418
        return iter(
 
1419
            [subp for subp in self.extras() if not self.is_ignored(subp)])
1299
1420
    
1300
1421
    @needs_tree_write_lock
1301
1422
    def unversion(self, file_ids):
1357
1478
                pp.next_phase()
1358
1479
                repository = self.branch.repository
1359
1480
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1481
                basis_tree.lock_read()
1360
1482
                try:
1361
1483
                    new_basis_tree = self.branch.basis_tree()
1362
1484
                    merge.merge_inner(
1371
1493
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1372
1494
                finally:
1373
1495
                    pb.finished()
 
1496
                    basis_tree.unlock()
1374
1497
                # TODO - dedup parents list with things merged by pull ?
1375
1498
                # reuse the revisiontree we merged against to set the new
1376
1499
                # tree data.
1378
1501
                # we have to pull the merge trees out again, because 
1379
1502
                # merge_inner has set the ids. - this corner is not yet 
1380
1503
                # layered well enough to prevent double handling.
 
1504
                # XXX TODO: Fix the double handling: telling the tree about
 
1505
                # the already known parent data is wasteful.
1381
1506
                merges = self.get_parent_ids()[1:]
1382
1507
                parent_trees.extend([
1383
1508
                    (parent, repository.revision_tree(parent)) for
1400
1525
        # TODO: update the hashcache here ?
1401
1526
 
1402
1527
    def extras(self):
1403
 
        """Yield all unknown files in this WorkingTree.
 
1528
        """Yield all unversioned files in this WorkingTree.
1404
1529
 
1405
 
        If there are any unknown directories then only the directory is
1406
 
        returned, not all its children.  But if there are unknown files
 
1530
        If there are any unversioned directories then only the directory is
 
1531
        returned, not all its children.  But if there are unversioned files
1407
1532
        under a versioned subdirectory, they are returned.
1408
1533
 
1409
1534
        Currently returned depth-first, sorted by name within directories.
 
1535
        This is the same order used by 'osutils.walkdirs'.
1410
1536
        """
1411
1537
        ## TODO: Work from given directory downwards
1412
1538
        for path, dir_entry in self.inventory.directories():
1433
1559
                subp = pathjoin(path, subf)
1434
1560
                yield subp
1435
1561
 
1436
 
 
1437
1562
    def ignored_files(self):
1438
1563
        """Yield list of PATH, IGNORE_PATTERN"""
1439
1564
        for subp in self.extras():
1530
1655
 
1531
1656
    def lock_read(self):
1532
1657
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1658
        if not self.is_locked():
 
1659
            self._reset_data()
1533
1660
        self.branch.lock_read()
1534
1661
        try:
1535
1662
            return self._control_files.lock_read()
1539
1666
 
1540
1667
    def lock_tree_write(self):
1541
1668
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
1669
        if not self.is_locked():
 
1670
            self._reset_data()
1542
1671
        self.branch.lock_read()
1543
1672
        try:
1544
1673
            return self._control_files.lock_write()
1548
1677
 
1549
1678
    def lock_write(self):
1550
1679
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
1680
        if not self.is_locked():
 
1681
            self._reset_data()
1551
1682
        self.branch.lock_write()
1552
1683
        try:
1553
1684
            return self._control_files.lock_write()
1561
1692
    def _basis_inventory_name(self):
1562
1693
        return 'basis-inventory-cache'
1563
1694
 
 
1695
    def _reset_data(self):
 
1696
        """Reset transient data that cannot be revalidated."""
 
1697
        self._inventory_is_modified = False
 
1698
        result = self._deserialize(self._control_files.get('inventory'))
 
1699
        self._set_inventory(result, dirty=False)
 
1700
 
1564
1701
    @needs_tree_write_lock
1565
1702
    def set_last_revision(self, new_revision):
1566
1703
        """Change the last revision in the working tree."""
1597
1734
        #       as all callers should have already converted the revision_id to
1598
1735
        #       utf8
1599
1736
        inventory.revision_id = osutils.safe_revision_id(revision_id)
1600
 
        return xml6.serializer_v6.write_inventory_to_string(inventory)
 
1737
        return xml7.serializer_v7.write_inventory_to_string(inventory)
1601
1738
 
1602
1739
    def _cache_basis_inventory(self, new_revision):
1603
1740
        """Cache new_revision as the basis inventory."""
1618
1755
            xml = self.branch.repository.get_inventory_xml(new_revision)
1619
1756
            firstline = xml.split('\n', 1)[0]
1620
1757
            if (not 'revision_id="' in firstline or 
1621
 
                'format="6"' not in firstline):
 
1758
                'format="7"' not in firstline):
1622
1759
                inv = self.branch.repository.deserialise_inventory(
1623
1760
                    new_revision, xml)
1624
1761
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
1644
1781
        # binary.
1645
1782
        if self._inventory_is_modified:
1646
1783
            raise errors.InventoryModified(self)
1647
 
        result = xml5.serializer_v5.read_inventory(
1648
 
            self._control_files.get('inventory'))
 
1784
        result = self._deserialize(self._control_files.get('inventory'))
1649
1785
        self._set_inventory(result, dirty=False)
1650
1786
        return result
1651
1787
 
1704
1840
            resolve(self, filenames, ignore_misses=True)
1705
1841
        return conflicts
1706
1842
 
 
1843
    def revision_tree(self, revision_id):
 
1844
        """See Tree.revision_tree.
 
1845
 
 
1846
        WorkingTree can supply revision_trees for the basis revision only
 
1847
        because there is only one cached inventory in the bzr directory.
 
1848
        """
 
1849
        if revision_id == self.last_revision():
 
1850
            try:
 
1851
                xml = self.read_basis_inventory()
 
1852
            except errors.NoSuchFile:
 
1853
                pass
 
1854
            else:
 
1855
                try:
 
1856
                    inv = xml7.serializer_v7.read_inventory_from_string(xml)
 
1857
                    # dont use the repository revision_tree api because we want
 
1858
                    # to supply the inventory.
 
1859
                    if inv.revision_id == revision_id:
 
1860
                        return revisiontree.RevisionTree(self.branch.repository,
 
1861
                            inv, revision_id)
 
1862
                except errors.BadInventoryFormat:
 
1863
                    pass
 
1864
        # raise if there was no inventory, or if we read the wrong inventory.
 
1865
        raise errors.NoSuchRevisionInTree(self, revision_id)
 
1866
 
1707
1867
    # XXX: This method should be deprecated in favour of taking in a proper
1708
1868
    # new Inventory object.
1709
1869
    @needs_tree_write_lock
1741
1901
            file_id = ROOT_ID
1742
1902
        else:
1743
1903
            file_id = osutils.safe_file_id(file_id)
 
1904
        self._set_root_id(file_id)
 
1905
 
 
1906
    def _set_root_id(self, file_id):
 
1907
        """Set the root id for this tree, in a format specific manner.
 
1908
 
 
1909
        :param file_id: The file id to assign to the root. It must not be 
 
1910
            present in the current inventory or an error will occur. It must
 
1911
            not be None, but rather a valid file id.
 
1912
        """
1744
1913
        inv = self._inventory
1745
1914
        orig_root_id = inv.root.file_id
1746
1915
        # TODO: it might be nice to exit early if there was nothing
1836
2005
        if last_rev != self.branch.last_revision():
1837
2006
            # merge tree state up to new branch tip.
1838
2007
            basis = self.basis_tree()
1839
 
            to_tree = self.branch.basis_tree()
1840
 
            if basis.inventory.root is None:
1841
 
                self.set_root_id(to_tree.inventory.root.file_id)
1842
 
            result += merge.merge_inner(
1843
 
                                  self.branch,
1844
 
                                  to_tree,
1845
 
                                  basis,
1846
 
                                  this_tree=self)
 
2008
            basis.lock_read()
 
2009
            try:
 
2010
                to_tree = self.branch.basis_tree()
 
2011
                if basis.inventory.root is None:
 
2012
                    self.set_root_id(to_tree.inventory.root.file_id)
 
2013
                    self.flush()
 
2014
                result += merge.merge_inner(
 
2015
                                      self.branch,
 
2016
                                      to_tree,
 
2017
                                      basis,
 
2018
                                      this_tree=self)
 
2019
            finally:
 
2020
                basis.unlock()
1847
2021
            # TODO - dedup parents list with things merged by pull ?
1848
2022
            # reuse the tree we've updated to to set the basis:
1849
2023
            parent_trees = [(self.branch.last_revision(), to_tree)]
1872
2046
            # and we have converted that last revision to a pending merge.
1873
2047
            # base is somewhere between the branch tip now
1874
2048
            # and the now pending merge
 
2049
 
 
2050
            # Since we just modified the working tree and inventory, flush out
 
2051
            # the current state, before we modify it again.
 
2052
            # TODO: jam 20070214 WorkingTree3 doesn't require this, dirstate
 
2053
            #       requires it only because TreeTransform directly munges the
 
2054
            #       inventory and calls tree._write_inventory(). Ultimately we
 
2055
            #       should be able to remove this extra flush.
 
2056
            self.flush()
1875
2057
            from bzrlib.revision import common_ancestor
1876
2058
            try:
1877
2059
                base_rev_id = common_ancestor(self.branch.last_revision(),
1940
2122
                             file_id=self.path2id(conflicted)))
1941
2123
        return conflicts
1942
2124
 
 
2125
    def walkdirs(self, prefix=""):
 
2126
        """Walk the directories of this tree.
 
2127
 
 
2128
        This API returns a generator, which is only valid during the current
 
2129
        tree transaction - within a single lock_read or lock_write duration.
 
2130
 
 
2131
        If the tree is not locked, it may cause an error to be raised, depending
 
2132
        on the tree implementation.
 
2133
        """
 
2134
        disk_top = self.abspath(prefix)
 
2135
        if disk_top.endswith('/'):
 
2136
            disk_top = disk_top[:-1]
 
2137
        top_strip_len = len(disk_top) + 1
 
2138
        inventory_iterator = self._walkdirs(prefix)
 
2139
        disk_iterator = osutils.walkdirs(disk_top, prefix)
 
2140
        try:
 
2141
            current_disk = disk_iterator.next()
 
2142
            disk_finished = False
 
2143
        except OSError, e:
 
2144
            if e.errno != errno.ENOENT:
 
2145
                raise
 
2146
            current_disk = None
 
2147
            disk_finished = True
 
2148
        try:
 
2149
            current_inv = inventory_iterator.next()
 
2150
            inv_finished = False
 
2151
        except StopIteration:
 
2152
            current_inv = None
 
2153
            inv_finished = True
 
2154
        while not inv_finished or not disk_finished:
 
2155
            if not disk_finished:
 
2156
                # strip out .bzr dirs
 
2157
                if current_disk[0][1][top_strip_len:] == '':
 
2158
                    # osutils.walkdirs can be made nicer - 
 
2159
                    # yield the path-from-prefix rather than the pathjoined
 
2160
                    # value.
 
2161
                    bzrdir_loc = bisect_left(current_disk[1], ('.bzr', '.bzr'))
 
2162
                    if current_disk[1][bzrdir_loc][0] == '.bzr':
 
2163
                        # we dont yield the contents of, or, .bzr itself.
 
2164
                        del current_disk[1][bzrdir_loc]
 
2165
            if inv_finished:
 
2166
                # everything is unknown
 
2167
                direction = 1
 
2168
            elif disk_finished:
 
2169
                # everything is missing
 
2170
                direction = -1
 
2171
            else:
 
2172
                direction = cmp(current_inv[0][0], current_disk[0][0])
 
2173
            if direction > 0:
 
2174
                # disk is before inventory - unknown
 
2175
                dirblock = [(relpath, basename, kind, stat, None, None) for
 
2176
                    relpath, basename, kind, stat, top_path in current_disk[1]]
 
2177
                yield (current_disk[0][0], None), dirblock
 
2178
                try:
 
2179
                    current_disk = disk_iterator.next()
 
2180
                except StopIteration:
 
2181
                    disk_finished = True
 
2182
            elif direction < 0:
 
2183
                # inventory is before disk - missing.
 
2184
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
 
2185
                    for relpath, basename, dkind, stat, fileid, kind in 
 
2186
                    current_inv[1]]
 
2187
                yield (current_inv[0][0], current_inv[0][1]), dirblock
 
2188
                try:
 
2189
                    current_inv = inventory_iterator.next()
 
2190
                except StopIteration:
 
2191
                    inv_finished = True
 
2192
            else:
 
2193
                # versioned present directory
 
2194
                # merge the inventory and disk data together
 
2195
                dirblock = []
 
2196
                for relpath, subiterator in itertools.groupby(sorted(
 
2197
                    current_inv[1] + current_disk[1], key=operator.itemgetter(0)), operator.itemgetter(1)):
 
2198
                    path_elements = list(subiterator)
 
2199
                    if len(path_elements) == 2:
 
2200
                        inv_row, disk_row = path_elements
 
2201
                        # versioned, present file
 
2202
                        dirblock.append((inv_row[0],
 
2203
                            inv_row[1], disk_row[2],
 
2204
                            disk_row[3], inv_row[4],
 
2205
                            inv_row[5]))
 
2206
                    elif len(path_elements[0]) == 5:
 
2207
                        # unknown disk file
 
2208
                        dirblock.append((path_elements[0][0],
 
2209
                            path_elements[0][1], path_elements[0][2],
 
2210
                            path_elements[0][3], None, None))
 
2211
                    elif len(path_elements[0]) == 6:
 
2212
                        # versioned, absent file.
 
2213
                        dirblock.append((path_elements[0][0],
 
2214
                            path_elements[0][1], 'unknown', None,
 
2215
                            path_elements[0][4], path_elements[0][5]))
 
2216
                    else:
 
2217
                        raise NotImplementedError('unreachable code')
 
2218
                yield current_inv[0], dirblock
 
2219
                try:
 
2220
                    current_inv = inventory_iterator.next()
 
2221
                except StopIteration:
 
2222
                    inv_finished = True
 
2223
                try:
 
2224
                    current_disk = disk_iterator.next()
 
2225
                except StopIteration:
 
2226
                    disk_finished = True
 
2227
 
 
2228
    def _walkdirs(self, prefix=""):
 
2229
        _directory = 'directory'
 
2230
        # get the root in the inventory
 
2231
        inv = self.inventory
 
2232
        top_id = inv.path2id(prefix)
 
2233
        if top_id is None:
 
2234
            pending = []
 
2235
        else:
 
2236
            pending = [(prefix, '', _directory, None, top_id, None)]
 
2237
        while pending:
 
2238
            dirblock = []
 
2239
            currentdir = pending.pop()
 
2240
            # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
 
2241
            top_id = currentdir[4]
 
2242
            if currentdir[0]:
 
2243
                relroot = currentdir[0] + '/'
 
2244
            else:
 
2245
                relroot = ""
 
2246
            # FIXME: stash the node in pending
 
2247
            entry = inv[top_id]
 
2248
            for name, child in entry.sorted_children():
 
2249
                dirblock.append((relroot + name, name, child.kind, None,
 
2250
                    child.file_id, child.kind
 
2251
                    ))
 
2252
            yield (currentdir[0], entry.file_id), dirblock
 
2253
            # push the user specified dirs from dirblock
 
2254
            for dir in reversed(dirblock):
 
2255
                if dir[2] == _directory:
 
2256
                    pending.append(dir)
 
2257
 
 
2258
    @needs_tree_write_lock
 
2259
    def auto_resolve(self):
 
2260
        """Automatically resolve text conflicts according to contents.
 
2261
 
 
2262
        Only text conflicts are auto_resolvable. Files with no conflict markers
 
2263
        are considered 'resolved', because bzr always puts conflict markers
 
2264
        into files that have text conflicts.  The corresponding .THIS .BASE and
 
2265
        .OTHER files are deleted, as per 'resolve'.
 
2266
        :return: a tuple of ConflictLists: (un_resolved, resolved).
 
2267
        """
 
2268
        un_resolved = _mod_conflicts.ConflictList()
 
2269
        resolved = _mod_conflicts.ConflictList()
 
2270
        conflict_re = re.compile('^(<{7}|={7}|>{7})')
 
2271
        for conflict in self.conflicts():
 
2272
            if (conflict.typestring != 'text conflict' or
 
2273
                self.kind(conflict.file_id) != 'file'):
 
2274
                un_resolved.append(conflict)
 
2275
                continue
 
2276
            my_file = open(self.id2abspath(conflict.file_id), 'rb')
 
2277
            try:
 
2278
                for line in my_file:
 
2279
                    if conflict_re.search(line):
 
2280
                        un_resolved.append(conflict)
 
2281
                        break
 
2282
                else:
 
2283
                    resolved.append(conflict)
 
2284
            finally:
 
2285
                my_file.close()
 
2286
        resolved.remove_files(self)
 
2287
        self.set_conflicts(un_resolved)
 
2288
        return un_resolved, resolved
 
2289
 
1943
2290
 
1944
2291
class WorkingTree2(WorkingTree):
1945
2292
    """This is the Format 2 working tree.
2051
2398
        if path.endswith(suffix):
2052
2399
            return path[:-len(suffix)]
2053
2400
 
 
2401
 
2054
2402
@deprecated_function(zero_eight)
2055
2403
def is_control_file(filename):
2056
2404
    """See WorkingTree.is_control_filename(filename)."""
2091
2439
    _formats = {}
2092
2440
    """The known formats."""
2093
2441
 
 
2442
    requires_rich_root = False
 
2443
 
2094
2444
    @classmethod
2095
2445
    def find_format(klass, a_bzrdir):
2096
2446
        """Return the format for the working tree object in a_bzrdir."""
2103
2453
        except KeyError:
2104
2454
            raise errors.UnknownFormatError(format=format_string)
2105
2455
 
 
2456
    def __eq__(self, other):
 
2457
        return self.__class__ is other.__class__
 
2458
 
 
2459
    def __ne__(self, other):
 
2460
        return not (self == other)
 
2461
 
2106
2462
    @classmethod
2107
2463
    def get_default_format(klass):
2108
2464
        """Return the current default format."""
2243
2599
    _lock_file_name = 'lock'
2244
2600
    _lock_class = LockDir
2245
2601
 
 
2602
    _tree_class = WorkingTree3
 
2603
 
 
2604
    def __get_matchingbzrdir(self):
 
2605
        return bzrdir.BzrDirMetaFormat1()
 
2606
 
 
2607
    _matchingbzrdir = property(__get_matchingbzrdir)
 
2608
 
2246
2609
    def _open_control_files(self, a_bzrdir):
2247
2610
        transport = a_bzrdir.get_workingtree_transport(None)
2248
2611
        return LockableFiles(transport, self._lock_file_name, 
2271
2634
        # those trees. And because there isn't a format bump inbetween, we
2272
2635
        # are maintaining compatibility with older clients.
2273
2636
        # inv = Inventory(root_id=gen_root_id())
2274
 
        inv = Inventory()
2275
 
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
2637
        inv = self._initial_inventory()
 
2638
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
2276
2639
                         branch,
2277
2640
                         inv,
2278
2641
                         _internal=True,
2297
2660
            wt.unlock()
2298
2661
        return wt
2299
2662
 
 
2663
    def _initial_inventory(self):
 
2664
        return Inventory()
 
2665
 
2300
2666
    def __init__(self):
2301
2667
        super(WorkingTreeFormat3, self).__init__()
2302
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
2303
2668
 
2304
2669
    def open(self, a_bzrdir, _found=False):
2305
2670
        """Return the WorkingTree object for a_bzrdir
2320
2685
        :param a_bzrdir: the dir for the tree.
2321
2686
        :param control_files: the control files for the tree.
2322
2687
        """
2323
 
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2324
 
                           _internal=True,
2325
 
                           _format=self,
2326
 
                           _bzrdir=a_bzrdir,
2327
 
                           _control_files=control_files)
 
2688
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
2689
                                _internal=True,
 
2690
                                _format=self,
 
2691
                                _bzrdir=a_bzrdir,
 
2692
                                _control_files=control_files)
2328
2693
 
2329
2694
    def __str__(self):
2330
2695
        return self.get_format_string()
2331
2696
 
2332
2697
 
 
2698
__default_format = WorkingTreeFormat4()
 
2699
WorkingTreeFormat.register_format(__default_format)
 
2700
WorkingTreeFormat.register_format(WorkingTreeFormat3())
 
2701
WorkingTreeFormat.set_default_format(__default_format)
2333
2702
# formats which have no format string are not discoverable
2334
2703
# and not independently creatable, so are not registered.
2335
 
__default_format = WorkingTreeFormat3()
2336
 
WorkingTreeFormat.register_format(__default_format)
2337
 
WorkingTreeFormat.set_default_format(__default_format)
2338
2704
_legacy_formats = [WorkingTreeFormat2(),
2339
2705
                   ]
2340
2706