~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

Merge 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
    ignores,
 
59
    merge,
 
60
    osutils,
 
61
    textui,
 
62
    transform,
 
63
    urlutils,
 
64
    xml5,
 
65
    xml6,
 
66
    )
57
67
import bzrlib.branch
58
 
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
68
from bzrlib.transport import get_transport
 
69
import bzrlib.ui
 
70
""")
 
71
 
 
72
from bzrlib import symbol_versioning
59
73
from bzrlib.decorators import needs_read_lock, needs_write_lock
60
74
from bzrlib.errors import (BzrCheckError,
61
75
                           BzrError,
67
81
                           MergeModifiedFormatError,
68
82
                           UnsupportedOperation,
69
83
                           )
70
 
from bzrlib.inventory import InventoryEntry, Inventory
 
84
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
71
85
from bzrlib.lockable_files import LockableFiles, TransportLock
72
86
from bzrlib.lockdir import LockDir
73
 
from bzrlib.merge import merge_inner, transform_tree
 
87
import bzrlib.mutabletree
 
88
from bzrlib.mutabletree import needs_tree_write_lock
74
89
from bzrlib.osutils import (
75
 
                            abspath,
76
 
                            compact_date,
77
 
                            file_kind,
78
 
                            isdir,
79
 
                            getcwd,
80
 
                            pathjoin,
81
 
                            pumpfile,
82
 
                            safe_unicode,
83
 
                            splitpath,
84
 
                            rand_chars,
85
 
                            normpath,
86
 
                            realpath,
87
 
                            relpath,
88
 
                            rename,
89
 
                            supports_executable,
90
 
                            )
 
90
    compact_date,
 
91
    file_kind,
 
92
    isdir,
 
93
    pathjoin,
 
94
    safe_unicode,
 
95
    splitpath,
 
96
    rand_chars,
 
97
    normpath,
 
98
    realpath,
 
99
    supports_executable,
 
100
    )
 
101
from bzrlib.trace import mutter, note
 
102
from bzrlib.transport.local import LocalTransport
 
103
import bzrlib.tree
91
104
from bzrlib.progress import DummyProgress, ProgressPhase
92
105
from bzrlib.revision import NULL_REVISION
 
106
import bzrlib.revisiontree
93
107
from bzrlib.rio import RioReader, rio_file, Stanza
94
108
from bzrlib.symbol_versioning import (deprecated_passed,
95
109
        deprecated_method,
96
110
        deprecated_function,
97
111
        DEPRECATED_PARAMETER,
98
112
        zero_eight,
 
113
        zero_eleven,
99
114
        )
100
 
from bzrlib.trace import mutter, note
101
 
from bzrlib.transform import build_tree
102
 
from bzrlib.transport import get_transport
103
 
from bzrlib.transport.local import LocalTransport
104
 
from bzrlib.textui import show_status
105
 
import bzrlib.tree
106
 
import bzrlib.ui
107
 
import bzrlib.xml5
108
 
 
 
115
 
 
116
 
 
117
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
118
CONFLICT_HEADER_1 = "BZR conflict list format 1"
109
119
 
110
120
# the regex removes any weird characters; we don't escape them 
111
121
# but rather just pull them out
155
165
 
156
166
def gen_root_id():
157
167
    """Return a new tree-root file id."""
158
 
    return gen_file_id('TREE_ROOT')
 
168
    return gen_file_id('tree_root')
159
169
 
160
170
 
161
171
class TreeEntry(object):
213
223
        return ''
214
224
 
215
225
 
216
 
class WorkingTree(bzrlib.tree.Tree):
 
226
class WorkingTree(bzrlib.mutabletree.MutableTree):
217
227
    """Working copy tree.
218
228
 
219
229
    The inventory is held in the `Branch` working-inventory, and the
250
260
            self.basedir = wt.basedir
251
261
            self._control_files = wt._control_files
252
262
            self._hashcache = wt._hashcache
253
 
            self._set_inventory(wt._inventory)
 
263
            self._set_inventory(wt._inventory, dirty=False)
254
264
            self._format = wt._format
255
265
            self.bzrdir = wt.bzrdir
256
266
        from bzrlib.hashcache import HashCache
298
308
            hc.write()
299
309
 
300
310
        if _inventory is None:
301
 
            self._set_inventory(self.read_working_inventory())
 
311
            self._inventory_is_modified = False
 
312
            self.read_working_inventory()
302
313
        else:
303
 
            self._set_inventory(_inventory)
 
314
            # the caller of __init__ has provided an inventory,
 
315
            # we assume they know what they are doing - as its only
 
316
            # the Format factory and creation methods that are
 
317
            # permitted to do this.
 
318
            self._set_inventory(_inventory, dirty=False)
304
319
 
305
320
    branch = property(
306
321
        fget=lambda self: self._branch,
321
336
        self._control_files.break_lock()
322
337
        self.branch.break_lock()
323
338
 
324
 
    def _set_inventory(self, inv):
 
339
    def _set_inventory(self, inv, dirty):
 
340
        """Set the internal cached inventory.
 
341
 
 
342
        :param inv: The inventory to set.
 
343
        :param dirty: A boolean indicating whether the inventory is the same
 
344
            logical inventory as whats on disk. If True the inventory is not
 
345
            the same and should be written to disk or data will be lost, if
 
346
            False then the inventory is the same as that on disk and any
 
347
            serialisation would be unneeded overhead.
 
348
        """
325
349
        assert inv.root is not None
326
350
        self._inventory = inv
327
 
        self.path2id = self._inventory.path2id
328
 
 
329
 
    def is_control_filename(self, filename):
330
 
        """True if filename is the name of a control file in this tree.
331
 
        
332
 
        :param filename: A filename within the tree. This is a relative path
333
 
        from the root of this tree.
334
 
 
335
 
        This is true IF and ONLY IF the filename is part of the meta data
336
 
        that bzr controls in this tree. I.E. a random .bzr directory placed
337
 
        on disk will not be a control file for this tree.
338
 
        """
339
 
        return self.bzrdir.is_control_filename(filename)
 
351
        self._inventory_is_modified = dirty
340
352
 
341
353
    @staticmethod
342
354
    def open(path=None, _unsupported=False):
399
411
        If the left most parent is a ghost then the returned tree will be an
400
412
        empty tree - one obtained by calling repository.revision_tree(None).
401
413
        """
402
 
        revision_id = self.last_revision()
403
 
        if revision_id is not None:
 
414
        try:
 
415
            revision_id = self.get_parent_ids()[0]
 
416
        except IndexError:
 
417
            # no parents, return an empty revision tree.
 
418
            # in the future this should return the tree for
 
419
            # 'empty:' - the implicit root empty tree.
 
420
            return self.branch.repository.revision_tree(None)
 
421
        else:
404
422
            try:
405
423
                xml = self.read_basis_inventory()
406
 
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
407
 
                inv.root.revision = revision_id
408
 
            except NoSuchFile:
409
 
                inv = None
410
 
            if inv is not None and inv.revision_id == revision_id:
411
 
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
412
 
                                                revision_id)
413
 
        # FIXME? RBC 20060403 should we cache the inventory here ?
 
424
                inv = xml6.serializer_v6.read_inventory_from_string(xml)
 
425
                if inv is not None and inv.revision_id == revision_id:
 
426
                    return bzrlib.tree.RevisionTree(self.branch.repository,
 
427
                                                    inv, revision_id)
 
428
            except (NoSuchFile, errors.BadInventoryFormat):
 
429
                pass
 
430
        # No cached copy available, retrieve from the repository.
 
431
        # FIXME? RBC 20060403 should we cache the inventory locally
 
432
        # at this point ?
414
433
        try:
415
434
            return self.branch.repository.revision_tree(revision_id)
416
435
        except errors.RevisionNotPresent:
466
485
        The path may be absolute or relative. If its a relative path it is 
467
486
        interpreted relative to the python current working directory.
468
487
        """
469
 
        return relpath(self.basedir, path)
 
488
        return osutils.relpath(self.basedir, path)
470
489
 
471
490
    def has_filename(self, filename):
472
491
        return osutils.lexists(self.abspath(filename))
486
505
        This implementation reads the pending merges list and last_revision
487
506
        value and uses that to decide what the parents list should be.
488
507
        """
489
 
        last_rev = self.last_revision()
 
508
        last_rev = self._last_revision()
490
509
        if last_rev is None:
491
510
            parents = []
492
511
        else:
500
519
                parents.append(l.rstrip('\n'))
501
520
        return parents
502
521
 
 
522
    @needs_read_lock
503
523
    def get_root_id(self):
504
524
        """Return the id of this trees root"""
505
 
        inv = self.read_working_inventory()
506
 
        return inv.root.file_id
 
525
        return self._inventory.root.file_id
507
526
        
508
527
    def _get_store_filename(self, file_id):
509
528
        ## XXX: badly named; this is not in the store at all
535
554
    @needs_read_lock
536
555
    def copy_content_into(self, tree, revision_id=None):
537
556
        """Copy the current content and user files of this tree into tree."""
 
557
        tree.set_root_id(self.get_root_id())
538
558
        if revision_id is None:
539
 
            transform_tree(tree, self)
 
559
            merge.transform_tree(tree, self)
540
560
        else:
541
561
            # TODO now merge from tree.last_revision to revision (to preserve
542
562
            # user local changes)
543
 
            transform_tree(tree, self)
 
563
            merge.transform_tree(tree, self)
544
564
            tree.set_parent_ids([revision_id])
545
565
 
546
 
    @needs_write_lock
547
 
    def commit(self, message=None, revprops=None, *args, **kwargs):
548
 
        # avoid circular imports
549
 
        from bzrlib.commit import Commit
550
 
        if revprops is None:
551
 
            revprops = {}
552
 
        if not 'branch-nick' in revprops:
553
 
            revprops['branch-nick'] = self.branch.nick
554
 
        # args for wt.commit start at message from the Commit.commit method,
555
 
        # but with branch a kwarg now, passing in args as is results in the
556
 
        #message being used for the branch
557
 
        args = (DEPRECATED_PARAMETER, message, ) + args
558
 
        committed_id = Commit().commit( working_tree=self, revprops=revprops,
559
 
            *args, **kwargs)
560
 
        self._set_inventory(self.read_working_inventory())
561
 
        return committed_id
562
 
 
563
566
    def id2abspath(self, file_id):
564
567
        return self.abspath(self.id2path(file_id))
565
568
 
603
606
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
604
607
 
605
608
    @needs_write_lock
606
 
    def add(self, files, ids=None):
607
 
        """Make files versioned.
608
 
 
609
 
        Note that the command line normally calls smart_add instead,
610
 
        which can automatically recurse.
611
 
 
612
 
        This adds the files to the inventory, so that they will be
613
 
        recorded by the next commit.
614
 
 
615
 
        files
616
 
            List of paths to add, relative to the base of the tree.
617
 
 
618
 
        ids
619
 
            If set, use these instead of automatically generated ids.
620
 
            Must be the same length as the list of files, but may
621
 
            contain None for ids that are to be autogenerated.
622
 
 
623
 
        TODO: Perhaps have an option to add the ids even if the files do
624
 
              not (yet) exist.
625
 
 
626
 
        TODO: Perhaps callback with the ids and paths as they're added.
627
 
        """
 
609
    def _add(self, files, ids, kinds):
 
610
        """See MutableTree._add."""
628
611
        # TODO: Re-adding a file that is removed in the working copy
629
612
        # should probably put it back with the previous ID.
630
 
        if isinstance(files, basestring):
631
 
            assert(ids is None or isinstance(ids, basestring))
632
 
            files = [files]
633
 
            if ids is not None:
634
 
                ids = [ids]
635
 
 
636
 
        if ids is None:
637
 
            ids = [None] * len(files)
638
 
        else:
639
 
            assert(len(ids) == len(files))
640
 
 
 
613
        # the read and write working inventory should not occur in this 
 
614
        # function - they should be part of lock_write and unlock.
641
615
        inv = self.read_working_inventory()
642
 
        for f,file_id in zip(files, ids):
643
 
            if self.is_control_filename(f):
644
 
                raise errors.ForbiddenControlFileError(filename=f)
645
 
 
646
 
            fp = splitpath(f)
647
 
 
648
 
            if len(fp) == 0:
649
 
                raise BzrError("cannot add top-level %r" % f)
650
 
 
651
 
            fullpath = normpath(self.abspath(f))
652
 
            try:
653
 
                kind = file_kind(fullpath)
654
 
            except OSError, e:
655
 
                if e.errno == errno.ENOENT:
656
 
                    raise NoSuchFile(fullpath)
657
 
            if not InventoryEntry.versionable_kind(kind):
658
 
                raise errors.BadFileKindError(filename=f, kind=kind)
 
616
        for f, file_id, kind in zip(files, ids, kinds):
 
617
            assert kind is not None
659
618
            if file_id is None:
660
619
                inv.add_path(f, kind=kind)
661
620
            else:
662
621
                inv.add_path(f, kind=kind, file_id=file_id)
663
 
 
664
622
        self._write_inventory(inv)
665
623
 
 
624
    @needs_tree_write_lock
 
625
    def _gather_kinds(self, files, kinds):
 
626
        """See MutableTree._gather_kinds."""
 
627
        for pos, f in enumerate(files):
 
628
            if kinds[pos] is None:
 
629
                fullpath = normpath(self.abspath(f))
 
630
                try:
 
631
                    kinds[pos] = file_kind(fullpath)
 
632
                except OSError, e:
 
633
                    if e.errno == errno.ENOENT:
 
634
                        raise NoSuchFile(fullpath)
 
635
 
666
636
    @needs_write_lock
667
637
    def add_parent_tree_id(self, revision_id, allow_leftmost_as_ghost=False):
668
638
        """Add revision_id as a parent.
679
649
        self.set_parent_ids(parents,
680
650
            allow_leftmost_as_ghost=len(parents) > 1 or allow_leftmost_as_ghost)
681
651
 
682
 
    @needs_write_lock
 
652
    @needs_tree_write_lock
683
653
    def add_parent_tree(self, parent_tuple, allow_leftmost_as_ghost=False):
684
654
        """Add revision_id, tree tuple as a parent.
685
655
 
701
671
        self.set_parent_ids(parent_ids,
702
672
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
703
673
 
704
 
    @needs_write_lock
 
674
    @needs_tree_write_lock
705
675
    def add_pending_merge(self, *revision_ids):
706
676
        # TODO: Perhaps should check at this point that the
707
677
        # history of the revision is actually present?
715
685
        if updated:
716
686
            self.set_parent_ids(parents, allow_leftmost_as_ghost=True)
717
687
 
 
688
    @deprecated_method(zero_eleven)
718
689
    @needs_read_lock
719
690
    def pending_merges(self):
720
691
        """Return a list of pending merges.
721
692
 
722
693
        These are revisions that have been merged into the working
723
694
        directory but not yet committed.
 
695
 
 
696
        As of 0.11 this is deprecated. Please see WorkingTree.get_parent_ids()
 
697
        instead - which is available on all tree objects.
724
698
        """
725
699
        return self.get_parent_ids()[1:]
726
700
 
727
 
    @needs_write_lock
 
701
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
 
702
        """Common ghost checking functionality from set_parent_*.
 
703
 
 
704
        This checks that the left hand-parent exists if there are any
 
705
        revisions present.
 
706
        """
 
707
        if len(revision_ids) > 0:
 
708
            leftmost_id = revision_ids[0]
 
709
            if (not allow_leftmost_as_ghost and not
 
710
                self.branch.repository.has_revision(leftmost_id)):
 
711
                raise errors.GhostRevisionUnusableHere(leftmost_id)
 
712
 
 
713
    def _set_merges_from_parent_ids(self, parent_ids):
 
714
        merges = parent_ids[1:]
 
715
        self._control_files.put_utf8('pending-merges', '\n'.join(merges))
 
716
 
 
717
    @needs_tree_write_lock
728
718
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
729
719
        """Set the parent ids to revision_ids.
730
720
        
737
727
        :param revision_ids: The revision_ids to set as the parent ids of this
738
728
            working tree. Any of these may be ghosts.
739
729
        """
 
730
        self._check_parents_for_ghosts(revision_ids,
 
731
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
732
 
740
733
        if len(revision_ids) > 0:
741
 
            leftmost_id = revision_ids[0]
742
 
            if (not allow_leftmost_as_ghost and not
743
 
                self.branch.repository.has_revision(leftmost_id)):
744
 
                raise errors.GhostRevisionUnusableHere(leftmost_id)
745
 
            self.set_last_revision(leftmost_id)
 
734
            self.set_last_revision(revision_ids[0])
746
735
        else:
747
736
            self.set_last_revision(None)
748
 
        merges = revision_ids[1:]
749
 
        self._control_files.put_utf8('pending-merges', '\n'.join(merges))
750
 
 
751
 
    @needs_write_lock
 
737
 
 
738
        self._set_merges_from_parent_ids(revision_ids)
 
739
 
 
740
    @needs_tree_write_lock
752
741
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
753
 
        """Set the parents of the working tree.
 
742
        """See MutableTree.set_parent_trees."""
 
743
        parent_ids = [rev for (rev, tree) in parents_list]
754
744
 
755
 
        :param parents_list: A list of (revision_id, tree) tuples. 
756
 
            If tree is None, then that element is treated as an unreachable
757
 
            parent tree - i.e. a ghost.
758
 
        """
759
 
        # parent trees are not used in current format trees, delegate to
760
 
        # set_parent_ids
761
 
        self.set_parent_ids([rev for (rev, tree) in parents_list],
 
745
        self._check_parents_for_ghosts(parent_ids,
762
746
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
763
747
 
764
 
    @needs_write_lock
 
748
        if len(parent_ids) == 0:
 
749
            leftmost_parent_id = None
 
750
            leftmost_parent_tree = None
 
751
        else:
 
752
            leftmost_parent_id, leftmost_parent_tree = parents_list[0]
 
753
 
 
754
        if self._change_last_revision(leftmost_parent_id):
 
755
            if leftmost_parent_tree is None:
 
756
                # If we don't have a tree, fall back to reading the
 
757
                # parent tree from the repository.
 
758
                self._cache_basis_inventory(leftmost_parent_id)
 
759
            else:
 
760
                inv = leftmost_parent_tree.inventory
 
761
                xml = self._create_basis_xml_from_inventory(
 
762
                                        leftmost_parent_id, inv)
 
763
                self._write_basis_inventory(xml)
 
764
        self._set_merges_from_parent_ids(parent_ids)
 
765
 
 
766
    @needs_tree_write_lock
765
767
    def set_pending_merges(self, rev_list):
766
768
        parents = self.get_parent_ids()
767
769
        leftmost = parents[:1]
768
770
        new_parents = leftmost + rev_list
769
771
        self.set_parent_ids(new_parents)
770
772
 
771
 
    @needs_write_lock
 
773
    @needs_tree_write_lock
772
774
    def set_merge_modified(self, modified_hashes):
773
775
        def iter_stanzas():
774
776
            for file_id, hash in modified_hashes.iteritems():
775
777
                yield Stanza(file_id=file_id, hash=hash)
776
778
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
777
779
 
778
 
    @needs_write_lock
 
780
    @needs_tree_write_lock
779
781
    def _put_rio(self, filename, stanzas, header):
780
782
        my_file = rio_file(stanzas, header)
781
783
        self._control_files.put(filename, my_file)
782
784
 
783
 
    @needs_write_lock
 
785
    @needs_write_lock # because merge pulls data into the branch.
784
786
    def merge_from_branch(self, branch, to_revision=None):
785
787
        """Merge from a branch into this working tree.
786
788
 
844
846
                merge_hashes[file_id] = hash
845
847
        return merge_hashes
846
848
 
 
849
    @needs_write_lock
 
850
    def mkdir(self, path, file_id=None):
 
851
        """See MutableTree.mkdir()."""
 
852
        if file_id is None:
 
853
            file_id = gen_file_id(os.path.basename(path))
 
854
        os.mkdir(self.abspath(path))
 
855
        self.add(path, file_id, 'directory')
 
856
        return file_id
 
857
 
847
858
    def get_symlink_target(self, file_id):
848
859
        return os.readlink(self.id2abspath(file_id))
849
860
 
855
866
        else:
856
867
            return '?'
857
868
 
858
 
    def list_files(self):
 
869
    def flush(self):
 
870
        """Write the in memory inventory to disk."""
 
871
        # TODO: Maybe this should only write on dirty ?
 
872
        if self._control_files._lock_mode != 'w':
 
873
            raise errors.NotWriteLocked(self)
 
874
        sio = StringIO()
 
875
        xml5.serializer_v5.write_inventory(self._inventory, sio)
 
876
        sio.seek(0)
 
877
        self._control_files.put('inventory', sio)
 
878
        self._inventory_is_modified = False
 
879
 
 
880
    def list_files(self, include_root=False):
859
881
        """Recursively list all files as (path, class, kind, id, entry).
860
882
 
861
883
        Lists, but does not descend into unversioned directories.
866
888
        Skips the control directory.
867
889
        """
868
890
        inv = self._inventory
 
891
        if include_root is True:
 
892
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
869
893
        # Convert these into local objects to save lookup times
870
894
        pathjoin = osutils.pathjoin
871
895
        file_kind = osutils.file_kind
964
988
                # if we finished all children, pop it off the stack
965
989
                stack.pop()
966
990
 
967
 
 
968
 
    @needs_write_lock
 
991
    @needs_tree_write_lock
969
992
    def move(self, from_paths, to_name):
970
993
        """Rename files.
971
994
 
990
1013
        if not self.has_filename(to_name):
991
1014
            raise BzrError("destination %r not in working directory" % to_abs)
992
1015
        to_dir_id = inv.path2id(to_name)
993
 
        if to_dir_id == None and to_name != '':
 
1016
        if to_dir_id is None and to_name != '':
994
1017
            raise BzrError("destination %r is not a versioned directory" % to_name)
995
1018
        to_dir_ie = inv[to_dir_id]
996
1019
        if to_dir_ie.kind != 'directory':
1002
1025
            if not self.has_filename(f):
1003
1026
                raise BzrError("%r does not exist in working tree" % f)
1004
1027
            f_id = inv.path2id(f)
1005
 
            if f_id == None:
 
1028
            if f_id is None:
1006
1029
                raise BzrError("%r is not versioned" % f)
1007
1030
            name_tail = splitpath(f)[-1]
1008
1031
            dest_path = pathjoin(to_name, name_tail)
1015
1038
        # create a file in this interval and then the rename might be
1016
1039
        # left half-done.  But we should have caught most problems.
1017
1040
        orig_inv = deepcopy(self.inventory)
 
1041
        original_modified = self._inventory_is_modified
1018
1042
        try:
 
1043
            if len(from_paths):
 
1044
                self._inventory_is_modified = True
1019
1045
            for f in from_paths:
1020
1046
                name_tail = splitpath(f)[-1]
1021
1047
                dest_path = pathjoin(to_name, name_tail)
1022
1048
                result.append((f, dest_path))
1023
1049
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1024
1050
                try:
1025
 
                    rename(self.abspath(f), self.abspath(dest_path))
 
1051
                    osutils.rename(self.abspath(f), self.abspath(dest_path))
1026
1052
                except OSError, e:
1027
1053
                    raise BzrError("failed to rename %r to %r: %s" %
1028
1054
                                   (f, dest_path, e[1]),
1029
1055
                            ["rename rolled back"])
1030
1056
        except:
1031
1057
            # restore the inventory on error
1032
 
            self._set_inventory(orig_inv)
 
1058
            self._set_inventory(orig_inv, dirty=original_modified)
1033
1059
            raise
1034
1060
        self._write_inventory(inv)
1035
1061
        return result
1036
1062
 
1037
 
    @needs_write_lock
 
1063
    @needs_tree_write_lock
1038
1064
    def rename_one(self, from_rel, to_rel):
1039
1065
        """Rename one file.
1040
1066
 
1047
1073
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
1048
1074
 
1049
1075
        file_id = inv.path2id(from_rel)
1050
 
        if file_id == None:
 
1076
        if file_id is None:
1051
1077
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1052
1078
 
1053
1079
        entry = inv[file_id]
1059
1085
 
1060
1086
        to_dir, to_tail = os.path.split(to_rel)
1061
1087
        to_dir_id = inv.path2id(to_dir)
1062
 
        if to_dir_id == None and to_dir != '':
 
1088
        if to_dir_id is None and to_dir != '':
1063
1089
            raise BzrError("can't determine destination directory id for %r" % to_dir)
1064
1090
 
1065
1091
        mutter("rename_one:")
1074
1100
        from_abs = self.abspath(from_rel)
1075
1101
        to_abs = self.abspath(to_rel)
1076
1102
        try:
1077
 
            rename(from_abs, to_abs)
 
1103
            osutils.rename(from_abs, to_abs)
1078
1104
        except OSError, e:
1079
1105
            inv.rename(file_id, from_parent, from_name)
1080
1106
            raise BzrError("failed to rename %r to %r: %s"
1092
1118
        for subp in self.extras():
1093
1119
            if not self.is_ignored(subp):
1094
1120
                yield subp
1095
 
 
 
1121
    
 
1122
    @needs_tree_write_lock
 
1123
    def unversion(self, file_ids):
 
1124
        """Remove the file ids in file_ids from the current versioned set.
 
1125
 
 
1126
        When a file_id is unversioned, all of its children are automatically
 
1127
        unversioned.
 
1128
 
 
1129
        :param file_ids: The file ids to stop versioning.
 
1130
        :raises: NoSuchId if any fileid is not currently versioned.
 
1131
        """
 
1132
        for file_id in file_ids:
 
1133
            if self._inventory.has_id(file_id):
 
1134
                self._inventory.remove_recursive_id(file_id)
 
1135
            else:
 
1136
                raise errors.NoSuchId(self, file_id)
 
1137
        if len(file_ids):
 
1138
            # in the future this should just set a dirty bit to wait for the 
 
1139
            # final unlock. However, until all methods of workingtree start
 
1140
            # with the current in -memory inventory rather than triggering 
 
1141
            # a read, it is more complex - we need to teach read_inventory
 
1142
            # to know when to read, and when to not read first... and possibly
 
1143
            # to save first when the in memory one may be corrupted.
 
1144
            # so for now, we just only write it if it is indeed dirty.
 
1145
            # - RBC 20060907
 
1146
            self._write_inventory(self._inventory)
 
1147
    
1096
1148
    @deprecated_method(zero_eight)
1097
1149
    def iter_conflicts(self):
1098
1150
        """List all files in the tree that have text or content conflicts.
1131
1183
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
1132
1184
                try:
1133
1185
                    new_basis_tree = self.branch.basis_tree()
1134
 
                    merge_inner(self.branch,
 
1186
                    merge.merge_inner(
 
1187
                                self.branch,
1135
1188
                                new_basis_tree,
1136
1189
                                basis_tree,
1137
1190
                                this_tree=self,
1138
1191
                                pb=pb)
 
1192
                    if (basis_tree.inventory.root is None and
 
1193
                        new_basis_tree.inventory.root is not None):
 
1194
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1139
1195
                finally:
1140
1196
                    pb.finished()
1141
1197
                # TODO - dedup parents list with things merged by pull ?
1155
1211
            source.unlock()
1156
1212
            top_pb.finished()
1157
1213
 
 
1214
    @needs_write_lock
 
1215
    def put_file_bytes_non_atomic(self, file_id, bytes):
 
1216
        """See MutableTree.put_file_bytes_non_atomic."""
 
1217
        stream = file(self.id2abspath(file_id), 'wb')
 
1218
        try:
 
1219
            stream.write(bytes)
 
1220
        finally:
 
1221
            stream.close()
 
1222
        # TODO: update the hashcache here ?
 
1223
 
1158
1224
    def extras(self):
1159
1225
        """Yield all unknown files in this WorkingTree.
1160
1226
 
1248
1314
        """Yield list of PATH, IGNORE_PATTERN"""
1249
1315
        for subp in self.extras():
1250
1316
            pat = self.is_ignored(subp)
1251
 
            if pat != None:
 
1317
            if pat is not None:
1252
1318
                yield subp, pat
1253
1319
 
1254
1320
    def get_ignore_list(self):
1320
1386
    def kind(self, file_id):
1321
1387
        return file_kind(self.id2abspath(file_id))
1322
1388
 
1323
 
    @needs_read_lock
1324
1389
    def last_revision(self):
1325
 
        """Return the last revision id of this working tree.
1326
 
 
1327
 
        In early branch formats this was == the branch last_revision,
1328
 
        but that cannot be relied upon - for working tree operations,
1329
 
        always use tree.last_revision().
 
1390
        """Return the last revision of the branch for this tree.
 
1391
 
 
1392
        This format tree does not support a separate marker for last-revision
 
1393
        compared to the branch.
 
1394
 
 
1395
        See MutableTree.last_revision
1330
1396
        """
 
1397
        return self._last_revision()
 
1398
 
 
1399
    @needs_read_lock
 
1400
    def _last_revision(self):
 
1401
        """helper for get_parent_ids."""
1331
1402
        return self.branch.last_revision()
1332
1403
 
1333
1404
    def is_locked(self):
1342
1413
            self.branch.unlock()
1343
1414
            raise
1344
1415
 
 
1416
    def lock_tree_write(self):
 
1417
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
1418
        self.branch.lock_read()
 
1419
        try:
 
1420
            return self._control_files.lock_write()
 
1421
        except:
 
1422
            self.branch.unlock()
 
1423
            raise
 
1424
 
1345
1425
    def lock_write(self):
1346
 
        """See Branch.lock_write, and WorkingTree.unlock."""
 
1426
        """See MutableTree.lock_write, and WorkingTree.unlock."""
1347
1427
        self.branch.lock_write()
1348
1428
        try:
1349
1429
            return self._control_files.lock_write()
1355
1435
        return self._control_files.get_physical_lock_status()
1356
1436
 
1357
1437
    def _basis_inventory_name(self):
1358
 
        return 'basis-inventory'
 
1438
        return 'basis-inventory-cache'
1359
1439
 
1360
 
    @needs_write_lock
 
1440
    @needs_tree_write_lock
1361
1441
    def set_last_revision(self, new_revision):
1362
1442
        """Change the last revision in the working tree."""
1363
1443
        if self._change_last_revision(new_revision):
1379
1459
            self.branch.set_revision_history([new_revision])
1380
1460
        return True
1381
1461
 
 
1462
    def _write_basis_inventory(self, xml):
 
1463
        """Write the basis inventory XML to the basis-inventory file"""
 
1464
        assert isinstance(xml, str), 'serialised xml must be bytestring.'
 
1465
        path = self._basis_inventory_name()
 
1466
        sio = StringIO(xml)
 
1467
        self._control_files.put(path, sio)
 
1468
 
 
1469
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
 
1470
        """Create the text that will be saved in basis-inventory"""
 
1471
        inventory.revision_id = revision_id
 
1472
        return xml6.serializer_v6.write_inventory_to_string(inventory)
 
1473
 
1382
1474
    def _cache_basis_inventory(self, new_revision):
1383
1475
        """Cache new_revision as the basis inventory."""
1384
1476
        # TODO: this should allow the ready-to-use inventory to be passed in,
1396
1488
            # root node id can legitimately look like 'revision_id' but cannot
1397
1489
            # contain a '"'.
1398
1490
            xml = self.branch.repository.get_inventory_xml(new_revision)
1399
 
            if not 'revision_id="' in xml.split('\n', 1)[0]:
 
1491
            firstline = xml.split('\n', 1)[0]
 
1492
            if (not 'revision_id="' in firstline or 
 
1493
                'format="6"' not in firstline):
1400
1494
                inv = self.branch.repository.deserialise_inventory(
1401
1495
                    new_revision, xml)
1402
 
                inv.revision_id = new_revision
1403
 
                xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1404
 
            assert isinstance(xml, str), 'serialised xml must be bytestring.'
1405
 
            path = self._basis_inventory_name()
1406
 
            sio = StringIO(xml)
1407
 
            self._control_files.put(path, sio)
 
1496
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
 
1497
            self._write_basis_inventory(xml)
1408
1498
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1409
1499
            pass
1410
1500
 
1415
1505
        
1416
1506
    @needs_read_lock
1417
1507
    def read_working_inventory(self):
1418
 
        """Read the working inventory."""
 
1508
        """Read the working inventory.
 
1509
        
 
1510
        :raises errors.InventoryModified: read_working_inventory will fail
 
1511
            when the current in memory inventory has been modified.
 
1512
        """
 
1513
        # conceptually this should be an implementation detail of the tree. 
 
1514
        # XXX: Deprecate this.
1419
1515
        # ElementTree does its own conversion from UTF-8, so open in
1420
1516
        # binary.
1421
 
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1517
        if self._inventory_is_modified:
 
1518
            raise errors.InventoryModified(self)
 
1519
        result = xml5.serializer_v5.read_inventory(
1422
1520
            self._control_files.get('inventory'))
1423
 
        self._set_inventory(result)
 
1521
        self._set_inventory(result, dirty=False)
1424
1522
        return result
1425
1523
 
1426
 
    @needs_write_lock
 
1524
    @needs_tree_write_lock
1427
1525
    def remove(self, files, verbose=False, to_file=None):
1428
1526
        """Remove nominated files from the working inventory..
1429
1527
 
1458
1556
                    new_status = 'I'
1459
1557
                else:
1460
1558
                    new_status = '?'
1461
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1559
                textui.show_status(new_status, inv[fid].kind, f,
 
1560
                                   to_file=to_file)
1462
1561
            del inv[fid]
1463
1562
 
1464
1563
        self._write_inventory(inv)
1465
1564
 
1466
 
    @needs_write_lock
 
1565
    @needs_tree_write_lock
1467
1566
    def revert(self, filenames, old_tree=None, backups=True, 
1468
1567
               pb=DummyProgress()):
1469
 
        from transform import revert
1470
 
        from conflicts import resolve
 
1568
        from bzrlib.conflicts import resolve
1471
1569
        if old_tree is None:
1472
1570
            old_tree = self.basis_tree()
1473
 
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1571
        conflicts = transform.revert(self, old_tree, filenames, backups, pb)
1474
1572
        if not len(filenames):
1475
1573
            self.set_parent_ids(self.get_parent_ids()[:1])
1476
1574
            resolve(self)
1480
1578
 
1481
1579
    # XXX: This method should be deprecated in favour of taking in a proper
1482
1580
    # new Inventory object.
1483
 
    @needs_write_lock
 
1581
    @needs_tree_write_lock
1484
1582
    def set_inventory(self, new_inventory_list):
1485
1583
        from bzrlib.inventory import (Inventory,
1486
1584
                                      InventoryDirectory,
1503
1601
                raise BzrError("unknown kind %r" % kind)
1504
1602
        self._write_inventory(inv)
1505
1603
 
1506
 
    @needs_write_lock
 
1604
    @needs_tree_write_lock
1507
1605
    def set_root_id(self, file_id):
1508
1606
        """Set the root id for this tree."""
1509
 
        inv = self.read_working_inventory()
 
1607
        # for compatability 
 
1608
        if file_id is None:
 
1609
            symbol_versioning.warn(symbol_versioning.zero_twelve
 
1610
                % 'WorkingTree.set_root_id with fileid=None',
 
1611
                DeprecationWarning,
 
1612
                stacklevel=3)
 
1613
            file_id = ROOT_ID
 
1614
        inv = self._inventory
1510
1615
        orig_root_id = inv.root.file_id
 
1616
        # TODO: it might be nice to exit early if there was nothing
 
1617
        # to do, saving us from trigger a sync on unlock.
 
1618
        self._inventory_is_modified = True
 
1619
        # we preserve the root inventory entry object, but
 
1620
        # unlinkit from the byid index
1511
1621
        del inv._byid[inv.root.file_id]
1512
1622
        inv.root.file_id = file_id
 
1623
        # and link it into the index with the new changed id.
1513
1624
        inv._byid[inv.root.file_id] = inv.root
 
1625
        # and finally update all children to reference the new id.
 
1626
        # XXX: this should be safe to just look at the root.children
 
1627
        # list, not the WHOLE INVENTORY.
1514
1628
        for fid in inv:
1515
1629
            entry = inv[fid]
1516
1630
            if entry.parent_id == orig_root_id:
1517
1631
                entry.parent_id = inv.root.file_id
1518
 
        self._write_inventory(inv)
1519
1632
 
1520
1633
    def unlock(self):
1521
1634
        """See Branch.unlock.
1532
1645
    def update(self):
1533
1646
        """Update a working tree along its branch.
1534
1647
 
1535
 
        This will update the branch if its bound too, which means we have multiple trees involved:
1536
 
        The new basis tree of the master.
1537
 
        The old basis tree of the branch.
1538
 
        The old basis tree of the working tree.
1539
 
        The current working tree state.
1540
 
        pathologically all three may be different, and non ancestors of each other.
1541
 
        Conceptually we want to:
1542
 
        Preserve the wt.basis->wt.state changes
1543
 
        Transform the wt.basis to the new master basis.
1544
 
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1545
 
        Restore the wt.basis->wt.state changes.
 
1648
        This will update the branch if its bound too, which means we have
 
1649
        multiple trees involved:
 
1650
 
 
1651
        - The new basis tree of the master.
 
1652
        - The old basis tree of the branch.
 
1653
        - The old basis tree of the working tree.
 
1654
        - The current working tree state.
 
1655
 
 
1656
        Pathologically, all three may be different, and non-ancestors of each
 
1657
        other.  Conceptually we want to:
 
1658
 
 
1659
        - Preserve the wt.basis->wt.state changes
 
1660
        - Transform the wt.basis to the new master basis.
 
1661
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1662
          it into the tree.
 
1663
        - Restore the wt.basis->wt.state changes.
1546
1664
 
1547
1665
        There isn't a single operation at the moment to do that, so we:
1548
 
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
1549
 
        Do a 'normal' merge of the old branch basis if it is relevant.
 
1666
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1667
          basis.
 
1668
        - Do a 'normal' merge of the old branch basis if it is relevant.
1550
1669
        """
1551
1670
        old_tip = self.branch.update()
 
1671
 
1552
1672
        # here if old_tip is not None, it is the old tip of the branch before
1553
1673
        # it was updated from the master branch. This should become a pending
1554
1674
        # merge in the working tree to preserve the user existing work.  we
1560
1680
        # local work is unreferenced and will appear to have been lost.
1561
1681
        # 
1562
1682
        result = 0
1563
 
        if self.last_revision() != self.branch.last_revision():
 
1683
        try:
 
1684
            last_rev = self.get_parent_ids()[0]
 
1685
        except IndexError:
 
1686
            last_rev = None
 
1687
        if last_rev != self.branch.last_revision():
1564
1688
            # merge tree state up to new branch tip.
1565
1689
            basis = self.basis_tree()
1566
1690
            to_tree = self.branch.basis_tree()
1567
 
            result += merge_inner(self.branch,
 
1691
            if basis.inventory.root is None:
 
1692
                self.set_root_id(to_tree.inventory.root.file_id)
 
1693
            result += merge.merge_inner(
 
1694
                                  self.branch,
1568
1695
                                  to_tree,
1569
1696
                                  basis,
1570
1697
                                  this_tree=self)
1584
1711
                parent_trees.append(
1585
1712
                    (old_tip, self.branch.repository.revision_tree(old_tip)))
1586
1713
            self.set_parent_trees(parent_trees)
 
1714
            last_rev = parent_trees[0][0]
1587
1715
        else:
1588
1716
            # the working tree had the same last-revision as the master
1589
1717
            # branch did. We may still have pivot local work from the local
1590
1718
            # branch into old_tip:
1591
1719
            if old_tip is not None:
1592
1720
                self.add_parent_tree_id(old_tip)
1593
 
        if old_tip and old_tip != self.last_revision():
 
1721
        if old_tip and old_tip != last_rev:
1594
1722
            # our last revision was not the prior branch last revision
1595
1723
            # and we have converted that last revision to a pending merge.
1596
1724
            # base is somewhere between the branch tip now
1604
1732
                base_rev_id = None
1605
1733
            base_tree = self.branch.repository.revision_tree(base_rev_id)
1606
1734
            other_tree = self.branch.repository.revision_tree(old_tip)
1607
 
            result += merge_inner(self.branch,
 
1735
            result += merge.merge_inner(
 
1736
                                  self.branch,
1608
1737
                                  other_tree,
1609
1738
                                  base_tree,
1610
1739
                                  this_tree=self)
1611
1740
        return result
1612
1741
 
1613
 
    @needs_write_lock
 
1742
    @needs_tree_write_lock
1614
1743
    def _write_inventory(self, inv):
1615
1744
        """Write inventory as the current inventory."""
1616
 
        sio = StringIO()
1617
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1618
 
        sio.seek(0)
1619
 
        self._control_files.put('inventory', sio)
1620
 
        self._set_inventory(inv)
1621
 
        mutter('wrote working inventory')
 
1745
        self._set_inventory(inv, dirty=True)
 
1746
        self.flush()
1622
1747
 
1623
1748
    def set_conflicts(self, arg):
1624
1749
        raise UnsupportedOperation(self.set_conflicts, self)
1628
1753
 
1629
1754
    @needs_read_lock
1630
1755
    def conflicts(self):
1631
 
        conflicts = ConflictList()
 
1756
        conflicts = _mod_conflicts.ConflictList()
1632
1757
        for conflicted in self._iter_conflicts():
1633
1758
            text = True
1634
1759
            try:
1647
1772
                    if text == False:
1648
1773
                        break
1649
1774
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1650
 
            conflicts.append(Conflict.factory(ctype, path=conflicted,
 
1775
            conflicts.append(_mod_conflicts.Conflict.factory(ctype,
 
1776
                             path=conflicted,
1651
1777
                             file_id=self.path2id(conflicted)))
1652
1778
        return conflicts
1653
1779
 
1660
1786
     - uses the branch last-revision.
1661
1787
    """
1662
1788
 
 
1789
    def lock_tree_write(self):
 
1790
        """See WorkingTree.lock_tree_write().
 
1791
 
 
1792
        In Format2 WorkingTrees we have a single lock for the branch and tree
 
1793
        so lock_tree_write() degrades to lock_write().
 
1794
        """
 
1795
        self.branch.lock_write()
 
1796
        try:
 
1797
            return self._control_files.lock_write()
 
1798
        except:
 
1799
            self.branch.unlock()
 
1800
            raise
 
1801
 
1663
1802
    def unlock(self):
1664
1803
        # we share control files:
1665
 
        if self._hashcache.needs_write and self._control_files._lock_count==3:
1666
 
            self._hashcache.write()
 
1804
        if self._control_files._lock_count == 3:
 
1805
            # _inventory_is_modified is always False during a read lock.
 
1806
            if self._inventory_is_modified:
 
1807
                self.flush()
 
1808
            if self._hashcache.needs_write:
 
1809
                self._hashcache.write()
1667
1810
        # reverse order of locking.
1668
1811
        try:
1669
1812
            return self._control_files.unlock()
1682
1825
    """
1683
1826
 
1684
1827
    @needs_read_lock
1685
 
    def last_revision(self):
1686
 
        """See WorkingTree.last_revision."""
 
1828
    def _last_revision(self):
 
1829
        """See Mutable.last_revision."""
1687
1830
        try:
1688
1831
            return self._control_files.get_utf8('last-revision').read()
1689
1832
        except NoSuchFile:
1701
1844
            self._control_files.put_utf8('last-revision', revision_id)
1702
1845
            return True
1703
1846
 
1704
 
    @needs_write_lock
 
1847
    @needs_tree_write_lock
1705
1848
    def set_conflicts(self, conflicts):
1706
1849
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1707
1850
                      CONFLICT_HEADER_1)
1708
1851
 
1709
 
    @needs_write_lock
 
1852
    @needs_tree_write_lock
1710
1853
    def add_conflicts(self, new_conflicts):
1711
1854
        conflict_set = set(self.conflicts())
1712
1855
        conflict_set.update(set(list(new_conflicts)))
1713
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1714
 
                                               key=Conflict.sort_key)))
 
1856
        self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
 
1857
                                       key=_mod_conflicts.Conflict.sort_key)))
1715
1858
 
1716
1859
    @needs_read_lock
1717
1860
    def conflicts(self):
1718
1861
        try:
1719
1862
            confile = self._control_files.get('conflicts')
1720
1863
        except NoSuchFile:
1721
 
            return ConflictList()
 
1864
            return _mod_conflicts.ConflictList()
1722
1865
        try:
1723
1866
            if confile.next() != CONFLICT_HEADER_1 + '\n':
1724
1867
                raise ConflictFormatError()
1725
1868
        except StopIteration:
1726
1869
            raise ConflictFormatError()
1727
 
        return ConflictList.from_stanzas(RioReader(confile))
 
1870
        return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
1728
1871
 
1729
1872
    def unlock(self):
1730
 
        if self._hashcache.needs_write and self._control_files._lock_count==1:
1731
 
            self._hashcache.write()
 
1873
        if self._control_files._lock_count == 1:
 
1874
            # _inventory_is_modified is always False during a read lock.
 
1875
            if self._inventory_is_modified:
 
1876
                self.flush()
 
1877
            if self._hashcache.needs_write:
 
1878
                self._hashcache.write()
1732
1879
        # reverse order of locking.
1733
1880
        try:
1734
1881
            return self._control_files.unlock()
1737
1884
 
1738
1885
 
1739
1886
def get_conflicted_stem(path):
1740
 
    for suffix in CONFLICT_SUFFIXES:
 
1887
    for suffix in _mod_conflicts.CONFLICT_SUFFIXES:
1741
1888
        if path.endswith(suffix):
1742
1889
            return path[:-len(suffix)]
1743
1890
 
1849
1996
        """
1850
1997
        sio = StringIO()
1851
1998
        inv = Inventory()
1852
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1999
        xml5.serializer_v5.write_inventory(inv, sio)
1853
2000
        sio.seek(0)
1854
2001
        control_files.put('inventory', sio)
1855
2002
 
1880
2027
                         _internal=True,
1881
2028
                         _format=self,
1882
2029
                         _bzrdir=a_bzrdir)
1883
 
        wt._write_inventory(inv)
1884
 
        wt.set_root_id(inv.root.file_id)
1885
2030
        basis_tree = branch.repository.revision_tree(revision)
 
2031
        if basis_tree.inventory.root is not None:
 
2032
            wt.set_root_id(basis_tree.inventory.root.file_id)
 
2033
        # set the parent list and cache the basis tree.
1886
2034
        wt.set_parent_trees([(revision, basis_tree)])
1887
 
        build_tree(basis_tree, wt)
 
2035
        transform.build_tree(basis_tree, wt)
1888
2036
        return wt
1889
2037
 
1890
2038
    def __init__(self):
1952
2100
        branch = a_bzrdir.open_branch()
1953
2101
        if revision_id is None:
1954
2102
            revision_id = branch.last_revision()
1955
 
        inv = Inventory() 
 
2103
        inv = Inventory(root_id=gen_root_id())
1956
2104
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1957
2105
                         branch,
1958
2106
                         inv,
1960
2108
                         _format=self,
1961
2109
                         _bzrdir=a_bzrdir,
1962
2110
                         _control_files=control_files)
1963
 
        wt.lock_write()
 
2111
        wt.lock_tree_write()
1964
2112
        try:
1965
 
            wt._write_inventory(inv)
1966
 
            wt.set_root_id(inv.root.file_id)
1967
2113
            basis_tree = branch.repository.revision_tree(revision_id)
1968
 
            if revision_id == bzrlib.revision.NULL_REVISION:
 
2114
            # only set an explicit root id if there is one to set.
 
2115
            if basis_tree.inventory.root is not None:
 
2116
                wt.set_root_id(basis_tree.inventory.root.file_id)
 
2117
            if revision_id == NULL_REVISION:
1969
2118
                wt.set_parent_trees([])
1970
2119
            else:
1971
2120
                wt.set_parent_trees([(revision_id, basis_tree)])
1972
 
            build_tree(basis_tree, wt)
 
2121
            transform.build_tree(basis_tree, wt)
1973
2122
        finally:
 
2123
            # Unlock in this order so that the unlock-triggers-flush in
 
2124
            # WorkingTree is given a chance to fire.
 
2125
            control_files.unlock()
1974
2126
            wt.unlock()
1975
 
            control_files.unlock()
1976
2127
        return wt
1977
2128
 
1978
2129
    def __init__(self):