~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 21:51:29 UTC
  • mfrom: (1396)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20051002215128-5686c7d24bf9bdb9
merge from martins newformat branch - brings in transport abstraction

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
 
    else:
99
 
        f = bzrlib.osutils.normalizepath(f)
100
 
    if not bzrlib.osutils.lexists(f):
101
 
        raise BzrError('%r does not exist' % f)
102
 
 
103
 
    orig_f = f
104
 
 
 
99
    orig_base = t.base
105
100
    while True:
106
 
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
107
 
            return f
108
 
        head, tail = os.path.split(f)
109
 
        if head == f:
 
101
        if t.has(bzrlib.BZRDIR):
 
102
            return t
 
103
        new_t = t.clone('..')
 
104
        if new_t.base == t.base:
110
105
            # reached the root, whatever that may be
111
 
            raise NotBranchError('%s is not in a branch' % orig_f)
112
 
        f = head
113
 
 
114
 
 
 
106
            raise NotBranchError('%s is not in a branch' % orig_base)
 
107
        t = new_t
115
108
 
116
109
 
117
110
######################################################################
129
122
        raise NotImplementedError('The Branch class is abstract')
130
123
 
131
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
132
132
    def open(base):
133
133
        """Open an existing branch, rooted at 'base' (url)"""
134
 
        if base and (base.startswith('http://') or base.startswith('https://')):
135
 
            from bzrlib.remotebranch import RemoteBranch
136
 
            return RemoteBranch(base, find_root=False)
137
 
        else:
138
 
            return LocalBranch(base, find_root=False)
 
134
        t = get_transport(base)
 
135
        return _Branch(t)
139
136
 
140
137
    @staticmethod
141
138
    def open_containing(url):
143
140
        
144
141
        This probes for a branch at url, and searches upwards from there.
145
142
        """
146
 
        if url and (url.startswith('http://') or url.startswith('https://')):
147
 
            from bzrlib.remotebranch import RemoteBranch
148
 
            return RemoteBranch(url)
149
 
        else:
150
 
            return LocalBranch(url)
 
143
        t = get_transport(url)
 
144
        t = find_branch_root(t)
 
145
        return _Branch(t)
151
146
 
152
147
    @staticmethod
153
148
    def initialize(base):
154
149
        """Create a new branch, rooted at 'base' (url)"""
155
 
        if base and (base.startswith('http://') or base.startswith('https://')):
156
 
            from bzrlib.remotebranch import RemoteBranch
157
 
            return RemoteBranch(base, init=True)
158
 
        else:
159
 
            return LocalBranch(base, init=True)
 
150
        t = get_transport(base)
 
151
        return _Branch(t, init=True)
160
152
 
161
153
    def setup_caching(self, cache_root):
162
154
        """Subclasses that care about caching should override this, and set
164
156
        """
165
157
 
166
158
 
167
 
class LocalBranch(Branch):
 
159
class _Branch(Branch):
168
160
    """A branch stored in the actual filesystem.
169
161
 
170
162
    Note that it's "local" in the context of the filesystem; it doesn't
215
207
        except UnlistableStore:
216
208
            raise UnlistableBranch(from_store)
217
209
 
218
 
    def __init__(self, base, init=False, find_root=True,
 
210
    def __init__(self, transport, init=False,
219
211
                 relax_version_check=False):
220
212
        """Create new branch object at a particular location.
221
213
 
222
 
        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)
223
217
        
224
218
        init -- If True, create new control files in a previously
225
219
             unversioned directory.  If False, the branch must already
226
220
             be versioned.
227
221
 
228
 
        find_root -- If true and init is false, find the root of the
229
 
             existing branch containing base.
230
 
 
231
222
        relax_version_check -- If true, the usual check for the branch
232
223
            version is not applied.  This is intended only for
233
224
            upgrade/recovery type use; it's not guaranteed that
236
227
        In the test suite, creation of new trees is tested using the
237
228
        `ScratchBranch` class.
238
229
        """
 
230
        assert isinstance(transport, Transport), \
 
231
            "%r is not a Transport" % transport
 
232
        self._transport = transport
239
233
        if init:
240
 
            self.base = os.path.realpath(base)
241
234
            self._make_control()
242
 
        elif find_root:
243
 
            self.base = find_branch_root(base)
244
 
        else:
245
 
            if base.startswith("file://"):
246
 
                base = base[7:]
247
 
            self.base = os.path.realpath(base)
248
 
            if not isdir(self.controlfilename('.')):
249
 
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
250
 
                                     ['use "bzr init" to initialize a '
251
 
                                      'new working tree'])
252
235
        self._check_format(relax_version_check)
253
 
        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
 
254
256
        if self._branch_format == 4:
255
 
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
256
 
            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')
257
260
        elif self._branch_format == 5:
258
 
            self.control_weaves = WeaveStore(cfn([]))
259
 
            self.weave_store = WeaveStore(cfn('weaves'))
260
 
            if init:
261
 
                # FIXME: Unify with make_control_files
262
 
                self.control_weaves.put_empty_weave('inventory')
263
 
                self.control_weaves.put_empty_weave('ancestry')
264
 
        self.revision_store = ImmutableStore(cfn('revision-store'))
265
 
 
 
261
            self.control_weaves = get_weave([])
 
262
            self.weave_store = get_weave('weaves')
 
263
            self.revision_store = get_store('revision-store', compressed=False)
266
264
 
267
265
    def __str__(self):
268
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
266
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
269
267
 
270
268
 
271
269
    __repr__ = __str__
278
276
            warn("branch %r was not explicitly unlocked" % self)
279
277
            self._lock.unlock()
280
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
 
281
301
    def lock_write(self):
 
302
        # TODO: Upgrade locking to support using a Transport,
 
303
        # and potentially a remote locking protocol
282
304
        if self._lock_mode:
283
305
            if self._lock_mode != 'w':
284
306
                raise LockError("can't upgrade to a write lock from %r" %
285
307
                                self._lock_mode)
286
308
            self._lock_count += 1
287
309
        else:
288
 
            from bzrlib.lock import WriteLock
289
 
 
290
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
310
            self._lock = self._transport.lock_write(
 
311
                    self._rel_controlfilename('branch-lock'))
291
312
            self._lock_mode = 'w'
292
313
            self._lock_count = 1
293
314
 
298
319
                   "invalid lock mode %r" % self._lock_mode
299
320
            self._lock_count += 1
300
321
        else:
301
 
            from bzrlib.lock import ReadLock
302
 
 
303
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
322
            self._lock = self._transport.lock_read(
 
323
                    self._rel_controlfilename('branch-lock'))
304
324
            self._lock_mode = 'r'
305
325
            self._lock_count = 1
306
326
                        
317
337
 
318
338
    def abspath(self, name):
319
339
        """Return absolute filename for something in the branch"""
320
 
        return os.path.join(self.base, name)
 
340
        return self._transport.abspath(name)
321
341
 
322
342
    def relpath(self, path):
323
343
        """Return path relative to this branch of something inside it.
324
344
 
325
345
        Raises an error if path is not in this branch."""
326
 
        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
327
353
 
328
354
    def controlfilename(self, file_or_path):
329
355
        """Return location relative to branch."""
330
 
        if isinstance(file_or_path, basestring):
331
 
            file_or_path = [file_or_path]
332
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
356
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
333
357
 
334
358
 
335
359
    def controlfile(self, file_or_path, mode='r'):
343
367
        Controlfiles should almost never be opened in write mode but
344
368
        rather should be atomically copied and replaced using atomicfile.
345
369
        """
346
 
 
347
 
        fn = self.controlfilename(file_or_path)
348
 
 
349
 
        if mode == 'rb' or mode == 'wb':
350
 
            return file(fn, mode)
351
 
        elif mode == 'r' or mode == 'w':
352
 
            # open in binary mode anyhow so there's no newline translation;
353
 
            # codecs uses line buffering by default; don't want that.
354
 
            import codecs
355
 
            return codecs.open(fn, mode + 'b', 'utf-8',
356
 
                               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")
357
383
        else:
358
384
            raise BzrError("invalid controlfile mode %r" % mode)
359
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
 
360
415
    def _make_control(self):
361
 
        os.mkdir(self.controlfilename([]))
362
 
        self.controlfile('README', 'w').write(
363
 
            "This is a Bazaar-NG control directory.\n"
364
 
            "Do not change any files in this directory.\n")
365
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
366
 
        for d in ('text-store', 'revision-store',
367
 
                  'weaves'):
368
 
            os.mkdir(self.controlfilename(d))
369
 
        for f in ('revision-history',
370
 
                  'branch-name',
371
 
                  'branch-lock',
372
 
                  'pending-merges'):
373
 
            self.controlfile(f, 'w').write('')
374
 
        mutter('created control directory in ' + self.base)
375
 
 
 
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()
376
422
        # if we want per-tree root ids then this is the place to set
377
423
        # them; they're not needed for now and so ommitted for
378
424
        # simplicity.
379
 
        f = self.controlfile('inventory','w')
380
 
        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()
381
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)
382
448
 
383
449
    def _check_format(self, relax_version_check):
384
450
        """Check this branch format is supported.
391
457
        """
392
458
        try:
393
459
            fmt = self.controlfile('branch-format', 'r').read()
394
 
        except IOError, e:
395
 
            if hasattr(e, 'errno'):
396
 
                if e.errno == errno.ENOENT:
397
 
                    raise NotBranchError(self.base)
398
 
                else:
399
 
                    raise
400
 
            else:
401
 
                raise
 
460
        except NoSuchFile:
 
461
            raise NotBranchError(self.base)
402
462
 
403
463
        if fmt == BZR_BRANCH_FORMAT_5:
404
464
            self._branch_format = 5
409
469
            and self._branch_format != 5):
410
470
            raise BzrError('sorry, branch format %r not supported' % fmt,
411
471
                           ['use a different bzr version',
412
 
                            'or remove the .bzr directory and "bzr init" again'])
 
472
                            'or remove the .bzr directory'
 
473
                            ' and "bzr init" again'])
413
474
 
414
475
    def get_root_id(self):
415
476
        """Return the id of this branches root"""
446
507
        That is to say, the inventory describing changes underway, that
447
508
        will be committed to the next revision.
448
509
        """
449
 
        from bzrlib.atomicfile import AtomicFile
450
 
        
 
510
        from cStringIO import StringIO
451
511
        self.lock_write()
452
512
        try:
453
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
454
 
            try:
455
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
456
 
                f.commit()
457
 
            finally:
458
 
                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)
459
518
        finally:
460
519
            self.unlock()
461
520
        
622
681
 
623
682
 
624
683
    def append_revision(self, *revision_ids):
625
 
        from bzrlib.atomicfile import AtomicFile
626
 
 
627
684
        for revision_id in revision_ids:
628
685
            mutter("add {%s} to revision-history" % revision_id)
629
 
 
630
 
        rev_history = self.revision_history()
631
 
        rev_history.extend(revision_ids)
632
 
 
633
 
        f = AtomicFile(self.controlfilename('revision-history'))
 
686
        self.lock_write()
634
687
        try:
635
 
            for rev_id in rev_history:
636
 
                print >>f, rev_id
637
 
            f.commit()
 
688
            rev_history = self.revision_history()
 
689
            rev_history.extend(revision_ids)
 
690
            self.put_controlfile('revision-history', '\n'.join(rev_history))
638
691
        finally:
639
 
            f.close()
 
692
            self.unlock()
640
693
 
641
694
    def has_revision(self, revision_id):
642
695
        """True if this branch has a copy of the revision.
935
988
    def working_tree(self):
936
989
        """Return a `Tree` for the working copy."""
937
990
        from bzrlib.workingtree import WorkingTree
938
 
        return WorkingTree(self.base, self.read_working_inventory())
 
991
        # TODO: In the future, WorkingTree should utilize Transport
 
992
        return WorkingTree(self._transport.base, self.read_working_inventory())
939
993
 
940
994
 
941
995
    def basis_tree(self):
1115
1169
        These are revisions that have been merged into the working
1116
1170
        directory but not yet committed.
1117
1171
        """
1118
 
        cfn = self.controlfilename('pending-merges')
1119
 
        if not os.path.exists(cfn):
 
1172
        cfn = self._rel_controlfilename('pending-merges')
 
1173
        if not self._transport.has(cfn):
1120
1174
            return []
1121
1175
        p = []
1122
1176
        for l in self.controlfile('pending-merges', 'r').readlines():
1124
1178
        return p
1125
1179
 
1126
1180
 
1127
 
    def add_pending_merge(self, revision_id):
1128
 
        validate_revision_id(revision_id)
 
1181
    def add_pending_merge(self, *revision_ids):
1129
1182
        # TODO: Perhaps should check at this point that the
1130
1183
        # history of the revision is actually present?
 
1184
        for rev_id in revision_ids:
 
1185
            validate_revision_id(rev_id)
 
1186
 
1131
1187
        p = self.pending_merges()
1132
 
        if revision_id in p:
1133
 
            return
1134
 
        p.append(revision_id)
1135
 
        self.set_pending_merges(p)
1136
 
 
 
1188
        updated = False
 
1189
        for rev_id in revision_ids:
 
1190
            if rev_id in p:
 
1191
                continue
 
1192
            p.append(rev_id)
 
1193
            updated = True
 
1194
        if updated:
 
1195
            self.set_pending_merges(p)
1137
1196
 
1138
1197
    def set_pending_merges(self, rev_list):
1139
 
        from bzrlib.atomicfile import AtomicFile
1140
1198
        self.lock_write()
1141
1199
        try:
1142
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1143
 
            try:
1144
 
                for l in rev_list:
1145
 
                    print >>f, l
1146
 
                f.commit()
1147
 
            finally:
1148
 
                f.close()
 
1200
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1149
1201
        finally:
1150
1202
            self.unlock()
1151
1203
 
1202
1254
        
1203
1255
 
1204
1256
 
1205
 
class ScratchBranch(LocalBranch):
 
1257
class ScratchBranch(_Branch):
1206
1258
    """Special test class: a branch that cleans up after itself.
1207
1259
 
1208
1260
    >>> b = ScratchBranch()
1225
1277
        if base is None:
1226
1278
            base = mkdtemp()
1227
1279
            init = True
1228
 
        LocalBranch.__init__(self, base, init=init)
 
1280
        if isinstance(base, basestring):
 
1281
            base = get_transport(base)
 
1282
        _Branch.__init__(self, base, init=init)
1229
1283
        for d in dirs:
1230
 
            os.mkdir(self.abspath(d))
 
1284
            self._transport.mkdir(d)
1231
1285
            
1232
1286
        for f in files:
1233
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1287
            self._transport.put(f, 'content of %s' % f)
1234
1288
 
1235
1289
 
1236
1290
    def clone(self):
1271
1325
                for name in files:
1272
1326
                    os.chmod(os.path.join(root, name), 0700)
1273
1327
            rmtree(self.base)
1274
 
        self.base = None
 
1328
        self._transport = None
1275
1329
 
1276
1330
    
1277
1331
 
1328
1382
    return gen_file_id('TREE_ROOT')
1329
1383
 
1330
1384
 
1331
 
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1332
 
    """Copy branch_from into the existing directory to_location.
1333
 
 
1334
 
    revision
1335
 
        If not None, only revisions up to this point will be copied.
1336
 
        The head of the new branch will be that revision.  Must be a
1337
 
        revid or None.
1338
 
 
1339
 
    to_location
1340
 
        The name of a local directory that exists but is empty.
1341
 
 
1342
 
    revno
1343
 
        The revision to copy up to
1344
 
 
1345
 
    basis_branch
1346
 
        A local branch to copy revisions from, related to branch_from
1347
 
    """
1348
 
    # TODO: This could be done *much* more efficiently by just copying
1349
 
    # all the whole weaves and revisions, rather than getting one
1350
 
    # revision at a time.
1351
 
    from bzrlib.merge import merge
1352
 
 
1353
 
    assert isinstance(branch_from, Branch)
1354
 
    assert isinstance(to_location, basestring)
1355
 
    
1356
 
    br_to = Branch.initialize(to_location)
1357
 
    mutter("copy branch from %s to %s", branch_from, br_to)
1358
 
    if basis_branch is not None:
1359
 
        basis_branch.push_stores(br_to)
1360
 
    br_to.set_root_id(branch_from.get_root_id())
1361
 
    if revision is None:
1362
 
        revision = branch_from.last_revision()
1363
 
    br_to.update_revisions(branch_from, stop_revision=revision)
1364
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1365
 
          check_clean=False, ignore_zero=True)
1366
 
    br_to.set_parent(branch_from.base)
1367
 
    mutter("copied")
1368
 
    return br_to