~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-09-30 02:54:51 UTC
  • mfrom: (1395)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050930025451-47b9e412202be44b
symlink and weaves, whaddya know

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import sys
19
19
import os
 
20
import errno
 
21
from warnings import warn
 
22
 
20
23
 
21
24
import bzrlib
22
25
from bzrlib.trace import mutter, note
23
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     rename, splitpath, sha_file, appendpath, file_kind
25
 
 
26
 
from bzrlib.store import copy_all
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError, UnlistableStore, UnlistableBranch
 
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
 
27
                            rename, splitpath, sha_file, appendpath, 
 
28
                            file_kind)
 
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
30
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
31
                           DivergedBranches, LockError, UnlistableStore,
 
32
                           UnlistableBranch)
29
33
from bzrlib.textui import show_status
30
 
from bzrlib.revision import Revision, is_ancestor
 
34
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
31
35
from bzrlib.delta import compare_trees
32
36
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
 
37
from bzrlib.inventory import Inventory
 
38
from bzrlib.weavestore import WeaveStore
 
39
from bzrlib.store import copy_all, ImmutableStore
 
40
import bzrlib.xml5
34
41
import bzrlib.ui
35
42
 
36
43
 
37
 
 
38
 
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
44
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
45
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
39
46
## TODO: Maybe include checks for common corruption of newlines, etc?
40
47
 
41
48
 
42
49
# TODO: Some operations like log might retrieve the same revisions
43
50
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
 
# cache in memory to make this faster.
 
51
# cache in memory to make this faster.  In general anything can be
 
52
# cached in memory between lock and unlock operations.
45
53
 
46
54
def find_branch(*ignored, **ignored_too):
47
55
    # XXX: leave this here for about one release, then remove it
180
188
    _lock_mode = None
181
189
    _lock_count = None
182
190
    _lock = None
183
 
 
184
 
    def __init__(self, base, init=False, find_root=True):
 
191
    _inventory_weave = None
 
192
    
 
193
    # Map some sort of prefix into a namespace
 
194
    # stuff like "revno:10", "revid:", etc.
 
195
    # This should match a prefix with a function which accepts
 
196
    REVISION_NAMESPACES = {}
 
197
 
 
198
    def push_stores(self, branch_to):
 
199
        """Copy the content of this branches store to branch_to."""
 
200
        if (self._branch_format != branch_to._branch_format
 
201
            or self._branch_format != 4):
 
202
            from bzrlib.fetch import greedy_fetch
 
203
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
204
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
205
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
206
                         revision=self.last_revision())
 
207
            return
 
208
 
 
209
        store_pairs = ((self.text_store,      branch_to.text_store),
 
210
                       (self.inventory_store, branch_to.inventory_store),
 
211
                       (self.revision_store,  branch_to.revision_store))
 
212
        try:
 
213
            for from_store, to_store in store_pairs: 
 
214
                copy_all(from_store, to_store)
 
215
        except UnlistableStore:
 
216
            raise UnlistableBranch(from_store)
 
217
 
 
218
    def __init__(self, base, init=False, find_root=True,
 
219
                 relax_version_check=False):
185
220
        """Create new branch object at a particular location.
186
221
 
187
222
        base -- Base directory for the branch. May be a file:// url.
193
228
        find_root -- If true and init is false, find the root of the
194
229
             existing branch containing base.
195
230
 
 
231
        relax_version_check -- If true, the usual check for the branch
 
232
            version is not applied.  This is intended only for
 
233
            upgrade/recovery type use; it's not guaranteed that
 
234
            all operations will work on old format branches.
 
235
 
196
236
        In the test suite, creation of new trees is tested using the
197
237
        `ScratchBranch` class.
198
238
        """
199
 
        from bzrlib.store import ImmutableStore
200
239
        if init:
201
240
            self.base = os.path.realpath(base)
202
241
            self._make_control()
207
246
                base = base[7:]
208
247
            self.base = os.path.realpath(base)
209
248
            if not isdir(self.controlfilename('.')):
210
 
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
211
 
                                     ['use "bzr init" to initialize a new working tree',
212
 
                                      'current bzr can only operate from top-of-tree'])
213
 
        self._check_format()
214
 
 
215
 
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
216
 
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
217
 
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
 
249
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
 
250
                                     ['use "bzr init" to initialize a '
 
251
                                      'new working tree'])
 
252
        self._check_format(relax_version_check)
 
253
        cfn = self.controlfilename
 
254
        if self._branch_format == 4:
 
255
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
 
256
            self.text_store = ImmutableStore(cfn('text-store'))
 
257
        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'))
218
265
 
219
266
 
220
267
    def __str__(self):
226
273
 
227
274
    def __del__(self):
228
275
        if self._lock_mode or self._lock:
229
 
            from bzrlib.warnings import warn
 
276
            # XXX: This should show something every time, and be suitable for
 
277
            # headless operation and embedding
230
278
            warn("branch %r was not explicitly unlocked" % self)
231
279
            self._lock.unlock()
232
280
 
233
281
    def lock_write(self):
234
282
        if self._lock_mode:
235
283
            if self._lock_mode != 'w':
236
 
                from bzrlib.errors import LockError
237
284
                raise LockError("can't upgrade to a write lock from %r" %
238
285
                                self._lock_mode)
239
286
            self._lock_count += 1
259
306
                        
260
307
    def unlock(self):
261
308
        if not self._lock_mode:
262
 
            from bzrlib.errors import LockError
263
309
            raise LockError('branch %r is not locked' % (self))
264
310
 
265
311
        if self._lock_count > 1:
312
358
            raise BzrError("invalid controlfile mode %r" % mode)
313
359
 
314
360
    def _make_control(self):
315
 
        from bzrlib.inventory import Inventory
316
 
        
317
361
        os.mkdir(self.controlfilename([]))
318
362
        self.controlfile('README', 'w').write(
319
363
            "This is a Bazaar-NG control directory.\n"
320
364
            "Do not change any files in this directory.\n")
321
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
322
 
        for d in ('text-store', 'inventory-store', 'revision-store'):
 
365
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
 
366
        for d in ('text-store', 'revision-store',
 
367
                  'weaves'):
323
368
            os.mkdir(self.controlfilename(d))
324
 
        for f in ('revision-history', 'merged-patches',
325
 
                  'pending-merged-patches', 'branch-name',
 
369
        for f in ('revision-history',
 
370
                  'branch-name',
326
371
                  'branch-lock',
327
372
                  'pending-merges'):
328
373
            self.controlfile(f, 'w').write('')
332
377
        # them; they're not needed for now and so ommitted for
333
378
        # simplicity.
334
379
        f = self.controlfile('inventory','w')
335
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
336
 
 
337
 
 
338
 
    def _check_format(self):
 
380
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
 
381
 
 
382
 
 
383
    def _check_format(self, relax_version_check):
339
384
        """Check this branch format is supported.
340
385
 
341
 
        The current tool only supports the current unstable format.
 
386
        The format level is stored, as an integer, in
 
387
        self._branch_format for code that needs to check it later.
342
388
 
343
389
        In the future, we might need different in-memory Branch
344
390
        classes to support downlevel branches.  But not yet.
345
391
        """
346
 
        # This ignores newlines so that we can open branches created
347
 
        # on Windows from Linux and so on.  I think it might be better
348
 
        # to always make all internal files in unix format.
349
 
        fmt = self.controlfile('branch-format', 'r').read()
350
 
        fmt = fmt.replace('\r\n', '\n')
351
 
        if fmt != BZR_BRANCH_FORMAT:
 
392
        try:
 
393
            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
 
402
 
 
403
        if fmt == BZR_BRANCH_FORMAT_5:
 
404
            self._branch_format = 5
 
405
        elif fmt == BZR_BRANCH_FORMAT_4:
 
406
            self._branch_format = 4
 
407
 
 
408
        if (not relax_version_check
 
409
            and self._branch_format != 5):
352
410
            raise BzrError('sorry, branch format %r not supported' % fmt,
353
411
                           ['use a different bzr version',
354
412
                            'or remove the .bzr directory and "bzr init" again'])
372
430
 
373
431
    def read_working_inventory(self):
374
432
        """Read the working inventory."""
375
 
        from bzrlib.inventory import Inventory
376
433
        self.lock_read()
377
434
        try:
378
435
            # ElementTree does its own conversion from UTF-8, so open in
379
436
            # binary.
380
437
            f = self.controlfile('inventory', 'rb')
381
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
 
438
            return bzrlib.xml5.serializer_v5.read_inventory(f)
382
439
        finally:
383
440
            self.unlock()
384
441
            
395
452
        try:
396
453
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
397
454
            try:
398
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
 
455
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
399
456
                f.commit()
400
457
            finally:
401
458
                f.close()
581
638
        finally:
582
639
            f.close()
583
640
 
 
641
    def has_revision(self, revision_id):
 
642
        """True if this branch has a copy of the revision.
 
643
 
 
644
        This does not necessarily imply the revision is merge
 
645
        or on the mainline."""
 
646
        return (revision_id is None
 
647
                or revision_id in self.revision_store)
 
648
 
584
649
    def get_revision_xml_file(self, revision_id):
585
650
        """Return XML file object for revision object."""
586
651
        if not revision_id or not isinstance(revision_id, basestring):
598
663
    #deprecated
599
664
    get_revision_xml = get_revision_xml_file
600
665
 
601
 
    #deprecated
602
 
    get_revision_xml = get_revision_xml_file
 
666
    def get_revision_xml(self, revision_id):
 
667
        return self.get_revision_xml_file(revision_id).read()
603
668
 
604
669
 
605
670
    def get_revision(self, revision_id):
607
672
        xml_file = self.get_revision_xml_file(revision_id)
608
673
 
609
674
        try:
610
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
675
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
611
676
        except SyntaxError, e:
612
677
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
613
678
                                         [revision_id,
645
710
        # the revision, (add signatures/remove signatures) and still
646
711
        # have all hash pointers stay consistent.
647
712
        # But for now, just hash the contents.
648
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
649
 
 
650
 
    def get_inventory(self, inventory_id):
651
 
        """Get Inventory object by hash.
652
 
 
653
 
        TODO: Perhaps for this and similar methods, take a revision
654
 
               parameter which can be either an integer revno or a
655
 
               string hash."""
656
 
        from bzrlib.inventory import Inventory
657
 
        f = self.get_inventory_xml_file(inventory_id)
658
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
659
 
 
660
 
    def get_inventory_xml(self, inventory_id):
 
713
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
714
 
 
715
    def _get_ancestry_weave(self):
 
716
        return self.control_weaves.get_weave('ancestry')
 
717
 
 
718
    def get_ancestry(self, revision_id):
 
719
        """Return a list of revision-ids integrated by a revision.
 
720
        """
 
721
        # strip newlines
 
722
        if revision_id is None:
 
723
            return [None]
 
724
        w = self._get_ancestry_weave()
 
725
        return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
 
726
 
 
727
    def get_inventory_weave(self):
 
728
        return self.control_weaves.get_weave('inventory')
 
729
 
 
730
    def get_inventory(self, revision_id):
 
731
        """Get Inventory object by hash."""
 
732
        xml = self.get_inventory_xml(revision_id)
 
733
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
734
 
 
735
    def get_inventory_xml(self, revision_id):
661
736
        """Get inventory XML as a file object."""
662
 
        return self.inventory_store[inventory_id]
 
737
        try:
 
738
            assert isinstance(revision_id, basestring), type(revision_id)
 
739
            iw = self.get_inventory_weave()
 
740
            return iw.get_text(iw.lookup(revision_id))
 
741
        except IndexError:
 
742
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
663
743
 
664
 
    get_inventory_xml_file = get_inventory_xml
665
 
            
666
 
    def get_inventory_sha1(self, inventory_id):
 
744
    def get_inventory_sha1(self, revision_id):
667
745
        """Return the sha1 hash of the inventory entry
668
746
        """
669
 
        return sha_file(self.get_inventory_xml(inventory_id))
 
747
        return self.get_revision(revision_id).inventory_sha1
670
748
 
671
749
    def get_revision_inventory(self, revision_id):
672
750
        """Return inventory of a past revision."""
673
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
 
751
        # TODO: Unify this with get_inventory()
 
752
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
674
753
        # must be the same as its revision, so this is trivial.
675
754
        if revision_id == None:
676
 
            from bzrlib.inventory import Inventory
677
755
            return Inventory(self.get_root_id())
678
756
        else:
679
757
            return self.get_inventory(revision_id)
680
758
 
681
759
    def revision_history(self):
682
 
        """Return sequence of revision hashes on to this branch.
683
 
 
684
 
        >>> ScratchBranch().revision_history()
685
 
        []
686
 
        """
 
760
        """Return sequence of revision hashes on to this branch."""
687
761
        self.lock_read()
688
762
        try:
689
763
            return [l.rstrip('\r\n') for l in
745
819
        return len(self.revision_history())
746
820
 
747
821
 
748
 
    def last_patch(self):
 
822
    def last_revision(self):
749
823
        """Return last patch hash, or None if no history.
750
824
        """
751
825
        ph = self.revision_history()
756
830
 
757
831
 
758
832
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
759
 
        """
 
833
        """Return a list of new revisions that would perfectly fit.
 
834
        
760
835
        If self and other have not diverged, return a list of the revisions
761
836
        present in other, but missing from self.
762
837
 
782
857
        Traceback (most recent call last):
783
858
        DivergedBranches: These branches have diverged.
784
859
        """
 
860
        # FIXME: If the branches have diverged, but the latest
 
861
        # revision in this branch is completely merged into the other,
 
862
        # then we should still be able to pull.
785
863
        self_history = self.revision_history()
786
864
        self_len = len(self_history)
787
865
        other_history = other.revision_history()
793
871
 
794
872
        if stop_revision is None:
795
873
            stop_revision = other_len
796
 
        elif stop_revision > other_len:
797
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
798
 
        
 
874
        else:
 
875
            assert isinstance(stop_revision, int)
 
876
            if stop_revision > other_len:
 
877
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
799
878
        return other_history[self_len:stop_revision]
800
879
 
801
 
 
802
880
    def update_revisions(self, other, stop_revision=None):
803
 
        """Pull in all new revisions from other branch.
804
 
        """
 
881
        """Pull in new perfect-fit revisions."""
805
882
        from bzrlib.fetch import greedy_fetch
806
883
        from bzrlib.revision import get_intervening_revisions
807
 
 
808
 
        pb = bzrlib.ui.ui_factory.progress_bar()
809
 
        pb.update('comparing histories')
810
884
        if stop_revision is None:
811
 
            other_revision = other.last_patch()
812
 
        else:
813
 
            other_revision = other.get_rev_id(stop_revision)
814
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
815
 
        try:
816
 
            revision_ids = self.missing_revisions(other, stop_revision)
817
 
        except DivergedBranches, e:
818
 
            try:
819
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
820
 
                                                         other_revision, self)
821
 
                assert self.last_patch() not in revision_ids
822
 
            except bzrlib.errors.NotAncestor:
823
 
                if is_ancestor(self.last_patch(), other_revision, self):
824
 
                    revision_ids = []
825
 
                else:
826
 
                    raise e
827
 
        self.append_revision(*revision_ids)
828
 
        pb.clear()
829
 
 
830
 
    def install_revisions(self, other, revision_ids, pb):
831
 
        if hasattr(other.revision_store, "prefetch"):
832
 
            other.revision_store.prefetch(revision_ids)
833
 
        if hasattr(other.inventory_store, "prefetch"):
834
 
            inventory_ids = []
835
 
            for rev_id in revision_ids:
836
 
                try:
837
 
                    revision = other.get_revision(rev_id).inventory_id
838
 
                    inventory_ids.append(revision)
839
 
                except bzrlib.errors.NoSuchRevision:
840
 
                    pass
841
 
            other.inventory_store.prefetch(inventory_ids)
842
 
 
843
 
        if pb is None:
844
 
            pb = bzrlib.ui.ui_factory.progress_bar()
845
 
                
846
 
        revisions = []
847
 
        needed_texts = set()
848
 
        i = 0
849
 
 
850
 
        failures = set()
851
 
        for i, rev_id in enumerate(revision_ids):
852
 
            pb.update('fetching revision', i+1, len(revision_ids))
853
 
            try:
854
 
                rev = other.get_revision(rev_id)
855
 
            except bzrlib.errors.NoSuchRevision:
856
 
                failures.add(rev_id)
857
 
                continue
858
 
 
859
 
            revisions.append(rev)
860
 
            inv = other.get_inventory(str(rev.inventory_id))
861
 
            for key, entry in inv.iter_entries():
862
 
                if entry.text_id is None:
863
 
                    continue
864
 
                if entry.text_id not in self.text_store:
865
 
                    needed_texts.add(entry.text_id)
866
 
 
867
 
        pb.clear()
868
 
                    
869
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
870
 
                                                    needed_texts)
871
 
        #print "Added %d texts." % count 
872
 
        inventory_ids = [ f.inventory_id for f in revisions ]
873
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
874
 
                                                         inventory_ids)
875
 
        #print "Added %d inventories." % count 
876
 
        revision_ids = [ f.revision_id for f in revisions]
877
 
 
878
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
879
 
                                                          revision_ids,
880
 
                                                          permit_failure=True)
881
 
        assert len(cp_fail) == 0 
882
 
        return count, failures
883
 
       
 
885
            stop_revision = other.last_revision()
 
886
        greedy_fetch(to_branch=self, from_branch=other,
 
887
                     revision=stop_revision)
 
888
        pullable_revs = self.missing_revisions(
 
889
            other, other.revision_id_to_revno(stop_revision))
 
890
        if pullable_revs:
 
891
            greedy_fetch(to_branch=self,
 
892
                         from_branch=other,
 
893
                         revision=pullable_revs[-1])
 
894
            self.append_revision(*pullable_revs)
 
895
    
884
896
 
885
897
    def commit(self, *args, **kw):
886
 
        from bzrlib.commit import commit
887
 
        commit(self, *args, **kw)
888
 
        
 
898
        from bzrlib.commit import Commit
 
899
        Commit().commit(self, *args, **kw)
 
900
    
889
901
    def revision_id_to_revno(self, revision_id):
890
902
        """Given a revision id, return its revno"""
 
903
        if revision_id is None:
 
904
            return 0
891
905
        history = self.revision_history()
892
906
        try:
893
907
            return history.index(revision_id) + 1
915
929
            return EmptyTree()
916
930
        else:
917
931
            inv = self.get_revision_inventory(revision_id)
918
 
            return RevisionTree(self.text_store, inv)
 
932
            return RevisionTree(self.weave_store, inv, revision_id)
919
933
 
920
934
 
921
935
    def working_tree(self):
929
943
 
930
944
        If there are no revisions yet, return an `EmptyTree`.
931
945
        """
932
 
        r = self.last_patch()
933
 
        if r == None:
934
 
            return EmptyTree()
935
 
        else:
936
 
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
937
 
 
 
946
        return self.revision_tree(self.last_revision())
938
947
 
939
948
 
940
949
    def rename_one(self, from_rel, to_rel):
1116
1125
 
1117
1126
 
1118
1127
    def add_pending_merge(self, revision_id):
1119
 
        from bzrlib.revision import validate_revision_id
1120
 
 
1121
1128
        validate_revision_id(revision_id)
1122
 
 
 
1129
        # TODO: Perhaps should check at this point that the
 
1130
        # history of the revision is actually present?
1123
1131
        p = self.pending_merges()
1124
1132
        if revision_id in p:
1125
1133
            return
1320
1328
    return gen_file_id('TREE_ROOT')
1321
1329
 
1322
1330
 
1323
 
def copy_branch(branch_from, to_location, revno=None, basis_branch=None):
 
1331
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
1324
1332
    """Copy branch_from into the existing directory to_location.
1325
1333
 
1326
1334
    revision
1327
1335
        If not None, only revisions up to this point will be copied.
1328
 
        The head of the new branch will be that revision.
 
1336
        The head of the new branch will be that revision.  Must be a
 
1337
        revid or None.
1329
1338
 
1330
1339
    to_location
1331
1340
        The name of a local directory that exists but is empty.
1336
1345
    basis_branch
1337
1346
        A local branch to copy revisions from, related to branch_from
1338
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.
1339
1351
    from bzrlib.merge import merge
1340
1352
 
1341
1353
    assert isinstance(branch_from, Branch)
1342
1354
    assert isinstance(to_location, basestring)
1343
1355
    
1344
1356
    br_to = Branch.initialize(to_location)
 
1357
    mutter("copy branch from %s to %s", branch_from, br_to)
1345
1358
    if basis_branch is not None:
1346
 
        copy_stores(basis_branch, br_to)
 
1359
        basis_branch.push_stores(br_to)
1347
1360
    br_to.set_root_id(branch_from.get_root_id())
1348
 
    if revno is None:
1349
 
        revno = branch_from.revno()
1350
 
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1361
    if revision is None:
 
1362
        revision = branch_from.last_revision()
 
1363
    br_to.update_revisions(branch_from, stop_revision=revision)
1351
1364
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1352
1365
          check_clean=False, ignore_zero=True)
1353
1366
    br_to.set_parent(branch_from.base)
 
1367
    mutter("copied")
1354
1368
    return br_to
1355
 
 
1356
 
def copy_stores(branch_from, branch_to):
1357
 
    """Copies all entries from branch stores to another branch's stores.
1358
 
    """
1359
 
    store_pairs = ((branch_from.text_store,      branch_to.text_store),
1360
 
                   (branch_from.inventory_store, branch_to.inventory_store),
1361
 
                   (branch_from.revision_store,  branch_to.revision_store))
1362
 
    try:
1363
 
        for from_store, to_store in store_pairs: 
1364
 
            copy_all(from_store, to_store)
1365
 
    except UnlistableStore:
1366
 
        raise UnlistableBranch(from_store)