~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-03-09 06:39:13 UTC
  • mfrom: (1596.2.6 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060309063913-6d8ce700706d0802
Merge knit performance stage 1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
 
17
"""WorkingTree object and friends.
 
18
 
 
19
A WorkingTree represents the editable working copy of a branch.
 
20
Operations which represent the WorkingTree are also done here, 
 
21
such as renaming or adding files.  The WorkingTree has an inventory 
 
22
which is updated by these operations.  A commit produces a 
 
23
new revision based on the workingtree and its inventory.
 
24
 
 
25
At the moment every WorkingTree has its own branch.  Remote
 
26
WorkingTrees aren't supported.
 
27
 
 
28
To get a WorkingTree, call bzrdir.open_workingtree() or
 
29
WorkingTree.open(dir).
 
30
"""
 
31
 
 
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
18
33
 
19
34
# FIXME: I don't know if writing out the cache from the destructor is really a
20
 
# good idea, because destructors are considered poor taste in Python, and
21
 
# it's not predictable when it will be written out.
22
 
 
 
35
# good idea, because destructors are considered poor taste in Python, and it's
 
36
# not predictable when it will be written out.
 
37
 
 
38
# TODO: Give the workingtree sole responsibility for the working inventory;
 
39
# remove the variable and references to it from the branch.  This may require
 
40
# updating the commit code so as to update the inventory within the working
 
41
# copy, and making sure there's only one WorkingTree for any directory on disk.
 
42
# At the momenthey may alias the inventory and have old copies of it in memory.
 
43
 
 
44
from copy import deepcopy
 
45
from cStringIO import StringIO
 
46
import errno
 
47
import fnmatch
23
48
import os
24
 
import fnmatch
25
 
        
 
49
import stat
 
50
 
 
51
 
 
52
from bzrlib.atomicfile import AtomicFile
 
53
from bzrlib.branch import (Branch,
 
54
                           quotefn)
 
55
import bzrlib.bzrdir as bzrdir
 
56
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
57
import bzrlib.errors as errors
 
58
from bzrlib.errors import (BzrCheckError,
 
59
                           BzrError,
 
60
                           DivergedBranches,
 
61
                           WeaveRevisionNotPresent,
 
62
                           NotBranchError,
 
63
                           NoSuchFile,
 
64
                           NotVersionedError,
 
65
                           MergeModifiedFormatError)
 
66
from bzrlib.inventory import InventoryEntry, Inventory
 
67
from bzrlib.lockable_files import LockableFiles, TransportLock
 
68
from bzrlib.merge import merge_inner, transform_tree
 
69
from bzrlib.osutils import (
 
70
                            abspath,
 
71
                            appendpath,
 
72
                            compact_date,
 
73
                            file_kind,
 
74
                            isdir,
 
75
                            getcwd,
 
76
                            pathjoin,
 
77
                            pumpfile,
 
78
                            safe_unicode,
 
79
                            splitpath,
 
80
                            rand_bytes,
 
81
                            normpath,
 
82
                            realpath,
 
83
                            relpath,
 
84
                            rename,
 
85
                            supports_executable,
 
86
                            )
 
87
from bzrlib.progress import DummyProgress
 
88
from bzrlib.revision import NULL_REVISION
 
89
from bzrlib.rio import RioReader, RioWriter, Stanza
 
90
from bzrlib.symbol_versioning import *
 
91
from bzrlib.textui import show_status
26
92
import bzrlib.tree
27
 
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
28
 
from bzrlib.errors import BzrCheckError
29
93
from bzrlib.trace import mutter
 
94
from bzrlib.transform import build_tree
 
95
from bzrlib.transport import get_transport
 
96
from bzrlib.transport.local import LocalTransport
 
97
import bzrlib.ui
 
98
import bzrlib.xml5
 
99
 
 
100
 
 
101
def gen_file_id(name):
 
102
    """Return new file id.
 
103
 
 
104
    This should probably generate proper UUIDs, but for the moment we
 
105
    cope with just randomness because running uuidgen every time is
 
106
    slow."""
 
107
    import re
 
108
    from binascii import hexlify
 
109
    from time import time
 
110
 
 
111
    # get last component
 
112
    idx = name.rfind('/')
 
113
    if idx != -1:
 
114
        name = name[idx+1 : ]
 
115
    idx = name.rfind('\\')
 
116
    if idx != -1:
 
117
        name = name[idx+1 : ]
 
118
 
 
119
    # make it not a hidden file
 
120
    name = name.lstrip('.')
 
121
 
 
122
    # remove any wierd characters; we don't escape them but rather
 
123
    # just pull them out
 
124
    name = re.sub(r'[^\w.]', '', name)
 
125
 
 
126
    s = hexlify(rand_bytes(8))
 
127
    return '-'.join((name, compact_date(time()), s))
 
128
 
 
129
 
 
130
def gen_root_id():
 
131
    """Return a new tree-root file id."""
 
132
    return gen_file_id('TREE_ROOT')
 
133
 
 
134
 
 
135
class TreeEntry(object):
 
136
    """An entry that implements the minium interface used by commands.
 
137
 
 
138
    This needs further inspection, it may be better to have 
 
139
    InventoryEntries without ids - though that seems wrong. For now,
 
140
    this is a parallel hierarchy to InventoryEntry, and needs to become
 
141
    one of several things: decorates to that hierarchy, children of, or
 
142
    parents of it.
 
143
    Another note is that these objects are currently only used when there is
 
144
    no InventoryEntry available - i.e. for unversioned objects.
 
145
    Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
 
146
    """
 
147
 
 
148
    def __eq__(self, other):
 
149
        # yes, this us ugly, TODO: best practice __eq__ style.
 
150
        return (isinstance(other, TreeEntry)
 
151
                and other.__class__ == self.__class__)
 
152
 
 
153
    def kind_character(self):
 
154
        return "???"
 
155
 
 
156
 
 
157
class TreeDirectory(TreeEntry):
 
158
    """See TreeEntry. This is a directory in a working tree."""
 
159
 
 
160
    def __eq__(self, other):
 
161
        return (isinstance(other, TreeDirectory)
 
162
                and other.__class__ == self.__class__)
 
163
 
 
164
    def kind_character(self):
 
165
        return "/"
 
166
 
 
167
 
 
168
class TreeFile(TreeEntry):
 
169
    """See TreeEntry. This is a regular file in a working tree."""
 
170
 
 
171
    def __eq__(self, other):
 
172
        return (isinstance(other, TreeFile)
 
173
                and other.__class__ == self.__class__)
 
174
 
 
175
    def kind_character(self):
 
176
        return ''
 
177
 
 
178
 
 
179
class TreeLink(TreeEntry):
 
180
    """See TreeEntry. This is a symlink in a working tree."""
 
181
 
 
182
    def __eq__(self, other):
 
183
        return (isinstance(other, TreeLink)
 
184
                and other.__class__ == self.__class__)
 
185
 
 
186
    def kind_character(self):
 
187
        return ''
 
188
 
30
189
 
31
190
class WorkingTree(bzrlib.tree.Tree):
32
191
    """Working copy tree.
37
196
    It is possible for a `WorkingTree` to have a filename which is
38
197
    not listed in the Inventory and vice versa.
39
198
    """
40
 
    def __init__(self, basedir, inv):
 
199
 
 
200
    def __init__(self, basedir='.',
 
201
                 branch=DEPRECATED_PARAMETER,
 
202
                 _inventory=None,
 
203
                 _control_files=None,
 
204
                 _internal=False,
 
205
                 _format=None,
 
206
                 _bzrdir=None):
 
207
        """Construct a WorkingTree for basedir.
 
208
 
 
209
        If the branch is not supplied, it is opened automatically.
 
210
        If the branch is supplied, it must be the branch for this basedir.
 
211
        (branch.base is not cross checked, because for remote branches that
 
212
        would be meaningless).
 
213
        """
 
214
        self._format = _format
 
215
        self.bzrdir = _bzrdir
 
216
        if not _internal:
 
217
            # not created via open etc.
 
218
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
219
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
220
                 DeprecationWarning,
 
221
                 stacklevel=2)
 
222
            wt = WorkingTree.open(basedir)
 
223
            self.branch = wt.branch
 
224
            self.basedir = wt.basedir
 
225
            self._control_files = wt._control_files
 
226
            self._hashcache = wt._hashcache
 
227
            self._set_inventory(wt._inventory)
 
228
            self._format = wt._format
 
229
            self.bzrdir = wt.bzrdir
41
230
        from bzrlib.hashcache import HashCache
42
231
        from bzrlib.trace import note, mutter
43
 
 
44
 
        self._inventory = inv
45
 
        self.basedir = basedir
46
 
        self.path2id = inv.path2id
 
232
        assert isinstance(basedir, basestring), \
 
233
            "base directory %r is not a string" % basedir
 
234
        basedir = safe_unicode(basedir)
 
235
        mutter("opening working tree %r", basedir)
 
236
        if deprecated_passed(branch):
 
237
            if not _internal:
 
238
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
239
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
 
240
                     DeprecationWarning,
 
241
                     stacklevel=2
 
242
                     )
 
243
            self.branch = branch
 
244
        else:
 
245
            self.branch = self.bzrdir.open_branch()
 
246
        assert isinstance(self.branch, Branch), \
 
247
            "branch %r is not a Branch" % self.branch
 
248
        self.basedir = realpath(basedir)
 
249
        # if branch is at our basedir and is a format 6 or less
 
250
        if isinstance(self._format, WorkingTreeFormat2):
 
251
            # share control object
 
252
            self._control_files = self.branch.control_files
 
253
        elif _control_files is not None:
 
254
            assert False, "not done yet"
 
255
#            self._control_files = _control_files
 
256
        else:
 
257
            # only ready for format 3
 
258
            assert isinstance(self._format, WorkingTreeFormat3)
 
259
            self._control_files = LockableFiles(
 
260
                self.bzrdir.get_workingtree_transport(None),
 
261
                'lock', TransportLock)
47
262
 
48
263
        # update the whole cache up front and write to disk if anything changed;
49
264
        # in the future we might want to do this more selectively
50
 
        hc = self._hashcache = HashCache(basedir)
 
265
        # two possible ways offer themselves : in self._unlock, write the cache
 
266
        # if needed, or, when the cache sees a change, append it to the hash
 
267
        # cache file, and have the parser take the most recent entry for a
 
268
        # given path only.
 
269
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
270
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
51
271
        hc.read()
 
272
        # is this scan needed ? it makes things kinda slow.
52
273
        hc.scan()
53
274
 
54
275
        if hc.needs_write:
55
276
            mutter("write hc")
56
277
            hc.write()
57
 
            
58
 
            
59
 
    def __del__(self):
60
 
        if self._hashcache.needs_write:
61
 
            self._hashcache.write()
62
 
 
 
278
 
 
279
        if _inventory is None:
 
280
            self._set_inventory(self.read_working_inventory())
 
281
        else:
 
282
            self._set_inventory(_inventory)
 
283
 
 
284
    def _set_inventory(self, inv):
 
285
        self._inventory = inv
 
286
        self.path2id = self._inventory.path2id
 
287
 
 
288
    def is_control_filename(self, filename):
 
289
        """True if filename is the name of a control file in this tree.
 
290
        
 
291
        This is true IF and ONLY IF the filename is part of the meta data
 
292
        that bzr controls in this tree. I.E. a random .bzr directory placed
 
293
        on disk will not be a control file for this tree.
 
294
        """
 
295
        try:
 
296
            self.bzrdir.transport.relpath(self.abspath(filename))
 
297
            return True
 
298
        except errors.PathNotChild:
 
299
            return False
 
300
 
 
301
    @staticmethod
 
302
    def open(path=None, _unsupported=False):
 
303
        """Open an existing working tree at path.
 
304
 
 
305
        """
 
306
        if path is None:
 
307
            path = os.path.getcwdu()
 
308
        control = bzrdir.BzrDir.open(path, _unsupported)
 
309
        return control.open_workingtree(_unsupported)
 
310
        
 
311
    @staticmethod
 
312
    def open_containing(path=None):
 
313
        """Open an existing working tree which has its root about path.
 
314
        
 
315
        This probes for a working tree at path and searches upwards from there.
 
316
 
 
317
        Basically we keep looking up until we find the control directory or
 
318
        run into /.  If there isn't one, raises NotBranchError.
 
319
        TODO: give this a new exception.
 
320
        If there is one, it is returned, along with the unused portion of path.
 
321
        """
 
322
        if path is None:
 
323
            path = os.getcwdu()
 
324
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
325
        return control.open_workingtree(), relpath
 
326
 
 
327
    @staticmethod
 
328
    def open_downlevel(path=None):
 
329
        """Open an unsupported working tree.
 
330
 
 
331
        Only intended for advanced situations like upgrading part of a bzrdir.
 
332
        """
 
333
        return WorkingTree.open(path, _unsupported=True)
63
334
 
64
335
    def __iter__(self):
65
336
        """Iterate through file_ids for this tree.
69
340
        """
70
341
        inv = self._inventory
71
342
        for path, ie in inv.iter_entries():
72
 
            if os.path.exists(self.abspath(path)):
 
343
            if bzrlib.osutils.lexists(self.abspath(path)):
73
344
                yield ie.file_id
74
345
 
75
 
 
76
346
    def __repr__(self):
77
347
        return "<%s of %s>" % (self.__class__.__name__,
78
348
                               getattr(self, 'basedir', None))
79
349
 
80
 
 
81
 
 
82
350
    def abspath(self, filename):
83
 
        return os.path.join(self.basedir, filename)
 
351
        return pathjoin(self.basedir, filename)
 
352
    
 
353
    def basis_tree(self):
 
354
        """Return RevisionTree for the current last revision."""
 
355
        revision_id = self.last_revision()
 
356
        if revision_id is not None:
 
357
            try:
 
358
                xml = self.read_basis_inventory(revision_id)
 
359
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
360
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
 
361
                                                revision_id)
 
362
            except NoSuchFile:
 
363
                pass
 
364
        return self.branch.repository.revision_tree(revision_id)
 
365
 
 
366
    @staticmethod
 
367
    @deprecated_method(zero_eight)
 
368
    def create(branch, directory):
 
369
        """Create a workingtree for branch at directory.
 
370
 
 
371
        If existing_directory already exists it must have a .bzr directory.
 
372
        If it does not exist, it will be created.
 
373
 
 
374
        This returns a new WorkingTree object for the new checkout.
 
375
 
 
376
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
377
        should accept an optional revisionid to checkout [and reject this if
 
378
        checking out into the same dir as a pre-checkout-aware branch format.]
 
379
 
 
380
        XXX: When BzrDir is present, these should be created through that 
 
381
        interface instead.
 
382
        """
 
383
        warn('delete WorkingTree.create', stacklevel=3)
 
384
        transport = get_transport(directory)
 
385
        if branch.bzrdir.root_transport.base == transport.base:
 
386
            # same dir 
 
387
            return branch.bzrdir.create_workingtree()
 
388
        # different directory, 
 
389
        # create a branch reference
 
390
        # and now a working tree.
 
391
        raise NotImplementedError
 
392
 
 
393
    @staticmethod
 
394
    @deprecated_method(zero_eight)
 
395
    def create_standalone(directory):
 
396
        """Create a checkout and a branch and a repo at directory.
 
397
 
 
398
        Directory must exist and be empty.
 
399
 
 
400
        please use BzrDir.create_standalone_workingtree
 
401
        """
 
402
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
 
403
 
 
404
    def relpath(self, abs):
 
405
        """Return the local path portion from a given absolute path."""
 
406
        return relpath(self.basedir, abs)
84
407
 
85
408
    def has_filename(self, filename):
86
 
        return os.path.exists(self.abspath(filename))
 
409
        return bzrlib.osutils.lexists(self.abspath(filename))
87
410
 
88
411
    def get_file(self, file_id):
89
412
        return self.get_file_byname(self.id2path(file_id))
91
414
    def get_file_byname(self, filename):
92
415
        return file(self.abspath(filename), 'rb')
93
416
 
 
417
    def get_root_id(self):
 
418
        """Return the id of this trees root"""
 
419
        inv = self.read_working_inventory()
 
420
        return inv.root.file_id
 
421
        
94
422
    def _get_store_filename(self, file_id):
95
 
        ## XXX: badly named; this isn't in the store at all
96
 
        return self.abspath(self.id2path(file_id))
97
 
 
98
 
                
 
423
        ## XXX: badly named; this is not in the store at all
 
424
        return self.abspath(self.id2path(file_id))
 
425
 
 
426
    @needs_read_lock
 
427
    def clone(self, to_bzrdir, revision_id=None, basis=None):
 
428
        """Duplicate this working tree into to_bzr, including all state.
 
429
        
 
430
        Specifically modified files are kept as modified, but
 
431
        ignored and unknown files are discarded.
 
432
 
 
433
        If you want to make a new line of development, see bzrdir.sprout()
 
434
 
 
435
        revision
 
436
            If not None, the cloned tree will have its last revision set to 
 
437
            revision, and and difference between the source trees last revision
 
438
            and this one merged in.
 
439
 
 
440
        basis
 
441
            If not None, a closer copy of a tree which may have some files in
 
442
            common, and which file content should be preferentially copied from.
 
443
        """
 
444
        # assumes the target bzr dir format is compatible.
 
445
        result = self._format.initialize(to_bzrdir)
 
446
        self.copy_content_into(result, revision_id)
 
447
        return result
 
448
 
 
449
    @needs_read_lock
 
450
    def copy_content_into(self, tree, revision_id=None):
 
451
        """Copy the current content and user files of this tree into tree."""
 
452
        if revision_id is None:
 
453
            transform_tree(tree, self)
 
454
        else:
 
455
            # TODO now merge from tree.last_revision to revision
 
456
            transform_tree(tree, self)
 
457
            tree.set_last_revision(revision_id)
 
458
 
 
459
    @needs_write_lock
 
460
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
461
        # avoid circular imports
 
462
        from bzrlib.commit import Commit
 
463
        if revprops is None:
 
464
            revprops = {}
 
465
        if not 'branch-nick' in revprops:
 
466
            revprops['branch-nick'] = self.branch.nick
 
467
        # args for wt.commit start at message from the Commit.commit method,
 
468
        # but with branch a kwarg now, passing in args as is results in the
 
469
        #message being used for the branch
 
470
        args = (DEPRECATED_PARAMETER, message, ) + args
 
471
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
 
472
        self._set_inventory(self.read_working_inventory())
 
473
 
 
474
    def id2abspath(self, file_id):
 
475
        return self.abspath(self.id2path(file_id))
 
476
 
99
477
    def has_id(self, file_id):
100
478
        # files that have been deleted are excluded
101
479
        inv = self._inventory
102
480
        if not inv.has_id(file_id):
103
481
            return False
104
482
        path = inv.id2path(file_id)
105
 
        return os.path.exists(self.abspath(path))
 
483
        return bzrlib.osutils.lexists(self.abspath(path))
106
484
 
 
485
    def has_or_had_id(self, file_id):
 
486
        if file_id == self.inventory.root.file_id:
 
487
            return True
 
488
        return self.inventory.has_id(file_id)
107
489
 
108
490
    __contains__ = has_id
109
 
    
110
491
 
111
492
    def get_file_size(self, file_id):
112
 
        # is this still called?
113
 
        raise NotImplementedError()
114
 
 
115
 
 
 
493
        return os.path.getsize(self.id2abspath(file_id))
 
494
 
 
495
    @needs_read_lock
116
496
    def get_file_sha1(self, file_id):
117
497
        path = self._inventory.id2path(file_id)
118
498
        return self._hashcache.get_sha1(path)
119
499
 
 
500
    def is_executable(self, file_id):
 
501
        if not supports_executable():
 
502
            return self._inventory[file_id].executable
 
503
        else:
 
504
            path = self._inventory.id2path(file_id)
 
505
            mode = os.lstat(self.abspath(path)).st_mode
 
506
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
507
 
 
508
    @needs_write_lock
 
509
    def add(self, files, ids=None):
 
510
        """Make files versioned.
 
511
 
 
512
        Note that the command line normally calls smart_add instead,
 
513
        which can automatically recurse.
 
514
 
 
515
        This adds the files to the inventory, so that they will be
 
516
        recorded by the next commit.
 
517
 
 
518
        files
 
519
            List of paths to add, relative to the base of the tree.
 
520
 
 
521
        ids
 
522
            If set, use these instead of automatically generated ids.
 
523
            Must be the same length as the list of files, but may
 
524
            contain None for ids that are to be autogenerated.
 
525
 
 
526
        TODO: Perhaps have an option to add the ids even if the files do
 
527
              not (yet) exist.
 
528
 
 
529
        TODO: Perhaps callback with the ids and paths as they're added.
 
530
        """
 
531
        # TODO: Re-adding a file that is removed in the working copy
 
532
        # should probably put it back with the previous ID.
 
533
        if isinstance(files, basestring):
 
534
            assert(ids is None or isinstance(ids, basestring))
 
535
            files = [files]
 
536
            if ids is not None:
 
537
                ids = [ids]
 
538
 
 
539
        if ids is None:
 
540
            ids = [None] * len(files)
 
541
        else:
 
542
            assert(len(ids) == len(files))
 
543
 
 
544
        inv = self.read_working_inventory()
 
545
        for f,file_id in zip(files, ids):
 
546
            if self.is_control_filename(f):
 
547
                raise BzrError("cannot add control file %s" % quotefn(f))
 
548
 
 
549
            fp = splitpath(f)
 
550
 
 
551
            if len(fp) == 0:
 
552
                raise BzrError("cannot add top-level %r" % f)
 
553
 
 
554
            fullpath = normpath(self.abspath(f))
 
555
 
 
556
            try:
 
557
                kind = file_kind(fullpath)
 
558
            except OSError, e:
 
559
                if e.errno == errno.ENOENT:
 
560
                    raise NoSuchFile(fullpath)
 
561
                # maybe something better?
 
562
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
563
 
 
564
            if not InventoryEntry.versionable_kind(kind):
 
565
                raise BzrError('cannot add: not a versionable file ('
 
566
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
567
 
 
568
            if file_id is None:
 
569
                file_id = gen_file_id(f)
 
570
            inv.add_path(f, kind=kind, file_id=file_id)
 
571
 
 
572
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
573
        self._write_inventory(inv)
 
574
 
 
575
    @needs_write_lock
 
576
    def add_pending_merge(self, *revision_ids):
 
577
        # TODO: Perhaps should check at this point that the
 
578
        # history of the revision is actually present?
 
579
        p = self.pending_merges()
 
580
        updated = False
 
581
        for rev_id in revision_ids:
 
582
            if rev_id in p:
 
583
                continue
 
584
            p.append(rev_id)
 
585
            updated = True
 
586
        if updated:
 
587
            self.set_pending_merges(p)
 
588
 
 
589
    @needs_read_lock
 
590
    def pending_merges(self):
 
591
        """Return a list of pending merges.
 
592
 
 
593
        These are revisions that have been merged into the working
 
594
        directory but not yet committed.
 
595
        """
 
596
        try:
 
597
            merges_file = self._control_files.get_utf8('pending-merges')
 
598
        except OSError, e:
 
599
            if e.errno != errno.ENOENT:
 
600
                raise
 
601
            return []
 
602
        p = []
 
603
        for l in merges_file.readlines():
 
604
            p.append(l.rstrip('\n'))
 
605
        return p
 
606
 
 
607
    @needs_write_lock
 
608
    def set_pending_merges(self, rev_list):
 
609
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
 
610
 
 
611
    @needs_write_lock
 
612
    def set_merge_modified(self, modified_hashes):
 
613
        my_file = StringIO()
 
614
        my_file.write(MERGE_MODIFIED_HEADER_1 + '\n')
 
615
        writer = RioWriter(my_file)
 
616
        for file_id, hash in modified_hashes.iteritems():
 
617
            s = Stanza(file_id=file_id, hash=hash)
 
618
            writer.write_stanza(s)
 
619
        my_file.seek(0)
 
620
        self._control_files.put('merge-hashes', my_file)
 
621
 
 
622
    @needs_read_lock
 
623
    def merge_modified(self):
 
624
        try:
 
625
            hashfile = self._control_files.get('merge-hashes')
 
626
        except NoSuchFile:
 
627
            return {}
 
628
        merge_hashes = {}
 
629
        try:
 
630
            if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
 
631
                raise MergeModifiedFormatError()
 
632
        except StopIteration:
 
633
            raise MergeModifiedFormatError()
 
634
        for s in RioReader(hashfile):
 
635
            file_id = s.get("file_id")
 
636
            hash = s.get("hash")
 
637
            if hash == self.get_file_sha1(file_id):
 
638
                merge_hashes[file_id] = hash
 
639
        return merge_hashes
 
640
 
 
641
    def get_symlink_target(self, file_id):
 
642
        return os.readlink(self.id2abspath(file_id))
120
643
 
121
644
    def file_class(self, filename):
122
645
        if self.path2id(filename):
126
649
        else:
127
650
            return '?'
128
651
 
129
 
 
130
652
    def list_files(self):
131
653
        """Recursively list all files as (path, class, kind, id).
132
654
 
146
668
                ## TODO: If we find a subdirectory with its own .bzr
147
669
                ## directory, then that is a separate tree and we
148
670
                ## should exclude it.
149
 
                if bzrlib.BZRDIR == f:
 
671
 
 
672
                # the bzrdir for this tree
 
673
                if self.bzrdir.transport.base.endswith(f + '/'):
150
674
                    continue
151
675
 
152
676
                # path within tree
171
695
                                            "now of kind %r"
172
696
                                            % (fap, f_ie.kind, f_ie.file_id, fk))
173
697
 
174
 
                yield fp, c, fk, (f_ie and f_ie.file_id)
 
698
                # make a last minute entry
 
699
                if f_ie:
 
700
                    entry = f_ie
 
701
                else:
 
702
                    if fk == 'directory':
 
703
                        entry = TreeDirectory()
 
704
                    elif fk == 'file':
 
705
                        entry = TreeFile()
 
706
                    elif fk == 'symlink':
 
707
                        entry = TreeLink()
 
708
                    else:
 
709
                        entry = TreeEntry()
 
710
                
 
711
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
175
712
 
176
713
                if fk != 'directory':
177
714
                    continue
183
720
                for ff in descend(fp, f_ie.file_id, fap):
184
721
                    yield ff
185
722
 
186
 
        for f in descend('', inv.root.file_id, self.basedir):
 
723
        for f in descend(u'', inv.root.file_id, self.basedir):
187
724
            yield f
188
 
            
189
 
 
190
 
 
 
725
 
 
726
    @needs_write_lock
 
727
    def move(self, from_paths, to_name):
 
728
        """Rename files.
 
729
 
 
730
        to_name must exist in the inventory.
 
731
 
 
732
        If to_name exists and is a directory, the files are moved into
 
733
        it, keeping their old names.  
 
734
 
 
735
        Note that to_name is only the last component of the new name;
 
736
        this doesn't change the directory.
 
737
 
 
738
        This returns a list of (from_path, to_path) pairs for each
 
739
        entry that is moved.
 
740
        """
 
741
        result = []
 
742
        ## TODO: Option to move IDs only
 
743
        assert not isinstance(from_paths, basestring)
 
744
        inv = self.inventory
 
745
        to_abs = self.abspath(to_name)
 
746
        if not isdir(to_abs):
 
747
            raise BzrError("destination %r is not a directory" % to_abs)
 
748
        if not self.has_filename(to_name):
 
749
            raise BzrError("destination %r not in working directory" % to_abs)
 
750
        to_dir_id = inv.path2id(to_name)
 
751
        if to_dir_id == None and to_name != '':
 
752
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
753
        to_dir_ie = inv[to_dir_id]
 
754
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
755
            raise BzrError("destination %r is not a directory" % to_abs)
 
756
 
 
757
        to_idpath = inv.get_idpath(to_dir_id)
 
758
 
 
759
        for f in from_paths:
 
760
            if not self.has_filename(f):
 
761
                raise BzrError("%r does not exist in working tree" % f)
 
762
            f_id = inv.path2id(f)
 
763
            if f_id == None:
 
764
                raise BzrError("%r is not versioned" % f)
 
765
            name_tail = splitpath(f)[-1]
 
766
            dest_path = appendpath(to_name, name_tail)
 
767
            if self.has_filename(dest_path):
 
768
                raise BzrError("destination %r already exists" % dest_path)
 
769
            if f_id in to_idpath:
 
770
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
771
 
 
772
        # OK, so there's a race here, it's possible that someone will
 
773
        # create a file in this interval and then the rename might be
 
774
        # left half-done.  But we should have caught most problems.
 
775
        orig_inv = deepcopy(self.inventory)
 
776
        try:
 
777
            for f in from_paths:
 
778
                name_tail = splitpath(f)[-1]
 
779
                dest_path = appendpath(to_name, name_tail)
 
780
                result.append((f, dest_path))
 
781
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
782
                try:
 
783
                    rename(self.abspath(f), self.abspath(dest_path))
 
784
                except OSError, e:
 
785
                    raise BzrError("failed to rename %r to %r: %s" %
 
786
                                   (f, dest_path, e[1]),
 
787
                            ["rename rolled back"])
 
788
        except:
 
789
            # restore the inventory on error
 
790
            self._set_inventory(orig_inv)
 
791
            raise
 
792
        self._write_inventory(inv)
 
793
        return result
 
794
 
 
795
    @needs_write_lock
 
796
    def rename_one(self, from_rel, to_rel):
 
797
        """Rename one file.
 
798
 
 
799
        This can change the directory or the filename or both.
 
800
        """
 
801
        inv = self.inventory
 
802
        if not self.has_filename(from_rel):
 
803
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
804
        if self.has_filename(to_rel):
 
805
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
806
 
 
807
        file_id = inv.path2id(from_rel)
 
808
        if file_id == None:
 
809
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
810
 
 
811
        entry = inv[file_id]
 
812
        from_parent = entry.parent_id
 
813
        from_name = entry.name
 
814
        
 
815
        if inv.path2id(to_rel):
 
816
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
817
 
 
818
        to_dir, to_tail = os.path.split(to_rel)
 
819
        to_dir_id = inv.path2id(to_dir)
 
820
        if to_dir_id == None and to_dir != '':
 
821
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
822
 
 
823
        mutter("rename_one:")
 
824
        mutter("  file_id    {%s}" % file_id)
 
825
        mutter("  from_rel   %r" % from_rel)
 
826
        mutter("  to_rel     %r" % to_rel)
 
827
        mutter("  to_dir     %r" % to_dir)
 
828
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
829
 
 
830
        inv.rename(file_id, to_dir_id, to_tail)
 
831
 
 
832
        from_abs = self.abspath(from_rel)
 
833
        to_abs = self.abspath(to_rel)
 
834
        try:
 
835
            rename(from_abs, to_abs)
 
836
        except OSError, e:
 
837
            inv.rename(file_id, from_parent, from_name)
 
838
            raise BzrError("failed to rename %r to %r: %s"
 
839
                    % (from_abs, to_abs, e[1]),
 
840
                    ["rename rolled back"])
 
841
        self._write_inventory(inv)
 
842
 
 
843
    @needs_read_lock
191
844
    def unknowns(self):
 
845
        """Return all unknown files.
 
846
 
 
847
        These are files in the working directory that are not versioned or
 
848
        control files or ignored.
 
849
        
 
850
        >>> from bzrlib.bzrdir import ScratchDir
 
851
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
852
        >>> b = d.open_branch()
 
853
        >>> tree = d.open_workingtree()
 
854
        >>> map(str, tree.unknowns())
 
855
        ['foo']
 
856
        >>> tree.add('foo')
 
857
        >>> list(b.unknowns())
 
858
        []
 
859
        >>> tree.remove('foo')
 
860
        >>> list(b.unknowns())
 
861
        [u'foo']
 
862
        """
192
863
        for subp in self.extras():
193
864
            if not self.is_ignored(subp):
194
865
                yield subp
195
866
 
 
867
    def iter_conflicts(self):
 
868
        conflicted = set()
 
869
        for path in (s[0] for s in self.list_files()):
 
870
            stem = get_conflicted_stem(path)
 
871
            if stem is None:
 
872
                continue
 
873
            if stem not in conflicted:
 
874
                conflicted.add(stem)
 
875
                yield stem
 
876
 
 
877
    @needs_write_lock
 
878
    def pull(self, source, overwrite=False, stop_revision=None):
 
879
        source.lock_read()
 
880
        try:
 
881
            old_revision_history = self.branch.revision_history()
 
882
            basis_tree = self.basis_tree()
 
883
            count = self.branch.pull(source, overwrite, stop_revision)
 
884
            new_revision_history = self.branch.revision_history()
 
885
            if new_revision_history != old_revision_history:
 
886
                if len(old_revision_history):
 
887
                    other_revision = old_revision_history[-1]
 
888
                else:
 
889
                    other_revision = None
 
890
                repository = self.branch.repository
 
891
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
892
                try:
 
893
                    merge_inner(self.branch,
 
894
                                self.branch.basis_tree(),
 
895
                                basis_tree, 
 
896
                                this_tree=self, 
 
897
                                pb=pb)
 
898
                finally:
 
899
                    pb.finished()
 
900
                self.set_last_revision(self.branch.last_revision())
 
901
            return count
 
902
        finally:
 
903
            source.unlock()
196
904
 
197
905
    def extras(self):
198
906
        """Yield all unknown files in this WorkingTree.
205
913
        """
206
914
        ## TODO: Work from given directory downwards
207
915
        for path, dir_entry in self.inventory.directories():
208
 
            mutter("search for unknowns in %r" % path)
 
916
            mutter("search for unknowns in %r", path)
209
917
            dirabs = self.abspath(path)
210
918
            if not isdir(dirabs):
211
919
                # e.g. directory deleted
284
992
                    return pat
285
993
        else:
286
994
            return None
287
 
        
 
995
 
 
996
    def kind(self, file_id):
 
997
        return file_kind(self.id2abspath(file_id))
 
998
 
 
999
    @needs_read_lock
 
1000
    def last_revision(self):
 
1001
        """Return the last revision id of this working tree.
 
1002
 
 
1003
        In early branch formats this was == the branch last_revision,
 
1004
        but that cannot be relied upon - for working tree operations,
 
1005
        always use tree.last_revision().
 
1006
        """
 
1007
        return self.branch.last_revision()
 
1008
 
 
1009
    def lock_read(self):
 
1010
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1011
        self.branch.lock_read()
 
1012
        try:
 
1013
            return self._control_files.lock_read()
 
1014
        except:
 
1015
            self.branch.unlock()
 
1016
            raise
 
1017
 
 
1018
    def lock_write(self):
 
1019
        """See Branch.lock_write, and WorkingTree.unlock."""
 
1020
        self.branch.lock_write()
 
1021
        try:
 
1022
            return self._control_files.lock_write()
 
1023
        except:
 
1024
            self.branch.unlock()
 
1025
            raise
 
1026
 
 
1027
    def _basis_inventory_name(self, revision_id):
 
1028
        return 'basis-inventory.%s' % revision_id
 
1029
 
 
1030
    @needs_write_lock
 
1031
    def set_last_revision(self, new_revision, old_revision=None):
 
1032
        """Change the last revision in the working tree."""
 
1033
        self._remove_old_basis(old_revision)
 
1034
        if self._change_last_revision(new_revision):
 
1035
            self._cache_basis_inventory(new_revision)
 
1036
 
 
1037
    def _change_last_revision(self, new_revision):
 
1038
        """Template method part of set_last_revision to perform the change."""
 
1039
        if new_revision is None:
 
1040
            self.branch.set_revision_history([])
 
1041
            return False
 
1042
        # current format is locked in with the branch
 
1043
        revision_history = self.branch.revision_history()
 
1044
        try:
 
1045
            position = revision_history.index(new_revision)
 
1046
        except ValueError:
 
1047
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1048
        self.branch.set_revision_history(revision_history[:position + 1])
 
1049
        return True
 
1050
 
 
1051
    def _cache_basis_inventory(self, new_revision):
 
1052
        """Cache new_revision as the basis inventory."""
 
1053
        try:
 
1054
            xml = self.branch.repository.get_inventory_xml(new_revision)
 
1055
            path = self._basis_inventory_name(new_revision)
 
1056
            self._control_files.put_utf8(path, xml)
 
1057
        except WeaveRevisionNotPresent:
 
1058
            pass
 
1059
 
 
1060
    def _remove_old_basis(self, old_revision):
 
1061
        """Remove the old basis inventory 'old_revision'."""
 
1062
        if old_revision is not None:
 
1063
            try:
 
1064
                path = self._basis_inventory_name(old_revision)
 
1065
                path = self._control_files._escape(path)
 
1066
                self._control_files._transport.delete(path)
 
1067
            except NoSuchFile:
 
1068
                pass
 
1069
 
 
1070
    def read_basis_inventory(self, revision_id):
 
1071
        """Read the cached basis inventory."""
 
1072
        path = self._basis_inventory_name(revision_id)
 
1073
        return self._control_files.get_utf8(path).read()
 
1074
        
 
1075
    @needs_read_lock
 
1076
    def read_working_inventory(self):
 
1077
        """Read the working inventory."""
 
1078
        # ElementTree does its own conversion from UTF-8, so open in
 
1079
        # binary.
 
1080
        result = bzrlib.xml5.serializer_v5.read_inventory(
 
1081
            self._control_files.get('inventory'))
 
1082
        self._set_inventory(result)
 
1083
        return result
 
1084
 
 
1085
    @needs_write_lock
 
1086
    def remove(self, files, verbose=False):
 
1087
        """Remove nominated files from the working inventory..
 
1088
 
 
1089
        This does not remove their text.  This does not run on XXX on what? RBC
 
1090
 
 
1091
        TODO: Refuse to remove modified files unless --force is given?
 
1092
 
 
1093
        TODO: Do something useful with directories.
 
1094
 
 
1095
        TODO: Should this remove the text or not?  Tough call; not
 
1096
        removing may be useful and the user can just use use rm, and
 
1097
        is the opposite of add.  Removing it is consistent with most
 
1098
        other tools.  Maybe an option.
 
1099
        """
 
1100
        ## TODO: Normalize names
 
1101
        ## TODO: Remove nested loops; better scalability
 
1102
        if isinstance(files, basestring):
 
1103
            files = [files]
 
1104
 
 
1105
        inv = self.inventory
 
1106
 
 
1107
        # do this before any modifications
 
1108
        for f in files:
 
1109
            fid = inv.path2id(f)
 
1110
            if not fid:
 
1111
                # TODO: Perhaps make this just a warning, and continue?
 
1112
                # This tends to happen when 
 
1113
                raise NotVersionedError(path=f)
 
1114
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
 
1115
            if verbose:
 
1116
                # having remove it, it must be either ignored or unknown
 
1117
                if self.is_ignored(f):
 
1118
                    new_status = 'I'
 
1119
                else:
 
1120
                    new_status = '?'
 
1121
                show_status(new_status, inv[fid].kind, quotefn(f))
 
1122
            del inv[fid]
 
1123
 
 
1124
        self._write_inventory(inv)
 
1125
 
 
1126
    @needs_write_lock
 
1127
    def revert(self, filenames, old_tree=None, backups=True, 
 
1128
               pb=DummyProgress()):
 
1129
        from transform import revert
 
1130
        if old_tree is None:
 
1131
            old_tree = self.basis_tree()
 
1132
        revert(self, old_tree, filenames, backups, pb)
 
1133
        if not len(filenames):
 
1134
            self.set_pending_merges([])
 
1135
 
 
1136
    @needs_write_lock
 
1137
    def set_inventory(self, new_inventory_list):
 
1138
        from bzrlib.inventory import (Inventory,
 
1139
                                      InventoryDirectory,
 
1140
                                      InventoryEntry,
 
1141
                                      InventoryFile,
 
1142
                                      InventoryLink)
 
1143
        inv = Inventory(self.get_root_id())
 
1144
        for path, file_id, parent, kind in new_inventory_list:
 
1145
            name = os.path.basename(path)
 
1146
            if name == "":
 
1147
                continue
 
1148
            # fixme, there should be a factory function inv,add_?? 
 
1149
            if kind == 'directory':
 
1150
                inv.add(InventoryDirectory(file_id, name, parent))
 
1151
            elif kind == 'file':
 
1152
                inv.add(InventoryFile(file_id, name, parent))
 
1153
            elif kind == 'symlink':
 
1154
                inv.add(InventoryLink(file_id, name, parent))
 
1155
            else:
 
1156
                raise BzrError("unknown kind %r" % kind)
 
1157
        self._write_inventory(inv)
 
1158
 
 
1159
    @needs_write_lock
 
1160
    def set_root_id(self, file_id):
 
1161
        """Set the root id for this tree."""
 
1162
        inv = self.read_working_inventory()
 
1163
        orig_root_id = inv.root.file_id
 
1164
        del inv._byid[inv.root.file_id]
 
1165
        inv.root.file_id = file_id
 
1166
        inv._byid[inv.root.file_id] = inv.root
 
1167
        for fid in inv:
 
1168
            entry = inv[fid]
 
1169
            if entry.parent_id == orig_root_id:
 
1170
                entry.parent_id = inv.root.file_id
 
1171
        self._write_inventory(inv)
 
1172
 
 
1173
    def unlock(self):
 
1174
        """See Branch.unlock.
 
1175
        
 
1176
        WorkingTree locking just uses the Branch locking facilities.
 
1177
        This is current because all working trees have an embedded branch
 
1178
        within them. IF in the future, we were to make branch data shareable
 
1179
        between multiple working trees, i.e. via shared storage, then we 
 
1180
        would probably want to lock both the local tree, and the branch.
 
1181
        """
 
1182
        # FIXME: We want to write out the hashcache only when the last lock on
 
1183
        # this working copy is released.  Peeking at the lock count is a bit
 
1184
        # of a nasty hack; probably it's better to have a transaction object,
 
1185
        # which can do some finalization when it's either successfully or
 
1186
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1187
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1188
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1189
        
 
1190
        # TODO: split this per format so there is no ugly if block
 
1191
        if self._hashcache.needs_write and (
 
1192
            # dedicated lock files
 
1193
            self._control_files._lock_count==1 or 
 
1194
            # shared lock files
 
1195
            (self._control_files is self.branch.control_files and 
 
1196
             self._control_files._lock_count==3)):
 
1197
            self._hashcache.write()
 
1198
        # reverse order of locking.
 
1199
        result = self._control_files.unlock()
 
1200
        try:
 
1201
            self.branch.unlock()
 
1202
        finally:
 
1203
            return result
 
1204
 
 
1205
    @needs_write_lock
 
1206
    def update(self):
 
1207
        """Update a working tree along its branch.
 
1208
 
 
1209
        This will update the branch if its bound too, which means we have multiple trees involved:
 
1210
        The new basis tree of the master.
 
1211
        The old basis tree of the branch.
 
1212
        The old basis tree of the working tree.
 
1213
        The current working tree state.
 
1214
        pathologically all three may be different, and non ancestors of each other.
 
1215
        Conceptually we want to:
 
1216
        Preserve the wt.basis->wt.state changes
 
1217
        Transform the wt.basis to the new master basis.
 
1218
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
 
1219
        Restore the wt.basis->wt.state changes.
 
1220
 
 
1221
        There isn't a single operation at the moment to do that, so we:
 
1222
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
 
1223
        Do a 'normal' merge of the old branch basis if it is relevant.
 
1224
        """
 
1225
        old_tip = self.branch.update()
 
1226
        if old_tip is not None:
 
1227
            self.add_pending_merge(old_tip)
 
1228
        self.branch.lock_read()
 
1229
        try:
 
1230
            result = 0
 
1231
            if self.last_revision() != self.branch.last_revision():
 
1232
                # merge tree state up to new branch tip.
 
1233
                basis = self.basis_tree()
 
1234
                to_tree = self.branch.basis_tree()
 
1235
                result += merge_inner(self.branch,
 
1236
                                      to_tree,
 
1237
                                      basis,
 
1238
                                      this_tree=self)
 
1239
                self.set_last_revision(self.branch.last_revision())
 
1240
            if old_tip and old_tip != self.last_revision():
 
1241
                # our last revision was not the prior branch last reivison
 
1242
                # and we have converted that last revision to a pending merge.
 
1243
                # base is somewhere between the branch tip now
 
1244
                # and the now pending merge
 
1245
                from bzrlib.revision import common_ancestor
 
1246
                try:
 
1247
                    base_rev_id = common_ancestor(self.branch.last_revision(),
 
1248
                                                  old_tip,
 
1249
                                                  self.branch.repository)
 
1250
                except errors.NoCommonAncestor:
 
1251
                    base_rev_id = None
 
1252
                base_tree = self.branch.repository.revision_tree(base_rev_id)
 
1253
                other_tree = self.branch.repository.revision_tree(old_tip)
 
1254
                result += merge_inner(self.branch,
 
1255
                                      other_tree,
 
1256
                                      base_tree,
 
1257
                                      this_tree=self)
 
1258
            return result
 
1259
        finally:
 
1260
            self.branch.unlock()
 
1261
 
 
1262
    @needs_write_lock
 
1263
    def _write_inventory(self, inv):
 
1264
        """Write inventory as the current inventory."""
 
1265
        sio = StringIO()
 
1266
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1267
        sio.seek(0)
 
1268
        self._control_files.put('inventory', sio)
 
1269
        self._set_inventory(inv)
 
1270
        mutter('wrote working inventory')
 
1271
 
 
1272
 
 
1273
class WorkingTree3(WorkingTree):
 
1274
    """This is the Format 3 working tree.
 
1275
 
 
1276
    This differs from the base WorkingTree by:
 
1277
     - having its own file lock
 
1278
     - having its own last-revision property.
 
1279
    """
 
1280
 
 
1281
    @needs_read_lock
 
1282
    def last_revision(self):
 
1283
        """See WorkingTree.last_revision."""
 
1284
        try:
 
1285
            return self._control_files.get_utf8('last-revision').read()
 
1286
        except NoSuchFile:
 
1287
            return None
 
1288
 
 
1289
    def _change_last_revision(self, revision_id):
 
1290
        """See WorkingTree._change_last_revision."""
 
1291
        if revision_id is None or revision_id == NULL_REVISION:
 
1292
            try:
 
1293
                self._control_files._transport.delete('last-revision')
 
1294
            except errors.NoSuchFile:
 
1295
                pass
 
1296
            return False
 
1297
        else:
 
1298
            try:
 
1299
                self.branch.revision_history().index(revision_id)
 
1300
            except ValueError:
 
1301
                raise errors.NoSuchRevision(self.branch, revision_id)
 
1302
            self._control_files.put_utf8('last-revision', revision_id)
 
1303
            return True
 
1304
 
 
1305
 
 
1306
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
 
1307
def get_conflicted_stem(path):
 
1308
    for suffix in CONFLICT_SUFFIXES:
 
1309
        if path.endswith(suffix):
 
1310
            return path[:-len(suffix)]
 
1311
 
 
1312
@deprecated_function(zero_eight)
 
1313
def is_control_file(filename):
 
1314
    """See WorkingTree.is_control_filename(filename)."""
 
1315
    ## FIXME: better check
 
1316
    filename = normpath(filename)
 
1317
    while filename != '':
 
1318
        head, tail = os.path.split(filename)
 
1319
        ## mutter('check %r for control file' % ((head, tail),))
 
1320
        if tail == '.bzr':
 
1321
            return True
 
1322
        if filename == head:
 
1323
            break
 
1324
        filename = head
 
1325
    return False
 
1326
 
 
1327
 
 
1328
class WorkingTreeFormat(object):
 
1329
    """An encapsulation of the initialization and open routines for a format.
 
1330
 
 
1331
    Formats provide three things:
 
1332
     * An initialization routine,
 
1333
     * a format string,
 
1334
     * an open routine.
 
1335
 
 
1336
    Formats are placed in an dict by their format string for reference 
 
1337
    during workingtree opening. Its not required that these be instances, they
 
1338
    can be classes themselves with class methods - it simply depends on 
 
1339
    whether state is needed for a given format or not.
 
1340
 
 
1341
    Once a format is deprecated, just deprecate the initialize and open
 
1342
    methods on the format class. Do not deprecate the object, as the 
 
1343
    object will be created every time regardless.
 
1344
    """
 
1345
 
 
1346
    _default_format = None
 
1347
    """The default format used for new trees."""
 
1348
 
 
1349
    _formats = {}
 
1350
    """The known formats."""
 
1351
 
 
1352
    @classmethod
 
1353
    def find_format(klass, a_bzrdir):
 
1354
        """Return the format for the working tree object in a_bzrdir."""
 
1355
        try:
 
1356
            transport = a_bzrdir.get_workingtree_transport(None)
 
1357
            format_string = transport.get("format").read()
 
1358
            return klass._formats[format_string]
 
1359
        except NoSuchFile:
 
1360
            raise errors.NoWorkingTree(base=transport.base)
 
1361
        except KeyError:
 
1362
            raise errors.UnknownFormatError(format_string)
 
1363
 
 
1364
    @classmethod
 
1365
    def get_default_format(klass):
 
1366
        """Return the current default format."""
 
1367
        return klass._default_format
 
1368
 
 
1369
    def get_format_string(self):
 
1370
        """Return the ASCII format string that identifies this format."""
 
1371
        raise NotImplementedError(self.get_format_string)
 
1372
 
 
1373
    def is_supported(self):
 
1374
        """Is this format supported?
 
1375
 
 
1376
        Supported formats can be initialized and opened.
 
1377
        Unsupported formats may not support initialization or committing or 
 
1378
        some other features depending on the reason for not being supported.
 
1379
        """
 
1380
        return True
 
1381
 
 
1382
    @classmethod
 
1383
    def register_format(klass, format):
 
1384
        klass._formats[format.get_format_string()] = format
 
1385
 
 
1386
    @classmethod
 
1387
    def set_default_format(klass, format):
 
1388
        klass._default_format = format
 
1389
 
 
1390
    @classmethod
 
1391
    def unregister_format(klass, format):
 
1392
        assert klass._formats[format.get_format_string()] is format
 
1393
        del klass._formats[format.get_format_string()]
 
1394
 
 
1395
 
 
1396
 
 
1397
class WorkingTreeFormat2(WorkingTreeFormat):
 
1398
    """The second working tree format. 
 
1399
 
 
1400
    This format modified the hash cache from the format 1 hash cache.
 
1401
    """
 
1402
 
 
1403
    def initialize(self, a_bzrdir, revision_id=None):
 
1404
        """See WorkingTreeFormat.initialize()."""
 
1405
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1406
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1407
        branch = a_bzrdir.open_branch()
 
1408
        if revision_id is not None:
 
1409
            branch.lock_write()
 
1410
            try:
 
1411
                revision_history = branch.revision_history()
 
1412
                try:
 
1413
                    position = revision_history.index(revision_id)
 
1414
                except ValueError:
 
1415
                    raise errors.NoSuchRevision(branch, revision_id)
 
1416
                branch.set_revision_history(revision_history[:position + 1])
 
1417
            finally:
 
1418
                branch.unlock()
 
1419
        revision = branch.last_revision()
 
1420
        inv = Inventory() 
 
1421
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1422
                         branch,
 
1423
                         inv,
 
1424
                         _internal=True,
 
1425
                         _format=self,
 
1426
                         _bzrdir=a_bzrdir)
 
1427
        wt._write_inventory(inv)
 
1428
        wt.set_root_id(inv.root.file_id)
 
1429
        wt.set_last_revision(revision)
 
1430
        wt.set_pending_merges([])
 
1431
        build_tree(wt.basis_tree(), wt)
 
1432
        return wt
 
1433
 
 
1434
    def __init__(self):
 
1435
        super(WorkingTreeFormat2, self).__init__()
 
1436
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1437
 
 
1438
    def open(self, a_bzrdir, _found=False):
 
1439
        """Return the WorkingTree object for a_bzrdir
 
1440
 
 
1441
        _found is a private parameter, do not use it. It is used to indicate
 
1442
               if format probing has already been done.
 
1443
        """
 
1444
        if not _found:
 
1445
            # we are being called directly and must probe.
 
1446
            raise NotImplementedError
 
1447
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1448
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1449
        return WorkingTree(a_bzrdir.root_transport.base,
 
1450
                           _internal=True,
 
1451
                           _format=self,
 
1452
                           _bzrdir=a_bzrdir)
 
1453
 
 
1454
 
 
1455
class WorkingTreeFormat3(WorkingTreeFormat):
 
1456
    """The second working tree format updated to record a format marker.
 
1457
 
 
1458
    This format modified the hash cache from the format 1 hash cache.
 
1459
    """
 
1460
 
 
1461
    def get_format_string(self):
 
1462
        """See WorkingTreeFormat.get_format_string()."""
 
1463
        return "Bazaar-NG Working Tree format 3"
 
1464
 
 
1465
    def initialize(self, a_bzrdir, revision_id=None):
 
1466
        """See WorkingTreeFormat.initialize().
 
1467
        
 
1468
        revision_id allows creating a working tree at a differnet
 
1469
        revision than the branch is at.
 
1470
        """
 
1471
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1472
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1473
        transport = a_bzrdir.get_workingtree_transport(self)
 
1474
        control_files = LockableFiles(transport, 'lock', TransportLock)
 
1475
        control_files.put_utf8('format', self.get_format_string())
 
1476
        branch = a_bzrdir.open_branch()
 
1477
        if revision_id is None:
 
1478
            revision_id = branch.last_revision()
 
1479
        inv = Inventory() 
 
1480
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1481
                         branch,
 
1482
                         inv,
 
1483
                         _internal=True,
 
1484
                         _format=self,
 
1485
                         _bzrdir=a_bzrdir)
 
1486
        wt._write_inventory(inv)
 
1487
        wt.set_root_id(inv.root.file_id)
 
1488
        wt.set_last_revision(revision_id)
 
1489
        wt.set_pending_merges([])
 
1490
        build_tree(wt.basis_tree(), wt)
 
1491
        return wt
 
1492
 
 
1493
    def __init__(self):
 
1494
        super(WorkingTreeFormat3, self).__init__()
 
1495
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1496
 
 
1497
    def open(self, a_bzrdir, _found=False):
 
1498
        """Return the WorkingTree object for a_bzrdir
 
1499
 
 
1500
        _found is a private parameter, do not use it. It is used to indicate
 
1501
               if format probing has already been done.
 
1502
        """
 
1503
        if not _found:
 
1504
            # we are being called directly and must probe.
 
1505
            raise NotImplementedError
 
1506
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1507
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1508
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1509
                           _internal=True,
 
1510
                           _format=self,
 
1511
                           _bzrdir=a_bzrdir)
 
1512
 
 
1513
    def __str__(self):
 
1514
        return self.get_format_string()
 
1515
 
 
1516
 
 
1517
# formats which have no format string are not discoverable
 
1518
# and not independently creatable, so are not registered.
 
1519
__default_format = WorkingTreeFormat3()
 
1520
WorkingTreeFormat.register_format(__default_format)
 
1521
WorkingTreeFormat.set_default_format(__default_format)
 
1522
_legacy_formats = [WorkingTreeFormat2(),
 
1523
                   ]
 
1524
 
 
1525
 
 
1526
class WorkingTreeTestProviderAdapter(object):
 
1527
    """A tool to generate a suite testing multiple workingtree formats at once.
 
1528
 
 
1529
    This is done by copying the test once for each transport and injecting
 
1530
    the transport_server, transport_readonly_server, and workingtree_format
 
1531
    classes into each copy. Each copy is also given a new id() to make it
 
1532
    easy to identify.
 
1533
    """
 
1534
 
 
1535
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1536
        self._transport_server = transport_server
 
1537
        self._transport_readonly_server = transport_readonly_server
 
1538
        self._formats = formats
 
1539
    
 
1540
    def adapt(self, test):
 
1541
        from bzrlib.tests import TestSuite
 
1542
        result = TestSuite()
 
1543
        for workingtree_format, bzrdir_format in self._formats:
 
1544
            new_test = deepcopy(test)
 
1545
            new_test.transport_server = self._transport_server
 
1546
            new_test.transport_readonly_server = self._transport_readonly_server
 
1547
            new_test.bzrdir_format = bzrdir_format
 
1548
            new_test.workingtree_format = workingtree_format
 
1549
            def make_new_test_id():
 
1550
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1551
                return lambda: new_id
 
1552
            new_test.id = make_new_test_id()
 
1553
            result.addTest(new_test)
 
1554
        return result