~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

[merge] update from bzr.dev

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 WorkingTree(dir[, branch])
29
29
"""
30
30
 
31
31
 
32
 
# TODO: Don't allow WorkingTrees to be constructed for remote branches if 
33
 
# they don't work.
34
 
 
35
32
# FIXME: I don't know if writing out the cache from the destructor is really a
36
33
# good idea, because destructors are considered poor taste in Python, and it's
37
34
# not predictable when it will be written out.
43
40
# At the momenthey may alias the inventory and have old copies of it in memory.
44
41
 
45
42
from copy import deepcopy
 
43
from cStringIO import StringIO
 
44
import errno
 
45
import fnmatch
46
46
import os
47
47
import stat
48
 
import fnmatch
49
48
 
 
49
 
 
50
from bzrlib.atomicfile import AtomicFile
50
51
from bzrlib.branch import (Branch,
 
52
                           BzrBranchFormat4,
 
53
                           BzrBranchFormat5,
 
54
                           BzrBranchFormat6,
51
55
                           is_control_file,
52
 
                           needs_read_lock,
53
 
                           needs_write_lock,
54
56
                           quotefn)
 
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
55
58
from bzrlib.errors import (BzrCheckError,
56
59
                           BzrError,
57
60
                           DivergedBranches,
58
61
                           WeaveRevisionNotPresent,
59
62
                           NotBranchError,
 
63
                           NoSuchFile,
60
64
                           NotVersionedError)
61
65
from bzrlib.inventory import InventoryEntry
 
66
from bzrlib.lockable_files import LockableFiles
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
79
87
import bzrlib.xml5
80
88
 
81
89
 
178
186
    not listed in the Inventory and vice versa.
179
187
    """
180
188
 
181
 
    def __init__(self, basedir=u'.', branch=None):
 
189
    def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
182
190
        """Construct a WorkingTree for basedir.
183
191
 
184
192
        If the branch is not supplied, it is opened automatically.
190
198
        from bzrlib.trace import note, mutter
191
199
        assert isinstance(basedir, basestring), \
192
200
            "base directory %r is not a string" % basedir
 
201
        basedir = safe_unicode(basedir)
 
202
        mutter("openeing working tree %r", basedir)
193
203
        if branch is None:
194
204
            branch = Branch.open(basedir)
195
205
        assert isinstance(branch, Branch), \
196
206
            "branch %r is not a Branch" % branch
197
207
        self.branch = branch
198
208
        self.basedir = realpath(basedir)
 
209
        # if branch is at our basedir and is a format 6 or less
 
210
        if (isinstance(self.branch._branch_format,
 
211
                       (BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
 
212
            # might be able to share control object
 
213
            and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
 
214
            self._control_files = self.branch.control_files
 
215
        elif _control_files is not None:
 
216
            assert False, "not done yet"
 
217
#            self._control_files = _control_files
 
218
        else:
 
219
            self._control_files = LockableFiles(
 
220
                get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
199
221
 
200
222
        # update the whole cache up front and write to disk if anything changed;
201
223
        # in the future we might want to do this more selectively
211
233
            mutter("write hc")
212
234
            hc.write()
213
235
 
214
 
        self._set_inventory(self.read_working_inventory())
 
236
        if _inventory is None:
 
237
            self._set_inventory(self.read_working_inventory())
 
238
        else:
 
239
            self._set_inventory(_inventory)
215
240
 
216
241
    def _set_inventory(self, inv):
217
242
        self._inventory = inv
235
260
            if path.find('://') != -1:
236
261
                raise NotBranchError(path=path)
237
262
        path = abspath(path)
 
263
        orig_path = path[:]
238
264
        tail = u''
239
265
        while True:
240
266
            try:
249
275
            path = os.path.dirname(path)
250
276
            if lastpath == path:
251
277
                # reached the root, whatever that may be
252
 
                raise NotBranchError(path=path)
 
278
                raise NotBranchError(path=orig_path)
253
279
 
254
280
    def __iter__(self):
255
281
        """Iterate through file_ids for this tree.
269
295
    def abspath(self, filename):
270
296
        return pathjoin(self.basedir, filename)
271
297
 
 
298
    @staticmethod
 
299
    def create(branch, directory):
 
300
        """Create a workingtree for branch at directory.
 
301
 
 
302
        If existing_directory already exists it must have a .bzr directory.
 
303
        If it does not exist, it will be created.
 
304
 
 
305
        This returns a new WorkingTree object for the new checkout.
 
306
 
 
307
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
308
        should accept an optional revisionid to checkout [and reject this if
 
309
        checking out into the same dir as a pre-checkout-aware branch format.]
 
310
 
 
311
        XXX: When BzrDir is present, these should be created through that 
 
312
        interface instead.
 
313
        """
 
314
        try:
 
315
            os.mkdir(directory)
 
316
        except OSError, e:
 
317
            if e.errno != errno.EEXIST:
 
318
                raise
 
319
        try:
 
320
            os.mkdir(pathjoin(directory, '.bzr'))
 
321
        except OSError, e:
 
322
            if e.errno != errno.EEXIST:
 
323
                raise
 
324
        inv = branch.repository.revision_tree(branch.last_revision()).inventory
 
325
        wt = WorkingTree(directory, branch, inv)
 
326
        wt._write_inventory(inv)
 
327
        if branch.last_revision() is not None:
 
328
            wt.set_last_revision(branch.last_revision())
 
329
        wt.set_pending_merges([])
 
330
        wt.revert([])
 
331
        return wt
 
332
 
 
333
    @staticmethod
 
334
    def create_standalone(directory):
 
335
        """Create a checkout and a branch at directory.
 
336
 
 
337
        Directory must exist and be empty.
 
338
 
 
339
        XXX: When BzrDir is present, these should be created through that 
 
340
        interface instead.
 
341
        """
 
342
        directory = safe_unicode(directory)
 
343
        b = Branch.create(directory)
 
344
        return WorkingTree.create(b, directory)
 
345
 
272
346
    def relpath(self, abs):
273
347
        """Return the local path portion from a given absolute path."""
274
348
        return relpath(self.basedir, abs)
292
366
        return self.abspath(self.id2path(file_id))
293
367
 
294
368
    @needs_write_lock
295
 
    def commit(self, *args, **kw):
 
369
    def commit(self, *args, **kwargs):
296
370
        from bzrlib.commit import Commit
297
 
        Commit().commit(self.branch, *args, **kw)
 
371
        # args for wt.commit start at message from the Commit.commit method,
 
372
        # but with branch a kwarg now, passing in args as is results in the
 
373
        #message being used for the branch
 
374
        args = (DEPRECATED_PARAMETER, ) + args
 
375
        Commit().commit(working_tree=self, *args, **kwargs)
298
376
        self._set_inventory(self.read_working_inventory())
299
377
 
300
378
    def id2abspath(self, file_id):
410
488
        if updated:
411
489
            self.set_pending_merges(p)
412
490
 
 
491
    @needs_read_lock
413
492
    def pending_merges(self):
414
493
        """Return a list of pending merges.
415
494
 
416
495
        These are revisions that have been merged into the working
417
496
        directory but not yet committed.
418
497
        """
419
 
        cfn = self.branch._rel_controlfilename('pending-merges')
420
 
        if not self.branch._transport.has(cfn):
 
498
        try:
 
499
            merges_file = self._control_files.get_utf8('pending-merges')
 
500
        except OSError, e:
 
501
            if e.errno != errno.ENOENT:
 
502
                raise
421
503
            return []
422
504
        p = []
423
 
        for l in self.branch.controlfile('pending-merges', 'r').readlines():
 
505
        for l in merges_file.readlines():
424
506
            p.append(l.rstrip('\n'))
425
507
        return p
426
508
 
427
509
    @needs_write_lock
428
510
    def set_pending_merges(self, rev_list):
429
 
        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
 
511
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
430
512
 
431
513
    def get_symlink_target(self, file_id):
432
514
        return os.readlink(self.id2abspath(file_id))
675
757
                    other_revision = old_revision_history[-1]
676
758
                else:
677
759
                    other_revision = None
 
760
                repository = self.branch.repository
678
761
                merge_inner(self.branch,
679
762
                            self.branch.basis_tree(), 
680
 
                            self.branch.revision_tree(other_revision))
 
763
                            repository.revision_tree(other_revision),
 
764
                            this_tree=self)
 
765
                self.set_last_revision(self.branch.last_revision())
681
766
            return count
682
767
        finally:
683
768
            source.unlock()
788
873
        return 'basis-inventory.%s' % revision_id
789
874
 
790
875
    def set_last_revision(self, new_revision, old_revision=None):
791
 
        if old_revision:
 
876
        if old_revision is not None:
792
877
            try:
793
878
                path = self._basis_inventory_name(old_revision)
794
 
                path = self.branch._rel_controlfilename(path)
795
 
                self.branch._transport.delete(path)
796
 
            except:
 
879
                path = self.branch.control_files._escape(path)
 
880
                self.branch.control_files._transport.delete(path)
 
881
            except NoSuchFile:
797
882
                pass
798
883
        try:
799
 
            xml = self.branch.get_inventory_xml(new_revision)
 
884
            xml = self.branch.repository.get_inventory_xml(new_revision)
800
885
            path = self._basis_inventory_name(new_revision)
801
 
            self.branch.put_controlfile(path, xml)
 
886
            self.branch.control_files.put_utf8(path, xml)
802
887
        except WeaveRevisionNotPresent:
803
888
            pass
804
889
 
805
890
    def read_basis_inventory(self, revision_id):
806
891
        """Read the cached basis inventory."""
807
892
        path = self._basis_inventory_name(revision_id)
808
 
        return self.branch.controlfile(path, 'r').read()
 
893
        return self.branch.control_files.get_utf8(path).read()
809
894
        
810
895
    @needs_read_lock
811
896
    def read_working_inventory(self):
812
897
        """Read the working inventory."""
813
898
        # ElementTree does its own conversion from UTF-8, so open in
814
899
        # binary.
815
 
        f = self.branch.controlfile('inventory', 'rb')
816
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
 
900
        return bzrlib.xml5.serializer_v5.read_inventory(
 
901
            self._control_files.get('inventory'))
817
902
 
818
903
    @needs_write_lock
819
904
    def remove(self, files, verbose=False):
864
949
        merge_inner(self.branch, old_tree,
865
950
                    self, ignore_zero=True,
866
951
                    backup_files=backups, 
867
 
                    interesting_files=filenames)
 
952
                    interesting_files=filenames,
 
953
                    this_tree=self)
868
954
        if not len(filenames):
869
955
            self.set_pending_merges([])
870
956
 
914
1000
        between multiple working trees, i.e. via shared storage, then we 
915
1001
        would probably want to lock both the local tree, and the branch.
916
1002
        """
917
 
        if self._hashcache.needs_write:
 
1003
        # FIXME: We want to write out the hashcache only when the last lock on
 
1004
        # this working copy is released.  Peeking at the lock count is a bit
 
1005
        # of a nasty hack; probably it's better to have a transaction object,
 
1006
        # which can do some finalization when it's either successfully or
 
1007
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1008
        if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
918
1009
            self._hashcache.write()
919
1010
        return self.branch.unlock()
920
1011
 
921
1012
    @needs_write_lock
922
1013
    def _write_inventory(self, inv):
923
1014
        """Write inventory as the current inventory."""
924
 
        from cStringIO import StringIO
925
 
        from bzrlib.atomicfile import AtomicFile
926
1015
        sio = StringIO()
927
1016
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
928
1017
        sio.seek(0)
929
 
        f = AtomicFile(self.branch.controlfilename('inventory'))
930
 
        try:
931
 
            pumpfile(sio, f)
932
 
            f.commit()
933
 
        finally:
934
 
            f.close()
 
1018
        self._control_files.put('inventory', sio)
935
1019
        self._set_inventory(inv)
936
1020
        mutter('wrote working inventory')
937
1021