~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-09 14:43:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1912.
  • Revision ID: john@arbash-meinel.com-20060809144327-d604af2edf646794
Clean up and write tests for permissions. Now we use fstat which should be cheap, and lets us check the permissions and the file size

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()
523
547
        if not inv.has_id(file_id):
524
548
            return False
525
549
        path = inv.id2path(file_id)
526
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
550
        return osutils.lexists(self.abspath(path))
527
551
 
528
552
    def has_or_had_id(self, file_id):
529
553
        if file_id == self.inventory.root.file_id:
595
619
        inv = self.read_working_inventory()
596
620
        for f,file_id in zip(files, ids):
597
621
            if self.is_control_filename(f):
598
 
                raise BzrError("cannot add control file %s" % quotefn(f))
 
622
                raise errors.ForbiddenControlFileError(filename=f)
599
623
 
600
624
            fp = splitpath(f)
601
625
 
603
627
                raise BzrError("cannot add top-level %r" % f)
604
628
 
605
629
            fullpath = normpath(self.abspath(f))
606
 
 
607
630
            try:
608
631
                kind = file_kind(fullpath)
609
632
            except OSError, e:
610
633
                if e.errno == errno.ENOENT:
611
634
                    raise NoSuchFile(fullpath)
612
 
                # maybe something better?
613
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
614
 
 
615
635
            if not InventoryEntry.versionable_kind(kind):
616
 
                raise BzrError('cannot add: not a versionable file ('
617
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
618
 
 
 
636
                raise errors.BadFileKindError(filename=f, kind=kind)
619
637
            if file_id is None:
620
638
                inv.add_path(f, kind=kind)
621
639
            else:
646
664
        """
647
665
        try:
648
666
            merges_file = self._control_files.get_utf8('pending-merges')
649
 
        except OSError, e:
650
 
            if e.errno != errno.ENOENT:
651
 
                raise
 
667
        except NoSuchFile:
652
668
            return []
653
669
        p = []
654
670
        for l in merges_file.readlines():
715
731
        """
716
732
        inv = self._inventory
717
733
        # Convert these into local objects to save lookup times
718
 
        pathjoin = bzrlib.osutils.pathjoin
719
 
        file_kind = bzrlib.osutils.file_kind
 
734
        pathjoin = osutils.pathjoin
 
735
        file_kind = osutils.file_kind
720
736
 
721
737
        # transport.base ends in a slash, we want the piece
722
738
        # between the last two slashes
760
776
                elif self.is_ignored(fp[1:]):
761
777
                    c = 'I'
762
778
                else:
763
 
                    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 = '?'
764
798
 
765
799
                fk = file_kind(fap)
766
800
 
823
857
        if to_dir_id == None and to_name != '':
824
858
            raise BzrError("destination %r is not a versioned directory" % to_name)
825
859
        to_dir_ie = inv[to_dir_id]
826
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
860
        if to_dir_ie.kind != 'directory':
827
861
            raise BzrError("destination %r is not a directory" % to_abs)
828
862
 
829
863
        to_idpath = inv.get_idpath(to_dir_id)
918
952
 
919
953
        These are files in the working directory that are not versioned or
920
954
        control files or ignored.
921
 
        
922
 
        >>> from bzrlib.bzrdir import ScratchDir
923
 
        >>> d = ScratchDir(files=['foo', 'foo~'])
924
 
        >>> b = d.open_branch()
925
 
        >>> tree = d.open_workingtree()
926
 
        >>> map(str, tree.unknowns())
927
 
        ['foo']
928
 
        >>> tree.add('foo')
929
 
        >>> list(b.unknowns())
930
 
        []
931
 
        >>> tree.remove('foo')
932
 
        >>> list(b.unknowns())
933
 
        [u'foo']
934
955
        """
935
956
        for subp in self.extras():
936
957
            if not self.is_ignored(subp):
997
1018
        """
998
1019
        ## TODO: Work from given directory downwards
999
1020
        for path, dir_entry in self.inventory.directories():
1000
 
            mutter("search for unknowns in %r", path)
 
1021
            # mutter("search for unknowns in %r", path)
1001
1022
            dirabs = self.abspath(path)
1002
1023
            if not isdir(dirabs):
1003
1024
                # e.g. directory deleted
1005
1026
 
1006
1027
            fl = []
1007
1028
            for subf in os.listdir(dirabs):
1008
 
                if (subf != '.bzr'
1009
 
                    and (subf not in dir_entry.children)):
1010
 
                    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)
1011
1038
            
1012
1039
            fl.sort()
1013
1040
            for subf in fl:
1081
1108
 
1082
1109
        Cached in the Tree object after the first call.
1083
1110
        """
1084
 
        if hasattr(self, '_ignorelist'):
1085
 
            return self._ignorelist
1086
 
 
1087
 
        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
 
1088
1120
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1089
1121
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1090
 
            l.extend([line.rstrip("\n\r").decode('utf-8') 
1091
 
                      for line in f.readlines()])
1092
 
        self._ignorelist = l
1093
 
        self._ignore_regex = self._combine_ignore_rules(l)
1094
 
        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
1095
1130
 
1096
1131
    def _get_ignore_rules_as_regex(self):
1097
1132
        """Return a regex of the ignore rules and a mapping dict.
1099
1134
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1100
1135
        indices to original rule.)
1101
1136
        """
1102
 
        if getattr(self, '_ignorelist', None) is None:
 
1137
        if getattr(self, '_ignoreset', None) is None:
1103
1138
            self.get_ignore_list()
1104
1139
        return self._ignore_regex
1105
1140
 
1189
1224
        if new_revision is None:
1190
1225
            self.branch.set_revision_history([])
1191
1226
            return False
1192
 
        # current format is locked in with the branch
1193
 
        revision_history = self.branch.revision_history()
1194
1227
        try:
1195
 
            position = revision_history.index(new_revision)
1196
 
        except ValueError:
1197
 
            raise errors.NoSuchRevision(self.branch, new_revision)
1198
 
        self.branch.set_revision_history(revision_history[:position + 1])
 
1228
            self.branch.generate_revision_history(new_revision)
 
1229
        except errors.NoSuchRevision:
 
1230
            # not present in the repo - dont try to set it deeper than the tip
 
1231
            self.branch.set_revision_history([new_revision])
1199
1232
        return True
1200
1233
 
1201
1234
    def _cache_basis_inventory(self, new_revision):
1224
1257
            path = self._basis_inventory_name()
1225
1258
            sio = StringIO(xml)
1226
1259
            self._control_files.put(path, sio)
1227
 
        except WeaveRevisionNotPresent:
 
1260
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1228
1261
            pass
1229
1262
 
1230
1263
    def read_basis_inventory(self):
1271
1304
                # TODO: Perhaps make this just a warning, and continue?
1272
1305
                # This tends to happen when 
1273
1306
                raise NotVersionedError(path=f)
1274
 
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1275
1307
            if verbose:
1276
1308
                # having remove it, it must be either ignored or unknown
1277
1309
                if self.is_ignored(f):
1278
1310
                    new_status = 'I'
1279
1311
                else:
1280
1312
                    new_status = '?'
1281
 
                show_status(new_status, inv[fid].kind, quotefn(f), to_file=to_file)
 
1313
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
1282
1314
            del inv[fid]
1283
1315
 
1284
1316
        self._write_inventory(inv)
1346
1378
        between multiple working trees, i.e. via shared storage, then we 
1347
1379
        would probably want to lock both the local tree, and the branch.
1348
1380
        """
1349
 
        # FIXME: We want to write out the hashcache only when the last lock on
1350
 
        # this working copy is released.  Peeking at the lock count is a bit
1351
 
        # of a nasty hack; probably it's better to have a transaction object,
1352
 
        # which can do some finalization when it's either successfully or
1353
 
        # unsuccessfully completed.  (Denys's original patch did that.)
1354
 
        # RBC 20060206 hooking into transaction will couple lock and transaction
1355
 
        # wrongly. Hooking into unlock on the control files object is fine though.
1356
 
        
1357
 
        # TODO: split this per format so there is no ugly if block
1358
 
        if self._hashcache.needs_write and (
1359
 
            # dedicated lock files
1360
 
            self._control_files._lock_count==1 or 
1361
 
            # shared lock files
1362
 
            (self._control_files is self.branch.control_files and 
1363
 
             self._control_files._lock_count==3)):
1364
 
            self._hashcache.write()
1365
 
        # reverse order of locking.
1366
 
        try:
1367
 
            return self._control_files.unlock()
1368
 
        finally:
1369
 
            self.branch.unlock()
 
1381
        raise NotImplementedError(self.unlock)
1370
1382
 
1371
1383
    @needs_write_lock
1372
1384
    def update(self):
1438
1450
    def set_conflicts(self, arg):
1439
1451
        raise UnsupportedOperation(self.set_conflicts, self)
1440
1452
 
 
1453
    def add_conflicts(self, arg):
 
1454
        raise UnsupportedOperation(self.add_conflicts, self)
 
1455
 
1441
1456
    @needs_read_lock
1442
1457
    def conflicts(self):
1443
1458
        conflicts = ConflictList()
1464
1479
        return conflicts
1465
1480
 
1466
1481
 
 
1482
class WorkingTree2(WorkingTree):
 
1483
    """This is the Format 2 working tree.
 
1484
 
 
1485
    This was the first weave based working tree. 
 
1486
     - uses os locks for locking.
 
1487
     - uses the branch last-revision.
 
1488
    """
 
1489
 
 
1490
    def unlock(self):
 
1491
        # we share control files:
 
1492
        if self._hashcache.needs_write and self._control_files._lock_count==3:
 
1493
            self._hashcache.write()
 
1494
        # reverse order of locking.
 
1495
        try:
 
1496
            return self._control_files.unlock()
 
1497
        finally:
 
1498
            self.branch.unlock()
 
1499
 
 
1500
 
1467
1501
class WorkingTree3(WorkingTree):
1468
1502
    """This is the Format 3 working tree.
1469
1503
 
1491
1525
                pass
1492
1526
            return False
1493
1527
        else:
1494
 
            try:
1495
 
                self.branch.revision_history().index(revision_id)
1496
 
            except ValueError:
1497
 
                raise errors.NoSuchRevision(self.branch, revision_id)
1498
1528
            self._control_files.put_utf8('last-revision', revision_id)
1499
1529
            return True
1500
1530
 
1503
1533
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1504
1534
                      CONFLICT_HEADER_1)
1505
1535
 
 
1536
    @needs_write_lock
 
1537
    def add_conflicts(self, new_conflicts):
 
1538
        conflict_set = set(self.conflicts())
 
1539
        conflict_set.update(set(list(new_conflicts)))
 
1540
        self.set_conflicts(ConflictList(sorted(conflict_set,
 
1541
                                               key=Conflict.sort_key)))
 
1542
 
1506
1543
    @needs_read_lock
1507
1544
    def conflicts(self):
1508
1545
        try:
1516
1553
            raise ConflictFormatError()
1517
1554
        return ConflictList.from_stanzas(RioReader(confile))
1518
1555
 
 
1556
    def unlock(self):
 
1557
        if self._hashcache.needs_write and self._control_files._lock_count==1:
 
1558
            self._hashcache.write()
 
1559
        # reverse order of locking.
 
1560
        try:
 
1561
            return self._control_files.unlock()
 
1562
        finally:
 
1563
            self.branch.unlock()
 
1564
 
1519
1565
 
1520
1566
def get_conflicted_stem(path):
1521
1567
    for suffix in CONFLICT_SUFFIXES:
1572
1618
        except NoSuchFile:
1573
1619
            raise errors.NoWorkingTree(base=transport.base)
1574
1620
        except KeyError:
1575
 
            raise errors.UnknownFormatError(format_string)
 
1621
            raise errors.UnknownFormatError(format=format_string)
1576
1622
 
1577
1623
    @classmethod
1578
1624
    def get_default_format(klass):
1655
1701
                branch.unlock()
1656
1702
        revision = branch.last_revision()
1657
1703
        inv = Inventory() 
1658
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1704
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1659
1705
                         branch,
1660
1706
                         inv,
1661
1707
                         _internal=True,
1683
1729
            raise NotImplementedError
1684
1730
        if not isinstance(a_bzrdir.transport, LocalTransport):
1685
1731
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1686
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1732
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1687
1733
                           _internal=True,
1688
1734
                           _format=self,
1689
1735
                           _bzrdir=a_bzrdir)
1698
1744
          files, separate from the BzrDir format
1699
1745
        - modifies the hash cache format
1700
1746
        - is new in bzr 0.8
1701
 
        - uses a LockDir to guard access to the repository
 
1747
        - uses a LockDir to guard access for writes.
1702
1748
    """
1703
1749
 
1704
1750
    def get_format_string(self):
1768
1814
            raise NotImplementedError
1769
1815
        if not isinstance(a_bzrdir.transport, LocalTransport):
1770
1816
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1771
 
        control_files = self._open_control_files(a_bzrdir)
 
1817
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1818
 
 
1819
    def _open(self, a_bzrdir, control_files):
 
1820
        """Open the tree itself.
 
1821
        
 
1822
        :param a_bzrdir: the dir for the tree.
 
1823
        :param control_files: the control files for the tree.
 
1824
        """
1772
1825
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1773
1826
                           _internal=True,
1774
1827
                           _format=self,
1802
1855
        self._transport_readonly_server = transport_readonly_server
1803
1856
        self._formats = formats
1804
1857
    
 
1858
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
1859
        """Clone test for adaption."""
 
1860
        new_test = deepcopy(test)
 
1861
        new_test.transport_server = self._transport_server
 
1862
        new_test.transport_readonly_server = self._transport_readonly_server
 
1863
        new_test.bzrdir_format = bzrdir_format
 
1864
        new_test.workingtree_format = workingtree_format
 
1865
        def make_new_test_id():
 
1866
            new_id = "%s(%s)" % (test.id(), variation)
 
1867
            return lambda: new_id
 
1868
        new_test.id = make_new_test_id()
 
1869
        return new_test
 
1870
    
1805
1871
    def adapt(self, test):
1806
1872
        from bzrlib.tests import TestSuite
1807
1873
        result = TestSuite()
1808
1874
        for workingtree_format, bzrdir_format in self._formats:
1809
 
            new_test = deepcopy(test)
1810
 
            new_test.transport_server = self._transport_server
1811
 
            new_test.transport_readonly_server = self._transport_readonly_server
1812
 
            new_test.bzrdir_format = bzrdir_format
1813
 
            new_test.workingtree_format = workingtree_format
1814
 
            def make_new_test_id():
1815
 
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1816
 
                return lambda: new_id
1817
 
            new_test.id = make_new_test_id()
 
1875
            new_test = self._clone_test(
 
1876
                test,
 
1877
                bzrdir_format,
 
1878
                workingtree_format, workingtree_format.__class__.__name__)
1818
1879
            result.addTest(new_test)
1819
1880
        return result