~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

[merge] bzr.dev 2255

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
 
 
42
from bzrlib.lazy_import import lazy_import
 
43
lazy_import(globals(), """
43
44
from bisect import bisect_left
44
45
import collections
45
46
from copy import deepcopy
46
 
from cStringIO import StringIO
47
47
import errno
48
 
import fnmatch
49
48
import itertools
50
49
import operator
51
 
import os
52
 
import re
53
50
import stat
54
51
from time import time
55
52
import warnings
57
54
import bzrlib
58
55
from bzrlib import (
59
56
    bzrdir,
 
57
    conflicts as _mod_conflicts,
60
58
    errors,
 
59
    generate_ids,
 
60
    globbing,
 
61
    hashcache,
61
62
    ignores,
 
63
    merge,
62
64
    osutils,
63
 
    symbol_versioning,
 
65
    revisiontree,
 
66
    textui,
 
67
    transform,
64
68
    urlutils,
 
69
    xml5,
 
70
    xml6,
65
71
    )
66
 
from bzrlib.atomicfile import AtomicFile
67
72
import bzrlib.branch
68
 
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
73
from bzrlib.transport import get_transport
 
74
import bzrlib.ui
 
75
""")
 
76
 
 
77
from bzrlib import symbol_versioning
69
78
from bzrlib.decorators import needs_read_lock, needs_write_lock
70
 
from bzrlib.errors import (BzrCheckError,
71
 
                           BzrError,
72
 
                           ConflictFormatError,
73
 
                           WeaveRevisionNotPresent,
74
 
                           NotBranchError,
75
 
                           NoSuchFile,
76
 
                           NotVersionedError,
77
 
                           MergeModifiedFormatError,
78
 
                           UnsupportedOperation,
79
 
                           )
80
79
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
81
80
from bzrlib.lockable_files import LockableFiles, TransportLock
82
81
from bzrlib.lockdir import LockDir
83
 
from bzrlib.merge import merge_inner, transform_tree
84
82
import bzrlib.mutabletree
85
83
from bzrlib.mutabletree import needs_tree_write_lock
86
84
from bzrlib.osutils import (
87
 
                            abspath,
88
 
                            compact_date,
89
 
                            file_kind,
90
 
                            isdir,
91
 
                            getcwd,
92
 
                            pathjoin,
93
 
                            pumpfile,
94
 
                            safe_unicode,
95
 
                            splitpath,
96
 
                            rand_chars,
97
 
                            normpath,
98
 
                            realpath,
99
 
                            relpath,
100
 
                            rename,
101
 
                            supports_executable,
102
 
                            )
 
85
    compact_date,
 
86
    file_kind,
 
87
    isdir,
 
88
    normpath,
 
89
    pathjoin,
 
90
    rand_chars,
 
91
    realpath,
 
92
    safe_unicode,
 
93
    splitpath,
 
94
    supports_executable,
 
95
    )
 
96
from bzrlib.trace import mutter, note
 
97
from bzrlib.transport.local import LocalTransport
103
98
from bzrlib.progress import DummyProgress, ProgressPhase
104
 
from bzrlib.revision import NULL_REVISION
105
 
import bzrlib.revisiontree
 
99
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
106
100
from bzrlib.rio import RioReader, rio_file, Stanza
107
101
from bzrlib.symbol_versioning import (deprecated_passed,
108
102
        deprecated_method,
110
104
        DEPRECATED_PARAMETER,
111
105
        zero_eight,
112
106
        zero_eleven,
 
107
        zero_thirteen,
113
108
        )
114
 
from bzrlib.trace import mutter, note
115
 
from bzrlib.transform import build_tree
116
 
from bzrlib.transport import get_transport
117
 
from bzrlib.transport.local import LocalTransport
118
 
from bzrlib.textui import show_status
119
 
import bzrlib.ui
120
 
import bzrlib.xml5
121
 
 
122
 
 
123
 
# the regex removes any weird characters; we don't escape them 
124
 
# but rather just pull them out
125
 
_gen_file_id_re = re.compile(r'[^\w.]')
126
 
_gen_id_suffix = None
127
 
_gen_id_serial = 0
128
 
 
129
 
 
130
 
def _next_id_suffix():
131
 
    """Create a new file id suffix that is reasonably unique.
132
 
    
133
 
    On the first call we combine the current time with 64 bits of randomness
134
 
    to give a highly probably globally unique number. Then each call in the same
135
 
    process adds 1 to a serial number we append to that unique value.
136
 
    """
137
 
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
138
 
    # than having to move the id randomness out of the inner loop like this.
139
 
    # XXX TODO: for the global randomness this uses we should add the thread-id
140
 
    # before the serial #.
141
 
    global _gen_id_suffix, _gen_id_serial
142
 
    if _gen_id_suffix is None:
143
 
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
144
 
    _gen_id_serial += 1
145
 
    return _gen_id_suffix + str(_gen_id_serial)
146
 
 
147
 
 
 
109
 
 
110
 
 
111
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
112
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
113
 
 
114
 
 
115
@deprecated_function(zero_thirteen)
148
116
def gen_file_id(name):
149
117
    """Return new file id for the basename 'name'.
150
118
 
151
 
    The uniqueness is supplied from _next_id_suffix.
 
119
    Use bzrlib.generate_ids.gen_file_id() instead
152
120
    """
153
 
    # The real randomness is in the _next_id_suffix, the
154
 
    # rest of the identifier is just to be nice.
155
 
    # So we:
156
 
    # 1) Remove non-ascii word characters to keep the ids portable
157
 
    # 2) squash to lowercase, so the file id doesn't have to
158
 
    #    be escaped (case insensitive filesystems would bork for ids
159
 
    #    that only differred in case without escaping).
160
 
    # 3) truncate the filename to 20 chars. Long filenames also bork on some
161
 
    #    filesystems
162
 
    # 4) Removing starting '.' characters to prevent the file ids from
163
 
    #    being considered hidden.
164
 
    ascii_word_only = _gen_file_id_re.sub('', name.lower())
165
 
    short_no_dots = ascii_word_only.lstrip('.')[:20]
166
 
    return short_no_dots + _next_id_suffix()
167
 
 
168
 
 
 
121
    return generate_ids.gen_file_id(name)
 
122
 
 
123
 
 
124
@deprecated_function(zero_thirteen)
169
125
def gen_root_id():
170
 
    """Return a new tree-root file id."""
171
 
    return gen_file_id('TREE_ROOT')
 
126
    """Return a new tree-root file id.
 
127
 
 
128
    This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
 
129
    """
 
130
    return generate_ids.gen_root_id()
172
131
 
173
132
 
174
133
class TreeEntry(object):
266
225
            self._set_inventory(wt._inventory, dirty=False)
267
226
            self._format = wt._format
268
227
            self.bzrdir = wt.bzrdir
269
 
        from bzrlib.hashcache import HashCache
270
 
        from bzrlib.trace import note, mutter
271
228
        assert isinstance(basedir, basestring), \
272
229
            "base directory %r is not a string" % basedir
273
230
        basedir = safe_unicode(basedir)
274
231
        mutter("opening working tree %r", basedir)
275
232
        if deprecated_passed(branch):
276
233
            if not _internal:
277
 
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
278
 
                     " Please use bzrdir.open_workingtree() or"
 
234
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated"
 
235
                     " as of bzr 0.8. Please use bzrdir.open_workingtree() or"
279
236
                     " WorkingTree.open().",
280
237
                     DeprecationWarning,
281
238
                     stacklevel=2
300
257
        # if needed, or, when the cache sees a change, append it to the hash
301
258
        # cache file, and have the parser take the most recent entry for a
302
259
        # given path only.
303
 
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
304
 
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
 
260
        wt_trans = self.bzrdir.get_workingtree_transport(None)
 
261
        cache_filename = wt_trans.local_abspath('stat-cache')
 
262
        self._hashcache = hashcache.HashCache(basedir, cache_filename,
 
263
                                              self._control_files._file_mode)
 
264
        hc = self._hashcache
305
265
        hc.read()
306
266
        # is this scan needed ? it makes things kinda slow.
307
267
        #hc.scan()
414
374
        If the left most parent is a ghost then the returned tree will be an
415
375
        empty tree - one obtained by calling repository.revision_tree(None).
416
376
        """
417
 
        revision_id = self.last_revision()
418
 
        if revision_id is None:
 
377
        try:
 
378
            revision_id = self.get_parent_ids()[0]
 
379
        except IndexError:
419
380
            # no parents, return an empty revision tree.
420
381
            # in the future this should return the tree for
421
382
            # 'empty:' - the implicit root empty tree.
482
443
        The path may be absolute or relative. If its a relative path it is 
483
444
        interpreted relative to the python current working directory.
484
445
        """
485
 
        return relpath(self.basedir, path)
 
446
        return osutils.relpath(self.basedir, path)
486
447
 
487
448
    def has_filename(self, filename):
488
449
        return osutils.lexists(self.abspath(filename))
496
457
    def get_file_byname(self, filename):
497
458
        return file(self.abspath(filename), 'rb')
498
459
 
 
460
    def annotate_iter(self, file_id):
 
461
        """See Tree.annotate_iter
 
462
 
 
463
        This implementation will use the basis tree implementation if possible.
 
464
        Lines not in the basis are attributed to CURRENT_REVISION
 
465
 
 
466
        If there are pending merges, lines added by those merges will be
 
467
        incorrectly attributed to CURRENT_REVISION (but after committing, the
 
468
        attribution will be correct).
 
469
        """
 
470
        basis = self.basis_tree()
 
471
        changes = self._iter_changes(basis, True, [file_id]).next()
 
472
        changed_content, kind = changes[2], changes[6]
 
473
        if not changed_content:
 
474
            return basis.annotate_iter(file_id)
 
475
        if kind[1] is None:
 
476
            return None
 
477
        import annotate
 
478
        if kind[0] != 'file':
 
479
            old_lines = []
 
480
        else:
 
481
            old_lines = list(basis.annotate_iter(file_id))
 
482
        old = [old_lines]
 
483
        for tree in self.branch.repository.revision_trees(
 
484
            self.get_parent_ids()[1:]):
 
485
            if file_id not in tree:
 
486
                continue
 
487
            old.append(list(tree.annotate_iter(file_id)))
 
488
        return annotate.reannotate(old, self.get_file(file_id).readlines(),
 
489
                                   CURRENT_REVISION)
 
490
 
499
491
    def get_parent_ids(self):
500
492
        """See Tree.get_parent_ids.
501
493
        
509
501
            parents = [last_rev]
510
502
        try:
511
503
            merges_file = self._control_files.get_utf8('pending-merges')
512
 
        except NoSuchFile:
 
504
        except errors.NoSuchFile:
513
505
            pass
514
506
        else:
515
507
            for l in merges_file.readlines():
551
543
    @needs_read_lock
552
544
    def copy_content_into(self, tree, revision_id=None):
553
545
        """Copy the current content and user files of this tree into tree."""
 
546
        tree.set_root_id(self.get_root_id())
554
547
        if revision_id is None:
555
 
            transform_tree(tree, self)
 
548
            merge.transform_tree(tree, self)
556
549
        else:
557
550
            # TODO now merge from tree.last_revision to revision (to preserve
558
551
            # user local changes)
559
 
            transform_tree(tree, self)
 
552
            merge.transform_tree(tree, self)
560
553
            tree.set_parent_ids([revision_id])
561
554
 
562
555
    def id2abspath(self, file_id):
581
574
        return os.path.getsize(self.id2abspath(file_id))
582
575
 
583
576
    @needs_read_lock
584
 
    def get_file_sha1(self, file_id, path=None):
 
577
    def get_file_sha1(self, file_id, path=None, stat_value=None):
585
578
        if not path:
586
579
            path = self._inventory.id2path(file_id)
587
 
        return self._hashcache.get_sha1(path)
 
580
        return self._hashcache.get_sha1(path, stat_value)
588
581
 
589
582
    def get_file_mtime(self, file_id, path=None):
590
583
        if not path:
627
620
                    kinds[pos] = file_kind(fullpath)
628
621
                except OSError, e:
629
622
                    if e.errno == errno.ENOENT:
630
 
                        raise NoSuchFile(fullpath)
 
623
                        raise errors.NoSuchFile(fullpath)
631
624
 
632
625
    @needs_write_lock
633
626
    def add_parent_tree_id(self, revision_id, allow_leftmost_as_ghost=False):
642
635
        :param allow_leftmost_as_ghost: Allow the first parent to be a ghost.
643
636
        """
644
637
        parents = self.get_parent_ids() + [revision_id]
645
 
        self.set_parent_ids(parents,
646
 
            allow_leftmost_as_ghost=len(parents) > 1 or allow_leftmost_as_ghost)
 
638
        self.set_parent_ids(parents, allow_leftmost_as_ghost=len(parents) > 1
 
639
            or allow_leftmost_as_ghost)
647
640
 
648
641
    @needs_tree_write_lock
649
642
    def add_parent_tree(self, parent_tuple, allow_leftmost_as_ghost=False):
783
776
        """Merge from a branch into this working tree.
784
777
 
785
778
        :param branch: The branch to merge from.
786
 
        :param to_revision: If non-None, the merge will merge to to_revision, but 
787
 
            not beyond it. to_revision does not need to be in the history of
788
 
            the branch when it is supplied. If None, to_revision defaults to
 
779
        :param to_revision: If non-None, the merge will merge to to_revision,
 
780
            but not beyond it. to_revision does not need to be in the history
 
781
            of the branch when it is supplied. If None, to_revision defaults to
789
782
            branch.last_revision().
790
783
        """
791
784
        from bzrlib.merge import Merger, Merge3Merger
825
818
    def merge_modified(self):
826
819
        try:
827
820
            hashfile = self._control_files.get('merge-hashes')
828
 
        except NoSuchFile:
 
821
        except errors.NoSuchFile:
829
822
            return {}
830
823
        merge_hashes = {}
831
824
        try:
832
825
            if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
833
 
                raise MergeModifiedFormatError()
 
826
                raise errors.MergeModifiedFormatError()
834
827
        except StopIteration:
835
 
            raise MergeModifiedFormatError()
 
828
            raise errors.MergeModifiedFormatError()
836
829
        for s in RioReader(hashfile):
837
830
            file_id = s.get("file_id")
838
831
            if file_id not in self.inventory:
846
839
    def mkdir(self, path, file_id=None):
847
840
        """See MutableTree.mkdir()."""
848
841
        if file_id is None:
849
 
            file_id = gen_file_id(os.path.basename(path))
 
842
            file_id = generate_ids.gen_file_id(os.path.basename(path))
850
843
        os.mkdir(self.abspath(path))
851
844
        self.add(path, file_id, 'directory')
852
845
        return file_id
868
861
        if self._control_files._lock_mode != 'w':
869
862
            raise errors.NotWriteLocked(self)
870
863
        sio = StringIO()
871
 
        bzrlib.xml5.serializer_v5.write_inventory(self._inventory, sio)
 
864
        xml5.serializer_v5.write_inventory(self._inventory, sio)
872
865
        sio.seek(0)
873
866
        self._control_files.put('inventory', sio)
874
867
        self._inventory_is_modified = False
956
949
 
957
950
                if f_ie:
958
951
                    if f_ie.kind != fk:
959
 
                        raise BzrCheckError("file %r entered as kind %r id %r, "
960
 
                                            "now of kind %r"
961
 
                                            % (fap, f_ie.kind, f_ie.file_id, fk))
 
952
                        raise errors.BzrCheckError(
 
953
                            "file %r entered as kind %r id %r, now of kind %r"
 
954
                            % (fap, f_ie.kind, f_ie.file_id, fk))
962
955
 
963
956
                # make a last minute entry
964
957
                if f_ie:
978
971
                new_children.sort()
979
972
                new_children = collections.deque(new_children)
980
973
                stack.append((f_ie.file_id, fp, fap, new_children))
981
 
                # Break out of inner loop, so that we start outer loop with child
 
974
                # Break out of inner loop,
 
975
                # so that we start outer loop with child
982
976
                break
983
977
            else:
984
978
                # if we finished all children, pop it off the stack
985
979
                stack.pop()
986
980
 
987
981
    @needs_tree_write_lock
988
 
    def move(self, from_paths, to_name):
 
982
    def move(self, from_paths, to_dir=None, after=False, **kwargs):
989
983
        """Rename files.
990
984
 
991
 
        to_name must exist in the inventory.
 
985
        to_dir must exist in the inventory.
992
986
 
993
 
        If to_name exists and is a directory, the files are moved into
 
987
        If to_dir exists and is a directory, the files are moved into
994
988
        it, keeping their old names.  
995
989
 
996
 
        Note that to_name is only the last component of the new name;
 
990
        Note that to_dir is only the last component of the new name;
997
991
        this doesn't change the directory.
998
992
 
 
993
        For each entry in from_paths the move mode will be determined
 
994
        independently.
 
995
 
 
996
        The first mode moves the file in the filesystem and updates the
 
997
        inventory. The second mode only updates the inventory without
 
998
        touching the file on the filesystem. This is the new mode introduced
 
999
        in version 0.15.
 
1000
 
 
1001
        move uses the second mode if 'after == True' and the target is not
 
1002
        versioned but present in the working tree.
 
1003
 
 
1004
        move uses the second mode if 'after == False' and the source is
 
1005
        versioned but no longer in the working tree, and the target is not
 
1006
        versioned but present in the working tree.
 
1007
 
 
1008
        move uses the first mode if 'after == False' and the source is
 
1009
        versioned and present in the working tree, and the target is not
 
1010
        versioned and not present in the working tree.
 
1011
 
 
1012
        Everything else results in an error.
 
1013
 
999
1014
        This returns a list of (from_path, to_path) pairs for each
1000
1015
        entry that is moved.
1001
1016
        """
1002
 
        result = []
1003
 
        ## TODO: Option to move IDs only
 
1017
        rename_entries = []
 
1018
        rename_tuples = []
 
1019
 
 
1020
        # check for deprecated use of signature
 
1021
        if to_dir is None:
 
1022
            to_dir = kwargs.get('to_name', None)
 
1023
            if to_dir is None:
 
1024
                raise TypeError('You must supply a target directory')
 
1025
            else:
 
1026
                symbol_versioning.warn('The parameter to_name was deprecated'
 
1027
                                       ' in version 0.13. Use to_dir instead',
 
1028
                                       DeprecationWarning)
 
1029
 
 
1030
        # check destination directory
1004
1031
        assert not isinstance(from_paths, basestring)
1005
1032
        inv = self.inventory
1006
 
        to_abs = self.abspath(to_name)
 
1033
        to_abs = self.abspath(to_dir)
1007
1034
        if not isdir(to_abs):
1008
 
            raise BzrError("destination %r is not a directory" % to_abs)
1009
 
        if not self.has_filename(to_name):
1010
 
            raise BzrError("destination %r not in working directory" % to_abs)
1011
 
        to_dir_id = inv.path2id(to_name)
1012
 
        if to_dir_id is None and to_name != '':
1013
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
1035
            raise errors.BzrMoveFailedError('',to_dir,
 
1036
                errors.NotADirectory(to_abs))
 
1037
        if not self.has_filename(to_dir):
 
1038
            raise errors.BzrMoveFailedError('',to_dir,
 
1039
                errors.NotInWorkingDirectory(to_dir))
 
1040
        to_dir_id = inv.path2id(to_dir)
 
1041
        if to_dir_id is None:
 
1042
            raise errors.BzrMoveFailedError('',to_dir,
 
1043
                errors.NotVersionedError(path=str(to_dir)))
 
1044
 
1014
1045
        to_dir_ie = inv[to_dir_id]
1015
1046
        if to_dir_ie.kind != 'directory':
1016
 
            raise BzrError("destination %r is not a directory" % to_abs)
1017
 
 
1018
 
        to_idpath = inv.get_idpath(to_dir_id)
1019
 
 
1020
 
        for f in from_paths:
1021
 
            if not self.has_filename(f):
1022
 
                raise BzrError("%r does not exist in working tree" % f)
1023
 
            f_id = inv.path2id(f)
1024
 
            if f_id is None:
1025
 
                raise BzrError("%r is not versioned" % f)
1026
 
            name_tail = splitpath(f)[-1]
1027
 
            dest_path = pathjoin(to_name, name_tail)
1028
 
            if self.has_filename(dest_path):
1029
 
                raise BzrError("destination %r already exists" % dest_path)
1030
 
            if f_id in to_idpath:
1031
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
1032
 
 
1033
 
        # OK, so there's a race here, it's possible that someone will
1034
 
        # create a file in this interval and then the rename might be
1035
 
        # left half-done.  But we should have caught most problems.
1036
 
        orig_inv = deepcopy(self.inventory)
 
1047
            raise errors.BzrMoveFailedError('',to_dir,
 
1048
                errors.NotADirectory(to_abs))
 
1049
 
 
1050
        # create rename entries and tuples
 
1051
        for from_rel in from_paths:
 
1052
            from_tail = splitpath(from_rel)[-1]
 
1053
            from_id = inv.path2id(from_rel)
 
1054
            if from_id is None:
 
1055
                raise errors.BzrMoveFailedError(from_rel,to_dir,
 
1056
                    errors.NotVersionedError(path=str(from_rel)))
 
1057
 
 
1058
            from_entry = inv[from_id]
 
1059
            from_parent_id = from_entry.parent_id
 
1060
            to_rel = pathjoin(to_dir, from_tail)
 
1061
            rename_entry = WorkingTree._RenameEntry(from_rel=from_rel,
 
1062
                                         from_id=from_id,
 
1063
                                         from_tail=from_tail,
 
1064
                                         from_parent_id=from_parent_id,
 
1065
                                         to_rel=to_rel, to_tail=from_tail,
 
1066
                                         to_parent_id=to_dir_id)
 
1067
            rename_entries.append(rename_entry)
 
1068
            rename_tuples.append((from_rel, to_rel))
 
1069
 
 
1070
        # determine which move mode to use. checks also for movability
 
1071
        rename_entries = self._determine_mv_mode(rename_entries, after)
 
1072
 
1037
1073
        original_modified = self._inventory_is_modified
1038
1074
        try:
1039
1075
            if len(from_paths):
1040
1076
                self._inventory_is_modified = True
1041
 
            for f in from_paths:
1042
 
                name_tail = splitpath(f)[-1]
1043
 
                dest_path = pathjoin(to_name, name_tail)
1044
 
                result.append((f, dest_path))
1045
 
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1046
 
                try:
1047
 
                    rename(self.abspath(f), self.abspath(dest_path))
1048
 
                except OSError, e:
1049
 
                    raise BzrError("failed to rename %r to %r: %s" %
1050
 
                                   (f, dest_path, e[1]),
1051
 
                            ["rename rolled back"])
 
1077
            self._move(rename_entries)
1052
1078
        except:
1053
1079
            # restore the inventory on error
1054
 
            self._set_inventory(orig_inv, dirty=original_modified)
 
1080
            self._inventory_is_modified = original_modified
1055
1081
            raise
1056
1082
        self._write_inventory(inv)
1057
 
        return result
 
1083
        return rename_tuples
 
1084
 
 
1085
    def _determine_mv_mode(self, rename_entries, after=False):
 
1086
        """Determines for each from-to pair if both inventory and working tree
 
1087
        or only the inventory has to be changed.
 
1088
 
 
1089
        Also does basic plausability tests.
 
1090
        """
 
1091
        inv = self.inventory
 
1092
 
 
1093
        for rename_entry in rename_entries:
 
1094
            # store to local variables for easier reference
 
1095
            from_rel = rename_entry.from_rel
 
1096
            from_id = rename_entry.from_id
 
1097
            to_rel = rename_entry.to_rel
 
1098
            to_id = inv.path2id(to_rel)
 
1099
            only_change_inv = False
 
1100
 
 
1101
            # check the inventory for source and destination
 
1102
            if from_id is None:
 
1103
                raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1104
                    errors.NotVersionedError(path=str(from_rel)))
 
1105
            if to_id is not None:
 
1106
                raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1107
                    errors.AlreadyVersionedError(path=str(to_rel)))
 
1108
 
 
1109
            # try to determine the mode for rename (only change inv or change
 
1110
            # inv and file system)
 
1111
            if after:
 
1112
                if not self.has_filename(to_rel):
 
1113
                    raise errors.BzrMoveFailedError(from_id,to_rel,
 
1114
                        errors.NoSuchFile(path=str(to_rel),
 
1115
                        extra="New file has not been created yet"))
 
1116
                only_change_inv = True
 
1117
            elif not self.has_filename(from_rel) and self.has_filename(to_rel):
 
1118
                only_change_inv = True
 
1119
            elif self.has_filename(from_rel) and not self.has_filename(to_rel):
 
1120
                only_change_inv = False
 
1121
            else:
 
1122
                # something is wrong, so lets determine what exactly
 
1123
                if not self.has_filename(from_rel) and \
 
1124
                   not self.has_filename(to_rel):
 
1125
                    raise errors.BzrRenameFailedError(from_rel,to_rel,
 
1126
                        errors.PathsDoNotExist(paths=(str(from_rel),
 
1127
                        str(to_rel))))
 
1128
                else:
 
1129
                    raise errors.RenameFailedFilesExist(from_rel, to_rel,
 
1130
                        extra="(Use --after to update the Bazaar id)")
 
1131
            rename_entry.only_change_inv = only_change_inv
 
1132
        return rename_entries
 
1133
 
 
1134
    def _move(self, rename_entries):
 
1135
        """Moves a list of files.
 
1136
 
 
1137
        Depending on the value of the flag 'only_change_inv', the
 
1138
        file will be moved on the file system or not.
 
1139
        """
 
1140
        inv = self.inventory
 
1141
        moved = []
 
1142
 
 
1143
        for entry in rename_entries:
 
1144
            try:
 
1145
                self._move_entry(entry)
 
1146
            except:
 
1147
                self._rollback_move(moved)
 
1148
                raise
 
1149
            moved.append(entry)
 
1150
 
 
1151
    def _rollback_move(self, moved):
 
1152
        """Try to rollback a previous move in case of an filesystem error."""
 
1153
        inv = self.inventory
 
1154
        for entry in moved:
 
1155
            try:
 
1156
                self._move_entry(_RenameEntry(entry.to_rel, entry.from_id,
 
1157
                    entry.to_tail, entry.to_parent_id, entry.from_rel,
 
1158
                    entry.from_tail, entry.from_parent_id,
 
1159
                    entry.only_change_inv))
 
1160
            except errors.BzrMoveFailedError, e:
 
1161
                raise errors.BzrMoveFailedError( '', '', "Rollback failed."
 
1162
                        " The working tree is in an inconsistent state."
 
1163
                        " Please consider doing a 'bzr revert'."
 
1164
                        " Error message is: %s" % e)
 
1165
 
 
1166
    def _move_entry(self, entry):
 
1167
        inv = self.inventory
 
1168
        from_rel_abs = self.abspath(entry.from_rel)
 
1169
        to_rel_abs = self.abspath(entry.to_rel)
 
1170
        if from_rel_abs == to_rel_abs:
 
1171
            raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
 
1172
                "Source and target are identical.")
 
1173
 
 
1174
        if not entry.only_change_inv:
 
1175
            try:
 
1176
                osutils.rename(from_rel_abs, to_rel_abs)
 
1177
            except OSError, e:
 
1178
                raise errors.BzrMoveFailedError(entry.from_rel,
 
1179
                    entry.to_rel, e[1])
 
1180
        inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1058
1181
 
1059
1182
    @needs_tree_write_lock
1060
 
    def rename_one(self, from_rel, to_rel):
 
1183
    def rename_one(self, from_rel, to_rel, after=False):
1061
1184
        """Rename one file.
1062
1185
 
1063
1186
        This can change the directory or the filename or both.
 
1187
 
 
1188
        rename_one has several 'modes' to work. First, it can rename a physical
 
1189
        file and change the file_id. That is the normal mode. Second, it can
 
1190
        only change the file_id without touching any physical file. This is
 
1191
        the new mode introduced in version 0.15.
 
1192
 
 
1193
        rename_one uses the second mode if 'after == True' and 'to_rel' is not
 
1194
        versioned but present in the working tree.
 
1195
 
 
1196
        rename_one uses the second mode if 'after == False' and 'from_rel' is
 
1197
        versioned but no longer in the working tree, and 'to_rel' is not
 
1198
        versioned but present in the working tree.
 
1199
 
 
1200
        rename_one uses the first mode if 'after == False' and 'from_rel' is
 
1201
        versioned and present in the working tree, and 'to_rel' is not
 
1202
        versioned and not present in the working tree.
 
1203
 
 
1204
        Everything else results in an error.
1064
1205
        """
1065
1206
        inv = self.inventory
1066
 
        if not self.has_filename(from_rel):
1067
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1068
 
        if self.has_filename(to_rel):
1069
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
1070
 
 
1071
 
        file_id = inv.path2id(from_rel)
1072
 
        if file_id is None:
1073
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1074
 
 
1075
 
        entry = inv[file_id]
1076
 
        from_parent = entry.parent_id
1077
 
        from_name = entry.name
1078
 
        
1079
 
        if inv.path2id(to_rel):
1080
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1081
 
 
 
1207
        rename_entries = []
 
1208
 
 
1209
        # create rename entries and tuples
 
1210
        from_tail = splitpath(from_rel)[-1]
 
1211
        from_id = inv.path2id(from_rel)
 
1212
        if from_id is None:
 
1213
            raise errors.BzrRenameFailedError(from_rel,to_rel,
 
1214
                errors.NotVersionedError(path=str(from_rel)))
 
1215
        from_entry = inv[from_id]
 
1216
        from_parent_id = from_entry.parent_id
1082
1217
        to_dir, to_tail = os.path.split(to_rel)
1083
1218
        to_dir_id = inv.path2id(to_dir)
1084
 
        if to_dir_id is None and to_dir != '':
1085
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
1086
 
 
1087
 
        mutter("rename_one:")
1088
 
        mutter("  file_id    {%s}" % file_id)
1089
 
        mutter("  from_rel   %r" % from_rel)
1090
 
        mutter("  to_rel     %r" % to_rel)
1091
 
        mutter("  to_dir     %r" % to_dir)
1092
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
1093
 
 
1094
 
        inv.rename(file_id, to_dir_id, to_tail)
1095
 
 
1096
 
        from_abs = self.abspath(from_rel)
1097
 
        to_abs = self.abspath(to_rel)
1098
 
        try:
1099
 
            rename(from_abs, to_abs)
1100
 
        except OSError, e:
1101
 
            inv.rename(file_id, from_parent, from_name)
1102
 
            raise BzrError("failed to rename %r to %r: %s"
1103
 
                    % (from_abs, to_abs, e[1]),
1104
 
                    ["rename rolled back"])
 
1219
        rename_entry = WorkingTree._RenameEntry(from_rel=from_rel,
 
1220
                                     from_id=from_id,
 
1221
                                     from_tail=from_tail,
 
1222
                                     from_parent_id=from_parent_id,
 
1223
                                     to_rel=to_rel, to_tail=to_tail,
 
1224
                                     to_parent_id=to_dir_id)
 
1225
        rename_entries.append(rename_entry)
 
1226
 
 
1227
        # determine which move mode to use. checks also for movability
 
1228
        rename_entries = self._determine_mv_mode(rename_entries, after)
 
1229
 
 
1230
        # check if the target changed directory and if the target directory is
 
1231
        # versioned
 
1232
        if to_dir_id is None:
 
1233
            raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1234
                errors.NotVersionedError(path=str(to_dir)))
 
1235
 
 
1236
        # all checks done. now we can continue with our actual work
 
1237
        mutter('rename_one:\n'
 
1238
               '  from_id   {%s}\n'
 
1239
               '  from_rel: %r\n'
 
1240
               '  to_rel:   %r\n'
 
1241
               '  to_dir    %r\n'
 
1242
               '  to_dir_id {%s}\n',
 
1243
               from_id, from_rel, to_rel, to_dir, to_dir_id)
 
1244
 
 
1245
        self._move(rename_entries)
1105
1246
        self._write_inventory(inv)
1106
1247
 
 
1248
    class _RenameEntry(object):
 
1249
        def __init__(self, from_rel, from_id, from_tail, from_parent_id,
 
1250
                     to_rel, to_tail, to_parent_id, only_change_inv=False):
 
1251
            self.from_rel = from_rel
 
1252
            self.from_id = from_id
 
1253
            self.from_tail = from_tail
 
1254
            self.from_parent_id = from_parent_id
 
1255
            self.to_rel = to_rel
 
1256
            self.to_tail = to_tail
 
1257
            self.to_parent_id = to_parent_id
 
1258
            self.only_change_inv = only_change_inv
 
1259
 
1107
1260
    @needs_read_lock
1108
1261
    def unknowns(self):
1109
1262
        """Return all unknown files.
1179
1332
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
1180
1333
                try:
1181
1334
                    new_basis_tree = self.branch.basis_tree()
1182
 
                    merge_inner(self.branch,
 
1335
                    merge.merge_inner(
 
1336
                                self.branch,
1183
1337
                                new_basis_tree,
1184
1338
                                basis_tree,
1185
1339
                                this_tree=self,
1186
1340
                                pb=pb)
 
1341
                    if (basis_tree.inventory.root is None and
 
1342
                        new_basis_tree.inventory.root is not None):
 
1343
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1187
1344
                finally:
1188
1345
                    pb.finished()
1189
1346
                # TODO - dedup parents list with things merged by pull ?
1247
1404
                subp = pathjoin(path, subf)
1248
1405
                yield subp
1249
1406
 
1250
 
    def _translate_ignore_rule(self, rule):
1251
 
        """Translate a single ignore rule to a regex.
1252
 
 
1253
 
        There are two types of ignore rules.  Those that do not contain a / are
1254
 
        matched against the tail of the filename (that is, they do not care
1255
 
        what directory the file is in.)  Rules which do contain a slash must
1256
 
        match the entire path.  As a special case, './' at the start of the
1257
 
        string counts as a slash in the string but is removed before matching
1258
 
        (e.g. ./foo.c, ./src/foo.c)
1259
 
 
1260
 
        :return: The translated regex.
1261
 
        """
1262
 
        if rule[:2] in ('./', '.\\'):
1263
 
            # rootdir rule
1264
 
            result = fnmatch.translate(rule[2:])
1265
 
        elif '/' in rule or '\\' in rule:
1266
 
            # path prefix 
1267
 
            result = fnmatch.translate(rule)
1268
 
        else:
1269
 
            # default rule style.
1270
 
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1271
 
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1272
 
        return "(" + result + ")"
1273
 
 
1274
 
    def _combine_ignore_rules(self, rules):
1275
 
        """Combine a list of ignore rules into a single regex object.
1276
 
 
1277
 
        Each individual rule is combined with | to form a big regex, which then
1278
 
        has $ added to it to form something like ()|()|()$. The group index for
1279
 
        each subregex's outermost group is placed in a dictionary mapping back 
1280
 
        to the rule. This allows quick identification of the matching rule that
1281
 
        triggered a match.
1282
 
        :return: a list of the compiled regex and the matching-group index 
1283
 
        dictionaries. We return a list because python complains if you try to 
1284
 
        combine more than 100 regexes.
1285
 
        """
1286
 
        result = []
1287
 
        groups = {}
1288
 
        next_group = 0
1289
 
        translated_rules = []
1290
 
        for rule in rules:
1291
 
            translated_rule = self._translate_ignore_rule(rule)
1292
 
            compiled_rule = re.compile(translated_rule)
1293
 
            groups[next_group] = rule
1294
 
            next_group += compiled_rule.groups
1295
 
            translated_rules.append(translated_rule)
1296
 
            if next_group == 99:
1297
 
                result.append((re.compile("|".join(translated_rules)), groups))
1298
 
                groups = {}
1299
 
                next_group = 0
1300
 
                translated_rules = []
1301
 
        if len(translated_rules):
1302
 
            result.append((re.compile("|".join(translated_rules)), groups))
1303
 
        return result
1304
1407
 
1305
1408
    def ignored_files(self):
1306
1409
        """Yield list of PATH, IGNORE_PATTERN"""
1320
1423
 
1321
1424
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
1322
1425
        ignore_globs.update(ignores.get_runtime_ignores())
1323
 
 
1324
1426
        ignore_globs.update(ignores.get_user_ignores())
1325
 
 
1326
1427
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1327
1428
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1328
1429
            try:
1329
1430
                ignore_globs.update(ignores.parse_ignore_file(f))
1330
1431
            finally:
1331
1432
                f.close()
1332
 
 
1333
1433
        self._ignoreset = ignore_globs
1334
 
        self._ignore_regex = self._combine_ignore_rules(ignore_globs)
1335
1434
        return ignore_globs
1336
1435
 
1337
 
    def _get_ignore_rules_as_regex(self):
1338
 
        """Return a regex of the ignore rules and a mapping dict.
1339
 
 
1340
 
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1341
 
        indices to original rule.)
1342
 
        """
1343
 
        if getattr(self, '_ignoreset', None) is None:
1344
 
            self.get_ignore_list()
1345
 
        return self._ignore_regex
 
1436
    def _flush_ignore_list_cache(self):
 
1437
        """Resets the cached ignore list to force a cache rebuild."""
 
1438
        self._ignoreset = None
 
1439
        self._ignoreglobster = None
1346
1440
 
1347
1441
    def is_ignored(self, filename):
1348
1442
        r"""Check whether the filename matches an ignore pattern.
1353
1447
        If the file is ignored, returns the pattern which caused it to
1354
1448
        be ignored, otherwise None.  So this can simply be used as a
1355
1449
        boolean if desired."""
1356
 
 
1357
 
        # TODO: Use '**' to match directories, and other extended
1358
 
        # globbing stuff from cvs/rsync.
1359
 
 
1360
 
        # XXX: fnmatch is actually not quite what we want: it's only
1361
 
        # approximately the same as real Unix fnmatch, and doesn't
1362
 
        # treat dotfiles correctly and allows * to match /.
1363
 
        # Eventually it should be replaced with something more
1364
 
        # accurate.
1365
 
    
1366
 
        rules = self._get_ignore_rules_as_regex()
1367
 
        for regex, mapping in rules:
1368
 
            match = regex.match(filename)
1369
 
            if match is not None:
1370
 
                # one or more of the groups in mapping will have a non-None
1371
 
                # group match.
1372
 
                groups = match.groups()
1373
 
                rules = [mapping[group] for group in 
1374
 
                    mapping if groups[group] is not None]
1375
 
                return rules[0]
1376
 
        return None
 
1450
        if getattr(self, '_ignoreglobster', None) is None:
 
1451
            self._ignoreglobster = globbing.Globster(self.get_ignore_list())
 
1452
        return self._ignoreglobster.match(filename)
1377
1453
 
1378
1454
    def kind(self, file_id):
1379
1455
        return file_kind(self.id2abspath(file_id))
1380
1456
 
 
1457
    def _comparison_data(self, entry, path):
 
1458
        abspath = self.abspath(path)
 
1459
        try:
 
1460
            stat_value = os.lstat(abspath)
 
1461
        except OSError, e:
 
1462
            if getattr(e, 'errno', None) == errno.ENOENT:
 
1463
                stat_value = None
 
1464
                kind = None
 
1465
                executable = False
 
1466
            else:
 
1467
                raise
 
1468
        else:
 
1469
            mode = stat_value.st_mode
 
1470
            kind = osutils.file_kind_from_stat_mode(mode)
 
1471
            if not supports_executable():
 
1472
                executable = entry.executable
 
1473
            else:
 
1474
                executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
1475
        return kind, executable, stat_value
 
1476
 
 
1477
    def _file_size(self, entry, stat_value):
 
1478
        return stat_value.st_size
 
1479
 
1381
1480
    def last_revision(self):
1382
1481
        """Return the last revision of the branch for this tree.
1383
1482
 
1461
1560
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
1462
1561
        """Create the text that will be saved in basis-inventory"""
1463
1562
        inventory.revision_id = revision_id
1464
 
        return bzrlib.xml6.serializer_v6.write_inventory_to_string(inventory)
 
1563
        return xml6.serializer_v6.write_inventory_to_string(inventory)
1465
1564
 
1466
1565
    def _cache_basis_inventory(self, new_revision):
1467
1566
        """Cache new_revision as the basis inventory."""
1499
1598
    def read_working_inventory(self):
1500
1599
        """Read the working inventory.
1501
1600
        
1502
 
        :raises errors.InventoryModified: When the current in memory
1503
 
            inventory has been modified, read_working_inventory will
1504
 
            fail.
 
1601
        :raises errors.InventoryModified: read_working_inventory will fail
 
1602
            when the current in memory inventory has been modified.
1505
1603
        """
1506
1604
        # conceptually this should be an implementation detail of the tree. 
1507
1605
        # XXX: Deprecate this.
1509
1607
        # binary.
1510
1608
        if self._inventory_is_modified:
1511
1609
            raise errors.InventoryModified(self)
1512
 
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1610
        result = xml5.serializer_v5.read_inventory(
1513
1611
            self._control_files.get('inventory'))
1514
1612
        self._set_inventory(result, dirty=False)
1515
1613
        return result
1542
1640
            if not fid:
1543
1641
                # TODO: Perhaps make this just a warning, and continue?
1544
1642
                # This tends to happen when 
1545
 
                raise NotVersionedError(path=f)
 
1643
                raise errors.NotVersionedError(path=f)
1546
1644
            if verbose:
1547
1645
                # having remove it, it must be either ignored or unknown
1548
1646
                if self.is_ignored(f):
1549
1647
                    new_status = 'I'
1550
1648
                else:
1551
1649
                    new_status = '?'
1552
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1650
                textui.show_status(new_status, inv[fid].kind, f,
 
1651
                                   to_file=to_file)
1553
1652
            del inv[fid]
1554
1653
 
1555
1654
        self._write_inventory(inv)
1556
1655
 
1557
1656
    @needs_tree_write_lock
1558
1657
    def revert(self, filenames, old_tree=None, backups=True, 
1559
 
               pb=DummyProgress()):
1560
 
        from transform import revert
1561
 
        from conflicts import resolve
 
1658
               pb=DummyProgress(), report_changes=False):
 
1659
        from bzrlib.conflicts import resolve
1562
1660
        if old_tree is None:
1563
1661
            old_tree = self.basis_tree()
1564
 
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1662
        conflicts = transform.revert(self, old_tree, filenames, backups, pb,
 
1663
                                     report_changes)
1565
1664
        if not len(filenames):
1566
1665
            self.set_parent_ids(self.get_parent_ids()[:1])
1567
1666
            resolve(self)
1578
1677
        if revision_id == self.last_revision():
1579
1678
            try:
1580
1679
                xml = self.read_basis_inventory()
1581
 
            except NoSuchFile:
 
1680
            except errors.NoSuchFile:
1582
1681
                pass
1583
1682
            else:
1584
1683
                try:
1585
 
                    inv = bzrlib.xml6.serializer_v6.read_inventory_from_string(xml)
 
1684
                    inv = xml6.serializer_v6.read_inventory_from_string(xml)
1586
1685
                    # dont use the repository revision_tree api because we want
1587
1686
                    # to supply the inventory.
1588
1687
                    if inv.revision_id == revision_id:
1589
 
                        return bzrlib.tree.RevisionTree(self.branch.repository,
 
1688
                        return revisiontree.RevisionTree(self.branch.repository,
1590
1689
                            inv, revision_id)
1591
1690
                except errors.BadInventoryFormat:
1592
1691
                    pass
1615
1714
            elif kind == 'symlink':
1616
1715
                inv.add(InventoryLink(file_id, name, parent))
1617
1716
            else:
1618
 
                raise BzrError("unknown kind %r" % kind)
 
1717
                raise errors.BzrError("unknown kind %r" % kind)
1619
1718
        self._write_inventory(inv)
1620
1719
 
1621
1720
    @needs_tree_write_lock
1658
1757
        """
1659
1758
        raise NotImplementedError(self.unlock)
1660
1759
 
1661
 
    @needs_write_lock
1662
1760
    def update(self):
1663
1761
        """Update a working tree along its branch.
1664
1762
 
1665
 
        This will update the branch if its bound too, which means we have multiple trees involved:
1666
 
        The new basis tree of the master.
1667
 
        The old basis tree of the branch.
1668
 
        The old basis tree of the working tree.
1669
 
        The current working tree state.
1670
 
        pathologically all three may be different, and non ancestors of each other.
1671
 
        Conceptually we want to:
1672
 
        Preserve the wt.basis->wt.state changes
1673
 
        Transform the wt.basis to the new master basis.
1674
 
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1675
 
        Restore the wt.basis->wt.state changes.
 
1763
        This will update the branch if its bound too, which means we have
 
1764
        multiple trees involved:
 
1765
 
 
1766
        - The new basis tree of the master.
 
1767
        - The old basis tree of the branch.
 
1768
        - The old basis tree of the working tree.
 
1769
        - The current working tree state.
 
1770
 
 
1771
        Pathologically, all three may be different, and non-ancestors of each
 
1772
        other.  Conceptually we want to:
 
1773
 
 
1774
        - Preserve the wt.basis->wt.state changes
 
1775
        - Transform the wt.basis to the new master basis.
 
1776
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1777
          it into the tree.
 
1778
        - Restore the wt.basis->wt.state changes.
1676
1779
 
1677
1780
        There isn't a single operation at the moment to do that, so we:
1678
 
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
1679
 
        Do a 'normal' merge of the old branch basis if it is relevant.
1680
 
        """
1681
 
        old_tip = self.branch.update()
 
1781
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1782
          basis.
 
1783
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
1784
        """
 
1785
        if self.branch.get_master_branch() is not None:
 
1786
            self.lock_write()
 
1787
            update_branch = True
 
1788
        else:
 
1789
            self.lock_tree_write()
 
1790
            update_branch = False
 
1791
        try:
 
1792
            if update_branch:
 
1793
                old_tip = self.branch.update()
 
1794
            else:
 
1795
                old_tip = None
 
1796
            return self._update_tree(old_tip)
 
1797
        finally:
 
1798
            self.unlock()
 
1799
 
 
1800
    @needs_tree_write_lock
 
1801
    def _update_tree(self, old_tip=None):
 
1802
        """Update a tree to the master branch.
 
1803
 
 
1804
        :param old_tip: if supplied, the previous tip revision the branch,
 
1805
            before it was changed to the master branch's tip.
 
1806
        """
1682
1807
        # here if old_tip is not None, it is the old tip of the branch before
1683
1808
        # it was updated from the master branch. This should become a pending
1684
1809
        # merge in the working tree to preserve the user existing work.  we
1698
1823
            # merge tree state up to new branch tip.
1699
1824
            basis = self.basis_tree()
1700
1825
            to_tree = self.branch.basis_tree()
1701
 
            result += merge_inner(self.branch,
 
1826
            if basis.inventory.root is None:
 
1827
                self.set_root_id(to_tree.inventory.root.file_id)
 
1828
            result += merge.merge_inner(
 
1829
                                  self.branch,
1702
1830
                                  to_tree,
1703
1831
                                  basis,
1704
1832
                                  this_tree=self)
1739
1867
                base_rev_id = None
1740
1868
            base_tree = self.branch.repository.revision_tree(base_rev_id)
1741
1869
            other_tree = self.branch.repository.revision_tree(old_tip)
1742
 
            result += merge_inner(self.branch,
 
1870
            result += merge.merge_inner(
 
1871
                                  self.branch,
1743
1872
                                  other_tree,
1744
1873
                                  base_tree,
1745
1874
                                  this_tree=self)
1746
1875
        return result
1747
1876
 
 
1877
    def _write_hashcache_if_dirty(self):
 
1878
        """Write out the hashcache if it is dirty."""
 
1879
        if self._hashcache.needs_write:
 
1880
            try:
 
1881
                self._hashcache.write()
 
1882
            except OSError, e:
 
1883
                if e.errno not in (errno.EPERM, errno.EACCES):
 
1884
                    raise
 
1885
                # TODO: jam 20061219 Should this be a warning? A single line
 
1886
                #       warning might be sufficient to let the user know what
 
1887
                #       is going on.
 
1888
                mutter('Could not write hashcache for %s\nError: %s',
 
1889
                       self._hashcache.cache_file_name(), e)
 
1890
 
1748
1891
    @needs_tree_write_lock
1749
1892
    def _write_inventory(self, inv):
1750
1893
        """Write inventory as the current inventory."""
1752
1895
        self.flush()
1753
1896
 
1754
1897
    def set_conflicts(self, arg):
1755
 
        raise UnsupportedOperation(self.set_conflicts, self)
 
1898
        raise errors.UnsupportedOperation(self.set_conflicts, self)
1756
1899
 
1757
1900
    def add_conflicts(self, arg):
1758
 
        raise UnsupportedOperation(self.add_conflicts, self)
 
1901
        raise errors.UnsupportedOperation(self.add_conflicts, self)
1759
1902
 
1760
1903
    @needs_read_lock
1761
1904
    def conflicts(self):
1762
 
        conflicts = ConflictList()
 
1905
        conflicts = _mod_conflicts.ConflictList()
1763
1906
        for conflicted in self._iter_conflicts():
1764
1907
            text = True
1765
1908
            try:
1778
1921
                    if text == False:
1779
1922
                        break
1780
1923
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1781
 
            conflicts.append(Conflict.factory(ctype, path=conflicted,
 
1924
            conflicts.append(_mod_conflicts.Conflict.factory(ctype,
 
1925
                             path=conflicted,
1782
1926
                             file_id=self.path2id(conflicted)))
1783
1927
        return conflicts
1784
1928
 
1935
2079
            # _inventory_is_modified is always False during a read lock.
1936
2080
            if self._inventory_is_modified:
1937
2081
                self.flush()
1938
 
            if self._hashcache.needs_write:
1939
 
                self._hashcache.write()
 
2082
            self._write_hashcache_if_dirty()
 
2083
                    
1940
2084
        # reverse order of locking.
1941
2085
        try:
1942
2086
            return self._control_files.unlock()
1959
2103
        """See Mutable.last_revision."""
1960
2104
        try:
1961
2105
            return self._control_files.get_utf8('last-revision').read()
1962
 
        except NoSuchFile:
 
2106
        except errors.NoSuchFile:
1963
2107
            return None
1964
2108
 
1965
2109
    def _change_last_revision(self, revision_id):
1983
2127
    def add_conflicts(self, new_conflicts):
1984
2128
        conflict_set = set(self.conflicts())
1985
2129
        conflict_set.update(set(list(new_conflicts)))
1986
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1987
 
                                               key=Conflict.sort_key)))
 
2130
        self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
 
2131
                                       key=_mod_conflicts.Conflict.sort_key)))
1988
2132
 
1989
2133
    @needs_read_lock
1990
2134
    def conflicts(self):
1991
2135
        try:
1992
2136
            confile = self._control_files.get('conflicts')
1993
 
        except NoSuchFile:
1994
 
            return ConflictList()
 
2137
        except errors.NoSuchFile:
 
2138
            return _mod_conflicts.ConflictList()
1995
2139
        try:
1996
2140
            if confile.next() != CONFLICT_HEADER_1 + '\n':
1997
 
                raise ConflictFormatError()
 
2141
                raise errors.ConflictFormatError()
1998
2142
        except StopIteration:
1999
 
            raise ConflictFormatError()
2000
 
        return ConflictList.from_stanzas(RioReader(confile))
 
2143
            raise errors.ConflictFormatError()
 
2144
        return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
2001
2145
 
2002
2146
    def unlock(self):
2003
2147
        if self._control_files._lock_count == 1:
2004
2148
            # _inventory_is_modified is always False during a read lock.
2005
2149
            if self._inventory_is_modified:
2006
2150
                self.flush()
2007
 
            if self._hashcache.needs_write:
2008
 
                self._hashcache.write()
 
2151
            self._write_hashcache_if_dirty()
2009
2152
        # reverse order of locking.
2010
2153
        try:
2011
2154
            return self._control_files.unlock()
2014
2157
 
2015
2158
 
2016
2159
def get_conflicted_stem(path):
2017
 
    for suffix in CONFLICT_SUFFIXES:
 
2160
    for suffix in _mod_conflicts.CONFLICT_SUFFIXES:
2018
2161
        if path.endswith(suffix):
2019
2162
            return path[:-len(suffix)]
2020
2163
 
2065
2208
            transport = a_bzrdir.get_workingtree_transport(None)
2066
2209
            format_string = transport.get("format").read()
2067
2210
            return klass._formats[format_string]
2068
 
        except NoSuchFile:
 
2211
        except errors.NoSuchFile:
2069
2212
            raise errors.NoWorkingTree(base=transport.base)
2070
2213
        except KeyError:
2071
2214
            raise errors.UnknownFormatError(format=format_string)
2126
2269
        """
2127
2270
        sio = StringIO()
2128
2271
        inv = Inventory()
2129
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
2272
        xml5.serializer_v5.write_inventory(inv, sio)
2130
2273
        sio.seek(0)
2131
2274
        control_files.put('inventory', sio)
2132
2275
 
2157
2300
                         _internal=True,
2158
2301
                         _format=self,
2159
2302
                         _bzrdir=a_bzrdir)
2160
 
        wt.set_root_id(inv.root.file_id)
2161
2303
        basis_tree = branch.repository.revision_tree(revision)
 
2304
        if basis_tree.inventory.root is not None:
 
2305
            wt.set_root_id(basis_tree.inventory.root.file_id)
 
2306
        # set the parent list and cache the basis tree.
2162
2307
        wt.set_parent_trees([(revision, basis_tree)])
2163
 
        build_tree(basis_tree, wt)
 
2308
        transform.build_tree(basis_tree, wt)
2164
2309
        return wt
2165
2310
 
2166
2311
    def __init__(self):
2228
2373
        branch = a_bzrdir.open_branch()
2229
2374
        if revision_id is None:
2230
2375
            revision_id = branch.last_revision()
 
2376
        # WorkingTree3 can handle an inventory which has a unique root id.
 
2377
        # as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
 
2378
        # those trees. And because there isn't a format bump inbetween, we
 
2379
        # are maintaining compatibility with older clients.
 
2380
        # inv = Inventory(root_id=gen_root_id())
2231
2381
        inv = Inventory()
2232
2382
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2233
2383
                         branch,
2238
2388
                         _control_files=control_files)
2239
2389
        wt.lock_tree_write()
2240
2390
        try:
2241
 
            wt.set_root_id(inv.root.file_id)
2242
2391
            basis_tree = branch.repository.revision_tree(revision_id)
2243
 
            if revision_id == bzrlib.revision.NULL_REVISION:
 
2392
            # only set an explicit root id if there is one to set.
 
2393
            if basis_tree.inventory.root is not None:
 
2394
                wt.set_root_id(basis_tree.inventory.root.file_id)
 
2395
            if revision_id == NULL_REVISION:
2244
2396
                wt.set_parent_trees([])
2245
2397
            else:
2246
2398
                wt.set_parent_trees([(revision_id, basis_tree)])
2247
 
            build_tree(basis_tree, wt)
 
2399
            transform.build_tree(basis_tree, wt)
2248
2400
        finally:
2249
 
            # unlock in this order so that the unlock-triggers-flush in
 
2401
            # Unlock in this order so that the unlock-triggers-flush in
2250
2402
            # WorkingTree is given a chance to fire.
2251
2403
            control_files.unlock()
2252
2404
            wt.unlock()