~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-10-02 01:53:46 UTC
  • mfrom: (1393.1.23)
  • Revision ID: robertc@robertcollins.net-20051002015346-587422189352289e
merge from upstream newformat

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
######################################################################
132
122
        raise NotImplementedError('The Branch class is abstract')
133
123
 
134
124
    @staticmethod
 
125
    def open_downlevel(base):
 
126
        """Open a branch which may be of an old format.
 
127
        
 
128
        Only local branches are supported."""
 
129
        return _Branch(get_transport(base), relax_version_check=True)
 
130
        
 
131
    @staticmethod
135
132
    def open(base):
136
133
        """Open an existing branch, rooted at 'base' (url)"""
137
 
        if base and (base.startswith('http://') or base.startswith('https://')):
138
 
            from bzrlib.remotebranch import RemoteBranch
139
 
            return RemoteBranch(base, find_root=False)
140
 
        else:
141
 
            return LocalBranch(base, find_root=False)
 
134
        t = get_transport(base)
 
135
        return _Branch(t)
142
136
 
143
137
    @staticmethod
144
138
    def open_containing(url):
146
140
        
147
141
        This probes for a branch at url, and searches upwards from there.
148
142
        """
149
 
        if url and (url.startswith('http://') or url.startswith('https://')):
150
 
            from bzrlib.remotebranch import RemoteBranch
151
 
            return RemoteBranch(url)
152
 
        else:
153
 
            return LocalBranch(url)
 
143
        t = get_transport(url)
 
144
        t = find_branch_root(t)
 
145
        return _Branch(t)
154
146
 
155
147
    @staticmethod
156
148
    def initialize(base):
157
149
        """Create a new branch, rooted at 'base' (url)"""
158
 
        if base and (base.startswith('http://') or base.startswith('https://')):
159
 
            from bzrlib.remotebranch import RemoteBranch
160
 
            return RemoteBranch(base, init=True)
161
 
        else:
162
 
            return LocalBranch(base, init=True)
 
150
        t = get_transport(base)
 
151
        return _Branch(t, init=True)
163
152
 
164
153
    def setup_caching(self, cache_root):
165
154
        """Subclasses that care about caching should override this, and set
167
156
        """
168
157
 
169
158
 
170
 
class LocalBranch(Branch):
 
159
class _Branch(Branch):
171
160
    """A branch stored in the actual filesystem.
172
161
 
173
162
    Note that it's "local" in the context of the filesystem; it doesn't
218
207
        except UnlistableStore:
219
208
            raise UnlistableBranch(from_store)
220
209
 
221
 
    def __init__(self, base, init=False, find_root=True,
 
210
    def __init__(self, transport, init=False,
222
211
                 relax_version_check=False):
223
212
        """Create new branch object at a particular location.
224
213
 
225
 
        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)
226
217
        
227
218
        init -- If True, create new control files in a previously
228
219
             unversioned directory.  If False, the branch must already
229
220
             be versioned.
230
221
 
231
 
        find_root -- If true and init is false, find the root of the
232
 
             existing branch containing base.
233
 
 
234
222
        relax_version_check -- If true, the usual check for the branch
235
223
            version is not applied.  This is intended only for
236
224
            upgrade/recovery type use; it's not guaranteed that
239
227
        In the test suite, creation of new trees is tested using the
240
228
        `ScratchBranch` class.
241
229
        """
 
230
        assert isinstance(transport, Transport), \
 
231
            "%r is not a Transport" % transport
 
232
        self._transport = transport
242
233
        if init:
243
 
            self.base = os.path.realpath(base)
244
234
            self._make_control()
245
 
        elif find_root:
246
 
            self.base = find_branch_root(base)
247
 
        else:
248
 
            if base.startswith("file://"):
249
 
                base = base[7:]
250
 
            self.base = os.path.realpath(base)
251
 
            if not isdir(self.controlfilename('.')):
252
 
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
253
 
                                     ['use "bzr init" to initialize a '
254
 
                                      'new working tree'])
255
235
        self._check_format(relax_version_check)
256
 
        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
 
257
256
        if self._branch_format == 4:
258
 
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
259
 
            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')
260
260
        elif self._branch_format == 5:
261
 
            self.control_weaves = WeaveStore(cfn([]))
262
 
            self.weave_store = WeaveStore(cfn('weaves'))
263
 
            if init:
264
 
                # FIXME: Unify with make_control_files
265
 
                self.control_weaves.put_empty_weave('inventory')
266
 
                self.control_weaves.put_empty_weave('ancestry')
267
 
        self.revision_store = ImmutableStore(cfn('revision-store'))
268
 
 
 
261
            self.control_weaves = get_weave([])
 
262
            self.weave_store = get_weave('weaves')
 
263
            self.revision_store = get_store('revision-store', compressed=False)
269
264
 
270
265
    def __str__(self):
271
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
266
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
272
267
 
273
268
 
274
269
    __repr__ = __str__
281
276
            warn("branch %r was not explicitly unlocked" % self)
282
277
            self._lock.unlock()
283
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
 
284
301
    def lock_write(self):
 
302
        # TODO: Upgrade locking to support using a Transport,
 
303
        # and potentially a remote locking protocol
285
304
        if self._lock_mode:
286
305
            if self._lock_mode != 'w':
287
306
                raise LockError("can't upgrade to a write lock from %r" %
288
307
                                self._lock_mode)
289
308
            self._lock_count += 1
290
309
        else:
291
 
            from bzrlib.lock import WriteLock
292
 
 
293
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
310
            self._lock = self._transport.lock_write(
 
311
                    self._rel_controlfilename('branch-lock'))
294
312
            self._lock_mode = 'w'
295
313
            self._lock_count = 1
296
314
 
301
319
                   "invalid lock mode %r" % self._lock_mode
302
320
            self._lock_count += 1
303
321
        else:
304
 
            from bzrlib.lock import ReadLock
305
 
 
306
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
322
            self._lock = self._transport.lock_read(
 
323
                    self._rel_controlfilename('branch-lock'))
307
324
            self._lock_mode = 'r'
308
325
            self._lock_count = 1
309
326
                        
320
337
 
321
338
    def abspath(self, name):
322
339
        """Return absolute filename for something in the branch"""
323
 
        return os.path.join(self.base, name)
 
340
        return self._transport.abspath(name)
324
341
 
325
342
    def relpath(self, path):
326
343
        """Return path relative to this branch of something inside it.
327
344
 
328
345
        Raises an error if path is not in this branch."""
329
 
        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
330
353
 
331
354
    def controlfilename(self, file_or_path):
332
355
        """Return location relative to branch."""
333
 
        if isinstance(file_or_path, basestring):
334
 
            file_or_path = [file_or_path]
335
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
356
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
336
357
 
337
358
 
338
359
    def controlfile(self, file_or_path, mode='r'):
346
367
        Controlfiles should almost never be opened in write mode but
347
368
        rather should be atomically copied and replaced using atomicfile.
348
369
        """
349
 
 
350
 
        fn = self.controlfilename(file_or_path)
351
 
 
352
 
        if mode == 'rb' or mode == 'wb':
353
 
            return file(fn, mode)
354
 
        elif mode == 'r' or mode == 'w':
355
 
            # open in binary mode anyhow so there's no newline translation;
356
 
            # codecs uses line buffering by default; don't want that.
357
 
            import codecs
358
 
            return codecs.open(fn, mode + 'b', 'utf-8',
359
 
                               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")
360
383
        else:
361
384
            raise BzrError("invalid controlfile mode %r" % mode)
362
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
 
363
415
    def _make_control(self):
364
 
        os.mkdir(self.controlfilename([]))
365
 
        self.controlfile('README', 'w').write(
366
 
            "This is a Bazaar-NG control directory.\n"
367
 
            "Do not change any files in this directory.\n")
368
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
369
 
        for d in ('text-store', 'revision-store',
370
 
                  'weaves'):
371
 
            os.mkdir(self.controlfilename(d))
372
 
        for f in ('revision-history',
373
 
                  'branch-name',
374
 
                  'branch-lock',
375
 
                  'pending-merges'):
376
 
            self.controlfile(f, 'w').write('')
377
 
        mutter('created control directory in ' + self.base)
378
 
 
 
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()
379
422
        # if we want per-tree root ids then this is the place to set
380
423
        # them; they're not needed for now and so ommitted for
381
424
        # simplicity.
382
 
        f = self.controlfile('inventory','w')
383
 
        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()
384
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)
385
448
 
386
449
    def _check_format(self, relax_version_check):
387
450
        """Check this branch format is supported.
394
457
        """
395
458
        try:
396
459
            fmt = self.controlfile('branch-format', 'r').read()
397
 
        except IOError, e:
398
 
            if hasattr(e, 'errno'):
399
 
                if e.errno == errno.ENOENT:
400
 
                    raise NotBranchError(self.base)
401
 
                else:
402
 
                    raise
403
 
            else:
404
 
                raise
 
460
        except NoSuchFile:
 
461
            raise NotBranchError(self.base)
405
462
 
406
463
        if fmt == BZR_BRANCH_FORMAT_5:
407
464
            self._branch_format = 5
412
469
            and self._branch_format != 5):
413
470
            raise BzrError('sorry, branch format %r not supported' % fmt,
414
471
                           ['use a different bzr version',
415
 
                            'or remove the .bzr directory and "bzr init" again'])
 
472
                            'or remove the .bzr directory'
 
473
                            ' and "bzr init" again'])
416
474
 
417
475
    def get_root_id(self):
418
476
        """Return the id of this branches root"""
449
507
        That is to say, the inventory describing changes underway, that
450
508
        will be committed to the next revision.
451
509
        """
452
 
        from bzrlib.atomicfile import AtomicFile
453
 
        
 
510
        from cStringIO import StringIO
454
511
        self.lock_write()
455
512
        try:
456
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
457
 
            try:
458
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
459
 
                f.commit()
460
 
            finally:
461
 
                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)
462
518
        finally:
463
519
            self.unlock()
464
520
        
629
685
 
630
686
 
631
687
    def append_revision(self, *revision_ids):
632
 
        from bzrlib.atomicfile import AtomicFile
633
 
 
634
688
        for revision_id in revision_ids:
635
689
            mutter("add {%s} to revision-history" % revision_id)
636
 
 
637
 
        rev_history = self.revision_history()
638
 
        rev_history.extend(revision_ids)
639
 
 
640
 
        f = AtomicFile(self.controlfilename('revision-history'))
 
690
        self.lock_write()
641
691
        try:
642
 
            for rev_id in rev_history:
643
 
                print >>f, rev_id
644
 
            f.commit()
 
692
            rev_history = self.revision_history()
 
693
            rev_history.extend(revision_ids)
 
694
            self.put_controlfile('revision-history', '\n'.join(rev_history))
645
695
        finally:
646
 
            f.close()
 
696
            self.unlock()
647
697
 
648
698
 
649
699
    def has_revision(self, revision_id):
710
760
 
711
761
        return compare_trees(old_tree, new_tree)
712
762
 
713
 
 
714
763
    def get_revision_sha1(self, revision_id):
715
764
        """Hash the stored value of a revision, and return it."""
716
765
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
947
996
    def working_tree(self):
948
997
        """Return a `Tree` for the working copy."""
949
998
        from bzrlib.workingtree import WorkingTree
950
 
        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())
951
1001
 
952
1002
 
953
1003
    def basis_tree(self):
1127
1177
        These are revisions that have been merged into the working
1128
1178
        directory but not yet committed.
1129
1179
        """
1130
 
        cfn = self.controlfilename('pending-merges')
1131
 
        if not os.path.exists(cfn):
 
1180
        cfn = self._rel_controlfilename('pending-merges')
 
1181
        if not self._transport.has(cfn):
1132
1182
            return []
1133
1183
        p = []
1134
1184
        for l in self.controlfile('pending-merges', 'r').readlines():
1136
1186
        return p
1137
1187
 
1138
1188
 
1139
 
    def add_pending_merge(self, revision_id):
1140
 
        validate_revision_id(revision_id)
 
1189
    def add_pending_merge(self, *revision_ids):
1141
1190
        # TODO: Perhaps should check at this point that the
1142
1191
        # history of the revision is actually present?
 
1192
        for rev_id in revision_ids:
 
1193
            validate_revision_id(rev_id)
 
1194
 
1143
1195
        p = self.pending_merges()
1144
 
        if revision_id in p:
1145
 
            return
1146
 
        p.append(revision_id)
1147
 
        self.set_pending_merges(p)
1148
 
 
 
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)
1149
1204
 
1150
1205
    def set_pending_merges(self, rev_list):
1151
 
        from bzrlib.atomicfile import AtomicFile
1152
1206
        self.lock_write()
1153
1207
        try:
1154
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1155
 
            try:
1156
 
                for l in rev_list:
1157
 
                    print >>f, l
1158
 
                f.commit()
1159
 
            finally:
1160
 
                f.close()
 
1208
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1161
1209
        finally:
1162
1210
            self.unlock()
1163
1211
 
1214
1262
        
1215
1263
 
1216
1264
 
1217
 
class ScratchBranch(LocalBranch):
 
1265
class ScratchBranch(_Branch):
1218
1266
    """Special test class: a branch that cleans up after itself.
1219
1267
 
1220
1268
    >>> b = ScratchBranch()
1237
1285
        if base is None:
1238
1286
            base = mkdtemp()
1239
1287
            init = True
1240
 
        LocalBranch.__init__(self, base, init=init)
 
1288
        if isinstance(base, basestring):
 
1289
            base = get_transport(base)
 
1290
        _Branch.__init__(self, base, init=init)
1241
1291
        for d in dirs:
1242
 
            os.mkdir(self.abspath(d))
 
1292
            self._transport.mkdir(d)
1243
1293
            
1244
1294
        for f in files:
1245
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1295
            self._transport.put(f, 'content of %s' % f)
1246
1296
 
1247
1297
 
1248
1298
    def clone(self):
1285
1335
                for name in files:
1286
1336
                    os.chmod(os.path.join(root, name), 0700)
1287
1337
            rmtree(self.base)
1288
 
        self.base = None
 
1338
        self._transport = None
1289
1339
 
1290
1340
    
1291
1341
 
1342
1392
    return gen_file_id('TREE_ROOT')
1343
1393
 
1344
1394
 
1345
 
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1346
 
    """Copy branch_from into the existing directory to_location.
1347
 
 
1348
 
    revision
1349
 
        If not None, only revisions up to this point will be copied.
1350
 
        The head of the new branch will be that revision.  Must be a
1351
 
        revid or None.
1352
 
 
1353
 
    to_location
1354
 
        The name of a local directory that exists but is empty.
1355
 
 
1356
 
    revno
1357
 
        The revision to copy up to
1358
 
 
1359
 
    basis_branch
1360
 
        A local branch to copy revisions from, related to branch_from
1361
 
    """
1362
 
    # TODO: This could be done *much* more efficiently by just copying
1363
 
    # all the whole weaves and revisions, rather than getting one
1364
 
    # revision at a time.
1365
 
    from bzrlib.merge import merge
1366
 
 
1367
 
    assert isinstance(branch_from, Branch)
1368
 
    assert isinstance(to_location, basestring)
1369
 
    
1370
 
    br_to = Branch.initialize(to_location)
1371
 
    mutter("copy branch from %s to %s", branch_from, br_to)
1372
 
    if basis_branch is not None:
1373
 
        basis_branch.push_stores(br_to)
1374
 
    br_to.set_root_id(branch_from.get_root_id())
1375
 
    if revision is None:
1376
 
        revision = branch_from.last_revision()
1377
 
    br_to.update_revisions(branch_from, stop_revision=revision)
1378
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1379
 
          check_clean=False, ignore_zero=True)
1380
 
    br_to.set_parent(branch_from.base)
1381
 
    mutter("copied")
1382
 
    return br_to