~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-21 17:41:02 UTC
  • mfrom: (1185.50.85 bzr-jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060221174102-aa6bd4464296c614
Mac OSX raises EPERM when you try to unlink a directory

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
17
 
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
 
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
 
18
32
 
19
33
# FIXME: I don't know if writing out the cache from the destructor is really a
20
 
# good idea, because destructors are considered poor taste in Python, and
21
 
# it's not predictable when it will be written out.
22
 
 
 
34
# good idea, because destructors are considered poor taste in Python, and it's
 
35
# not predictable when it will be written out.
 
36
 
 
37
# TODO: Give the workingtree sole responsibility for the working inventory;
 
38
# remove the variable and references to it from the branch.  This may require
 
39
# updating the commit code so as to update the inventory within the working
 
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
 
41
# At the momenthey may alias the inventory and have old copies of it in memory.
 
42
 
 
43
from copy import deepcopy
 
44
from cStringIO import StringIO
 
45
import errno
 
46
import fnmatch
23
47
import os
24
48
import stat
25
 
import fnmatch
26
 
        
 
49
 
 
50
 
 
51
from bzrlib.atomicfile import AtomicFile
 
52
from bzrlib.branch import (Branch,
 
53
                           quotefn)
 
54
import bzrlib.bzrdir as bzrdir
 
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
56
import bzrlib.errors as errors
 
57
from bzrlib.errors import (BzrCheckError,
 
58
                           BzrError,
 
59
                           DivergedBranches,
 
60
                           WeaveRevisionNotPresent,
 
61
                           NotBranchError,
 
62
                           NoSuchFile,
 
63
                           NotVersionedError)
 
64
from bzrlib.inventory import InventoryEntry, Inventory
 
65
from bzrlib.lockable_files import LockableFiles
 
66
from bzrlib.merge import merge_inner, transform_tree
 
67
from bzrlib.osutils import (appendpath,
 
68
                            compact_date,
 
69
                            file_kind,
 
70
                            isdir,
 
71
                            getcwd,
 
72
                            pathjoin,
 
73
                            pumpfile,
 
74
                            safe_unicode,
 
75
                            splitpath,
 
76
                            rand_bytes,
 
77
                            abspath,
 
78
                            normpath,
 
79
                            realpath,
 
80
                            relpath,
 
81
                            rename,
 
82
                            supports_executable,
 
83
                            )
 
84
from bzrlib.revision import NULL_REVISION
 
85
from bzrlib.symbol_versioning import *
 
86
from bzrlib.textui import show_status
27
87
import bzrlib.tree
28
 
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
 
from bzrlib.errors import BzrCheckError
30
88
from bzrlib.trace import mutter
 
89
from bzrlib.transform import build_tree
 
90
from bzrlib.transport import get_transport
 
91
from bzrlib.transport.local import LocalTransport
 
92
import bzrlib.xml5
 
93
 
 
94
 
 
95
def gen_file_id(name):
 
96
    """Return new file id.
 
97
 
 
98
    This should probably generate proper UUIDs, but for the moment we
 
99
    cope with just randomness because running uuidgen every time is
 
100
    slow."""
 
101
    import re
 
102
    from binascii import hexlify
 
103
    from time import time
 
104
 
 
105
    # get last component
 
106
    idx = name.rfind('/')
 
107
    if idx != -1:
 
108
        name = name[idx+1 : ]
 
109
    idx = name.rfind('\\')
 
110
    if idx != -1:
 
111
        name = name[idx+1 : ]
 
112
 
 
113
    # make it not a hidden file
 
114
    name = name.lstrip('.')
 
115
 
 
116
    # remove any wierd characters; we don't escape them but rather
 
117
    # just pull them out
 
118
    name = re.sub(r'[^\w.]', '', name)
 
119
 
 
120
    s = hexlify(rand_bytes(8))
 
121
    return '-'.join((name, compact_date(time()), s))
 
122
 
 
123
 
 
124
def gen_root_id():
 
125
    """Return a new tree-root file id."""
 
126
    return gen_file_id('TREE_ROOT')
 
127
 
31
128
 
32
129
class TreeEntry(object):
33
130
    """An entry that implements the minium interface used by commands.
93
190
    It is possible for a `WorkingTree` to have a filename which is
94
191
    not listed in the Inventory and vice versa.
95
192
    """
96
 
    def __init__(self, basedir, inv):
 
193
 
 
194
    def __init__(self, basedir='.',
 
195
                 branch=DEPRECATED_PARAMETER,
 
196
                 _inventory=None,
 
197
                 _control_files=None,
 
198
                 _internal=False,
 
199
                 _format=None,
 
200
                 _bzrdir=None):
 
201
        """Construct a WorkingTree for basedir.
 
202
 
 
203
        If the branch is not supplied, it is opened automatically.
 
204
        If the branch is supplied, it must be the branch for this basedir.
 
205
        (branch.base is not cross checked, because for remote branches that
 
206
        would be meaningless).
 
207
        """
 
208
        self._format = _format
 
209
        self.bzrdir = _bzrdir
 
210
        if not _internal:
 
211
            # not created via open etc.
 
212
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
213
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
214
                 DeprecationWarning,
 
215
                 stacklevel=2)
 
216
            wt = WorkingTree.open(basedir)
 
217
            self.branch = wt.branch
 
218
            self.basedir = wt.basedir
 
219
            self._control_files = wt._control_files
 
220
            self._hashcache = wt._hashcache
 
221
            self._set_inventory(wt._inventory)
 
222
            self._format = wt._format
 
223
            self.bzrdir = wt.bzrdir
97
224
        from bzrlib.hashcache import HashCache
98
225
        from bzrlib.trace import note, mutter
99
 
 
100
 
        self._inventory = inv
101
 
        self.basedir = basedir
102
 
        self.path2id = inv.path2id
 
226
        assert isinstance(basedir, basestring), \
 
227
            "base directory %r is not a string" % basedir
 
228
        basedir = safe_unicode(basedir)
 
229
        mutter("opening working tree %r", basedir)
 
230
        if deprecated_passed(branch):
 
231
            if not _internal:
 
232
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
233
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
 
234
                     DeprecationWarning,
 
235
                     stacklevel=2
 
236
                     )
 
237
            self.branch = branch
 
238
        else:
 
239
            self.branch = self.bzrdir.open_branch()
 
240
        assert isinstance(self.branch, Branch), \
 
241
            "branch %r is not a Branch" % self.branch
 
242
        self.basedir = realpath(basedir)
 
243
        # if branch is at our basedir and is a format 6 or less
 
244
        if isinstance(self._format, WorkingTreeFormat2):
 
245
            # share control object
 
246
            self._control_files = self.branch.control_files
 
247
        elif _control_files is not None:
 
248
            assert False, "not done yet"
 
249
#            self._control_files = _control_files
 
250
        else:
 
251
            # only ready for format 3
 
252
            assert isinstance(self._format, WorkingTreeFormat3)
 
253
            self._control_files = LockableFiles(
 
254
                self.bzrdir.get_workingtree_transport(None),
 
255
                'lock')
103
256
 
104
257
        # update the whole cache up front and write to disk if anything changed;
105
258
        # in the future we might want to do this more selectively
106
 
        hc = self._hashcache = HashCache(basedir)
 
259
        # two possible ways offer themselves : in self._unlock, write the cache
 
260
        # if needed, or, when the cache sees a change, append it to the hash
 
261
        # cache file, and have the parser take the most recent entry for a
 
262
        # given path only.
 
263
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
264
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
107
265
        hc.read()
 
266
        # is this scan needed ? it makes things kinda slow.
108
267
        hc.scan()
109
268
 
110
269
        if hc.needs_write:
111
270
            mutter("write hc")
112
271
            hc.write()
113
 
            
114
 
            
115
 
    def __del__(self):
116
 
        if self._hashcache.needs_write:
117
 
            self._hashcache.write()
118
 
 
 
272
 
 
273
        if _inventory is None:
 
274
            self._set_inventory(self.read_working_inventory())
 
275
        else:
 
276
            self._set_inventory(_inventory)
 
277
 
 
278
    def _set_inventory(self, inv):
 
279
        self._inventory = inv
 
280
        self.path2id = self._inventory.path2id
 
281
 
 
282
    def is_control_filename(self, filename):
 
283
        """True if filename is the name of a control file in this tree.
 
284
        
 
285
        This is true IF and ONLY IF the filename is part of the meta data
 
286
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
287
        on disk will not be a control file for this tree.
 
288
        """
 
289
        try:
 
290
            self.bzrdir.transport.relpath(self.abspath(filename))
 
291
            return True
 
292
        except errors.PathNotChild:
 
293
            return False
 
294
 
 
295
    @staticmethod
 
296
    def open(path=None, _unsupported=False):
 
297
        """Open an existing working tree at path.
 
298
 
 
299
        """
 
300
        if path is None:
 
301
            path = os.path.getcwdu()
 
302
        control = bzrdir.BzrDir.open(path, _unsupported)
 
303
        return control.open_workingtree(_unsupported)
 
304
        
 
305
    @staticmethod
 
306
    def open_containing(path=None):
 
307
        """Open an existing working tree which has its root about path.
 
308
        
 
309
        This probes for a working tree at path and searches upwards from there.
 
310
 
 
311
        Basically we keep looking up until we find the control directory or
 
312
        run into /.  If there isn't one, raises NotBranchError.
 
313
        TODO: give this a new exception.
 
314
        If there is one, it is returned, along with the unused portion of path.
 
315
        """
 
316
        if path is None:
 
317
            path = os.getcwdu()
 
318
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
319
        return control.open_workingtree(), relpath
 
320
 
 
321
    @staticmethod
 
322
    def open_downlevel(path=None):
 
323
        """Open an unsupported working tree.
 
324
 
 
325
        Only intended for advanced situations like upgrading part of a bzrdir.
 
326
        """
 
327
        return WorkingTree.open(path, _unsupported=True)
119
328
 
120
329
    def __iter__(self):
121
330
        """Iterate through file_ids for this tree.
128
337
            if bzrlib.osutils.lexists(self.abspath(path)):
129
338
                yield ie.file_id
130
339
 
131
 
 
132
340
    def __repr__(self):
133
341
        return "<%s of %s>" % (self.__class__.__name__,
134
342
                               getattr(self, 'basedir', None))
135
343
 
136
 
 
137
 
 
138
344
    def abspath(self, filename):
139
 
        return os.path.join(self.basedir, filename)
 
345
        return pathjoin(self.basedir, filename)
 
346
    
 
347
    def basis_tree(self):
 
348
        """Return RevisionTree for the current last revision."""
 
349
        revision_id = self.last_revision()
 
350
        if revision_id is not None:
 
351
            try:
 
352
                xml = self.read_basis_inventory(revision_id)
 
353
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
354
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
355
                                                revision_id)
 
356
            except NoSuchFile:
 
357
                pass
 
358
        return self.branch.repository.revision_tree(revision_id)
 
359
 
 
360
    @staticmethod
 
361
    @deprecated_method(zero_eight)
 
362
    def create(branch, directory):
 
363
        """Create a workingtree for branch at directory.
 
364
 
 
365
        If existing_directory already exists it must have a .bzr directory.
 
366
        If it does not exist, it will be created.
 
367
 
 
368
        This returns a new WorkingTree object for the new checkout.
 
369
 
 
370
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
371
        should accept an optional revisionid to checkout [and reject this if
 
372
        checking out into the same dir as a pre-checkout-aware branch format.]
 
373
 
 
374
        XXX: When BzrDir is present, these should be created through that 
 
375
        interface instead.
 
376
        """
 
377
        warn('delete WorkingTree.create', stacklevel=3)
 
378
        transport = get_transport(directory)
 
379
        if branch.bzrdir.root_transport.base == transport.base:
 
380
            # same dir 
 
381
            return branch.bzrdir.create_workingtree()
 
382
        # different directory, 
 
383
        # create a branch reference
 
384
        # and now a working tree.
 
385
        raise NotImplementedError
 
386
 
 
387
    @staticmethod
 
388
    @deprecated_method(zero_eight)
 
389
    def create_standalone(directory):
 
390
        """Create a checkout and a branch and a repo at directory.
 
391
 
 
392
        Directory must exist and be empty.
 
393
 
 
394
        please use BzrDir.create_standalone_workingtree
 
395
        """
 
396
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
 
397
 
 
398
    def relpath(self, abs):
 
399
        """Return the local path portion from a given absolute path."""
 
400
        return relpath(self.basedir, abs)
140
401
 
141
402
    def has_filename(self, filename):
142
403
        return bzrlib.osutils.lexists(self.abspath(filename))
147
408
    def get_file_byname(self, filename):
148
409
        return file(self.abspath(filename), 'rb')
149
410
 
 
411
    def get_root_id(self):
 
412
        """Return the id of this trees root"""
 
413
        inv = self.read_working_inventory()
 
414
        return inv.root.file_id
 
415
        
150
416
    def _get_store_filename(self, file_id):
151
 
        ## XXX: badly named; this isn't in the store at all
 
417
        ## XXX: badly named; this is not in the store at all
152
418
        return self.abspath(self.id2path(file_id))
153
419
 
 
420
    @needs_read_lock
 
421
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
422
        """Duplicate this working tree into to_bzr, including all state.
 
423
        
 
424
        Specifically modified files are kept as modified, but
 
425
        ignored and unknown files are discarded.
 
426
 
 
427
        If you want to make a new line of development, see bzrdir.sprout()
 
428
 
 
429
        revision
 
430
            If not None, the cloned tree will have its last revision set to 
 
431
            revision, and and difference between the source trees last revision
 
432
            and this one merged in.
 
433
 
 
434
        basis
 
435
            If not None, a closer copy of a tree which may have some files in
 
436
            common, and which file content should be preferentially copied from.
 
437
        """
 
438
        # assumes the target bzr dir format is compatible.
 
439
        result = self._format.initialize(to_bzrdir)
 
440
        self.copy_content_into(result, revision_id)
 
441
        return result
 
442
 
 
443
    @needs_read_lock
 
444
    def copy_content_into(self, tree, revision_id=None):
 
445
        """Copy the current content and user files of this tree into tree."""
 
446
        if revision_id is None:
 
447
            transform_tree(tree, self)
 
448
        else:
 
449
            # TODO now merge from tree.last_revision to revision
 
450
            transform_tree(tree, self)
 
451
            tree.set_last_revision(revision_id)
 
452
 
 
453
    @needs_write_lock
 
454
    def commit(self, *args, **kwargs):
 
455
        from bzrlib.commit import Commit
 
456
        # args for wt.commit start at message from the Commit.commit method,
 
457
        # but with branch a kwarg now, passing in args as is results in the
 
458
        #message being used for the branch
 
459
        args = (DEPRECATED_PARAMETER, ) + args
 
460
        Commit().commit(working_tree=self, *args, **kwargs)
 
461
        self._set_inventory(self.read_working_inventory())
154
462
 
155
463
    def id2abspath(self, file_id):
156
464
        return self.abspath(self.id2path(file_id))
157
465
 
158
 
                
159
466
    def has_id(self, file_id):
160
467
        # files that have been deleted are excluded
161
468
        inv = self._inventory
164
471
        path = inv.id2path(file_id)
165
472
        return bzrlib.osutils.lexists(self.abspath(path))
166
473
 
 
474
    def has_or_had_id(self, file_id):
 
475
        if file_id == self.inventory.root.file_id:
 
476
            return True
 
477
        return self.inventory.has_id(file_id)
167
478
 
168
479
    __contains__ = has_id
169
 
    
170
480
 
171
481
    def get_file_size(self, file_id):
172
482
        return os.path.getsize(self.id2abspath(file_id))
173
483
 
 
484
    @needs_read_lock
174
485
    def get_file_sha1(self, file_id):
175
486
        path = self._inventory.id2path(file_id)
176
487
        return self._hashcache.get_sha1(path)
177
488
 
178
 
 
179
489
    def is_executable(self, file_id):
180
 
        if os.name == "nt":
 
490
        if not supports_executable():
181
491
            return self._inventory[file_id].executable
182
492
        else:
183
493
            path = self._inventory.id2path(file_id)
184
494
            mode = os.lstat(self.abspath(path)).st_mode
185
495
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
186
496
 
 
497
    @needs_write_lock
 
498
    def add(self, files, ids=None):
 
499
        """Make files versioned.
 
500
 
 
501
        Note that the command line normally calls smart_add instead,
 
502
        which can automatically recurse.
 
503
 
 
504
        This adds the files to the inventory, so that they will be
 
505
        recorded by the next commit.
 
506
 
 
507
        files
 
508
            List of paths to add, relative to the base of the tree.
 
509
 
 
510
        ids
 
511
            If set, use these instead of automatically generated ids.
 
512
            Must be the same length as the list of files, but may
 
513
            contain None for ids that are to be autogenerated.
 
514
 
 
515
        TODO: Perhaps have an option to add the ids even if the files do
 
516
              not (yet) exist.
 
517
 
 
518
        TODO: Perhaps callback with the ids and paths as they're added.
 
519
        """
 
520
        # TODO: Re-adding a file that is removed in the working copy
 
521
        # should probably put it back with the previous ID.
 
522
        if isinstance(files, basestring):
 
523
            assert(ids is None or isinstance(ids, basestring))
 
524
            files = [files]
 
525
            if ids is not None:
 
526
                ids = [ids]
 
527
 
 
528
        if ids is None:
 
529
            ids = [None] * len(files)
 
530
        else:
 
531
            assert(len(ids) == len(files))
 
532
 
 
533
        inv = self.read_working_inventory()
 
534
        for f,file_id in zip(files, ids):
 
535
            if self.is_control_filename(f):
 
536
                raise BzrError("cannot add control file %s" % quotefn(f))
 
537
 
 
538
            fp = splitpath(f)
 
539
 
 
540
            if len(fp) == 0:
 
541
                raise BzrError("cannot add top-level %r" % f)
 
542
 
 
543
            fullpath = normpath(self.abspath(f))
 
544
 
 
545
            try:
 
546
                kind = file_kind(fullpath)
 
547
            except OSError, e:
 
548
                if e.errno == errno.ENOENT:
 
549
                    raise NoSuchFile(fullpath)
 
550
                # maybe something better?
 
551
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
552
 
 
553
            if not InventoryEntry.versionable_kind(kind):
 
554
                raise BzrError('cannot add: not a versionable file ('
 
555
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
556
 
 
557
            if file_id is None:
 
558
                file_id = gen_file_id(f)
 
559
            inv.add_path(f, kind=kind, file_id=file_id)
 
560
 
 
561
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
562
        self._write_inventory(inv)
 
563
 
 
564
    @needs_write_lock
 
565
    def add_pending_merge(self, *revision_ids):
 
566
        # TODO: Perhaps should check at this point that the
 
567
        # history of the revision is actually present?
 
568
        p = self.pending_merges()
 
569
        updated = False
 
570
        for rev_id in revision_ids:
 
571
            if rev_id in p:
 
572
                continue
 
573
            p.append(rev_id)
 
574
            updated = True
 
575
        if updated:
 
576
            self.set_pending_merges(p)
 
577
 
 
578
    @needs_read_lock
 
579
    def pending_merges(self):
 
580
        """Return a list of pending merges.
 
581
 
 
582
        These are revisions that have been merged into the working
 
583
        directory but not yet committed.
 
584
        """
 
585
        try:
 
586
            merges_file = self._control_files.get_utf8('pending-merges')
 
587
        except OSError, e:
 
588
            if e.errno != errno.ENOENT:
 
589
                raise
 
590
            return []
 
591
        p = []
 
592
        for l in merges_file.readlines():
 
593
            p.append(l.rstrip('\n'))
 
594
        return p
 
595
 
 
596
    @needs_write_lock
 
597
    def set_pending_merges(self, rev_list):
 
598
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
 
599
 
187
600
    def get_symlink_target(self, file_id):
188
601
        return os.readlink(self.id2abspath(file_id))
189
602
 
195
608
        else:
196
609
            return '?'
197
610
 
198
 
 
199
611
    def list_files(self):
200
612
        """Recursively list all files as (path, class, kind, id).
201
613
 
215
627
                ## TODO: If we find a subdirectory with its own .bzr
216
628
                ## directory, then that is a separate tree and we
217
629
                ## should exclude it.
218
 
                if bzrlib.BZRDIR == f:
 
630
 
 
631
                # the bzrdir for this tree
 
632
                if self.bzrdir.transport.base.endswith(f + '/'):
219
633
                    continue
220
634
 
221
635
                # path within tree
265
679
                for ff in descend(fp, f_ie.file_id, fap):
266
680
                    yield ff
267
681
 
268
 
        for f in descend('', inv.root.file_id, self.basedir):
 
682
        for f in descend(u'', inv.root.file_id, self.basedir):
269
683
            yield f
270
 
            
271
 
 
272
 
 
 
684
 
 
685
    @needs_write_lock
 
686
    def move(self, from_paths, to_name):
 
687
        """Rename files.
 
688
 
 
689
        to_name must exist in the inventory.
 
690
 
 
691
        If to_name exists and is a directory, the files are moved into
 
692
        it, keeping their old names.  
 
693
 
 
694
        Note that to_name is only the last component of the new name;
 
695
        this doesn't change the directory.
 
696
 
 
697
        This returns a list of (from_path, to_path) pairs for each
 
698
        entry that is moved.
 
699
        """
 
700
        result = []
 
701
        ## TODO: Option to move IDs only
 
702
        assert not isinstance(from_paths, basestring)
 
703
        inv = self.inventory
 
704
        to_abs = self.abspath(to_name)
 
705
        if not isdir(to_abs):
 
706
            raise BzrError("destination %r is not a directory" % to_abs)
 
707
        if not self.has_filename(to_name):
 
708
            raise BzrError("destination %r not in working directory" % to_abs)
 
709
        to_dir_id = inv.path2id(to_name)
 
710
        if to_dir_id == None and to_name != '':
 
711
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
712
        to_dir_ie = inv[to_dir_id]
 
713
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
714
            raise BzrError("destination %r is not a directory" % to_abs)
 
715
 
 
716
        to_idpath = inv.get_idpath(to_dir_id)
 
717
 
 
718
        for f in from_paths:
 
719
            if not self.has_filename(f):
 
720
                raise BzrError("%r does not exist in working tree" % f)
 
721
            f_id = inv.path2id(f)
 
722
            if f_id == None:
 
723
                raise BzrError("%r is not versioned" % f)
 
724
            name_tail = splitpath(f)[-1]
 
725
            dest_path = appendpath(to_name, name_tail)
 
726
            if self.has_filename(dest_path):
 
727
                raise BzrError("destination %r already exists" % dest_path)
 
728
            if f_id in to_idpath:
 
729
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
730
 
 
731
        # OK, so there's a race here, it's possible that someone will
 
732
        # create a file in this interval and then the rename might be
 
733
        # left half-done.  But we should have caught most problems.
 
734
        orig_inv = deepcopy(self.inventory)
 
735
        try:
 
736
            for f in from_paths:
 
737
                name_tail = splitpath(f)[-1]
 
738
                dest_path = appendpath(to_name, name_tail)
 
739
                result.append((f, dest_path))
 
740
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
741
                try:
 
742
                    rename(self.abspath(f), self.abspath(dest_path))
 
743
                except OSError, e:
 
744
                    raise BzrError("failed to rename %r to %r: %s" %
 
745
                                   (f, dest_path, e[1]),
 
746
                            ["rename rolled back"])
 
747
        except:
 
748
            # restore the inventory on error
 
749
            self._set_inventory(orig_inv)
 
750
            raise
 
751
        self._write_inventory(inv)
 
752
        return result
 
753
 
 
754
    @needs_write_lock
 
755
    def rename_one(self, from_rel, to_rel):
 
756
        """Rename one file.
 
757
 
 
758
        This can change the directory or the filename or both.
 
759
        """
 
760
        inv = self.inventory
 
761
        if not self.has_filename(from_rel):
 
762
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
763
        if self.has_filename(to_rel):
 
764
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
765
 
 
766
        file_id = inv.path2id(from_rel)
 
767
        if file_id == None:
 
768
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
769
 
 
770
        entry = inv[file_id]
 
771
        from_parent = entry.parent_id
 
772
        from_name = entry.name
 
773
        
 
774
        if inv.path2id(to_rel):
 
775
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
776
 
 
777
        to_dir, to_tail = os.path.split(to_rel)
 
778
        to_dir_id = inv.path2id(to_dir)
 
779
        if to_dir_id == None and to_dir != '':
 
780
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
781
 
 
782
        mutter("rename_one:")
 
783
        mutter("  file_id    {%s}" % file_id)
 
784
        mutter("  from_rel   %r" % from_rel)
 
785
        mutter("  to_rel     %r" % to_rel)
 
786
        mutter("  to_dir     %r" % to_dir)
 
787
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
788
 
 
789
        inv.rename(file_id, to_dir_id, to_tail)
 
790
 
 
791
        from_abs = self.abspath(from_rel)
 
792
        to_abs = self.abspath(to_rel)
 
793
        try:
 
794
            rename(from_abs, to_abs)
 
795
        except OSError, e:
 
796
            inv.rename(file_id, from_parent, from_name)
 
797
            raise BzrError("failed to rename %r to %r: %s"
 
798
                    % (from_abs, to_abs, e[1]),
 
799
                    ["rename rolled back"])
 
800
        self._write_inventory(inv)
 
801
 
 
802
    @needs_read_lock
273
803
    def unknowns(self):
 
804
        """Return all unknown files.
 
805
 
 
806
        These are files in the working directory that are not versioned or
 
807
        control files or ignored.
 
808
        
 
809
        >>> from bzrlib.bzrdir import ScratchDir
 
810
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
811
        >>> b = d.open_branch()
 
812
        >>> tree = d.open_workingtree()
 
813
        >>> map(str, tree.unknowns())
 
814
        ['foo']
 
815
        >>> tree.add('foo')
 
816
        >>> list(b.unknowns())
 
817
        []
 
818
        >>> tree.remove('foo')
 
819
        >>> list(b.unknowns())
 
820
        [u'foo']
 
821
        """
274
822
        for subp in self.extras():
275
823
            if not self.is_ignored(subp):
276
824
                yield subp
285
833
                conflicted.add(stem)
286
834
                yield stem
287
835
 
 
836
    @needs_write_lock
 
837
    def pull(self, source, overwrite=False, stop_revision=None):
 
838
        source.lock_read()
 
839
        try:
 
840
            old_revision_history = self.branch.revision_history()
 
841
            count = self.branch.pull(source, overwrite, stop_revision)
 
842
            new_revision_history = self.branch.revision_history()
 
843
            if new_revision_history != old_revision_history:
 
844
                if len(old_revision_history):
 
845
                    other_revision = old_revision_history[-1]
 
846
                else:
 
847
                    other_revision = None
 
848
                repository = self.branch.repository
 
849
                merge_inner(self.branch,
 
850
                            self.basis_tree(), 
 
851
                            repository.revision_tree(other_revision),
 
852
                            this_tree=self)
 
853
                self.set_last_revision(self.branch.last_revision())
 
854
            return count
 
855
        finally:
 
856
            source.unlock()
 
857
 
288
858
    def extras(self):
289
859
        """Yield all unknown files in this WorkingTree.
290
860
 
296
866
        """
297
867
        ## TODO: Work from given directory downwards
298
868
        for path, dir_entry in self.inventory.directories():
299
 
            mutter("search for unknowns in %r" % path)
 
869
            mutter("search for unknowns in %r", path)
300
870
            dirabs = self.abspath(path)
301
871
            if not isdir(dirabs):
302
872
                # e.g. directory deleted
376
946
        else:
377
947
            return None
378
948
 
 
949
    def kind(self, file_id):
 
950
        return file_kind(self.id2abspath(file_id))
 
951
 
 
952
    @needs_read_lock
 
953
    def last_revision(self):
 
954
        """Return the last revision id of this working tree.
 
955
 
 
956
        In early branch formats this was == the branch last_revision,
 
957
        but that cannot be relied upon - for working tree operations,
 
958
        always use tree.last_revision().
 
959
        """
 
960
        return self.branch.last_revision()
 
961
 
 
962
    def lock_read(self):
 
963
        """See Branch.lock_read, and WorkingTree.unlock."""
 
964
        self.branch.lock_read()
 
965
        try:
 
966
            return self._control_files.lock_read()
 
967
        except:
 
968
            self.branch.unlock()
 
969
            raise
 
970
 
 
971
    def lock_write(self):
 
972
        """See Branch.lock_write, and WorkingTree.unlock."""
 
973
        self.branch.lock_write()
 
974
        try:
 
975
            return self._control_files.lock_write()
 
976
        except:
 
977
            self.branch.unlock()
 
978
            raise
 
979
 
 
980
    def _basis_inventory_name(self, revision_id):
 
981
        return 'basis-inventory.%s' % revision_id
 
982
 
 
983
    @needs_write_lock
 
984
    def set_last_revision(self, new_revision, old_revision=None):
 
985
        """Change the last revision in the working tree."""
 
986
        self._remove_old_basis(old_revision)
 
987
        if self._change_last_revision(new_revision):
 
988
            self._cache_basis_inventory(new_revision)
 
989
 
 
990
    def _change_last_revision(self, new_revision):
 
991
        """Template method part of set_last_revision to perform the change."""
 
992
        if new_revision is None:
 
993
            self.branch.set_revision_history([])
 
994
            return False
 
995
        # current format is locked in with the branch
 
996
        revision_history = self.branch.revision_history()
 
997
        try:
 
998
            position = revision_history.index(new_revision)
 
999
        except ValueError:
 
1000
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1001
        self.branch.set_revision_history(revision_history[:position + 1])
 
1002
        return True
 
1003
 
 
1004
    def _cache_basis_inventory(self, new_revision):
 
1005
        """Cache new_revision as the basis inventory."""
 
1006
        try:
 
1007
            xml = self.branch.repository.get_inventory_xml(new_revision)
 
1008
            path = self._basis_inventory_name(new_revision)
 
1009
            self._control_files.put_utf8(path, xml)
 
1010
        except WeaveRevisionNotPresent:
 
1011
            pass
 
1012
 
 
1013
    def _remove_old_basis(self, old_revision):
 
1014
        """Remove the old basis inventory 'old_revision'."""
 
1015
        if old_revision is not None:
 
1016
            try:
 
1017
                path = self._basis_inventory_name(old_revision)
 
1018
                path = self._control_files._escape(path)
 
1019
                self._control_files._transport.delete(path)
 
1020
            except NoSuchFile:
 
1021
                pass
 
1022
 
 
1023
    def read_basis_inventory(self, revision_id):
 
1024
        """Read the cached basis inventory."""
 
1025
        path = self._basis_inventory_name(revision_id)
 
1026
        return self._control_files.get_utf8(path).read()
 
1027
        
 
1028
    @needs_read_lock
 
1029
    def read_working_inventory(self):
 
1030
        """Read the working inventory."""
 
1031
        # ElementTree does its own conversion from UTF-8, so open in
 
1032
        # binary.
 
1033
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1034
            self._control_files.get('inventory'))
 
1035
        self._set_inventory(result)
 
1036
        return result
 
1037
 
 
1038
    @needs_write_lock
 
1039
    def remove(self, files, verbose=False):
 
1040
        """Remove nominated files from the working inventory..
 
1041
 
 
1042
        This does not remove their text.  This does not run on XXX on what? RBC
 
1043
 
 
1044
        TODO: Refuse to remove modified files unless --force is given?
 
1045
 
 
1046
        TODO: Do something useful with directories.
 
1047
 
 
1048
        TODO: Should this remove the text or not?  Tough call; not
 
1049
        removing may be useful and the user can just use use rm, and
 
1050
        is the opposite of add.  Removing it is consistent with most
 
1051
        other tools.  Maybe an option.
 
1052
        """
 
1053
        ## TODO: Normalize names
 
1054
        ## TODO: Remove nested loops; better scalability
 
1055
        if isinstance(files, basestring):
 
1056
            files = [files]
 
1057
 
 
1058
        inv = self.inventory
 
1059
 
 
1060
        # do this before any modifications
 
1061
        for f in files:
 
1062
            fid = inv.path2id(f)
 
1063
            if not fid:
 
1064
                # TODO: Perhaps make this just a warning, and continue?
 
1065
                # This tends to happen when 
 
1066
                raise NotVersionedError(path=f)
 
1067
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
 
1068
            if verbose:
 
1069
                # having remove it, it must be either ignored or unknown
 
1070
                if self.is_ignored(f):
 
1071
                    new_status = 'I'
 
1072
                else:
 
1073
                    new_status = '?'
 
1074
                show_status(new_status, inv[fid].kind, quotefn(f))
 
1075
            del inv[fid]
 
1076
 
 
1077
        self._write_inventory(inv)
 
1078
 
 
1079
    @needs_write_lock
 
1080
    def revert(self, filenames, old_tree=None, backups=True):
 
1081
        from transform import revert
 
1082
        if old_tree is None:
 
1083
            old_tree = self.basis_tree()
 
1084
        revert(self, old_tree, filenames, backups)
 
1085
        if not len(filenames):
 
1086
            self.set_pending_merges([])
 
1087
 
 
1088
    @needs_write_lock
 
1089
    def set_inventory(self, new_inventory_list):
 
1090
        from bzrlib.inventory import (Inventory,
 
1091
                                      InventoryDirectory,
 
1092
                                      InventoryEntry,
 
1093
                                      InventoryFile,
 
1094
                                      InventoryLink)
 
1095
        inv = Inventory(self.get_root_id())
 
1096
        for path, file_id, parent, kind in new_inventory_list:
 
1097
            name = os.path.basename(path)
 
1098
            if name == "":
 
1099
                continue
 
1100
            # fixme, there should be a factory function inv,add_?? 
 
1101
            if kind == 'directory':
 
1102
                inv.add(InventoryDirectory(file_id, name, parent))
 
1103
            elif kind == 'file':
 
1104
                inv.add(InventoryFile(file_id, name, parent))
 
1105
            elif kind == 'symlink':
 
1106
                inv.add(InventoryLink(file_id, name, parent))
 
1107
            else:
 
1108
                raise BzrError("unknown kind %r" % kind)
 
1109
        self._write_inventory(inv)
 
1110
 
 
1111
    @needs_write_lock
 
1112
    def set_root_id(self, file_id):
 
1113
        """Set the root id for this tree."""
 
1114
        inv = self.read_working_inventory()
 
1115
        orig_root_id = inv.root.file_id
 
1116
        del inv._byid[inv.root.file_id]
 
1117
        inv.root.file_id = file_id
 
1118
        inv._byid[inv.root.file_id] = inv.root
 
1119
        for fid in inv:
 
1120
            entry = inv[fid]
 
1121
            if entry.parent_id == orig_root_id:
 
1122
                entry.parent_id = inv.root.file_id
 
1123
        self._write_inventory(inv)
 
1124
 
 
1125
    def unlock(self):
 
1126
        """See Branch.unlock.
 
1127
        
 
1128
        WorkingTree locking just uses the Branch locking facilities.
 
1129
        This is current because all working trees have an embedded branch
 
1130
        within them. IF in the future, we were to make branch data shareable
 
1131
        between multiple working trees, i.e. via shared storage, then we 
 
1132
        would probably want to lock both the local tree, and the branch.
 
1133
        """
 
1134
        # FIXME: We want to write out the hashcache only when the last lock on
 
1135
        # this working copy is released.  Peeking at the lock count is a bit
 
1136
        # of a nasty hack; probably it's better to have a transaction object,
 
1137
        # which can do some finalization when it's either successfully or
 
1138
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1139
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1140
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1141
        
 
1142
        # TODO: split this per format so there is no ugly if block
 
1143
        if self._hashcache.needs_write and (
 
1144
            # dedicated lock files
 
1145
            self._control_files._lock_count==1 or 
 
1146
            # shared lock files
 
1147
            (self._control_files is self.branch.control_files and 
 
1148
             self._control_files._lock_count==3)):
 
1149
            self._hashcache.write()
 
1150
        # reverse order of locking.
 
1151
        result = self._control_files.unlock()
 
1152
        try:
 
1153
            self.branch.unlock()
 
1154
        finally:
 
1155
            return result
 
1156
 
 
1157
    @needs_write_lock
 
1158
    def update(self):
 
1159
        self.branch.lock_read()
 
1160
        try:
 
1161
            if self.last_revision() == self.branch.last_revision():
 
1162
                return
 
1163
            basis = self.basis_tree()
 
1164
            to_tree = self.branch.basis_tree()
 
1165
            result = merge_inner(self.branch,
 
1166
                                 to_tree,
 
1167
                                 basis,
 
1168
                                 this_tree=self)
 
1169
            self.set_last_revision(self.branch.last_revision())
 
1170
            return result
 
1171
        finally:
 
1172
            self.branch.unlock()
 
1173
 
 
1174
    @needs_write_lock
 
1175
    def _write_inventory(self, inv):
 
1176
        """Write inventory as the current inventory."""
 
1177
        sio = StringIO()
 
1178
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1179
        sio.seek(0)
 
1180
        self._control_files.put('inventory', sio)
 
1181
        self._set_inventory(inv)
 
1182
        mutter('wrote working inventory')
 
1183
 
 
1184
 
 
1185
class WorkingTree3(WorkingTree):
 
1186
    """This is the Format 3 working tree.
 
1187
 
 
1188
    This differs from the base WorkingTree by:
 
1189
     - having its own file lock
 
1190
     - having its own last-revision property.
 
1191
    """
 
1192
 
 
1193
    @needs_read_lock
 
1194
    def last_revision(self):
 
1195
        """See WorkingTree.last_revision."""
 
1196
        try:
 
1197
            return self._control_files.get_utf8('last-revision').read()
 
1198
        except NoSuchFile:
 
1199
            return None
 
1200
 
 
1201
    def _change_last_revision(self, revision_id):
 
1202
        """See WorkingTree._change_last_revision."""
 
1203
        if revision_id is None or revision_id == NULL_REVISION:
 
1204
            try:
 
1205
                self._control_files._transport.delete('last-revision')
 
1206
            except errors.NoSuchFile:
 
1207
                pass
 
1208
            return False
 
1209
        else:
 
1210
            try:
 
1211
                self.branch.revision_history().index(revision_id)
 
1212
            except ValueError:
 
1213
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1214
            self._control_files.put_utf8('last-revision', revision_id)
 
1215
            return True
 
1216
 
 
1217
 
379
1218
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
380
1219
def get_conflicted_stem(path):
381
1220
    for suffix in CONFLICT_SUFFIXES:
382
1221
        if path.endswith(suffix):
383
1222
            return path[:-len(suffix)]
 
1223
 
 
1224
@deprecated_function(zero_eight)
 
1225
def is_control_file(filename):
 
1226
    """See WorkingTree.is_control_filename(filename)."""
 
1227
    ## FIXME: better check
 
1228
    filename = normpath(filename)
 
1229
    while filename != '':
 
1230
        head, tail = os.path.split(filename)
 
1231
        ## mutter('check %r for control file' % ((head, tail),))
 
1232
        if tail == '.bzr':
 
1233
            return True
 
1234
        if filename == head:
 
1235
            break
 
1236
        filename = head
 
1237
    return False
 
1238
 
 
1239
 
 
1240
class WorkingTreeFormat(object):
 
1241
    """An encapsulation of the initialization and open routines for a format.
 
1242
 
 
1243
    Formats provide three things:
 
1244
     * An initialization routine,
 
1245
     * a format string,
 
1246
     * an open routine.
 
1247
 
 
1248
    Formats are placed in an dict by their format string for reference 
 
1249
    during workingtree opening. Its not required that these be instances, they
 
1250
    can be classes themselves with class methods - it simply depends on 
 
1251
    whether state is needed for a given format or not.
 
1252
 
 
1253
    Once a format is deprecated, just deprecate the initialize and open
 
1254
    methods on the format class. Do not deprecate the object, as the 
 
1255
    object will be created every time regardless.
 
1256
    """
 
1257
 
 
1258
    _default_format = None
 
1259
    """The default format used for new trees."""
 
1260
 
 
1261
    _formats = {}
 
1262
    """The known formats."""
 
1263
 
 
1264
    @classmethod
 
1265
    def find_format(klass, a_bzrdir):
 
1266
        """Return the format for the working tree object in a_bzrdir."""
 
1267
        try:
 
1268
            transport = a_bzrdir.get_workingtree_transport(None)
 
1269
            format_string = transport.get("format").read()
 
1270
            return klass._formats[format_string]
 
1271
        except NoSuchFile:
 
1272
            raise errors.NoWorkingTree(base=transport.base)
 
1273
        except KeyError:
 
1274
            raise errors.UnknownFormatError(format_string)
 
1275
 
 
1276
    @classmethod
 
1277
    def get_default_format(klass):
 
1278
        """Return the current default format."""
 
1279
        return klass._default_format
 
1280
 
 
1281
    def get_format_string(self):
 
1282
        """Return the ASCII format string that identifies this format."""
 
1283
        raise NotImplementedError(self.get_format_string)
 
1284
 
 
1285
    def is_supported(self):
 
1286
        """Is this format supported?
 
1287
 
 
1288
        Supported formats can be initialized and opened.
 
1289
        Unsupported formats may not support initialization or committing or 
 
1290
        some other features depending on the reason for not being supported.
 
1291
        """
 
1292
        return True
 
1293
 
 
1294
    @classmethod
 
1295
    def register_format(klass, format):
 
1296
        klass._formats[format.get_format_string()] = format
 
1297
 
 
1298
    @classmethod
 
1299
    def set_default_format(klass, format):
 
1300
        klass._default_format = format
 
1301
 
 
1302
    @classmethod
 
1303
    def unregister_format(klass, format):
 
1304
        assert klass._formats[format.get_format_string()] is format
 
1305
        del klass._formats[format.get_format_string()]
 
1306
 
 
1307
 
 
1308
 
 
1309
class WorkingTreeFormat2(WorkingTreeFormat):
 
1310
    """The second working tree format. 
 
1311
 
 
1312
    This format modified the hash cache from the format 1 hash cache.
 
1313
    """
 
1314
 
 
1315
    def initialize(self, a_bzrdir, revision_id=None):
 
1316
        """See WorkingTreeFormat.initialize()."""
 
1317
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1318
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1319
        branch = a_bzrdir.open_branch()
 
1320
        if revision_id is not None:
 
1321
            branch.lock_write()
 
1322
            try:
 
1323
                revision_history = branch.revision_history()
 
1324
                try:
 
1325
                    position = revision_history.index(revision_id)
 
1326
                except ValueError:
 
1327
                    raise errors.NoSuchRevision(branch, revision_id)
 
1328
                branch.set_revision_history(revision_history[:position + 1])
 
1329
            finally:
 
1330
                branch.unlock()
 
1331
        revision = branch.last_revision()
 
1332
        inv = Inventory() 
 
1333
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1334
                         branch,
 
1335
                         inv,
 
1336
                         _internal=True,
 
1337
                         _format=self,
 
1338
                         _bzrdir=a_bzrdir)
 
1339
        wt._write_inventory(inv)
 
1340
        wt.set_root_id(inv.root.file_id)
 
1341
        wt.set_last_revision(revision)
 
1342
        wt.set_pending_merges([])
 
1343
        build_tree(wt.basis_tree(), wt)
 
1344
        return wt
 
1345
 
 
1346
    def __init__(self):
 
1347
        super(WorkingTreeFormat2, self).__init__()
 
1348
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1349
 
 
1350
    def open(self, a_bzrdir, _found=False):
 
1351
        """Return the WorkingTree object for a_bzrdir
 
1352
 
 
1353
        _found is a private parameter, do not use it. It is used to indicate
 
1354
               if format probing has already been done.
 
1355
        """
 
1356
        if not _found:
 
1357
            # we are being called directly and must probe.
 
1358
            raise NotImplementedError
 
1359
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1360
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1361
        return WorkingTree(a_bzrdir.root_transport.base,
 
1362
                           _internal=True,
 
1363
                           _format=self,
 
1364
                           _bzrdir=a_bzrdir)
 
1365
 
 
1366
 
 
1367
class WorkingTreeFormat3(WorkingTreeFormat):
 
1368
    """The second working tree format updated to record a format marker.
 
1369
 
 
1370
    This format modified the hash cache from the format 1 hash cache.
 
1371
    """
 
1372
 
 
1373
    def get_format_string(self):
 
1374
        """See WorkingTreeFormat.get_format_string()."""
 
1375
        return "Bazaar-NG Working Tree format 3"
 
1376
 
 
1377
    def initialize(self, a_bzrdir, revision_id=None):
 
1378
        """See WorkingTreeFormat.initialize().
 
1379
        
 
1380
        revision_id allows creating a working tree at a differnet
 
1381
        revision than the branch is at.
 
1382
        """
 
1383
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1384
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1385
        transport = a_bzrdir.get_workingtree_transport(self)
 
1386
        control_files = LockableFiles(transport, 'lock')
 
1387
        control_files.put_utf8('format', self.get_format_string())
 
1388
        branch = a_bzrdir.open_branch()
 
1389
        if revision_id is None:
 
1390
            revision_id = branch.last_revision()
 
1391
        inv = Inventory() 
 
1392
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1393
                         branch,
 
1394
                         inv,
 
1395
                         _internal=True,
 
1396
                         _format=self,
 
1397
                         _bzrdir=a_bzrdir)
 
1398
        wt._write_inventory(inv)
 
1399
        wt.set_root_id(inv.root.file_id)
 
1400
        wt.set_last_revision(revision_id)
 
1401
        wt.set_pending_merges([])
 
1402
        build_tree(wt.basis_tree(), wt)
 
1403
        return wt
 
1404
 
 
1405
    def __init__(self):
 
1406
        super(WorkingTreeFormat3, self).__init__()
 
1407
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1408
 
 
1409
    def open(self, a_bzrdir, _found=False):
 
1410
        """Return the WorkingTree object for a_bzrdir
 
1411
 
 
1412
        _found is a private parameter, do not use it. It is used to indicate
 
1413
               if format probing has already been done.
 
1414
        """
 
1415
        if not _found:
 
1416
            # we are being called directly and must probe.
 
1417
            raise NotImplementedError
 
1418
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1419
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1420
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1421
                           _internal=True,
 
1422
                           _format=self,
 
1423
                           _bzrdir=a_bzrdir)
 
1424
 
 
1425
    def __str__(self):
 
1426
        return self.get_format_string()
 
1427
 
 
1428
 
 
1429
# formats which have no format string are not discoverable
 
1430
# and not independently creatable, so are not registered.
 
1431
__default_format = WorkingTreeFormat3()
 
1432
WorkingTreeFormat.register_format(__default_format)
 
1433
WorkingTreeFormat.set_default_format(__default_format)
 
1434
_legacy_formats = [WorkingTreeFormat2(),
 
1435
                   ]
 
1436
 
 
1437
 
 
1438
class WorkingTreeTestProviderAdapter(object):
 
1439
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1440
 
 
1441
    This is done by copying the test once for each transport and injecting
 
1442
    the transport_server, transport_readonly_server, and workingtree_format
 
1443
    classes into each copy. Each copy is also given a new id() to make it
 
1444
    easy to identify.
 
1445
    """
 
1446
 
 
1447
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1448
        self._transport_server = transport_server
 
1449
        self._transport_readonly_server = transport_readonly_server
 
1450
        self._formats = formats
 
1451
    
 
1452
    def adapt(self, test):
 
1453
        from bzrlib.tests import TestSuite
 
1454
        result = TestSuite()
 
1455
        for workingtree_format, bzrdir_format in self._formats:
 
1456
            new_test = deepcopy(test)
 
1457
            new_test.transport_server = self._transport_server
 
1458
            new_test.transport_readonly_server = self._transport_readonly_server
 
1459
            new_test.bzrdir_format = bzrdir_format
 
1460
            new_test.workingtree_format = workingtree_format
 
1461
            def make_new_test_id():
 
1462
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1463
                return lambda: new_id
 
1464
            new_test.id = make_new_test_id()
 
1465
            result.addTest(new_test)
 
1466
        return result