~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Aaron Bentley
  • Date: 2006-08-06 22:49:11 UTC
  • mfrom: (1907 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1908.
  • Revision ID: aaron.bentley@utoronto.ca-20060806224911-0a0f2eda7f34eccf
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
 
 
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
49
49
import re
50
50
import stat
51
51
from time import time
 
52
import warnings
52
53
 
 
54
import bzrlib
 
55
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
53
56
from bzrlib.atomicfile import AtomicFile
54
 
from bzrlib.branch import (Branch,
55
 
                           quotefn)
 
57
import bzrlib.branch
56
58
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
 
import bzrlib.bzrdir as bzrdir
58
59
from bzrlib.decorators import needs_read_lock, needs_write_lock
59
 
import bzrlib.errors as errors
60
60
from bzrlib.errors import (BzrCheckError,
61
61
                           BzrError,
62
62
                           ConflictFormatError,
63
 
                           DivergedBranches,
64
63
                           WeaveRevisionNotPresent,
65
64
                           NotBranchError,
66
65
                           NoSuchFile,
92
91
from bzrlib.progress import DummyProgress, ProgressPhase
93
92
from bzrlib.revision import NULL_REVISION
94
93
from bzrlib.rio import RioReader, rio_file, Stanza
95
 
from bzrlib.symbol_versioning import *
96
 
from bzrlib.textui import show_status
97
 
import bzrlib.tree
 
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
98
101
from bzrlib.transform import build_tree
99
 
from bzrlib.trace import mutter, note
100
102
from bzrlib.transport import get_transport
101
103
from bzrlib.transport.local import LocalTransport
102
 
import bzrlib.urlutils as urlutils
 
104
from bzrlib.textui import show_status
 
105
import bzrlib.tree
103
106
import bzrlib.ui
104
107
import bzrlib.xml5
105
108
 
106
109
 
107
 
# the regex here does the following:
108
 
# 1) remove any weird characters; we don't escape them but rather
109
 
# just pull them out
110
 
 # 2) match leading '.'s to make it not hidden
111
 
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
 
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.]')
112
113
_gen_id_suffix = None
113
114
_gen_id_serial = 0
114
115
 
136
137
 
137
138
    The uniqueness is supplied from _next_id_suffix.
138
139
    """
139
 
    # XXX TODO: squash the filename to lowercase.
140
 
    # XXX TODO: truncate the filename to something like 20 or 30 chars.
141
 
    # XXX TODO: consider what to do with ids that look like illegal filepaths
142
 
    # on platforms we support.
143
 
    return _gen_file_id_re.sub('', name) + _next_id_suffix()
 
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()
144
154
 
145
155
 
146
156
def gen_root_id():
231
241
        self.bzrdir = _bzrdir
232
242
        if not _internal:
233
243
            # not created via open etc.
234
 
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
244
            warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
235
245
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
236
246
                 DeprecationWarning,
237
247
                 stacklevel=2)
251
261
        mutter("opening working tree %r", basedir)
252
262
        if deprecated_passed(branch):
253
263
            if not _internal:
254
 
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
264
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
255
265
                     " Please use bzrdir.open_workingtree() or"
256
266
                     " WorkingTree.open().",
257
267
                     DeprecationWarning,
260
270
            self._branch = branch
261
271
        else:
262
272
            self._branch = self.bzrdir.open_branch()
263
 
        assert isinstance(self.branch, Branch), \
264
 
            "branch %r is not a Branch" % self.branch
265
273
        self.basedir = realpath(basedir)
266
274
        # if branch is at our basedir and is a format 6 or less
267
275
        if isinstance(self._format, WorkingTreeFormat2):
268
276
            # share control object
269
277
            self._control_files = self.branch.control_files
270
278
        else:
271
 
            # only ready for format 3
272
 
            assert isinstance(self._format, WorkingTreeFormat3)
 
279
            # assume all other formats have their own control files.
273
280
            assert isinstance(_control_files, LockableFiles), \
274
281
                    "_control_files must be a LockableFiles, not %r" \
275
282
                    % _control_files
354
361
        :return: The WorkingTree that contains 'path', and the rest of path
355
362
        """
356
363
        if path is None:
357
 
            path = os.getcwdu()
 
364
            path = osutils.getcwd()
358
365
        control, relpath = bzrdir.BzrDir.open_containing(path)
359
366
 
360
367
        return control.open_workingtree(), relpath
375
382
        """
376
383
        inv = self._inventory
377
384
        for path, ie in inv.iter_entries():
378
 
            if bzrlib.osutils.lexists(self.abspath(path)):
 
385
            if osutils.lexists(self.abspath(path)):
379
386
                yield ie.file_id
380
387
 
381
388
    def __repr__(self):
417
424
        XXX: When BzrDir is present, these should be created through that 
418
425
        interface instead.
419
426
        """
420
 
        warn('delete WorkingTree.create', stacklevel=3)
 
427
        warnings.warn('delete WorkingTree.create', stacklevel=3)
421
428
        transport = get_transport(directory)
422
429
        if branch.bzrdir.root_transport.base == transport.base:
423
430
            # same dir 
447
454
        return relpath(self.basedir, path)
448
455
 
449
456
    def has_filename(self, filename):
450
 
        return bzrlib.osutils.lexists(self.abspath(filename))
 
457
        return osutils.lexists(self.abspath(filename))
451
458
 
452
459
    def get_file(self, file_id):
453
460
        return self.get_file_byname(self.id2path(file_id))
454
461
 
 
462
    def get_file_text(self, file_id):
 
463
        return self.get_file(file_id).read()
 
464
 
455
465
    def get_file_byname(self, filename):
456
466
        return file(self.abspath(filename), 'rb')
457
467
 
 
468
    def get_parent_ids(self):
 
469
        """See Tree.get_parent_ids.
 
470
        
 
471
        This implementation reads the pending merges list and last_revision
 
472
        value and uses that to decide what the parents list should be.
 
473
        """
 
474
        last_rev = self.last_revision()
 
475
        if last_rev is None:
 
476
            parents = []
 
477
        else:
 
478
            parents = [last_rev]
 
479
        other_parents = self.pending_merges()
 
480
        return parents + other_parents
 
481
 
458
482
    def get_root_id(self):
459
483
        """Return the id of this trees root"""
460
484
        inv = self.read_working_inventory()
509
533
        # but with branch a kwarg now, passing in args as is results in the
510
534
        #message being used for the branch
511
535
        args = (DEPRECATED_PARAMETER, message, ) + args
512
 
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
 
536
        committed_id = Commit().commit( working_tree=self, revprops=revprops,
 
537
            *args, **kwargs)
513
538
        self._set_inventory(self.read_working_inventory())
 
539
        return committed_id
514
540
 
515
541
    def id2abspath(self, file_id):
516
542
        return self.abspath(self.id2path(file_id))
521
547
        if not inv.has_id(file_id):
522
548
            return False
523
549
        path = inv.id2path(file_id)
524
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
550
        return osutils.lexists(self.abspath(path))
525
551
 
526
552
    def has_or_had_id(self, file_id):
527
553
        if file_id == self.inventory.root.file_id:
552
578
            if not path:
553
579
                path = self._inventory.id2path(file_id)
554
580
            mode = os.lstat(self.abspath(path)).st_mode
555
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
581
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
556
582
 
557
583
    @needs_write_lock
558
584
    def add(self, files, ids=None):
593
619
        inv = self.read_working_inventory()
594
620
        for f,file_id in zip(files, ids):
595
621
            if self.is_control_filename(f):
596
 
                raise BzrError("cannot add control file %s" % quotefn(f))
 
622
                raise errors.ForbiddenControlFileError(filename=f)
597
623
 
598
624
            fp = splitpath(f)
599
625
 
601
627
                raise BzrError("cannot add top-level %r" % f)
602
628
 
603
629
            fullpath = normpath(self.abspath(f))
604
 
 
605
630
            try:
606
631
                kind = file_kind(fullpath)
607
632
            except OSError, e:
608
633
                if e.errno == errno.ENOENT:
609
634
                    raise NoSuchFile(fullpath)
610
 
                # maybe something better?
611
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
612
 
 
613
635
            if not InventoryEntry.versionable_kind(kind):
614
 
                raise BzrError('cannot add: not a versionable file ('
615
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
616
 
 
 
636
                raise errors.BadFileKindError(filename=f, kind=kind)
617
637
            if file_id is None:
618
638
                inv.add_path(f, kind=kind)
619
639
            else:
644
664
        """
645
665
        try:
646
666
            merges_file = self._control_files.get_utf8('pending-merges')
647
 
        except OSError, e:
648
 
            if e.errno != errno.ENOENT:
649
 
                raise
 
667
        except NoSuchFile:
650
668
            return []
651
669
        p = []
652
670
        for l in merges_file.readlines():
713
731
        """
714
732
        inv = self._inventory
715
733
        # Convert these into local objects to save lookup times
716
 
        pathjoin = bzrlib.osutils.pathjoin
717
 
        file_kind = bzrlib.osutils.file_kind
 
734
        pathjoin = osutils.pathjoin
 
735
        file_kind = osutils.file_kind
718
736
 
719
737
        # transport.base ends in a slash, we want the piece
720
738
        # between the last two slashes
758
776
                elif self.is_ignored(fp[1:]):
759
777
                    c = 'I'
760
778
                else:
761
 
                    c = '?'
 
779
                    # we may not have found this file, because of a unicode issue
 
780
                    f_norm, can_access = osutils.normalized_filename(f)
 
781
                    if f == f_norm or not can_access:
 
782
                        # No change, so treat this file normally
 
783
                        c = '?'
 
784
                    else:
 
785
                        # this file can be accessed by a normalized path
 
786
                        # check again if it is versioned
 
787
                        # these lines are repeated here for performance
 
788
                        f = f_norm
 
789
                        fp = from_dir_relpath + '/' + f
 
790
                        fap = from_dir_abspath + '/' + f
 
791
                        f_ie = inv.get_child(from_dir_id, f)
 
792
                        if f_ie:
 
793
                            c = 'V'
 
794
                        elif self.is_ignored(fp[1:]):
 
795
                            c = 'I'
 
796
                        else:
 
797
                            c = '?'
762
798
 
763
799
                fk = file_kind(fap)
764
800
 
916
952
 
917
953
        These are files in the working directory that are not versioned or
918
954
        control files or ignored.
919
 
        
920
 
        >>> from bzrlib.bzrdir import ScratchDir
921
 
        >>> d = ScratchDir(files=['foo', 'foo~'])
922
 
        >>> b = d.open_branch()
923
 
        >>> tree = d.open_workingtree()
924
 
        >>> map(str, tree.unknowns())
925
 
        ['foo']
926
 
        >>> tree.add('foo')
927
 
        >>> list(b.unknowns())
928
 
        []
929
 
        >>> tree.remove('foo')
930
 
        >>> list(b.unknowns())
931
 
        [u'foo']
932
955
        """
933
956
        for subp in self.extras():
934
957
            if not self.is_ignored(subp):
995
1018
        """
996
1019
        ## TODO: Work from given directory downwards
997
1020
        for path, dir_entry in self.inventory.directories():
998
 
            mutter("search for unknowns in %r", path)
 
1021
            # mutter("search for unknowns in %r", path)
999
1022
            dirabs = self.abspath(path)
1000
1023
            if not isdir(dirabs):
1001
1024
                # e.g. directory deleted
1003
1026
 
1004
1027
            fl = []
1005
1028
            for subf in os.listdir(dirabs):
1006
 
                if (subf != '.bzr'
1007
 
                    and (subf not in dir_entry.children)):
1008
 
                    fl.append(subf)
 
1029
                if subf == '.bzr':
 
1030
                    continue
 
1031
                if subf not in dir_entry.children:
 
1032
                    subf_norm, can_access = osutils.normalized_filename(subf)
 
1033
                    if subf_norm != subf and can_access:
 
1034
                        if subf_norm not in dir_entry.children:
 
1035
                            fl.append(subf_norm)
 
1036
                    else:
 
1037
                        fl.append(subf)
1009
1038
            
1010
1039
            fl.sort()
1011
1040
            for subf in fl:
1079
1108
 
1080
1109
        Cached in the Tree object after the first call.
1081
1110
        """
1082
 
        if hasattr(self, '_ignorelist'):
1083
 
            return self._ignorelist
1084
 
 
1085
 
        l = bzrlib.DEFAULT_IGNORE[:]
 
1111
        ignoreset = getattr(self, '_ignoreset', None)
 
1112
        if ignoreset is not None:
 
1113
            return ignoreset
 
1114
 
 
1115
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
 
1116
        ignore_globs.update(ignores.get_runtime_ignores())
 
1117
 
 
1118
        ignore_globs.update(ignores.get_user_ignores())
 
1119
 
1086
1120
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1087
1121
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1088
 
            l.extend([line.rstrip("\n\r").decode('utf-8') 
1089
 
                      for line in f.readlines()])
1090
 
        self._ignorelist = l
1091
 
        self._ignore_regex = self._combine_ignore_rules(l)
1092
 
        return l
 
1122
            try:
 
1123
                ignore_globs.update(ignores.parse_ignore_file(f))
 
1124
            finally:
 
1125
                f.close()
 
1126
 
 
1127
        self._ignoreset = ignore_globs
 
1128
        self._ignore_regex = self._combine_ignore_rules(ignore_globs)
 
1129
        return ignore_globs
1093
1130
 
1094
1131
    def _get_ignore_rules_as_regex(self):
1095
1132
        """Return a regex of the ignore rules and a mapping dict.
1097
1134
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1098
1135
        indices to original rule.)
1099
1136
        """
1100
 
        if getattr(self, '_ignorelist', None) is None:
 
1137
        if getattr(self, '_ignoreset', None) is None:
1101
1138
            self.get_ignore_list()
1102
1139
        return self._ignore_regex
1103
1140
 
1269
1306
                # TODO: Perhaps make this just a warning, and continue?
1270
1307
                # This tends to happen when 
1271
1308
                raise NotVersionedError(path=f)
1272
 
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1273
1309
            if verbose:
1274
1310
                # having remove it, it must be either ignored or unknown
1275
1311
                if self.is_ignored(f):
1276
1312
                    new_status = 'I'
1277
1313
                else:
1278
1314
                    new_status = '?'
1279
 
                show_status(new_status, inv[fid].kind, quotefn(f), to_file=to_file)
 
1315
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
1280
1316
            del inv[fid]
1281
1317
 
1282
1318
        self._write_inventory(inv)
1344
1380
        between multiple working trees, i.e. via shared storage, then we 
1345
1381
        would probably want to lock both the local tree, and the branch.
1346
1382
        """
1347
 
        # FIXME: We want to write out the hashcache only when the last lock on
1348
 
        # this working copy is released.  Peeking at the lock count is a bit
1349
 
        # of a nasty hack; probably it's better to have a transaction object,
1350
 
        # which can do some finalization when it's either successfully or
1351
 
        # unsuccessfully completed.  (Denys's original patch did that.)
1352
 
        # RBC 20060206 hooking into transaction will couple lock and transaction
1353
 
        # wrongly. Hooking into unlock on the control files object is fine though.
1354
 
        
1355
 
        # TODO: split this per format so there is no ugly if block
1356
 
        if self._hashcache.needs_write and (
1357
 
            # dedicated lock files
1358
 
            self._control_files._lock_count==1 or 
1359
 
            # shared lock files
1360
 
            (self._control_files is self.branch.control_files and 
1361
 
             self._control_files._lock_count==3)):
1362
 
            self._hashcache.write()
1363
 
        # reverse order of locking.
1364
 
        try:
1365
 
            return self._control_files.unlock()
1366
 
        finally:
1367
 
            self.branch.unlock()
 
1383
        raise NotImplementedError(self.unlock)
1368
1384
 
1369
1385
    @needs_write_lock
1370
1386
    def update(self):
1436
1452
    def set_conflicts(self, arg):
1437
1453
        raise UnsupportedOperation(self.set_conflicts, self)
1438
1454
 
 
1455
    def add_conflicts(self, arg):
 
1456
        raise UnsupportedOperation(self.add_conflicts, self)
 
1457
 
1439
1458
    @needs_read_lock
1440
1459
    def conflicts(self):
1441
1460
        conflicts = ConflictList()
1444
1463
            try:
1445
1464
                if file_kind(self.abspath(conflicted)) != "file":
1446
1465
                    text = False
1447
 
            except OSError, e:
1448
 
                if e.errno == errno.ENOENT:
1449
 
                    text = False
1450
 
                else:
1451
 
                    raise
 
1466
            except errors.NoSuchFile:
 
1467
                text = False
1452
1468
            if text is True:
1453
1469
                for suffix in ('.THIS', '.OTHER'):
1454
1470
                    try:
1455
1471
                        kind = file_kind(self.abspath(conflicted+suffix))
1456
 
                    except OSError, e:
1457
 
                        if e.errno == errno.ENOENT:
 
1472
                        if kind != "file":
1458
1473
                            text = False
1459
 
                            break
1460
 
                        else:
1461
 
                            raise
1462
 
                    if kind != "file":
 
1474
                    except errors.NoSuchFile:
1463
1475
                        text = False
 
1476
                    if text == False:
1464
1477
                        break
1465
1478
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1466
1479
            conflicts.append(Conflict.factory(ctype, path=conflicted,
1468
1481
        return conflicts
1469
1482
 
1470
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
 
1471
1503
class WorkingTree3(WorkingTree):
1472
1504
    """This is the Format 3 working tree.
1473
1505
 
1507
1539
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1508
1540
                      CONFLICT_HEADER_1)
1509
1541
 
 
1542
    @needs_write_lock
 
1543
    def add_conflicts(self, new_conflicts):
 
1544
        conflict_set = set(self.conflicts())
 
1545
        conflict_set.update(set(list(new_conflicts)))
 
1546
        self.set_conflicts(ConflictList(sorted(conflict_set,
 
1547
                                               key=Conflict.sort_key)))
 
1548
 
1510
1549
    @needs_read_lock
1511
1550
    def conflicts(self):
1512
1551
        try:
1520
1559
            raise ConflictFormatError()
1521
1560
        return ConflictList.from_stanzas(RioReader(confile))
1522
1561
 
 
1562
    def unlock(self):
 
1563
        if self._hashcache.needs_write and self._control_files._lock_count==1:
 
1564
            self._hashcache.write()
 
1565
        # reverse order of locking.
 
1566
        try:
 
1567
            return self._control_files.unlock()
 
1568
        finally:
 
1569
            self.branch.unlock()
 
1570
 
1523
1571
 
1524
1572
def get_conflicted_stem(path):
1525
1573
    for suffix in CONFLICT_SUFFIXES:
1576
1624
        except NoSuchFile:
1577
1625
            raise errors.NoWorkingTree(base=transport.base)
1578
1626
        except KeyError:
1579
 
            raise errors.UnknownFormatError(format_string)
 
1627
            raise errors.UnknownFormatError(format=format_string)
1580
1628
 
1581
1629
    @classmethod
1582
1630
    def get_default_format(klass):
1659
1707
                branch.unlock()
1660
1708
        revision = branch.last_revision()
1661
1709
        inv = Inventory() 
1662
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1710
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1663
1711
                         branch,
1664
1712
                         inv,
1665
1713
                         _internal=True,
1687
1735
            raise NotImplementedError
1688
1736
        if not isinstance(a_bzrdir.transport, LocalTransport):
1689
1737
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1690
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1738
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1691
1739
                           _internal=True,
1692
1740
                           _format=self,
1693
1741
                           _bzrdir=a_bzrdir)
1702
1750
          files, separate from the BzrDir format
1703
1751
        - modifies the hash cache format
1704
1752
        - is new in bzr 0.8
1705
 
        - uses a LockDir to guard access to the repository
 
1753
        - uses a LockDir to guard access for writes.
1706
1754
    """
1707
1755
 
1708
1756
    def get_format_string(self):
1772
1820
            raise NotImplementedError
1773
1821
        if not isinstance(a_bzrdir.transport, LocalTransport):
1774
1822
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1775
 
        control_files = self._open_control_files(a_bzrdir)
 
1823
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1824
 
 
1825
    def _open(self, a_bzrdir, control_files):
 
1826
        """Open the tree itself.
 
1827
        
 
1828
        :param a_bzrdir: the dir for the tree.
 
1829
        :param control_files: the control files for the tree.
 
1830
        """
1776
1831
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1777
1832
                           _internal=True,
1778
1833
                           _format=self,
1806
1861
        self._transport_readonly_server = transport_readonly_server
1807
1862
        self._formats = formats
1808
1863
    
 
1864
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
1865
        """Clone test for adaption."""
 
1866
        new_test = deepcopy(test)
 
1867
        new_test.transport_server = self._transport_server
 
1868
        new_test.transport_readonly_server = self._transport_readonly_server
 
1869
        new_test.bzrdir_format = bzrdir_format
 
1870
        new_test.workingtree_format = workingtree_format
 
1871
        def make_new_test_id():
 
1872
            new_id = "%s(%s)" % (test.id(), variation)
 
1873
            return lambda: new_id
 
1874
        new_test.id = make_new_test_id()
 
1875
        return new_test
 
1876
    
1809
1877
    def adapt(self, test):
1810
1878
        from bzrlib.tests import TestSuite
1811
1879
        result = TestSuite()
1812
1880
        for workingtree_format, bzrdir_format in self._formats:
1813
 
            new_test = deepcopy(test)
1814
 
            new_test.transport_server = self._transport_server
1815
 
            new_test.transport_readonly_server = self._transport_readonly_server
1816
 
            new_test.bzrdir_format = bzrdir_format
1817
 
            new_test.workingtree_format = workingtree_format
1818
 
            def make_new_test_id():
1819
 
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1820
 
                return lambda: new_id
1821
 
            new_test.id = make_new_test_id()
 
1881
            new_test = self._clone_test(
 
1882
                test,
 
1883
                bzrdir_format,
 
1884
                workingtree_format, workingtree_format.__class__.__name__)
1822
1885
            result.addTest(new_test)
1823
1886
        return result