~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Robert Collins
  • Date: 2005-10-30 00:00:09 UTC
  • mfrom: (1185.16.134)
  • Revision ID: robertc@robertcollins.net-20051030000009-9db99a338a0dfdac
MergeĀ fromĀ Martin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
42
42
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
43
# At the momenthey may alias the inventory and have old copies of it in memory.
44
44
 
45
 
from copy import deepcopy
46
45
import os
47
46
import stat
48
47
import fnmatch
49
48
 
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
 
                            getcwd,
67
 
                            pathjoin,
68
 
                            pumpfile,
69
 
                            splitpath,
70
 
                            rand_bytes,
71
 
                            abspath,
72
 
                            normpath,
73
 
                            realpath,
74
 
                            relpath,
75
 
                            rename)
76
 
from bzrlib.textui import show_status
 
49
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
77
50
import bzrlib.tree
 
51
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath, relpath
 
52
from bzrlib.errors import BzrCheckError, DivergedBranches, NotVersionedError
78
53
from bzrlib.trace import mutter
79
 
import bzrlib.xml5
80
 
 
81
 
 
82
 
def gen_file_id(name):
83
 
    """Return new file id.
84
 
 
85
 
    This should probably generate proper UUIDs, but for the moment we
86
 
    cope with just randomness because running uuidgen every time is
87
 
    slow."""
88
 
    import re
89
 
    from binascii import hexlify
90
 
    from time import time
91
 
 
92
 
    # get last component
93
 
    idx = name.rfind('/')
94
 
    if idx != -1:
95
 
        name = name[idx+1 : ]
96
 
    idx = name.rfind('\\')
97
 
    if idx != -1:
98
 
        name = name[idx+1 : ]
99
 
 
100
 
    # make it not a hidden file
101
 
    name = name.lstrip('.')
102
 
 
103
 
    # remove any wierd characters; we don't escape them but rather
104
 
    # just pull them out
105
 
    name = re.sub(r'[^\w.]', '', name)
106
 
 
107
 
    s = hexlify(rand_bytes(8))
108
 
    return '-'.join((name, compact_date(time()), s))
109
 
 
110
 
 
111
 
def gen_root_id():
112
 
    """Return a new tree-root file id."""
113
 
    return gen_file_id('TREE_ROOT')
114
54
 
115
55
 
116
56
class TreeEntry(object):
178
118
    not listed in the Inventory and vice versa.
179
119
    """
180
120
 
181
 
    def __init__(self, basedir=u'.', branch=None):
 
121
    def __init__(self, basedir, branch=None):
182
122
        """Construct a WorkingTree for basedir.
183
123
 
184
124
        If the branch is not supplied, it is opened automatically.
194
134
            branch = Branch.open(basedir)
195
135
        assert isinstance(branch, Branch), \
196
136
            "branch %r is not a Branch" % branch
 
137
        self._inventory = branch.inventory
 
138
        self.path2id = self._inventory.path2id
197
139
        self.branch = branch
198
 
        self.basedir = realpath(basedir)
 
140
        self.basedir = basedir
199
141
 
200
142
        # update the whole cache up front and write to disk if anything changed;
201
143
        # in the future we might want to do this more selectively
211
153
            mutter("write hc")
212
154
            hc.write()
213
155
 
214
 
        self._set_inventory(self.read_working_inventory())
215
 
 
216
 
    def _set_inventory(self, inv):
217
 
        self._inventory = inv
218
 
        self.path2id = self._inventory.path2id
219
 
 
220
 
    @staticmethod
221
 
    def open_containing(path=None):
222
 
        """Open an existing working tree which has its root about path.
223
 
        
224
 
        This probes for a working tree at path and searches upwards from there.
225
 
 
226
 
        Basically we keep looking up until we find the control directory or
227
 
        run into /.  If there isn't one, raises NotBranchError.
228
 
        TODO: give this a new exception.
229
 
        If there is one, it is returned, along with the unused portion of path.
230
 
        """
231
 
        if path is None:
232
 
            path = getcwd()
233
 
        else:
234
 
            # sanity check.
235
 
            if path.find('://') != -1:
236
 
                raise NotBranchError(path=path)
237
 
        path = abspath(path)
238
 
        tail = u''
239
 
        while True:
240
 
            try:
241
 
                return WorkingTree(path), tail
242
 
            except NotBranchError:
243
 
                pass
244
 
            if tail:
245
 
                tail = pathjoin(os.path.basename(path), tail)
246
 
            else:
247
 
                tail = os.path.basename(path)
248
 
            lastpath = path
249
 
            path = os.path.dirname(path)
250
 
            if lastpath == path:
251
 
                # reached the root, whatever that may be
252
 
                raise NotBranchError(path=path)
253
 
 
254
156
    def __iter__(self):
255
157
        """Iterate through file_ids for this tree.
256
158
 
262
164
            if bzrlib.osutils.lexists(self.abspath(path)):
263
165
                yield ie.file_id
264
166
 
 
167
 
265
168
    def __repr__(self):
266
169
        return "<%s of %s>" % (self.__class__.__name__,
267
170
                               getattr(self, 'basedir', None))
268
171
 
 
172
 
 
173
 
269
174
    def abspath(self, filename):
270
 
        return pathjoin(self.basedir, filename)
 
175
        return os.path.join(self.basedir, filename)
271
176
 
272
 
    def relpath(self, abs):
 
177
    def relpath(self, abspath):
273
178
        """Return the local path portion from a given absolute path."""
274
 
        return relpath(self.basedir, abs)
 
179
        return relpath(self.basedir, abspath)
275
180
 
276
181
    def has_filename(self, filename):
277
182
        return bzrlib.osutils.lexists(self.abspath(filename))
282
187
    def get_file_byname(self, filename):
283
188
        return file(self.abspath(filename), 'rb')
284
189
 
285
 
    def get_root_id(self):
286
 
        """Return the id of this trees root"""
287
 
        inv = self.read_working_inventory()
288
 
        return inv.root.file_id
289
 
        
290
190
    def _get_store_filename(self, file_id):
291
 
        ## XXX: badly named; this is not in the store at all
 
191
        ## XXX: badly named; this isn't in the store at all
292
192
        return self.abspath(self.id2path(file_id))
293
193
 
294
 
    @needs_write_lock
295
 
    def commit(self, *args, **kw):
296
 
        from bzrlib.commit import Commit
297
 
        Commit().commit(self.branch, *args, **kw)
298
 
        self._set_inventory(self.read_working_inventory())
299
194
 
300
195
    def id2abspath(self, file_id):
301
196
        return self.abspath(self.id2path(file_id))
302
197
 
 
198
                
303
199
    def has_id(self, file_id):
304
200
        # files that have been deleted are excluded
305
201
        inv = self._inventory
314
210
        return self.inventory.has_id(file_id)
315
211
 
316
212
    __contains__ = has_id
 
213
    
317
214
 
318
215
    def get_file_size(self, file_id):
319
216
        return os.path.getsize(self.id2abspath(file_id))
320
217
 
321
 
    @needs_read_lock
322
218
    def get_file_sha1(self, file_id):
323
219
        path = self._inventory.id2path(file_id)
324
220
        return self._hashcache.get_sha1(path)
325
221
 
 
222
 
326
223
    def is_executable(self, file_id):
327
224
        if os.name == "nt":
328
225
            return self._inventory[file_id].executable
331
228
            mode = os.lstat(self.abspath(path)).st_mode
332
229
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
333
230
 
334
 
    @needs_write_lock
335
 
    def add(self, files, ids=None):
336
 
        """Make files versioned.
337
 
 
338
 
        Note that the command line normally calls smart_add instead,
339
 
        which can automatically recurse.
340
 
 
341
 
        This adds the files to the inventory, so that they will be
342
 
        recorded by the next commit.
343
 
 
344
 
        files
345
 
            List of paths to add, relative to the base of the tree.
346
 
 
347
 
        ids
348
 
            If set, use these instead of automatically generated ids.
349
 
            Must be the same length as the list of files, but may
350
 
            contain None for ids that are to be autogenerated.
351
 
 
352
 
        TODO: Perhaps have an option to add the ids even if the files do
353
 
              not (yet) exist.
354
 
 
355
 
        TODO: Perhaps callback with the ids and paths as they're added.
356
 
        """
357
 
        # TODO: Re-adding a file that is removed in the working copy
358
 
        # should probably put it back with the previous ID.
359
 
        if isinstance(files, basestring):
360
 
            assert(ids is None or isinstance(ids, basestring))
361
 
            files = [files]
362
 
            if ids is not None:
363
 
                ids = [ids]
364
 
 
365
 
        if ids is None:
366
 
            ids = [None] * len(files)
367
 
        else:
368
 
            assert(len(ids) == len(files))
369
 
 
370
 
        inv = self.read_working_inventory()
371
 
        for f,file_id in zip(files, ids):
372
 
            if is_control_file(f):
373
 
                raise BzrError("cannot add control file %s" % quotefn(f))
374
 
 
375
 
            fp = splitpath(f)
376
 
 
377
 
            if len(fp) == 0:
378
 
                raise BzrError("cannot add top-level %r" % f)
379
 
 
380
 
            fullpath = normpath(self.abspath(f))
381
 
 
382
 
            try:
383
 
                kind = file_kind(fullpath)
384
 
            except OSError:
385
 
                # maybe something better?
386
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
387
 
 
388
 
            if not InventoryEntry.versionable_kind(kind):
389
 
                raise BzrError('cannot add: not a versionable file ('
390
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
391
 
 
392
 
            if file_id is None:
393
 
                file_id = gen_file_id(f)
394
 
            inv.add_path(f, kind=kind, file_id=file_id)
395
 
 
396
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
397
 
        self._write_inventory(inv)
398
 
 
399
 
    @needs_write_lock
400
 
    def add_pending_merge(self, *revision_ids):
401
 
        # TODO: Perhaps should check at this point that the
402
 
        # history of the revision is actually present?
403
 
        p = self.pending_merges()
404
 
        updated = False
405
 
        for rev_id in revision_ids:
406
 
            if rev_id in p:
407
 
                continue
408
 
            p.append(rev_id)
409
 
            updated = True
410
 
        if updated:
411
 
            self.set_pending_merges(p)
412
 
 
413
 
    def pending_merges(self):
414
 
        """Return a list of pending merges.
415
 
 
416
 
        These are revisions that have been merged into the working
417
 
        directory but not yet committed.
418
 
        """
419
 
        cfn = self.branch._rel_controlfilename('pending-merges')
420
 
        if not self.branch._transport.has(cfn):
421
 
            return []
422
 
        p = []
423
 
        for l in self.branch.controlfile('pending-merges', 'r').readlines():
424
 
            p.append(l.rstrip('\n'))
425
 
        return p
426
 
 
427
 
    @needs_write_lock
428
 
    def set_pending_merges(self, rev_list):
429
 
        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
430
 
 
431
231
    def get_symlink_target(self, file_id):
432
232
        return os.readlink(self.id2abspath(file_id))
433
233
 
509
309
                for ff in descend(fp, f_ie.file_id, fap):
510
310
                    yield ff
511
311
 
512
 
        for f in descend(u'', inv.root.file_id, self.basedir):
 
312
        for f in descend('', inv.root.file_id, self.basedir):
513
313
            yield f
514
 
 
515
 
    @needs_write_lock
516
 
    def move(self, from_paths, to_name):
517
 
        """Rename files.
518
 
 
519
 
        to_name must exist in the inventory.
520
 
 
521
 
        If to_name exists and is a directory, the files are moved into
522
 
        it, keeping their old names.  
523
 
 
524
 
        Note that to_name is only the last component of the new name;
525
 
        this doesn't change the directory.
526
 
 
527
 
        This returns a list of (from_path, to_path) pairs for each
528
 
        entry that is moved.
529
 
        """
530
 
        result = []
531
 
        ## TODO: Option to move IDs only
532
 
        assert not isinstance(from_paths, basestring)
533
 
        inv = self.inventory
534
 
        to_abs = self.abspath(to_name)
535
 
        if not isdir(to_abs):
536
 
            raise BzrError("destination %r is not a directory" % to_abs)
537
 
        if not self.has_filename(to_name):
538
 
            raise BzrError("destination %r not in working directory" % to_abs)
539
 
        to_dir_id = inv.path2id(to_name)
540
 
        if to_dir_id == None and to_name != '':
541
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
542
 
        to_dir_ie = inv[to_dir_id]
543
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
544
 
            raise BzrError("destination %r is not a directory" % to_abs)
545
 
 
546
 
        to_idpath = inv.get_idpath(to_dir_id)
547
 
 
548
 
        for f in from_paths:
549
 
            if not self.has_filename(f):
550
 
                raise BzrError("%r does not exist in working tree" % f)
551
 
            f_id = inv.path2id(f)
552
 
            if f_id == None:
553
 
                raise BzrError("%r is not versioned" % f)
554
 
            name_tail = splitpath(f)[-1]
555
 
            dest_path = appendpath(to_name, name_tail)
556
 
            if self.has_filename(dest_path):
557
 
                raise BzrError("destination %r already exists" % dest_path)
558
 
            if f_id in to_idpath:
559
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
560
 
 
561
 
        # OK, so there's a race here, it's possible that someone will
562
 
        # create a file in this interval and then the rename might be
563
 
        # left half-done.  But we should have caught most problems.
564
 
        orig_inv = deepcopy(self.inventory)
565
 
        try:
566
 
            for f in from_paths:
567
 
                name_tail = splitpath(f)[-1]
568
 
                dest_path = appendpath(to_name, name_tail)
569
 
                result.append((f, dest_path))
570
 
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
571
 
                try:
572
 
                    rename(self.abspath(f), self.abspath(dest_path))
573
 
                except OSError, e:
574
 
                    raise BzrError("failed to rename %r to %r: %s" %
575
 
                                   (f, dest_path, e[1]),
576
 
                            ["rename rolled back"])
577
 
        except:
578
 
            # restore the inventory on error
579
 
            self._set_inventory(orig_inv)
580
 
            raise
581
 
        self._write_inventory(inv)
582
 
        return result
583
 
 
584
 
    @needs_write_lock
585
 
    def rename_one(self, from_rel, to_rel):
586
 
        """Rename one file.
587
 
 
588
 
        This can change the directory or the filename or both.
589
 
        """
590
 
        inv = self.inventory
591
 
        if not self.has_filename(from_rel):
592
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
593
 
        if self.has_filename(to_rel):
594
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
595
 
 
596
 
        file_id = inv.path2id(from_rel)
597
 
        if file_id == None:
598
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
599
 
 
600
 
        entry = inv[file_id]
601
 
        from_parent = entry.parent_id
602
 
        from_name = entry.name
603
 
        
604
 
        if inv.path2id(to_rel):
605
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
606
 
 
607
 
        to_dir, to_tail = os.path.split(to_rel)
608
 
        to_dir_id = inv.path2id(to_dir)
609
 
        if to_dir_id == None and to_dir != '':
610
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
611
 
 
612
 
        mutter("rename_one:")
613
 
        mutter("  file_id    {%s}" % file_id)
614
 
        mutter("  from_rel   %r" % from_rel)
615
 
        mutter("  to_rel     %r" % to_rel)
616
 
        mutter("  to_dir     %r" % to_dir)
617
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
618
 
 
619
 
        inv.rename(file_id, to_dir_id, to_tail)
620
 
 
621
 
        from_abs = self.abspath(from_rel)
622
 
        to_abs = self.abspath(to_rel)
623
 
        try:
624
 
            rename(from_abs, to_abs)
625
 
        except OSError, e:
626
 
            inv.rename(file_id, from_parent, from_name)
627
 
            raise BzrError("failed to rename %r to %r: %s"
628
 
                    % (from_abs, to_abs, e[1]),
629
 
                    ["rename rolled back"])
630
 
        self._write_inventory(inv)
631
 
 
632
 
    @needs_read_lock
 
314
            
 
315
 
 
316
 
633
317
    def unknowns(self):
634
 
        """Return all unknown files.
635
 
 
636
 
        These are files in the working directory that are not versioned or
637
 
        control files or ignored.
638
 
        
639
 
        >>> from bzrlib.branch import ScratchBranch
640
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
641
 
        >>> tree = WorkingTree(b.base, b)
642
 
        >>> map(str, tree.unknowns())
643
 
        ['foo']
644
 
        >>> tree.add('foo')
645
 
        >>> list(b.unknowns())
646
 
        []
647
 
        >>> tree.remove('foo')
648
 
        >>> list(b.unknowns())
649
 
        [u'foo']
650
 
        """
651
318
        for subp in self.extras():
652
319
            if not self.is_ignored(subp):
653
320
                yield subp
668
335
        source.lock_read()
669
336
        try:
670
337
            old_revision_history = self.branch.revision_history()
671
 
            count = self.branch.pull(source, overwrite)
 
338
            self.branch.pull(source, overwrite)
672
339
            new_revision_history = self.branch.revision_history()
673
340
            if new_revision_history != old_revision_history:
674
341
                if len(old_revision_history):
678
345
                merge_inner(self.branch,
679
346
                            self.branch.basis_tree(), 
680
347
                            self.branch.revision_tree(other_revision))
681
 
            return count
682
348
        finally:
683
349
            source.unlock()
684
350
 
693
359
        """
694
360
        ## TODO: Work from given directory downwards
695
361
        for path, dir_entry in self.inventory.directories():
696
 
            mutter("search for unknowns in %r", path)
 
362
            mutter("search for unknowns in %r" % path)
697
363
            dirabs = self.abspath(path)
698
364
            if not isdir(dirabs):
699
365
                # e.g. directory deleted
784
450
        """See Branch.lock_write, and WorkingTree.unlock."""
785
451
        return self.branch.lock_write()
786
452
 
787
 
    def _basis_inventory_name(self, revision_id):
788
 
        return 'basis-inventory.%s' % revision_id
789
 
 
790
 
    def set_last_revision(self, new_revision, old_revision=None):
791
 
        if old_revision:
792
 
            try:
793
 
                path = self._basis_inventory_name(old_revision)
794
 
                path = self.branch._rel_controlfilename(path)
795
 
                self.branch._transport.delete(path)
796
 
            except:
797
 
                pass
798
 
        try:
799
 
            xml = self.branch.get_inventory_xml(new_revision)
800
 
            path = self._basis_inventory_name(new_revision)
801
 
            self.branch.put_controlfile(path, xml)
802
 
        except WeaveRevisionNotPresent:
803
 
            pass
804
 
 
805
 
    def read_basis_inventory(self, revision_id):
806
 
        """Read the cached basis inventory."""
807
 
        path = self._basis_inventory_name(revision_id)
808
 
        return self.branch.controlfile(path, 'r').read()
809
 
        
810
 
    @needs_read_lock
811
 
    def read_working_inventory(self):
812
 
        """Read the working inventory."""
813
 
        # ElementTree does its own conversion from UTF-8, so open in
814
 
        # binary.
815
 
        f = self.branch.controlfile('inventory', 'rb')
816
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
817
 
 
818
453
    @needs_write_lock
819
454
    def remove(self, files, verbose=False):
820
455
        """Remove nominated files from the working inventory..
844
479
                # TODO: Perhaps make this just a warning, and continue?
845
480
                # This tends to happen when 
846
481
                raise NotVersionedError(path=f)
847
 
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
 
482
            mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
848
483
            if verbose:
849
484
                # having remove it, it must be either ignored or unknown
850
485
                if self.is_ignored(f):
854
489
                show_status(new_status, inv[fid].kind, quotefn(f))
855
490
            del inv[fid]
856
491
 
857
 
        self._write_inventory(inv)
858
 
 
859
 
    @needs_write_lock
860
 
    def revert(self, filenames, old_tree=None, backups=True):
861
 
        from bzrlib.merge import merge_inner
862
 
        if old_tree is None:
863
 
            old_tree = self.branch.basis_tree()
864
 
        merge_inner(self.branch, old_tree,
865
 
                    self, ignore_zero=True,
866
 
                    backup_files=backups, 
867
 
                    interesting_files=filenames)
868
 
        if not len(filenames):
869
 
            self.set_pending_merges([])
870
 
 
871
 
    @needs_write_lock
872
 
    def set_inventory(self, new_inventory_list):
873
 
        from bzrlib.inventory import (Inventory,
874
 
                                      InventoryDirectory,
875
 
                                      InventoryEntry,
876
 
                                      InventoryFile,
877
 
                                      InventoryLink)
878
 
        inv = Inventory(self.get_root_id())
879
 
        for path, file_id, parent, kind in new_inventory_list:
880
 
            name = os.path.basename(path)
881
 
            if name == "":
882
 
                continue
883
 
            # fixme, there should be a factory function inv,add_?? 
884
 
            if kind == 'directory':
885
 
                inv.add(InventoryDirectory(file_id, name, parent))
886
 
            elif kind == 'file':
887
 
                inv.add(InventoryFile(file_id, name, parent))
888
 
            elif kind == 'symlink':
889
 
                inv.add(InventoryLink(file_id, name, parent))
890
 
            else:
891
 
                raise BzrError("unknown kind %r" % kind)
892
 
        self._write_inventory(inv)
893
 
 
894
 
    @needs_write_lock
895
 
    def set_root_id(self, file_id):
896
 
        """Set the root id for this tree."""
897
 
        inv = self.read_working_inventory()
898
 
        orig_root_id = inv.root.file_id
899
 
        del inv._byid[inv.root.file_id]
900
 
        inv.root.file_id = file_id
901
 
        inv._byid[inv.root.file_id] = inv.root
902
 
        for fid in inv:
903
 
            entry = inv[fid]
904
 
            if entry.parent_id in (None, orig_root_id):
905
 
                entry.parent_id = inv.root.file_id
906
 
        self._write_inventory(inv)
 
492
        self.branch._write_inventory(inv)
907
493
 
908
494
    def unlock(self):
909
495
        """See Branch.unlock.
914
500
        between multiple working trees, i.e. via shared storage, then we 
915
501
        would probably want to lock both the local tree, and the branch.
916
502
        """
917
 
        if self._hashcache.needs_write:
918
 
            self._hashcache.write()
919
503
        return self.branch.unlock()
920
504
 
921
 
    @needs_write_lock
922
 
    def _write_inventory(self, inv):
923
 
        """Write inventory as the current inventory."""
924
 
        from cStringIO import StringIO
925
 
        from bzrlib.atomicfile import AtomicFile
926
 
        sio = StringIO()
927
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
928
 
        sio.seek(0)
929
 
        f = AtomicFile(self.branch.controlfilename('inventory'))
930
 
        try:
931
 
            pumpfile(sio, f)
932
 
            f.commit()
933
 
        finally:
934
 
            f.close()
935
 
        self._set_inventory(inv)
936
 
        mutter('wrote working inventory')
937
 
            
938
505
 
939
506
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
940
507
def get_conflicted_stem(path):