~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Aaron Bentley
  • Date: 2006-02-22 14:39:42 UTC
  • mto: (2027.1.2 revert-subpath-56549)
  • mto: This revision was merged to the branch mainline in revision 1570.
  • Revision ID: abentley@panoramicfeedback.com-20060222143942-ae72299f2de66767
Fixed build_tree with symlinks

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
 
from bzrlib.inventory import InventoryEntry
 
64
from bzrlib.inventory import InventoryEntry, Inventory
 
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,
72
78
                            normpath,
73
79
                            realpath,
74
80
                            relpath,
75
 
                            rename)
 
81
                            rename,
 
82
                            supports_executable,
 
83
                            )
 
84
from bzrlib.revision import NULL_REVISION
 
85
from bzrlib.symbol_versioning import *
76
86
from bzrlib.textui import show_status
77
87
import bzrlib.tree
78
88
from bzrlib.trace import mutter
 
89
from bzrlib.transform import build_tree
 
90
from bzrlib.transport import get_transport
 
91
from bzrlib.transport.local import LocalTransport
79
92
import bzrlib.xml5
80
93
 
81
94
 
178
191
    not listed in the Inventory and vice versa.
179
192
    """
180
193
 
181
 
    def __init__(self, basedir=u'.', branch=None):
 
194
    def __init__(self, basedir='.',
 
195
                 branch=DEPRECATED_PARAMETER,
 
196
                 _inventory=None,
 
197
                 _control_files=None,
 
198
                 _internal=False,
 
199
                 _format=None,
 
200
                 _bzrdir=None):
182
201
        """Construct a WorkingTree for basedir.
183
202
 
184
203
        If the branch is not supplied, it is opened automatically.
186
205
        (branch.base is not cross checked, because for remote branches that
187
206
        would be meaningless).
188
207
        """
 
208
        self._format = _format
 
209
        self.bzrdir = _bzrdir
 
210
        if not _internal:
 
211
            # not created via open etc.
 
212
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
213
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
214
                 DeprecationWarning,
 
215
                 stacklevel=2)
 
216
            wt = WorkingTree.open(basedir)
 
217
            self.branch = wt.branch
 
218
            self.basedir = wt.basedir
 
219
            self._control_files = wt._control_files
 
220
            self._hashcache = wt._hashcache
 
221
            self._set_inventory(wt._inventory)
 
222
            self._format = wt._format
 
223
            self.bzrdir = wt.bzrdir
189
224
        from bzrlib.hashcache import HashCache
190
225
        from bzrlib.trace import note, mutter
191
226
        assert isinstance(basedir, basestring), \
192
227
            "base directory %r is not a string" % basedir
193
 
        if branch is None:
194
 
            branch = Branch.open(basedir)
195
 
        assert isinstance(branch, Branch), \
196
 
            "branch %r is not a Branch" % branch
197
 
        self.branch = branch
 
228
        basedir = safe_unicode(basedir)
 
229
        mutter("opening working tree %r", basedir)
 
230
        if deprecated_passed(branch):
 
231
            if not _internal:
 
232
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
233
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
 
234
                     DeprecationWarning,
 
235
                     stacklevel=2
 
236
                     )
 
237
            self.branch = branch
 
238
        else:
 
239
            self.branch = self.bzrdir.open_branch()
 
240
        assert isinstance(self.branch, Branch), \
 
241
            "branch %r is not a Branch" % self.branch
198
242
        self.basedir = realpath(basedir)
 
243
        # if branch is at our basedir and is a format 6 or less
 
244
        if isinstance(self._format, WorkingTreeFormat2):
 
245
            # share control object
 
246
            self._control_files = self.branch.control_files
 
247
        elif _control_files is not None:
 
248
            assert False, "not done yet"
 
249
#            self._control_files = _control_files
 
250
        else:
 
251
            # only ready for format 3
 
252
            assert isinstance(self._format, WorkingTreeFormat3)
 
253
            self._control_files = LockableFiles(
 
254
                self.bzrdir.get_workingtree_transport(None),
 
255
                'lock')
199
256
 
200
257
        # update the whole cache up front and write to disk if anything changed;
201
258
        # in the future we might want to do this more selectively
203
260
        # if needed, or, when the cache sees a change, append it to the hash
204
261
        # cache file, and have the parser take the most recent entry for a
205
262
        # given path only.
206
 
        hc = self._hashcache = HashCache(basedir)
 
263
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
264
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
207
265
        hc.read()
 
266
        # is this scan needed ? it makes things kinda slow.
208
267
        hc.scan()
209
268
 
210
269
        if hc.needs_write:
211
270
            mutter("write hc")
212
271
            hc.write()
213
272
 
214
 
        self._set_inventory(self.read_working_inventory())
 
273
        if _inventory is None:
 
274
            self._set_inventory(self.read_working_inventory())
 
275
        else:
 
276
            self._set_inventory(_inventory)
215
277
 
216
278
    def _set_inventory(self, inv):
217
279
        self._inventory = inv
218
280
        self.path2id = self._inventory.path2id
219
281
 
 
282
    def is_control_filename(self, filename):
 
283
        """True if filename is the name of a control file in this tree.
 
284
        
 
285
        This is true IF and ONLY IF the filename is part of the meta data
 
286
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
287
        on disk will not be a control file for this tree.
 
288
        """
 
289
        try:
 
290
            self.bzrdir.transport.relpath(self.abspath(filename))
 
291
            return True
 
292
        except errors.PathNotChild:
 
293
            return False
 
294
 
 
295
    @staticmethod
 
296
    def open(path=None, _unsupported=False):
 
297
        """Open an existing working tree at path.
 
298
 
 
299
        """
 
300
        if path is None:
 
301
            path = os.path.getcwdu()
 
302
        control = bzrdir.BzrDir.open(path, _unsupported)
 
303
        return control.open_workingtree(_unsupported)
 
304
        
220
305
    @staticmethod
221
306
    def open_containing(path=None):
222
307
        """Open an existing working tree which has its root about path.
229
314
        If there is one, it is returned, along with the unused portion of path.
230
315
        """
231
316
        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)
 
317
            path = os.getcwdu()
 
318
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
319
        return control.open_workingtree(), relpath
 
320
 
 
321
    @staticmethod
 
322
    def open_downlevel(path=None):
 
323
        """Open an unsupported working tree.
 
324
 
 
325
        Only intended for advanced situations like upgrading part of a bzrdir.
 
326
        """
 
327
        return WorkingTree.open(path, _unsupported=True)
253
328
 
254
329
    def __iter__(self):
255
330
        """Iterate through file_ids for this tree.
268
343
 
269
344
    def abspath(self, filename):
270
345
        return pathjoin(self.basedir, filename)
 
346
    
 
347
    def basis_tree(self):
 
348
        """Return RevisionTree for the current last revision."""
 
349
        revision_id = self.last_revision()
 
350
        if revision_id is not None:
 
351
            try:
 
352
                xml = self.read_basis_inventory(revision_id)
 
353
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
354
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
355
                                                revision_id)
 
356
            except NoSuchFile:
 
357
                pass
 
358
        return self.branch.repository.revision_tree(revision_id)
 
359
 
 
360
    @staticmethod
 
361
    @deprecated_method(zero_eight)
 
362
    def create(branch, directory):
 
363
        """Create a workingtree for branch at directory.
 
364
 
 
365
        If existing_directory already exists it must have a .bzr directory.
 
366
        If it does not exist, it will be created.
 
367
 
 
368
        This returns a new WorkingTree object for the new checkout.
 
369
 
 
370
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
371
        should accept an optional revisionid to checkout [and reject this if
 
372
        checking out into the same dir as a pre-checkout-aware branch format.]
 
373
 
 
374
        XXX: When BzrDir is present, these should be created through that 
 
375
        interface instead.
 
376
        """
 
377
        warn('delete WorkingTree.create', stacklevel=3)
 
378
        transport = get_transport(directory)
 
379
        if branch.bzrdir.root_transport.base == transport.base:
 
380
            # same dir 
 
381
            return branch.bzrdir.create_workingtree()
 
382
        # different directory, 
 
383
        # create a branch reference
 
384
        # and now a working tree.
 
385
        raise NotImplementedError
 
386
 
 
387
    @staticmethod
 
388
    @deprecated_method(zero_eight)
 
389
    def create_standalone(directory):
 
390
        """Create a checkout and a branch and a repo at directory.
 
391
 
 
392
        Directory must exist and be empty.
 
393
 
 
394
        please use BzrDir.create_standalone_workingtree
 
395
        """
 
396
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
271
397
 
272
398
    def relpath(self, abs):
273
399
        """Return the local path portion from a given absolute path."""
291
417
        ## XXX: badly named; this is not in the store at all
292
418
        return self.abspath(self.id2path(file_id))
293
419
 
 
420
    @needs_read_lock
 
421
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
422
        """Duplicate this working tree into to_bzr, including all state.
 
423
        
 
424
        Specifically modified files are kept as modified, but
 
425
        ignored and unknown files are discarded.
 
426
 
 
427
        If you want to make a new line of development, see bzrdir.sprout()
 
428
 
 
429
        revision
 
430
            If not None, the cloned tree will have its last revision set to 
 
431
            revision, and and difference between the source trees last revision
 
432
            and this one merged in.
 
433
 
 
434
        basis
 
435
            If not None, a closer copy of a tree which may have some files in
 
436
            common, and which file content should be preferentially copied from.
 
437
        """
 
438
        # assumes the target bzr dir format is compatible.
 
439
        result = self._format.initialize(to_bzrdir)
 
440
        self.copy_content_into(result, revision_id)
 
441
        return result
 
442
 
 
443
    @needs_read_lock
 
444
    def copy_content_into(self, tree, revision_id=None):
 
445
        """Copy the current content and user files of this tree into tree."""
 
446
        if revision_id is None:
 
447
            transform_tree(tree, self)
 
448
        else:
 
449
            # TODO now merge from tree.last_revision to revision
 
450
            transform_tree(tree, self)
 
451
            tree.set_last_revision(revision_id)
 
452
 
294
453
    @needs_write_lock
295
 
    def commit(self, *args, **kw):
 
454
    def commit(self, *args, **kwargs):
296
455
        from bzrlib.commit import Commit
297
 
        Commit().commit(self.branch, *args, **kw)
 
456
        # args for wt.commit start at message from the Commit.commit method,
 
457
        # but with branch a kwarg now, passing in args as is results in the
 
458
        #message being used for the branch
 
459
        args = (DEPRECATED_PARAMETER, ) + args
 
460
        Commit().commit(working_tree=self, *args, **kwargs)
298
461
        self._set_inventory(self.read_working_inventory())
299
462
 
300
463
    def id2abspath(self, file_id):
324
487
        return self._hashcache.get_sha1(path)
325
488
 
326
489
    def is_executable(self, file_id):
327
 
        if os.name == "nt":
 
490
        if not supports_executable():
328
491
            return self._inventory[file_id].executable
329
492
        else:
330
493
            path = self._inventory.id2path(file_id)
369
532
 
370
533
        inv = self.read_working_inventory()
371
534
        for f,file_id in zip(files, ids):
372
 
            if is_control_file(f):
 
535
            if self.is_control_filename(f):
373
536
                raise BzrError("cannot add control file %s" % quotefn(f))
374
537
 
375
538
            fp = splitpath(f)
381
544
 
382
545
            try:
383
546
                kind = file_kind(fullpath)
384
 
            except OSError:
 
547
            except OSError, e:
 
548
                if e.errno == errno.ENOENT:
 
549
                    raise NoSuchFile(fullpath)
385
550
                # maybe something better?
386
551
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
387
552
 
410
575
        if updated:
411
576
            self.set_pending_merges(p)
412
577
 
 
578
    @needs_read_lock
413
579
    def pending_merges(self):
414
580
        """Return a list of pending merges.
415
581
 
416
582
        These are revisions that have been merged into the working
417
583
        directory but not yet committed.
418
584
        """
419
 
        cfn = self.branch._rel_controlfilename('pending-merges')
420
 
        if not self.branch._transport.has(cfn):
 
585
        try:
 
586
            merges_file = self._control_files.get_utf8('pending-merges')
 
587
        except OSError, e:
 
588
            if e.errno != errno.ENOENT:
 
589
                raise
421
590
            return []
422
591
        p = []
423
 
        for l in self.branch.controlfile('pending-merges', 'r').readlines():
 
592
        for l in merges_file.readlines():
424
593
            p.append(l.rstrip('\n'))
425
594
        return p
426
595
 
427
596
    @needs_write_lock
428
597
    def set_pending_merges(self, rev_list):
429
 
        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
 
598
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
430
599
 
431
600
    def get_symlink_target(self, file_id):
432
601
        return os.readlink(self.id2abspath(file_id))
439
608
        else:
440
609
            return '?'
441
610
 
442
 
 
443
611
    def list_files(self):
444
612
        """Recursively list all files as (path, class, kind, id).
445
613
 
459
627
                ## TODO: If we find a subdirectory with its own .bzr
460
628
                ## directory, then that is a separate tree and we
461
629
                ## should exclude it.
462
 
                if bzrlib.BZRDIR == f:
 
630
 
 
631
                # the bzrdir for this tree
 
632
                if self.bzrdir.transport.base.endswith(f + '/'):
463
633
                    continue
464
634
 
465
635
                # path within tree
636
806
        These are files in the working directory that are not versioned or
637
807
        control files or ignored.
638
808
        
639
 
        >>> from bzrlib.branch import ScratchBranch
640
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
641
 
        >>> tree = WorkingTree(b.base, b)
 
809
        >>> from bzrlib.bzrdir import ScratchDir
 
810
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
811
        >>> b = d.open_branch()
 
812
        >>> tree = d.open_workingtree()
642
813
        >>> map(str, tree.unknowns())
643
814
        ['foo']
644
815
        >>> tree.add('foo')
663
834
                yield stem
664
835
 
665
836
    @needs_write_lock
666
 
    def pull(self, source, overwrite=False):
667
 
        from bzrlib.merge import merge_inner
 
837
    def pull(self, source, overwrite=False, stop_revision=None):
668
838
        source.lock_read()
669
839
        try:
670
840
            old_revision_history = self.branch.revision_history()
671
 
            count = self.branch.pull(source, overwrite)
 
841
            count = self.branch.pull(source, overwrite, stop_revision)
672
842
            new_revision_history = self.branch.revision_history()
673
843
            if new_revision_history != old_revision_history:
674
844
                if len(old_revision_history):
675
845
                    other_revision = old_revision_history[-1]
676
846
                else:
677
847
                    other_revision = None
 
848
                repository = self.branch.repository
678
849
                merge_inner(self.branch,
679
 
                            self.branch.basis_tree(), 
680
 
                            self.branch.revision_tree(other_revision))
 
850
                            self.basis_tree(), 
 
851
                            repository.revision_tree(other_revision),
 
852
                            this_tree=self)
 
853
                self.set_last_revision(self.branch.last_revision())
681
854
            return count
682
855
        finally:
683
856
            source.unlock()
776
949
    def kind(self, file_id):
777
950
        return file_kind(self.id2abspath(file_id))
778
951
 
 
952
    @needs_read_lock
 
953
    def last_revision(self):
 
954
        """Return the last revision id of this working tree.
 
955
 
 
956
        In early branch formats this was == the branch last_revision,
 
957
        but that cannot be relied upon - for working tree operations,
 
958
        always use tree.last_revision().
 
959
        """
 
960
        return self.branch.last_revision()
 
961
 
779
962
    def lock_read(self):
780
963
        """See Branch.lock_read, and WorkingTree.unlock."""
781
 
        return self.branch.lock_read()
 
964
        self.branch.lock_read()
 
965
        try:
 
966
            return self._control_files.lock_read()
 
967
        except:
 
968
            self.branch.unlock()
 
969
            raise
782
970
 
783
971
    def lock_write(self):
784
972
        """See Branch.lock_write, and WorkingTree.unlock."""
785
 
        return self.branch.lock_write()
 
973
        self.branch.lock_write()
 
974
        try:
 
975
            return self._control_files.lock_write()
 
976
        except:
 
977
            self.branch.unlock()
 
978
            raise
786
979
 
787
980
    def _basis_inventory_name(self, revision_id):
788
981
        return 'basis-inventory.%s' % revision_id
789
982
 
 
983
    @needs_write_lock
790
984
    def set_last_revision(self, new_revision, old_revision=None):
791
 
        if old_revision:
792
 
            try:
793
 
                path = self._basis_inventory_name(old_revision)
794
 
                path = self.branch._rel_controlfilename(path)
795
 
                self.branch._transport.delete(path)
796
 
            except:
797
 
                pass
798
 
        try:
799
 
            xml = self.branch.get_inventory_xml(new_revision)
 
985
        """Change the last revision in the working tree."""
 
986
        self._remove_old_basis(old_revision)
 
987
        if self._change_last_revision(new_revision):
 
988
            self._cache_basis_inventory(new_revision)
 
989
 
 
990
    def _change_last_revision(self, new_revision):
 
991
        """Template method part of set_last_revision to perform the change."""
 
992
        if new_revision is None:
 
993
            self.branch.set_revision_history([])
 
994
            return False
 
995
        # current format is locked in with the branch
 
996
        revision_history = self.branch.revision_history()
 
997
        try:
 
998
            position = revision_history.index(new_revision)
 
999
        except ValueError:
 
1000
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1001
        self.branch.set_revision_history(revision_history[:position + 1])
 
1002
        return True
 
1003
 
 
1004
    def _cache_basis_inventory(self, new_revision):
 
1005
        """Cache new_revision as the basis inventory."""
 
1006
        try:
 
1007
            xml = self.branch.repository.get_inventory_xml(new_revision)
800
1008
            path = self._basis_inventory_name(new_revision)
801
 
            self.branch.put_controlfile(path, xml)
 
1009
            self._control_files.put_utf8(path, xml)
802
1010
        except WeaveRevisionNotPresent:
803
1011
            pass
804
1012
 
 
1013
    def _remove_old_basis(self, old_revision):
 
1014
        """Remove the old basis inventory 'old_revision'."""
 
1015
        if old_revision is not None:
 
1016
            try:
 
1017
                path = self._basis_inventory_name(old_revision)
 
1018
                path = self._control_files._escape(path)
 
1019
                self._control_files._transport.delete(path)
 
1020
            except NoSuchFile:
 
1021
                pass
 
1022
 
805
1023
    def read_basis_inventory(self, revision_id):
806
1024
        """Read the cached basis inventory."""
807
1025
        path = self._basis_inventory_name(revision_id)
808
 
        return self.branch.controlfile(path, 'r').read()
 
1026
        return self._control_files.get_utf8(path).read()
809
1027
        
810
1028
    @needs_read_lock
811
1029
    def read_working_inventory(self):
812
1030
        """Read the working inventory."""
813
1031
        # ElementTree does its own conversion from UTF-8, so open in
814
1032
        # binary.
815
 
        f = self.branch.controlfile('inventory', 'rb')
816
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
 
1033
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1034
            self._control_files.get('inventory'))
 
1035
        self._set_inventory(result)
 
1036
        return result
817
1037
 
818
1038
    @needs_write_lock
819
1039
    def remove(self, files, verbose=False):
858
1078
 
859
1079
    @needs_write_lock
860
1080
    def revert(self, filenames, old_tree=None, backups=True):
861
 
        from bzrlib.merge import merge_inner
 
1081
        from transform import revert
862
1082
        if old_tree is None:
863
 
            old_tree = self.branch.basis_tree()
864
 
        merge_inner(self.branch, old_tree,
865
 
                    self, ignore_zero=True,
866
 
                    backup_files=backups, 
867
 
                    interesting_files=filenames)
 
1083
            old_tree = self.basis_tree()
 
1084
        revert(self, old_tree, filenames, backups)
868
1085
        if not len(filenames):
869
1086
            self.set_pending_merges([])
870
1087
 
901
1118
        inv._byid[inv.root.file_id] = inv.root
902
1119
        for fid in inv:
903
1120
            entry = inv[fid]
904
 
            if entry.parent_id in (None, orig_root_id):
 
1121
            if entry.parent_id == orig_root_id:
905
1122
                entry.parent_id = inv.root.file_id
906
1123
        self._write_inventory(inv)
907
1124
 
914
1131
        between multiple working trees, i.e. via shared storage, then we 
915
1132
        would probably want to lock both the local tree, and the branch.
916
1133
        """
917
 
        if self._hashcache.needs_write:
 
1134
        # FIXME: We want to write out the hashcache only when the last lock on
 
1135
        # this working copy is released.  Peeking at the lock count is a bit
 
1136
        # of a nasty hack; probably it's better to have a transaction object,
 
1137
        # which can do some finalization when it's either successfully or
 
1138
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1139
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1140
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1141
        
 
1142
        # TODO: split this per format so there is no ugly if block
 
1143
        if self._hashcache.needs_write and (
 
1144
            # dedicated lock files
 
1145
            self._control_files._lock_count==1 or 
 
1146
            # shared lock files
 
1147
            (self._control_files is self.branch.control_files and 
 
1148
             self._control_files._lock_count==3)):
918
1149
            self._hashcache.write()
919
 
        return self.branch.unlock()
 
1150
        # reverse order of locking.
 
1151
        result = self._control_files.unlock()
 
1152
        try:
 
1153
            self.branch.unlock()
 
1154
        finally:
 
1155
            return result
 
1156
 
 
1157
    @needs_write_lock
 
1158
    def update(self):
 
1159
        self.branch.lock_read()
 
1160
        try:
 
1161
            if self.last_revision() == self.branch.last_revision():
 
1162
                return
 
1163
            basis = self.basis_tree()
 
1164
            to_tree = self.branch.basis_tree()
 
1165
            result = merge_inner(self.branch,
 
1166
                                 to_tree,
 
1167
                                 basis,
 
1168
                                 this_tree=self)
 
1169
            self.set_last_revision(self.branch.last_revision())
 
1170
            return result
 
1171
        finally:
 
1172
            self.branch.unlock()
920
1173
 
921
1174
    @needs_write_lock
922
1175
    def _write_inventory(self, inv):
923
1176
        """Write inventory as the current inventory."""
924
 
        from cStringIO import StringIO
925
 
        from bzrlib.atomicfile import AtomicFile
926
1177
        sio = StringIO()
927
1178
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
928
1179
        sio.seek(0)
929
 
        f = AtomicFile(self.branch.controlfilename('inventory'))
930
 
        try:
931
 
            pumpfile(sio, f)
932
 
            f.commit()
933
 
        finally:
934
 
            f.close()
 
1180
        self._control_files.put('inventory', sio)
935
1181
        self._set_inventory(inv)
936
1182
        mutter('wrote working inventory')
937
 
            
 
1183
 
 
1184
 
 
1185
class WorkingTree3(WorkingTree):
 
1186
    """This is the Format 3 working tree.
 
1187
 
 
1188
    This differs from the base WorkingTree by:
 
1189
     - having its own file lock
 
1190
     - having its own last-revision property.
 
1191
    """
 
1192
 
 
1193
    @needs_read_lock
 
1194
    def last_revision(self):
 
1195
        """See WorkingTree.last_revision."""
 
1196
        try:
 
1197
            return self._control_files.get_utf8('last-revision').read()
 
1198
        except NoSuchFile:
 
1199
            return None
 
1200
 
 
1201
    def _change_last_revision(self, revision_id):
 
1202
        """See WorkingTree._change_last_revision."""
 
1203
        if revision_id is None or revision_id == NULL_REVISION:
 
1204
            try:
 
1205
                self._control_files._transport.delete('last-revision')
 
1206
            except errors.NoSuchFile:
 
1207
                pass
 
1208
            return False
 
1209
        else:
 
1210
            try:
 
1211
                self.branch.revision_history().index(revision_id)
 
1212
            except ValueError:
 
1213
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1214
            self._control_files.put_utf8('last-revision', revision_id)
 
1215
            return True
 
1216
 
938
1217
 
939
1218
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
940
1219
def get_conflicted_stem(path):
941
1220
    for suffix in CONFLICT_SUFFIXES:
942
1221
        if path.endswith(suffix):
943
1222
            return path[:-len(suffix)]
 
1223
 
 
1224
@deprecated_function(zero_eight)
 
1225
def is_control_file(filename):
 
1226
    """See WorkingTree.is_control_filename(filename)."""
 
1227
    ## FIXME: better check
 
1228
    filename = normpath(filename)
 
1229
    while filename != '':
 
1230
        head, tail = os.path.split(filename)
 
1231
        ## mutter('check %r for control file' % ((head, tail),))
 
1232
        if tail == '.bzr':
 
1233
            return True
 
1234
        if filename == head:
 
1235
            break
 
1236
        filename = head
 
1237
    return False
 
1238
 
 
1239
 
 
1240
class WorkingTreeFormat(object):
 
1241
    """An encapsulation of the initialization and open routines for a format.
 
1242
 
 
1243
    Formats provide three things:
 
1244
     * An initialization routine,
 
1245
     * a format string,
 
1246
     * an open routine.
 
1247
 
 
1248
    Formats are placed in an dict by their format string for reference 
 
1249
    during workingtree opening. Its not required that these be instances, they
 
1250
    can be classes themselves with class methods - it simply depends on 
 
1251
    whether state is needed for a given format or not.
 
1252
 
 
1253
    Once a format is deprecated, just deprecate the initialize and open
 
1254
    methods on the format class. Do not deprecate the object, as the 
 
1255
    object will be created every time regardless.
 
1256
    """
 
1257
 
 
1258
    _default_format = None
 
1259
    """The default format used for new trees."""
 
1260
 
 
1261
    _formats = {}
 
1262
    """The known formats."""
 
1263
 
 
1264
    @classmethod
 
1265
    def find_format(klass, a_bzrdir):
 
1266
        """Return the format for the working tree object in a_bzrdir."""
 
1267
        try:
 
1268
            transport = a_bzrdir.get_workingtree_transport(None)
 
1269
            format_string = transport.get("format").read()
 
1270
            return klass._formats[format_string]
 
1271
        except NoSuchFile:
 
1272
            raise errors.NoWorkingTree(base=transport.base)
 
1273
        except KeyError:
 
1274
            raise errors.UnknownFormatError(format_string)
 
1275
 
 
1276
    @classmethod
 
1277
    def get_default_format(klass):
 
1278
        """Return the current default format."""
 
1279
        return klass._default_format
 
1280
 
 
1281
    def get_format_string(self):
 
1282
        """Return the ASCII format string that identifies this format."""
 
1283
        raise NotImplementedError(self.get_format_string)
 
1284
 
 
1285
    def is_supported(self):
 
1286
        """Is this format supported?
 
1287
 
 
1288
        Supported formats can be initialized and opened.
 
1289
        Unsupported formats may not support initialization or committing or 
 
1290
        some other features depending on the reason for not being supported.
 
1291
        """
 
1292
        return True
 
1293
 
 
1294
    @classmethod
 
1295
    def register_format(klass, format):
 
1296
        klass._formats[format.get_format_string()] = format
 
1297
 
 
1298
    @classmethod
 
1299
    def set_default_format(klass, format):
 
1300
        klass._default_format = format
 
1301
 
 
1302
    @classmethod
 
1303
    def unregister_format(klass, format):
 
1304
        assert klass._formats[format.get_format_string()] is format
 
1305
        del klass._formats[format.get_format_string()]
 
1306
 
 
1307
 
 
1308
 
 
1309
class WorkingTreeFormat2(WorkingTreeFormat):
 
1310
    """The second working tree format. 
 
1311
 
 
1312
    This format modified the hash cache from the format 1 hash cache.
 
1313
    """
 
1314
 
 
1315
    def initialize(self, a_bzrdir, revision_id=None):
 
1316
        """See WorkingTreeFormat.initialize()."""
 
1317
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1318
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1319
        branch = a_bzrdir.open_branch()
 
1320
        if revision_id is not None:
 
1321
            branch.lock_write()
 
1322
            try:
 
1323
                revision_history = branch.revision_history()
 
1324
                try:
 
1325
                    position = revision_history.index(revision_id)
 
1326
                except ValueError:
 
1327
                    raise errors.NoSuchRevision(branch, revision_id)
 
1328
                branch.set_revision_history(revision_history[:position + 1])
 
1329
            finally:
 
1330
                branch.unlock()
 
1331
        revision = branch.last_revision()
 
1332
        inv = Inventory() 
 
1333
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1334
                         branch,
 
1335
                         inv,
 
1336
                         _internal=True,
 
1337
                         _format=self,
 
1338
                         _bzrdir=a_bzrdir)
 
1339
        wt._write_inventory(inv)
 
1340
        wt.set_root_id(inv.root.file_id)
 
1341
        wt.set_last_revision(revision)
 
1342
        wt.set_pending_merges([])
 
1343
        build_tree(wt.basis_tree(), wt)
 
1344
        return wt
 
1345
 
 
1346
    def __init__(self):
 
1347
        super(WorkingTreeFormat2, self).__init__()
 
1348
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1349
 
 
1350
    def open(self, a_bzrdir, _found=False):
 
1351
        """Return the WorkingTree object for a_bzrdir
 
1352
 
 
1353
        _found is a private parameter, do not use it. It is used to indicate
 
1354
               if format probing has already been done.
 
1355
        """
 
1356
        if not _found:
 
1357
            # we are being called directly and must probe.
 
1358
            raise NotImplementedError
 
1359
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1360
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1361
        return WorkingTree(a_bzrdir.root_transport.base,
 
1362
                           _internal=True,
 
1363
                           _format=self,
 
1364
                           _bzrdir=a_bzrdir)
 
1365
 
 
1366
 
 
1367
class WorkingTreeFormat3(WorkingTreeFormat):
 
1368
    """The second working tree format updated to record a format marker.
 
1369
 
 
1370
    This format modified the hash cache from the format 1 hash cache.
 
1371
    """
 
1372
 
 
1373
    def get_format_string(self):
 
1374
        """See WorkingTreeFormat.get_format_string()."""
 
1375
        return "Bazaar-NG Working Tree format 3"
 
1376
 
 
1377
    def initialize(self, a_bzrdir, revision_id=None):
 
1378
        """See WorkingTreeFormat.initialize().
 
1379
        
 
1380
        revision_id allows creating a working tree at a differnet
 
1381
        revision than the branch is at.
 
1382
        """
 
1383
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1384
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1385
        transport = a_bzrdir.get_workingtree_transport(self)
 
1386
        control_files = LockableFiles(transport, 'lock')
 
1387
        control_files.put_utf8('format', self.get_format_string())
 
1388
        branch = a_bzrdir.open_branch()
 
1389
        if revision_id is None:
 
1390
            revision_id = branch.last_revision()
 
1391
        inv = Inventory() 
 
1392
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1393
                         branch,
 
1394
                         inv,
 
1395
                         _internal=True,
 
1396
                         _format=self,
 
1397
                         _bzrdir=a_bzrdir)
 
1398
        wt._write_inventory(inv)
 
1399
        wt.set_root_id(inv.root.file_id)
 
1400
        wt.set_last_revision(revision_id)
 
1401
        wt.set_pending_merges([])
 
1402
        build_tree(wt.basis_tree(), wt)
 
1403
        return wt
 
1404
 
 
1405
    def __init__(self):
 
1406
        super(WorkingTreeFormat3, self).__init__()
 
1407
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1408
 
 
1409
    def open(self, a_bzrdir, _found=False):
 
1410
        """Return the WorkingTree object for a_bzrdir
 
1411
 
 
1412
        _found is a private parameter, do not use it. It is used to indicate
 
1413
               if format probing has already been done.
 
1414
        """
 
1415
        if not _found:
 
1416
            # we are being called directly and must probe.
 
1417
            raise NotImplementedError
 
1418
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1419
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1420
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1421
                           _internal=True,
 
1422
                           _format=self,
 
1423
                           _bzrdir=a_bzrdir)
 
1424
 
 
1425
    def __str__(self):
 
1426
        return self.get_format_string()
 
1427
 
 
1428
 
 
1429
# formats which have no format string are not discoverable
 
1430
# and not independently creatable, so are not registered.
 
1431
__default_format = WorkingTreeFormat3()
 
1432
WorkingTreeFormat.register_format(__default_format)
 
1433
WorkingTreeFormat.set_default_format(__default_format)
 
1434
_legacy_formats = [WorkingTreeFormat2(),
 
1435
                   ]
 
1436
 
 
1437
 
 
1438
class WorkingTreeTestProviderAdapter(object):
 
1439
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1440
 
 
1441
    This is done by copying the test once for each transport and injecting
 
1442
    the transport_server, transport_readonly_server, and workingtree_format
 
1443
    classes into each copy. Each copy is also given a new id() to make it
 
1444
    easy to identify.
 
1445
    """
 
1446
 
 
1447
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1448
        self._transport_server = transport_server
 
1449
        self._transport_readonly_server = transport_readonly_server
 
1450
        self._formats = formats
 
1451
    
 
1452
    def adapt(self, test):
 
1453
        from bzrlib.tests import TestSuite
 
1454
        result = TestSuite()
 
1455
        for workingtree_format, bzrdir_format in self._formats:
 
1456
            new_test = deepcopy(test)
 
1457
            new_test.transport_server = self._transport_server
 
1458
            new_test.transport_readonly_server = self._transport_readonly_server
 
1459
            new_test.bzrdir_format = bzrdir_format
 
1460
            new_test.workingtree_format = workingtree_format
 
1461
            def make_new_test_id():
 
1462
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1463
                return lambda: new_id
 
1464
            new_test.id = make_new_test_id()
 
1465
            result.addTest(new_test)
 
1466
        return result