/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

Move working tree initialisation out from  Branch.initialize, deprecated Branch.initialize to Branch.create.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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 WorkingTree(dir[, branch])
 
29
"""
 
30
 
 
31
 
 
32
# FIXME: I don't know if writing out the cache from the destructor is really a
 
33
# good idea, because destructors are considered poor taste in Python, and it's
 
34
# not predictable when it will be written out.
 
35
 
 
36
# TODO: Give the workingtree sole responsibility for the working inventory;
 
37
# remove the variable and references to it from the branch.  This may require
 
38
# updating the commit code so as to update the inventory within the working
 
39
# copy, and making sure there's only one WorkingTree for any directory on disk.
 
40
# At the momenthey may alias the inventory and have old copies of it in memory.
 
41
 
 
42
from copy import deepcopy
 
43
from cStringIO import StringIO
 
44
import errno
 
45
import fnmatch
 
46
import os
 
47
import stat
 
48
 
 
49
 
 
50
from bzrlib.atomicfile import AtomicFile
 
51
from bzrlib.branch import (Branch,
 
52
                           is_control_file,
 
53
                           needs_read_lock,
 
54
                           needs_write_lock,
 
55
                           quotefn)
 
56
from bzrlib.errors import (BzrCheckError,
 
57
                           BzrError,
 
58
                           DivergedBranches,
 
59
                           WeaveRevisionNotPresent,
 
60
                           NotBranchError,
 
61
                           NotVersionedError)
 
62
from bzrlib.inventory import InventoryEntry
 
63
from bzrlib.osutils import (appendpath,
 
64
                            compact_date,
 
65
                            file_kind,
 
66
                            isdir,
 
67
                            getcwd,
 
68
                            pathjoin,
 
69
                            pumpfile,
 
70
                            safe_unicode,
 
71
                            splitpath,
 
72
                            rand_bytes,
 
73
                            abspath,
 
74
                            normpath,
 
75
                            realpath,
 
76
                            relpath,
 
77
                            rename)
 
78
from bzrlib.symbol_versioning import *
 
79
from bzrlib.textui import show_status
 
80
import bzrlib.tree
 
81
from bzrlib.trace import mutter
 
82
import bzrlib.xml5
 
83
 
 
84
 
 
85
def gen_file_id(name):
 
86
    """Return new file id.
 
87
 
 
88
    This should probably generate proper UUIDs, but for the moment we
 
89
    cope with just randomness because running uuidgen every time is
 
90
    slow."""
 
91
    import re
 
92
    from binascii import hexlify
 
93
    from time import time
 
94
 
 
95
    # get last component
 
96
    idx = name.rfind('/')
 
97
    if idx != -1:
 
98
        name = name[idx+1 : ]
 
99
    idx = name.rfind('\\')
 
100
    if idx != -1:
 
101
        name = name[idx+1 : ]
 
102
 
 
103
    # make it not a hidden file
 
104
    name = name.lstrip('.')
 
105
 
 
106
    # remove any wierd characters; we don't escape them but rather
 
107
    # just pull them out
 
108
    name = re.sub(r'[^\w.]', '', name)
 
109
 
 
110
    s = hexlify(rand_bytes(8))
 
111
    return '-'.join((name, compact_date(time()), s))
 
112
 
 
113
 
 
114
def gen_root_id():
 
115
    """Return a new tree-root file id."""
 
116
    return gen_file_id('TREE_ROOT')
 
117
 
 
118
 
 
119
class TreeEntry(object):
 
120
    """An entry that implements the minium interface used by commands.
 
121
 
 
122
    This needs further inspection, it may be better to have 
 
123
    InventoryEntries without ids - though that seems wrong. For now,
 
124
    this is a parallel hierarchy to InventoryEntry, and needs to become
 
125
    one of several things: decorates to that hierarchy, children of, or
 
126
    parents of it.
 
127
    Another note is that these objects are currently only used when there is
 
128
    no InventoryEntry available - i.e. for unversioned objects.
 
129
    Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
 
130
    """
 
131
 
 
132
    def __eq__(self, other):
 
133
        # yes, this us ugly, TODO: best practice __eq__ style.
 
134
        return (isinstance(other, TreeEntry)
 
135
                and other.__class__ == self.__class__)
 
136
 
 
137
    def kind_character(self):
 
138
        return "???"
 
139
 
 
140
 
 
141
class TreeDirectory(TreeEntry):
 
142
    """See TreeEntry. This is a directory in a working tree."""
 
143
 
 
144
    def __eq__(self, other):
 
145
        return (isinstance(other, TreeDirectory)
 
146
                and other.__class__ == self.__class__)
 
147
 
 
148
    def kind_character(self):
 
149
        return "/"
 
150
 
 
151
 
 
152
class TreeFile(TreeEntry):
 
153
    """See TreeEntry. This is a regular file in a working tree."""
 
154
 
 
155
    def __eq__(self, other):
 
156
        return (isinstance(other, TreeFile)
 
157
                and other.__class__ == self.__class__)
 
158
 
 
159
    def kind_character(self):
 
160
        return ''
 
161
 
 
162
 
 
163
class TreeLink(TreeEntry):
 
164
    """See TreeEntry. This is a symlink in a working tree."""
 
165
 
 
166
    def __eq__(self, other):
 
167
        return (isinstance(other, TreeLink)
 
168
                and other.__class__ == self.__class__)
 
169
 
 
170
    def kind_character(self):
 
171
        return ''
 
172
 
 
173
 
 
174
class WorkingTree(bzrlib.tree.Tree):
 
175
    """Working copy tree.
 
176
 
 
177
    The inventory is held in the `Branch` working-inventory, and the
 
178
    files are in a directory on disk.
 
179
 
 
180
    It is possible for a `WorkingTree` to have a filename which is
 
181
    not listed in the Inventory and vice versa.
 
182
    """
 
183
 
 
184
    def __init__(self, basedir='.', branch=None, _inventory=None):
 
185
        """Construct a WorkingTree for basedir.
 
186
 
 
187
        If the branch is not supplied, it is opened automatically.
 
188
        If the branch is supplied, it must be the branch for this basedir.
 
189
        (branch.base is not cross checked, because for remote branches that
 
190
        would be meaningless).
 
191
        """
 
192
        from bzrlib.hashcache import HashCache
 
193
        from bzrlib.trace import note, mutter
 
194
        assert isinstance(basedir, basestring), \
 
195
            "base directory %r is not a string" % basedir
 
196
        basedir = safe_unicode(basedir)
 
197
        if branch is None:
 
198
            branch = Branch.open(basedir)
 
199
        assert isinstance(branch, Branch), \
 
200
            "branch %r is not a Branch" % branch
 
201
        self.branch = branch
 
202
        self.basedir = realpath(basedir)
 
203
 
 
204
        # update the whole cache up front and write to disk if anything changed;
 
205
        # in the future we might want to do this more selectively
 
206
        # two possible ways offer themselves : in self._unlock, write the cache
 
207
        # if needed, or, when the cache sees a change, append it to the hash
 
208
        # cache file, and have the parser take the most recent entry for a
 
209
        # given path only.
 
210
        hc = self._hashcache = HashCache(basedir)
 
211
        hc.read()
 
212
        hc.scan()
 
213
 
 
214
        if hc.needs_write:
 
215
            mutter("write hc")
 
216
            hc.write()
 
217
 
 
218
        if _inventory is None:
 
219
            self._set_inventory(self.read_working_inventory())
 
220
        else:
 
221
            self._set_inventory(_inventory)
 
222
 
 
223
    def _set_inventory(self, inv):
 
224
        self._inventory = inv
 
225
        self.path2id = self._inventory.path2id
 
226
 
 
227
    @staticmethod
 
228
    def open_containing(path=None):
 
229
        """Open an existing working tree which has its root about path.
 
230
        
 
231
        This probes for a working tree at path and searches upwards from there.
 
232
 
 
233
        Basically we keep looking up until we find the control directory or
 
234
        run into /.  If there isn't one, raises NotBranchError.
 
235
        TODO: give this a new exception.
 
236
        If there is one, it is returned, along with the unused portion of path.
 
237
        """
 
238
        if path is None:
 
239
            path = getcwd()
 
240
        else:
 
241
            # sanity check.
 
242
            if path.find('://') != -1:
 
243
                raise NotBranchError(path=path)
 
244
        path = abspath(path)
 
245
        orig_path = path[:]
 
246
        tail = u''
 
247
        while True:
 
248
            try:
 
249
                return WorkingTree(path), tail
 
250
            except NotBranchError:
 
251
                pass
 
252
            if tail:
 
253
                tail = pathjoin(os.path.basename(path), tail)
 
254
            else:
 
255
                tail = os.path.basename(path)
 
256
            lastpath = path
 
257
            path = os.path.dirname(path)
 
258
            if lastpath == path:
 
259
                # reached the root, whatever that may be
 
260
                raise NotBranchError(path=orig_path)
 
261
 
 
262
    def __iter__(self):
 
263
        """Iterate through file_ids for this tree.
 
264
 
 
265
        file_ids are in a WorkingTree if they are in the working inventory
 
266
        and the working file exists.
 
267
        """
 
268
        inv = self._inventory
 
269
        for path, ie in inv.iter_entries():
 
270
            if bzrlib.osutils.lexists(self.abspath(path)):
 
271
                yield ie.file_id
 
272
 
 
273
    def __repr__(self):
 
274
        return "<%s of %s>" % (self.__class__.__name__,
 
275
                               getattr(self, 'basedir', None))
 
276
 
 
277
    def abspath(self, filename):
 
278
        return pathjoin(self.basedir, filename)
 
279
 
 
280
    @staticmethod
 
281
    def create(branch, directory):
 
282
        """Create a workingtree for branch at directory.
 
283
 
 
284
        If existing_directory already exists it must have a .bzr directory.
 
285
        If it does not exist, it will be created.
 
286
 
 
287
        This returns a new WorkingTree object for the new checkout.
 
288
 
 
289
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
290
        should accept an optional revisionid to checkout [and reject this if
 
291
        checking out into the same dir as a pre-checkout-aware branch format.]
 
292
        """
 
293
        try:
 
294
            os.mkdir(directory)
 
295
        except OSError, e:
 
296
            if e.errno != errno.EEXIST:
 
297
                raise
 
298
        try:
 
299
            os.mkdir(pathjoin(directory, '.bzr'))
 
300
        except OSError, e:
 
301
            if e.errno != errno.EEXIST:
 
302
                raise
 
303
        inv = branch.revision_tree(branch.last_revision()).inventory
 
304
        wt = WorkingTree(directory, branch, inv)
 
305
        wt._write_inventory(inv)
 
306
        if branch.last_revision() is not None:
 
307
            wt.set_last_revision(branch.last_revision())
 
308
        wt.set_pending_merges([])
 
309
        wt.revert([])
 
310
        return wt
 
311
 
 
312
    @staticmethod
 
313
    def create_standalone(directory):
 
314
        """Create a checkout and a branch at directory.
 
315
 
 
316
        Directory must exist and be empty.
 
317
        """
 
318
        directory = safe_unicode(directory)
 
319
        b = Branch.create(directory)
 
320
        return WorkingTree.create(b, directory)
 
321
 
 
322
    def relpath(self, abs):
 
323
        """Return the local path portion from a given absolute path."""
 
324
        return relpath(self.basedir, abs)
 
325
 
 
326
    def has_filename(self, filename):
 
327
        return bzrlib.osutils.lexists(self.abspath(filename))
 
328
 
 
329
    def get_file(self, file_id):
 
330
        return self.get_file_byname(self.id2path(file_id))
 
331
 
 
332
    def get_file_byname(self, filename):
 
333
        return file(self.abspath(filename), 'rb')
 
334
 
 
335
    def get_root_id(self):
 
336
        """Return the id of this trees root"""
 
337
        inv = self.read_working_inventory()
 
338
        return inv.root.file_id
 
339
        
 
340
    def _get_store_filename(self, file_id):
 
341
        ## XXX: badly named; this is not in the store at all
 
342
        return self.abspath(self.id2path(file_id))
 
343
 
 
344
    @needs_write_lock
 
345
    def commit(self, *args, **kwargs):
 
346
        from bzrlib.commit import Commit
 
347
        # args for wt.commit start at message from the Commit.commit method,
 
348
        # but with branch a kwarg now, passing in args as is results in the
 
349
        #message being used for the branch
 
350
        args = (deprecated_nonce, ) + args
 
351
        Commit().commit(working_tree=self, *args, **kwargs)
 
352
        self._set_inventory(self.read_working_inventory())
 
353
 
 
354
    def id2abspath(self, file_id):
 
355
        return self.abspath(self.id2path(file_id))
 
356
 
 
357
    def has_id(self, file_id):
 
358
        # files that have been deleted are excluded
 
359
        inv = self._inventory
 
360
        if not inv.has_id(file_id):
 
361
            return False
 
362
        path = inv.id2path(file_id)
 
363
        return bzrlib.osutils.lexists(self.abspath(path))
 
364
 
 
365
    def has_or_had_id(self, file_id):
 
366
        if file_id == self.inventory.root.file_id:
 
367
            return True
 
368
        return self.inventory.has_id(file_id)
 
369
 
 
370
    __contains__ = has_id
 
371
 
 
372
    def get_file_size(self, file_id):
 
373
        return os.path.getsize(self.id2abspath(file_id))
 
374
 
 
375
    @needs_read_lock
 
376
    def get_file_sha1(self, file_id):
 
377
        path = self._inventory.id2path(file_id)
 
378
        return self._hashcache.get_sha1(path)
 
379
 
 
380
    def is_executable(self, file_id):
 
381
        if os.name == "nt":
 
382
            return self._inventory[file_id].executable
 
383
        else:
 
384
            path = self._inventory.id2path(file_id)
 
385
            mode = os.lstat(self.abspath(path)).st_mode
 
386
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
387
 
 
388
    @needs_write_lock
 
389
    def add(self, files, ids=None):
 
390
        """Make files versioned.
 
391
 
 
392
        Note that the command line normally calls smart_add instead,
 
393
        which can automatically recurse.
 
394
 
 
395
        This adds the files to the inventory, so that they will be
 
396
        recorded by the next commit.
 
397
 
 
398
        files
 
399
            List of paths to add, relative to the base of the tree.
 
400
 
 
401
        ids
 
402
            If set, use these instead of automatically generated ids.
 
403
            Must be the same length as the list of files, but may
 
404
            contain None for ids that are to be autogenerated.
 
405
 
 
406
        TODO: Perhaps have an option to add the ids even if the files do
 
407
              not (yet) exist.
 
408
 
 
409
        TODO: Perhaps callback with the ids and paths as they're added.
 
410
        """
 
411
        # TODO: Re-adding a file that is removed in the working copy
 
412
        # should probably put it back with the previous ID.
 
413
        if isinstance(files, basestring):
 
414
            assert(ids is None or isinstance(ids, basestring))
 
415
            files = [files]
 
416
            if ids is not None:
 
417
                ids = [ids]
 
418
 
 
419
        if ids is None:
 
420
            ids = [None] * len(files)
 
421
        else:
 
422
            assert(len(ids) == len(files))
 
423
 
 
424
        inv = self.read_working_inventory()
 
425
        for f,file_id in zip(files, ids):
 
426
            if is_control_file(f):
 
427
                raise BzrError("cannot add control file %s" % quotefn(f))
 
428
 
 
429
            fp = splitpath(f)
 
430
 
 
431
            if len(fp) == 0:
 
432
                raise BzrError("cannot add top-level %r" % f)
 
433
 
 
434
            fullpath = normpath(self.abspath(f))
 
435
 
 
436
            try:
 
437
                kind = file_kind(fullpath)
 
438
            except OSError:
 
439
                # maybe something better?
 
440
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
441
 
 
442
            if not InventoryEntry.versionable_kind(kind):
 
443
                raise BzrError('cannot add: not a versionable file ('
 
444
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
445
 
 
446
            if file_id is None:
 
447
                file_id = gen_file_id(f)
 
448
            inv.add_path(f, kind=kind, file_id=file_id)
 
449
 
 
450
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
451
        self._write_inventory(inv)
 
452
 
 
453
    @needs_write_lock
 
454
    def add_pending_merge(self, *revision_ids):
 
455
        # TODO: Perhaps should check at this point that the
 
456
        # history of the revision is actually present?
 
457
        p = self.pending_merges()
 
458
        updated = False
 
459
        for rev_id in revision_ids:
 
460
            if rev_id in p:
 
461
                continue
 
462
            p.append(rev_id)
 
463
            updated = True
 
464
        if updated:
 
465
            self.set_pending_merges(p)
 
466
 
 
467
    def pending_merges(self):
 
468
        """Return a list of pending merges.
 
469
 
 
470
        These are revisions that have been merged into the working
 
471
        directory but not yet committed.
 
472
        """
 
473
        try:
 
474
            merges_file = self._controlfile('pending-merges')
 
475
        except OSError, e:
 
476
            if e.errno != errno.ENOENT:
 
477
                raise
 
478
            return []
 
479
        p = []
 
480
        for l in merges_file.readlines():
 
481
            p.append(l.rstrip('\n'))
 
482
        return p
 
483
 
 
484
    def _abs_controlfilename(self, name):
 
485
        """return the path for the controlfile name in the workingtree."""
 
486
        return pathjoin(self.basedir, '.bzr', name)
 
487
 
 
488
    def _controlfile(self, name, encoding='utf-8'):
 
489
        """Get a control file for the checkout.
 
490
 
 
491
        FIXME RBC 20060123 when storage comes in this should be a lockable
 
492
        files group ?.
 
493
        """
 
494
        import codecs
 
495
        return codecs.open(self._abs_controlfilename(name), encoding=encoding)
 
496
 
 
497
    @needs_write_lock
 
498
    def set_pending_merges(self, rev_list):
 
499
        sio = StringIO()
 
500
        sio.write('\n'.join(rev_list).encode('utf-8'))
 
501
        sio.seek(0)
 
502
        f = AtomicFile(self._abs_controlfilename('pending-merges'))
 
503
        try:
 
504
            pumpfile(sio, f)
 
505
            f.commit()
 
506
        finally:
 
507
            f.close()
 
508
 
 
509
    def get_symlink_target(self, file_id):
 
510
        return os.readlink(self.id2abspath(file_id))
 
511
 
 
512
    def file_class(self, filename):
 
513
        if self.path2id(filename):
 
514
            return 'V'
 
515
        elif self.is_ignored(filename):
 
516
            return 'I'
 
517
        else:
 
518
            return '?'
 
519
 
 
520
 
 
521
    def list_files(self):
 
522
        """Recursively list all files as (path, class, kind, id).
 
523
 
 
524
        Lists, but does not descend into unversioned directories.
 
525
 
 
526
        This does not include files that have been deleted in this
 
527
        tree.
 
528
 
 
529
        Skips the control directory.
 
530
        """
 
531
        inv = self._inventory
 
532
 
 
533
        def descend(from_dir_relpath, from_dir_id, dp):
 
534
            ls = os.listdir(dp)
 
535
            ls.sort()
 
536
            for f in ls:
 
537
                ## TODO: If we find a subdirectory with its own .bzr
 
538
                ## directory, then that is a separate tree and we
 
539
                ## should exclude it.
 
540
                if bzrlib.BZRDIR == f:
 
541
                    continue
 
542
 
 
543
                # path within tree
 
544
                fp = appendpath(from_dir_relpath, f)
 
545
 
 
546
                # absolute path
 
547
                fap = appendpath(dp, f)
 
548
                
 
549
                f_ie = inv.get_child(from_dir_id, f)
 
550
                if f_ie:
 
551
                    c = 'V'
 
552
                elif self.is_ignored(fp):
 
553
                    c = 'I'
 
554
                else:
 
555
                    c = '?'
 
556
 
 
557
                fk = file_kind(fap)
 
558
 
 
559
                if f_ie:
 
560
                    if f_ie.kind != fk:
 
561
                        raise BzrCheckError("file %r entered as kind %r id %r, "
 
562
                                            "now of kind %r"
 
563
                                            % (fap, f_ie.kind, f_ie.file_id, fk))
 
564
 
 
565
                # make a last minute entry
 
566
                if f_ie:
 
567
                    entry = f_ie
 
568
                else:
 
569
                    if fk == 'directory':
 
570
                        entry = TreeDirectory()
 
571
                    elif fk == 'file':
 
572
                        entry = TreeFile()
 
573
                    elif fk == 'symlink':
 
574
                        entry = TreeLink()
 
575
                    else:
 
576
                        entry = TreeEntry()
 
577
                
 
578
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
 
579
 
 
580
                if fk != 'directory':
 
581
                    continue
 
582
 
 
583
                if c != 'V':
 
584
                    # don't descend unversioned directories
 
585
                    continue
 
586
                
 
587
                for ff in descend(fp, f_ie.file_id, fap):
 
588
                    yield ff
 
589
 
 
590
        for f in descend(u'', inv.root.file_id, self.basedir):
 
591
            yield f
 
592
 
 
593
    @needs_write_lock
 
594
    def move(self, from_paths, to_name):
 
595
        """Rename files.
 
596
 
 
597
        to_name must exist in the inventory.
 
598
 
 
599
        If to_name exists and is a directory, the files are moved into
 
600
        it, keeping their old names.  
 
601
 
 
602
        Note that to_name is only the last component of the new name;
 
603
        this doesn't change the directory.
 
604
 
 
605
        This returns a list of (from_path, to_path) pairs for each
 
606
        entry that is moved.
 
607
        """
 
608
        result = []
 
609
        ## TODO: Option to move IDs only
 
610
        assert not isinstance(from_paths, basestring)
 
611
        inv = self.inventory
 
612
        to_abs = self.abspath(to_name)
 
613
        if not isdir(to_abs):
 
614
            raise BzrError("destination %r is not a directory" % to_abs)
 
615
        if not self.has_filename(to_name):
 
616
            raise BzrError("destination %r not in working directory" % to_abs)
 
617
        to_dir_id = inv.path2id(to_name)
 
618
        if to_dir_id == None and to_name != '':
 
619
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
620
        to_dir_ie = inv[to_dir_id]
 
621
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
622
            raise BzrError("destination %r is not a directory" % to_abs)
 
623
 
 
624
        to_idpath = inv.get_idpath(to_dir_id)
 
625
 
 
626
        for f in from_paths:
 
627
            if not self.has_filename(f):
 
628
                raise BzrError("%r does not exist in working tree" % f)
 
629
            f_id = inv.path2id(f)
 
630
            if f_id == None:
 
631
                raise BzrError("%r is not versioned" % f)
 
632
            name_tail = splitpath(f)[-1]
 
633
            dest_path = appendpath(to_name, name_tail)
 
634
            if self.has_filename(dest_path):
 
635
                raise BzrError("destination %r already exists" % dest_path)
 
636
            if f_id in to_idpath:
 
637
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
638
 
 
639
        # OK, so there's a race here, it's possible that someone will
 
640
        # create a file in this interval and then the rename might be
 
641
        # left half-done.  But we should have caught most problems.
 
642
        orig_inv = deepcopy(self.inventory)
 
643
        try:
 
644
            for f in from_paths:
 
645
                name_tail = splitpath(f)[-1]
 
646
                dest_path = appendpath(to_name, name_tail)
 
647
                result.append((f, dest_path))
 
648
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
649
                try:
 
650
                    rename(self.abspath(f), self.abspath(dest_path))
 
651
                except OSError, e:
 
652
                    raise BzrError("failed to rename %r to %r: %s" %
 
653
                                   (f, dest_path, e[1]),
 
654
                            ["rename rolled back"])
 
655
        except:
 
656
            # restore the inventory on error
 
657
            self._set_inventory(orig_inv)
 
658
            raise
 
659
        self._write_inventory(inv)
 
660
        return result
 
661
 
 
662
    @needs_write_lock
 
663
    def rename_one(self, from_rel, to_rel):
 
664
        """Rename one file.
 
665
 
 
666
        This can change the directory or the filename or both.
 
667
        """
 
668
        inv = self.inventory
 
669
        if not self.has_filename(from_rel):
 
670
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
671
        if self.has_filename(to_rel):
 
672
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
673
 
 
674
        file_id = inv.path2id(from_rel)
 
675
        if file_id == None:
 
676
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
677
 
 
678
        entry = inv[file_id]
 
679
        from_parent = entry.parent_id
 
680
        from_name = entry.name
 
681
        
 
682
        if inv.path2id(to_rel):
 
683
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
684
 
 
685
        to_dir, to_tail = os.path.split(to_rel)
 
686
        to_dir_id = inv.path2id(to_dir)
 
687
        if to_dir_id == None and to_dir != '':
 
688
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
689
 
 
690
        mutter("rename_one:")
 
691
        mutter("  file_id    {%s}" % file_id)
 
692
        mutter("  from_rel   %r" % from_rel)
 
693
        mutter("  to_rel     %r" % to_rel)
 
694
        mutter("  to_dir     %r" % to_dir)
 
695
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
696
 
 
697
        inv.rename(file_id, to_dir_id, to_tail)
 
698
 
 
699
        from_abs = self.abspath(from_rel)
 
700
        to_abs = self.abspath(to_rel)
 
701
        try:
 
702
            rename(from_abs, to_abs)
 
703
        except OSError, e:
 
704
            inv.rename(file_id, from_parent, from_name)
 
705
            raise BzrError("failed to rename %r to %r: %s"
 
706
                    % (from_abs, to_abs, e[1]),
 
707
                    ["rename rolled back"])
 
708
        self._write_inventory(inv)
 
709
 
 
710
    @needs_read_lock
 
711
    def unknowns(self):
 
712
        """Return all unknown files.
 
713
 
 
714
        These are files in the working directory that are not versioned or
 
715
        control files or ignored.
 
716
        
 
717
        >>> from bzrlib.branch import ScratchBranch
 
718
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
719
        >>> tree = WorkingTree(b.base, b)
 
720
        >>> map(str, tree.unknowns())
 
721
        ['foo']
 
722
        >>> tree.add('foo')
 
723
        >>> list(b.unknowns())
 
724
        []
 
725
        >>> tree.remove('foo')
 
726
        >>> list(b.unknowns())
 
727
        [u'foo']
 
728
        """
 
729
        for subp in self.extras():
 
730
            if not self.is_ignored(subp):
 
731
                yield subp
 
732
 
 
733
    def iter_conflicts(self):
 
734
        conflicted = set()
 
735
        for path in (s[0] for s in self.list_files()):
 
736
            stem = get_conflicted_stem(path)
 
737
            if stem is None:
 
738
                continue
 
739
            if stem not in conflicted:
 
740
                conflicted.add(stem)
 
741
                yield stem
 
742
 
 
743
    @needs_write_lock
 
744
    def pull(self, source, overwrite=False):
 
745
        from bzrlib.merge import merge_inner
 
746
        source.lock_read()
 
747
        try:
 
748
            old_revision_history = self.branch.revision_history()
 
749
            count = self.branch.pull(source, overwrite)
 
750
            new_revision_history = self.branch.revision_history()
 
751
            if new_revision_history != old_revision_history:
 
752
                if len(old_revision_history):
 
753
                    other_revision = old_revision_history[-1]
 
754
                else:
 
755
                    other_revision = None
 
756
                merge_inner(self.branch,
 
757
                            self.branch.basis_tree(), 
 
758
                            self.branch.revision_tree(other_revision),
 
759
                            this_tree=self)
 
760
                self.set_last_revision(self.branch.last_revision())
 
761
            return count
 
762
        finally:
 
763
            source.unlock()
 
764
 
 
765
    def extras(self):
 
766
        """Yield all unknown files in this WorkingTree.
 
767
 
 
768
        If there are any unknown directories then only the directory is
 
769
        returned, not all its children.  But if there are unknown files
 
770
        under a versioned subdirectory, they are returned.
 
771
 
 
772
        Currently returned depth-first, sorted by name within directories.
 
773
        """
 
774
        ## TODO: Work from given directory downwards
 
775
        for path, dir_entry in self.inventory.directories():
 
776
            mutter("search for unknowns in %r", path)
 
777
            dirabs = self.abspath(path)
 
778
            if not isdir(dirabs):
 
779
                # e.g. directory deleted
 
780
                continue
 
781
 
 
782
            fl = []
 
783
            for subf in os.listdir(dirabs):
 
784
                if (subf != '.bzr'
 
785
                    and (subf not in dir_entry.children)):
 
786
                    fl.append(subf)
 
787
            
 
788
            fl.sort()
 
789
            for subf in fl:
 
790
                subp = appendpath(path, subf)
 
791
                yield subp
 
792
 
 
793
 
 
794
    def ignored_files(self):
 
795
        """Yield list of PATH, IGNORE_PATTERN"""
 
796
        for subp in self.extras():
 
797
            pat = self.is_ignored(subp)
 
798
            if pat != None:
 
799
                yield subp, pat
 
800
 
 
801
 
 
802
    def get_ignore_list(self):
 
803
        """Return list of ignore patterns.
 
804
 
 
805
        Cached in the Tree object after the first call.
 
806
        """
 
807
        if hasattr(self, '_ignorelist'):
 
808
            return self._ignorelist
 
809
 
 
810
        l = bzrlib.DEFAULT_IGNORE[:]
 
811
        if self.has_filename(bzrlib.IGNORE_FILENAME):
 
812
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
 
813
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
 
814
        self._ignorelist = l
 
815
        return l
 
816
 
 
817
 
 
818
    def is_ignored(self, filename):
 
819
        r"""Check whether the filename matches an ignore pattern.
 
820
 
 
821
        Patterns containing '/' or '\' need to match the whole path;
 
822
        others match against only the last component.
 
823
 
 
824
        If the file is ignored, returns the pattern which caused it to
 
825
        be ignored, otherwise None.  So this can simply be used as a
 
826
        boolean if desired."""
 
827
 
 
828
        # TODO: Use '**' to match directories, and other extended
 
829
        # globbing stuff from cvs/rsync.
 
830
 
 
831
        # XXX: fnmatch is actually not quite what we want: it's only
 
832
        # approximately the same as real Unix fnmatch, and doesn't
 
833
        # treat dotfiles correctly and allows * to match /.
 
834
        # Eventually it should be replaced with something more
 
835
        # accurate.
 
836
        
 
837
        for pat in self.get_ignore_list():
 
838
            if '/' in pat or '\\' in pat:
 
839
                
 
840
                # as a special case, you can put ./ at the start of a
 
841
                # pattern; this is good to match in the top-level
 
842
                # only;
 
843
                
 
844
                if (pat[:2] == './') or (pat[:2] == '.\\'):
 
845
                    newpat = pat[2:]
 
846
                else:
 
847
                    newpat = pat
 
848
                if fnmatch.fnmatchcase(filename, newpat):
 
849
                    return pat
 
850
            else:
 
851
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
 
852
                    return pat
 
853
        else:
 
854
            return None
 
855
 
 
856
    def kind(self, file_id):
 
857
        return file_kind(self.id2abspath(file_id))
 
858
 
 
859
    def lock_read(self):
 
860
        """See Branch.lock_read, and WorkingTree.unlock."""
 
861
        return self.branch.lock_read()
 
862
 
 
863
    def lock_write(self):
 
864
        """See Branch.lock_write, and WorkingTree.unlock."""
 
865
        return self.branch.lock_write()
 
866
 
 
867
    def _basis_inventory_name(self, revision_id):
 
868
        return 'basis-inventory.%s' % revision_id
 
869
 
 
870
    def set_last_revision(self, new_revision, old_revision=None):
 
871
        if old_revision:
 
872
            try:
 
873
                path = self._basis_inventory_name(old_revision)
 
874
                path = self.branch._rel_controlfilename(path)
 
875
                self.branch._transport.delete(path)
 
876
            except:
 
877
                pass
 
878
        try:
 
879
            xml = self.branch.get_inventory_xml(new_revision)
 
880
            path = self._basis_inventory_name(new_revision)
 
881
            self.branch.put_controlfile(path, xml)
 
882
        except WeaveRevisionNotPresent:
 
883
            pass
 
884
 
 
885
    def read_basis_inventory(self, revision_id):
 
886
        """Read the cached basis inventory."""
 
887
        path = self._basis_inventory_name(revision_id)
 
888
        return self.branch.controlfile(path, 'r').read()
 
889
        
 
890
    @needs_read_lock
 
891
    def read_working_inventory(self):
 
892
        """Read the working inventory."""
 
893
        # ElementTree does its own conversion from UTF-8, so open in
 
894
        # binary.
 
895
        return bzrlib.xml5.serializer_v5.read_inventory(
 
896
            self._controlfile('inventory', encoding=None))
 
897
 
 
898
    @needs_write_lock
 
899
    def remove(self, files, verbose=False):
 
900
        """Remove nominated files from the working inventory..
 
901
 
 
902
        This does not remove their text.  This does not run on XXX on what? RBC
 
903
 
 
904
        TODO: Refuse to remove modified files unless --force is given?
 
905
 
 
906
        TODO: Do something useful with directories.
 
907
 
 
908
        TODO: Should this remove the text or not?  Tough call; not
 
909
        removing may be useful and the user can just use use rm, and
 
910
        is the opposite of add.  Removing it is consistent with most
 
911
        other tools.  Maybe an option.
 
912
        """
 
913
        ## TODO: Normalize names
 
914
        ## TODO: Remove nested loops; better scalability
 
915
        if isinstance(files, basestring):
 
916
            files = [files]
 
917
 
 
918
        inv = self.inventory
 
919
 
 
920
        # do this before any modifications
 
921
        for f in files:
 
922
            fid = inv.path2id(f)
 
923
            if not fid:
 
924
                # TODO: Perhaps make this just a warning, and continue?
 
925
                # This tends to happen when 
 
926
                raise NotVersionedError(path=f)
 
927
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
 
928
            if verbose:
 
929
                # having remove it, it must be either ignored or unknown
 
930
                if self.is_ignored(f):
 
931
                    new_status = 'I'
 
932
                else:
 
933
                    new_status = '?'
 
934
                show_status(new_status, inv[fid].kind, quotefn(f))
 
935
            del inv[fid]
 
936
 
 
937
        self._write_inventory(inv)
 
938
 
 
939
    @needs_write_lock
 
940
    def revert(self, filenames, old_tree=None, backups=True):
 
941
        from bzrlib.merge import merge_inner
 
942
        if old_tree is None:
 
943
            old_tree = self.branch.basis_tree()
 
944
        merge_inner(self.branch, old_tree,
 
945
                    self, ignore_zero=True,
 
946
                    backup_files=backups, 
 
947
                    interesting_files=filenames,
 
948
                    this_tree=self)
 
949
        if not len(filenames):
 
950
            self.set_pending_merges([])
 
951
 
 
952
    @needs_write_lock
 
953
    def set_inventory(self, new_inventory_list):
 
954
        from bzrlib.inventory import (Inventory,
 
955
                                      InventoryDirectory,
 
956
                                      InventoryEntry,
 
957
                                      InventoryFile,
 
958
                                      InventoryLink)
 
959
        inv = Inventory(self.get_root_id())
 
960
        for path, file_id, parent, kind in new_inventory_list:
 
961
            name = os.path.basename(path)
 
962
            if name == "":
 
963
                continue
 
964
            # fixme, there should be a factory function inv,add_?? 
 
965
            if kind == 'directory':
 
966
                inv.add(InventoryDirectory(file_id, name, parent))
 
967
            elif kind == 'file':
 
968
                inv.add(InventoryFile(file_id, name, parent))
 
969
            elif kind == 'symlink':
 
970
                inv.add(InventoryLink(file_id, name, parent))
 
971
            else:
 
972
                raise BzrError("unknown kind %r" % kind)
 
973
        self._write_inventory(inv)
 
974
 
 
975
    @needs_write_lock
 
976
    def set_root_id(self, file_id):
 
977
        """Set the root id for this tree."""
 
978
        inv = self.read_working_inventory()
 
979
        orig_root_id = inv.root.file_id
 
980
        del inv._byid[inv.root.file_id]
 
981
        inv.root.file_id = file_id
 
982
        inv._byid[inv.root.file_id] = inv.root
 
983
        for fid in inv:
 
984
            entry = inv[fid]
 
985
            if entry.parent_id in (None, orig_root_id):
 
986
                entry.parent_id = inv.root.file_id
 
987
        self._write_inventory(inv)
 
988
 
 
989
    def unlock(self):
 
990
        """See Branch.unlock.
 
991
        
 
992
        WorkingTree locking just uses the Branch locking facilities.
 
993
        This is current because all working trees have an embedded branch
 
994
        within them. IF in the future, we were to make branch data shareable
 
995
        between multiple working trees, i.e. via shared storage, then we 
 
996
        would probably want to lock both the local tree, and the branch.
 
997
        """
 
998
        if self._hashcache.needs_write and self.branch._lock_count==1:
 
999
            self._hashcache.write()
 
1000
        return self.branch.unlock()
 
1001
 
 
1002
    @needs_write_lock
 
1003
    def _write_inventory(self, inv):
 
1004
        """Write inventory as the current inventory."""
 
1005
        sio = StringIO()
 
1006
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
1007
        sio.seek(0)
 
1008
        f = AtomicFile(self._abs_controlfilename('inventory'))
 
1009
        try:
 
1010
            pumpfile(sio, f)
 
1011
            f.commit()
 
1012
        finally:
 
1013
            f.close()
 
1014
        self._set_inventory(inv)
 
1015
        mutter('wrote working inventory')
 
1016
            
 
1017
 
 
1018
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
 
1019
def get_conflicted_stem(path):
 
1020
    for suffix in CONFLICT_SUFFIXES:
 
1021
        if path.endswith(suffix):
 
1022
            return path[:-len(suffix)]