/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

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""WorkingTree object and friends.
 
18
 
 
19
A WorkingTree represents the editable working copy of a branch.
 
20
Operations which represent the WorkingTree are also done here, 
 
21
such as renaming or adding files.  The WorkingTree has an inventory 
 
22
which is updated by these operations.  A commit produces a 
 
23
new revision based on the workingtree and its inventory.
 
24
 
 
25
At the moment every WorkingTree has its own branch.  Remote
 
26
WorkingTrees aren't supported.
 
27
 
 
28
To get a WorkingTree, call bzrdir.open_workingtree() or
 
29
WorkingTree.open(dir).
 
30
"""
 
31
 
 
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
34
 
 
35
# TODO: Give the workingtree sole responsibility for the working inventory;
 
36
# remove the variable and references to it from the branch.  This may require
 
37
# updating the commit code so as to update the inventory within the working
 
38
# copy, and making sure there's only one WorkingTree for any directory on disk.
 
39
# At the moment they may alias the inventory and have old copies of it in
 
40
# memory.  (Now done? -- mbp 20060309)
 
41
 
 
42
from binascii import hexlify
 
43
import collections
 
44
from copy import deepcopy
 
45
from cStringIO import StringIO
 
46
import errno
 
47
import fnmatch
 
48
import os
 
49
import re
 
50
import stat
 
51
from time import time
 
52
 
 
53
from bzrlib.atomicfile import AtomicFile
 
54
from bzrlib.branch import (Branch,
 
55
                           quotefn)
 
56
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
57
import bzrlib.bzrdir as bzrdir
 
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
59
import bzrlib.errors as errors
 
60
from bzrlib.errors import (BzrCheckError,
 
61
                           BzrError,
 
62
                           ConflictFormatError,
 
63
                           DivergedBranches,
 
64
                           WeaveRevisionNotPresent,
 
65
                           NotBranchError,
 
66
                           NoSuchFile,
 
67
                           NotVersionedError,
 
68
                           MergeModifiedFormatError,
 
69
                           UnsupportedOperation,
 
70
                           )
 
71
from bzrlib.inventory import InventoryEntry, Inventory
 
72
from bzrlib.lockable_files import LockableFiles, TransportLock
 
73
from bzrlib.lockdir import LockDir
 
74
from bzrlib.merge import merge_inner, transform_tree
 
75
from bzrlib.osutils import (
 
76
                            abspath,
 
77
                            compact_date,
 
78
                            file_kind,
 
79
                            isdir,
 
80
                            getcwd,
 
81
                            pathjoin,
 
82
                            pumpfile,
 
83
                            safe_unicode,
 
84
                            splitpath,
 
85
                            rand_chars,
 
86
                            normpath,
 
87
                            realpath,
 
88
                            relpath,
 
89
                            rename,
 
90
                            supports_executable,
 
91
                            )
 
92
from bzrlib.progress import DummyProgress, ProgressPhase
 
93
from bzrlib.revision import NULL_REVISION
 
94
from bzrlib.rio import RioReader, rio_file, Stanza
 
95
from bzrlib.symbol_versioning import *
 
96
from bzrlib.textui import show_status
 
97
import bzrlib.tree
 
98
from bzrlib.transform import build_tree
 
99
from bzrlib.trace import mutter, note
 
100
from bzrlib.transport import get_transport
 
101
from bzrlib.transport.local import LocalTransport
 
102
import bzrlib.urlutils as urlutils
 
103
import bzrlib.ui
 
104
import bzrlib.xml5
 
105
 
 
106
 
 
107
# the regex here does the following:
 
108
# 1) remove any weird characters; we don't escape them but rather
 
109
# just pull them out
 
110
 # 2) match leading '.'s to make it not hidden
 
111
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
 
112
_gen_id_suffix = None
 
113
_gen_id_serial = 0
 
114
 
 
115
 
 
116
def _next_id_suffix():
 
117
    """Create a new file id suffix that is reasonably unique.
 
118
    
 
119
    On the first call we combine the current time with 64 bits of randomness
 
120
    to give a highly probably globally unique number. Then each call in the same
 
121
    process adds 1 to a serial number we append to that unique value.
 
122
    """
 
123
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
 
124
    # than having to move the id randomness out of the inner loop like this.
 
125
    # XXX TODO: for the global randomness this uses we should add the thread-id
 
126
    # before the serial #.
 
127
    global _gen_id_suffix, _gen_id_serial
 
128
    if _gen_id_suffix is None:
 
129
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
 
130
    _gen_id_serial += 1
 
131
    return _gen_id_suffix + str(_gen_id_serial)
 
132
 
 
133
 
 
134
def gen_file_id(name):
 
135
    """Return new file id for the basename 'name'.
 
136
 
 
137
    The uniqueness is supplied from _next_id_suffix.
 
138
    """
 
139
    # XXX TODO: squash the filename to lowercase.
 
140
    # XXX TODO: truncate the filename to something like 20 or 30 chars.
 
141
    # XXX TODO: consider what to do with ids that look like illegal filepaths
 
142
    # on platforms we support.
 
143
    return _gen_file_id_re.sub('', name) + _next_id_suffix()
 
144
 
 
145
 
 
146
def gen_root_id():
 
147
    """Return a new tree-root file id."""
 
148
    return gen_file_id('TREE_ROOT')
 
149
 
 
150
 
 
151
class TreeEntry(object):
 
152
    """An entry that implements the minium interface used by commands.
 
153
 
 
154
    This needs further inspection, it may be better to have 
 
155
    InventoryEntries without ids - though that seems wrong. For now,
 
156
    this is a parallel hierarchy to InventoryEntry, and needs to become
 
157
    one of several things: decorates to that hierarchy, children of, or
 
158
    parents of it.
 
159
    Another note is that these objects are currently only used when there is
 
160
    no InventoryEntry available - i.e. for unversioned objects.
 
161
    Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
 
162
    """
 
163
 
 
164
    def __eq__(self, other):
 
165
        # yes, this us ugly, TODO: best practice __eq__ style.
 
166
        return (isinstance(other, TreeEntry)
 
167
                and other.__class__ == self.__class__)
 
168
 
 
169
    def kind_character(self):
 
170
        return "???"
 
171
 
 
172
 
 
173
class TreeDirectory(TreeEntry):
 
174
    """See TreeEntry. This is a directory in a working tree."""
 
175
 
 
176
    def __eq__(self, other):
 
177
        return (isinstance(other, TreeDirectory)
 
178
                and other.__class__ == self.__class__)
 
179
 
 
180
    def kind_character(self):
 
181
        return "/"
 
182
 
 
183
 
 
184
class TreeFile(TreeEntry):
 
185
    """See TreeEntry. This is a regular file in a working tree."""
 
186
 
 
187
    def __eq__(self, other):
 
188
        return (isinstance(other, TreeFile)
 
189
                and other.__class__ == self.__class__)
 
190
 
 
191
    def kind_character(self):
 
192
        return ''
 
193
 
 
194
 
 
195
class TreeLink(TreeEntry):
 
196
    """See TreeEntry. This is a symlink in a working tree."""
 
197
 
 
198
    def __eq__(self, other):
 
199
        return (isinstance(other, TreeLink)
 
200
                and other.__class__ == self.__class__)
 
201
 
 
202
    def kind_character(self):
 
203
        return ''
 
204
 
 
205
 
 
206
class WorkingTree(bzrlib.tree.Tree):
 
207
    """Working copy tree.
 
208
 
 
209
    The inventory is held in the `Branch` working-inventory, and the
 
210
    files are in a directory on disk.
 
211
 
 
212
    It is possible for a `WorkingTree` to have a filename which is
 
213
    not listed in the Inventory and vice versa.
 
214
    """
 
215
 
 
216
    def __init__(self, basedir='.',
 
217
                 branch=DEPRECATED_PARAMETER,
 
218
                 _inventory=None,
 
219
                 _control_files=None,
 
220
                 _internal=False,
 
221
                 _format=None,
 
222
                 _bzrdir=None):
 
223
        """Construct a WorkingTree for basedir.
 
224
 
 
225
        If the branch is not supplied, it is opened automatically.
 
226
        If the branch is supplied, it must be the branch for this basedir.
 
227
        (branch.base is not cross checked, because for remote branches that
 
228
        would be meaningless).
 
229
        """
 
230
        self._format = _format
 
231
        self.bzrdir = _bzrdir
 
232
        if not _internal:
 
233
            # not created via open etc.
 
234
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
235
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
236
                 DeprecationWarning,
 
237
                 stacklevel=2)
 
238
            wt = WorkingTree.open(basedir)
 
239
            self._branch = wt.branch
 
240
            self.basedir = wt.basedir
 
241
            self._control_files = wt._control_files
 
242
            self._hashcache = wt._hashcache
 
243
            self._set_inventory(wt._inventory)
 
244
            self._format = wt._format
 
245
            self.bzrdir = wt.bzrdir
 
246
        from bzrlib.hashcache import HashCache
 
247
        from bzrlib.trace import note, mutter
 
248
        assert isinstance(basedir, basestring), \
 
249
            "base directory %r is not a string" % basedir
 
250
        basedir = safe_unicode(basedir)
 
251
        mutter("opening working tree %r", basedir)
 
252
        if deprecated_passed(branch):
 
253
            if not _internal:
 
254
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
255
                     " Please use bzrdir.open_workingtree() or"
 
256
                     " WorkingTree.open().",
 
257
                     DeprecationWarning,
 
258
                     stacklevel=2
 
259
                     )
 
260
            self._branch = branch
 
261
        else:
 
262
            self._branch = self.bzrdir.open_branch()
 
263
        assert isinstance(self.branch, Branch), \
 
264
            "branch %r is not a Branch" % self.branch
 
265
        self.basedir = realpath(basedir)
 
266
        # if branch is at our basedir and is a format 6 or less
 
267
        if isinstance(self._format, WorkingTreeFormat2):
 
268
            # share control object
 
269
            self._control_files = self.branch.control_files
 
270
        else:
 
271
            # only ready for format 3
 
272
            assert isinstance(self._format, WorkingTreeFormat3)
 
273
            assert isinstance(_control_files, LockableFiles), \
 
274
                    "_control_files must be a LockableFiles, not %r" \
 
275
                    % _control_files
 
276
            self._control_files = _control_files
 
277
        # update the whole cache up front and write to disk if anything changed;
 
278
        # in the future we might want to do this more selectively
 
279
        # two possible ways offer themselves : in self._unlock, write the cache
 
280
        # if needed, or, when the cache sees a change, append it to the hash
 
281
        # cache file, and have the parser take the most recent entry for a
 
282
        # given path only.
 
283
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
 
284
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
 
285
        hc.read()
 
286
        # is this scan needed ? it makes things kinda slow.
 
287
        #hc.scan()
 
288
 
 
289
        if hc.needs_write:
 
290
            mutter("write hc")
 
291
            hc.write()
 
292
 
 
293
        if _inventory is None:
 
294
            self._set_inventory(self.read_working_inventory())
 
295
        else:
 
296
            self._set_inventory(_inventory)
 
297
 
 
298
    branch = property(
 
299
        fget=lambda self: self._branch,
 
300
        doc="""The branch this WorkingTree is connected to.
 
301
 
 
302
            This cannot be set - it is reflective of the actual disk structure
 
303
            the working tree has been constructed from.
 
304
            """)
 
305
 
 
306
    def break_lock(self):
 
307
        """Break a lock if one is present from another instance.
 
308
 
 
309
        Uses the ui factory to ask for confirmation if the lock may be from
 
310
        an active process.
 
311
 
 
312
        This will probe the repository for its lock as well.
 
313
        """
 
314
        self._control_files.break_lock()
 
315
        self.branch.break_lock()
 
316
 
 
317
    def _set_inventory(self, inv):
 
318
        self._inventory = inv
 
319
        self.path2id = self._inventory.path2id
 
320
 
 
321
    def is_control_filename(self, filename):
 
322
        """True if filename is the name of a control file in this tree.
 
323
        
 
324
        :param filename: A filename within the tree. This is a relative path
 
325
        from the root of this tree.
 
326
 
 
327
        This is true IF and ONLY IF the filename is part of the meta data
 
328
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
329
        on disk will not be a control file for this tree.
 
330
        """
 
331
        return self.bzrdir.is_control_filename(filename)
 
332
 
 
333
    @staticmethod
 
334
    def open(path=None, _unsupported=False):
 
335
        """Open an existing working tree at path.
 
336
 
 
337
        """
 
338
        if path is None:
 
339
            path = os.path.getcwdu()
 
340
        control = bzrdir.BzrDir.open(path, _unsupported)
 
341
        return control.open_workingtree(_unsupported)
 
342
        
 
343
    @staticmethod
 
344
    def open_containing(path=None):
 
345
        """Open an existing working tree which has its root about path.
 
346
        
 
347
        This probes for a working tree at path and searches upwards from there.
 
348
 
 
349
        Basically we keep looking up until we find the control directory or
 
350
        run into /.  If there isn't one, raises NotBranchError.
 
351
        TODO: give this a new exception.
 
352
        If there is one, it is returned, along with the unused portion of path.
 
353
 
 
354
        :return: The WorkingTree that contains 'path', and the rest of path
 
355
        """
 
356
        if path is None:
 
357
            path = os.getcwdu()
 
358
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
359
 
 
360
        return control.open_workingtree(), relpath
 
361
 
 
362
    @staticmethod
 
363
    def open_downlevel(path=None):
 
364
        """Open an unsupported working tree.
 
365
 
 
366
        Only intended for advanced situations like upgrading part of a bzrdir.
 
367
        """
 
368
        return WorkingTree.open(path, _unsupported=True)
 
369
 
 
370
    def __iter__(self):
 
371
        """Iterate through file_ids for this tree.
 
372
 
 
373
        file_ids are in a WorkingTree if they are in the working inventory
 
374
        and the working file exists.
 
375
        """
 
376
        inv = self._inventory
 
377
        for path, ie in inv.iter_entries():
 
378
            if bzrlib.osutils.lexists(self.abspath(path)):
 
379
                yield ie.file_id
 
380
 
 
381
    def __repr__(self):
 
382
        return "<%s of %s>" % (self.__class__.__name__,
 
383
                               getattr(self, 'basedir', None))
 
384
 
 
385
    def abspath(self, filename):
 
386
        return pathjoin(self.basedir, filename)
 
387
    
 
388
    def basis_tree(self):
 
389
        """Return RevisionTree for the current last revision."""
 
390
        revision_id = self.last_revision()
 
391
        if revision_id is not None:
 
392
            try:
 
393
                xml = self.read_basis_inventory()
 
394
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
395
            except NoSuchFile:
 
396
                inv = None
 
397
            if inv is not None and inv.revision_id == revision_id:
 
398
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
399
                                                revision_id)
 
400
        # FIXME? RBC 20060403 should we cache the inventory here ?
 
401
        return self.branch.repository.revision_tree(revision_id)
 
402
 
 
403
    @staticmethod
 
404
    @deprecated_method(zero_eight)
 
405
    def create(branch, directory):
 
406
        """Create a workingtree for branch at directory.
 
407
 
 
408
        If existing_directory already exists it must have a .bzr directory.
 
409
        If it does not exist, it will be created.
 
410
 
 
411
        This returns a new WorkingTree object for the new checkout.
 
412
 
 
413
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
414
        should accept an optional revisionid to checkout [and reject this if
 
415
        checking out into the same dir as a pre-checkout-aware branch format.]
 
416
 
 
417
        XXX: When BzrDir is present, these should be created through that 
 
418
        interface instead.
 
419
        """
 
420
        warn('delete WorkingTree.create', stacklevel=3)
 
421
        transport = get_transport(directory)
 
422
        if branch.bzrdir.root_transport.base == transport.base:
 
423
            # same dir 
 
424
            return branch.bzrdir.create_workingtree()
 
425
        # different directory, 
 
426
        # create a branch reference
 
427
        # and now a working tree.
 
428
        raise NotImplementedError
 
429
 
 
430
    @staticmethod
 
431
    @deprecated_method(zero_eight)
 
432
    def create_standalone(directory):
 
433
        """Create a checkout and a branch and a repo at directory.
 
434
 
 
435
        Directory must exist and be empty.
 
436
 
 
437
        please use BzrDir.create_standalone_workingtree
 
438
        """
 
439
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
 
440
 
 
441
    def relpath(self, path):
 
442
        """Return the local path portion from a given path.
 
443
        
 
444
        The path may be absolute or relative. If its a relative path it is 
 
445
        interpreted relative to the python current working directory.
 
446
        """
 
447
        return relpath(self.basedir, path)
 
448
 
 
449
    def has_filename(self, filename):
 
450
        return bzrlib.osutils.lexists(self.abspath(filename))
 
451
 
 
452
    def get_file(self, file_id):
 
453
        return self.get_file_byname(self.id2path(file_id))
 
454
 
 
455
    def get_file_byname(self, filename):
 
456
        return file(self.abspath(filename), 'rb')
 
457
 
 
458
    def get_root_id(self):
 
459
        """Return the id of this trees root"""
 
460
        inv = self.read_working_inventory()
 
461
        return inv.root.file_id
 
462
        
 
463
    def _get_store_filename(self, file_id):
 
464
        ## XXX: badly named; this is not in the store at all
 
465
        return self.abspath(self.id2path(file_id))
 
466
 
 
467
    @needs_read_lock
 
468
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
469
        """Duplicate this working tree into to_bzr, including all state.
 
470
        
 
471
        Specifically modified files are kept as modified, but
 
472
        ignored and unknown files are discarded.
 
473
 
 
474
        If you want to make a new line of development, see bzrdir.sprout()
 
475
 
 
476
        revision
 
477
            If not None, the cloned tree will have its last revision set to 
 
478
            revision, and and difference between the source trees last revision
 
479
            and this one merged in.
 
480
 
 
481
        basis
 
482
            If not None, a closer copy of a tree which may have some files in
 
483
            common, and which file content should be preferentially copied from.
 
484
        """
 
485
        # assumes the target bzr dir format is compatible.
 
486
        result = self._format.initialize(to_bzrdir)
 
487
        self.copy_content_into(result, revision_id)
 
488
        return result
 
489
 
 
490
    @needs_read_lock
 
491
    def copy_content_into(self, tree, revision_id=None):
 
492
        """Copy the current content and user files of this tree into tree."""
 
493
        tree.set_root_id(self.get_root_id())
 
494
        if revision_id is None:
 
495
            transform_tree(tree, self)
 
496
        else:
 
497
            # TODO now merge from tree.last_revision to revision
 
498
            transform_tree(tree, self)
 
499
            tree.set_last_revision(revision_id)
 
500
 
 
501
    @needs_write_lock
 
502
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
503
        # avoid circular imports
 
504
        from bzrlib.commit import Commit
 
505
        if revprops is None:
 
506
            revprops = {}
 
507
        if not 'branch-nick' in revprops:
 
508
            revprops['branch-nick'] = self.branch.nick
 
509
        # args for wt.commit start at message from the Commit.commit method,
 
510
        # but with branch a kwarg now, passing in args as is results in the
 
511
        #message being used for the branch
 
512
        args = (DEPRECATED_PARAMETER, message, ) + args
 
513
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
 
514
        self._set_inventory(self.read_working_inventory())
 
515
 
 
516
    def id2abspath(self, file_id):
 
517
        return self.abspath(self.id2path(file_id))
 
518
 
 
519
    def has_id(self, file_id):
 
520
        # files that have been deleted are excluded
 
521
        inv = self._inventory
 
522
        if not inv.has_id(file_id):
 
523
            return False
 
524
        path = inv.id2path(file_id)
 
525
        return bzrlib.osutils.lexists(self.abspath(path))
 
526
 
 
527
    def has_or_had_id(self, file_id):
 
528
        if file_id == self.inventory.root.file_id:
 
529
            return True
 
530
        return self.inventory.has_id(file_id)
 
531
 
 
532
    __contains__ = has_id
 
533
 
 
534
    def get_file_size(self, file_id):
 
535
        return os.path.getsize(self.id2abspath(file_id))
 
536
 
 
537
    @needs_read_lock
 
538
    def get_file_sha1(self, file_id, path=None):
 
539
        if not path:
 
540
            path = self._inventory.id2path(file_id)
 
541
        return self._hashcache.get_sha1(path)
 
542
 
 
543
    def get_file_mtime(self, file_id, path=None):
 
544
        if not path:
 
545
            path = self._inventory.id2path(file_id)
 
546
        return os.lstat(self.abspath(path)).st_mtime
 
547
 
 
548
    if not supports_executable():
 
549
        def is_executable(self, file_id, path=None):
 
550
            return self._inventory[file_id].executable
 
551
    else:
 
552
        def is_executable(self, file_id, path=None):
 
553
            if not path:
 
554
                path = self._inventory.id2path(file_id)
 
555
            mode = os.lstat(self.abspath(path)).st_mode
 
556
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
557
 
 
558
    @needs_write_lock
 
559
    def add(self, files, ids=None):
 
560
        """Make files versioned.
 
561
 
 
562
        Note that the command line normally calls smart_add instead,
 
563
        which can automatically recurse.
 
564
 
 
565
        This adds the files to the inventory, so that they will be
 
566
        recorded by the next commit.
 
567
 
 
568
        files
 
569
            List of paths to add, relative to the base of the tree.
 
570
 
 
571
        ids
 
572
            If set, use these instead of automatically generated ids.
 
573
            Must be the same length as the list of files, but may
 
574
            contain None for ids that are to be autogenerated.
 
575
 
 
576
        TODO: Perhaps have an option to add the ids even if the files do
 
577
              not (yet) exist.
 
578
 
 
579
        TODO: Perhaps callback with the ids and paths as they're added.
 
580
        """
 
581
        # TODO: Re-adding a file that is removed in the working copy
 
582
        # should probably put it back with the previous ID.
 
583
        if isinstance(files, basestring):
 
584
            assert(ids is None or isinstance(ids, basestring))
 
585
            files = [files]
 
586
            if ids is not None:
 
587
                ids = [ids]
 
588
 
 
589
        if ids is None:
 
590
            ids = [None] * len(files)
 
591
        else:
 
592
            assert(len(ids) == len(files))
 
593
 
 
594
        inv = self.read_working_inventory()
 
595
        for f,file_id in zip(files, ids):
 
596
            if self.is_control_filename(f):
 
597
                raise BzrError("cannot add control file %s" % quotefn(f))
 
598
 
 
599
            fp = splitpath(f)
 
600
 
 
601
            if len(fp) == 0:
 
602
                raise BzrError("cannot add top-level %r" % f)
 
603
 
 
604
            fullpath = normpath(self.abspath(f))
 
605
 
 
606
            try:
 
607
                kind = file_kind(fullpath)
 
608
            except OSError, e:
 
609
                if e.errno == errno.ENOENT:
 
610
                    raise NoSuchFile(fullpath)
 
611
                # maybe something better?
 
612
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
613
 
 
614
            if not InventoryEntry.versionable_kind(kind):
 
615
                raise BzrError('cannot add: not a versionable file ('
 
616
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
617
 
 
618
            if file_id is None:
 
619
                inv.add_path(f, kind=kind)
 
620
            else:
 
621
                inv.add_path(f, kind=kind, file_id=file_id)
 
622
 
 
623
        self._write_inventory(inv)
 
624
 
 
625
    @needs_write_lock
 
626
    def add_pending_merge(self, *revision_ids):
 
627
        # TODO: Perhaps should check at this point that the
 
628
        # history of the revision is actually present?
 
629
        p = self.pending_merges()
 
630
        updated = False
 
631
        for rev_id in revision_ids:
 
632
            if rev_id in p:
 
633
                continue
 
634
            p.append(rev_id)
 
635
            updated = True
 
636
        if updated:
 
637
            self.set_pending_merges(p)
 
638
 
 
639
    @needs_read_lock
 
640
    def pending_merges(self):
 
641
        """Return a list of pending merges.
 
642
 
 
643
        These are revisions that have been merged into the working
 
644
        directory but not yet committed.
 
645
        """
 
646
        try:
 
647
            merges_file = self._control_files.get_utf8('pending-merges')
 
648
        except OSError, e:
 
649
            if e.errno != errno.ENOENT:
 
650
                raise
 
651
            return []
 
652
        p = []
 
653
        for l in merges_file.readlines():
 
654
            p.append(l.rstrip('\n'))
 
655
        return p
 
656
 
 
657
    @needs_write_lock
 
658
    def set_pending_merges(self, rev_list):
 
659
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
 
660
 
 
661
    @needs_write_lock
 
662
    def set_merge_modified(self, modified_hashes):
 
663
        def iter_stanzas():
 
664
            for file_id, hash in modified_hashes.iteritems():
 
665
                yield Stanza(file_id=file_id, hash=hash)
 
666
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
 
667
 
 
668
    @needs_write_lock
 
669
    def _put_rio(self, filename, stanzas, header):
 
670
        my_file = rio_file(stanzas, header)
 
671
        self._control_files.put(filename, my_file)
 
672
 
 
673
    @needs_read_lock
 
674
    def merge_modified(self):
 
675
        try:
 
676
            hashfile = self._control_files.get('merge-hashes')
 
677
        except NoSuchFile:
 
678
            return {}
 
679
        merge_hashes = {}
 
680
        try:
 
681
            if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
 
682
                raise MergeModifiedFormatError()
 
683
        except StopIteration:
 
684
            raise MergeModifiedFormatError()
 
685
        for s in RioReader(hashfile):
 
686
            file_id = s.get("file_id")
 
687
            if file_id not in self.inventory:
 
688
                continue
 
689
            hash = s.get("hash")
 
690
            if hash == self.get_file_sha1(file_id):
 
691
                merge_hashes[file_id] = hash
 
692
        return merge_hashes
 
693
 
 
694
    def get_symlink_target(self, file_id):
 
695
        return os.readlink(self.id2abspath(file_id))
 
696
 
 
697
    def file_class(self, filename):
 
698
        if self.path2id(filename):
 
699
            return 'V'
 
700
        elif self.is_ignored(filename):
 
701
            return 'I'
 
702
        else:
 
703
            return '?'
 
704
 
 
705
    def list_files(self, include_root=False):
 
706
        """Recursively list all files as (path, class, kind, id, entry).
 
707
 
 
708
        Lists, but does not descend into unversioned directories.
 
709
 
 
710
        This does not include files that have been deleted in this
 
711
        tree.
 
712
 
 
713
        Skips the control directory.
 
714
        """
 
715
        inv = self._inventory
 
716
        # Convert these into local objects to save lookup times
 
717
        pathjoin = bzrlib.osutils.pathjoin
 
718
        file_kind = bzrlib.osutils.file_kind
 
719
 
 
720
        # transport.base ends in a slash, we want the piece
 
721
        # between the last two slashes
 
722
        transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
 
723
 
 
724
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
 
725
 
 
726
        # directory file_id, relative path, absolute path, reverse sorted children
 
727
        children = os.listdir(self.basedir)
 
728
        children.sort()
 
729
        # jam 20060527 The kernel sized tree seems equivalent whether we 
 
730
        # use a deque and popleft to keep them sorted, or if we use a plain
 
731
        # list and just reverse() them.
 
732
        children = collections.deque(children)
 
733
        stack = [(inv.root.file_id, u'', self.basedir, children)]
 
734
        if include_root:
 
735
            yield u'', 'V', 'directory', inv.root.file_id, inv.root
 
736
        while stack:
 
737
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
 
738
 
 
739
            while children:
 
740
                f = children.popleft()
 
741
                ## TODO: If we find a subdirectory with its own .bzr
 
742
                ## directory, then that is a separate tree and we
 
743
                ## should exclude it.
 
744
 
 
745
                # the bzrdir for this tree
 
746
                if transport_base_dir == f:
 
747
                    continue
 
748
 
 
749
                # we know that from_dir_relpath and from_dir_abspath never end in a slash
 
750
                # and 'f' doesn't begin with one, we can do a string op, rather
 
751
                # than the checks of pathjoin(), all relative paths will have an extra slash
 
752
                # at the beginning
 
753
                fp = from_dir_relpath + '/' + f
 
754
 
 
755
                # absolute path
 
756
                fap = from_dir_abspath + '/' + f
 
757
                
 
758
                f_ie = inv.get_child(from_dir_id, f)
 
759
                if f_ie:
 
760
                    c = 'V'
 
761
                elif self.is_ignored(fp[1:]):
 
762
                    c = 'I'
 
763
                else:
 
764
                    c = '?'
 
765
 
 
766
                fk = file_kind(fap)
 
767
 
 
768
                if f_ie:
 
769
                    if f_ie.kind != fk:
 
770
                        raise BzrCheckError("file %r entered as kind %r id %r, "
 
771
                                            "now of kind %r"
 
772
                                            % (fap, f_ie.kind, f_ie.file_id, fk))
 
773
 
 
774
                # make a last minute entry
 
775
                if f_ie:
 
776
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
 
777
                else:
 
778
                    try:
 
779
                        yield fp[1:], c, fk, None, fk_entries[fk]()
 
780
                    except KeyError:
 
781
                        yield fp[1:], c, fk, None, TreeEntry()
 
782
                    continue
 
783
                
 
784
                if fk != 'directory':
 
785
                    continue
 
786
 
 
787
                # But do this child first
 
788
                new_children = os.listdir(fap)
 
789
                new_children.sort()
 
790
                new_children = collections.deque(new_children)
 
791
                stack.append((f_ie.file_id, fp, fap, new_children))
 
792
                # Break out of inner loop, so that we start outer loop with child
 
793
                break
 
794
            else:
 
795
                # if we finished all children, pop it off the stack
 
796
                stack.pop()
 
797
 
 
798
 
 
799
    @needs_write_lock
 
800
    def move(self, from_paths, to_name):
 
801
        """Rename files.
 
802
 
 
803
        to_name must exist in the inventory.
 
804
 
 
805
        If to_name exists and is a directory, the files are moved into
 
806
        it, keeping their old names.  
 
807
 
 
808
        Note that to_name is only the last component of the new name;
 
809
        this doesn't change the directory.
 
810
 
 
811
        This returns a list of (from_path, to_path) pairs for each
 
812
        entry that is moved.
 
813
        """
 
814
        result = []
 
815
        ## TODO: Option to move IDs only
 
816
        assert not isinstance(from_paths, basestring)
 
817
        inv = self.inventory
 
818
        to_abs = self.abspath(to_name)
 
819
        if not isdir(to_abs):
 
820
            raise BzrError("destination %r is not a directory" % to_abs)
 
821
        if not self.has_filename(to_name):
 
822
            raise BzrError("destination %r not in working directory" % to_abs)
 
823
        to_dir_id = inv.path2id(to_name)
 
824
        if to_dir_id == None and to_name != '':
 
825
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
826
        to_dir_ie = inv[to_dir_id]
 
827
        if to_dir_ie.kind != 'directory':
 
828
            raise BzrError("destination %r is not a directory" % to_abs)
 
829
 
 
830
        to_idpath = inv.get_idpath(to_dir_id)
 
831
 
 
832
        for f in from_paths:
 
833
            if not self.has_filename(f):
 
834
                raise BzrError("%r does not exist in working tree" % f)
 
835
            f_id = inv.path2id(f)
 
836
            if f_id == None:
 
837
                raise BzrError("%r is not versioned" % f)
 
838
            name_tail = splitpath(f)[-1]
 
839
            dest_path = pathjoin(to_name, name_tail)
 
840
            if self.has_filename(dest_path):
 
841
                raise BzrError("destination %r already exists" % dest_path)
 
842
            if f_id in to_idpath:
 
843
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
844
 
 
845
        # OK, so there's a race here, it's possible that someone will
 
846
        # create a file in this interval and then the rename might be
 
847
        # left half-done.  But we should have caught most problems.
 
848
        orig_inv = deepcopy(self.inventory)
 
849
        try:
 
850
            for f in from_paths:
 
851
                name_tail = splitpath(f)[-1]
 
852
                dest_path = pathjoin(to_name, name_tail)
 
853
                result.append((f, dest_path))
 
854
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
855
                try:
 
856
                    rename(self.abspath(f), self.abspath(dest_path))
 
857
                except OSError, e:
 
858
                    raise BzrError("failed to rename %r to %r: %s" %
 
859
                                   (f, dest_path, e[1]),
 
860
                            ["rename rolled back"])
 
861
        except:
 
862
            # restore the inventory on error
 
863
            self._set_inventory(orig_inv)
 
864
            raise
 
865
        self._write_inventory(inv)
 
866
        return result
 
867
 
 
868
    @needs_write_lock
 
869
    def rename_one(self, from_rel, to_rel):
 
870
        """Rename one file.
 
871
 
 
872
        This can change the directory or the filename or both.
 
873
        """
 
874
        inv = self.inventory
 
875
        if not self.has_filename(from_rel):
 
876
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
877
        if self.has_filename(to_rel):
 
878
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
879
 
 
880
        file_id = inv.path2id(from_rel)
 
881
        if file_id == None:
 
882
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
883
 
 
884
        entry = inv[file_id]
 
885
        from_parent = entry.parent_id
 
886
        from_name = entry.name
 
887
        
 
888
        if inv.path2id(to_rel):
 
889
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
890
 
 
891
        to_dir, to_tail = os.path.split(to_rel)
 
892
        to_dir_id = inv.path2id(to_dir)
 
893
        if to_dir_id == None and to_dir != '':
 
894
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
895
 
 
896
        mutter("rename_one:")
 
897
        mutter("  file_id    {%s}" % file_id)
 
898
        mutter("  from_rel   %r" % from_rel)
 
899
        mutter("  to_rel     %r" % to_rel)
 
900
        mutter("  to_dir     %r" % to_dir)
 
901
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
902
 
 
903
        inv.rename(file_id, to_dir_id, to_tail)
 
904
 
 
905
        from_abs = self.abspath(from_rel)
 
906
        to_abs = self.abspath(to_rel)
 
907
        try:
 
908
            rename(from_abs, to_abs)
 
909
        except OSError, e:
 
910
            inv.rename(file_id, from_parent, from_name)
 
911
            raise BzrError("failed to rename %r to %r: %s"
 
912
                    % (from_abs, to_abs, e[1]),
 
913
                    ["rename rolled back"])
 
914
        self._write_inventory(inv)
 
915
 
 
916
    @needs_read_lock
 
917
    def unknowns(self):
 
918
        """Return all unknown files.
 
919
 
 
920
        These are files in the working directory that are not versioned or
 
921
        control files or ignored.
 
922
        
 
923
        >>> from bzrlib.bzrdir import ScratchDir
 
924
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
925
        >>> b = d.open_branch()
 
926
        >>> tree = d.open_workingtree()
 
927
        >>> map(str, tree.unknowns())
 
928
        ['foo']
 
929
        >>> tree.add('foo')
 
930
        >>> list(b.unknowns())
 
931
        []
 
932
        >>> tree.remove('foo')
 
933
        >>> list(b.unknowns())
 
934
        [u'foo']
 
935
        """
 
936
        for subp in self.extras():
 
937
            if not self.is_ignored(subp):
 
938
                yield subp
 
939
 
 
940
    @deprecated_method(zero_eight)
 
941
    def iter_conflicts(self):
 
942
        """List all files in the tree that have text or content conflicts.
 
943
        DEPRECATED.  Use conflicts instead."""
 
944
        return self._iter_conflicts()
 
945
 
 
946
    def _iter_conflicts(self):
 
947
        conflicted = set()
 
948
        for info in self.list_files():
 
949
            path = info[0]
 
950
            stem = get_conflicted_stem(path)
 
951
            if stem is None:
 
952
                continue
 
953
            if stem not in conflicted:
 
954
                conflicted.add(stem)
 
955
                yield stem
 
956
 
 
957
    @needs_write_lock
 
958
    def pull(self, source, overwrite=False, stop_revision=None):
 
959
        top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
960
        source.lock_read()
 
961
        try:
 
962
            pp = ProgressPhase("Pull phase", 2, top_pb)
 
963
            pp.next_phase()
 
964
            old_revision_history = self.branch.revision_history()
 
965
            basis_tree = self.basis_tree()
 
966
            count = self.branch.pull(source, overwrite, stop_revision)
 
967
            new_revision_history = self.branch.revision_history()
 
968
            if new_revision_history != old_revision_history:
 
969
                pp.next_phase()
 
970
                if len(old_revision_history):
 
971
                    other_revision = old_revision_history[-1]
 
972
                else:
 
973
                    other_revision = None
 
974
                repository = self.branch.repository
 
975
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
976
                try:
 
977
                    branch_basis = self.branch.basis_tree() 
 
978
                    merge_inner(self.branch, branch_basis, basis_tree,
 
979
                                this_tree=self, pb=pb)
 
980
                    if (basis_tree.inventory.root is None and
 
981
                        branch_basis.inventory.root is not None):
 
982
                        self.set_root_id(branch_basis.inventory.root.file_id)
 
983
                finally:
 
984
                    pb.finished()
 
985
                self.set_last_revision(self.branch.last_revision())
 
986
            return count
 
987
        finally:
 
988
            source.unlock()
 
989
            top_pb.finished()
 
990
 
 
991
    def extras(self):
 
992
        """Yield all unknown files in this WorkingTree.
 
993
 
 
994
        If there are any unknown directories then only the directory is
 
995
        returned, not all its children.  But if there are unknown files
 
996
        under a versioned subdirectory, they are returned.
 
997
 
 
998
        Currently returned depth-first, sorted by name within directories.
 
999
        """
 
1000
        ## TODO: Work from given directory downwards
 
1001
        for path, dir_entry in self.inventory.directories():
 
1002
            mutter("search for unknowns in %r", path)
 
1003
            dirabs = self.abspath(path)
 
1004
            if not isdir(dirabs):
 
1005
                # e.g. directory deleted
 
1006
                continue
 
1007
 
 
1008
            fl = []
 
1009
            for subf in os.listdir(dirabs):
 
1010
                if (subf != '.bzr'
 
1011
                    and (subf not in dir_entry.children)):
 
1012
                    fl.append(subf)
 
1013
            
 
1014
            fl.sort()
 
1015
            for subf in fl:
 
1016
                subp = pathjoin(path, subf)
 
1017
                yield subp
 
1018
 
 
1019
    def _translate_ignore_rule(self, rule):
 
1020
        """Translate a single ignore rule to a regex.
 
1021
 
 
1022
        There are two types of ignore rules.  Those that do not contain a / are
 
1023
        matched against the tail of the filename (that is, they do not care
 
1024
        what directory the file is in.)  Rules which do contain a slash must
 
1025
        match the entire path.  As a special case, './' at the start of the
 
1026
        string counts as a slash in the string but is removed before matching
 
1027
        (e.g. ./foo.c, ./src/foo.c)
 
1028
 
 
1029
        :return: The translated regex.
 
1030
        """
 
1031
        if rule[:2] in ('./', '.\\'):
 
1032
            # rootdir rule
 
1033
            result = fnmatch.translate(rule[2:])
 
1034
        elif '/' in rule or '\\' in rule:
 
1035
            # path prefix 
 
1036
            result = fnmatch.translate(rule)
 
1037
        else:
 
1038
            # default rule style.
 
1039
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
 
1040
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
 
1041
        return "(" + result + ")"
 
1042
 
 
1043
    def _combine_ignore_rules(self, rules):
 
1044
        """Combine a list of ignore rules into a single regex object.
 
1045
 
 
1046
        Each individual rule is combined with | to form a big regex, which then
 
1047
        has $ added to it to form something like ()|()|()$. The group index for
 
1048
        each subregex's outermost group is placed in a dictionary mapping back 
 
1049
        to the rule. This allows quick identification of the matching rule that
 
1050
        triggered a match.
 
1051
        :return: a list of the compiled regex and the matching-group index 
 
1052
        dictionaries. We return a list because python complains if you try to 
 
1053
        combine more than 100 regexes.
 
1054
        """
 
1055
        result = []
 
1056
        groups = {}
 
1057
        next_group = 0
 
1058
        translated_rules = []
 
1059
        for rule in rules:
 
1060
            translated_rule = self._translate_ignore_rule(rule)
 
1061
            compiled_rule = re.compile(translated_rule)
 
1062
            groups[next_group] = rule
 
1063
            next_group += compiled_rule.groups
 
1064
            translated_rules.append(translated_rule)
 
1065
            if next_group == 99:
 
1066
                result.append((re.compile("|".join(translated_rules)), groups))
 
1067
                groups = {}
 
1068
                next_group = 0
 
1069
                translated_rules = []
 
1070
        if len(translated_rules):
 
1071
            result.append((re.compile("|".join(translated_rules)), groups))
 
1072
        return result
 
1073
 
 
1074
    def ignored_files(self):
 
1075
        """Yield list of PATH, IGNORE_PATTERN"""
 
1076
        for subp in self.extras():
 
1077
            pat = self.is_ignored(subp)
 
1078
            if pat != None:
 
1079
                yield subp, pat
 
1080
 
 
1081
    def get_ignore_list(self):
 
1082
        """Return list of ignore patterns.
 
1083
 
 
1084
        Cached in the Tree object after the first call.
 
1085
        """
 
1086
        if hasattr(self, '_ignorelist'):
 
1087
            return self._ignorelist
 
1088
 
 
1089
        l = bzrlib.DEFAULT_IGNORE[:]
 
1090
        if self.has_filename(bzrlib.IGNORE_FILENAME):
 
1091
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
 
1092
            l.extend([line.rstrip("\n\r").decode('utf-8') 
 
1093
                      for line in f.readlines()])
 
1094
        self._ignorelist = l
 
1095
        self._ignore_regex = self._combine_ignore_rules(l)
 
1096
        return l
 
1097
 
 
1098
    def _get_ignore_rules_as_regex(self):
 
1099
        """Return a regex of the ignore rules and a mapping dict.
 
1100
 
 
1101
        :return: (ignore rules compiled regex, dictionary mapping rule group 
 
1102
        indices to original rule.)
 
1103
        """
 
1104
        if getattr(self, '_ignorelist', None) is None:
 
1105
            self.get_ignore_list()
 
1106
        return self._ignore_regex
 
1107
 
 
1108
    def is_ignored(self, filename):
 
1109
        r"""Check whether the filename matches an ignore pattern.
 
1110
 
 
1111
        Patterns containing '/' or '\' need to match the whole path;
 
1112
        others match against only the last component.
 
1113
 
 
1114
        If the file is ignored, returns the pattern which caused it to
 
1115
        be ignored, otherwise None.  So this can simply be used as a
 
1116
        boolean if desired."""
 
1117
 
 
1118
        # TODO: Use '**' to match directories, and other extended
 
1119
        # globbing stuff from cvs/rsync.
 
1120
 
 
1121
        # XXX: fnmatch is actually not quite what we want: it's only
 
1122
        # approximately the same as real Unix fnmatch, and doesn't
 
1123
        # treat dotfiles correctly and allows * to match /.
 
1124
        # Eventually it should be replaced with something more
 
1125
        # accurate.
 
1126
    
 
1127
        rules = self._get_ignore_rules_as_regex()
 
1128
        for regex, mapping in rules:
 
1129
            match = regex.match(filename)
 
1130
            if match is not None:
 
1131
                # one or more of the groups in mapping will have a non-None group 
 
1132
                # match.
 
1133
                groups = match.groups()
 
1134
                rules = [mapping[group] for group in 
 
1135
                    mapping if groups[group] is not None]
 
1136
                return rules[0]
 
1137
        return None
 
1138
 
 
1139
    def kind(self, file_id):
 
1140
        return file_kind(self.id2abspath(file_id))
 
1141
 
 
1142
    @needs_read_lock
 
1143
    def last_revision(self):
 
1144
        """Return the last revision id of this working tree.
 
1145
 
 
1146
        In early branch formats this was == the branch last_revision,
 
1147
        but that cannot be relied upon - for working tree operations,
 
1148
        always use tree.last_revision().
 
1149
        """
 
1150
        return self.branch.last_revision()
 
1151
 
 
1152
    def is_locked(self):
 
1153
        return self._control_files.is_locked()
 
1154
 
 
1155
    def lock_read(self):
 
1156
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1157
        self.branch.lock_read()
 
1158
        try:
 
1159
            return self._control_files.lock_read()
 
1160
        except:
 
1161
            self.branch.unlock()
 
1162
            raise
 
1163
 
 
1164
    def lock_write(self):
 
1165
        """See Branch.lock_write, and WorkingTree.unlock."""
 
1166
        self.branch.lock_write()
 
1167
        try:
 
1168
            return self._control_files.lock_write()
 
1169
        except:
 
1170
            self.branch.unlock()
 
1171
            raise
 
1172
 
 
1173
    def get_physical_lock_status(self):
 
1174
        return self._control_files.get_physical_lock_status()
 
1175
 
 
1176
    def _basis_inventory_name(self):
 
1177
        return 'basis-inventory'
 
1178
 
 
1179
    @needs_write_lock
 
1180
    def set_last_revision(self, new_revision):
 
1181
        """Change the last revision in the working tree."""
 
1182
        if self._change_last_revision(new_revision):
 
1183
            self._cache_basis_inventory(new_revision)
 
1184
 
 
1185
    def _change_last_revision(self, new_revision):
 
1186
        """Template method part of set_last_revision to perform the change.
 
1187
        
 
1188
        This is used to allow WorkingTree3 instances to not affect branch
 
1189
        when their last revision is set.
 
1190
        """
 
1191
        if new_revision is None:
 
1192
            self.branch.set_revision_history([])
 
1193
            return False
 
1194
        # current format is locked in with the branch
 
1195
        revision_history = self.branch.revision_history()
 
1196
        try:
 
1197
            position = revision_history.index(new_revision)
 
1198
        except ValueError:
 
1199
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1200
        self.branch.set_revision_history(revision_history[:position + 1])
 
1201
        return True
 
1202
 
 
1203
    def _cache_basis_inventory(self, new_revision):
 
1204
        """Cache new_revision as the basis inventory."""
 
1205
        # TODO: this should allow the ready-to-use inventory to be passed in,
 
1206
        # as commit already has that ready-to-use [while the format is the
 
1207
        # same, that is].
 
1208
        try:
 
1209
            # this double handles the inventory - unpack and repack - 
 
1210
            # but is easier to understand. We can/should put a conditional
 
1211
            # in here based on whether the inventory is in the latest format
 
1212
            # - perhaps we should repack all inventories on a repository
 
1213
            # upgrade ?
 
1214
            # the fast path is to copy the raw xml from the repository. If the
 
1215
            # xml contains 'revision_id="', then we assume the right 
 
1216
            # revision_id is set. We must check for this full string, because a
 
1217
            # root node id can legitimately look like 'revision_id' but cannot
 
1218
            # contain a '"'.
 
1219
            xml = self.branch.repository.get_inventory_xml(new_revision)
 
1220
            if not 'revision_id="' in xml.split('\n', 1)[0]:
 
1221
                inv = self.branch.repository.deserialise_inventory(
 
1222
                    new_revision, xml)
 
1223
                inv.revision_id = new_revision
 
1224
                xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1225
 
 
1226
            path = self._basis_inventory_name()
 
1227
            self._control_files.put_utf8(path, xml)
 
1228
        except WeaveRevisionNotPresent:
 
1229
            pass
 
1230
 
 
1231
    def read_basis_inventory(self):
 
1232
        """Read the cached basis inventory."""
 
1233
        path = self._basis_inventory_name()
 
1234
        return self._control_files.get_utf8(path).read()
 
1235
        
 
1236
    @needs_read_lock
 
1237
    def read_working_inventory(self):
 
1238
        """Read the working inventory."""
 
1239
        # ElementTree does its own conversion from UTF-8, so open in
 
1240
        # binary.
 
1241
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1242
            self._control_files.get('inventory'))
 
1243
        self._set_inventory(result)
 
1244
        return result
 
1245
 
 
1246
    @needs_write_lock
 
1247
    def remove(self, files, verbose=False, to_file=None):
 
1248
        """Remove nominated files from the working inventory..
 
1249
 
 
1250
        This does not remove their text.  This does not run on XXX on what? RBC
 
1251
 
 
1252
        TODO: Refuse to remove modified files unless --force is given?
 
1253
 
 
1254
        TODO: Do something useful with directories.
 
1255
 
 
1256
        TODO: Should this remove the text or not?  Tough call; not
 
1257
        removing may be useful and the user can just use use rm, and
 
1258
        is the opposite of add.  Removing it is consistent with most
 
1259
        other tools.  Maybe an option.
 
1260
        """
 
1261
        ## TODO: Normalize names
 
1262
        ## TODO: Remove nested loops; better scalability
 
1263
        if isinstance(files, basestring):
 
1264
            files = [files]
 
1265
 
 
1266
        inv = self.inventory
 
1267
 
 
1268
        # do this before any modifications
 
1269
        for f in files:
 
1270
            fid = inv.path2id(f)
 
1271
            if not fid:
 
1272
                # TODO: Perhaps make this just a warning, and continue?
 
1273
                # This tends to happen when 
 
1274
                raise NotVersionedError(path=f)
 
1275
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
 
1276
            if verbose:
 
1277
                # having remove it, it must be either ignored or unknown
 
1278
                if self.is_ignored(f):
 
1279
                    new_status = 'I'
 
1280
                else:
 
1281
                    new_status = '?'
 
1282
                show_status(new_status, inv[fid].kind, quotefn(f), to_file=to_file)
 
1283
            del inv[fid]
 
1284
 
 
1285
        self._write_inventory(inv)
 
1286
 
 
1287
    @needs_write_lock
 
1288
    def revert(self, filenames, old_tree=None, backups=True, 
 
1289
               pb=DummyProgress()):
 
1290
        from transform import revert
 
1291
        from conflicts import resolve
 
1292
        if old_tree is None:
 
1293
            old_tree = self.basis_tree()
 
1294
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1295
        if not len(filenames):
 
1296
            self.set_pending_merges([])
 
1297
            resolve(self)
 
1298
        else:
 
1299
            resolve(self, filenames, ignore_misses=True)
 
1300
        return conflicts
 
1301
 
 
1302
    # XXX: This method should be deprecated in favour of taking in a proper
 
1303
    # new Inventory object.
 
1304
    @needs_write_lock
 
1305
    def set_inventory(self, new_inventory_list):
 
1306
        from bzrlib.inventory import (Inventory,
 
1307
                                      InventoryDirectory,
 
1308
                                      InventoryEntry,
 
1309
                                      InventoryFile,
 
1310
                                      InventoryLink)
 
1311
        inv = Inventory(self.get_root_id())
 
1312
        for path, file_id, parent, kind in new_inventory_list:
 
1313
            name = os.path.basename(path)
 
1314
            if name == "":
 
1315
                continue
 
1316
            # fixme, there should be a factory function inv,add_?? 
 
1317
            if kind == 'directory':
 
1318
                inv.add(InventoryDirectory(file_id, name, parent))
 
1319
            elif kind == 'file':
 
1320
                inv.add(InventoryFile(file_id, name, parent))
 
1321
            elif kind == 'symlink':
 
1322
                inv.add(InventoryLink(file_id, name, parent))
 
1323
            else:
 
1324
                raise BzrError("unknown kind %r" % kind)
 
1325
        self._write_inventory(inv)
 
1326
 
 
1327
    @needs_write_lock
 
1328
    def set_root_id(self, file_id):
 
1329
        """Set the root id for this tree."""
 
1330
        inv = self.read_working_inventory()
 
1331
        orig_root_id = inv.root.file_id
 
1332
        del inv._byid[inv.root.file_id]
 
1333
        inv.root.file_id = file_id
 
1334
        inv._byid[inv.root.file_id] = inv.root
 
1335
        for fid in inv:
 
1336
            entry = inv[fid]
 
1337
            if entry.parent_id == orig_root_id:
 
1338
                entry.parent_id = inv.root.file_id
 
1339
        self._write_inventory(inv)
 
1340
 
 
1341
    def unlock(self):
 
1342
        """See Branch.unlock.
 
1343
        
 
1344
        WorkingTree locking just uses the Branch locking facilities.
 
1345
        This is current because all working trees have an embedded branch
 
1346
        within them. IF in the future, we were to make branch data shareable
 
1347
        between multiple working trees, i.e. via shared storage, then we 
 
1348
        would probably want to lock both the local tree, and the branch.
 
1349
        """
 
1350
        # FIXME: We want to write out the hashcache only when the last lock on
 
1351
        # this working copy is released.  Peeking at the lock count is a bit
 
1352
        # of a nasty hack; probably it's better to have a transaction object,
 
1353
        # which can do some finalization when it's either successfully or
 
1354
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1355
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1356
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1357
        
 
1358
        # TODO: split this per format so there is no ugly if block
 
1359
        if self._hashcache.needs_write and (
 
1360
            # dedicated lock files
 
1361
            self._control_files._lock_count==1 or 
 
1362
            # shared lock files
 
1363
            (self._control_files is self.branch.control_files and 
 
1364
             self._control_files._lock_count==3)):
 
1365
            self._hashcache.write()
 
1366
        # reverse order of locking.
 
1367
        try:
 
1368
            return self._control_files.unlock()
 
1369
        finally:
 
1370
            self.branch.unlock()
 
1371
 
 
1372
    @needs_write_lock
 
1373
    def update(self):
 
1374
        """Update a working tree along its branch.
 
1375
 
 
1376
        This will update the branch if its bound too, which means we have
 
1377
        multiple trees involved:
 
1378
 
 
1379
        - The new basis tree of the master.
 
1380
        - The old basis tree of the branch.
 
1381
        - The old basis tree of the working tree.
 
1382
        - The current working tree state.
 
1383
 
 
1384
        Pathologically, all three may be different, and non-ancestors of each
 
1385
        other.  Conceptually we want to:
 
1386
 
 
1387
        - Preserve the wt.basis->wt.state changes
 
1388
        - Transform the wt.basis to the new master basis.
 
1389
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1390
          it into the tree.
 
1391
        - Restore the wt.basis->wt.state changes.
 
1392
 
 
1393
        There isn't a single operation at the moment to do that, so we:
 
1394
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1395
          basis.
 
1396
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
1397
        """
 
1398
        old_tip = self.branch.update()
 
1399
        if old_tip is not None:
 
1400
            self.add_pending_merge(old_tip)
 
1401
        self.branch.lock_read()
 
1402
        try:
 
1403
            result = 0
 
1404
            if self.last_revision() != self.branch.last_revision():
 
1405
                # merge tree state up to new branch tip.
 
1406
                basis = self.basis_tree()
 
1407
                to_tree = self.branch.basis_tree()
 
1408
                if basis.inventory.root is None:
 
1409
                    self.set_root_id(to_tree.inventory.root.file_id)
 
1410
                result += merge_inner(self.branch,
 
1411
                                      to_tree,
 
1412
                                      basis,
 
1413
                                      this_tree=self)
 
1414
                self.set_last_revision(self.branch.last_revision())
 
1415
            if old_tip and old_tip != self.last_revision():
 
1416
                # our last revision was not the prior branch last reivison
 
1417
                # and we have converted that last revision to a pending merge.
 
1418
                # base is somewhere between the branch tip now
 
1419
                # and the now pending merge
 
1420
                from bzrlib.revision import common_ancestor
 
1421
                try:
 
1422
                    base_rev_id = common_ancestor(self.branch.last_revision(),
 
1423
                                                  old_tip,
 
1424
                                                  self.branch.repository)
 
1425
                except errors.NoCommonAncestor:
 
1426
                    base_rev_id = None
 
1427
                base_tree = self.branch.repository.revision_tree(base_rev_id)
 
1428
                other_tree = self.branch.repository.revision_tree(old_tip)
 
1429
                result += merge_inner(self.branch,
 
1430
                                      other_tree,
 
1431
                                      base_tree,
 
1432
                                      this_tree=self)
 
1433
            return result
 
1434
        finally:
 
1435
            self.branch.unlock()
 
1436
 
 
1437
    @needs_write_lock
 
1438
    def _write_inventory(self, inv):
 
1439
        """Write inventory as the current inventory."""
 
1440
        sio = StringIO()
 
1441
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1442
        sio.seek(0)
 
1443
        self._control_files.put('inventory', sio)
 
1444
        self._set_inventory(inv)
 
1445
        mutter('wrote working inventory')
 
1446
 
 
1447
    def set_conflicts(self, arg):
 
1448
        raise UnsupportedOperation(self.set_conflicts, self)
 
1449
 
 
1450
    @needs_read_lock
 
1451
    def conflicts(self):
 
1452
        conflicts = ConflictList()
 
1453
        for conflicted in self._iter_conflicts():
 
1454
            text = True
 
1455
            try:
 
1456
                if file_kind(self.abspath(conflicted)) != "file":
 
1457
                    text = False
 
1458
            except OSError, e:
 
1459
                if e.errno == errno.ENOENT:
 
1460
                    text = False
 
1461
                else:
 
1462
                    raise
 
1463
            if text is True:
 
1464
                for suffix in ('.THIS', '.OTHER'):
 
1465
                    try:
 
1466
                        kind = file_kind(self.abspath(conflicted+suffix))
 
1467
                    except OSError, e:
 
1468
                        if e.errno == errno.ENOENT:
 
1469
                            text = False
 
1470
                            break
 
1471
                        else:
 
1472
                            raise
 
1473
                    if kind != "file":
 
1474
                        text = False
 
1475
                        break
 
1476
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
 
1477
            conflicts.append(Conflict.factory(ctype, path=conflicted,
 
1478
                             file_id=self.path2id(conflicted)))
 
1479
        return conflicts
 
1480
 
 
1481
 
 
1482
class WorkingTree3(WorkingTree):
 
1483
    """This is the Format 3 working tree.
 
1484
 
 
1485
    This differs from the base WorkingTree by:
 
1486
     - having its own file lock
 
1487
     - having its own last-revision property.
 
1488
 
 
1489
    This is new in bzr 0.8
 
1490
    """
 
1491
 
 
1492
    @needs_read_lock
 
1493
    def last_revision(self):
 
1494
        """See WorkingTree.last_revision."""
 
1495
        try:
 
1496
            return self._control_files.get_utf8('last-revision').read()
 
1497
        except NoSuchFile:
 
1498
            return None
 
1499
 
 
1500
    def _change_last_revision(self, revision_id):
 
1501
        """See WorkingTree._change_last_revision."""
 
1502
        if revision_id is None or revision_id == NULL_REVISION:
 
1503
            try:
 
1504
                self._control_files._transport.delete('last-revision')
 
1505
            except errors.NoSuchFile:
 
1506
                pass
 
1507
            return False
 
1508
        else:
 
1509
            try:
 
1510
                self.branch.revision_history().index(revision_id)
 
1511
            except ValueError:
 
1512
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1513
            self._control_files.put_utf8('last-revision', revision_id)
 
1514
            return True
 
1515
 
 
1516
    @needs_write_lock
 
1517
    def set_conflicts(self, conflicts):
 
1518
        self._put_rio('conflicts', conflicts.to_stanzas(), 
 
1519
                      CONFLICT_HEADER_1)
 
1520
 
 
1521
    @needs_read_lock
 
1522
    def conflicts(self):
 
1523
        try:
 
1524
            confile = self._control_files.get('conflicts')
 
1525
        except NoSuchFile:
 
1526
            return ConflictList()
 
1527
        try:
 
1528
            if confile.next() != CONFLICT_HEADER_1 + '\n':
 
1529
                raise ConflictFormatError()
 
1530
        except StopIteration:
 
1531
            raise ConflictFormatError()
 
1532
        return ConflictList.from_stanzas(RioReader(confile))
 
1533
 
 
1534
 
 
1535
def get_conflicted_stem(path):
 
1536
    for suffix in CONFLICT_SUFFIXES:
 
1537
        if path.endswith(suffix):
 
1538
            return path[:-len(suffix)]
 
1539
 
 
1540
@deprecated_function(zero_eight)
 
1541
def is_control_file(filename):
 
1542
    """See WorkingTree.is_control_filename(filename)."""
 
1543
    ## FIXME: better check
 
1544
    filename = normpath(filename)
 
1545
    while filename != '':
 
1546
        head, tail = os.path.split(filename)
 
1547
        ## mutter('check %r for control file' % ((head, tail),))
 
1548
        if tail == '.bzr':
 
1549
            return True
 
1550
        if filename == head:
 
1551
            break
 
1552
        filename = head
 
1553
    return False
 
1554
 
 
1555
 
 
1556
class WorkingTreeFormat(object):
 
1557
    """An encapsulation of the initialization and open routines for a format.
 
1558
 
 
1559
    Formats provide three things:
 
1560
     * An initialization routine,
 
1561
     * a format string,
 
1562
     * an open routine.
 
1563
 
 
1564
    Formats are placed in an dict by their format string for reference 
 
1565
    during workingtree opening. Its not required that these be instances, they
 
1566
    can be classes themselves with class methods - it simply depends on 
 
1567
    whether state is needed for a given format or not.
 
1568
 
 
1569
    Once a format is deprecated, just deprecate the initialize and open
 
1570
    methods on the format class. Do not deprecate the object, as the 
 
1571
    object will be created every time regardless.
 
1572
    """
 
1573
 
 
1574
    _default_format = None
 
1575
    """The default format used for new trees."""
 
1576
 
 
1577
    _formats = {}
 
1578
    """The known formats."""
 
1579
 
 
1580
    @classmethod
 
1581
    def find_format(klass, a_bzrdir):
 
1582
        """Return the format for the working tree object in a_bzrdir."""
 
1583
        try:
 
1584
            transport = a_bzrdir.get_workingtree_transport(None)
 
1585
            format_string = transport.get("format").read()
 
1586
            return klass._formats[format_string]
 
1587
        except NoSuchFile:
 
1588
            raise errors.NoWorkingTree(base=transport.base)
 
1589
        except KeyError:
 
1590
            raise errors.UnknownFormatError(format_string)
 
1591
 
 
1592
    @classmethod
 
1593
    def get_default_format(klass):
 
1594
        """Return the current default format."""
 
1595
        return klass._default_format
 
1596
 
 
1597
    def get_format_string(self):
 
1598
        """Return the ASCII format string that identifies this format."""
 
1599
        raise NotImplementedError(self.get_format_string)
 
1600
 
 
1601
    def get_format_description(self):
 
1602
        """Return the short description for this format."""
 
1603
        raise NotImplementedError(self.get_format_description)
 
1604
 
 
1605
    def is_supported(self):
 
1606
        """Is this format supported?
 
1607
 
 
1608
        Supported formats can be initialized and opened.
 
1609
        Unsupported formats may not support initialization or committing or 
 
1610
        some other features depending on the reason for not being supported.
 
1611
        """
 
1612
        return True
 
1613
 
 
1614
    @classmethod
 
1615
    def register_format(klass, format):
 
1616
        klass._formats[format.get_format_string()] = format
 
1617
 
 
1618
    @classmethod
 
1619
    def set_default_format(klass, format):
 
1620
        klass._default_format = format
 
1621
 
 
1622
    @classmethod
 
1623
    def unregister_format(klass, format):
 
1624
        assert klass._formats[format.get_format_string()] is format
 
1625
        del klass._formats[format.get_format_string()]
 
1626
 
 
1627
 
 
1628
 
 
1629
class WorkingTreeFormat2(WorkingTreeFormat):
 
1630
    """The second working tree format. 
 
1631
 
 
1632
    This format modified the hash cache from the format 1 hash cache.
 
1633
    """
 
1634
 
 
1635
    def get_format_description(self):
 
1636
        """See WorkingTreeFormat.get_format_description()."""
 
1637
        return "Working tree format 2"
 
1638
 
 
1639
    def stub_initialize_remote(self, control_files):
 
1640
        """As a special workaround create critical control files for a remote working tree
 
1641
        
 
1642
        This ensures that it can later be updated and dealt with locally,
 
1643
        since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with 
 
1644
        no working tree.  (See bug #43064).
 
1645
        """
 
1646
        sio = StringIO()
 
1647
        inv = Inventory()
 
1648
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1649
        sio.seek(0)
 
1650
        control_files.put('inventory', sio)
 
1651
 
 
1652
        control_files.put_utf8('pending-merges', '')
 
1653
        
 
1654
 
 
1655
    def initialize(self, a_bzrdir, revision_id=None):
 
1656
        """See WorkingTreeFormat.initialize()."""
 
1657
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1658
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1659
        branch = a_bzrdir.open_branch()
 
1660
        if revision_id is not None:
 
1661
            branch.lock_write()
 
1662
            try:
 
1663
                revision_history = branch.revision_history()
 
1664
                try:
 
1665
                    position = revision_history.index(revision_id)
 
1666
                except ValueError:
 
1667
                    raise errors.NoSuchRevision(branch, revision_id)
 
1668
                branch.set_revision_history(revision_history[:position + 1])
 
1669
            finally:
 
1670
                branch.unlock()
 
1671
        revision = branch.last_revision()
 
1672
        inv = Inventory(root_id=gen_root_id()) 
 
1673
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1674
                         branch,
 
1675
                         inv,
 
1676
                         _internal=True,
 
1677
                         _format=self,
 
1678
                         _bzrdir=a_bzrdir)
 
1679
        wt.set_last_revision(revision)
 
1680
        basis_tree = wt.basis_tree()
 
1681
        if basis_tree.inventory.root is not None:
 
1682
            inv.root.file_id = basis_tree.inventory.root.file_id
 
1683
        wt._write_inventory(inv)
 
1684
        wt.set_pending_merges([])
 
1685
        build_tree(basis_tree, wt)
 
1686
        return wt
 
1687
 
 
1688
    def __init__(self):
 
1689
        super(WorkingTreeFormat2, self).__init__()
 
1690
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1691
 
 
1692
    def open(self, a_bzrdir, _found=False):
 
1693
        """Return the WorkingTree object for a_bzrdir
 
1694
 
 
1695
        _found is a private parameter, do not use it. It is used to indicate
 
1696
               if format probing has already been done.
 
1697
        """
 
1698
        if not _found:
 
1699
            # we are being called directly and must probe.
 
1700
            raise NotImplementedError
 
1701
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1702
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1703
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1704
                           _internal=True,
 
1705
                           _format=self,
 
1706
                           _bzrdir=a_bzrdir)
 
1707
 
 
1708
 
 
1709
class WorkingTreeFormat3(WorkingTreeFormat):
 
1710
    """The second working tree format updated to record a format marker.
 
1711
 
 
1712
    This format:
 
1713
        - exists within a metadir controlling .bzr
 
1714
        - includes an explicit version marker for the workingtree control
 
1715
          files, separate from the BzrDir format
 
1716
        - modifies the hash cache format
 
1717
        - is new in bzr 0.8
 
1718
        - uses a LockDir to guard access to the repository
 
1719
    """
 
1720
 
 
1721
    def get_format_string(self):
 
1722
        """See WorkingTreeFormat.get_format_string()."""
 
1723
        return "Bazaar-NG Working Tree format 3"
 
1724
 
 
1725
    def get_format_description(self):
 
1726
        """See WorkingTreeFormat.get_format_description()."""
 
1727
        return "Working tree format 3"
 
1728
 
 
1729
    _lock_file_name = 'lock'
 
1730
    _lock_class = LockDir
 
1731
 
 
1732
    def _open_control_files(self, a_bzrdir):
 
1733
        transport = a_bzrdir.get_workingtree_transport(None)
 
1734
        return LockableFiles(transport, self._lock_file_name, 
 
1735
                             self._lock_class)
 
1736
 
 
1737
    def initialize(self, a_bzrdir, revision_id=None):
 
1738
        """See WorkingTreeFormat.initialize().
 
1739
        
 
1740
        revision_id allows creating a working tree at a differnet
 
1741
        revision than the branch is at.
 
1742
        """
 
1743
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1744
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1745
        transport = a_bzrdir.get_workingtree_transport(self)
 
1746
        control_files = self._open_control_files(a_bzrdir)
 
1747
        control_files.create_lock()
 
1748
        control_files.lock_write()
 
1749
        control_files.put_utf8('format', self.get_format_string())
 
1750
        branch = a_bzrdir.open_branch()
 
1751
        if revision_id is None:
 
1752
            revision_id = branch.last_revision()
 
1753
        inv = Inventory(root_id=gen_root_id()) 
 
1754
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1755
                         branch,
 
1756
                         inv,
 
1757
                         _internal=True,
 
1758
                         _format=self,
 
1759
                         _bzrdir=a_bzrdir,
 
1760
                         _control_files=control_files)
 
1761
        wt.lock_write()
 
1762
        try:
 
1763
            wt.set_last_revision(revision_id)
 
1764
            basis_tree = wt.basis_tree()
 
1765
            wt._write_inventory(inv)
 
1766
            if basis_tree.inventory.root is not None:
 
1767
                inv.root.file_id = basis_tree.inventory.root.file_id
 
1768
            wt.set_pending_merges([])
 
1769
            build_tree(basis_tree, wt)
 
1770
        finally:
 
1771
            wt.unlock()
 
1772
            control_files.unlock()
 
1773
        return wt
 
1774
 
 
1775
    def __init__(self):
 
1776
        super(WorkingTreeFormat3, self).__init__()
 
1777
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1778
 
 
1779
    def open(self, a_bzrdir, _found=False):
 
1780
        """Return the WorkingTree object for a_bzrdir
 
1781
 
 
1782
        _found is a private parameter, do not use it. It is used to indicate
 
1783
               if format probing has already been done.
 
1784
        """
 
1785
        if not _found:
 
1786
            # we are being called directly and must probe.
 
1787
            raise NotImplementedError
 
1788
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1789
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1790
        control_files = self._open_control_files(a_bzrdir)
 
1791
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1792
                           _internal=True,
 
1793
                           _format=self,
 
1794
                           _bzrdir=a_bzrdir,
 
1795
                           _control_files=control_files)
 
1796
 
 
1797
    def __str__(self):
 
1798
        return self.get_format_string()
 
1799
 
 
1800
 
 
1801
# formats which have no format string are not discoverable
 
1802
# and not independently creatable, so are not registered.
 
1803
__default_format = WorkingTreeFormat3()
 
1804
WorkingTreeFormat.register_format(__default_format)
 
1805
WorkingTreeFormat.set_default_format(__default_format)
 
1806
_legacy_formats = [WorkingTreeFormat2(),
 
1807
                   ]
 
1808
 
 
1809
 
 
1810
class WorkingTreeTestProviderAdapter(object):
 
1811
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1812
 
 
1813
    This is done by copying the test once for each transport and injecting
 
1814
    the transport_server, transport_readonly_server, and workingtree_format
 
1815
    classes into each copy. Each copy is also given a new id() to make it
 
1816
    easy to identify.
 
1817
    """
 
1818
 
 
1819
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1820
        self._transport_server = transport_server
 
1821
        self._transport_readonly_server = transport_readonly_server
 
1822
        self._formats = formats
 
1823
    
 
1824
    def adapt(self, test):
 
1825
        from bzrlib.tests import TestSuite
 
1826
        result = TestSuite()
 
1827
        for workingtree_format, bzrdir_format in self._formats:
 
1828
            new_test = deepcopy(test)
 
1829
            new_test.transport_server = self._transport_server
 
1830
            new_test.transport_readonly_server = self._transport_readonly_server
 
1831
            new_test.bzrdir_format = bzrdir_format
 
1832
            new_test.workingtree_format = workingtree_format
 
1833
            def make_new_test_id():
 
1834
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1835
                return lambda: new_id
 
1836
            new_test.id = make_new_test_id()
 
1837
            result.addTest(new_test)
 
1838
        return result