~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: John Arbash Meinel
  • Date: 2006-03-08 14:31:23 UTC
  • mfrom: (1598 +trunk)
  • mto: (1685.1.1 bzr-encoding)
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: john@arbash-meinel.com-20060308143123-448308b0db4de410
[merge] bzr.dev 1573, lots of updates

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
 
 
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
31
33
 
32
34
# FIXME: I don't know if writing out the cache from the destructor is really a
33
35
# good idea, because destructors are considered poor taste in Python, and it's
50
52
 
51
53
from bzrlib.atomicfile import AtomicFile
52
54
from bzrlib.branch import (Branch,
53
 
                           BzrBranchFormat4,
54
 
                           BzrBranchFormat5,
55
 
                           BzrBranchFormat6,
56
 
                           is_control_file,
57
55
                           quotefn)
 
56
import bzrlib.bzrdir as bzrdir
58
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
58
import bzrlib.errors as errors
59
59
from bzrlib.errors import (BzrCheckError,
60
60
                           BzrError,
61
61
                           DivergedBranches,
62
62
                           WeaveRevisionNotPresent,
63
63
                           NotBranchError,
64
64
                           NoSuchFile,
65
 
                           NotVersionedError)
66
 
from bzrlib.inventory import InventoryEntry
67
 
from bzrlib.lockable_files import LockableFiles
68
 
from bzrlib.osutils import (appendpath,
 
65
                           NotVersionedError,
 
66
                           MergeModifiedFormatError)
 
67
from bzrlib.inventory import InventoryEntry, Inventory
 
68
from bzrlib.lockable_files import LockableFiles, TransportLock
 
69
from bzrlib.merge import merge_inner, transform_tree
 
70
from bzrlib.osutils import (
 
71
                            abspath,
 
72
                            appendpath,
69
73
                            compact_date,
70
74
                            file_kind,
71
75
                            isdir,
75
79
                            safe_unicode,
76
80
                            splitpath,
77
81
                            rand_bytes,
78
 
                            abspath,
79
82
                            normpath,
80
83
                            realpath,
81
84
                            relpath,
82
 
                            rename)
 
85
                            rename,
 
86
                            supports_executable,
 
87
                            )
 
88
from bzrlib.progress import DummyProgress
 
89
from bzrlib.revision import NULL_REVISION
 
90
from bzrlib.rio import RioReader, RioWriter, Stanza
83
91
from bzrlib.symbol_versioning import *
84
92
from bzrlib.textui import show_status
85
93
import bzrlib.tree
86
94
from bzrlib.trace import mutter
 
95
from bzrlib.transform import build_tree
87
96
from bzrlib.transport import get_transport
 
97
from bzrlib.transport.local import LocalTransport
 
98
import bzrlib.ui
88
99
import bzrlib.xml5
89
100
 
90
101
_non_word_re = None
202
213
    not listed in the Inventory and vice versa.
203
214
    """
204
215
 
205
 
    def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
 
216
    def __init__(self, basedir='.',
 
217
                 branch=DEPRECATED_PARAMETER,
 
218
                 _inventory=None,
 
219
                 _control_files=None,
 
220
                 _internal=False,
 
221
                 _format=None,
 
222
                 _bzrdir=None):
206
223
        """Construct a WorkingTree for basedir.
207
224
 
208
225
        If the branch is not supplied, it is opened automatically.
210
227
        (branch.base is not cross checked, because for remote branches that
211
228
        would be meaningless).
212
229
        """
 
230
        self._format = _format
 
231
        self.bzrdir = _bzrdir
 
232
        if not _internal:
 
233
            # not created via open etc.
 
234
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
235
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
236
                 DeprecationWarning,
 
237
                 stacklevel=2)
 
238
            wt = WorkingTree.open(basedir)
 
239
            self.branch = wt.branch
 
240
            self.basedir = wt.basedir
 
241
            self._control_files = wt._control_files
 
242
            self._hashcache = wt._hashcache
 
243
            self._set_inventory(wt._inventory)
 
244
            self._format = wt._format
 
245
            self.bzrdir = wt.bzrdir
213
246
        from bzrlib.hashcache import HashCache
214
247
        from bzrlib.trace import note, mutter
215
248
        assert isinstance(basedir, basestring), \
216
249
            "base directory %r is not a string" % basedir
217
250
        basedir = safe_unicode(basedir)
218
 
        mutter("openeing working tree %r", basedir)
219
 
        if branch is None:
220
 
            branch = Branch.open(basedir)
221
 
        assert isinstance(branch, Branch), \
222
 
            "branch %r is not a Branch" % branch
223
 
        self.branch = branch
 
251
        mutter("opening working tree %r", basedir)
 
252
        if deprecated_passed(branch):
 
253
            if not _internal:
 
254
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
255
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
 
256
                     DeprecationWarning,
 
257
                     stacklevel=2
 
258
                     )
 
259
            self.branch = branch
 
260
        else:
 
261
            self.branch = self.bzrdir.open_branch()
 
262
        assert isinstance(self.branch, Branch), \
 
263
            "branch %r is not a Branch" % self.branch
224
264
        self.basedir = realpath(basedir)
225
265
        # if branch is at our basedir and is a format 6 or less
226
 
        if (isinstance(self.branch._branch_format,
227
 
                       (BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
228
 
            # might be able to share control object
229
 
            and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
 
266
        if isinstance(self._format, WorkingTreeFormat2):
 
267
            # share control object
230
268
            self._control_files = self.branch.control_files
231
269
        elif _control_files is not None:
232
270
            assert False, "not done yet"
233
271
#            self._control_files = _control_files
234
272
        else:
 
273
            # only ready for format 3
 
274
            assert isinstance(self._format, WorkingTreeFormat3)
235
275
            self._control_files = LockableFiles(
236
 
                get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
 
276
                self.bzrdir.get_workingtree_transport(None),
 
277
                'lock', TransportLock)
237
278
 
238
279
        # update the whole cache up front and write to disk if anything changed;
239
280
        # in the future we might want to do this more selectively
241
282
        # if needed, or, when the cache sees a change, append it to the hash
242
283
        # cache file, and have the parser take the most recent entry for a
243
284
        # given path only.
244
 
        hc = self._hashcache = HashCache(basedir)
 
285
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
286
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
245
287
        hc.read()
 
288
        # is this scan needed ? it makes things kinda slow.
246
289
        hc.scan()
247
290
 
248
291
        if hc.needs_write:
258
301
        self._inventory = inv
259
302
        self.path2id = self._inventory.path2id
260
303
 
 
304
    def is_control_filename(self, filename):
 
305
        """True if filename is the name of a control file in this tree.
 
306
        
 
307
        This is true IF and ONLY IF the filename is part of the meta data
 
308
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
309
        on disk will not be a control file for this tree.
 
310
        """
 
311
        try:
 
312
            self.bzrdir.transport.relpath(self.abspath(filename))
 
313
            return True
 
314
        except errors.PathNotChild:
 
315
            return False
 
316
 
 
317
    @staticmethod
 
318
    def open(path=None, _unsupported=False):
 
319
        """Open an existing working tree at path.
 
320
 
 
321
        """
 
322
        if path is None:
 
323
            path = os.path.getcwdu()
 
324
        control = bzrdir.BzrDir.open(path, _unsupported)
 
325
        return control.open_workingtree(_unsupported)
 
326
        
261
327
    @staticmethod
262
328
    def open_containing(path=None):
263
329
        """Open an existing working tree which has its root about path.
270
336
        If there is one, it is returned, along with the unused portion of path.
271
337
        """
272
338
        if path is None:
273
 
            path = getcwd()
274
 
        else:
275
 
            # sanity check.
276
 
            if path.find('://') != -1:
277
 
                raise NotBranchError(path=path)
278
 
        path = abspath(path)
279
 
        orig_path = path[:]
280
 
        tail = u''
281
 
        while True:
282
 
            try:
283
 
                return WorkingTree(path), tail
284
 
            except NotBranchError:
285
 
                pass
286
 
            if tail:
287
 
                tail = pathjoin(os.path.basename(path), tail)
288
 
            else:
289
 
                tail = os.path.basename(path)
290
 
            lastpath = path
291
 
            path = os.path.dirname(path)
292
 
            if lastpath == path:
293
 
                # reached the root, whatever that may be
294
 
                raise NotBranchError(path=orig_path)
 
339
            path = os.getcwdu()
 
340
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
341
        return control.open_workingtree(), relpath
 
342
 
 
343
    @staticmethod
 
344
    def open_downlevel(path=None):
 
345
        """Open an unsupported working tree.
 
346
 
 
347
        Only intended for advanced situations like upgrading part of a bzrdir.
 
348
        """
 
349
        return WorkingTree.open(path, _unsupported=True)
295
350
 
296
351
    def __iter__(self):
297
352
        """Iterate through file_ids for this tree.
310
365
 
311
366
    def abspath(self, filename):
312
367
        return pathjoin(self.basedir, filename)
 
368
    
 
369
    def basis_tree(self):
 
370
        """Return RevisionTree for the current last revision."""
 
371
        revision_id = self.last_revision()
 
372
        if revision_id is not None:
 
373
            try:
 
374
                xml = self.read_basis_inventory(revision_id)
 
375
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
376
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
377
                                                revision_id)
 
378
            except NoSuchFile:
 
379
                pass
 
380
        return self.branch.repository.revision_tree(revision_id)
313
381
 
314
382
    @staticmethod
 
383
    @deprecated_method(zero_eight)
315
384
    def create(branch, directory):
316
385
        """Create a workingtree for branch at directory.
317
386
 
327
396
        XXX: When BzrDir is present, these should be created through that 
328
397
        interface instead.
329
398
        """
330
 
        try:
331
 
            os.mkdir(directory)
332
 
        except OSError, e:
333
 
            if e.errno != errno.EEXIST:
334
 
                raise
335
 
        try:
336
 
            os.mkdir(pathjoin(directory, '.bzr'))
337
 
        except OSError, e:
338
 
            if e.errno != errno.EEXIST:
339
 
                raise
340
 
        inv = branch.repository.revision_tree(branch.last_revision()).inventory
341
 
        wt = WorkingTree(directory, branch, inv)
342
 
        wt._write_inventory(inv)
343
 
        if branch.last_revision() is not None:
344
 
            wt.set_last_revision(branch.last_revision())
345
 
        wt.set_pending_merges([])
346
 
        wt.revert([])
347
 
        return wt
 
399
        warn('delete WorkingTree.create', stacklevel=3)
 
400
        transport = get_transport(directory)
 
401
        if branch.bzrdir.root_transport.base == transport.base:
 
402
            # same dir 
 
403
            return branch.bzrdir.create_workingtree()
 
404
        # different directory, 
 
405
        # create a branch reference
 
406
        # and now a working tree.
 
407
        raise NotImplementedError
348
408
 
349
409
    @staticmethod
 
410
    @deprecated_method(zero_eight)
350
411
    def create_standalone(directory):
351
 
        """Create a checkout and a branch at directory.
 
412
        """Create a checkout and a branch and a repo at directory.
352
413
 
353
414
        Directory must exist and be empty.
354
415
 
355
 
        XXX: When BzrDir is present, these should be created through that 
356
 
        interface instead.
 
416
        please use BzrDir.create_standalone_workingtree
357
417
        """
358
 
        directory = safe_unicode(directory)
359
 
        b = Branch.create(directory)
360
 
        return WorkingTree.create(b, directory)
 
418
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
361
419
 
362
420
    def relpath(self, abs):
363
421
        """Return the local path portion from a given absolute path."""
381
439
        ## XXX: badly named; this is not in the store at all
382
440
        return self.abspath(self.id2path(file_id))
383
441
 
 
442
    @needs_read_lock
 
443
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
444
        """Duplicate this working tree into to_bzr, including all state.
 
445
        
 
446
        Specifically modified files are kept as modified, but
 
447
        ignored and unknown files are discarded.
 
448
 
 
449
        If you want to make a new line of development, see bzrdir.sprout()
 
450
 
 
451
        revision
 
452
            If not None, the cloned tree will have its last revision set to 
 
453
            revision, and and difference between the source trees last revision
 
454
            and this one merged in.
 
455
 
 
456
        basis
 
457
            If not None, a closer copy of a tree which may have some files in
 
458
            common, and which file content should be preferentially copied from.
 
459
        """
 
460
        # assumes the target bzr dir format is compatible.
 
461
        result = self._format.initialize(to_bzrdir)
 
462
        self.copy_content_into(result, revision_id)
 
463
        return result
 
464
 
 
465
    @needs_read_lock
 
466
    def copy_content_into(self, tree, revision_id=None):
 
467
        """Copy the current content and user files of this tree into tree."""
 
468
        if revision_id is None:
 
469
            transform_tree(tree, self)
 
470
        else:
 
471
            # TODO now merge from tree.last_revision to revision
 
472
            transform_tree(tree, self)
 
473
            tree.set_last_revision(revision_id)
 
474
 
384
475
    @needs_write_lock
385
 
    def commit(self, *args, **kwargs):
 
476
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
477
        # avoid circular imports
386
478
        from bzrlib.commit import Commit
 
479
        if revprops is None:
 
480
            revprops = {}
 
481
        if not 'branch-nick' in revprops:
 
482
            revprops['branch-nick'] = self.branch.nick
387
483
        # args for wt.commit start at message from the Commit.commit method,
388
484
        # but with branch a kwarg now, passing in args as is results in the
389
485
        #message being used for the branch
390
 
        args = (DEPRECATED_PARAMETER, ) + args
391
 
        Commit().commit(working_tree=self, *args, **kwargs)
 
486
        args = (DEPRECATED_PARAMETER, message, ) + args
 
487
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
392
488
        self._set_inventory(self.read_working_inventory())
393
489
 
394
490
    def id2abspath(self, file_id):
418
514
        return self._hashcache.get_sha1(path)
419
515
 
420
516
    def is_executable(self, file_id):
421
 
        if os.name == "nt":
 
517
        if not supports_executable():
422
518
            return self._inventory[file_id].executable
423
519
        else:
424
520
            path = self._inventory.id2path(file_id)
463
559
 
464
560
        inv = self.read_working_inventory()
465
561
        for f,file_id in zip(files, ids):
466
 
            if is_control_file(f):
 
562
            if self.is_control_filename(f):
467
563
                raise BzrError("cannot add control file %s" % quotefn(f))
468
564
 
469
565
            fp = splitpath(f)
475
571
 
476
572
            try:
477
573
                kind = file_kind(fullpath)
478
 
            except OSError:
 
574
            except OSError, e:
 
575
                if e.errno == errno.ENOENT:
 
576
                    raise NoSuchFile(fullpath)
479
577
                # maybe something better?
480
578
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
481
579
 
526
624
    def set_pending_merges(self, rev_list):
527
625
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
528
626
 
 
627
    @needs_write_lock
 
628
    def set_merge_modified(self, modified_hashes):
 
629
        my_file = StringIO()
 
630
        my_file.write(MERGE_MODIFIED_HEADER_1 + '\n')
 
631
        writer = RioWriter(my_file)
 
632
        for file_id, hash in modified_hashes.iteritems():
 
633
            s = Stanza(file_id=file_id, hash=hash)
 
634
            writer.write_stanza(s)
 
635
        my_file.seek(0)
 
636
        self._control_files.put('merge-hashes', my_file)
 
637
 
 
638
    @needs_read_lock
 
639
    def merge_modified(self):
 
640
        try:
 
641
            hashfile = self._control_files.get('merge-hashes')
 
642
        except NoSuchFile:
 
643
            return {}
 
644
        merge_hashes = {}
 
645
        try:
 
646
            if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
 
647
                raise MergeModifiedFormatError()
 
648
        except StopIteration:
 
649
            raise MergeModifiedFormatError()
 
650
        for s in RioReader(hashfile):
 
651
            file_id = s.get("file_id")
 
652
            hash = s.get("hash")
 
653
            if hash == self.get_file_sha1(file_id):
 
654
                merge_hashes[file_id] = hash
 
655
        return merge_hashes
 
656
 
529
657
    def get_symlink_target(self, file_id):
530
658
        return os.readlink(self.id2abspath(file_id))
531
659
 
537
665
        else:
538
666
            return '?'
539
667
 
540
 
 
541
668
    def list_files(self):
542
669
        """Recursively list all files as (path, class, kind, id).
543
670
 
557
684
                ## TODO: If we find a subdirectory with its own .bzr
558
685
                ## directory, then that is a separate tree and we
559
686
                ## should exclude it.
560
 
                if bzrlib.BZRDIR == f:
 
687
 
 
688
                # the bzrdir for this tree
 
689
                if self.bzrdir.transport.base.endswith(f + '/'):
561
690
                    continue
562
691
 
563
692
                # path within tree
734
863
        These are files in the working directory that are not versioned or
735
864
        control files or ignored.
736
865
        
737
 
        >>> from bzrlib.branch import ScratchBranch
738
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
739
 
        >>> tree = WorkingTree(b.base, b)
 
866
        >>> from bzrlib.bzrdir import ScratchDir
 
867
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
868
        >>> b = d.open_branch()
 
869
        >>> tree = d.open_workingtree()
740
870
        >>> map(str, tree.unknowns())
741
871
        ['foo']
742
872
        >>> tree.add('foo')
762
892
 
763
893
    @needs_write_lock
764
894
    def pull(self, source, overwrite=False, stop_revision=None):
765
 
        from bzrlib.merge import merge_inner
766
895
        source.lock_read()
767
896
        try:
768
897
            old_revision_history = self.branch.revision_history()
769
 
            count = self.branch.pull(source, overwrite,stop_revision)
 
898
            basis_tree = self.basis_tree()
 
899
            count = self.branch.pull(source, overwrite, stop_revision)
770
900
            new_revision_history = self.branch.revision_history()
771
901
            if new_revision_history != old_revision_history:
772
902
                if len(old_revision_history):
774
904
                else:
775
905
                    other_revision = None
776
906
                repository = self.branch.repository
777
 
                merge_inner(self.branch,
778
 
                            self.branch.basis_tree(), 
779
 
                            repository.revision_tree(other_revision),
780
 
                            this_tree=self)
 
907
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
908
                try:
 
909
                    merge_inner(self.branch,
 
910
                                self.branch.basis_tree(),
 
911
                                basis_tree, 
 
912
                                this_tree=self, 
 
913
                                pb=pb)
 
914
                finally:
 
915
                    pb.finished()
781
916
                self.set_last_revision(self.branch.last_revision())
782
917
            return count
783
918
        finally:
882
1017
    def kind(self, file_id):
883
1018
        return file_kind(self.id2abspath(file_id))
884
1019
 
 
1020
    @needs_read_lock
 
1021
    def last_revision(self):
 
1022
        """Return the last revision id of this working tree.
 
1023
 
 
1024
        In early branch formats this was == the branch last_revision,
 
1025
        but that cannot be relied upon - for working tree operations,
 
1026
        always use tree.last_revision().
 
1027
        """
 
1028
        return self.branch.last_revision()
 
1029
 
885
1030
    def lock_read(self):
886
1031
        """See Branch.lock_read, and WorkingTree.unlock."""
887
 
        return self.branch.lock_read()
 
1032
        self.branch.lock_read()
 
1033
        try:
 
1034
            return self._control_files.lock_read()
 
1035
        except:
 
1036
            self.branch.unlock()
 
1037
            raise
888
1038
 
889
1039
    def lock_write(self):
890
1040
        """See Branch.lock_write, and WorkingTree.unlock."""
891
 
        return self.branch.lock_write()
 
1041
        self.branch.lock_write()
 
1042
        try:
 
1043
            return self._control_files.lock_write()
 
1044
        except:
 
1045
            self.branch.unlock()
 
1046
            raise
892
1047
 
893
1048
    def _basis_inventory_name(self, revision_id):
894
1049
        return 'basis-inventory.%s' % revision_id
895
1050
 
 
1051
    @needs_write_lock
896
1052
    def set_last_revision(self, new_revision, old_revision=None):
897
 
        if old_revision is not None:
898
 
            try:
899
 
                path = self._basis_inventory_name(old_revision)
900
 
                path = self.branch.control_files._escape(path)
901
 
                self.branch.control_files._transport.delete(path)
902
 
            except NoSuchFile:
903
 
                pass
 
1053
        """Change the last revision in the working tree."""
 
1054
        self._remove_old_basis(old_revision)
 
1055
        if self._change_last_revision(new_revision):
 
1056
            self._cache_basis_inventory(new_revision)
 
1057
 
 
1058
    def _change_last_revision(self, new_revision):
 
1059
        """Template method part of set_last_revision to perform the change."""
 
1060
        if new_revision is None:
 
1061
            self.branch.set_revision_history([])
 
1062
            return False
 
1063
        # current format is locked in with the branch
 
1064
        revision_history = self.branch.revision_history()
 
1065
        try:
 
1066
            position = revision_history.index(new_revision)
 
1067
        except ValueError:
 
1068
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1069
        self.branch.set_revision_history(revision_history[:position + 1])
 
1070
        return True
 
1071
 
 
1072
    def _cache_basis_inventory(self, new_revision):
 
1073
        """Cache new_revision as the basis inventory."""
904
1074
        try:
905
1075
            xml = self.branch.repository.get_inventory_xml(new_revision)
906
1076
            path = self._basis_inventory_name(new_revision)
907
 
            self.branch.control_files.put_utf8(path, xml)
 
1077
            self._control_files.put_utf8(path, xml)
908
1078
        except WeaveRevisionNotPresent:
909
1079
            pass
910
1080
 
 
1081
    def _remove_old_basis(self, old_revision):
 
1082
        """Remove the old basis inventory 'old_revision'."""
 
1083
        if old_revision is not None:
 
1084
            try:
 
1085
                path = self._basis_inventory_name(old_revision)
 
1086
                path = self._control_files._escape(path)
 
1087
                self._control_files._transport.delete(path)
 
1088
            except NoSuchFile:
 
1089
                pass
 
1090
 
911
1091
    def read_basis_inventory(self, revision_id):
912
1092
        """Read the cached basis inventory."""
913
1093
        path = self._basis_inventory_name(revision_id)
914
 
        return self.branch.control_files.get_utf8(path).read()
 
1094
        return self._control_files.get_utf8(path).read()
915
1095
        
916
1096
    @needs_read_lock
917
1097
    def read_working_inventory(self):
918
1098
        """Read the working inventory."""
919
1099
        # ElementTree does its own conversion from UTF-8, so open in
920
1100
        # binary.
921
 
        return bzrlib.xml5.serializer_v5.read_inventory(
 
1101
        result = bzrlib.xml5.serializer_v5.read_inventory(
922
1102
            self._control_files.get('inventory'))
 
1103
        self._set_inventory(result)
 
1104
        return result
923
1105
 
924
1106
    @needs_write_lock
925
1107
    def remove(self, files, verbose=False):
963
1145
        self._write_inventory(inv)
964
1146
 
965
1147
    @needs_write_lock
966
 
    def revert(self, filenames, old_tree=None, backups=True):
967
 
        from bzrlib.merge import merge_inner
 
1148
    def revert(self, filenames, old_tree=None, backups=True, 
 
1149
               pb=DummyProgress()):
 
1150
        from transform import revert
968
1151
        if old_tree is None:
969
 
            old_tree = self.branch.basis_tree()
970
 
        merge_inner(self.branch, old_tree,
971
 
                    self, ignore_zero=True,
972
 
                    backup_files=backups, 
973
 
                    interesting_files=filenames,
974
 
                    this_tree=self)
 
1152
            old_tree = self.basis_tree()
 
1153
        revert(self, old_tree, filenames, backups, pb)
975
1154
        if not len(filenames):
976
1155
            self.set_pending_merges([])
977
1156
 
1008
1187
        inv._byid[inv.root.file_id] = inv.root
1009
1188
        for fid in inv:
1010
1189
            entry = inv[fid]
1011
 
            if entry.parent_id in (None, orig_root_id):
 
1190
            if entry.parent_id == orig_root_id:
1012
1191
                entry.parent_id = inv.root.file_id
1013
1192
        self._write_inventory(inv)
1014
1193
 
1026
1205
        # of a nasty hack; probably it's better to have a transaction object,
1027
1206
        # which can do some finalization when it's either successfully or
1028
1207
        # unsuccessfully completed.  (Denys's original patch did that.)
1029
 
        if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
 
1208
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1209
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1210
        
 
1211
        # TODO: split this per format so there is no ugly if block
 
1212
        if self._hashcache.needs_write and (
 
1213
            # dedicated lock files
 
1214
            self._control_files._lock_count==1 or 
 
1215
            # shared lock files
 
1216
            (self._control_files is self.branch.control_files and 
 
1217
             self._control_files._lock_count==3)):
1030
1218
            self._hashcache.write()
1031
 
        return self.branch.unlock()
 
1219
        # reverse order of locking.
 
1220
        result = self._control_files.unlock()
 
1221
        try:
 
1222
            self.branch.unlock()
 
1223
        finally:
 
1224
            return result
 
1225
 
 
1226
    @needs_write_lock
 
1227
    def update(self):
 
1228
        """Update a working tree along its branch.
 
1229
 
 
1230
        This will update the branch if its bound too, which means we have multiple trees involved:
 
1231
        The new basis tree of the master.
 
1232
        The old basis tree of the branch.
 
1233
        The old basis tree of the working tree.
 
1234
        The current working tree state.
 
1235
        pathologically all three may be different, and non ancestors of each other.
 
1236
        Conceptually we want to:
 
1237
        Preserve the wt.basis->wt.state changes
 
1238
        Transform the wt.basis to the new master basis.
 
1239
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
 
1240
        Restore the wt.basis->wt.state changes.
 
1241
 
 
1242
        There isn't a single operation at the moment to do that, so we:
 
1243
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
 
1244
        Do a 'normal' merge of the old branch basis if it is relevant.
 
1245
        """
 
1246
        old_tip = self.branch.update()
 
1247
        if old_tip is not None:
 
1248
            self.add_pending_merge(old_tip)
 
1249
        self.branch.lock_read()
 
1250
        try:
 
1251
            result = 0
 
1252
            if self.last_revision() != self.branch.last_revision():
 
1253
                # merge tree state up to new branch tip.
 
1254
                basis = self.basis_tree()
 
1255
                to_tree = self.branch.basis_tree()
 
1256
                result += merge_inner(self.branch,
 
1257
                                      to_tree,
 
1258
                                      basis,
 
1259
                                      this_tree=self)
 
1260
                self.set_last_revision(self.branch.last_revision())
 
1261
            if old_tip and old_tip != self.last_revision():
 
1262
                # our last revision was not the prior branch last reivison
 
1263
                # and we have converted that last revision to a pending merge.
 
1264
                # base is somewhere between the branch tip now
 
1265
                # and the now pending merge
 
1266
                from bzrlib.revision import common_ancestor
 
1267
                try:
 
1268
                    base_rev_id = common_ancestor(self.branch.last_revision(),
 
1269
                                                  old_tip,
 
1270
                                                  self.branch.repository)
 
1271
                except errors.NoCommonAncestor:
 
1272
                    base_rev_id = None
 
1273
                base_tree = self.branch.repository.revision_tree(base_rev_id)
 
1274
                other_tree = self.branch.repository.revision_tree(old_tip)
 
1275
                result += merge_inner(self.branch,
 
1276
                                      other_tree,
 
1277
                                      base_tree,
 
1278
                                      this_tree=self)
 
1279
            return result
 
1280
        finally:
 
1281
            self.branch.unlock()
1032
1282
 
1033
1283
    @needs_write_lock
1034
1284
    def _write_inventory(self, inv):
1039
1289
        self._control_files.put('inventory', sio)
1040
1290
        self._set_inventory(inv)
1041
1291
        mutter('wrote working inventory')
1042
 
            
 
1292
 
 
1293
 
 
1294
class WorkingTree3(WorkingTree):
 
1295
    """This is the Format 3 working tree.
 
1296
 
 
1297
    This differs from the base WorkingTree by:
 
1298
     - having its own file lock
 
1299
     - having its own last-revision property.
 
1300
    """
 
1301
 
 
1302
    @needs_read_lock
 
1303
    def last_revision(self):
 
1304
        """See WorkingTree.last_revision."""
 
1305
        try:
 
1306
            return self._control_files.get_utf8('last-revision').read()
 
1307
        except NoSuchFile:
 
1308
            return None
 
1309
 
 
1310
    def _change_last_revision(self, revision_id):
 
1311
        """See WorkingTree._change_last_revision."""
 
1312
        if revision_id is None or revision_id == NULL_REVISION:
 
1313
            try:
 
1314
                self._control_files._transport.delete('last-revision')
 
1315
            except errors.NoSuchFile:
 
1316
                pass
 
1317
            return False
 
1318
        else:
 
1319
            try:
 
1320
                self.branch.revision_history().index(revision_id)
 
1321
            except ValueError:
 
1322
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1323
            self._control_files.put_utf8('last-revision', revision_id)
 
1324
            return True
 
1325
 
1043
1326
 
1044
1327
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1045
1328
def get_conflicted_stem(path):
1046
1329
    for suffix in CONFLICT_SUFFIXES:
1047
1330
        if path.endswith(suffix):
1048
1331
            return path[:-len(suffix)]
 
1332
 
 
1333
@deprecated_function(zero_eight)
 
1334
def is_control_file(filename):
 
1335
    """See WorkingTree.is_control_filename(filename)."""
 
1336
    ## FIXME: better check
 
1337
    filename = normpath(filename)
 
1338
    while filename != '':
 
1339
        head, tail = os.path.split(filename)
 
1340
        ## mutter('check %r for control file' % ((head, tail),))
 
1341
        if tail == '.bzr':
 
1342
            return True
 
1343
        if filename == head:
 
1344
            break
 
1345
        filename = head
 
1346
    return False
 
1347
 
 
1348
 
 
1349
class WorkingTreeFormat(object):
 
1350
    """An encapsulation of the initialization and open routines for a format.
 
1351
 
 
1352
    Formats provide three things:
 
1353
     * An initialization routine,
 
1354
     * a format string,
 
1355
     * an open routine.
 
1356
 
 
1357
    Formats are placed in an dict by their format string for reference 
 
1358
    during workingtree opening. Its not required that these be instances, they
 
1359
    can be classes themselves with class methods - it simply depends on 
 
1360
    whether state is needed for a given format or not.
 
1361
 
 
1362
    Once a format is deprecated, just deprecate the initialize and open
 
1363
    methods on the format class. Do not deprecate the object, as the 
 
1364
    object will be created every time regardless.
 
1365
    """
 
1366
 
 
1367
    _default_format = None
 
1368
    """The default format used for new trees."""
 
1369
 
 
1370
    _formats = {}
 
1371
    """The known formats."""
 
1372
 
 
1373
    @classmethod
 
1374
    def find_format(klass, a_bzrdir):
 
1375
        """Return the format for the working tree object in a_bzrdir."""
 
1376
        try:
 
1377
            transport = a_bzrdir.get_workingtree_transport(None)
 
1378
            format_string = transport.get("format").read()
 
1379
            return klass._formats[format_string]
 
1380
        except NoSuchFile:
 
1381
            raise errors.NoWorkingTree(base=transport.base)
 
1382
        except KeyError:
 
1383
            raise errors.UnknownFormatError(format_string)
 
1384
 
 
1385
    @classmethod
 
1386
    def get_default_format(klass):
 
1387
        """Return the current default format."""
 
1388
        return klass._default_format
 
1389
 
 
1390
    def get_format_string(self):
 
1391
        """Return the ASCII format string that identifies this format."""
 
1392
        raise NotImplementedError(self.get_format_string)
 
1393
 
 
1394
    def is_supported(self):
 
1395
        """Is this format supported?
 
1396
 
 
1397
        Supported formats can be initialized and opened.
 
1398
        Unsupported formats may not support initialization or committing or 
 
1399
        some other features depending on the reason for not being supported.
 
1400
        """
 
1401
        return True
 
1402
 
 
1403
    @classmethod
 
1404
    def register_format(klass, format):
 
1405
        klass._formats[format.get_format_string()] = format
 
1406
 
 
1407
    @classmethod
 
1408
    def set_default_format(klass, format):
 
1409
        klass._default_format = format
 
1410
 
 
1411
    @classmethod
 
1412
    def unregister_format(klass, format):
 
1413
        assert klass._formats[format.get_format_string()] is format
 
1414
        del klass._formats[format.get_format_string()]
 
1415
 
 
1416
 
 
1417
 
 
1418
class WorkingTreeFormat2(WorkingTreeFormat):
 
1419
    """The second working tree format. 
 
1420
 
 
1421
    This format modified the hash cache from the format 1 hash cache.
 
1422
    """
 
1423
 
 
1424
    def initialize(self, a_bzrdir, revision_id=None):
 
1425
        """See WorkingTreeFormat.initialize()."""
 
1426
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1427
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1428
        branch = a_bzrdir.open_branch()
 
1429
        if revision_id is not None:
 
1430
            branch.lock_write()
 
1431
            try:
 
1432
                revision_history = branch.revision_history()
 
1433
                try:
 
1434
                    position = revision_history.index(revision_id)
 
1435
                except ValueError:
 
1436
                    raise errors.NoSuchRevision(branch, revision_id)
 
1437
                branch.set_revision_history(revision_history[:position + 1])
 
1438
            finally:
 
1439
                branch.unlock()
 
1440
        revision = branch.last_revision()
 
1441
        inv = Inventory() 
 
1442
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1443
                         branch,
 
1444
                         inv,
 
1445
                         _internal=True,
 
1446
                         _format=self,
 
1447
                         _bzrdir=a_bzrdir)
 
1448
        wt._write_inventory(inv)
 
1449
        wt.set_root_id(inv.root.file_id)
 
1450
        wt.set_last_revision(revision)
 
1451
        wt.set_pending_merges([])
 
1452
        build_tree(wt.basis_tree(), wt)
 
1453
        return wt
 
1454
 
 
1455
    def __init__(self):
 
1456
        super(WorkingTreeFormat2, self).__init__()
 
1457
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1458
 
 
1459
    def open(self, a_bzrdir, _found=False):
 
1460
        """Return the WorkingTree object for a_bzrdir
 
1461
 
 
1462
        _found is a private parameter, do not use it. It is used to indicate
 
1463
               if format probing has already been done.
 
1464
        """
 
1465
        if not _found:
 
1466
            # we are being called directly and must probe.
 
1467
            raise NotImplementedError
 
1468
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1469
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1470
        return WorkingTree(a_bzrdir.root_transport.base,
 
1471
                           _internal=True,
 
1472
                           _format=self,
 
1473
                           _bzrdir=a_bzrdir)
 
1474
 
 
1475
 
 
1476
class WorkingTreeFormat3(WorkingTreeFormat):
 
1477
    """The second working tree format updated to record a format marker.
 
1478
 
 
1479
    This format modified the hash cache from the format 1 hash cache.
 
1480
    """
 
1481
 
 
1482
    def get_format_string(self):
 
1483
        """See WorkingTreeFormat.get_format_string()."""
 
1484
        return "Bazaar-NG Working Tree format 3"
 
1485
 
 
1486
    def initialize(self, a_bzrdir, revision_id=None):
 
1487
        """See WorkingTreeFormat.initialize().
 
1488
        
 
1489
        revision_id allows creating a working tree at a differnet
 
1490
        revision than the branch is at.
 
1491
        """
 
1492
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1493
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1494
        transport = a_bzrdir.get_workingtree_transport(self)
 
1495
        control_files = LockableFiles(transport, 'lock', TransportLock)
 
1496
        control_files.put_utf8('format', self.get_format_string())
 
1497
        branch = a_bzrdir.open_branch()
 
1498
        if revision_id is None:
 
1499
            revision_id = branch.last_revision()
 
1500
        inv = Inventory() 
 
1501
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1502
                         branch,
 
1503
                         inv,
 
1504
                         _internal=True,
 
1505
                         _format=self,
 
1506
                         _bzrdir=a_bzrdir)
 
1507
        wt._write_inventory(inv)
 
1508
        wt.set_root_id(inv.root.file_id)
 
1509
        wt.set_last_revision(revision_id)
 
1510
        wt.set_pending_merges([])
 
1511
        build_tree(wt.basis_tree(), wt)
 
1512
        return wt
 
1513
 
 
1514
    def __init__(self):
 
1515
        super(WorkingTreeFormat3, self).__init__()
 
1516
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1517
 
 
1518
    def open(self, a_bzrdir, _found=False):
 
1519
        """Return the WorkingTree object for a_bzrdir
 
1520
 
 
1521
        _found is a private parameter, do not use it. It is used to indicate
 
1522
               if format probing has already been done.
 
1523
        """
 
1524
        if not _found:
 
1525
            # we are being called directly and must probe.
 
1526
            raise NotImplementedError
 
1527
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1528
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1529
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1530
                           _internal=True,
 
1531
                           _format=self,
 
1532
                           _bzrdir=a_bzrdir)
 
1533
 
 
1534
    def __str__(self):
 
1535
        return self.get_format_string()
 
1536
 
 
1537
 
 
1538
# formats which have no format string are not discoverable
 
1539
# and not independently creatable, so are not registered.
 
1540
__default_format = WorkingTreeFormat3()
 
1541
WorkingTreeFormat.register_format(__default_format)
 
1542
WorkingTreeFormat.set_default_format(__default_format)
 
1543
_legacy_formats = [WorkingTreeFormat2(),
 
1544
                   ]
 
1545
 
 
1546
 
 
1547
class WorkingTreeTestProviderAdapter(object):
 
1548
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1549
 
 
1550
    This is done by copying the test once for each transport and injecting
 
1551
    the transport_server, transport_readonly_server, and workingtree_format
 
1552
    classes into each copy. Each copy is also given a new id() to make it
 
1553
    easy to identify.
 
1554
    """
 
1555
 
 
1556
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1557
        self._transport_server = transport_server
 
1558
        self._transport_readonly_server = transport_readonly_server
 
1559
        self._formats = formats
 
1560
    
 
1561
    def adapt(self, test):
 
1562
        from bzrlib.tests import TestSuite
 
1563
        result = TestSuite()
 
1564
        for workingtree_format, bzrdir_format in self._formats:
 
1565
            new_test = deepcopy(test)
 
1566
            new_test.transport_server = self._transport_server
 
1567
            new_test.transport_readonly_server = self._transport_readonly_server
 
1568
            new_test.bzrdir_format = bzrdir_format
 
1569
            new_test.workingtree_format = workingtree_format
 
1570
            def make_new_test_id():
 
1571
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1572
                return lambda: new_id
 
1573
            new_test.id = make_new_test_id()
 
1574
            result.addTest(new_test)
 
1575
        return result