~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: 2010-01-14 00:01:32 UTC
  • mfrom: (4957.1.1 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100114000132-3p3rabnonjw3gzqb
(jam) Merge bzr.stable, bringing in bug fixes #175839, #504390

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
48
48
import itertools
49
49
import operator
50
50
import stat
51
 
from time import time
52
 
import warnings
53
51
import re
54
52
 
55
53
import bzrlib
57
55
    branch,
58
56
    bzrdir,
59
57
    conflicts as _mod_conflicts,
60
 
    dirstate,
61
58
    errors,
62
59
    generate_ids,
63
60
    globbing,
 
61
    graph as _mod_graph,
64
62
    hashcache,
65
63
    ignores,
 
64
    inventory,
66
65
    merge,
67
66
    revision as _mod_revision,
68
67
    revisiontree,
69
 
    repository,
70
 
    textui,
71
68
    trace,
72
69
    transform,
73
70
    ui,
74
 
    urlutils,
75
71
    views,
76
72
    xml5,
77
 
    xml6,
78
73
    xml7,
79
74
    )
80
75
import bzrlib.branch
81
76
from bzrlib.transport import get_transport
82
 
import bzrlib.ui
83
77
from bzrlib.workingtree_4 import (
84
78
    WorkingTreeFormat4,
85
79
    WorkingTreeFormat5,
89
83
 
90
84
from bzrlib import symbol_versioning
91
85
from bzrlib.decorators import needs_read_lock, needs_write_lock
92
 
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, TreeReference
93
86
from bzrlib.lockable_files import LockableFiles
94
87
from bzrlib.lockdir import LockDir
95
88
import bzrlib.mutabletree
96
89
from bzrlib.mutabletree import needs_tree_write_lock
97
90
from bzrlib import osutils
98
91
from bzrlib.osutils import (
99
 
    compact_date,
100
92
    file_kind,
101
93
    isdir,
102
94
    normpath,
103
95
    pathjoin,
104
 
    rand_chars,
105
96
    realpath,
106
97
    safe_unicode,
107
98
    splitpath,
111
102
from bzrlib.trace import mutter, note
112
103
from bzrlib.transport.local import LocalTransport
113
104
from bzrlib.progress import DummyProgress, ProgressPhase
114
 
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
 
105
from bzrlib.revision import CURRENT_REVISION
115
106
from bzrlib.rio import RioReader, rio_file, Stanza
116
 
from bzrlib.symbol_versioning import (deprecated_passed,
117
 
        deprecated_method,
118
 
        deprecated_function,
119
 
        DEPRECATED_PARAMETER,
120
 
        )
 
107
from bzrlib.symbol_versioning import (
 
108
    deprecated_passed,
 
109
    DEPRECATED_PARAMETER,
 
110
    )
121
111
 
122
112
 
123
113
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
290
280
        self._control_files.break_lock()
291
281
        self.branch.break_lock()
292
282
 
 
283
    def _get_check_refs(self):
 
284
        """Return the references needed to perform a check of this tree.
 
285
        
 
286
        The default implementation returns no refs, and is only suitable for
 
287
        trees that have no local caching and can commit on ghosts at any time.
 
288
 
 
289
        :seealso: bzrlib.check for details about check_refs.
 
290
        """
 
291
        return []
 
292
 
293
293
    def requires_rich_root(self):
294
294
        return self._format.requires_rich_root
295
295
 
446
446
 
447
447
    def get_file_with_stat(self, file_id, path=None, filtered=True,
448
448
        _fstat=os.fstat):
449
 
        """See MutableTree.get_file_with_stat."""
 
449
        """See Tree.get_file_with_stat."""
450
450
        if path is None:
451
451
            path = self.id2path(file_id)
452
452
        file_obj = self.get_file_byname(path, filtered=False)
453
453
        stat_value = _fstat(file_obj.fileno())
454
 
        if self.supports_content_filtering() and filtered:
 
454
        if filtered and self.supports_content_filtering():
455
455
            filters = self._content_filter_stack(path)
456
456
            file_obj = filtered_input_file(file_obj, filters)
457
457
        return (file_obj, stat_value)
462
462
    def get_file_byname(self, filename, filtered=True):
463
463
        path = self.abspath(filename)
464
464
        f = file(path, 'rb')
465
 
        if self.supports_content_filtering() and filtered:
 
465
        if filtered and self.supports_content_filtering():
466
466
            filters = self._content_filter_stack(filename)
467
467
            return filtered_input_file(f, filters)
468
468
        else:
487
487
        incorrectly attributed to CURRENT_REVISION (but after committing, the
488
488
        attribution will be correct).
489
489
        """
490
 
        basis = self.basis_tree()
491
 
        basis.lock_read()
492
 
        try:
493
 
            changes = self.iter_changes(basis, True, [self.id2path(file_id)],
494
 
                require_versioned=True).next()
495
 
            changed_content, kind = changes[2], changes[6]
496
 
            if not changed_content:
497
 
                return basis.annotate_iter(file_id)
498
 
            if kind[1] is None:
499
 
                return None
500
 
            import annotate
501
 
            if kind[0] != 'file':
502
 
                old_lines = []
503
 
            else:
504
 
                old_lines = list(basis.annotate_iter(file_id))
505
 
            old = [old_lines]
506
 
            for tree in self.branch.repository.revision_trees(
507
 
                self.get_parent_ids()[1:]):
508
 
                if file_id not in tree:
509
 
                    continue
510
 
                old.append(list(tree.annotate_iter(file_id)))
511
 
            return annotate.reannotate(old, self.get_file(file_id).readlines(),
512
 
                                       default_revision)
513
 
        finally:
514
 
            basis.unlock()
 
490
        maybe_file_parent_keys = []
 
491
        for parent_id in self.get_parent_ids():
 
492
            try:
 
493
                parent_tree = self.revision_tree(parent_id)
 
494
            except errors.NoSuchRevisionInTree:
 
495
                parent_tree = self.branch.repository.revision_tree(parent_id)
 
496
            parent_tree.lock_read()
 
497
            try:
 
498
                if file_id not in parent_tree:
 
499
                    continue
 
500
                ie = parent_tree.inventory[file_id]
 
501
                if ie.kind != 'file':
 
502
                    # Note: this is slightly unnecessary, because symlinks and
 
503
                    # directories have a "text" which is the empty text, and we
 
504
                    # know that won't mess up annotations. But it seems cleaner
 
505
                    continue
 
506
                parent_text_key = (file_id, ie.revision)
 
507
                if parent_text_key not in maybe_file_parent_keys:
 
508
                    maybe_file_parent_keys.append(parent_text_key)
 
509
            finally:
 
510
                parent_tree.unlock()
 
511
        graph = _mod_graph.Graph(self.branch.repository.texts)
 
512
        heads = graph.heads(maybe_file_parent_keys)
 
513
        file_parent_keys = []
 
514
        for key in maybe_file_parent_keys:
 
515
            if key in heads:
 
516
                file_parent_keys.append(key)
 
517
 
 
518
        # Now we have the parents of this content
 
519
        annotator = self.branch.repository.texts.get_annotator()
 
520
        text = self.get_file(file_id).read()
 
521
        this_key =(file_id, default_revision)
 
522
        annotator.add_special_text(this_key, file_parent_keys, text)
 
523
        annotations = [(key[-1], line)
 
524
                       for key, line in annotator.annotate_flat(this_key)]
 
525
        return annotations
515
526
 
516
527
    def _get_ancestors(self, default_revision):
517
528
        ancestors = set([default_revision])
532
543
        else:
533
544
            parents = [last_rev]
534
545
        try:
535
 
            merges_file = self._transport.get('pending-merges')
 
546
            merges_bytes = self._transport.get_bytes('pending-merges')
536
547
        except errors.NoSuchFile:
537
548
            pass
538
549
        else:
539
 
            for l in merges_file.readlines():
 
550
            for l in osutils.split_lines(merges_bytes):
540
551
                revision_id = l.rstrip('\n')
541
552
                parents.append(revision_id)
542
553
        return parents
601
612
 
602
613
    def get_file_size(self, file_id):
603
614
        """See Tree.get_file_size"""
 
615
        # XXX: this returns the on-disk size; it should probably return the
 
616
        # canonical size
604
617
        try:
605
618
            return os.path.getsize(self.id2abspath(file_id))
606
619
        except OSError, e:
622
635
 
623
636
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
624
637
        file_id = self.path2id(path)
 
638
        if file_id is None:
 
639
            # For unversioned files on win32, we just assume they are not
 
640
            # executable
 
641
            return False
625
642
        return self._inventory[file_id].executable
626
643
 
627
644
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
737
754
            raise
738
755
        kind = _mapper(stat_result.st_mode)
739
756
        if kind == 'file':
740
 
            size = stat_result.st_size
741
 
            # try for a stat cache lookup
742
 
            executable = self._is_executable_from_path_and_stat(path, stat_result)
743
 
            return (kind, size, executable, self._sha_from_stat(
744
 
                path, stat_result))
 
757
            return self._file_content_summary(path, stat_result)
745
758
        elif kind == 'directory':
746
759
            # perhaps it looks like a plain directory, but it's really a
747
760
            # reference.
749
762
                kind = 'tree-reference'
750
763
            return kind, None, None, None
751
764
        elif kind == 'symlink':
752
 
            return ('symlink', None, None,
753
 
                    os.readlink(abspath.encode(osutils._fs_enc)
754
 
                                ).decode(osutils._fs_enc))
 
765
            target = osutils.readlink(abspath)
 
766
            return ('symlink', None, None, target)
755
767
        else:
756
768
            return (kind, None, None, None)
757
769
 
 
770
    def _file_content_summary(self, path, stat_result):
 
771
        size = stat_result.st_size
 
772
        executable = self._is_executable_from_path_and_stat(path, stat_result)
 
773
        # try for a stat cache lookup
 
774
        return ('file', size, executable, self._sha_from_stat(
 
775
            path, stat_result))
 
776
 
758
777
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
759
778
        """Common ghost checking functionality from set_parent_*.
760
779
 
880
899
 
881
900
    @needs_write_lock # because merge pulls data into the branch.
882
901
    def merge_from_branch(self, branch, to_revision=None, from_revision=None,
883
 
        merge_type=None):
 
902
                          merge_type=None, force=False):
884
903
        """Merge from a branch into this working tree.
885
904
 
886
905
        :param branch: The branch to merge from.
890
909
            branch.last_revision().
891
910
        """
892
911
        from bzrlib.merge import Merger, Merge3Merger
893
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
912
        pb = ui.ui_factory.nested_progress_bar()
894
913
        try:
895
914
            merger = Merger(self.branch, this_tree=self, pb=pb)
896
915
            merger.pp = ProgressPhase("Merge phase", 5, pb)
897
916
            merger.pp.next_phase()
898
 
            # check that there are no
899
 
            # local alterations
900
 
            merger.check_basis(check_clean=True, require_commits=False)
 
917
            # check that there are no local alterations
 
918
            if not force and self.has_changes():
 
919
                raise errors.UncommittedChanges(self)
901
920
            if to_revision is None:
902
921
                to_revision = _mod_revision.ensure_null(branch.last_revision())
903
922
            merger.other_rev_id = to_revision
973
992
        return file_id
974
993
 
975
994
    def get_symlink_target(self, file_id):
976
 
        return os.readlink(self.id2abspath(file_id).encode(osutils._fs_enc)
977
 
            ).decode(osutils._fs_enc)
 
995
        abspath = self.id2abspath(file_id)
 
996
        target = osutils.readlink(abspath)
 
997
        return target
978
998
 
979
999
    @needs_write_lock
980
1000
    def subsume(self, other_tree):
1081
1101
            branch.BranchReferenceFormat().initialize(tree_bzrdir, new_branch)
1082
1102
        else:
1083
1103
            tree_bzrdir = branch_bzrdir
1084
 
        wt = tree_bzrdir.create_workingtree(NULL_REVISION)
 
1104
        wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
1085
1105
        wt.set_parent_ids(self.get_parent_ids())
1086
1106
        my_inv = self.inventory
1087
 
        child_inv = Inventory(root_id=None)
 
1107
        child_inv = inventory.Inventory(root_id=None)
1088
1108
        new_root = my_inv[file_id]
1089
1109
        my_inv.remove_recursive_id(file_id)
1090
1110
        new_root.parent_id = None
1115
1135
    def _kind(self, relpath):
1116
1136
        return osutils.file_kind(self.abspath(relpath))
1117
1137
 
1118
 
    def list_files(self, include_root=False):
1119
 
        """Recursively list all files as (path, class, kind, id, entry).
 
1138
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
1139
        """List all files as (path, class, kind, id, entry).
1120
1140
 
1121
1141
        Lists, but does not descend into unversioned directories.
1122
 
 
1123
1142
        This does not include files that have been deleted in this
1124
 
        tree.
 
1143
        tree. Skips the control directory.
1125
1144
 
1126
 
        Skips the control directory.
 
1145
        :param include_root: if True, do not return an entry for the root
 
1146
        :param from_dir: start from this directory or None for the root
 
1147
        :param recursive: whether to recurse into subdirectories or not
1127
1148
        """
1128
1149
        # list_files is an iterator, so @needs_read_lock doesn't work properly
1129
1150
        # with it. So callers should be careful to always read_lock the tree.
1131
1152
            raise errors.ObjectNotLocked(self)
1132
1153
 
1133
1154
        inv = self.inventory
1134
 
        if include_root is True:
 
1155
        if from_dir is None and include_root is True:
1135
1156
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
1136
1157
        # Convert these into local objects to save lookup times
1137
1158
        pathjoin = osutils.pathjoin
1144
1165
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
1145
1166
 
1146
1167
        # directory file_id, relative path, absolute path, reverse sorted children
1147
 
        children = os.listdir(self.basedir)
 
1168
        if from_dir is not None:
 
1169
            from_dir_id = inv.path2id(from_dir)
 
1170
            if from_dir_id is None:
 
1171
                # Directory not versioned
 
1172
                return
 
1173
            from_dir_abspath = pathjoin(self.basedir, from_dir)
 
1174
        else:
 
1175
            from_dir_id = inv.root.file_id
 
1176
            from_dir_abspath = self.basedir
 
1177
        children = os.listdir(from_dir_abspath)
1148
1178
        children.sort()
1149
1179
        # jam 20060527 The kernel sized tree seems equivalent whether we
1150
1180
        # use a deque and popleft to keep them sorted, or if we use a plain
1151
1181
        # list and just reverse() them.
1152
1182
        children = collections.deque(children)
1153
 
        stack = [(inv.root.file_id, u'', self.basedir, children)]
 
1183
        stack = [(from_dir_id, u'', from_dir_abspath, children)]
1154
1184
        while stack:
1155
1185
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
1156
1186
 
1214
1244
                if fk != 'directory':
1215
1245
                    continue
1216
1246
 
1217
 
                # But do this child first
1218
 
                new_children = os.listdir(fap)
1219
 
                new_children.sort()
1220
 
                new_children = collections.deque(new_children)
1221
 
                stack.append((f_ie.file_id, fp, fap, new_children))
1222
 
                # Break out of inner loop,
1223
 
                # so that we start outer loop with child
1224
 
                break
 
1247
                # But do this child first if recursing down
 
1248
                if recursive:
 
1249
                    new_children = os.listdir(fap)
 
1250
                    new_children.sort()
 
1251
                    new_children = collections.deque(new_children)
 
1252
                    stack.append((f_ie.file_id, fp, fap, new_children))
 
1253
                    # Break out of inner loop,
 
1254
                    # so that we start outer loop with child
 
1255
                    break
1225
1256
            else:
1226
1257
                # if we finished all children, pop it off the stack
1227
1258
                stack.pop()
1405
1436
        inv = self.inventory
1406
1437
        for entry in moved:
1407
1438
            try:
1408
 
                self._move_entry(_RenameEntry(entry.to_rel, entry.from_id,
 
1439
                self._move_entry(WorkingTree._RenameEntry(
 
1440
                    entry.to_rel, entry.from_id,
1409
1441
                    entry.to_tail, entry.to_parent_id, entry.from_rel,
1410
1442
                    entry.from_tail, entry.from_parent_id,
1411
1443
                    entry.only_change_inv))
1462
1494
        from_tail = splitpath(from_rel)[-1]
1463
1495
        from_id = inv.path2id(from_rel)
1464
1496
        if from_id is None:
1465
 
            raise errors.BzrRenameFailedError(from_rel,to_rel,
1466
 
                errors.NotVersionedError(path=str(from_rel)))
1467
 
        from_entry = inv[from_id]
 
1497
            # if file is missing in the inventory maybe it's in the basis_tree
 
1498
            basis_tree = self.branch.basis_tree()
 
1499
            from_id = basis_tree.path2id(from_rel)
 
1500
            if from_id is None:
 
1501
                raise errors.BzrRenameFailedError(from_rel,to_rel,
 
1502
                    errors.NotVersionedError(path=str(from_rel)))
 
1503
            # put entry back in the inventory so we can rename it
 
1504
            from_entry = basis_tree.inventory[from_id].copy()
 
1505
            inv.add(from_entry)
 
1506
        else:
 
1507
            from_entry = inv[from_id]
1468
1508
        from_parent_id = from_entry.parent_id
1469
1509
        to_dir, to_tail = os.path.split(to_rel)
1470
1510
        to_dir_id = inv.path2id(to_dir)
1561
1601
 
1562
1602
    @needs_write_lock
1563
1603
    def pull(self, source, overwrite=False, stop_revision=None,
1564
 
             change_reporter=None, possible_transports=None):
1565
 
        top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1604
             change_reporter=None, possible_transports=None, local=False):
 
1605
        top_pb = ui.ui_factory.nested_progress_bar()
1566
1606
        source.lock_read()
1567
1607
        try:
1568
1608
            pp = ProgressPhase("Pull phase", 2, top_pb)
1570
1610
            old_revision_info = self.branch.last_revision_info()
1571
1611
            basis_tree = self.basis_tree()
1572
1612
            count = self.branch.pull(source, overwrite, stop_revision,
1573
 
                                     possible_transports=possible_transports)
 
1613
                                     possible_transports=possible_transports,
 
1614
                                     local=local)
1574
1615
            new_revision_info = self.branch.last_revision_info()
1575
1616
            if new_revision_info != old_revision_info:
1576
1617
                pp.next_phase()
1577
1618
                repository = self.branch.repository
1578
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1619
                pb = ui.ui_factory.nested_progress_bar()
1579
1620
                basis_tree.lock_read()
1580
1621
                try:
1581
1622
                    new_basis_tree = self.branch.basis_tree()
1586
1627
                                this_tree=self,
1587
1628
                                pb=pb,
1588
1629
                                change_reporter=change_reporter)
1589
 
                    if (basis_tree.inventory.root is None and
1590
 
                        new_basis_tree.inventory.root is not None):
1591
 
                        self.set_root_id(new_basis_tree.get_root_id())
 
1630
                    basis_root_id = basis_tree.get_root_id()
 
1631
                    new_root_id = new_basis_tree.get_root_id()
 
1632
                    if basis_root_id != new_root_id:
 
1633
                        self.set_root_id(new_root_id)
1592
1634
                finally:
1593
1635
                    pb.finished()
1594
1636
                    basis_tree.unlock()
1641
1683
 
1642
1684
            fl = []
1643
1685
            for subf in os.listdir(dirabs):
1644
 
                if subf == '.bzr':
 
1686
                if self.bzrdir.is_control_filename(subf):
1645
1687
                    continue
1646
1688
                if subf not in dir_entry.children:
1647
1689
                    try:
1803
1845
    def _reset_data(self):
1804
1846
        """Reset transient data that cannot be revalidated."""
1805
1847
        self._inventory_is_modified = False
1806
 
        result = self._deserialize(self._transport.get('inventory'))
 
1848
        f = self._transport.get('inventory')
 
1849
        try:
 
1850
            result = self._deserialize(f)
 
1851
        finally:
 
1852
            f.close()
1807
1853
        self._set_inventory(result, dirty=False)
1808
1854
 
1809
1855
    @needs_tree_write_lock
1860
1906
            firstline = xml.split('\n', 1)[0]
1861
1907
            if (not 'revision_id="' in firstline or
1862
1908
                'format="7"' not in firstline):
1863
 
                inv = self.branch.repository.deserialise_inventory(
1864
 
                    new_revision, xml)
 
1909
                inv = self.branch.repository._serializer.read_inventory_from_string(
 
1910
                    xml, new_revision)
1865
1911
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
1866
1912
            self._write_basis_inventory(xml)
1867
1913
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1885
1931
        # binary.
1886
1932
        if self._inventory_is_modified:
1887
1933
            raise errors.InventoryModified(self)
1888
 
        result = self._deserialize(self._transport.get('inventory'))
 
1934
        f = self._transport.get('inventory')
 
1935
        try:
 
1936
            result = self._deserialize(f)
 
1937
        finally:
 
1938
            f.close()
1889
1939
        self._set_inventory(result, dirty=False)
1890
1940
        return result
1891
1941
 
1906
1956
 
1907
1957
        new_files=set()
1908
1958
        unknown_nested_files=set()
 
1959
        if to_file is None:
 
1960
            to_file = sys.stdout
1909
1961
 
1910
1962
        def recurse_directory_to_add_files(directory):
1911
1963
            # Recurse directory and add all files
1981
2033
                        new_status = 'I'
1982
2034
                    else:
1983
2035
                        new_status = '?'
1984
 
                    textui.show_status(new_status, self.kind(fid), f,
1985
 
                                       to_file=to_file)
 
2036
                    # XXX: Really should be a more abstract reporter interface
 
2037
                    kind_ch = osutils.kind_marker(self.kind(fid))
 
2038
                    to_file.write(new_status + '       ' + f + kind_ch + '\n')
1986
2039
                # Unversion file
1987
2040
                inv_delta.append((f, None, fid, None))
1988
2041
                message = "removed %s" % (f,)
2030
2083
            if filenames is None and len(self.get_parent_ids()) > 1:
2031
2084
                parent_trees = []
2032
2085
                last_revision = self.last_revision()
2033
 
                if last_revision != NULL_REVISION:
 
2086
                if last_revision != _mod_revision.NULL_REVISION:
2034
2087
                    if basis_tree is None:
2035
2088
                        basis_tree = self.basis_tree()
2036
2089
                        basis_tree.lock_read()
2074
2127
    def set_inventory(self, new_inventory_list):
2075
2128
        from bzrlib.inventory import (Inventory,
2076
2129
                                      InventoryDirectory,
2077
 
                                      InventoryEntry,
2078
2130
                                      InventoryFile,
2079
2131
                                      InventoryLink)
2080
2132
        inv = Inventory(self.get_root_id())
2140
2192
        """
2141
2193
        raise NotImplementedError(self.unlock)
2142
2194
 
2143
 
    def update(self, change_reporter=None, possible_transports=None):
 
2195
    _marker = object()
 
2196
 
 
2197
    def update(self, change_reporter=None, possible_transports=None,
 
2198
               revision=None, old_tip=_marker):
2144
2199
        """Update a working tree along its branch.
2145
2200
 
2146
2201
        This will update the branch if its bound too, which means we have
2164
2219
        - Merge current state -> basis tree of the master w.r.t. the old tree
2165
2220
          basis.
2166
2221
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
2222
 
 
2223
        :param revision: The target revision to update to. Must be in the
 
2224
            revision history.
 
2225
        :param old_tip: If branch.update() has already been run, the value it
 
2226
            returned (old tip of the branch or None). _marker is used
 
2227
            otherwise.
2167
2228
        """
2168
2229
        if self.branch.get_bound_location() is not None:
2169
2230
            self.lock_write()
2170
 
            update_branch = True
 
2231
            update_branch = (old_tip is self._marker)
2171
2232
        else:
2172
2233
            self.lock_tree_write()
2173
2234
            update_branch = False
2175
2236
            if update_branch:
2176
2237
                old_tip = self.branch.update(possible_transports)
2177
2238
            else:
2178
 
                old_tip = None
2179
 
            return self._update_tree(old_tip, change_reporter)
 
2239
                if old_tip is self._marker:
 
2240
                    old_tip = None
 
2241
            return self._update_tree(old_tip, change_reporter, revision)
2180
2242
        finally:
2181
2243
            self.unlock()
2182
2244
 
2183
2245
    @needs_tree_write_lock
2184
 
    def _update_tree(self, old_tip=None, change_reporter=None):
 
2246
    def _update_tree(self, old_tip=None, change_reporter=None, revision=None):
2185
2247
        """Update a tree to the master branch.
2186
2248
 
2187
2249
        :param old_tip: if supplied, the previous tip revision the branch,
2202
2264
            last_rev = self.get_parent_ids()[0]
2203
2265
        except IndexError:
2204
2266
            last_rev = _mod_revision.NULL_REVISION
2205
 
        if last_rev != _mod_revision.ensure_null(self.branch.last_revision()):
2206
 
            # merge tree state up to new branch tip.
 
2267
        if revision is None:
 
2268
            revision = self.branch.last_revision()
 
2269
        else:
 
2270
            if revision not in self.branch.revision_history():
 
2271
                raise errors.NoSuchRevision(self.branch, revision)
 
2272
        if last_rev != _mod_revision.ensure_null(revision):
 
2273
            # merge tree state up to specified revision.
2207
2274
            basis = self.basis_tree()
2208
2275
            basis.lock_read()
2209
2276
            try:
2210
 
                to_tree = self.branch.basis_tree()
2211
 
                if basis.inventory.root is None:
2212
 
                    self.set_root_id(to_tree.get_root_id())
 
2277
                to_tree = self.branch.repository.revision_tree(revision)
 
2278
                to_root_id = to_tree.get_root_id()
 
2279
                if (basis.inventory.root is None
 
2280
                    or basis.inventory.root.file_id != to_root_id):
 
2281
                    self.set_root_id(to_root_id)
2213
2282
                    self.flush()
2214
2283
                result += merge.merge_inner(
2215
2284
                                      self.branch,
2217
2286
                                      basis,
2218
2287
                                      this_tree=self,
2219
2288
                                      change_reporter=change_reporter)
 
2289
                self.set_last_revision(revision)
2220
2290
            finally:
2221
2291
                basis.unlock()
2222
2292
            # TODO - dedup parents list with things merged by pull ?
2223
2293
            # reuse the tree we've updated to to set the basis:
2224
 
            parent_trees = [(self.branch.last_revision(), to_tree)]
 
2294
            parent_trees = [(revision, to_tree)]
2225
2295
            merges = self.get_parent_ids()[1:]
2226
2296
            # Ideally we ask the tree for the trees here, that way the working
2227
2297
            # tree can decide whether to give us the entire tree or give us a
2257
2327
            #       should be able to remove this extra flush.
2258
2328
            self.flush()
2259
2329
            graph = self.branch.repository.get_graph()
2260
 
            base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
2261
 
                                                old_tip)
 
2330
            base_rev_id = graph.find_unique_lca(revision, old_tip)
2262
2331
            base_tree = self.branch.repository.revision_tree(base_rev_id)
2263
2332
            other_tree = self.branch.repository.revision_tree(old_tip)
2264
2333
            result += merge.merge_inner(
2373
2442
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
2374
2443
                        ('.bzr', '.bzr'))
2375
2444
                    if (bzrdir_loc < len(cur_disk_dir_content)
2376
 
                        and cur_disk_dir_content[bzrdir_loc][0] == '.bzr'):
 
2445
                        and self.bzrdir.is_control_filename(
 
2446
                            cur_disk_dir_content[bzrdir_loc][0])):
2377
2447
                        # we dont yield the contents of, or, .bzr itself.
2378
2448
                        del cur_disk_dir_content[bzrdir_loc]
2379
2449
            if inv_finished:
2513
2583
        return un_resolved, resolved
2514
2584
 
2515
2585
    @needs_read_lock
2516
 
    def _check(self):
 
2586
    def _check(self, references):
 
2587
        """Check the tree for consistency.
 
2588
 
 
2589
        :param references: A dict with keys matching the items returned by
 
2590
            self._get_check_refs(), and values from looking those keys up in
 
2591
            the repository.
 
2592
        """
2517
2593
        tree_basis = self.basis_tree()
2518
2594
        tree_basis.lock_read()
2519
2595
        try:
2520
 
            repo_basis = self.branch.repository.revision_tree(
2521
 
                self.last_revision())
 
2596
            repo_basis = references[('trees', self.last_revision())]
2522
2597
            if len(list(repo_basis.iter_changes(tree_basis))) > 0:
2523
2598
                raise errors.BzrCheckError(
2524
2599
                    "Mismatched basis inventory content.")
2537
2612
        """
2538
2613
        return
2539
2614
 
2540
 
    @needs_read_lock
2541
2615
    def _get_rules_searcher(self, default_searcher):
2542
2616
        """See Tree._get_rules_searcher."""
2543
2617
        if self._rules_searcher is None:
2570
2644
        if self._inventory is None:
2571
2645
            self.read_working_inventory()
2572
2646
 
 
2647
    def _get_check_refs(self):
 
2648
        """Return the references needed to perform a check of this tree."""
 
2649
        return [('trees', self.last_revision())]
 
2650
 
2573
2651
    def lock_tree_write(self):
2574
2652
        """See WorkingTree.lock_tree_write().
2575
2653
 
2621
2699
 
2622
2700
    def _change_last_revision(self, revision_id):
2623
2701
        """See WorkingTree._change_last_revision."""
2624
 
        if revision_id is None or revision_id == NULL_REVISION:
 
2702
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
2625
2703
            try:
2626
2704
                self._transport.delete('last-revision')
2627
2705
            except errors.NoSuchFile:
2632
2710
                mode=self.bzrdir._get_file_mode())
2633
2711
            return True
2634
2712
 
 
2713
    def _get_check_refs(self):
 
2714
        """Return the references needed to perform a check of this tree."""
 
2715
        return [('trees', self.last_revision())]
 
2716
 
2635
2717
    @needs_tree_write_lock
2636
2718
    def set_conflicts(self, conflicts):
2637
2719
        self._put_rio('conflicts', conflicts.to_stanzas(),
2714
2796
        """Return the format for the working tree object in a_bzrdir."""
2715
2797
        try:
2716
2798
            transport = a_bzrdir.get_workingtree_transport(None)
2717
 
            format_string = transport.get("format").read()
 
2799
            format_string = transport.get_bytes("format")
2718
2800
            return klass._formats[format_string]
2719
2801
        except errors.NoSuchFile:
2720
2802
            raise errors.NoWorkingTree(base=transport.base)
2791
2873
        no working tree.  (See bug #43064).
2792
2874
        """
2793
2875
        sio = StringIO()
2794
 
        inv = Inventory()
 
2876
        inv = inventory.Inventory()
2795
2877
        xml5.serializer_v5.write_inventory(inv, sio, working=True)
2796
2878
        sio.seek(0)
2797
2879
        transport.put_file('inventory', sio, file_mode)
2813
2895
            branch.generate_revision_history(revision_id)
2814
2896
        finally:
2815
2897
            branch.unlock()
2816
 
        inv = Inventory()
 
2898
        inv = inventory.Inventory()
2817
2899
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
2818
2900
                         branch,
2819
2901
                         inv,
2936
3018
            # only set an explicit root id if there is one to set.
2937
3019
            if basis_tree.inventory.root is not None:
2938
3020
                wt.set_root_id(basis_tree.get_root_id())
2939
 
            if revision_id == NULL_REVISION:
 
3021
            if revision_id == _mod_revision.NULL_REVISION:
2940
3022
                wt.set_parent_trees([])
2941
3023
            else:
2942
3024
                wt.set_parent_trees([(revision_id, basis_tree)])
2949
3031
        return wt
2950
3032
 
2951
3033
    def _initial_inventory(self):
2952
 
        return Inventory()
 
3034
        return inventory.Inventory()
2953
3035
 
2954
3036
    def __init__(self):
2955
3037
        super(WorkingTreeFormat3, self).__init__()
2984
3066
        return self.get_format_string()
2985
3067
 
2986
3068
 
2987
 
__default_format = WorkingTreeFormat4()
 
3069
__default_format = WorkingTreeFormat6()
2988
3070
WorkingTreeFormat.register_format(__default_format)
2989
 
WorkingTreeFormat.register_format(WorkingTreeFormat6())
2990
3071
WorkingTreeFormat.register_format(WorkingTreeFormat5())
 
3072
WorkingTreeFormat.register_format(WorkingTreeFormat4())
2991
3073
WorkingTreeFormat.register_format(WorkingTreeFormat3())
2992
3074
WorkingTreeFormat.set_default_format(__default_format)
2993
3075
# formats which have no format string are not discoverable