~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

Merge in bzr-dir phase 2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
At the moment every WorkingTree has its own branch.  Remote
26
26
WorkingTrees aren't supported.
27
27
 
28
 
To get a WorkingTree, call WorkingTree(dir[, branch])
 
28
To get a WorkingTree, call bzrdir.open_workingtree() or
 
29
WorkingTree.open(dir).
29
30
"""
30
31
 
31
32
 
49
50
 
50
51
from bzrlib.atomicfile import AtomicFile
51
52
from bzrlib.branch import (Branch,
52
 
                           BzrBranchFormat4,
53
 
                           BzrBranchFormat5,
54
 
                           BzrBranchFormat6,
55
 
                           is_control_file,
56
53
                           quotefn)
 
54
import bzrlib.bzrdir as bzrdir
57
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
56
import bzrlib.errors as errors
58
57
from bzrlib.errors import (BzrCheckError,
59
58
                           BzrError,
60
59
                           DivergedBranches,
64
63
                           NotVersionedError)
65
64
from bzrlib.inventory import InventoryEntry
66
65
from bzrlib.lockable_files import LockableFiles
 
66
from bzrlib.merge import merge_inner, transform_tree
67
67
from bzrlib.osutils import (appendpath,
68
68
                            compact_date,
69
69
                            file_kind,
84
84
import bzrlib.tree
85
85
from bzrlib.trace import mutter
86
86
from bzrlib.transport import get_transport
 
87
from bzrlib.transport.local import LocalTransport
87
88
import bzrlib.xml5
88
89
 
89
90
 
186
187
    not listed in the Inventory and vice versa.
187
188
    """
188
189
 
189
 
    def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
 
190
    def __init__(self, basedir='.',
 
191
                 branch=None,
 
192
                 _inventory=None,
 
193
                 _control_files=None,
 
194
                 _internal=False,
 
195
                 _format=None,
 
196
                 _bzrdir=None):
190
197
        """Construct a WorkingTree for basedir.
191
198
 
192
199
        If the branch is not supplied, it is opened automatically.
194
201
        (branch.base is not cross checked, because for remote branches that
195
202
        would be meaningless).
196
203
        """
 
204
        self._format = _format
 
205
        self.bzrdir = _bzrdir
 
206
        if not _internal:
 
207
            # created via open etc.
 
208
            wt = WorkingTree.open(basedir)
 
209
            self.branch = wt.branch
 
210
            self.basedir = wt.basedir
 
211
            self._control_files = wt._control_files
 
212
            self._hashcache = wt._hashcache
 
213
            self._set_inventory(wt._inventory)
 
214
            self._format = wt._format
 
215
            self.bzrdir = wt.bzrdir
197
216
        from bzrlib.hashcache import HashCache
198
217
        from bzrlib.trace import note, mutter
199
218
        assert isinstance(basedir, basestring), \
207
226
        self.branch = branch
208
227
        self.basedir = realpath(basedir)
209
228
        # if branch is at our basedir and is a format 6 or less
210
 
        if (isinstance(self.branch._branch_format,
211
 
                       (BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
212
 
            # might be able to share control object
213
 
            and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
 
229
        if isinstance(self._format, WorkingTreeFormat2):
 
230
            # share control object
214
231
            self._control_files = self.branch.control_files
215
232
        elif _control_files is not None:
216
233
            assert False, "not done yet"
217
234
#            self._control_files = _control_files
218
235
        else:
 
236
            # only ready for format 3
 
237
            assert isinstance(self._format, WorkingTreeFormat3)
219
238
            self._control_files = LockableFiles(
220
 
                get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
 
239
                self.bzrdir.get_workingtree_transport(None),
 
240
                'lock')
221
241
 
222
242
        # update the whole cache up front and write to disk if anything changed;
223
243
        # in the future we might want to do this more selectively
225
245
        # if needed, or, when the cache sees a change, append it to the hash
226
246
        # cache file, and have the parser take the most recent entry for a
227
247
        # given path only.
228
 
        hc = self._hashcache = HashCache(basedir)
 
248
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
249
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
229
250
        hc.read()
 
251
        # is this scan needed ? it makes things kinda slow.
230
252
        hc.scan()
231
253
 
232
254
        if hc.needs_write:
243
265
        self.path2id = self._inventory.path2id
244
266
 
245
267
    @staticmethod
 
268
    def open(path=None, _unsupported=False):
 
269
        """Open an existing working tree at path.
 
270
 
 
271
        """
 
272
        if path is None:
 
273
            path = os.path.getcwdu()
 
274
        control = bzrdir.BzrDir.open(path, _unsupported)
 
275
        return control.open_workingtree(_unsupported)
 
276
        
 
277
    @staticmethod
246
278
    def open_containing(path=None):
247
279
        """Open an existing working tree which has its root about path.
248
280
        
254
286
        If there is one, it is returned, along with the unused portion of path.
255
287
        """
256
288
        if path is None:
257
 
            path = getcwd()
258
 
        else:
259
 
            # sanity check.
260
 
            if path.find('://') != -1:
261
 
                raise NotBranchError(path=path)
262
 
        path = abspath(path)
263
 
        orig_path = path[:]
264
 
        tail = u''
265
 
        while True:
266
 
            try:
267
 
                return WorkingTree(path), tail
268
 
            except NotBranchError:
269
 
                pass
270
 
            if tail:
271
 
                tail = pathjoin(os.path.basename(path), tail)
272
 
            else:
273
 
                tail = os.path.basename(path)
274
 
            lastpath = path
275
 
            path = os.path.dirname(path)
276
 
            if lastpath == path:
277
 
                # reached the root, whatever that may be
278
 
                raise NotBranchError(path=orig_path)
 
289
            path = os.getcwdu()
 
290
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
291
        return control.open_workingtree(), relpath
 
292
 
 
293
    @staticmethod
 
294
    def open_downlevel(path=None):
 
295
        """Open an unsupported working tree.
 
296
 
 
297
        Only intended for advanced situations like upgrading part of a bzrdir.
 
298
        """
 
299
        return WorkingTree.open(path, _unsupported=True)
279
300
 
280
301
    def __iter__(self):
281
302
        """Iterate through file_ids for this tree.
294
315
 
295
316
    def abspath(self, filename):
296
317
        return pathjoin(self.basedir, filename)
 
318
    
 
319
    def basis_tree(self):
 
320
        """Return RevisionTree for the current last revision."""
 
321
        revision_id = self.last_revision()
 
322
        if revision_id is not None:
 
323
            try:
 
324
                xml = self.read_basis_inventory(revision_id)
 
325
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
326
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
327
                                                revision_id)
 
328
            except NoSuchFile:
 
329
                pass
 
330
        return self.branch.repository.revision_tree(revision_id)
297
331
 
298
332
    @staticmethod
 
333
    @deprecated_method(zero_eight)
299
334
    def create(branch, directory):
300
335
        """Create a workingtree for branch at directory.
301
336
 
311
346
        XXX: When BzrDir is present, these should be created through that 
312
347
        interface instead.
313
348
        """
314
 
        try:
315
 
            os.mkdir(directory)
316
 
        except OSError, e:
317
 
            if e.errno != errno.EEXIST:
318
 
                raise
319
 
        try:
320
 
            os.mkdir(pathjoin(directory, '.bzr'))
321
 
        except OSError, e:
322
 
            if e.errno != errno.EEXIST:
323
 
                raise
324
 
        inv = branch.repository.revision_tree(branch.last_revision()).inventory
325
 
        wt = WorkingTree(directory, branch, inv)
326
 
        wt._write_inventory(inv)
327
 
        if branch.last_revision() is not None:
328
 
            wt.set_last_revision(branch.last_revision())
329
 
        wt.set_pending_merges([])
330
 
        wt.revert([])
331
 
        return wt
 
349
        warn('delete WorkingTree.create', stacklevel=3)
 
350
        transport = get_transport(directory)
 
351
        if branch.bzrdir.root_transport.base == transport.base:
 
352
            # same dir 
 
353
            return branch.bzrdir.create_workingtree()
 
354
        # different directory, 
 
355
        # create a branch reference
 
356
        # and now a working tree.
 
357
        raise NotImplementedError
332
358
 
333
359
    @staticmethod
 
360
    @deprecated_method(zero_eight)
334
361
    def create_standalone(directory):
335
 
        """Create a checkout and a branch at directory.
 
362
        """Create a checkout and a branch and a repo at directory.
336
363
 
337
364
        Directory must exist and be empty.
338
365
 
339
 
        XXX: When BzrDir is present, these should be created through that 
340
 
        interface instead.
 
366
        please use BzrDir.create_standalone_workingtree
341
367
        """
342
 
        directory = safe_unicode(directory)
343
 
        b = Branch.create(directory)
344
 
        return WorkingTree.create(b, directory)
 
368
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
345
369
 
346
370
    def relpath(self, abs):
347
371
        """Return the local path portion from a given absolute path."""
365
389
        ## XXX: badly named; this is not in the store at all
366
390
        return self.abspath(self.id2path(file_id))
367
391
 
 
392
    @needs_read_lock
 
393
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
394
        """Duplicate this working tree into to_bzr, including all state.
 
395
        
 
396
        Specifically modified files are kept as modified, but
 
397
        ignored and unknown files are discarded.
 
398
 
 
399
        If you want to make a new line of development, see bzrdir.sprout()
 
400
 
 
401
        revision
 
402
            If not None, the cloned tree will have its last revision set to 
 
403
            revision, and and difference between the source trees last revision
 
404
            and this one merged in.
 
405
 
 
406
        basis
 
407
            If not None, a closer copy of a tree which may have some files in
 
408
            common, and which file content should be preferentially copied from.
 
409
        """
 
410
        # assumes the target bzr dir format is compatible.
 
411
        result = self._format.initialize(to_bzrdir)
 
412
        self.copy_content_into(result, revision_id)
 
413
        return result
 
414
 
 
415
    @needs_read_lock
 
416
    def copy_content_into(self, tree, revision_id=None):
 
417
        """Copy the current content and user files of this tree into tree."""
 
418
        if revision_id is None:
 
419
            transform_tree(tree, self)
 
420
        else:
 
421
            # TODO now merge from tree.last_revision to revision
 
422
            transform_tree(tree, self)
 
423
            tree.set_last_revision(revision_id)
 
424
 
368
425
    @needs_write_lock
369
426
    def commit(self, *args, **kwargs):
370
427
        from bzrlib.commit import Commit
459
516
 
460
517
            try:
461
518
                kind = file_kind(fullpath)
462
 
            except OSError:
 
519
            except OSError, e:
 
520
                if e.errno == errno.ENOENT:
 
521
                    raise NoSuchFile(fullpath)
463
522
                # maybe something better?
464
523
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
465
524
 
521
580
        else:
522
581
            return '?'
523
582
 
524
 
 
525
583
    def list_files(self):
526
584
        """Recursively list all files as (path, class, kind, id).
527
585
 
718
776
        These are files in the working directory that are not versioned or
719
777
        control files or ignored.
720
778
        
721
 
        >>> from bzrlib.branch import ScratchBranch
722
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
779
        >>> from bzrlib.bzrdir import ScratchDir
 
780
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
781
        >>> b = d.open_branch()
723
782
        >>> tree = WorkingTree(b.base, b)
724
783
        >>> map(str, tree.unknowns())
725
784
        ['foo']
746
805
 
747
806
    @needs_write_lock
748
807
    def pull(self, source, overwrite=False, stop_revision=None):
749
 
        from bzrlib.merge import merge_inner
750
808
        source.lock_read()
751
809
        try:
752
810
            old_revision_history = self.branch.revision_history()
753
 
            count = self.branch.pull(source, overwrite,stop_revision)
 
811
            count = self.branch.pull(source, overwrite, stop_revision)
754
812
            new_revision_history = self.branch.revision_history()
755
813
            if new_revision_history != old_revision_history:
756
814
                if len(old_revision_history):
759
817
                    other_revision = None
760
818
                repository = self.branch.repository
761
819
                merge_inner(self.branch,
762
 
                            self.branch.basis_tree(), 
 
820
                            self.basis_tree(), 
763
821
                            repository.revision_tree(other_revision),
764
822
                            this_tree=self)
765
823
                self.set_last_revision(self.branch.last_revision())
861
919
    def kind(self, file_id):
862
920
        return file_kind(self.id2abspath(file_id))
863
921
 
 
922
    def last_revision(self):
 
923
        """Return the last revision id of this working tree.
 
924
 
 
925
        In early branch formats this was == the branch last_revision,
 
926
        but that cannot be relied upon - for working tree operations,
 
927
        always use tree.last_revision().
 
928
        """
 
929
        return self.branch.last_revision()
 
930
 
864
931
    def lock_read(self):
865
932
        """See Branch.lock_read, and WorkingTree.unlock."""
866
 
        return self.branch.lock_read()
 
933
        self.branch.lock_read()
 
934
        try:
 
935
            return self._control_files.lock_read()
 
936
        except:
 
937
            self.branch.unlock()
 
938
            raise
867
939
 
868
940
    def lock_write(self):
869
941
        """See Branch.lock_write, and WorkingTree.unlock."""
870
 
        return self.branch.lock_write()
 
942
        self.branch.lock_write()
 
943
        try:
 
944
            return self._control_files.lock_write()
 
945
        except:
 
946
            self.branch.unlock()
 
947
            raise
871
948
 
872
949
    def _basis_inventory_name(self, revision_id):
873
950
        return 'basis-inventory.%s' % revision_id
876
953
        if old_revision is not None:
877
954
            try:
878
955
                path = self._basis_inventory_name(old_revision)
879
 
                path = self.branch.control_files._escape(path)
880
 
                self.branch.control_files._transport.delete(path)
 
956
                path = self._control_files._escape(path)
 
957
                self._control_files._transport.delete(path)
881
958
            except NoSuchFile:
882
959
                pass
 
960
        if new_revision is None:
 
961
            self.branch.set_revision_history([])
 
962
            return
 
963
        # current format is locked in with the branch
 
964
        revision_history = self.branch.revision_history()
 
965
        try:
 
966
            position = revision_history.index(new_revision)
 
967
        except ValueError:
 
968
            raise errors.NoSuchRevision(self.branch, new_revision)
 
969
        self.branch.set_revision_history(revision_history[:position + 1])
883
970
        try:
884
971
            xml = self.branch.repository.get_inventory_xml(new_revision)
885
972
            path = self._basis_inventory_name(new_revision)
886
 
            self.branch.control_files.put_utf8(path, xml)
 
973
            self._control_files.put_utf8(path, xml)
887
974
        except WeaveRevisionNotPresent:
888
975
            pass
889
976
 
890
977
    def read_basis_inventory(self, revision_id):
891
978
        """Read the cached basis inventory."""
892
979
        path = self._basis_inventory_name(revision_id)
893
 
        return self.branch.control_files.get_utf8(path).read()
 
980
        return self._control_files.get_utf8(path).read()
894
981
        
895
982
    @needs_read_lock
896
983
    def read_working_inventory(self):
897
984
        """Read the working inventory."""
898
985
        # ElementTree does its own conversion from UTF-8, so open in
899
986
        # binary.
900
 
        return bzrlib.xml5.serializer_v5.read_inventory(
 
987
        result = bzrlib.xml5.serializer_v5.read_inventory(
901
988
            self._control_files.get('inventory'))
 
989
        self._set_inventory(result)
 
990
        return result
902
991
 
903
992
    @needs_write_lock
904
993
    def remove(self, files, verbose=False):
945
1034
    def revert(self, filenames, old_tree=None, backups=True):
946
1035
        from bzrlib.merge import merge_inner
947
1036
        if old_tree is None:
948
 
            old_tree = self.branch.basis_tree()
 
1037
            old_tree = self.basis_tree()
949
1038
        merge_inner(self.branch, old_tree,
950
1039
                    self, ignore_zero=True,
951
1040
                    backup_files=backups, 
987
1076
        inv._byid[inv.root.file_id] = inv.root
988
1077
        for fid in inv:
989
1078
            entry = inv[fid]
990
 
            if entry.parent_id in (None, orig_root_id):
 
1079
            if entry.parent_id == orig_root_id:
991
1080
                entry.parent_id = inv.root.file_id
992
1081
        self._write_inventory(inv)
993
1082
 
1005
1094
        # of a nasty hack; probably it's better to have a transaction object,
1006
1095
        # which can do some finalization when it's either successfully or
1007
1096
        # unsuccessfully completed.  (Denys's original patch did that.)
1008
 
        if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
 
1097
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1098
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1099
        
 
1100
        # TODO: split this per format so there is no ugly if block
 
1101
        if self._hashcache.needs_write and (
 
1102
            self._control_files._lock_count==1 or 
 
1103
            (self._control_files is self.branch.control_files and 
 
1104
             self._control_files._lock_count==2)):
1009
1105
            self._hashcache.write()
1010
 
        return self.branch.unlock()
 
1106
        # reverse order of locking.
 
1107
        result = self._control_files.unlock()
 
1108
        try:
 
1109
            self.branch.unlock()
 
1110
        finally:
 
1111
            return result
1011
1112
 
1012
1113
    @needs_write_lock
1013
1114
    def _write_inventory(self, inv):
1025
1126
    for suffix in CONFLICT_SUFFIXES:
1026
1127
        if path.endswith(suffix):
1027
1128
            return path[:-len(suffix)]
 
1129
 
 
1130
def is_control_file(filename):
 
1131
    ## FIXME: better check
 
1132
    filename = normpath(filename)
 
1133
    while filename != '':
 
1134
        head, tail = os.path.split(filename)
 
1135
        ## mutter('check %r for control file' % ((head, tail),))
 
1136
        if tail == bzrlib.BZRDIR:
 
1137
            return True
 
1138
        if filename == head:
 
1139
            break
 
1140
        filename = head
 
1141
    return False
 
1142
 
 
1143
 
 
1144
class WorkingTreeFormat(object):
 
1145
    """An encapsulation of the initialization and open routines for a format.
 
1146
 
 
1147
    Formats provide three things:
 
1148
     * An initialization routine,
 
1149
     * a format string,
 
1150
     * an open routine.
 
1151
 
 
1152
    Formats are placed in an dict by their format string for reference 
 
1153
    during workingtree opening. Its not required that these be instances, they
 
1154
    can be classes themselves with class methods - it simply depends on 
 
1155
    whether state is needed for a given format or not.
 
1156
 
 
1157
    Once a format is deprecated, just deprecate the initialize and open
 
1158
    methods on the format class. Do not deprecate the object, as the 
 
1159
    object will be created every time regardless.
 
1160
    """
 
1161
 
 
1162
    _default_format = None
 
1163
    """The default format used for new trees."""
 
1164
 
 
1165
    _formats = {}
 
1166
    """The known formats."""
 
1167
 
 
1168
    @classmethod
 
1169
    def find_format(klass, a_bzrdir):
 
1170
        """Return the format for the working tree object in a_bzrdir."""
 
1171
        try:
 
1172
            transport = a_bzrdir.get_workingtree_transport(None)
 
1173
            format_string = transport.get("format").read()
 
1174
            return klass._formats[format_string]
 
1175
        except NoSuchFile:
 
1176
            raise errors.NotBranchError(path=transport.base)
 
1177
        except KeyError:
 
1178
            raise errors.UnknownFormatError(format_string)
 
1179
 
 
1180
    @classmethod
 
1181
    def get_default_format(klass):
 
1182
        """Return the current default format."""
 
1183
        return klass._default_format
 
1184
 
 
1185
    def get_format_string(self):
 
1186
        """Return the ASCII format string that identifies this format."""
 
1187
        raise NotImplementedError(self.get_format_string)
 
1188
 
 
1189
    def is_supported(self):
 
1190
        """Is this format supported?
 
1191
 
 
1192
        Supported formats can be initialized and opened.
 
1193
        Unsupported formats may not support initialization or committing or 
 
1194
        some other features depending on the reason for not being supported.
 
1195
        """
 
1196
        return True
 
1197
 
 
1198
    @classmethod
 
1199
    def register_format(klass, format):
 
1200
        klass._formats[format.get_format_string()] = format
 
1201
 
 
1202
    @classmethod
 
1203
    def set_default_format(klass, format):
 
1204
        klass._default_format = format
 
1205
 
 
1206
    @classmethod
 
1207
    def unregister_format(klass, format):
 
1208
        assert klass._formats[format.get_format_string()] is format
 
1209
        del klass._formats[format.get_format_string()]
 
1210
 
 
1211
 
 
1212
 
 
1213
class WorkingTreeFormat2(WorkingTreeFormat):
 
1214
    """The second working tree format. 
 
1215
 
 
1216
    This format modified the hash cache from the format 1 hash cache.
 
1217
    """
 
1218
 
 
1219
    def initialize(self, a_bzrdir):
 
1220
        """See WorkingTreeFormat.initialize()."""
 
1221
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1222
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1223
        branch = a_bzrdir.open_branch()
 
1224
        revision = branch.last_revision()
 
1225
        basis_tree = branch.repository.revision_tree(revision)
 
1226
        inv = basis_tree.inventory
 
1227
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1228
                         branch,
 
1229
                         inv,
 
1230
                         _internal=True,
 
1231
                         _format=self,
 
1232
                         _bzrdir=a_bzrdir)
 
1233
        wt._write_inventory(inv)
 
1234
        wt.set_root_id(inv.root.file_id)
 
1235
        wt.set_last_revision(revision)
 
1236
        wt.set_pending_merges([])
 
1237
        wt.revert([])
 
1238
        return wt
 
1239
 
 
1240
    def __init__(self):
 
1241
        super(WorkingTreeFormat2, self).__init__()
 
1242
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1243
 
 
1244
    def open(self, a_bzrdir, _found=False):
 
1245
        """Return the WorkingTree object for a_bzrdir
 
1246
 
 
1247
        _found is a private parameter, do not use it. It is used to indicate
 
1248
               if format probing has already been done.
 
1249
        """
 
1250
        if not _found:
 
1251
            # we are being called directly and must probe.
 
1252
            raise NotImplementedError
 
1253
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1254
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1255
        return WorkingTree(a_bzrdir.root_transport.base,
 
1256
                           _internal=True,
 
1257
                           _format=self,
 
1258
                           _bzrdir=a_bzrdir)
 
1259
 
 
1260
 
 
1261
class WorkingTreeFormat3(WorkingTreeFormat):
 
1262
    """The second working tree format updated to record a format marker.
 
1263
 
 
1264
    This format modified the hash cache from the format 1 hash cache.
 
1265
    """
 
1266
 
 
1267
    def get_format_string(self):
 
1268
        """See WorkingTreeFormat.get_format_string()."""
 
1269
        return "Bazaar-NG Working Tree format 3"
 
1270
 
 
1271
    def initialize(self, a_bzrdir):
 
1272
        """See WorkingTreeFormat.initialize()."""
 
1273
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1274
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1275
        transport = a_bzrdir.get_workingtree_transport(self)
 
1276
        control_files = LockableFiles(transport, 'lock')
 
1277
        control_files.put_utf8('format', self.get_format_string())
 
1278
        branch = a_bzrdir.open_branch()
 
1279
        revision = branch.last_revision()
 
1280
        basis_tree = branch.repository.revision_tree(revision)
 
1281
        inv = basis_tree.inventory
 
1282
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1283
                         branch,
 
1284
                         inv,
 
1285
                         _internal=True,
 
1286
                         _format=self,
 
1287
                         _bzrdir=a_bzrdir)
 
1288
        wt._write_inventory(inv)
 
1289
        wt.set_root_id(inv.root.file_id)
 
1290
        wt.set_last_revision(revision)
 
1291
        wt.set_pending_merges([])
 
1292
        wt.revert([])
 
1293
        return wt
 
1294
 
 
1295
    def __init__(self):
 
1296
        super(WorkingTreeFormat3, self).__init__()
 
1297
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1298
 
 
1299
    def open(self, a_bzrdir, _found=False):
 
1300
        """Return the WorkingTree object for a_bzrdir
 
1301
 
 
1302
        _found is a private parameter, do not use it. It is used to indicate
 
1303
               if format probing has already been done.
 
1304
        """
 
1305
        if not _found:
 
1306
            # we are being called directly and must probe.
 
1307
            raise NotImplementedError
 
1308
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1309
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1310
        return WorkingTree(a_bzrdir.root_transport.base,
 
1311
                           _internal=True,
 
1312
                           _format=self,
 
1313
                           _bzrdir=a_bzrdir)
 
1314
 
 
1315
 
 
1316
# formats which have no format string are not discoverable
 
1317
# and not independently creatable, so are not registered.
 
1318
__default_format = WorkingTreeFormat3()
 
1319
WorkingTreeFormat.register_format(__default_format)
 
1320
WorkingTreeFormat.set_default_format(__default_format)
 
1321
_legacy_formats = [WorkingTreeFormat2(),
 
1322
                   ]
 
1323
 
 
1324
 
 
1325
class WorkingTreeTestProviderAdapter(object):
 
1326
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1327
 
 
1328
    This is done by copying the test once for each transport and injecting
 
1329
    the transport_server, transport_readonly_server, and workingtree_format
 
1330
    classes into each copy. Each copy is also given a new id() to make it
 
1331
    easy to identify.
 
1332
    """
 
1333
 
 
1334
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1335
        self._transport_server = transport_server
 
1336
        self._transport_readonly_server = transport_readonly_server
 
1337
        self._formats = formats
 
1338
    
 
1339
    def adapt(self, test):
 
1340
        from bzrlib.tests import TestSuite
 
1341
        result = TestSuite()
 
1342
        for workingtree_format, bzrdir_format in self._formats:
 
1343
            new_test = deepcopy(test)
 
1344
            new_test.transport_server = self._transport_server
 
1345
            new_test.transport_readonly_server = self._transport_readonly_server
 
1346
            new_test.bzrdir_format = bzrdir_format
 
1347
            new_test.workingtree_format = workingtree_format
 
1348
            def make_new_test_id():
 
1349
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1350
                return lambda: new_id
 
1351
            new_test.id = make_new_test_id()
 
1352
            result.addTest(new_test)
 
1353
        return result