~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Robert Collins
  • Date: 2006-02-15 08:11:37 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060215081137-4c27377517e96dd1
Make format 4/5/6 branches share a single LockableFiles instance across wt/branch/repository.

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.
42
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
41
# At the momenthey may alias the inventory and have old copies of it in memory.
44
42
 
 
43
from copy import deepcopy
 
44
from cStringIO import StringIO
 
45
import errno
 
46
import fnmatch
45
47
import os
46
48
import stat
47
 
import fnmatch
48
49
 
49
 
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
50
 
import bzrlib.tree
 
50
 
 
51
from bzrlib.atomicfile import AtomicFile
 
52
from bzrlib.branch import (Branch,
 
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
 
57
from bzrlib.errors import (BzrCheckError,
 
58
                           BzrError,
 
59
                           DivergedBranches,
 
60
                           WeaveRevisionNotPresent,
 
61
                           NotBranchError,
 
62
                           NoSuchFile,
 
63
                           NotVersionedError)
 
64
from bzrlib.inventory import InventoryEntry
 
65
from bzrlib.lockable_files import LockableFiles
 
66
from bzrlib.merge import merge_inner, transform_tree
51
67
from bzrlib.osutils import (appendpath,
 
68
                            compact_date,
52
69
                            file_kind,
53
70
                            isdir,
 
71
                            getcwd,
 
72
                            pathjoin,
54
73
                            pumpfile,
 
74
                            safe_unicode,
55
75
                            splitpath,
56
 
                            relpath)
57
 
from bzrlib.errors import BzrCheckError, DivergedBranches, NotVersionedError
 
76
                            rand_bytes,
 
77
                            abspath,
 
78
                            normpath,
 
79
                            realpath,
 
80
                            relpath,
 
81
                            rename)
 
82
from bzrlib.revision import NULL_REVISION
 
83
from bzrlib.symbol_versioning import *
 
84
from bzrlib.textui import show_status
 
85
import bzrlib.tree
58
86
from bzrlib.trace import mutter
 
87
from bzrlib.transport import get_transport
 
88
from bzrlib.transport.local import LocalTransport
59
89
import bzrlib.xml5
60
90
 
61
91
 
 
92
def gen_file_id(name):
 
93
    """Return new file id.
 
94
 
 
95
    This should probably generate proper UUIDs, but for the moment we
 
96
    cope with just randomness because running uuidgen every time is
 
97
    slow."""
 
98
    import re
 
99
    from binascii import hexlify
 
100
    from time import time
 
101
 
 
102
    # get last component
 
103
    idx = name.rfind('/')
 
104
    if idx != -1:
 
105
        name = name[idx+1 : ]
 
106
    idx = name.rfind('\\')
 
107
    if idx != -1:
 
108
        name = name[idx+1 : ]
 
109
 
 
110
    # make it not a hidden file
 
111
    name = name.lstrip('.')
 
112
 
 
113
    # remove any wierd characters; we don't escape them but rather
 
114
    # just pull them out
 
115
    name = re.sub(r'[^\w.]', '', name)
 
116
 
 
117
    s = hexlify(rand_bytes(8))
 
118
    return '-'.join((name, compact_date(time()), s))
 
119
 
 
120
 
 
121
def gen_root_id():
 
122
    """Return a new tree-root file id."""
 
123
    return gen_file_id('TREE_ROOT')
 
124
 
 
125
 
62
126
class TreeEntry(object):
63
127
    """An entry that implements the minium interface used by commands.
64
128
 
124
188
    not listed in the Inventory and vice versa.
125
189
    """
126
190
 
127
 
    def __init__(self, basedir, branch=None):
 
191
    def __init__(self, basedir='.',
 
192
                 branch=DEPRECATED_PARAMETER,
 
193
                 _inventory=None,
 
194
                 _control_files=None,
 
195
                 _internal=False,
 
196
                 _format=None,
 
197
                 _bzrdir=None):
128
198
        """Construct a WorkingTree for basedir.
129
199
 
130
200
        If the branch is not supplied, it is opened automatically.
132
202
        (branch.base is not cross checked, because for remote branches that
133
203
        would be meaningless).
134
204
        """
 
205
        self._format = _format
 
206
        self.bzrdir = _bzrdir
 
207
        if not _internal:
 
208
            # not created via open etc.
 
209
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
210
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
211
                 DeprecationWarning,
 
212
                 stacklevel=2)
 
213
            wt = WorkingTree.open(basedir)
 
214
            self.branch = wt.branch
 
215
            self.basedir = wt.basedir
 
216
            self._control_files = wt._control_files
 
217
            self._hashcache = wt._hashcache
 
218
            self._set_inventory(wt._inventory)
 
219
            self._format = wt._format
 
220
            self.bzrdir = wt.bzrdir
135
221
        from bzrlib.hashcache import HashCache
136
222
        from bzrlib.trace import note, mutter
137
223
        assert isinstance(basedir, basestring), \
138
224
            "base directory %r is not a string" % basedir
139
 
        if branch is None:
140
 
            branch = Branch.open(basedir)
141
 
        assert isinstance(branch, Branch), \
142
 
            "branch %r is not a Branch" % branch
143
 
        self.branch = branch
144
 
        self.basedir = basedir
145
 
        self._inventory = self.read_working_inventory()
146
 
        self.path2id = self._inventory.path2id
 
225
        basedir = safe_unicode(basedir)
 
226
        mutter("opening working tree %r", basedir)
 
227
        if deprecated_passed(branch):
 
228
            if not _internal:
 
229
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
230
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
 
231
                     DeprecationWarning,
 
232
                     stacklevel=2
 
233
                     )
 
234
            self.branch = branch
 
235
        else:
 
236
            self.branch = self.bzrdir.open_branch()
 
237
        assert isinstance(self.branch, Branch), \
 
238
            "branch %r is not a Branch" % self.branch
 
239
        self.basedir = realpath(basedir)
 
240
        # if branch is at our basedir and is a format 6 or less
 
241
        if isinstance(self._format, WorkingTreeFormat2):
 
242
            # share control object
 
243
            self._control_files = self.branch.control_files
 
244
        elif _control_files is not None:
 
245
            assert False, "not done yet"
 
246
#            self._control_files = _control_files
 
247
        else:
 
248
            # only ready for format 3
 
249
            assert isinstance(self._format, WorkingTreeFormat3)
 
250
            self._control_files = LockableFiles(
 
251
                self.bzrdir.get_workingtree_transport(None),
 
252
                'lock')
147
253
 
148
254
        # update the whole cache up front and write to disk if anything changed;
149
255
        # in the future we might want to do this more selectively
151
257
        # if needed, or, when the cache sees a change, append it to the hash
152
258
        # cache file, and have the parser take the most recent entry for a
153
259
        # given path only.
154
 
        hc = self._hashcache = HashCache(basedir)
 
260
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
261
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
155
262
        hc.read()
 
263
        # is this scan needed ? it makes things kinda slow.
156
264
        hc.scan()
157
265
 
158
266
        if hc.needs_write:
159
267
            mutter("write hc")
160
268
            hc.write()
161
269
 
 
270
        if _inventory is None:
 
271
            self._set_inventory(self.read_working_inventory())
 
272
        else:
 
273
            self._set_inventory(_inventory)
 
274
 
 
275
    def _set_inventory(self, inv):
 
276
        self._inventory = inv
 
277
        self.path2id = self._inventory.path2id
 
278
 
 
279
    @staticmethod
 
280
    def open(path=None, _unsupported=False):
 
281
        """Open an existing working tree at path.
 
282
 
 
283
        """
 
284
        if path is None:
 
285
            path = os.path.getcwdu()
 
286
        control = bzrdir.BzrDir.open(path, _unsupported)
 
287
        return control.open_workingtree(_unsupported)
 
288
        
 
289
    @staticmethod
 
290
    def open_containing(path=None):
 
291
        """Open an existing working tree which has its root about path.
 
292
        
 
293
        This probes for a working tree at path and searches upwards from there.
 
294
 
 
295
        Basically we keep looking up until we find the control directory or
 
296
        run into /.  If there isn't one, raises NotBranchError.
 
297
        TODO: give this a new exception.
 
298
        If there is one, it is returned, along with the unused portion of path.
 
299
        """
 
300
        if path is None:
 
301
            path = os.getcwdu()
 
302
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
303
        return control.open_workingtree(), relpath
 
304
 
 
305
    @staticmethod
 
306
    def open_downlevel(path=None):
 
307
        """Open an unsupported working tree.
 
308
 
 
309
        Only intended for advanced situations like upgrading part of a bzrdir.
 
310
        """
 
311
        return WorkingTree.open(path, _unsupported=True)
 
312
 
162
313
    def __iter__(self):
163
314
        """Iterate through file_ids for this tree.
164
315
 
170
321
            if bzrlib.osutils.lexists(self.abspath(path)):
171
322
                yield ie.file_id
172
323
 
173
 
 
174
324
    def __repr__(self):
175
325
        return "<%s of %s>" % (self.__class__.__name__,
176
326
                               getattr(self, 'basedir', None))
177
327
 
178
 
 
179
 
 
180
328
    def abspath(self, filename):
181
 
        return os.path.join(self.basedir, filename)
182
 
 
183
 
    def relpath(self, abspath):
 
329
        return pathjoin(self.basedir, filename)
 
330
    
 
331
    def basis_tree(self):
 
332
        """Return RevisionTree for the current last revision."""
 
333
        revision_id = self.last_revision()
 
334
        if revision_id is not None:
 
335
            try:
 
336
                xml = self.read_basis_inventory(revision_id)
 
337
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
338
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
339
                                                revision_id)
 
340
            except NoSuchFile:
 
341
                pass
 
342
        return self.branch.repository.revision_tree(revision_id)
 
343
 
 
344
    @staticmethod
 
345
    @deprecated_method(zero_eight)
 
346
    def create(branch, directory):
 
347
        """Create a workingtree for branch at directory.
 
348
 
 
349
        If existing_directory already exists it must have a .bzr directory.
 
350
        If it does not exist, it will be created.
 
351
 
 
352
        This returns a new WorkingTree object for the new checkout.
 
353
 
 
354
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
355
        should accept an optional revisionid to checkout [and reject this if
 
356
        checking out into the same dir as a pre-checkout-aware branch format.]
 
357
 
 
358
        XXX: When BzrDir is present, these should be created through that 
 
359
        interface instead.
 
360
        """
 
361
        warn('delete WorkingTree.create', stacklevel=3)
 
362
        transport = get_transport(directory)
 
363
        if branch.bzrdir.root_transport.base == transport.base:
 
364
            # same dir 
 
365
            return branch.bzrdir.create_workingtree()
 
366
        # different directory, 
 
367
        # create a branch reference
 
368
        # and now a working tree.
 
369
        raise NotImplementedError
 
370
 
 
371
    @staticmethod
 
372
    @deprecated_method(zero_eight)
 
373
    def create_standalone(directory):
 
374
        """Create a checkout and a branch and a repo at directory.
 
375
 
 
376
        Directory must exist and be empty.
 
377
 
 
378
        please use BzrDir.create_standalone_workingtree
 
379
        """
 
380
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
 
381
 
 
382
    def relpath(self, abs):
184
383
        """Return the local path portion from a given absolute path."""
185
 
        return relpath(self.basedir, abspath)
 
384
        return relpath(self.basedir, abs)
186
385
 
187
386
    def has_filename(self, filename):
188
387
        return bzrlib.osutils.lexists(self.abspath(filename))
199
398
        return inv.root.file_id
200
399
        
201
400
    def _get_store_filename(self, file_id):
202
 
        ## XXX: badly named; this isn't in the store at all
 
401
        ## XXX: badly named; this is not in the store at all
203
402
        return self.abspath(self.id2path(file_id))
204
403
 
 
404
    @needs_read_lock
 
405
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
406
        """Duplicate this working tree into to_bzr, including all state.
 
407
        
 
408
        Specifically modified files are kept as modified, but
 
409
        ignored and unknown files are discarded.
 
410
 
 
411
        If you want to make a new line of development, see bzrdir.sprout()
 
412
 
 
413
        revision
 
414
            If not None, the cloned tree will have its last revision set to 
 
415
            revision, and and difference between the source trees last revision
 
416
            and this one merged in.
 
417
 
 
418
        basis
 
419
            If not None, a closer copy of a tree which may have some files in
 
420
            common, and which file content should be preferentially copied from.
 
421
        """
 
422
        # assumes the target bzr dir format is compatible.
 
423
        result = self._format.initialize(to_bzrdir)
 
424
        self.copy_content_into(result, revision_id)
 
425
        return result
 
426
 
 
427
    @needs_read_lock
 
428
    def copy_content_into(self, tree, revision_id=None):
 
429
        """Copy the current content and user files of this tree into tree."""
 
430
        if revision_id is None:
 
431
            transform_tree(tree, self)
 
432
        else:
 
433
            # TODO now merge from tree.last_revision to revision
 
434
            transform_tree(tree, self)
 
435
            tree.set_last_revision(revision_id)
 
436
 
205
437
    @needs_write_lock
206
 
    def commit(self, *args, **kw):
 
438
    def commit(self, *args, **kwargs):
207
439
        from bzrlib.commit import Commit
208
 
        Commit().commit(self.branch, *args, **kw)
209
 
        self._inventory = self.read_working_inventory()
 
440
        # args for wt.commit start at message from the Commit.commit method,
 
441
        # but with branch a kwarg now, passing in args as is results in the
 
442
        #message being used for the branch
 
443
        args = (DEPRECATED_PARAMETER, ) + args
 
444
        Commit().commit(working_tree=self, *args, **kwargs)
 
445
        self._set_inventory(self.read_working_inventory())
210
446
 
211
447
    def id2abspath(self, file_id):
212
448
        return self.abspath(self.id2path(file_id))
213
449
 
214
 
                
215
450
    def has_id(self, file_id):
216
451
        # files that have been deleted are excluded
217
452
        inv = self._inventory
226
461
        return self.inventory.has_id(file_id)
227
462
 
228
463
    __contains__ = has_id
229
 
    
230
464
 
231
465
    def get_file_size(self, file_id):
232
466
        return os.path.getsize(self.id2abspath(file_id))
233
467
 
 
468
    @needs_read_lock
234
469
    def get_file_sha1(self, file_id):
235
470
        path = self._inventory.id2path(file_id)
236
471
        return self._hashcache.get_sha1(path)
237
472
 
238
 
 
239
473
    def is_executable(self, file_id):
240
474
        if os.name == "nt":
241
475
            return self._inventory[file_id].executable
245
479
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
246
480
 
247
481
    @needs_write_lock
 
482
    def add(self, files, ids=None):
 
483
        """Make files versioned.
 
484
 
 
485
        Note that the command line normally calls smart_add instead,
 
486
        which can automatically recurse.
 
487
 
 
488
        This adds the files to the inventory, so that they will be
 
489
        recorded by the next commit.
 
490
 
 
491
        files
 
492
            List of paths to add, relative to the base of the tree.
 
493
 
 
494
        ids
 
495
            If set, use these instead of automatically generated ids.
 
496
            Must be the same length as the list of files, but may
 
497
            contain None for ids that are to be autogenerated.
 
498
 
 
499
        TODO: Perhaps have an option to add the ids even if the files do
 
500
              not (yet) exist.
 
501
 
 
502
        TODO: Perhaps callback with the ids and paths as they're added.
 
503
        """
 
504
        # TODO: Re-adding a file that is removed in the working copy
 
505
        # should probably put it back with the previous ID.
 
506
        if isinstance(files, basestring):
 
507
            assert(ids is None or isinstance(ids, basestring))
 
508
            files = [files]
 
509
            if ids is not None:
 
510
                ids = [ids]
 
511
 
 
512
        if ids is None:
 
513
            ids = [None] * len(files)
 
514
        else:
 
515
            assert(len(ids) == len(files))
 
516
 
 
517
        inv = self.read_working_inventory()
 
518
        for f,file_id in zip(files, ids):
 
519
            if is_control_file(f):
 
520
                raise BzrError("cannot add control file %s" % quotefn(f))
 
521
 
 
522
            fp = splitpath(f)
 
523
 
 
524
            if len(fp) == 0:
 
525
                raise BzrError("cannot add top-level %r" % f)
 
526
 
 
527
            fullpath = normpath(self.abspath(f))
 
528
 
 
529
            try:
 
530
                kind = file_kind(fullpath)
 
531
            except OSError, e:
 
532
                if e.errno == errno.ENOENT:
 
533
                    raise NoSuchFile(fullpath)
 
534
                # maybe something better?
 
535
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
536
 
 
537
            if not InventoryEntry.versionable_kind(kind):
 
538
                raise BzrError('cannot add: not a versionable file ('
 
539
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
540
 
 
541
            if file_id is None:
 
542
                file_id = gen_file_id(f)
 
543
            inv.add_path(f, kind=kind, file_id=file_id)
 
544
 
 
545
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
546
        self._write_inventory(inv)
 
547
 
 
548
    @needs_write_lock
248
549
    def add_pending_merge(self, *revision_ids):
249
550
        # TODO: Perhaps should check at this point that the
250
551
        # history of the revision is actually present?
258
559
        if updated:
259
560
            self.set_pending_merges(p)
260
561
 
 
562
    @needs_read_lock
261
563
    def pending_merges(self):
262
564
        """Return a list of pending merges.
263
565
 
264
566
        These are revisions that have been merged into the working
265
567
        directory but not yet committed.
266
568
        """
267
 
        cfn = self.branch._rel_controlfilename('pending-merges')
268
 
        if not self.branch._transport.has(cfn):
 
569
        try:
 
570
            merges_file = self._control_files.get_utf8('pending-merges')
 
571
        except OSError, e:
 
572
            if e.errno != errno.ENOENT:
 
573
                raise
269
574
            return []
270
575
        p = []
271
 
        for l in self.branch.controlfile('pending-merges', 'r').readlines():
 
576
        for l in merges_file.readlines():
272
577
            p.append(l.rstrip('\n'))
273
578
        return p
274
579
 
275
580
    @needs_write_lock
276
581
    def set_pending_merges(self, rev_list):
277
 
        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
 
582
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
278
583
 
279
584
    def get_symlink_target(self, file_id):
280
585
        return os.readlink(self.id2abspath(file_id))
287
592
        else:
288
593
            return '?'
289
594
 
290
 
 
291
595
    def list_files(self):
292
596
        """Recursively list all files as (path, class, kind, id).
293
597
 
357
661
                for ff in descend(fp, f_ie.file_id, fap):
358
662
                    yield ff
359
663
 
360
 
        for f in descend('', inv.root.file_id, self.basedir):
 
664
        for f in descend(u'', inv.root.file_id, self.basedir):
361
665
            yield f
362
 
            
363
 
 
364
 
 
 
666
 
 
667
    @needs_write_lock
 
668
    def move(self, from_paths, to_name):
 
669
        """Rename files.
 
670
 
 
671
        to_name must exist in the inventory.
 
672
 
 
673
        If to_name exists and is a directory, the files are moved into
 
674
        it, keeping their old names.  
 
675
 
 
676
        Note that to_name is only the last component of the new name;
 
677
        this doesn't change the directory.
 
678
 
 
679
        This returns a list of (from_path, to_path) pairs for each
 
680
        entry that is moved.
 
681
        """
 
682
        result = []
 
683
        ## TODO: Option to move IDs only
 
684
        assert not isinstance(from_paths, basestring)
 
685
        inv = self.inventory
 
686
        to_abs = self.abspath(to_name)
 
687
        if not isdir(to_abs):
 
688
            raise BzrError("destination %r is not a directory" % to_abs)
 
689
        if not self.has_filename(to_name):
 
690
            raise BzrError("destination %r not in working directory" % to_abs)
 
691
        to_dir_id = inv.path2id(to_name)
 
692
        if to_dir_id == None and to_name != '':
 
693
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
694
        to_dir_ie = inv[to_dir_id]
 
695
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
696
            raise BzrError("destination %r is not a directory" % to_abs)
 
697
 
 
698
        to_idpath = inv.get_idpath(to_dir_id)
 
699
 
 
700
        for f in from_paths:
 
701
            if not self.has_filename(f):
 
702
                raise BzrError("%r does not exist in working tree" % f)
 
703
            f_id = inv.path2id(f)
 
704
            if f_id == None:
 
705
                raise BzrError("%r is not versioned" % f)
 
706
            name_tail = splitpath(f)[-1]
 
707
            dest_path = appendpath(to_name, name_tail)
 
708
            if self.has_filename(dest_path):
 
709
                raise BzrError("destination %r already exists" % dest_path)
 
710
            if f_id in to_idpath:
 
711
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
712
 
 
713
        # OK, so there's a race here, it's possible that someone will
 
714
        # create a file in this interval and then the rename might be
 
715
        # left half-done.  But we should have caught most problems.
 
716
        orig_inv = deepcopy(self.inventory)
 
717
        try:
 
718
            for f in from_paths:
 
719
                name_tail = splitpath(f)[-1]
 
720
                dest_path = appendpath(to_name, name_tail)
 
721
                result.append((f, dest_path))
 
722
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
723
                try:
 
724
                    rename(self.abspath(f), self.abspath(dest_path))
 
725
                except OSError, e:
 
726
                    raise BzrError("failed to rename %r to %r: %s" %
 
727
                                   (f, dest_path, e[1]),
 
728
                            ["rename rolled back"])
 
729
        except:
 
730
            # restore the inventory on error
 
731
            self._set_inventory(orig_inv)
 
732
            raise
 
733
        self._write_inventory(inv)
 
734
        return result
 
735
 
 
736
    @needs_write_lock
 
737
    def rename_one(self, from_rel, to_rel):
 
738
        """Rename one file.
 
739
 
 
740
        This can change the directory or the filename or both.
 
741
        """
 
742
        inv = self.inventory
 
743
        if not self.has_filename(from_rel):
 
744
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
745
        if self.has_filename(to_rel):
 
746
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
747
 
 
748
        file_id = inv.path2id(from_rel)
 
749
        if file_id == None:
 
750
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
751
 
 
752
        entry = inv[file_id]
 
753
        from_parent = entry.parent_id
 
754
        from_name = entry.name
 
755
        
 
756
        if inv.path2id(to_rel):
 
757
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
758
 
 
759
        to_dir, to_tail = os.path.split(to_rel)
 
760
        to_dir_id = inv.path2id(to_dir)
 
761
        if to_dir_id == None and to_dir != '':
 
762
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
763
 
 
764
        mutter("rename_one:")
 
765
        mutter("  file_id    {%s}" % file_id)
 
766
        mutter("  from_rel   %r" % from_rel)
 
767
        mutter("  to_rel     %r" % to_rel)
 
768
        mutter("  to_dir     %r" % to_dir)
 
769
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
770
 
 
771
        inv.rename(file_id, to_dir_id, to_tail)
 
772
 
 
773
        from_abs = self.abspath(from_rel)
 
774
        to_abs = self.abspath(to_rel)
 
775
        try:
 
776
            rename(from_abs, to_abs)
 
777
        except OSError, e:
 
778
            inv.rename(file_id, from_parent, from_name)
 
779
            raise BzrError("failed to rename %r to %r: %s"
 
780
                    % (from_abs, to_abs, e[1]),
 
781
                    ["rename rolled back"])
 
782
        self._write_inventory(inv)
 
783
 
 
784
    @needs_read_lock
365
785
    def unknowns(self):
 
786
        """Return all unknown files.
 
787
 
 
788
        These are files in the working directory that are not versioned or
 
789
        control files or ignored.
 
790
        
 
791
        >>> from bzrlib.bzrdir import ScratchDir
 
792
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
793
        >>> b = d.open_branch()
 
794
        >>> tree = d.open_workingtree()
 
795
        >>> map(str, tree.unknowns())
 
796
        ['foo']
 
797
        >>> tree.add('foo')
 
798
        >>> list(b.unknowns())
 
799
        []
 
800
        >>> tree.remove('foo')
 
801
        >>> list(b.unknowns())
 
802
        [u'foo']
 
803
        """
366
804
        for subp in self.extras():
367
805
            if not self.is_ignored(subp):
368
806
                yield subp
378
816
                yield stem
379
817
 
380
818
    @needs_write_lock
381
 
    def pull(self, source, overwrite=False):
382
 
        from bzrlib.merge import merge_inner
 
819
    def pull(self, source, overwrite=False, stop_revision=None):
383
820
        source.lock_read()
384
821
        try:
385
822
            old_revision_history = self.branch.revision_history()
386
 
            self.branch.pull(source, overwrite)
 
823
            count = self.branch.pull(source, overwrite, stop_revision)
387
824
            new_revision_history = self.branch.revision_history()
388
825
            if new_revision_history != old_revision_history:
389
826
                if len(old_revision_history):
390
827
                    other_revision = old_revision_history[-1]
391
828
                else:
392
829
                    other_revision = None
 
830
                repository = self.branch.repository
393
831
                merge_inner(self.branch,
394
 
                            self.branch.basis_tree(), 
395
 
                            self.branch.revision_tree(other_revision))
 
832
                            self.basis_tree(), 
 
833
                            repository.revision_tree(other_revision),
 
834
                            this_tree=self)
 
835
                self.set_last_revision(self.branch.last_revision())
 
836
            return count
396
837
        finally:
397
838
            source.unlock()
398
839
 
490
931
    def kind(self, file_id):
491
932
        return file_kind(self.id2abspath(file_id))
492
933
 
 
934
    @needs_read_lock
 
935
    def last_revision(self):
 
936
        """Return the last revision id of this working tree.
 
937
 
 
938
        In early branch formats this was == the branch last_revision,
 
939
        but that cannot be relied upon - for working tree operations,
 
940
        always use tree.last_revision().
 
941
        """
 
942
        return self.branch.last_revision()
 
943
 
493
944
    def lock_read(self):
494
945
        """See Branch.lock_read, and WorkingTree.unlock."""
495
 
        return self.branch.lock_read()
 
946
        self.branch.lock_read()
 
947
        try:
 
948
            return self._control_files.lock_read()
 
949
        except:
 
950
            self.branch.unlock()
 
951
            raise
496
952
 
497
953
    def lock_write(self):
498
954
        """See Branch.lock_write, and WorkingTree.unlock."""
499
 
        return self.branch.lock_write()
500
 
 
 
955
        self.branch.lock_write()
 
956
        try:
 
957
            return self._control_files.lock_write()
 
958
        except:
 
959
            self.branch.unlock()
 
960
            raise
 
961
 
 
962
    def _basis_inventory_name(self, revision_id):
 
963
        return 'basis-inventory.%s' % revision_id
 
964
 
 
965
    @needs_write_lock
 
966
    def set_last_revision(self, new_revision, old_revision=None):
 
967
        """Change the last revision in the working tree."""
 
968
        self._remove_old_basis(old_revision)
 
969
        if self._change_last_revision(new_revision):
 
970
            self._cache_basis_inventory(new_revision)
 
971
 
 
972
    def _change_last_revision(self, new_revision):
 
973
        """Template method part of set_last_revision to perform the change."""
 
974
        if new_revision is None:
 
975
            self.branch.set_revision_history([])
 
976
            return False
 
977
        # current format is locked in with the branch
 
978
        revision_history = self.branch.revision_history()
 
979
        try:
 
980
            position = revision_history.index(new_revision)
 
981
        except ValueError:
 
982
            raise errors.NoSuchRevision(self.branch, new_revision)
 
983
        self.branch.set_revision_history(revision_history[:position + 1])
 
984
        return True
 
985
 
 
986
    def _cache_basis_inventory(self, new_revision):
 
987
        """Cache new_revision as the basis inventory."""
 
988
        try:
 
989
            xml = self.branch.repository.get_inventory_xml(new_revision)
 
990
            path = self._basis_inventory_name(new_revision)
 
991
            self._control_files.put_utf8(path, xml)
 
992
        except WeaveRevisionNotPresent:
 
993
            pass
 
994
 
 
995
    def _remove_old_basis(self, old_revision):
 
996
        """Remove the old basis inventory 'old_revision'."""
 
997
        if old_revision is not None:
 
998
            try:
 
999
                path = self._basis_inventory_name(old_revision)
 
1000
                path = self._control_files._escape(path)
 
1001
                self._control_files._transport.delete(path)
 
1002
            except NoSuchFile:
 
1003
                pass
 
1004
 
 
1005
    def read_basis_inventory(self, revision_id):
 
1006
        """Read the cached basis inventory."""
 
1007
        path = self._basis_inventory_name(revision_id)
 
1008
        return self._control_files.get_utf8(path).read()
 
1009
        
501
1010
    @needs_read_lock
502
1011
    def read_working_inventory(self):
503
1012
        """Read the working inventory."""
504
1013
        # ElementTree does its own conversion from UTF-8, so open in
505
1014
        # binary.
506
 
        f = self.branch.controlfile('inventory', 'rb')
507
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
 
1015
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1016
            self._control_files.get('inventory'))
 
1017
        self._set_inventory(result)
 
1018
        return result
508
1019
 
509
1020
    @needs_write_lock
510
1021
    def remove(self, files, verbose=False):
551
1062
    def revert(self, filenames, old_tree=None, backups=True):
552
1063
        from bzrlib.merge import merge_inner
553
1064
        if old_tree is None:
554
 
            old_tree = self.branch.basis_tree()
 
1065
            old_tree = self.basis_tree()
555
1066
        merge_inner(self.branch, old_tree,
556
1067
                    self, ignore_zero=True,
557
1068
                    backup_files=backups, 
558
 
                    interesting_files=filenames)
 
1069
                    interesting_files=filenames,
 
1070
                    this_tree=self)
559
1071
        if not len(filenames):
560
1072
            self.set_pending_merges([])
561
1073
 
592
1104
        inv._byid[inv.root.file_id] = inv.root
593
1105
        for fid in inv:
594
1106
            entry = inv[fid]
595
 
            if entry.parent_id in (None, orig_root_id):
 
1107
            if entry.parent_id == orig_root_id:
596
1108
                entry.parent_id = inv.root.file_id
597
1109
        self._write_inventory(inv)
598
1110
 
605
1117
        between multiple working trees, i.e. via shared storage, then we 
606
1118
        would probably want to lock both the local tree, and the branch.
607
1119
        """
608
 
        return self.branch.unlock()
 
1120
        # FIXME: We want to write out the hashcache only when the last lock on
 
1121
        # this working copy is released.  Peeking at the lock count is a bit
 
1122
        # of a nasty hack; probably it's better to have a transaction object,
 
1123
        # which can do some finalization when it's either successfully or
 
1124
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1125
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1126
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1127
        
 
1128
        # TODO: split this per format so there is no ugly if block
 
1129
        if self._hashcache.needs_write and (
 
1130
            # dedicated lock files
 
1131
            self._control_files._lock_count==1 or 
 
1132
            # shared lock files
 
1133
            (self._control_files is self.branch.control_files and 
 
1134
             self._control_files._lock_count==3)):
 
1135
            self._hashcache.write()
 
1136
        # reverse order of locking.
 
1137
        result = self._control_files.unlock()
 
1138
        try:
 
1139
            self.branch.unlock()
 
1140
        finally:
 
1141
            return result
 
1142
 
 
1143
    @needs_write_lock
 
1144
    def update(self):
 
1145
        self.branch.lock_read()
 
1146
        try:
 
1147
            if self.last_revision() == self.branch.last_revision():
 
1148
                return
 
1149
            basis = self.basis_tree()
 
1150
            to_tree = self.branch.basis_tree()
 
1151
            result = merge_inner(self.branch,
 
1152
                                 to_tree,
 
1153
                                 basis,
 
1154
                                 this_tree=self)
 
1155
            self.set_last_revision(self.branch.last_revision())
 
1156
            return result
 
1157
        finally:
 
1158
            self.branch.unlock()
609
1159
 
610
1160
    @needs_write_lock
611
1161
    def _write_inventory(self, inv):
612
1162
        """Write inventory as the current inventory."""
613
 
        from cStringIO import StringIO
614
 
        from bzrlib.atomicfile import AtomicFile
615
1163
        sio = StringIO()
616
1164
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
617
1165
        sio.seek(0)
618
 
        f = AtomicFile(self.branch.controlfilename('inventory'))
619
 
        try:
620
 
            pumpfile(sio, f)
621
 
            f.commit()
622
 
        finally:
623
 
            f.close()
 
1166
        self._control_files.put('inventory', sio)
 
1167
        self._set_inventory(inv)
624
1168
        mutter('wrote working inventory')
625
 
            
 
1169
 
 
1170
 
 
1171
class WorkingTree3(WorkingTree):
 
1172
    """This is the Format 3 working tree.
 
1173
 
 
1174
    This differs from the base WorkingTree by:
 
1175
     - having its own file lock
 
1176
     - having its own last-revision property.
 
1177
    """
 
1178
 
 
1179
    @needs_read_lock
 
1180
    def last_revision(self):
 
1181
        """See WorkingTree.last_revision."""
 
1182
        try:
 
1183
            return self._control_files.get_utf8('last-revision').read()
 
1184
        except NoSuchFile:
 
1185
            return None
 
1186
 
 
1187
    def _change_last_revision(self, revision_id):
 
1188
        """See WorkingTree._change_last_revision."""
 
1189
        if revision_id is None or revision_id == NULL_REVISION:
 
1190
            try:
 
1191
                self._control_files._transport.delete('last-revision')
 
1192
            except errors.NoSuchFile:
 
1193
                pass
 
1194
            return False
 
1195
        else:
 
1196
            try:
 
1197
                self.branch.revision_history().index(revision_id)
 
1198
            except ValueError:
 
1199
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1200
            self._control_files.put_utf8('last-revision', revision_id)
 
1201
            return True
 
1202
 
626
1203
 
627
1204
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
628
1205
def get_conflicted_stem(path):
629
1206
    for suffix in CONFLICT_SUFFIXES:
630
1207
        if path.endswith(suffix):
631
1208
            return path[:-len(suffix)]
 
1209
 
 
1210
def is_control_file(filename):
 
1211
    ## FIXME: better check
 
1212
    filename = normpath(filename)
 
1213
    while filename != '':
 
1214
        head, tail = os.path.split(filename)
 
1215
        ## mutter('check %r for control file' % ((head, tail),))
 
1216
        if tail == bzrlib.BZRDIR:
 
1217
            return True
 
1218
        if filename == head:
 
1219
            break
 
1220
        filename = head
 
1221
    return False
 
1222
 
 
1223
 
 
1224
class WorkingTreeFormat(object):
 
1225
    """An encapsulation of the initialization and open routines for a format.
 
1226
 
 
1227
    Formats provide three things:
 
1228
     * An initialization routine,
 
1229
     * a format string,
 
1230
     * an open routine.
 
1231
 
 
1232
    Formats are placed in an dict by their format string for reference 
 
1233
    during workingtree opening. Its not required that these be instances, they
 
1234
    can be classes themselves with class methods - it simply depends on 
 
1235
    whether state is needed for a given format or not.
 
1236
 
 
1237
    Once a format is deprecated, just deprecate the initialize and open
 
1238
    methods on the format class. Do not deprecate the object, as the 
 
1239
    object will be created every time regardless.
 
1240
    """
 
1241
 
 
1242
    _default_format = None
 
1243
    """The default format used for new trees."""
 
1244
 
 
1245
    _formats = {}
 
1246
    """The known formats."""
 
1247
 
 
1248
    @classmethod
 
1249
    def find_format(klass, a_bzrdir):
 
1250
        """Return the format for the working tree object in a_bzrdir."""
 
1251
        try:
 
1252
            transport = a_bzrdir.get_workingtree_transport(None)
 
1253
            format_string = transport.get("format").read()
 
1254
            return klass._formats[format_string]
 
1255
        except NoSuchFile:
 
1256
            raise errors.NoWorkingTree(base=transport.base)
 
1257
        except KeyError:
 
1258
            raise errors.UnknownFormatError(format_string)
 
1259
 
 
1260
    @classmethod
 
1261
    def get_default_format(klass):
 
1262
        """Return the current default format."""
 
1263
        return klass._default_format
 
1264
 
 
1265
    def get_format_string(self):
 
1266
        """Return the ASCII format string that identifies this format."""
 
1267
        raise NotImplementedError(self.get_format_string)
 
1268
 
 
1269
    def is_supported(self):
 
1270
        """Is this format supported?
 
1271
 
 
1272
        Supported formats can be initialized and opened.
 
1273
        Unsupported formats may not support initialization or committing or 
 
1274
        some other features depending on the reason for not being supported.
 
1275
        """
 
1276
        return True
 
1277
 
 
1278
    @classmethod
 
1279
    def register_format(klass, format):
 
1280
        klass._formats[format.get_format_string()] = format
 
1281
 
 
1282
    @classmethod
 
1283
    def set_default_format(klass, format):
 
1284
        klass._default_format = format
 
1285
 
 
1286
    @classmethod
 
1287
    def unregister_format(klass, format):
 
1288
        assert klass._formats[format.get_format_string()] is format
 
1289
        del klass._formats[format.get_format_string()]
 
1290
 
 
1291
 
 
1292
 
 
1293
class WorkingTreeFormat2(WorkingTreeFormat):
 
1294
    """The second working tree format. 
 
1295
 
 
1296
    This format modified the hash cache from the format 1 hash cache.
 
1297
    """
 
1298
 
 
1299
    def initialize(self, a_bzrdir, revision_id=None):
 
1300
        """See WorkingTreeFormat.initialize()."""
 
1301
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1302
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1303
        branch = a_bzrdir.open_branch()
 
1304
        if revision_id is not None:
 
1305
            branch.lock_write()
 
1306
            try:
 
1307
                revision_history = branch.revision_history()
 
1308
                try:
 
1309
                    position = revision_history.index(revision_id)
 
1310
                except ValueError:
 
1311
                    raise errors.NoSuchRevision(branch, revision_id)
 
1312
                branch.set_revision_history(revision_history[:position + 1])
 
1313
            finally:
 
1314
                branch.unlock()
 
1315
        revision = branch.last_revision()
 
1316
        basis_tree = branch.repository.revision_tree(revision)
 
1317
        inv = basis_tree.inventory
 
1318
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1319
                         branch,
 
1320
                         inv,
 
1321
                         _internal=True,
 
1322
                         _format=self,
 
1323
                         _bzrdir=a_bzrdir)
 
1324
        wt._write_inventory(inv)
 
1325
        wt.set_root_id(inv.root.file_id)
 
1326
        wt.set_last_revision(revision)
 
1327
        wt.set_pending_merges([])
 
1328
        wt.revert([])
 
1329
        return wt
 
1330
 
 
1331
    def __init__(self):
 
1332
        super(WorkingTreeFormat2, self).__init__()
 
1333
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1334
 
 
1335
    def open(self, a_bzrdir, _found=False):
 
1336
        """Return the WorkingTree object for a_bzrdir
 
1337
 
 
1338
        _found is a private parameter, do not use it. It is used to indicate
 
1339
               if format probing has already been done.
 
1340
        """
 
1341
        if not _found:
 
1342
            # we are being called directly and must probe.
 
1343
            raise NotImplementedError
 
1344
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1345
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1346
        return WorkingTree(a_bzrdir.root_transport.base,
 
1347
                           _internal=True,
 
1348
                           _format=self,
 
1349
                           _bzrdir=a_bzrdir)
 
1350
 
 
1351
 
 
1352
class WorkingTreeFormat3(WorkingTreeFormat):
 
1353
    """The second working tree format updated to record a format marker.
 
1354
 
 
1355
    This format modified the hash cache from the format 1 hash cache.
 
1356
    """
 
1357
 
 
1358
    def get_format_string(self):
 
1359
        """See WorkingTreeFormat.get_format_string()."""
 
1360
        return "Bazaar-NG Working Tree format 3"
 
1361
 
 
1362
    def initialize(self, a_bzrdir, revision_id=None):
 
1363
        """See WorkingTreeFormat.initialize().
 
1364
        
 
1365
        revision_id allows creating a working tree at a differnet
 
1366
        revision than the branch is at.
 
1367
        """
 
1368
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1369
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1370
        transport = a_bzrdir.get_workingtree_transport(self)
 
1371
        control_files = LockableFiles(transport, 'lock')
 
1372
        control_files.put_utf8('format', self.get_format_string())
 
1373
        branch = a_bzrdir.open_branch()
 
1374
        if revision_id is None:
 
1375
            revision_id = branch.last_revision()
 
1376
        new_basis_tree = branch.repository.revision_tree(revision_id)
 
1377
        inv = new_basis_tree.inventory
 
1378
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1379
                         branch,
 
1380
                         inv,
 
1381
                         _internal=True,
 
1382
                         _format=self,
 
1383
                         _bzrdir=a_bzrdir)
 
1384
        wt._write_inventory(inv)
 
1385
        wt.set_root_id(inv.root.file_id)
 
1386
        wt.set_last_revision(revision_id)
 
1387
        wt.set_pending_merges([])
 
1388
        wt.revert([])
 
1389
        return wt
 
1390
 
 
1391
    def __init__(self):
 
1392
        super(WorkingTreeFormat3, self).__init__()
 
1393
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1394
 
 
1395
    def open(self, a_bzrdir, _found=False):
 
1396
        """Return the WorkingTree object for a_bzrdir
 
1397
 
 
1398
        _found is a private parameter, do not use it. It is used to indicate
 
1399
               if format probing has already been done.
 
1400
        """
 
1401
        if not _found:
 
1402
            # we are being called directly and must probe.
 
1403
            raise NotImplementedError
 
1404
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1405
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1406
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1407
                           _internal=True,
 
1408
                           _format=self,
 
1409
                           _bzrdir=a_bzrdir)
 
1410
 
 
1411
    def __str__(self):
 
1412
        return self.get_format_string()
 
1413
 
 
1414
 
 
1415
# formats which have no format string are not discoverable
 
1416
# and not independently creatable, so are not registered.
 
1417
__default_format = WorkingTreeFormat3()
 
1418
WorkingTreeFormat.register_format(__default_format)
 
1419
WorkingTreeFormat.set_default_format(__default_format)
 
1420
_legacy_formats = [WorkingTreeFormat2(),
 
1421
                   ]
 
1422
 
 
1423
 
 
1424
class WorkingTreeTestProviderAdapter(object):
 
1425
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1426
 
 
1427
    This is done by copying the test once for each transport and injecting
 
1428
    the transport_server, transport_readonly_server, and workingtree_format
 
1429
    classes into each copy. Each copy is also given a new id() to make it
 
1430
    easy to identify.
 
1431
    """
 
1432
 
 
1433
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1434
        self._transport_server = transport_server
 
1435
        self._transport_readonly_server = transport_readonly_server
 
1436
        self._formats = formats
 
1437
    
 
1438
    def adapt(self, test):
 
1439
        from bzrlib.tests import TestSuite
 
1440
        result = TestSuite()
 
1441
        for workingtree_format, bzrdir_format in self._formats:
 
1442
            new_test = deepcopy(test)
 
1443
            new_test.transport_server = self._transport_server
 
1444
            new_test.transport_readonly_server = self._transport_readonly_server
 
1445
            new_test.bzrdir_format = bzrdir_format
 
1446
            new_test.workingtree_format = workingtree_format
 
1447
            def make_new_test_id():
 
1448
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1449
                return lambda: new_id
 
1450
            new_test.id = make_new_test_id()
 
1451
            result.addTest(new_test)
 
1452
        return result