/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: John Arbash Meinel
  • Date: 2006-04-27 01:47:39 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: john@arbash-meinel.com-20060427014739-07fac5ec153f15a2
A few places that were comparing a working trees base to a branch's base, where Branch.base is now a URL

Show diffs side-by-side

added added

removed removed

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