~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

[patch] Aaron Bentley: make revert work in a subdirectory.

Show diffs side-by-side

added added

removed removed

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