~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

add a clean target

Show diffs side-by-side

added added

removed removed

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