~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-09-28 05:37:53 UTC
  • mfrom: (1092.3.4)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050928053753-68e6e4c0642eccea
merge from symlink branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import bzrlib
22
22
from bzrlib.trace import mutter, note
23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     splitpath, \
25
 
     sha_file, appendpath, file_kind
 
24
     rename, splitpath, sha_file, appendpath, file_kind
26
25
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
 
import bzrlib.errors
 
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
 
27
     DivergedBranches, NotBranchError
29
28
from bzrlib.textui import show_status
30
29
from bzrlib.revision import Revision
31
30
from bzrlib.delta import compare_trees
43
42
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
43
# cache in memory to make this faster.
45
44
 
46
 
# TODO: please move the revision-string syntax stuff out of the branch
47
 
# object; it's clutter
48
 
 
49
 
 
50
 
def find_branch(f, **args):
51
 
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        import remotebranch 
53
 
        return remotebranch.RemoteBranch(f, **args)
54
 
    else:
55
 
        return Branch(f, **args)
56
 
 
57
 
 
58
 
def find_cached_branch(f, cache_root, **args):
59
 
    from remotebranch import RemoteBranch
60
 
    br = find_branch(f, **args)
61
 
    def cacheify(br, store_name):
62
 
        from store import CachedStore
63
 
        cache_path = os.path.join(cache_root, store_name)
64
 
        os.mkdir(cache_path)
65
 
        new_store = CachedStore(getattr(br, store_name), cache_path)
66
 
        setattr(br, store_name, new_store)
67
 
 
68
 
    if isinstance(br, RemoteBranch):
69
 
        cacheify(br, 'inventory_store')
70
 
        cacheify(br, 'text_store')
71
 
        cacheify(br, 'revision_store')
72
 
    return br
73
 
 
 
45
def find_branch(*ignored, **ignored_too):
 
46
    # XXX: leave this here for about one release, then remove it
 
47
    raise NotImplementedError('find_branch() is not supported anymore, '
 
48
                              'please use one of the new branch constructors')
74
49
 
75
50
def _relpath(base, path):
76
51
    """Return path relative to base, or raise exception.
94
69
        if tail:
95
70
            s.insert(0, tail)
96
71
    else:
97
 
        from errors import NotBranchError
98
72
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
99
73
 
100
74
    return os.sep.join(s)
116
90
        f = bzrlib.osutils.normalizepath(f)
117
91
    if not bzrlib.osutils.lexists(f):
118
92
        raise BzrError('%r does not exist' % f)
119
 
        
120
93
 
121
94
    orig_f = f
122
95
 
126
99
        head, tail = os.path.split(f)
127
100
        if head == f:
128
101
            # reached the root, whatever that may be
129
 
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
 
102
            raise NotBranchError('%s is not in a branch' % orig_f)
130
103
        f = head
131
104
 
132
105
 
133
106
 
134
 
# XXX: move into bzrlib.errors; subclass BzrError    
135
 
class DivergedBranches(Exception):
136
 
    def __init__(self, branch1, branch2):
137
 
        self.branch1 = branch1
138
 
        self.branch2 = branch2
139
 
        Exception.__init__(self, "These branches have diverged.")
140
 
 
141
107
 
142
108
######################################################################
143
109
# branch objects
146
112
    """Branch holding a history of revisions.
147
113
 
148
114
    base
149
 
        Base directory of the branch.
 
115
        Base directory/url of the branch.
 
116
    """
 
117
    base = None
 
118
 
 
119
    def __init__(self, *ignored, **ignored_too):
 
120
        raise NotImplementedError('The Branch class is abstract')
 
121
 
 
122
    @staticmethod
 
123
    def open(base):
 
124
        """Open an existing branch, rooted at 'base' (url)"""
 
125
        if base and (base.startswith('http://') or base.startswith('https://')):
 
126
            from bzrlib.remotebranch import RemoteBranch
 
127
            return RemoteBranch(base, find_root=False)
 
128
        else:
 
129
            return LocalBranch(base, find_root=False)
 
130
 
 
131
    @staticmethod
 
132
    def open_containing(url):
 
133
        """Open an existing branch which contains url.
 
134
        
 
135
        This probes for a branch at url, and searches upwards from there.
 
136
        """
 
137
        if url and (url.startswith('http://') or url.startswith('https://')):
 
138
            from bzrlib.remotebranch import RemoteBranch
 
139
            return RemoteBranch(url)
 
140
        else:
 
141
            return LocalBranch(url)
 
142
 
 
143
    @staticmethod
 
144
    def initialize(base):
 
145
        """Create a new branch, rooted at 'base' (url)"""
 
146
        if base and (base.startswith('http://') or base.startswith('https://')):
 
147
            from bzrlib.remotebranch import RemoteBranch
 
148
            return RemoteBranch(base, init=True)
 
149
        else:
 
150
            return LocalBranch(base, init=True)
 
151
 
 
152
    def setup_caching(self, cache_root):
 
153
        """Subclasses that care about caching should override this, and set
 
154
        up cached stores located under cache_root.
 
155
        """
 
156
 
 
157
 
 
158
class LocalBranch(Branch):
 
159
    """A branch stored in the actual filesystem.
 
160
 
 
161
    Note that it's "local" in the context of the filesystem; it doesn't
 
162
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
163
    it's writable, and can be accessed via the normal filesystem API.
150
164
 
151
165
    _lock_mode
152
166
        None, or 'r' or 'w'
158
172
    _lock
159
173
        Lock object from bzrlib.lock.
160
174
    """
161
 
    base = None
 
175
    # We actually expect this class to be somewhat short-lived; part of its
 
176
    # purpose is to try to isolate what bits of the branch logic are tied to
 
177
    # filesystem access, so that in a later step, we can extricate them to
 
178
    # a separarte ("storage") class.
162
179
    _lock_mode = None
163
180
    _lock_count = None
164
181
    _lock = None
165
 
    
166
 
    # Map some sort of prefix into a namespace
167
 
    # stuff like "revno:10", "revid:", etc.
168
 
    # This should match a prefix with a function which accepts
169
 
    REVISION_NAMESPACES = {}
170
182
 
171
183
    def __init__(self, base, init=False, find_root=True):
172
184
        """Create new branch object at a particular location.
194
206
                base = base[7:]
195
207
            self.base = os.path.realpath(base)
196
208
            if not isdir(self.controlfilename('.')):
197
 
                from errors import NotBranchError
198
209
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
199
210
                                     ['use "bzr init" to initialize a new working tree',
200
211
                                      'current bzr can only operate from top-of-tree'])
214
225
 
215
226
    def __del__(self):
216
227
        if self._lock_mode or self._lock:
217
 
            from warnings import warn
 
228
            from bzrlib.warnings import warn
218
229
            warn("branch %r was not explicitly unlocked" % self)
219
230
            self._lock.unlock()
220
231
 
221
232
    def lock_write(self):
222
233
        if self._lock_mode:
223
234
            if self._lock_mode != 'w':
224
 
                from errors import LockError
 
235
                from bzrlib.errors import LockError
225
236
                raise LockError("can't upgrade to a write lock from %r" %
226
237
                                self._lock_mode)
227
238
            self._lock_count += 1
247
258
                        
248
259
    def unlock(self):
249
260
        if not self._lock_mode:
250
 
            from errors import LockError
 
261
            from bzrlib.errors import LockError
251
262
            raise LockError('branch %r is not locked' % (self))
252
263
 
253
264
        if self._lock_count > 1:
468
479
        """Print `file` to stdout."""
469
480
        self.lock_read()
470
481
        try:
471
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
482
            tree = self.revision_tree(self.get_rev_id(revno))
472
483
            # use inventory as it was in that revision
473
484
            file_id = tree.inventory.path2id(file)
474
485
            if not file_id:
578
589
        try:
579
590
            try:
580
591
                return self.revision_store[revision_id]
581
 
            except IndexError:
 
592
            except (IndexError, KeyError):
582
593
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
583
594
        finally:
584
595
            self.unlock()
586
597
    #deprecated
587
598
    get_revision_xml = get_revision_xml_file
588
599
 
 
600
    #deprecated
 
601
    get_revision_xml = get_revision_xml_file
 
602
 
 
603
 
589
604
    def get_revision(self, revision_id):
590
605
        """Return the Revision object for a named revision"""
591
606
        xml_file = self.get_revision_xml_file(revision_id)
677
692
 
678
693
    def common_ancestor(self, other, self_revno=None, other_revno=None):
679
694
        """
680
 
        >>> import commit
 
695
        >>> from bzrlib.commit import commit
681
696
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
682
697
        >>> sb.common_ancestor(sb) == (None, None)
683
698
        True
684
 
        >>> commit.commit(sb, "Committing first revision", verbose=False)
 
699
        >>> commit(sb, "Committing first revision", verbose=False)
685
700
        >>> sb.common_ancestor(sb)[0]
686
701
        1
687
702
        >>> clone = sb.clone()
688
 
        >>> commit.commit(sb, "Committing second revision", verbose=False)
 
703
        >>> commit(sb, "Committing second revision", verbose=False)
689
704
        >>> sb.common_ancestor(sb)[0]
690
705
        2
691
706
        >>> sb.common_ancestor(clone)[0]
692
707
        1
693
 
        >>> commit.commit(clone, "Committing divergent second revision", 
 
708
        >>> commit(clone, "Committing divergent second revision", 
694
709
        ...               verbose=False)
695
710
        >>> sb.common_ancestor(clone)[0]
696
711
        1
791
806
 
792
807
        pb = bzrlib.ui.ui_factory.progress_bar()
793
808
        pb.update('comparing histories')
794
 
 
 
809
        if stop_revision is None:
 
810
            other_revision = other.last_patch()
 
811
        else:
 
812
            other_revision = other.get_rev_id(stop_revision)
 
813
        count = greedy_fetch(self, other, other_revision, pb)[0]
795
814
        try:
796
815
            revision_ids = self.missing_revisions(other, stop_revision)
797
816
        except DivergedBranches, e:
798
817
            try:
799
 
                if stop_revision is None:
800
 
                    end_revision = other.last_patch()
801
818
                revision_ids = get_intervening_revisions(self.last_patch(), 
802
 
                                                         end_revision, other)
 
819
                                                         other_revision, self)
803
820
                assert self.last_patch() not in revision_ids
804
821
            except bzrlib.errors.NotAncestor:
805
822
                raise e
806
823
 
807
 
        if len(revision_ids) > 0:
808
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
809
 
        else:
810
 
            count = 0
811
824
        self.append_revision(*revision_ids)
812
 
        ## note("Added %d revisions." % count)
813
825
        pb.clear()
814
826
 
815
827
    def install_revisions(self, other, revision_ids, pb):
816
828
        if hasattr(other.revision_store, "prefetch"):
817
829
            other.revision_store.prefetch(revision_ids)
818
830
        if hasattr(other.inventory_store, "prefetch"):
819
 
            inventory_ids = [other.get_revision(r).inventory_id
820
 
                             for r in revision_ids]
 
831
            inventory_ids = []
 
832
            for rev_id in revision_ids:
 
833
                try:
 
834
                    revision = other.get_revision(rev_id).inventory_id
 
835
                    inventory_ids.append(revision)
 
836
                except bzrlib.errors.NoSuchRevision:
 
837
                    pass
821
838
            other.inventory_store.prefetch(inventory_ids)
822
839
 
823
840
        if pb is None:
866
883
        from bzrlib.commit import commit
867
884
        commit(self, *args, **kw)
868
885
        
869
 
 
870
 
    def lookup_revision(self, revision):
871
 
        """Return the revision identifier for a given revision information."""
872
 
        revno, info = self._get_revision_info(revision)
873
 
        return info
874
 
 
875
 
 
876
886
    def revision_id_to_revno(self, revision_id):
877
887
        """Given a revision id, return its revno"""
878
888
        history = self.revision_history()
881
891
        except ValueError:
882
892
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
883
893
 
884
 
 
885
 
    def get_revision_info(self, revision):
886
 
        """Return (revno, revision id) for revision identifier.
887
 
 
888
 
        revision can be an integer, in which case it is assumed to be revno (though
889
 
            this will translate negative values into positive ones)
890
 
        revision can also be a string, in which case it is parsed for something like
891
 
            'date:' or 'revid:' etc.
892
 
        """
893
 
        revno, rev_id = self._get_revision_info(revision)
894
 
        if revno is None:
895
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
896
 
        return revno, rev_id
897
 
 
898
894
    def get_rev_id(self, revno, history=None):
899
895
        """Find the revision id of the specified revno."""
900
896
        if revno == 0:
905
901
            raise bzrlib.errors.NoSuchRevision(self, revno)
906
902
        return history[revno - 1]
907
903
 
908
 
    def _get_revision_info(self, revision):
909
 
        """Return (revno, revision id) for revision specifier.
910
 
 
911
 
        revision can be an integer, in which case it is assumed to be revno
912
 
        (though this will translate negative values into positive ones)
913
 
        revision can also be a string, in which case it is parsed for something
914
 
        like 'date:' or 'revid:' etc.
915
 
 
916
 
        A revid is always returned.  If it is None, the specifier referred to
917
 
        the null revision.  If the revid does not occur in the revision
918
 
        history, revno will be None.
919
 
        """
920
 
        
921
 
        if revision is None:
922
 
            return 0, None
923
 
        revno = None
924
 
        try:# Convert to int if possible
925
 
            revision = int(revision)
926
 
        except ValueError:
927
 
            pass
928
 
        revs = self.revision_history()
929
 
        if isinstance(revision, int):
930
 
            if revision < 0:
931
 
                revno = len(revs) + revision + 1
932
 
            else:
933
 
                revno = revision
934
 
            rev_id = self.get_rev_id(revno, revs)
935
 
        elif isinstance(revision, basestring):
936
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
937
 
                if revision.startswith(prefix):
938
 
                    result = func(self, revs, revision)
939
 
                    if len(result) > 1:
940
 
                        revno, rev_id = result
941
 
                    else:
942
 
                        revno = result[0]
943
 
                        rev_id = self.get_rev_id(revno, revs)
944
 
                    break
945
 
            else:
946
 
                raise BzrError('No namespace registered for string: %r' %
947
 
                               revision)
948
 
        else:
949
 
            raise TypeError('Unhandled revision type %s' % revision)
950
 
 
951
 
        if revno is None:
952
 
            if rev_id is None:
953
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
954
 
        return revno, rev_id
955
 
 
956
 
    def _namespace_revno(self, revs, revision):
957
 
        """Lookup a revision by revision number"""
958
 
        assert revision.startswith('revno:')
959
 
        try:
960
 
            return (int(revision[6:]),)
961
 
        except ValueError:
962
 
            return None
963
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
964
 
 
965
 
    def _namespace_revid(self, revs, revision):
966
 
        assert revision.startswith('revid:')
967
 
        rev_id = revision[len('revid:'):]
968
 
        try:
969
 
            return revs.index(rev_id) + 1, rev_id
970
 
        except ValueError:
971
 
            return None, rev_id
972
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
973
 
 
974
 
    def _namespace_last(self, revs, revision):
975
 
        assert revision.startswith('last:')
976
 
        try:
977
 
            offset = int(revision[5:])
978
 
        except ValueError:
979
 
            return (None,)
980
 
        else:
981
 
            if offset <= 0:
982
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
983
 
            return (len(revs) - offset + 1,)
984
 
    REVISION_NAMESPACES['last:'] = _namespace_last
985
 
 
986
 
    def _namespace_tag(self, revs, revision):
987
 
        assert revision.startswith('tag:')
988
 
        raise BzrError('tag: namespace registered, but not implemented.')
989
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
990
 
 
991
 
    def _namespace_date(self, revs, revision):
992
 
        assert revision.startswith('date:')
993
 
        import datetime
994
 
        # Spec for date revisions:
995
 
        #   date:value
996
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
997
 
        #   it can also start with a '+/-/='. '+' says match the first
998
 
        #   entry after the given date. '-' is match the first entry before the date
999
 
        #   '=' is match the first entry after, but still on the given date.
1000
 
        #
1001
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1002
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1003
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1004
 
        #       May 13th, 2005 at 0:00
1005
 
        #
1006
 
        #   So the proper way of saying 'give me all entries for today' is:
1007
 
        #       -r {date:+today}:{date:-tomorrow}
1008
 
        #   The default is '=' when not supplied
1009
 
        val = revision[5:]
1010
 
        match_style = '='
1011
 
        if val[:1] in ('+', '-', '='):
1012
 
            match_style = val[:1]
1013
 
            val = val[1:]
1014
 
 
1015
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1016
 
        if val.lower() == 'yesterday':
1017
 
            dt = today - datetime.timedelta(days=1)
1018
 
        elif val.lower() == 'today':
1019
 
            dt = today
1020
 
        elif val.lower() == 'tomorrow':
1021
 
            dt = today + datetime.timedelta(days=1)
1022
 
        else:
1023
 
            import re
1024
 
            # This should be done outside the function to avoid recompiling it.
1025
 
            _date_re = re.compile(
1026
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1027
 
                    r'(,|T)?\s*'
1028
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1029
 
                )
1030
 
            m = _date_re.match(val)
1031
 
            if not m or (not m.group('date') and not m.group('time')):
1032
 
                raise BzrError('Invalid revision date %r' % revision)
1033
 
 
1034
 
            if m.group('date'):
1035
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1036
 
            else:
1037
 
                year, month, day = today.year, today.month, today.day
1038
 
            if m.group('time'):
1039
 
                hour = int(m.group('hour'))
1040
 
                minute = int(m.group('minute'))
1041
 
                if m.group('second'):
1042
 
                    second = int(m.group('second'))
1043
 
                else:
1044
 
                    second = 0
1045
 
            else:
1046
 
                hour, minute, second = 0,0,0
1047
 
 
1048
 
            dt = datetime.datetime(year=year, month=month, day=day,
1049
 
                    hour=hour, minute=minute, second=second)
1050
 
        first = dt
1051
 
        last = None
1052
 
        reversed = False
1053
 
        if match_style == '-':
1054
 
            reversed = True
1055
 
        elif match_style == '=':
1056
 
            last = dt + datetime.timedelta(days=1)
1057
 
 
1058
 
        if reversed:
1059
 
            for i in range(len(revs)-1, -1, -1):
1060
 
                r = self.get_revision(revs[i])
1061
 
                # TODO: Handle timezone.
1062
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1063
 
                if first >= dt and (last is None or dt >= last):
1064
 
                    return (i+1,)
1065
 
        else:
1066
 
            for i in range(len(revs)):
1067
 
                r = self.get_revision(revs[i])
1068
 
                # TODO: Handle timezone.
1069
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1070
 
                if first <= dt and (last is None or dt <= last):
1071
 
                    return (i+1,)
1072
 
    REVISION_NAMESPACES['date:'] = _namespace_date
1073
 
 
1074
904
    def revision_tree(self, revision_id):
1075
905
        """Return Tree for a revision on this branch.
1076
906
 
1087
917
 
1088
918
    def working_tree(self):
1089
919
        """Return a `Tree` for the working copy."""
1090
 
        from workingtree import WorkingTree
 
920
        from bzrlib.workingtree import WorkingTree
1091
921
        return WorkingTree(self.base, self.read_working_inventory())
1092
922
 
1093
923
 
1142
972
            from_abs = self.abspath(from_rel)
1143
973
            to_abs = self.abspath(to_rel)
1144
974
            try:
1145
 
                os.rename(from_abs, to_abs)
 
975
                rename(from_abs, to_abs)
1146
976
            except OSError, e:
1147
977
                raise BzrError("failed to rename %r to %r: %s"
1148
978
                        % (from_abs, to_abs, e[1]),
1211
1041
                result.append((f, dest_path))
1212
1042
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1213
1043
                try:
1214
 
                    os.rename(self.abspath(f), self.abspath(dest_path))
 
1044
                    rename(self.abspath(f), self.abspath(dest_path))
1215
1045
                except OSError, e:
1216
1046
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1217
1047
                            ["rename rolled back"])
1358
1188
            raise InvalidRevisionNumber(revno)
1359
1189
        
1360
1190
        
1361
 
 
1362
 
 
1363
 
class ScratchBranch(Branch):
 
1191
        
 
1192
 
 
1193
 
 
1194
class ScratchBranch(LocalBranch):
1364
1195
    """Special test class: a branch that cleans up after itself.
1365
1196
 
1366
1197
    >>> b = ScratchBranch()
1383
1214
        if base is None:
1384
1215
            base = mkdtemp()
1385
1216
            init = True
1386
 
        Branch.__init__(self, base, init=init)
 
1217
        LocalBranch.__init__(self, base, init=init)
1387
1218
        for d in dirs:
1388
1219
            os.mkdir(self.abspath(d))
1389
1220
            
1395
1226
        """
1396
1227
        >>> orig = ScratchBranch(files=["file1", "file2"])
1397
1228
        >>> clone = orig.clone()
1398
 
        >>> os.path.samefile(orig.base, clone.base)
 
1229
        >>> if os.name != 'nt':
 
1230
        ...   os.path.samefile(orig.base, clone.base)
 
1231
        ... else:
 
1232
        ...   orig.base == clone.base
 
1233
        ...
1399
1234
        False
1400
1235
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1401
1236
        True
1482
1317
    return gen_file_id('TREE_ROOT')
1483
1318
 
1484
1319
 
1485
 
def pull_loc(branch):
1486
 
    # TODO: Should perhaps just make attribute be 'base' in
1487
 
    # RemoteBranch and Branch?
1488
 
    if hasattr(branch, "baseurl"):
1489
 
        return branch.baseurl
1490
 
    else:
1491
 
        return branch.base
1492
 
 
1493
 
 
1494
 
def copy_branch(branch_from, to_location, revision=None):
 
1320
def copy_branch(branch_from, to_location, revno=None):
1495
1321
    """Copy branch_from into the existing directory to_location.
1496
1322
 
1497
1323
    revision
1502
1328
        The name of a local directory that exists but is empty.
1503
1329
    """
1504
1330
    from bzrlib.merge import merge
1505
 
    from bzrlib.branch import Branch
1506
1331
 
1507
1332
    assert isinstance(branch_from, Branch)
1508
1333
    assert isinstance(to_location, basestring)
1509
1334
    
1510
 
    br_to = Branch(to_location, init=True)
 
1335
    br_to = Branch.initialize(to_location)
1511
1336
    br_to.set_root_id(branch_from.get_root_id())
1512
 
    if revision is None:
 
1337
    if revno is None:
1513
1338
        revno = branch_from.revno()
1514
 
    else:
1515
 
        revno, rev_id = branch_from.get_revision_info(revision)
1516
1339
    br_to.update_revisions(branch_from, stop_revision=revno)
1517
1340
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1518
1341
          check_clean=False, ignore_zero=True)
1519
 
    
1520
 
    from_location = pull_loc(branch_from)
1521
 
    br_to.set_parent(pull_loc(branch_from))
 
1342
    br_to.set_parent(branch_from.base)
 
1343
    return br_to