~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Aaron Bentley
  • Date: 2005-10-01 05:57:36 UTC
  • mfrom: (1393.1.23)
  • mto: (1185.12.13)
  • mto: This revision was merged to the branch mainline in revision 1419.
  • Revision ID: aaron.bentley@utoronto.ca-20051001055736-a1bb0acdce40852a
Merge from mpool

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import os
20
20
import errno
21
21
from warnings import warn
 
22
from cStringIO import StringIO
22
23
 
23
24
 
24
25
import bzrlib
29
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
31
                           NoSuchRevision, HistoryMissing, NotBranchError,
31
32
                           DivergedBranches, LockError, UnlistableStore,
32
 
                           UnlistableBranch)
 
33
                           UnlistableBranch, NoSuchFile)
33
34
from bzrlib.textui import show_status
34
35
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
35
36
from bzrlib.delta import compare_trees
36
37
from bzrlib.tree import EmptyTree, RevisionTree
37
38
from bzrlib.inventory import Inventory
38
 
from bzrlib.weavestore import WeaveStore
39
 
from bzrlib.store import copy_all, ImmutableStore
 
39
from bzrlib.store import copy_all
 
40
from bzrlib.store.compressed_text import CompressedTextStore
 
41
from bzrlib.store.text import TextStore
 
42
from bzrlib.store.weave import WeaveStore
 
43
from bzrlib.transport import Transport, get_transport
40
44
import bzrlib.xml5
41
45
import bzrlib.ui
42
46
 
55
59
    # XXX: leave this here for about one release, then remove it
56
60
    raise NotImplementedError('find_branch() is not supported anymore, '
57
61
                              'please use one of the new branch constructors')
58
 
 
59
62
def _relpath(base, path):
60
63
    """Return path relative to base, or raise exception.
61
64
 
83
86
    return os.sep.join(s)
84
87
        
85
88
 
86
 
def find_branch_root(f=None):
87
 
    """Find the branch root enclosing f, or pwd.
88
 
 
89
 
    f may be a filename or a URL.
90
 
 
91
 
    It is not necessary that f exists.
 
89
def find_branch_root(t):
 
90
    """Find the branch root enclosing the transport's base.
 
91
 
 
92
    t is a Transport object.
 
93
 
 
94
    It is not necessary that the base of t exists.
92
95
 
93
96
    Basically we keep looking up until we find the control directory or
94
97
    run into the root.  If there isn't one, raises NotBranchError.
95
98
    """
96
 
    if f == None:
97
 
        f = os.getcwd()
98
 
    elif hasattr(os.path, 'realpath'):
99
 
        f = os.path.realpath(f)
100
 
    else:
101
 
        f = os.path.abspath(f)
102
 
    if not os.path.exists(f):
103
 
        raise BzrError('%r does not exist' % f)
104
 
        
105
 
 
106
 
    orig_f = f
107
 
 
 
99
    orig_base = t.base
108
100
    while True:
109
 
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
110
 
            return f
111
 
        head, tail = os.path.split(f)
112
 
        if head == f:
 
101
        if t.has(bzrlib.BZRDIR):
 
102
            return t
 
103
        new_t = t.clone('..')
 
104
        if new_t.base == t.base:
113
105
            # reached the root, whatever that may be
114
 
            raise NotBranchError('%s is not in a branch' % orig_f)
115
 
        f = head
116
 
 
117
 
 
 
106
            raise NotBranchError('%s is not in a branch' % orig_base)
 
107
        t = new_t
118
108
 
119
109
 
120
110
######################################################################
136
126
        """Open a branch which may be of an old format.
137
127
        
138
128
        Only local branches are supported."""
139
 
        return LocalBranch(base, find_root=False, relax_version_check=True)
 
129
        return _Branch(get_transport(base), relax_version_check=True)
140
130
        
141
131
    @staticmethod
142
132
    def open(base):
143
133
        """Open an existing branch, rooted at 'base' (url)"""
144
 
        if base and (base.startswith('http://') or base.startswith('https://')):
145
 
            from bzrlib.remotebranch import RemoteBranch
146
 
            return RemoteBranch(base, find_root=False)
147
 
        else:
148
 
            return LocalBranch(base, find_root=False)
 
134
        t = get_transport(base)
 
135
        return _Branch(t)
149
136
 
150
137
    @staticmethod
151
138
    def open_containing(url):
153
140
        
154
141
        This probes for a branch at url, and searches upwards from there.
155
142
        """
156
 
        if url and (url.startswith('http://') or url.startswith('https://')):
157
 
            from bzrlib.remotebranch import RemoteBranch
158
 
            return RemoteBranch(url)
159
 
        else:
160
 
            return LocalBranch(url)
 
143
        t = get_transport(url)
 
144
        t = find_branch_root(t)
 
145
        return _Branch(t)
161
146
 
162
147
    @staticmethod
163
148
    def initialize(base):
164
149
        """Create a new branch, rooted at 'base' (url)"""
165
 
        if base and (base.startswith('http://') or base.startswith('https://')):
166
 
            from bzrlib.remotebranch import RemoteBranch
167
 
            return RemoteBranch(base, init=True)
168
 
        else:
169
 
            return LocalBranch(base, init=True)
 
150
        t = get_transport(base)
 
151
        return _Branch(t, init=True)
170
152
 
171
153
    def setup_caching(self, cache_root):
172
154
        """Subclasses that care about caching should override this, and set
174
156
        """
175
157
 
176
158
 
177
 
class LocalBranch(Branch):
 
159
class _Branch(Branch):
178
160
    """A branch stored in the actual filesystem.
179
161
 
180
162
    Note that it's "local" in the context of the filesystem; it doesn't
225
207
        except UnlistableStore:
226
208
            raise UnlistableBranch(from_store)
227
209
 
228
 
    def __init__(self, base, init=False, find_root=True,
 
210
    def __init__(self, transport, init=False,
229
211
                 relax_version_check=False):
230
212
        """Create new branch object at a particular location.
231
213
 
232
 
        base -- Base directory for the branch. May be a file:// url.
 
214
        transport -- A Transport object, defining how to access files.
 
215
                (If a string, transport.transport() will be used to
 
216
                create a Transport object)
233
217
        
234
218
        init -- If True, create new control files in a previously
235
219
             unversioned directory.  If False, the branch must already
236
220
             be versioned.
237
221
 
238
 
        find_root -- If true and init is false, find the root of the
239
 
             existing branch containing base.
240
 
 
241
222
        relax_version_check -- If true, the usual check for the branch
242
223
            version is not applied.  This is intended only for
243
224
            upgrade/recovery type use; it's not guaranteed that
246
227
        In the test suite, creation of new trees is tested using the
247
228
        `ScratchBranch` class.
248
229
        """
 
230
        assert isinstance(transport, Transport), \
 
231
            "%r is not a Transport" % transport
 
232
        self._transport = transport
249
233
        if init:
250
 
            self.base = os.path.realpath(base)
251
234
            self._make_control()
252
 
        elif find_root:
253
 
            self.base = find_branch_root(base)
254
 
        else:
255
 
            if base.startswith("file://"):
256
 
                base = base[7:]
257
 
            self.base = os.path.realpath(base)
258
 
            if not isdir(self.controlfilename('.')):
259
 
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
 
                                     ['use "bzr init" to initialize a '
261
 
                                      'new working tree'])
262
235
        self._check_format(relax_version_check)
263
 
        cfn = self.controlfilename
 
236
 
 
237
        def get_store(name, compressed=True):
 
238
            relpath = self._rel_controlfilename(name)
 
239
            if compressed:
 
240
                store = CompressedTextStore(self._transport.clone(relpath))
 
241
            else:
 
242
                store = TextStore(self._transport.clone(relpath))
 
243
            if self._transport.should_cache():
 
244
                from meta_store import CachedStore
 
245
                cache_path = os.path.join(self.cache_root, name)
 
246
                os.mkdir(cache_path)
 
247
                store = CachedStore(store, cache_path)
 
248
            return store
 
249
        def get_weave(name):
 
250
            relpath = self._rel_controlfilename(name)
 
251
            ws = WeaveStore(self._transport.clone(relpath))
 
252
            if self._transport.should_cache():
 
253
                ws.enable_cache = True
 
254
            return ws
 
255
 
264
256
        if self._branch_format == 4:
265
 
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
 
            self.text_store = ImmutableStore(cfn('text-store'))
 
257
            self.inventory_store = get_store('inventory-store')
 
258
            self.text_store = get_store('text-store')
 
259
            self.revision_store = get_store('revision-store')
267
260
        elif self._branch_format == 5:
268
 
            self.control_weaves = WeaveStore(cfn([]))
269
 
            self.weave_store = WeaveStore(cfn('weaves'))
270
 
            if init:
271
 
                # FIXME: Unify with make_control_files
272
 
                self.control_weaves.put_empty_weave('inventory')
273
 
                self.control_weaves.put_empty_weave('ancestry')
274
 
        self.revision_store = ImmutableStore(cfn('revision-store'))
275
 
 
 
261
            self.control_weaves = get_weave([])
 
262
            self.weave_store = get_weave('weaves')
 
263
            self.revision_store = get_store('revision-store', compressed=False)
276
264
 
277
265
    def __str__(self):
278
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
266
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
279
267
 
280
268
 
281
269
    __repr__ = __str__
288
276
            warn("branch %r was not explicitly unlocked" % self)
289
277
            self._lock.unlock()
290
278
 
 
279
        # TODO: It might be best to do this somewhere else,
 
280
        # but it is nice for a Branch object to automatically
 
281
        # cache it's information.
 
282
        # Alternatively, we could have the Transport objects cache requests
 
283
        # See the earlier discussion about how major objects (like Branch)
 
284
        # should never expect their __del__ function to run.
 
285
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
286
            try:
 
287
                import shutil
 
288
                shutil.rmtree(self.cache_root)
 
289
            except:
 
290
                pass
 
291
            self.cache_root = None
 
292
 
 
293
    def _get_base(self):
 
294
        if self._transport:
 
295
            return self._transport.base
 
296
        return None
 
297
 
 
298
    base = property(_get_base)
 
299
 
 
300
 
291
301
    def lock_write(self):
 
302
        # TODO: Upgrade locking to support using a Transport,
 
303
        # and potentially a remote locking protocol
292
304
        if self._lock_mode:
293
305
            if self._lock_mode != 'w':
294
306
                raise LockError("can't upgrade to a write lock from %r" %
295
307
                                self._lock_mode)
296
308
            self._lock_count += 1
297
309
        else:
298
 
            from bzrlib.lock import WriteLock
299
 
 
300
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
310
            self._lock = self._transport.lock_write(
 
311
                    self._rel_controlfilename('branch-lock'))
301
312
            self._lock_mode = 'w'
302
313
            self._lock_count = 1
303
314
 
308
319
                   "invalid lock mode %r" % self._lock_mode
309
320
            self._lock_count += 1
310
321
        else:
311
 
            from bzrlib.lock import ReadLock
312
 
 
313
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
322
            self._lock = self._transport.lock_read(
 
323
                    self._rel_controlfilename('branch-lock'))
314
324
            self._lock_mode = 'r'
315
325
            self._lock_count = 1
316
326
                        
327
337
 
328
338
    def abspath(self, name):
329
339
        """Return absolute filename for something in the branch"""
330
 
        return os.path.join(self.base, name)
 
340
        return self._transport.abspath(name)
331
341
 
332
342
    def relpath(self, path):
333
343
        """Return path relative to this branch of something inside it.
334
344
 
335
345
        Raises an error if path is not in this branch."""
336
 
        return _relpath(self.base, path)
 
346
        return self._transport.relpath(path)
 
347
 
 
348
 
 
349
    def _rel_controlfilename(self, file_or_path):
 
350
        if isinstance(file_or_path, basestring):
 
351
            file_or_path = [file_or_path]
 
352
        return [bzrlib.BZRDIR] + file_or_path
337
353
 
338
354
    def controlfilename(self, file_or_path):
339
355
        """Return location relative to branch."""
340
 
        if isinstance(file_or_path, basestring):
341
 
            file_or_path = [file_or_path]
342
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
356
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
343
357
 
344
358
 
345
359
    def controlfile(self, file_or_path, mode='r'):
353
367
        Controlfiles should almost never be opened in write mode but
354
368
        rather should be atomically copied and replaced using atomicfile.
355
369
        """
356
 
 
357
 
        fn = self.controlfilename(file_or_path)
358
 
 
359
 
        if mode == 'rb' or mode == 'wb':
360
 
            return file(fn, mode)
361
 
        elif mode == 'r' or mode == 'w':
362
 
            # open in binary mode anyhow so there's no newline translation;
363
 
            # codecs uses line buffering by default; don't want that.
364
 
            import codecs
365
 
            return codecs.open(fn, mode + 'b', 'utf-8',
366
 
                               buffering=60000)
 
370
        import codecs
 
371
 
 
372
        relpath = self._rel_controlfilename(file_or_path)
 
373
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
374
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
375
        if mode == 'rb': 
 
376
            return self._transport.get(relpath)
 
377
        elif mode == 'wb':
 
378
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
 
379
        elif mode == 'r':
 
380
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
381
        elif mode == 'w':
 
382
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
367
383
        else:
368
384
            raise BzrError("invalid controlfile mode %r" % mode)
369
385
 
 
386
    def put_controlfile(self, path, f, encode=True):
 
387
        """Write an entry as a controlfile.
 
388
 
 
389
        :param path: The path to put the file, relative to the .bzr control
 
390
                     directory
 
391
        :param f: A file-like or string object whose contents should be copied.
 
392
        :param encode:  If true, encode the contents as utf-8
 
393
        """
 
394
        self.put_controlfiles([(path, f)], encode=encode)
 
395
 
 
396
    def put_controlfiles(self, files, encode=True):
 
397
        """Write several entries as controlfiles.
 
398
 
 
399
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
400
                      underneath the bzr control directory
 
401
        :param encode:  If true, encode the contents as utf-8
 
402
        """
 
403
        import codecs
 
404
        ctrl_files = []
 
405
        for path, f in files:
 
406
            if encode:
 
407
                if isinstance(f, basestring):
 
408
                    f = f.encode('utf-8', 'replace')
 
409
                else:
 
410
                    f = codecs.getwriter('utf-8')(f, errors='replace')
 
411
            path = self._rel_controlfilename(path)
 
412
            ctrl_files.append((path, f))
 
413
        self._transport.put_multi(ctrl_files)
 
414
 
370
415
    def _make_control(self):
371
 
        os.mkdir(self.controlfilename([]))
372
 
        self.controlfile('README', 'w').write(
373
 
            "This is a Bazaar-NG control directory.\n"
374
 
            "Do not change any files in this directory.\n")
375
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
376
 
        for d in ('text-store', 'revision-store',
377
 
                  'weaves'):
378
 
            os.mkdir(self.controlfilename(d))
379
 
        for f in ('revision-history',
380
 
                  'branch-name',
381
 
                  'branch-lock',
382
 
                  'pending-merges'):
383
 
            self.controlfile(f, 'w').write('')
384
 
        mutter('created control directory in ' + self.base)
385
 
 
 
416
        from bzrlib.inventory import Inventory
 
417
        from bzrlib.weavefile import write_weave_v5
 
418
        from bzrlib.weave import Weave
 
419
        
 
420
        # Create an empty inventory
 
421
        sio = StringIO()
386
422
        # if we want per-tree root ids then this is the place to set
387
423
        # them; they're not needed for now and so ommitted for
388
424
        # simplicity.
389
 
        f = self.controlfile('inventory','w')
390
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
 
425
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
426
        empty_inv = sio.getvalue()
 
427
        sio = StringIO()
 
428
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
429
        empty_weave = sio.getvalue()
391
430
 
 
431
        dirs = [[], 'revision-store', 'weaves']
 
432
        files = [('README', 
 
433
            "This is a Bazaar-NG control directory.\n"
 
434
            "Do not change any files in this directory.\n"),
 
435
            ('branch-format', BZR_BRANCH_FORMAT_5),
 
436
            ('revision-history', ''),
 
437
            ('branch-name', ''),
 
438
            ('branch-lock', ''),
 
439
            ('pending-merges', ''),
 
440
            ('inventory', empty_inv),
 
441
            ('inventory.weave', empty_weave),
 
442
            ('ancestry.weave', empty_weave)
 
443
        ]
 
444
        cfn = self._rel_controlfilename
 
445
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
446
        self.put_controlfiles(files)
 
447
        mutter('created control directory in ' + self._transport.base)
392
448
 
393
449
    def _check_format(self, relax_version_check):
394
450
        """Check this branch format is supported.
401
457
        """
402
458
        try:
403
459
            fmt = self.controlfile('branch-format', 'r').read()
404
 
        except IOError, e:
405
 
            if e.errno == errno.ENOENT:
406
 
                raise NotBranchError(self.base)
407
 
            else:
408
 
                raise
 
460
        except NoSuchFile:
 
461
            raise NotBranchError(self.base)
409
462
 
410
463
        if fmt == BZR_BRANCH_FORMAT_5:
411
464
            self._branch_format = 5
416
469
            and self._branch_format != 5):
417
470
            raise BzrError('sorry, branch format %r not supported' % fmt,
418
471
                           ['use a different bzr version',
419
 
                            'or remove the .bzr directory and "bzr init" again'])
 
472
                            'or remove the .bzr directory'
 
473
                            ' and "bzr init" again'])
420
474
 
421
475
    def get_root_id(self):
422
476
        """Return the id of this branches root"""
453
507
        That is to say, the inventory describing changes underway, that
454
508
        will be committed to the next revision.
455
509
        """
456
 
        from bzrlib.atomicfile import AtomicFile
457
 
        
 
510
        from cStringIO import StringIO
458
511
        self.lock_write()
459
512
        try:
460
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
461
 
            try:
462
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
463
 
                f.commit()
464
 
            finally:
465
 
                f.close()
 
513
            sio = StringIO()
 
514
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
515
            sio.seek(0)
 
516
            # Transport handles atomicity
 
517
            self.put_controlfile('inventory', sio)
466
518
        finally:
467
519
            self.unlock()
468
520
        
633
685
 
634
686
 
635
687
    def append_revision(self, *revision_ids):
636
 
        from bzrlib.atomicfile import AtomicFile
637
 
 
638
688
        for revision_id in revision_ids:
639
689
            mutter("add {%s} to revision-history" % revision_id)
640
 
 
641
 
        rev_history = self.revision_history()
642
 
        rev_history.extend(revision_ids)
643
 
 
644
 
        f = AtomicFile(self.controlfilename('revision-history'))
 
690
        self.lock_write()
645
691
        try:
646
 
            for rev_id in rev_history:
647
 
                print >>f, rev_id
648
 
            f.commit()
 
692
            rev_history = self.revision_history()
 
693
            rev_history.extend(revision_ids)
 
694
            self.put_controlfile('revision-history', '\n'.join(rev_history))
649
695
        finally:
650
 
            f.close()
 
696
            self.unlock()
651
697
 
652
698
 
653
699
    def has_revision(self, revision_id):
714
760
 
715
761
        return compare_trees(old_tree, new_tree)
716
762
 
717
 
 
718
763
    def get_revision_sha1(self, revision_id):
719
764
        """Hash the stored value of a revision, and return it."""
720
765
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
951
996
    def working_tree(self):
952
997
        """Return a `Tree` for the working copy."""
953
998
        from bzrlib.workingtree import WorkingTree
954
 
        return WorkingTree(self.base, self.read_working_inventory())
 
999
        # TODO: In the future, WorkingTree should utilize Transport
 
1000
        return WorkingTree(self._transport.base, self.read_working_inventory())
955
1001
 
956
1002
 
957
1003
    def basis_tree(self):
1131
1177
        These are revisions that have been merged into the working
1132
1178
        directory but not yet committed.
1133
1179
        """
1134
 
        cfn = self.controlfilename('pending-merges')
1135
 
        if not os.path.exists(cfn):
 
1180
        cfn = self._rel_controlfilename('pending-merges')
 
1181
        if not self._transport.has(cfn):
1136
1182
            return []
1137
1183
        p = []
1138
1184
        for l in self.controlfile('pending-merges', 'r').readlines():
1140
1186
        return p
1141
1187
 
1142
1188
 
1143
 
    def add_pending_merge(self, revision_id):
1144
 
        validate_revision_id(revision_id)
 
1189
    def add_pending_merge(self, *revision_ids):
1145
1190
        # TODO: Perhaps should check at this point that the
1146
1191
        # history of the revision is actually present?
 
1192
        for rev_id in revision_ids:
 
1193
            validate_revision_id(rev_id)
 
1194
 
1147
1195
        p = self.pending_merges()
1148
 
        if revision_id in p:
1149
 
            return
1150
 
        p.append(revision_id)
1151
 
        self.set_pending_merges(p)
1152
 
 
 
1196
        updated = False
 
1197
        for rev_id in revision_ids:
 
1198
            if rev_id in p:
 
1199
                continue
 
1200
            p.append(rev_id)
 
1201
            updated = True
 
1202
        if updated:
 
1203
            self.set_pending_merges(p)
1153
1204
 
1154
1205
    def set_pending_merges(self, rev_list):
1155
 
        from bzrlib.atomicfile import AtomicFile
1156
1206
        self.lock_write()
1157
1207
        try:
1158
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1159
 
            try:
1160
 
                for l in rev_list:
1161
 
                    print >>f, l
1162
 
                f.commit()
1163
 
            finally:
1164
 
                f.close()
 
1208
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1165
1209
        finally:
1166
1210
            self.unlock()
1167
1211
 
1218
1262
        
1219
1263
 
1220
1264
 
1221
 
class ScratchBranch(LocalBranch):
 
1265
class ScratchBranch(_Branch):
1222
1266
    """Special test class: a branch that cleans up after itself.
1223
1267
 
1224
1268
    >>> b = ScratchBranch()
1241
1285
        if base is None:
1242
1286
            base = mkdtemp()
1243
1287
            init = True
1244
 
        LocalBranch.__init__(self, base, init=init)
 
1288
        if isinstance(base, basestring):
 
1289
            base = get_transport(base)
 
1290
        _Branch.__init__(self, base, init=init)
1245
1291
        for d in dirs:
1246
 
            os.mkdir(self.abspath(d))
 
1292
            self._transport.mkdir(d)
1247
1293
            
1248
1294
        for f in files:
1249
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1295
            self._transport.put(f, 'content of %s' % f)
1250
1296
 
1251
1297
 
1252
1298
    def clone(self):
1289
1335
                for name in files:
1290
1336
                    os.chmod(os.path.join(root, name), 0700)
1291
1337
            rmtree(self.base)
1292
 
        self.base = None
 
1338
        self._transport = None
1293
1339
 
1294
1340
    
1295
1341