~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Aaron Bentley
  • Date: 2006-11-17 04:06:03 UTC
  • mfrom: (2139 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2162.
  • Revision ID: aaron.bentley@utoronto.ca-20061117040603-pgebxndswvwk26tt
Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
WorkingTree.open(dir).
30
30
"""
31
31
 
32
 
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
 
CONFLICT_HEADER_1 = "BZR conflict list format 1"
34
 
 
35
32
# TODO: Give the workingtree sole responsibility for the working inventory;
36
33
# remove the variable and references to it from the branch.  This may require
37
34
# updating the commit code so as to update the inventory within the working
39
36
# At the moment they may alias the inventory and have old copies of it in
40
37
# memory.  (Now done? -- mbp 20060309)
41
38
 
42
 
from binascii import hexlify
 
39
from cStringIO import StringIO
 
40
import os
 
41
import re
 
42
 
 
43
from bzrlib.lazy_import import lazy_import
 
44
lazy_import(globals(), """
43
45
import collections
44
46
from copy import deepcopy
45
 
from cStringIO import StringIO
46
47
import errno
47
48
import fnmatch
48
 
import os
49
 
import re
50
49
import stat
51
50
from time import time
52
51
import warnings
53
52
 
54
53
import bzrlib
55
 
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
56
 
from bzrlib.atomicfile import AtomicFile
 
54
from bzrlib import (
 
55
    bzrdir,
 
56
    conflicts as _mod_conflicts,
 
57
    errors,
 
58
    generate_ids,
 
59
    ignores,
 
60
    merge,
 
61
    osutils,
 
62
    textui,
 
63
    transform,
 
64
    urlutils,
 
65
    xml5,
 
66
    xml6,
 
67
    )
57
68
import bzrlib.branch
58
 
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
69
from bzrlib.transport import get_transport
 
70
import bzrlib.ui
 
71
""")
 
72
 
 
73
from bzrlib import symbol_versioning
59
74
from bzrlib.decorators import needs_read_lock, needs_write_lock
60
75
from bzrlib.errors import (BzrCheckError,
61
76
                           BzrError,
67
82
                           MergeModifiedFormatError,
68
83
                           UnsupportedOperation,
69
84
                           )
70
 
from bzrlib.inventory import InventoryEntry, Inventory
 
85
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
71
86
from bzrlib.lockable_files import LockableFiles, TransportLock
72
87
from bzrlib.lockdir import LockDir
73
 
from bzrlib.merge import merge_inner, transform_tree
74
88
import bzrlib.mutabletree
75
89
from bzrlib.mutabletree import needs_tree_write_lock
76
90
from bzrlib.osutils import (
77
 
                            abspath,
78
 
                            compact_date,
79
 
                            file_kind,
80
 
                            isdir,
81
 
                            getcwd,
82
 
                            pathjoin,
83
 
                            pumpfile,
84
 
                            safe_unicode,
85
 
                            splitpath,
86
 
                            rand_chars,
87
 
                            normpath,
88
 
                            realpath,
89
 
                            relpath,
90
 
                            rename,
91
 
                            supports_executable,
92
 
                            )
 
91
    compact_date,
 
92
    file_kind,
 
93
    isdir,
 
94
    pathjoin,
 
95
    safe_unicode,
 
96
    splitpath,
 
97
    rand_chars,
 
98
    normpath,
 
99
    realpath,
 
100
    supports_executable,
 
101
    )
 
102
from bzrlib.trace import mutter, note
 
103
from bzrlib.transport.local import LocalTransport
 
104
import bzrlib.tree
93
105
from bzrlib.progress import DummyProgress, ProgressPhase
94
106
from bzrlib.revision import NULL_REVISION
95
107
import bzrlib.revisiontree
100
112
        DEPRECATED_PARAMETER,
101
113
        zero_eight,
102
114
        zero_eleven,
 
115
        zero_thirteen,
103
116
        )
104
 
from bzrlib.trace import mutter, note
105
 
from bzrlib.transform import build_tree
106
 
from bzrlib.transport import get_transport
107
 
from bzrlib.transport.local import LocalTransport
108
 
from bzrlib.textui import show_status
109
 
import bzrlib.ui
110
 
import bzrlib.xml5
111
 
 
112
 
 
113
 
# the regex removes any weird characters; we don't escape them 
114
 
# but rather just pull them out
115
 
_gen_file_id_re = re.compile(r'[^\w.]')
116
 
_gen_id_suffix = None
117
 
_gen_id_serial = 0
118
 
 
119
 
 
120
 
def _next_id_suffix():
121
 
    """Create a new file id suffix that is reasonably unique.
122
 
    
123
 
    On the first call we combine the current time with 64 bits of randomness
124
 
    to give a highly probably globally unique number. Then each call in the same
125
 
    process adds 1 to a serial number we append to that unique value.
126
 
    """
127
 
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
128
 
    # than having to move the id randomness out of the inner loop like this.
129
 
    # XXX TODO: for the global randomness this uses we should add the thread-id
130
 
    # before the serial #.
131
 
    global _gen_id_suffix, _gen_id_serial
132
 
    if _gen_id_suffix is None:
133
 
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
134
 
    _gen_id_serial += 1
135
 
    return _gen_id_suffix + str(_gen_id_serial)
136
 
 
137
 
 
 
117
 
 
118
 
 
119
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
120
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
121
 
 
122
 
 
123
@deprecated_function(zero_thirteen)
138
124
def gen_file_id(name):
139
125
    """Return new file id for the basename 'name'.
140
126
 
141
 
    The uniqueness is supplied from _next_id_suffix.
 
127
    Use bzrlib.generate_ids.gen_file_id() instead
142
128
    """
143
 
    # The real randomness is in the _next_id_suffix, the
144
 
    # rest of the identifier is just to be nice.
145
 
    # So we:
146
 
    # 1) Remove non-ascii word characters to keep the ids portable
147
 
    # 2) squash to lowercase, so the file id doesn't have to
148
 
    #    be escaped (case insensitive filesystems would bork for ids
149
 
    #    that only differred in case without escaping).
150
 
    # 3) truncate the filename to 20 chars. Long filenames also bork on some
151
 
    #    filesystems
152
 
    # 4) Removing starting '.' characters to prevent the file ids from
153
 
    #    being considered hidden.
154
 
    ascii_word_only = _gen_file_id_re.sub('', name.lower())
155
 
    short_no_dots = ascii_word_only.lstrip('.')[:20]
156
 
    return short_no_dots + _next_id_suffix()
157
 
 
158
 
 
 
129
    return generate_ids.gen_file_id(name)
 
130
 
 
131
 
 
132
@deprecated_function(zero_thirteen)
159
133
def gen_root_id():
160
 
    """Return a new tree-root file id."""
161
 
    return gen_file_id('TREE_ROOT')
 
134
    """Return a new tree-root file id.
 
135
 
 
136
    This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
 
137
    """
 
138
    return generate_ids.gen_root_id()
162
139
 
163
140
 
164
141
class TreeEntry(object):
253
230
            self.basedir = wt.basedir
254
231
            self._control_files = wt._control_files
255
232
            self._hashcache = wt._hashcache
256
 
            self._set_inventory(wt._inventory)
 
233
            self._set_inventory(wt._inventory, dirty=False)
257
234
            self._format = wt._format
258
235
            self.bzrdir = wt.bzrdir
259
236
        from bzrlib.hashcache import HashCache
301
278
            hc.write()
302
279
 
303
280
        if _inventory is None:
304
 
            self._set_inventory(self.read_working_inventory())
 
281
            self._inventory_is_modified = False
 
282
            self.read_working_inventory()
305
283
        else:
306
 
            self._set_inventory(_inventory)
 
284
            # the caller of __init__ has provided an inventory,
 
285
            # we assume they know what they are doing - as its only
 
286
            # the Format factory and creation methods that are
 
287
            # permitted to do this.
 
288
            self._set_inventory(_inventory, dirty=False)
307
289
 
308
290
    branch = property(
309
291
        fget=lambda self: self._branch,
324
306
        self._control_files.break_lock()
325
307
        self.branch.break_lock()
326
308
 
327
 
    def _set_inventory(self, inv):
 
309
    def _set_inventory(self, inv, dirty):
 
310
        """Set the internal cached inventory.
 
311
 
 
312
        :param inv: The inventory to set.
 
313
        :param dirty: A boolean indicating whether the inventory is the same
 
314
            logical inventory as whats on disk. If True the inventory is not
 
315
            the same and should be written to disk or data will be lost, if
 
316
            False then the inventory is the same as that on disk and any
 
317
            serialisation would be unneeded overhead.
 
318
        """
328
319
        assert inv.root is not None
329
320
        self._inventory = inv
 
321
        self._inventory_is_modified = dirty
330
322
 
331
323
    @staticmethod
332
324
    def open(path=None, _unsupported=False):
399
391
        else:
400
392
            try:
401
393
                xml = self.read_basis_inventory()
402
 
                inv = bzrlib.xml6.serializer_v6.read_inventory_from_string(xml)
 
394
                inv = xml6.serializer_v6.read_inventory_from_string(xml)
403
395
                if inv is not None and inv.revision_id == revision_id:
404
 
                    return bzrlib.tree.RevisionTree(self.branch.repository, 
405
 
                                                    inv, revision_id)
 
396
                    return bzrlib.revisiontree.RevisionTree(
 
397
                        self.branch.repository, inv, revision_id)
406
398
            except (NoSuchFile, errors.BadInventoryFormat):
407
399
                pass
408
400
        # No cached copy available, retrieve from the repository.
463
455
        The path may be absolute or relative. If its a relative path it is 
464
456
        interpreted relative to the python current working directory.
465
457
        """
466
 
        return relpath(self.basedir, path)
 
458
        return osutils.relpath(self.basedir, path)
467
459
 
468
460
    def has_filename(self, filename):
469
461
        return osutils.lexists(self.abspath(filename))
497
489
                parents.append(l.rstrip('\n'))
498
490
        return parents
499
491
 
 
492
    @needs_read_lock
500
493
    def get_root_id(self):
501
494
        """Return the id of this trees root"""
502
 
        inv = self.read_working_inventory()
503
 
        return inv.root.file_id
 
495
        return self._inventory.root.file_id
504
496
        
505
497
    def _get_store_filename(self, file_id):
506
498
        ## XXX: badly named; this is not in the store at all
532
524
    @needs_read_lock
533
525
    def copy_content_into(self, tree, revision_id=None):
534
526
        """Copy the current content and user files of this tree into tree."""
 
527
        tree.set_root_id(self.get_root_id())
535
528
        if revision_id is None:
536
 
            transform_tree(tree, self)
 
529
            merge.transform_tree(tree, self)
537
530
        else:
538
531
            # TODO now merge from tree.last_revision to revision (to preserve
539
532
            # user local changes)
540
 
            transform_tree(tree, self)
 
533
            merge.transform_tree(tree, self)
541
534
            tree.set_parent_ids([revision_id])
542
535
 
543
536
    def id2abspath(self, file_id):
675
668
        """
676
669
        return self.get_parent_ids()[1:]
677
670
 
 
671
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
 
672
        """Common ghost checking functionality from set_parent_*.
 
673
 
 
674
        This checks that the left hand-parent exists if there are any
 
675
        revisions present.
 
676
        """
 
677
        if len(revision_ids) > 0:
 
678
            leftmost_id = revision_ids[0]
 
679
            if (not allow_leftmost_as_ghost and not
 
680
                self.branch.repository.has_revision(leftmost_id)):
 
681
                raise errors.GhostRevisionUnusableHere(leftmost_id)
 
682
 
 
683
    def _set_merges_from_parent_ids(self, parent_ids):
 
684
        merges = parent_ids[1:]
 
685
        self._control_files.put_utf8('pending-merges', '\n'.join(merges))
 
686
 
678
687
    @needs_tree_write_lock
679
688
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
680
689
        """Set the parent ids to revision_ids.
688
697
        :param revision_ids: The revision_ids to set as the parent ids of this
689
698
            working tree. Any of these may be ghosts.
690
699
        """
 
700
        self._check_parents_for_ghosts(revision_ids,
 
701
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
702
 
691
703
        if len(revision_ids) > 0:
692
 
            leftmost_id = revision_ids[0]
693
 
            if (not allow_leftmost_as_ghost and not
694
 
                self.branch.repository.has_revision(leftmost_id)):
695
 
                raise errors.GhostRevisionUnusableHere(leftmost_id)
696
 
            self.set_last_revision(leftmost_id)
 
704
            self.set_last_revision(revision_ids[0])
697
705
        else:
698
706
            self.set_last_revision(None)
699
 
        merges = revision_ids[1:]
700
 
        self._control_files.put_utf8('pending-merges', '\n'.join(merges))
 
707
 
 
708
        self._set_merges_from_parent_ids(revision_ids)
701
709
 
702
710
    @needs_tree_write_lock
703
711
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
704
712
        """See MutableTree.set_parent_trees."""
705
 
        # parent trees are not used in current format trees, delegate to
706
 
        # set_parent_ids
707
 
        self.set_parent_ids([rev for (rev, tree) in parents_list],
 
713
        parent_ids = [rev for (rev, tree) in parents_list]
 
714
 
 
715
        self._check_parents_for_ghosts(parent_ids,
708
716
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
709
717
 
 
718
        if len(parent_ids) == 0:
 
719
            leftmost_parent_id = None
 
720
            leftmost_parent_tree = None
 
721
        else:
 
722
            leftmost_parent_id, leftmost_parent_tree = parents_list[0]
 
723
 
 
724
        if self._change_last_revision(leftmost_parent_id):
 
725
            if leftmost_parent_tree is None:
 
726
                # If we don't have a tree, fall back to reading the
 
727
                # parent tree from the repository.
 
728
                self._cache_basis_inventory(leftmost_parent_id)
 
729
            else:
 
730
                inv = leftmost_parent_tree.inventory
 
731
                xml = self._create_basis_xml_from_inventory(
 
732
                                        leftmost_parent_id, inv)
 
733
                self._write_basis_inventory(xml)
 
734
        self._set_merges_from_parent_ids(parent_ids)
 
735
 
710
736
    @needs_tree_write_lock
711
737
    def set_pending_merges(self, rev_list):
712
738
        parents = self.get_parent_ids()
794
820
    def mkdir(self, path, file_id=None):
795
821
        """See MutableTree.mkdir()."""
796
822
        if file_id is None:
797
 
            file_id = gen_file_id(os.path.basename(path))
 
823
            file_id = generate_ids.gen_file_id(os.path.basename(path))
798
824
        os.mkdir(self.abspath(path))
799
825
        self.add(path, file_id, 'directory')
800
826
        return file_id
810
836
        else:
811
837
            return '?'
812
838
 
813
 
    def list_files(self):
 
839
    def flush(self):
 
840
        """Write the in memory inventory to disk."""
 
841
        # TODO: Maybe this should only write on dirty ?
 
842
        if self._control_files._lock_mode != 'w':
 
843
            raise errors.NotWriteLocked(self)
 
844
        sio = StringIO()
 
845
        xml5.serializer_v5.write_inventory(self._inventory, sio)
 
846
        sio.seek(0)
 
847
        self._control_files.put('inventory', sio)
 
848
        self._inventory_is_modified = False
 
849
 
 
850
    def list_files(self, include_root=False):
814
851
        """Recursively list all files as (path, class, kind, id, entry).
815
852
 
816
853
        Lists, but does not descend into unversioned directories.
821
858
        Skips the control directory.
822
859
        """
823
860
        inv = self._inventory
 
861
        if include_root is True:
 
862
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
824
863
        # Convert these into local objects to save lookup times
825
864
        pathjoin = osutils.pathjoin
826
865
        file_kind = osutils.file_kind
969
1008
        # create a file in this interval and then the rename might be
970
1009
        # left half-done.  But we should have caught most problems.
971
1010
        orig_inv = deepcopy(self.inventory)
 
1011
        original_modified = self._inventory_is_modified
972
1012
        try:
 
1013
            if len(from_paths):
 
1014
                self._inventory_is_modified = True
973
1015
            for f in from_paths:
974
1016
                name_tail = splitpath(f)[-1]
975
1017
                dest_path = pathjoin(to_name, name_tail)
976
1018
                result.append((f, dest_path))
977
1019
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
978
1020
                try:
979
 
                    rename(self.abspath(f), self.abspath(dest_path))
 
1021
                    osutils.rename(self.abspath(f), self.abspath(dest_path))
980
1022
                except OSError, e:
981
1023
                    raise BzrError("failed to rename %r to %r: %s" %
982
 
                                   (f, dest_path, e[1]),
983
 
                            ["rename rolled back"])
 
1024
                                   (f, dest_path, e[1]))
984
1025
        except:
985
1026
            # restore the inventory on error
986
 
            self._set_inventory(orig_inv)
 
1027
            self._set_inventory(orig_inv, dirty=original_modified)
987
1028
            raise
988
1029
        self._write_inventory(inv)
989
1030
        return result
1028
1069
        from_abs = self.abspath(from_rel)
1029
1070
        to_abs = self.abspath(to_rel)
1030
1071
        try:
1031
 
            rename(from_abs, to_abs)
 
1072
            osutils.rename(from_abs, to_abs)
1032
1073
        except OSError, e:
1033
1074
            inv.rename(file_id, from_parent, from_name)
1034
1075
            raise BzrError("failed to rename %r to %r: %s"
1035
 
                    % (from_abs, to_abs, e[1]),
1036
 
                    ["rename rolled back"])
 
1076
                    % (from_abs, to_abs, e[1]))
1037
1077
        self._write_inventory(inv)
1038
1078
 
1039
1079
    @needs_read_lock
1111
1151
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
1112
1152
                try:
1113
1153
                    new_basis_tree = self.branch.basis_tree()
1114
 
                    merge_inner(self.branch,
 
1154
                    merge.merge_inner(
 
1155
                                self.branch,
1115
1156
                                new_basis_tree,
1116
1157
                                basis_tree,
1117
1158
                                this_tree=self,
1118
1159
                                pb=pb)
 
1160
                    if (basis_tree.inventory.root is None and
 
1161
                        new_basis_tree.inventory.root is not None):
 
1162
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1119
1163
                finally:
1120
1164
                    pb.finished()
1121
1165
                # TODO - dedup parents list with things merged by pull ?
1406
1450
            self.branch.set_revision_history([new_revision])
1407
1451
        return True
1408
1452
 
 
1453
    def _write_basis_inventory(self, xml):
 
1454
        """Write the basis inventory XML to the basis-inventory file"""
 
1455
        assert isinstance(xml, str), 'serialised xml must be bytestring.'
 
1456
        path = self._basis_inventory_name()
 
1457
        sio = StringIO(xml)
 
1458
        self._control_files.put(path, sio)
 
1459
 
 
1460
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
 
1461
        """Create the text that will be saved in basis-inventory"""
 
1462
        inventory.revision_id = revision_id
 
1463
        return xml6.serializer_v6.write_inventory_to_string(inventory)
 
1464
 
1409
1465
    def _cache_basis_inventory(self, new_revision):
1410
1466
        """Cache new_revision as the basis inventory."""
1411
1467
        # TODO: this should allow the ready-to-use inventory to be passed in,
1428
1484
                'format="6"' not in firstline):
1429
1485
                inv = self.branch.repository.deserialise_inventory(
1430
1486
                    new_revision, xml)
1431
 
                inv.revision_id = new_revision
1432
 
                xml = bzrlib.xml6.serializer_v6.write_inventory_to_string(inv)
1433
 
            assert isinstance(xml, str), 'serialised xml must be bytestring.'
1434
 
            path = self._basis_inventory_name()
1435
 
            sio = StringIO(xml)
1436
 
            self._control_files.put(path, sio)
 
1487
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
 
1488
            self._write_basis_inventory(xml)
1437
1489
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1438
1490
            pass
1439
1491
 
1444
1496
        
1445
1497
    @needs_read_lock
1446
1498
    def read_working_inventory(self):
1447
 
        """Read the working inventory."""
 
1499
        """Read the working inventory.
 
1500
        
 
1501
        :raises errors.InventoryModified: read_working_inventory will fail
 
1502
            when the current in memory inventory has been modified.
 
1503
        """
 
1504
        # conceptually this should be an implementation detail of the tree. 
 
1505
        # XXX: Deprecate this.
1448
1506
        # ElementTree does its own conversion from UTF-8, so open in
1449
1507
        # binary.
1450
 
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1508
        if self._inventory_is_modified:
 
1509
            raise errors.InventoryModified(self)
 
1510
        result = xml5.serializer_v5.read_inventory(
1451
1511
            self._control_files.get('inventory'))
1452
 
        self._set_inventory(result)
 
1512
        self._set_inventory(result, dirty=False)
1453
1513
        return result
1454
1514
 
1455
1515
    @needs_tree_write_lock
1487
1547
                    new_status = 'I'
1488
1548
                else:
1489
1549
                    new_status = '?'
1490
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1550
                textui.show_status(new_status, inv[fid].kind, f,
 
1551
                                   to_file=to_file)
1491
1552
            del inv[fid]
1492
1553
 
1493
1554
        self._write_inventory(inv)
1495
1556
    @needs_tree_write_lock
1496
1557
    def revert(self, filenames, old_tree=None, backups=True, 
1497
1558
               pb=DummyProgress()):
1498
 
        from transform import revert
1499
 
        from conflicts import resolve
 
1559
        from bzrlib.conflicts import resolve
1500
1560
        if old_tree is None:
1501
1561
            old_tree = self.basis_tree()
1502
 
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1562
        conflicts = transform.revert(self, old_tree, filenames, backups, pb)
1503
1563
        if not len(filenames):
1504
1564
            self.set_parent_ids(self.get_parent_ids()[:1])
1505
1565
            resolve(self)
1535
1595
    @needs_tree_write_lock
1536
1596
    def set_root_id(self, file_id):
1537
1597
        """Set the root id for this tree."""
1538
 
        inv = self.read_working_inventory()
 
1598
        # for compatability 
 
1599
        if file_id is None:
 
1600
            symbol_versioning.warn(symbol_versioning.zero_twelve
 
1601
                % 'WorkingTree.set_root_id with fileid=None',
 
1602
                DeprecationWarning,
 
1603
                stacklevel=3)
 
1604
            file_id = ROOT_ID
 
1605
        inv = self._inventory
1539
1606
        orig_root_id = inv.root.file_id
 
1607
        # TODO: it might be nice to exit early if there was nothing
 
1608
        # to do, saving us from trigger a sync on unlock.
 
1609
        self._inventory_is_modified = True
 
1610
        # we preserve the root inventory entry object, but
 
1611
        # unlinkit from the byid index
1540
1612
        del inv._byid[inv.root.file_id]
1541
1613
        inv.root.file_id = file_id
 
1614
        # and link it into the index with the new changed id.
1542
1615
        inv._byid[inv.root.file_id] = inv.root
 
1616
        # and finally update all children to reference the new id.
 
1617
        # XXX: this should be safe to just look at the root.children
 
1618
        # list, not the WHOLE INVENTORY.
1543
1619
        for fid in inv:
1544
1620
            entry = inv[fid]
1545
1621
            if entry.parent_id == orig_root_id:
1546
1622
                entry.parent_id = inv.root.file_id
1547
 
        self._write_inventory(inv)
1548
1623
 
1549
1624
    def unlock(self):
1550
1625
        """See Branch.unlock.
1557
1632
        """
1558
1633
        raise NotImplementedError(self.unlock)
1559
1634
 
1560
 
    @needs_write_lock
1561
1635
    def update(self):
1562
1636
        """Update a working tree along its branch.
1563
1637
 
1564
 
        This will update the branch if its bound too, which means we have multiple trees involved:
1565
 
        The new basis tree of the master.
1566
 
        The old basis tree of the branch.
1567
 
        The old basis tree of the working tree.
1568
 
        The current working tree state.
1569
 
        pathologically all three may be different, and non ancestors of each other.
1570
 
        Conceptually we want to:
1571
 
        Preserve the wt.basis->wt.state changes
1572
 
        Transform the wt.basis to the new master basis.
1573
 
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1574
 
        Restore the wt.basis->wt.state changes.
 
1638
        This will update the branch if its bound too, which means we have
 
1639
        multiple trees involved:
 
1640
 
 
1641
        - The new basis tree of the master.
 
1642
        - The old basis tree of the branch.
 
1643
        - The old basis tree of the working tree.
 
1644
        - The current working tree state.
 
1645
 
 
1646
        Pathologically, all three may be different, and non-ancestors of each
 
1647
        other.  Conceptually we want to:
 
1648
 
 
1649
        - Preserve the wt.basis->wt.state changes
 
1650
        - Transform the wt.basis to the new master basis.
 
1651
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1652
          it into the tree.
 
1653
        - Restore the wt.basis->wt.state changes.
1575
1654
 
1576
1655
        There isn't a single operation at the moment to do that, so we:
1577
 
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
1578
 
        Do a 'normal' merge of the old branch basis if it is relevant.
1579
 
        """
1580
 
        old_tip = self.branch.update()
 
1656
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1657
          basis.
 
1658
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
1659
        """
 
1660
        if self.branch.get_master_branch() is not None:
 
1661
            self.lock_write()
 
1662
            update_branch = True
 
1663
        else:
 
1664
            self.lock_tree_write()
 
1665
            update_branch = False
 
1666
        try:
 
1667
            if update_branch:
 
1668
                old_tip = self.branch.update()
 
1669
            else:
 
1670
                old_tip = None
 
1671
            return self._update_tree(old_tip)
 
1672
        finally:
 
1673
            self.unlock()
 
1674
 
 
1675
    @needs_tree_write_lock
 
1676
    def _update_tree(self, old_tip=None):
 
1677
        """Update a tree to the master branch.
 
1678
 
 
1679
        :param old_tip: if supplied, the previous tip revision the branch,
 
1680
            before it was changed to the master branch's tip.
 
1681
        """
1581
1682
        # here if old_tip is not None, it is the old tip of the branch before
1582
1683
        # it was updated from the master branch. This should become a pending
1583
1684
        # merge in the working tree to preserve the user existing work.  we
1597
1698
            # merge tree state up to new branch tip.
1598
1699
            basis = self.basis_tree()
1599
1700
            to_tree = self.branch.basis_tree()
1600
 
            result += merge_inner(self.branch,
 
1701
            if basis.inventory.root is None:
 
1702
                self.set_root_id(to_tree.inventory.root.file_id)
 
1703
            result += merge.merge_inner(
 
1704
                                  self.branch,
1601
1705
                                  to_tree,
1602
1706
                                  basis,
1603
1707
                                  this_tree=self)
1638
1742
                base_rev_id = None
1639
1743
            base_tree = self.branch.repository.revision_tree(base_rev_id)
1640
1744
            other_tree = self.branch.repository.revision_tree(old_tip)
1641
 
            result += merge_inner(self.branch,
 
1745
            result += merge.merge_inner(
 
1746
                                  self.branch,
1642
1747
                                  other_tree,
1643
1748
                                  base_tree,
1644
1749
                                  this_tree=self)
1647
1752
    @needs_tree_write_lock
1648
1753
    def _write_inventory(self, inv):
1649
1754
        """Write inventory as the current inventory."""
1650
 
        sio = StringIO()
1651
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1652
 
        sio.seek(0)
1653
 
        self._control_files.put('inventory', sio)
1654
 
        self._set_inventory(inv)
1655
 
        mutter('wrote working inventory')
 
1755
        self._set_inventory(inv, dirty=True)
 
1756
        self.flush()
1656
1757
 
1657
1758
    def set_conflicts(self, arg):
1658
1759
        raise UnsupportedOperation(self.set_conflicts, self)
1662
1763
 
1663
1764
    @needs_read_lock
1664
1765
    def conflicts(self):
1665
 
        conflicts = ConflictList()
 
1766
        conflicts = _mod_conflicts.ConflictList()
1666
1767
        for conflicted in self._iter_conflicts():
1667
1768
            text = True
1668
1769
            try:
1681
1782
                    if text == False:
1682
1783
                        break
1683
1784
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1684
 
            conflicts.append(Conflict.factory(ctype, path=conflicted,
 
1785
            conflicts.append(_mod_conflicts.Conflict.factory(ctype,
 
1786
                             path=conflicted,
1685
1787
                             file_id=self.path2id(conflicted)))
1686
1788
        return conflicts
1687
1789
 
1709
1811
 
1710
1812
    def unlock(self):
1711
1813
        # we share control files:
1712
 
        if self._hashcache.needs_write and self._control_files._lock_count==3:
1713
 
            self._hashcache.write()
 
1814
        if self._control_files._lock_count == 3:
 
1815
            # _inventory_is_modified is always False during a read lock.
 
1816
            if self._inventory_is_modified:
 
1817
                self.flush()
 
1818
            if self._hashcache.needs_write:
 
1819
                self._hashcache.write()
1714
1820
        # reverse order of locking.
1715
1821
        try:
1716
1822
            return self._control_files.unlock()
1757
1863
    def add_conflicts(self, new_conflicts):
1758
1864
        conflict_set = set(self.conflicts())
1759
1865
        conflict_set.update(set(list(new_conflicts)))
1760
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1761
 
                                               key=Conflict.sort_key)))
 
1866
        self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
 
1867
                                       key=_mod_conflicts.Conflict.sort_key)))
1762
1868
 
1763
1869
    @needs_read_lock
1764
1870
    def conflicts(self):
1765
1871
        try:
1766
1872
            confile = self._control_files.get('conflicts')
1767
1873
        except NoSuchFile:
1768
 
            return ConflictList()
 
1874
            return _mod_conflicts.ConflictList()
1769
1875
        try:
1770
1876
            if confile.next() != CONFLICT_HEADER_1 + '\n':
1771
1877
                raise ConflictFormatError()
1772
1878
        except StopIteration:
1773
1879
            raise ConflictFormatError()
1774
 
        return ConflictList.from_stanzas(RioReader(confile))
 
1880
        return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
1775
1881
 
1776
1882
    def unlock(self):
1777
 
        if self._hashcache.needs_write and self._control_files._lock_count==1:
1778
 
            self._hashcache.write()
 
1883
        if self._control_files._lock_count == 1:
 
1884
            # _inventory_is_modified is always False during a read lock.
 
1885
            if self._inventory_is_modified:
 
1886
                self.flush()
 
1887
            if self._hashcache.needs_write:
 
1888
                self._hashcache.write()
1779
1889
        # reverse order of locking.
1780
1890
        try:
1781
1891
            return self._control_files.unlock()
1784
1894
 
1785
1895
 
1786
1896
def get_conflicted_stem(path):
1787
 
    for suffix in CONFLICT_SUFFIXES:
 
1897
    for suffix in _mod_conflicts.CONFLICT_SUFFIXES:
1788
1898
        if path.endswith(suffix):
1789
1899
            return path[:-len(suffix)]
1790
1900
 
1896
2006
        """
1897
2007
        sio = StringIO()
1898
2008
        inv = Inventory()
1899
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
2009
        xml5.serializer_v5.write_inventory(inv, sio)
1900
2010
        sio.seek(0)
1901
2011
        control_files.put('inventory', sio)
1902
2012
 
1927
2037
                         _internal=True,
1928
2038
                         _format=self,
1929
2039
                         _bzrdir=a_bzrdir)
1930
 
        wt._write_inventory(inv)
1931
 
        wt.set_root_id(inv.root.file_id)
1932
2040
        basis_tree = branch.repository.revision_tree(revision)
 
2041
        if basis_tree.inventory.root is not None:
 
2042
            wt.set_root_id(basis_tree.inventory.root.file_id)
 
2043
        # set the parent list and cache the basis tree.
1933
2044
        wt.set_parent_trees([(revision, basis_tree)])
1934
 
        build_tree(basis_tree, wt)
 
2045
        transform.build_tree(basis_tree, wt)
1935
2046
        return wt
1936
2047
 
1937
2048
    def __init__(self):
1999
2110
        branch = a_bzrdir.open_branch()
2000
2111
        if revision_id is None:
2001
2112
            revision_id = branch.last_revision()
2002
 
        inv = Inventory() 
 
2113
        # WorkingTree3 can handle an inventory which has a unique root id.
 
2114
        # as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
 
2115
        # those trees. And because there isn't a format bump inbetween, we
 
2116
        # are maintaining compatibility with older clients.
 
2117
        # inv = Inventory(root_id=gen_root_id())
 
2118
        inv = Inventory()
2003
2119
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2004
2120
                         branch,
2005
2121
                         inv,
2009
2125
                         _control_files=control_files)
2010
2126
        wt.lock_tree_write()
2011
2127
        try:
2012
 
            wt._write_inventory(inv)
2013
 
            wt.set_root_id(inv.root.file_id)
2014
2128
            basis_tree = branch.repository.revision_tree(revision_id)
2015
 
            if revision_id == bzrlib.revision.NULL_REVISION:
 
2129
            # only set an explicit root id if there is one to set.
 
2130
            if basis_tree.inventory.root is not None:
 
2131
                wt.set_root_id(basis_tree.inventory.root.file_id)
 
2132
            if revision_id == NULL_REVISION:
2016
2133
                wt.set_parent_trees([])
2017
2134
            else:
2018
2135
                wt.set_parent_trees([(revision_id, basis_tree)])
2019
 
            build_tree(basis_tree, wt)
 
2136
            transform.build_tree(basis_tree, wt)
2020
2137
        finally:
 
2138
            # Unlock in this order so that the unlock-triggers-flush in
 
2139
            # WorkingTree is given a chance to fire.
 
2140
            control_files.unlock()
2021
2141
            wt.unlock()
2022
 
            control_files.unlock()
2023
2142
        return wt
2024
2143
 
2025
2144
    def __init__(self):