~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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