/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

[merge] bzr.dev 2240

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
import collections
44
45
from copy import deepcopy
45
 
from cStringIO import StringIO
46
46
import errno
47
 
import fnmatch
48
 
import os
49
 
import re
50
47
import stat
51
48
from time import time
52
49
import warnings
54
51
import bzrlib
55
52
from bzrlib import (
56
53
    bzrdir,
 
54
    conflicts as _mod_conflicts,
57
55
    errors,
 
56
    generate_ids,
 
57
    globbing,
 
58
    hashcache,
58
59
    ignores,
 
60
    merge,
59
61
    osutils,
60
 
    symbol_versioning,
 
62
    revisiontree,
 
63
    textui,
 
64
    transform,
61
65
    urlutils,
 
66
    xml5,
 
67
    xml6,
62
68
    )
63
 
from bzrlib.atomicfile import AtomicFile
64
69
import bzrlib.branch
65
 
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
70
from bzrlib.transport import get_transport
 
71
import bzrlib.ui
 
72
""")
 
73
 
 
74
from bzrlib import symbol_versioning
66
75
from bzrlib.decorators import needs_read_lock, needs_write_lock
67
76
from bzrlib.errors import (BzrCheckError,
68
77
                           BzrError,
77
86
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
78
87
from bzrlib.lockable_files import LockableFiles, TransportLock
79
88
from bzrlib.lockdir import LockDir
80
 
from bzrlib.merge import merge_inner, transform_tree
81
89
import bzrlib.mutabletree
82
90
from bzrlib.mutabletree import needs_tree_write_lock
83
91
from bzrlib.osutils import (
84
 
                            abspath,
85
 
                            compact_date,
86
 
                            file_kind,
87
 
                            isdir,
88
 
                            getcwd,
89
 
                            pathjoin,
90
 
                            pumpfile,
91
 
                            safe_unicode,
92
 
                            splitpath,
93
 
                            rand_chars,
94
 
                            normpath,
95
 
                            realpath,
96
 
                            relpath,
97
 
                            rename,
98
 
                            supports_executable,
99
 
                            )
 
92
    compact_date,
 
93
    file_kind,
 
94
    isdir,
 
95
    pathjoin,
 
96
    safe_unicode,
 
97
    splitpath,
 
98
    rand_chars,
 
99
    normpath,
 
100
    realpath,
 
101
    supports_executable,
 
102
    )
 
103
from bzrlib.trace import mutter, note
 
104
from bzrlib.transport.local import LocalTransport
100
105
from bzrlib.progress import DummyProgress, ProgressPhase
101
 
from bzrlib.revision import NULL_REVISION
102
 
import bzrlib.revisiontree
 
106
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
103
107
from bzrlib.rio import RioReader, rio_file, Stanza
104
108
from bzrlib.symbol_versioning import (deprecated_passed,
105
109
        deprecated_method,
107
111
        DEPRECATED_PARAMETER,
108
112
        zero_eight,
109
113
        zero_eleven,
 
114
        zero_thirteen,
110
115
        )
111
 
from bzrlib.trace import mutter, note
112
 
from bzrlib.transform import build_tree
113
 
from bzrlib.transport import get_transport
114
 
from bzrlib.transport.local import LocalTransport
115
 
from bzrlib.textui import show_status
116
 
import bzrlib.ui
117
 
import bzrlib.xml5
118
 
 
119
 
 
120
 
# the regex removes any weird characters; we don't escape them 
121
 
# but rather just pull them out
122
 
_gen_file_id_re = re.compile(r'[^\w.]')
123
 
_gen_id_suffix = None
124
 
_gen_id_serial = 0
125
 
 
126
 
 
127
 
def _next_id_suffix():
128
 
    """Create a new file id suffix that is reasonably unique.
129
 
    
130
 
    On the first call we combine the current time with 64 bits of randomness
131
 
    to give a highly probably globally unique number. Then each call in the same
132
 
    process adds 1 to a serial number we append to that unique value.
133
 
    """
134
 
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
135
 
    # than having to move the id randomness out of the inner loop like this.
136
 
    # XXX TODO: for the global randomness this uses we should add the thread-id
137
 
    # before the serial #.
138
 
    global _gen_id_suffix, _gen_id_serial
139
 
    if _gen_id_suffix is None:
140
 
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
141
 
    _gen_id_serial += 1
142
 
    return _gen_id_suffix + str(_gen_id_serial)
143
 
 
144
 
 
 
116
 
 
117
 
 
118
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
119
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
120
 
 
121
 
 
122
@deprecated_function(zero_thirteen)
145
123
def gen_file_id(name):
146
124
    """Return new file id for the basename 'name'.
147
125
 
148
 
    The uniqueness is supplied from _next_id_suffix.
 
126
    Use bzrlib.generate_ids.gen_file_id() instead
149
127
    """
150
 
    # The real randomness is in the _next_id_suffix, the
151
 
    # rest of the identifier is just to be nice.
152
 
    # So we:
153
 
    # 1) Remove non-ascii word characters to keep the ids portable
154
 
    # 2) squash to lowercase, so the file id doesn't have to
155
 
    #    be escaped (case insensitive filesystems would bork for ids
156
 
    #    that only differred in case without escaping).
157
 
    # 3) truncate the filename to 20 chars. Long filenames also bork on some
158
 
    #    filesystems
159
 
    # 4) Removing starting '.' characters to prevent the file ids from
160
 
    #    being considered hidden.
161
 
    ascii_word_only = _gen_file_id_re.sub('', name.lower())
162
 
    short_no_dots = ascii_word_only.lstrip('.')[:20]
163
 
    return short_no_dots + _next_id_suffix()
164
 
 
165
 
 
 
128
    return generate_ids.gen_file_id(name)
 
129
 
 
130
 
 
131
@deprecated_function(zero_thirteen)
166
132
def gen_root_id():
167
 
    """Return a new tree-root file id."""
168
 
    return gen_file_id('TREE_ROOT')
 
133
    """Return a new tree-root file id.
 
134
 
 
135
    This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
 
136
    """
 
137
    return generate_ids.gen_root_id()
169
138
 
170
139
 
171
140
class TreeEntry(object):
263
232
            self._set_inventory(wt._inventory, dirty=False)
264
233
            self._format = wt._format
265
234
            self.bzrdir = wt.bzrdir
266
 
        from bzrlib.hashcache import HashCache
267
 
        from bzrlib.trace import note, mutter
268
235
        assert isinstance(basedir, basestring), \
269
236
            "base directory %r is not a string" % basedir
270
237
        basedir = safe_unicode(basedir)
298
265
        # cache file, and have the parser take the most recent entry for a
299
266
        # given path only.
300
267
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
301
 
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
 
268
        self._hashcache = hashcache.HashCache(basedir, cache_filename,
 
269
                                              self._control_files._file_mode)
 
270
        hc = self._hashcache
302
271
        hc.read()
303
272
        # is this scan needed ? it makes things kinda slow.
304
273
        #hc.scan()
411
380
        If the left most parent is a ghost then the returned tree will be an
412
381
        empty tree - one obtained by calling repository.revision_tree(None).
413
382
        """
414
 
        revision_id = self.last_revision()
415
 
        if revision_id is None:
 
383
        try:
 
384
            revision_id = self.get_parent_ids()[0]
 
385
        except IndexError:
416
386
            # no parents, return an empty revision tree.
417
387
            # in the future this should return the tree for
418
388
            # 'empty:' - the implicit root empty tree.
479
449
        The path may be absolute or relative. If its a relative path it is 
480
450
        interpreted relative to the python current working directory.
481
451
        """
482
 
        return relpath(self.basedir, path)
 
452
        return osutils.relpath(self.basedir, path)
483
453
 
484
454
    def has_filename(self, filename):
485
455
        return osutils.lexists(self.abspath(filename))
493
463
    def get_file_byname(self, filename):
494
464
        return file(self.abspath(filename), 'rb')
495
465
 
 
466
    def annotate_iter(self, file_id):
 
467
        """See Tree.annotate_iter
 
468
 
 
469
        This implementation will use the basis tree implementation if possible.
 
470
        Lines not in the basis are attributed to CURRENT_REVISION
 
471
 
 
472
        If there are pending merges, lines added by those merges will be
 
473
        incorrectly attributed to CURRENT_REVISION (but after committing, the
 
474
        attribution will be correct).
 
475
        """
 
476
        basis = self.basis_tree()
 
477
        changes = self._iter_changes(basis, True, [file_id]).next()
 
478
        changed_content, kind = changes[2], changes[6]
 
479
        if not changed_content:
 
480
            return basis.annotate_iter(file_id)
 
481
        if kind[1] is None:
 
482
            return None
 
483
        import annotate
 
484
        if kind[0] != 'file':
 
485
            old_lines = []
 
486
        else:
 
487
            old_lines = list(basis.annotate_iter(file_id))
 
488
        old = [old_lines]
 
489
        for tree in self.branch.repository.revision_trees(
 
490
            self.get_parent_ids()[1:]):
 
491
            if file_id not in tree:
 
492
                continue
 
493
            old.append(list(tree.annotate_iter(file_id)))
 
494
        return annotate.reannotate(old, self.get_file(file_id).readlines(),
 
495
                                   CURRENT_REVISION)
 
496
 
496
497
    def get_parent_ids(self):
497
498
        """See Tree.get_parent_ids.
498
499
        
548
549
    @needs_read_lock
549
550
    def copy_content_into(self, tree, revision_id=None):
550
551
        """Copy the current content and user files of this tree into tree."""
 
552
        tree.set_root_id(self.get_root_id())
551
553
        if revision_id is None:
552
 
            transform_tree(tree, self)
 
554
            merge.transform_tree(tree, self)
553
555
        else:
554
556
            # TODO now merge from tree.last_revision to revision (to preserve
555
557
            # user local changes)
556
 
            transform_tree(tree, self)
 
558
            merge.transform_tree(tree, self)
557
559
            tree.set_parent_ids([revision_id])
558
560
 
559
561
    def id2abspath(self, file_id):
578
580
        return os.path.getsize(self.id2abspath(file_id))
579
581
 
580
582
    @needs_read_lock
581
 
    def get_file_sha1(self, file_id, path=None):
 
583
    def get_file_sha1(self, file_id, path=None, stat_value=None):
582
584
        if not path:
583
585
            path = self._inventory.id2path(file_id)
584
 
        return self._hashcache.get_sha1(path)
 
586
        return self._hashcache.get_sha1(path, stat_value)
585
587
 
586
588
    def get_file_mtime(self, file_id, path=None):
587
589
        if not path:
843
845
    def mkdir(self, path, file_id=None):
844
846
        """See MutableTree.mkdir()."""
845
847
        if file_id is None:
846
 
            file_id = gen_file_id(os.path.basename(path))
 
848
            file_id = generate_ids.gen_file_id(os.path.basename(path))
847
849
        os.mkdir(self.abspath(path))
848
850
        self.add(path, file_id, 'directory')
849
851
        return file_id
865
867
        if self._control_files._lock_mode != 'w':
866
868
            raise errors.NotWriteLocked(self)
867
869
        sio = StringIO()
868
 
        bzrlib.xml5.serializer_v5.write_inventory(self._inventory, sio)
 
870
        xml5.serializer_v5.write_inventory(self._inventory, sio)
869
871
        sio.seek(0)
870
872
        self._control_files.put('inventory', sio)
871
873
        self._inventory_is_modified = False
1041
1043
                result.append((f, dest_path))
1042
1044
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1043
1045
                try:
1044
 
                    rename(self.abspath(f), self.abspath(dest_path))
 
1046
                    osutils.rename(self.abspath(f), self.abspath(dest_path))
1045
1047
                except OSError, e:
1046
1048
                    raise BzrError("failed to rename %r to %r: %s" %
1047
 
                                   (f, dest_path, e[1]),
1048
 
                            ["rename rolled back"])
 
1049
                                   (f, dest_path, e[1]))
1049
1050
        except:
1050
1051
            # restore the inventory on error
1051
1052
            self._set_inventory(orig_inv, dirty=original_modified)
1093
1094
        from_abs = self.abspath(from_rel)
1094
1095
        to_abs = self.abspath(to_rel)
1095
1096
        try:
1096
 
            rename(from_abs, to_abs)
 
1097
            osutils.rename(from_abs, to_abs)
1097
1098
        except OSError, e:
1098
1099
            inv.rename(file_id, from_parent, from_name)
1099
1100
            raise BzrError("failed to rename %r to %r: %s"
1100
 
                    % (from_abs, to_abs, e[1]),
1101
 
                    ["rename rolled back"])
 
1101
                    % (from_abs, to_abs, e[1]))
1102
1102
        self._write_inventory(inv)
1103
1103
 
1104
1104
    @needs_read_lock
1176
1176
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
1177
1177
                try:
1178
1178
                    new_basis_tree = self.branch.basis_tree()
1179
 
                    merge_inner(self.branch,
 
1179
                    merge.merge_inner(
 
1180
                                self.branch,
1180
1181
                                new_basis_tree,
1181
1182
                                basis_tree,
1182
1183
                                this_tree=self,
1183
1184
                                pb=pb)
 
1185
                    if (basis_tree.inventory.root is None and
 
1186
                        new_basis_tree.inventory.root is not None):
 
1187
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1184
1188
                finally:
1185
1189
                    pb.finished()
1186
1190
                # TODO - dedup parents list with things merged by pull ?
1244
1248
                subp = pathjoin(path, subf)
1245
1249
                yield subp
1246
1250
 
1247
 
    def _translate_ignore_rule(self, rule):
1248
 
        """Translate a single ignore rule to a regex.
1249
 
 
1250
 
        There are two types of ignore rules.  Those that do not contain a / are
1251
 
        matched against the tail of the filename (that is, they do not care
1252
 
        what directory the file is in.)  Rules which do contain a slash must
1253
 
        match the entire path.  As a special case, './' at the start of the
1254
 
        string counts as a slash in the string but is removed before matching
1255
 
        (e.g. ./foo.c, ./src/foo.c)
1256
 
 
1257
 
        :return: The translated regex.
1258
 
        """
1259
 
        if rule[:2] in ('./', '.\\'):
1260
 
            # rootdir rule
1261
 
            result = fnmatch.translate(rule[2:])
1262
 
        elif '/' in rule or '\\' in rule:
1263
 
            # path prefix 
1264
 
            result = fnmatch.translate(rule)
1265
 
        else:
1266
 
            # default rule style.
1267
 
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1268
 
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1269
 
        return "(" + result + ")"
1270
 
 
1271
 
    def _combine_ignore_rules(self, rules):
1272
 
        """Combine a list of ignore rules into a single regex object.
1273
 
 
1274
 
        Each individual rule is combined with | to form a big regex, which then
1275
 
        has $ added to it to form something like ()|()|()$. The group index for
1276
 
        each subregex's outermost group is placed in a dictionary mapping back 
1277
 
        to the rule. This allows quick identification of the matching rule that
1278
 
        triggered a match.
1279
 
        :return: a list of the compiled regex and the matching-group index 
1280
 
        dictionaries. We return a list because python complains if you try to 
1281
 
        combine more than 100 regexes.
1282
 
        """
1283
 
        result = []
1284
 
        groups = {}
1285
 
        next_group = 0
1286
 
        translated_rules = []
1287
 
        for rule in rules:
1288
 
            translated_rule = self._translate_ignore_rule(rule)
1289
 
            compiled_rule = re.compile(translated_rule)
1290
 
            groups[next_group] = rule
1291
 
            next_group += compiled_rule.groups
1292
 
            translated_rules.append(translated_rule)
1293
 
            if next_group == 99:
1294
 
                result.append((re.compile("|".join(translated_rules)), groups))
1295
 
                groups = {}
1296
 
                next_group = 0
1297
 
                translated_rules = []
1298
 
        if len(translated_rules):
1299
 
            result.append((re.compile("|".join(translated_rules)), groups))
1300
 
        return result
1301
1251
 
1302
1252
    def ignored_files(self):
1303
1253
        """Yield list of PATH, IGNORE_PATTERN"""
1317
1267
 
1318
1268
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
1319
1269
        ignore_globs.update(ignores.get_runtime_ignores())
1320
 
 
1321
1270
        ignore_globs.update(ignores.get_user_ignores())
1322
 
 
1323
1271
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1324
1272
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1325
1273
            try:
1326
1274
                ignore_globs.update(ignores.parse_ignore_file(f))
1327
1275
            finally:
1328
1276
                f.close()
1329
 
 
1330
1277
        self._ignoreset = ignore_globs
1331
 
        self._ignore_regex = self._combine_ignore_rules(ignore_globs)
1332
1278
        return ignore_globs
1333
1279
 
1334
 
    def _get_ignore_rules_as_regex(self):
1335
 
        """Return a regex of the ignore rules and a mapping dict.
1336
 
 
1337
 
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1338
 
        indices to original rule.)
1339
 
        """
1340
 
        if getattr(self, '_ignoreset', None) is None:
1341
 
            self.get_ignore_list()
1342
 
        return self._ignore_regex
 
1280
    def _flush_ignore_list_cache(self):
 
1281
        """Resets the cached ignore list to force a cache rebuild."""
 
1282
        self._ignoreset = None
 
1283
        self._ignoreglobster = None
1343
1284
 
1344
1285
    def is_ignored(self, filename):
1345
1286
        r"""Check whether the filename matches an ignore pattern.
1350
1291
        If the file is ignored, returns the pattern which caused it to
1351
1292
        be ignored, otherwise None.  So this can simply be used as a
1352
1293
        boolean if desired."""
1353
 
 
1354
 
        # TODO: Use '**' to match directories, and other extended
1355
 
        # globbing stuff from cvs/rsync.
1356
 
 
1357
 
        # XXX: fnmatch is actually not quite what we want: it's only
1358
 
        # approximately the same as real Unix fnmatch, and doesn't
1359
 
        # treat dotfiles correctly and allows * to match /.
1360
 
        # Eventually it should be replaced with something more
1361
 
        # accurate.
1362
 
    
1363
 
        rules = self._get_ignore_rules_as_regex()
1364
 
        for regex, mapping in rules:
1365
 
            match = regex.match(filename)
1366
 
            if match is not None:
1367
 
                # one or more of the groups in mapping will have a non-None
1368
 
                # group match.
1369
 
                groups = match.groups()
1370
 
                rules = [mapping[group] for group in 
1371
 
                    mapping if groups[group] is not None]
1372
 
                return rules[0]
1373
 
        return None
 
1294
        if getattr(self, '_ignoreglobster', None) is None:
 
1295
            self._ignoreglobster = globbing.Globster(self.get_ignore_list())
 
1296
        return self._ignoreglobster.match(filename)
1374
1297
 
1375
1298
    def kind(self, file_id):
1376
1299
        return file_kind(self.id2abspath(file_id))
1377
1300
 
 
1301
    def _comparison_data(self, entry, path):
 
1302
        abspath = self.abspath(path)
 
1303
        try:
 
1304
            stat_value = os.lstat(abspath)
 
1305
        except OSError, e:
 
1306
            if getattr(e, 'errno', None) == errno.ENOENT:
 
1307
                stat_value = None
 
1308
                kind = None
 
1309
                executable = False
 
1310
            else:
 
1311
                raise
 
1312
        else:
 
1313
            mode = stat_value.st_mode
 
1314
            kind = osutils.file_kind_from_stat_mode(mode)
 
1315
            if not supports_executable():
 
1316
                executable = entry.executable
 
1317
            else:
 
1318
                executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
1319
        return kind, executable, stat_value
 
1320
 
 
1321
    def _file_size(self, entry, stat_value):
 
1322
        return stat_value.st_size
 
1323
 
1378
1324
    def last_revision(self):
1379
1325
        """Return the last revision of the branch for this tree.
1380
1326
 
1458
1404
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
1459
1405
        """Create the text that will be saved in basis-inventory"""
1460
1406
        inventory.revision_id = revision_id
1461
 
        return bzrlib.xml6.serializer_v6.write_inventory_to_string(inventory)
 
1407
        return xml6.serializer_v6.write_inventory_to_string(inventory)
1462
1408
 
1463
1409
    def _cache_basis_inventory(self, new_revision):
1464
1410
        """Cache new_revision as the basis inventory."""
1496
1442
    def read_working_inventory(self):
1497
1443
        """Read the working inventory.
1498
1444
        
1499
 
        :raises errors.InventoryModified: When the current in memory
1500
 
            inventory has been modified, read_working_inventory will
1501
 
            fail.
 
1445
        :raises errors.InventoryModified: read_working_inventory will fail
 
1446
            when the current in memory inventory has been modified.
1502
1447
        """
1503
1448
        # conceptually this should be an implementation detail of the tree. 
1504
1449
        # XXX: Deprecate this.
1506
1451
        # binary.
1507
1452
        if self._inventory_is_modified:
1508
1453
            raise errors.InventoryModified(self)
1509
 
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1454
        result = xml5.serializer_v5.read_inventory(
1510
1455
            self._control_files.get('inventory'))
1511
1456
        self._set_inventory(result, dirty=False)
1512
1457
        return result
1546
1491
                    new_status = 'I'
1547
1492
                else:
1548
1493
                    new_status = '?'
1549
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1494
                textui.show_status(new_status, inv[fid].kind, f,
 
1495
                                   to_file=to_file)
1550
1496
            del inv[fid]
1551
1497
 
1552
1498
        self._write_inventory(inv)
1554
1500
    @needs_tree_write_lock
1555
1501
    def revert(self, filenames, old_tree=None, backups=True, 
1556
1502
               pb=DummyProgress()):
1557
 
        from transform import revert
1558
 
        from conflicts import resolve
 
1503
        from bzrlib.conflicts import resolve
1559
1504
        if old_tree is None:
1560
1505
            old_tree = self.basis_tree()
1561
 
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1506
        conflicts = transform.revert(self, old_tree, filenames, backups, pb)
1562
1507
        if not len(filenames):
1563
1508
            self.set_parent_ids(self.get_parent_ids()[:1])
1564
1509
            resolve(self)
1583
1528
                    # dont use the repository revision_tree api because we want
1584
1529
                    # to supply the inventory.
1585
1530
                    if inv.revision_id == revision_id:
1586
 
                        return bzrlib.tree.RevisionTree(self.branch.repository,
 
1531
                        return revisiontree.RevisionTree(self.branch.repository,
1587
1532
                            inv, revision_id)
1588
1533
                except errors.BadInventoryFormat:
1589
1534
                    pass
1655
1600
        """
1656
1601
        raise NotImplementedError(self.unlock)
1657
1602
 
1658
 
    @needs_write_lock
1659
1603
    def update(self):
1660
1604
        """Update a working tree along its branch.
1661
1605
 
1662
 
        This will update the branch if its bound too, which means we have multiple trees involved:
1663
 
        The new basis tree of the master.
1664
 
        The old basis tree of the branch.
1665
 
        The old basis tree of the working tree.
1666
 
        The current working tree state.
1667
 
        pathologically all three may be different, and non ancestors of each other.
1668
 
        Conceptually we want to:
1669
 
        Preserve the wt.basis->wt.state changes
1670
 
        Transform the wt.basis to the new master basis.
1671
 
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1672
 
        Restore the wt.basis->wt.state changes.
 
1606
        This will update the branch if its bound too, which means we have
 
1607
        multiple trees involved:
 
1608
 
 
1609
        - The new basis tree of the master.
 
1610
        - The old basis tree of the branch.
 
1611
        - The old basis tree of the working tree.
 
1612
        - The current working tree state.
 
1613
 
 
1614
        Pathologically, all three may be different, and non-ancestors of each
 
1615
        other.  Conceptually we want to:
 
1616
 
 
1617
        - Preserve the wt.basis->wt.state changes
 
1618
        - Transform the wt.basis to the new master basis.
 
1619
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1620
          it into the tree.
 
1621
        - Restore the wt.basis->wt.state changes.
1673
1622
 
1674
1623
        There isn't a single operation at the moment to do that, so we:
1675
 
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
1676
 
        Do a 'normal' merge of the old branch basis if it is relevant.
1677
 
        """
1678
 
        old_tip = self.branch.update()
 
1624
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1625
          basis.
 
1626
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
1627
        """
 
1628
        if self.branch.get_master_branch() is not None:
 
1629
            self.lock_write()
 
1630
            update_branch = True
 
1631
        else:
 
1632
            self.lock_tree_write()
 
1633
            update_branch = False
 
1634
        try:
 
1635
            if update_branch:
 
1636
                old_tip = self.branch.update()
 
1637
            else:
 
1638
                old_tip = None
 
1639
            return self._update_tree(old_tip)
 
1640
        finally:
 
1641
            self.unlock()
 
1642
 
 
1643
    @needs_tree_write_lock
 
1644
    def _update_tree(self, old_tip=None):
 
1645
        """Update a tree to the master branch.
 
1646
 
 
1647
        :param old_tip: if supplied, the previous tip revision the branch,
 
1648
            before it was changed to the master branch's tip.
 
1649
        """
1679
1650
        # here if old_tip is not None, it is the old tip of the branch before
1680
1651
        # it was updated from the master branch. This should become a pending
1681
1652
        # merge in the working tree to preserve the user existing work.  we
1695
1666
            # merge tree state up to new branch tip.
1696
1667
            basis = self.basis_tree()
1697
1668
            to_tree = self.branch.basis_tree()
1698
 
            result += merge_inner(self.branch,
 
1669
            if basis.inventory.root is None:
 
1670
                self.set_root_id(to_tree.inventory.root.file_id)
 
1671
            result += merge.merge_inner(
 
1672
                                  self.branch,
1699
1673
                                  to_tree,
1700
1674
                                  basis,
1701
1675
                                  this_tree=self)
1736
1710
                base_rev_id = None
1737
1711
            base_tree = self.branch.repository.revision_tree(base_rev_id)
1738
1712
            other_tree = self.branch.repository.revision_tree(old_tip)
1739
 
            result += merge_inner(self.branch,
 
1713
            result += merge.merge_inner(
 
1714
                                  self.branch,
1740
1715
                                  other_tree,
1741
1716
                                  base_tree,
1742
1717
                                  this_tree=self)
1743
1718
        return result
1744
1719
 
 
1720
    def _write_hashcache_if_dirty(self):
 
1721
        """Write out the hashcache if it is dirty."""
 
1722
        if self._hashcache.needs_write:
 
1723
            try:
 
1724
                self._hashcache.write()
 
1725
            except OSError, e:
 
1726
                if e.errno not in (errno.EPERM, errno.EACCES):
 
1727
                    raise
 
1728
                # TODO: jam 20061219 Should this be a warning? A single line
 
1729
                #       warning might be sufficient to let the user know what
 
1730
                #       is going on.
 
1731
                mutter('Could not write hashcache for %s\nError: %s',
 
1732
                       self._hashcache.cache_file_name(), e)
 
1733
 
1745
1734
    @needs_tree_write_lock
1746
1735
    def _write_inventory(self, inv):
1747
1736
        """Write inventory as the current inventory."""
1756
1745
 
1757
1746
    @needs_read_lock
1758
1747
    def conflicts(self):
1759
 
        conflicts = ConflictList()
 
1748
        conflicts = _mod_conflicts.ConflictList()
1760
1749
        for conflicted in self._iter_conflicts():
1761
1750
            text = True
1762
1751
            try:
1775
1764
                    if text == False:
1776
1765
                        break
1777
1766
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1778
 
            conflicts.append(Conflict.factory(ctype, path=conflicted,
 
1767
            conflicts.append(_mod_conflicts.Conflict.factory(ctype,
 
1768
                             path=conflicted,
1779
1769
                             file_id=self.path2id(conflicted)))
1780
1770
        return conflicts
1781
1771
 
1807
1797
            # _inventory_is_modified is always False during a read lock.
1808
1798
            if self._inventory_is_modified:
1809
1799
                self.flush()
1810
 
            if self._hashcache.needs_write:
1811
 
                self._hashcache.write()
 
1800
            self._write_hashcache_if_dirty()
 
1801
                    
1812
1802
        # reverse order of locking.
1813
1803
        try:
1814
1804
            return self._control_files.unlock()
1855
1845
    def add_conflicts(self, new_conflicts):
1856
1846
        conflict_set = set(self.conflicts())
1857
1847
        conflict_set.update(set(list(new_conflicts)))
1858
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1859
 
                                               key=Conflict.sort_key)))
 
1848
        self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
 
1849
                                       key=_mod_conflicts.Conflict.sort_key)))
1860
1850
 
1861
1851
    @needs_read_lock
1862
1852
    def conflicts(self):
1863
1853
        try:
1864
1854
            confile = self._control_files.get('conflicts')
1865
1855
        except NoSuchFile:
1866
 
            return ConflictList()
 
1856
            return _mod_conflicts.ConflictList()
1867
1857
        try:
1868
1858
            if confile.next() != CONFLICT_HEADER_1 + '\n':
1869
1859
                raise ConflictFormatError()
1870
1860
        except StopIteration:
1871
1861
            raise ConflictFormatError()
1872
 
        return ConflictList.from_stanzas(RioReader(confile))
 
1862
        return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
1873
1863
 
1874
1864
    def unlock(self):
1875
1865
        if self._control_files._lock_count == 1:
1876
1866
            # _inventory_is_modified is always False during a read lock.
1877
1867
            if self._inventory_is_modified:
1878
1868
                self.flush()
1879
 
            if self._hashcache.needs_write:
1880
 
                self._hashcache.write()
 
1869
            self._write_hashcache_if_dirty()
1881
1870
        # reverse order of locking.
1882
1871
        try:
1883
1872
            return self._control_files.unlock()
1886
1875
 
1887
1876
 
1888
1877
def get_conflicted_stem(path):
1889
 
    for suffix in CONFLICT_SUFFIXES:
 
1878
    for suffix in _mod_conflicts.CONFLICT_SUFFIXES:
1890
1879
        if path.endswith(suffix):
1891
1880
            return path[:-len(suffix)]
1892
1881
 
1998
1987
        """
1999
1988
        sio = StringIO()
2000
1989
        inv = Inventory()
2001
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1990
        xml5.serializer_v5.write_inventory(inv, sio)
2002
1991
        sio.seek(0)
2003
1992
        control_files.put('inventory', sio)
2004
1993
 
2029
2018
                         _internal=True,
2030
2019
                         _format=self,
2031
2020
                         _bzrdir=a_bzrdir)
2032
 
        wt.set_root_id(inv.root.file_id)
2033
2021
        basis_tree = branch.repository.revision_tree(revision)
 
2022
        if basis_tree.inventory.root is not None:
 
2023
            wt.set_root_id(basis_tree.inventory.root.file_id)
 
2024
        # set the parent list and cache the basis tree.
2034
2025
        wt.set_parent_trees([(revision, basis_tree)])
2035
 
        build_tree(basis_tree, wt)
 
2026
        transform.build_tree(basis_tree, wt)
2036
2027
        return wt
2037
2028
 
2038
2029
    def __init__(self):
2100
2091
        branch = a_bzrdir.open_branch()
2101
2092
        if revision_id is None:
2102
2093
            revision_id = branch.last_revision()
 
2094
        # WorkingTree3 can handle an inventory which has a unique root id.
 
2095
        # as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
 
2096
        # those trees. And because there isn't a format bump inbetween, we
 
2097
        # are maintaining compatibility with older clients.
 
2098
        # inv = Inventory(root_id=gen_root_id())
2103
2099
        inv = Inventory()
2104
2100
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2105
2101
                         branch,
2110
2106
                         _control_files=control_files)
2111
2107
        wt.lock_tree_write()
2112
2108
        try:
2113
 
            wt.set_root_id(inv.root.file_id)
2114
2109
            basis_tree = branch.repository.revision_tree(revision_id)
2115
 
            if revision_id == bzrlib.revision.NULL_REVISION:
 
2110
            # only set an explicit root id if there is one to set.
 
2111
            if basis_tree.inventory.root is not None:
 
2112
                wt.set_root_id(basis_tree.inventory.root.file_id)
 
2113
            if revision_id == NULL_REVISION:
2116
2114
                wt.set_parent_trees([])
2117
2115
            else:
2118
2116
                wt.set_parent_trees([(revision_id, basis_tree)])
2119
 
            build_tree(basis_tree, wt)
 
2117
            transform.build_tree(basis_tree, wt)
2120
2118
        finally:
2121
 
            # unlock in this order so that the unlock-triggers-flush in
 
2119
            # Unlock in this order so that the unlock-triggers-flush in
2122
2120
            # WorkingTree is given a chance to fire.
2123
2121
            control_files.unlock()
2124
2122
            wt.unlock()