/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

Merge in bzrdir work to enable checkout improvements.

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 Branch.working_tree():
 
28
To get a WorkingTree, call bzrdir.open_workingtree() or
 
29
WorkingTree.open(dir).
29
30
"""
30
31
 
31
32
 
32
 
# TODO: Don't allow WorkingTrees to be constructed for remote branches if 
33
 
# they don't work.
34
 
 
35
33
# FIXME: I don't know if writing out the cache from the destructor is really a
36
34
# good idea, because destructors are considered poor taste in Python, and it's
37
35
# not predictable when it will be written out.
43
41
# At the momenthey may alias the inventory and have old copies of it in memory.
44
42
 
45
43
from copy import deepcopy
 
44
from cStringIO import StringIO
 
45
import errno
 
46
import fnmatch
46
47
import os
47
48
import stat
48
 
import fnmatch
49
49
 
 
50
 
 
51
from bzrlib.atomicfile import AtomicFile
50
52
from bzrlib.branch import (Branch,
51
 
                           is_control_file,
52
 
                           needs_read_lock,
53
 
                           needs_write_lock,
54
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
55
57
from bzrlib.errors import (BzrCheckError,
56
58
                           BzrError,
57
59
                           DivergedBranches,
58
60
                           WeaveRevisionNotPresent,
59
61
                           NotBranchError,
 
62
                           NoSuchFile,
60
63
                           NotVersionedError)
61
64
from bzrlib.inventory import InventoryEntry
 
65
from bzrlib.lockable_files import LockableFiles
 
66
from bzrlib.merge import merge_inner, transform_tree
62
67
from bzrlib.osutils import (appendpath,
63
68
                            compact_date,
64
69
                            file_kind,
66
71
                            getcwd,
67
72
                            pathjoin,
68
73
                            pumpfile,
 
74
                            safe_unicode,
69
75
                            splitpath,
70
76
                            rand_bytes,
71
77
                            abspath,
73
79
                            realpath,
74
80
                            relpath,
75
81
                            rename)
 
82
from bzrlib.symbol_versioning import *
76
83
from bzrlib.textui import show_status
77
84
import bzrlib.tree
78
85
from bzrlib.trace import mutter
 
86
from bzrlib.transport import get_transport
 
87
from bzrlib.transport.local import LocalTransport
79
88
import bzrlib.xml5
80
89
 
81
90
 
178
187
    not listed in the Inventory and vice versa.
179
188
    """
180
189
 
181
 
    def __init__(self, basedir=u'.', branch=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):
182
197
        """Construct a WorkingTree for basedir.
183
198
 
184
199
        If the branch is not supplied, it is opened automatically.
186
201
        (branch.base is not cross checked, because for remote branches that
187
202
        would be meaningless).
188
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
189
216
        from bzrlib.hashcache import HashCache
190
217
        from bzrlib.trace import note, mutter
191
218
        assert isinstance(basedir, basestring), \
192
219
            "base directory %r is not a string" % basedir
 
220
        basedir = safe_unicode(basedir)
 
221
        mutter("openeing working tree %r", basedir)
193
222
        if branch is None:
194
223
            branch = Branch.open(basedir)
195
224
        assert isinstance(branch, Branch), \
196
225
            "branch %r is not a Branch" % branch
197
226
        self.branch = branch
198
227
        self.basedir = realpath(basedir)
 
228
        # if branch is at our basedir and is a format 6 or less
 
229
        if isinstance(self._format, WorkingTreeFormat2):
 
230
            # share control object
 
231
            self._control_files = self.branch.control_files
 
232
        elif _control_files is not None:
 
233
            assert False, "not done yet"
 
234
#            self._control_files = _control_files
 
235
        else:
 
236
            # only ready for format 3
 
237
            assert isinstance(self._format, WorkingTreeFormat3)
 
238
            self._control_files = LockableFiles(
 
239
                self.bzrdir.get_workingtree_transport(None),
 
240
                'lock')
199
241
 
200
242
        # update the whole cache up front and write to disk if anything changed;
201
243
        # in the future we might want to do this more selectively
203
245
        # if needed, or, when the cache sees a change, append it to the hash
204
246
        # cache file, and have the parser take the most recent entry for a
205
247
        # given path only.
206
 
        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)
207
250
        hc.read()
 
251
        # is this scan needed ? it makes things kinda slow.
208
252
        hc.scan()
209
253
 
210
254
        if hc.needs_write:
211
255
            mutter("write hc")
212
256
            hc.write()
213
257
 
214
 
        self._set_inventory(self.read_working_inventory())
 
258
        if _inventory is None:
 
259
            self._set_inventory(self.read_working_inventory())
 
260
        else:
 
261
            self._set_inventory(_inventory)
215
262
 
216
263
    def _set_inventory(self, inv):
217
264
        self._inventory = inv
218
265
        self.path2id = self._inventory.path2id
219
266
 
220
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
221
278
    def open_containing(path=None):
222
279
        """Open an existing working tree which has its root about path.
223
280
        
229
286
        If there is one, it is returned, along with the unused portion of path.
230
287
        """
231
288
        if path is None:
232
 
            path = getcwd()
233
 
        else:
234
 
            # sanity check.
235
 
            if path.find('://') != -1:
236
 
                raise NotBranchError(path=path)
237
 
        path = abspath(path)
238
 
        tail = u''
239
 
        while True:
240
 
            try:
241
 
                return WorkingTree(path), tail
242
 
            except NotBranchError:
243
 
                pass
244
 
            if tail:
245
 
                tail = pathjoin(os.path.basename(path), tail)
246
 
            else:
247
 
                tail = os.path.basename(path)
248
 
            lastpath = path
249
 
            path = os.path.dirname(path)
250
 
            if lastpath == path:
251
 
                # reached the root, whatever that may be
252
 
                raise NotBranchError(path=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)
253
300
 
254
301
    def __iter__(self):
255
302
        """Iterate through file_ids for this tree.
268
315
 
269
316
    def abspath(self, filename):
270
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)
 
331
 
 
332
    @staticmethod
 
333
    @deprecated_method(zero_eight)
 
334
    def create(branch, directory):
 
335
        """Create a workingtree for branch at directory.
 
336
 
 
337
        If existing_directory already exists it must have a .bzr directory.
 
338
        If it does not exist, it will be created.
 
339
 
 
340
        This returns a new WorkingTree object for the new checkout.
 
341
 
 
342
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
343
        should accept an optional revisionid to checkout [and reject this if
 
344
        checking out into the same dir as a pre-checkout-aware branch format.]
 
345
 
 
346
        XXX: When BzrDir is present, these should be created through that 
 
347
        interface instead.
 
348
        """
 
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
 
358
 
 
359
    @staticmethod
 
360
    @deprecated_method(zero_eight)
 
361
    def create_standalone(directory):
 
362
        """Create a checkout and a branch and a repo at directory.
 
363
 
 
364
        Directory must exist and be empty.
 
365
 
 
366
        please use BzrDir.create_standalone_workingtree
 
367
        """
 
368
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
271
369
 
272
370
    def relpath(self, abs):
273
371
        """Return the local path portion from a given absolute path."""
291
389
        ## XXX: badly named; this is not in the store at all
292
390
        return self.abspath(self.id2path(file_id))
293
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
 
294
425
    @needs_write_lock
295
 
    def commit(self, *args, **kw):
 
426
    def commit(self, *args, **kwargs):
296
427
        from bzrlib.commit import Commit
297
 
        Commit().commit(self.branch, *args, **kw)
 
428
        # args for wt.commit start at message from the Commit.commit method,
 
429
        # but with branch a kwarg now, passing in args as is results in the
 
430
        #message being used for the branch
 
431
        args = (DEPRECATED_PARAMETER, ) + args
 
432
        Commit().commit(working_tree=self, *args, **kwargs)
298
433
        self._set_inventory(self.read_working_inventory())
299
434
 
300
435
    def id2abspath(self, file_id):
381
516
 
382
517
            try:
383
518
                kind = file_kind(fullpath)
384
 
            except OSError:
 
519
            except OSError, e:
 
520
                if e.errno == errno.ENOENT:
 
521
                    raise NoSuchFile(fullpath)
385
522
                # maybe something better?
386
523
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
387
524
 
410
547
        if updated:
411
548
            self.set_pending_merges(p)
412
549
 
 
550
    @needs_read_lock
413
551
    def pending_merges(self):
414
552
        """Return a list of pending merges.
415
553
 
416
554
        These are revisions that have been merged into the working
417
555
        directory but not yet committed.
418
556
        """
419
 
        cfn = self.branch._rel_controlfilename('pending-merges')
420
 
        if not self.branch._transport.has(cfn):
 
557
        try:
 
558
            merges_file = self._control_files.get_utf8('pending-merges')
 
559
        except OSError, e:
 
560
            if e.errno != errno.ENOENT:
 
561
                raise
421
562
            return []
422
563
        p = []
423
 
        for l in self.branch.controlfile('pending-merges', 'r').readlines():
 
564
        for l in merges_file.readlines():
424
565
            p.append(l.rstrip('\n'))
425
566
        return p
426
567
 
427
568
    @needs_write_lock
428
569
    def set_pending_merges(self, rev_list):
429
 
        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
 
570
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
430
571
 
431
572
    def get_symlink_target(self, file_id):
432
573
        return os.readlink(self.id2abspath(file_id))
439
580
        else:
440
581
            return '?'
441
582
 
442
 
 
443
583
    def list_files(self):
444
584
        """Recursively list all files as (path, class, kind, id).
445
585
 
636
776
        These are files in the working directory that are not versioned or
637
777
        control files or ignored.
638
778
        
639
 
        >>> from bzrlib.branch import ScratchBranch
640
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
779
        >>> from bzrlib.bzrdir import ScratchDir
 
780
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
781
        >>> b = d.open_branch()
641
782
        >>> tree = WorkingTree(b.base, b)
642
783
        >>> map(str, tree.unknowns())
643
784
        ['foo']
664
805
 
665
806
    @needs_write_lock
666
807
    def pull(self, source, overwrite=False):
667
 
        from bzrlib.merge import merge_inner
668
808
        source.lock_read()
669
809
        try:
670
810
            old_revision_history = self.branch.revision_history()
675
815
                    other_revision = old_revision_history[-1]
676
816
                else:
677
817
                    other_revision = None
 
818
                repository = self.branch.repository
678
819
                merge_inner(self.branch,
679
 
                            self.branch.basis_tree(), 
680
 
                            self.branch.revision_tree(other_revision))
 
820
                            self.basis_tree(), 
 
821
                            repository.revision_tree(other_revision),
 
822
                            this_tree=self)
 
823
                self.set_last_revision(self.branch.last_revision())
681
824
            return count
682
825
        finally:
683
826
            source.unlock()
776
919
    def kind(self, file_id):
777
920
        return file_kind(self.id2abspath(file_id))
778
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
 
779
931
    def lock_read(self):
780
932
        """See Branch.lock_read, and WorkingTree.unlock."""
781
 
        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
782
939
 
783
940
    def lock_write(self):
784
941
        """See Branch.lock_write, and WorkingTree.unlock."""
785
 
        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
786
948
 
787
949
    def _basis_inventory_name(self, revision_id):
788
950
        return 'basis-inventory.%s' % revision_id
789
951
 
790
952
    def set_last_revision(self, new_revision, old_revision=None):
791
 
        if old_revision:
 
953
        if old_revision is not None:
792
954
            try:
793
955
                path = self._basis_inventory_name(old_revision)
794
 
                path = self.branch._rel_controlfilename(path)
795
 
                self.branch._transport.delete(path)
796
 
            except:
 
956
                path = self._control_files._escape(path)
 
957
                self._control_files._transport.delete(path)
 
958
            except NoSuchFile:
797
959
                pass
798
 
        try:
799
 
            xml = self.branch.get_inventory_xml(new_revision)
 
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])
 
970
        try:
 
971
            xml = self.branch.repository.get_inventory_xml(new_revision)
800
972
            path = self._basis_inventory_name(new_revision)
801
 
            self.branch.put_controlfile(path, xml)
 
973
            self._control_files.put_utf8(path, xml)
802
974
        except WeaveRevisionNotPresent:
803
975
            pass
804
976
 
805
977
    def read_basis_inventory(self, revision_id):
806
978
        """Read the cached basis inventory."""
807
979
        path = self._basis_inventory_name(revision_id)
808
 
        return self.branch.controlfile(path, 'r').read()
 
980
        return self._control_files.get_utf8(path).read()
809
981
        
810
982
    @needs_read_lock
811
983
    def read_working_inventory(self):
812
984
        """Read the working inventory."""
813
985
        # ElementTree does its own conversion from UTF-8, so open in
814
986
        # binary.
815
 
        f = self.branch.controlfile('inventory', 'rb')
816
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
 
987
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
988
            self._control_files.get('inventory'))
 
989
        self._set_inventory(result)
 
990
        return result
817
991
 
818
992
    @needs_write_lock
819
993
    def remove(self, files, verbose=False):
860
1034
    def revert(self, filenames, old_tree=None, backups=True):
861
1035
        from bzrlib.merge import merge_inner
862
1036
        if old_tree is None:
863
 
            old_tree = self.branch.basis_tree()
 
1037
            old_tree = self.basis_tree()
864
1038
        merge_inner(self.branch, old_tree,
865
1039
                    self, ignore_zero=True,
866
1040
                    backup_files=backups, 
867
 
                    interesting_files=filenames)
 
1041
                    interesting_files=filenames,
 
1042
                    this_tree=self)
868
1043
        if not len(filenames):
869
1044
            self.set_pending_merges([])
870
1045
 
901
1076
        inv._byid[inv.root.file_id] = inv.root
902
1077
        for fid in inv:
903
1078
            entry = inv[fid]
904
 
            if entry.parent_id in (None, orig_root_id):
 
1079
            if entry.parent_id == orig_root_id:
905
1080
                entry.parent_id = inv.root.file_id
906
1081
        self._write_inventory(inv)
907
1082
 
914
1089
        between multiple working trees, i.e. via shared storage, then we 
915
1090
        would probably want to lock both the local tree, and the branch.
916
1091
        """
917
 
        if self._hashcache.needs_write:
 
1092
        # FIXME: We want to write out the hashcache only when the last lock on
 
1093
        # this working copy is released.  Peeking at the lock count is a bit
 
1094
        # of a nasty hack; probably it's better to have a transaction object,
 
1095
        # which can do some finalization when it's either successfully or
 
1096
        # unsuccessfully completed.  (Denys's original patch did that.)
 
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)):
918
1105
            self._hashcache.write()
919
 
        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
920
1112
 
921
1113
    @needs_write_lock
922
1114
    def _write_inventory(self, inv):
923
1115
        """Write inventory as the current inventory."""
924
 
        from cStringIO import StringIO
925
 
        from bzrlib.atomicfile import AtomicFile
926
1116
        sio = StringIO()
927
1117
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
928
1118
        sio.seek(0)
929
 
        f = AtomicFile(self.branch.controlfilename('inventory'))
930
 
        try:
931
 
            pumpfile(sio, f)
932
 
            f.commit()
933
 
        finally:
934
 
            f.close()
 
1119
        self._control_files.put('inventory', sio)
935
1120
        self._set_inventory(inv)
936
1121
        mutter('wrote working inventory')
937
1122
            
941
1126
    for suffix in CONFLICT_SUFFIXES:
942
1127
        if path.endswith(suffix):
943
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