~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:25:54 UTC
  • mfrom: (1185.1.42)
  • mto: (1092.2.18)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050928052554-beb985505f77ea6a
update symlink branch to integration

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:
470
481
        """Print `file` to stdout."""
471
482
        self.lock_read()
472
483
        try:
473
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
484
            tree = self.revision_tree(self.get_rev_id(revno))
474
485
            # use inventory as it was in that revision
475
486
            file_id = tree.inventory.path2id(file)
476
487
            if not file_id:
580
591
        try:
581
592
            try:
582
593
                return self.revision_store[revision_id]
583
 
            except IndexError:
 
594
            except (IndexError, KeyError):
584
595
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
585
596
        finally:
586
597
            self.unlock()
588
599
    #deprecated
589
600
    get_revision_xml = get_revision_xml_file
590
601
 
 
602
    #deprecated
 
603
    get_revision_xml = get_revision_xml_file
 
604
 
 
605
 
591
606
    def get_revision(self, revision_id):
592
607
        """Return the Revision object for a named revision"""
593
608
        xml_file = self.get_revision_xml_file(revision_id)
679
694
 
680
695
    def common_ancestor(self, other, self_revno=None, other_revno=None):
681
696
        """
682
 
        >>> import commit
 
697
        >>> from bzrlib.commit import commit
683
698
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
684
699
        >>> sb.common_ancestor(sb) == (None, None)
685
700
        True
686
 
        >>> commit.commit(sb, "Committing first revision", verbose=False)
 
701
        >>> commit(sb, "Committing first revision", verbose=False)
687
702
        >>> sb.common_ancestor(sb)[0]
688
703
        1
689
704
        >>> clone = sb.clone()
690
 
        >>> commit.commit(sb, "Committing second revision", verbose=False)
 
705
        >>> commit(sb, "Committing second revision", verbose=False)
691
706
        >>> sb.common_ancestor(sb)[0]
692
707
        2
693
708
        >>> sb.common_ancestor(clone)[0]
694
709
        1
695
 
        >>> commit.commit(clone, "Committing divergent second revision", 
 
710
        >>> commit(clone, "Committing divergent second revision", 
696
711
        ...               verbose=False)
697
712
        >>> sb.common_ancestor(clone)[0]
698
713
        1
793
808
 
794
809
        pb = bzrlib.ui.ui_factory.progress_bar()
795
810
        pb.update('comparing histories')
796
 
 
 
811
        if stop_revision is None:
 
812
            other_revision = other.last_patch()
 
813
        else:
 
814
            other_revision = other.get_rev_id(stop_revision)
 
815
        count = greedy_fetch(self, other, other_revision, pb)[0]
797
816
        try:
798
817
            revision_ids = self.missing_revisions(other, stop_revision)
799
818
        except DivergedBranches, e:
800
819
            try:
801
 
                if stop_revision is None:
802
 
                    end_revision = other.last_patch()
803
820
                revision_ids = get_intervening_revisions(self.last_patch(), 
804
 
                                                         end_revision, other)
 
821
                                                         other_revision, self)
805
822
                assert self.last_patch() not in revision_ids
806
823
            except bzrlib.errors.NotAncestor:
807
824
                raise e
808
825
 
809
 
        if len(revision_ids) > 0:
810
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
811
 
        else:
812
 
            count = 0
813
826
        self.append_revision(*revision_ids)
814
 
        ## note("Added %d revisions." % count)
815
827
        pb.clear()
816
828
 
817
829
    def install_revisions(self, other, revision_ids, pb):
818
830
        if hasattr(other.revision_store, "prefetch"):
819
831
            other.revision_store.prefetch(revision_ids)
820
832
        if hasattr(other.inventory_store, "prefetch"):
821
 
            inventory_ids = [other.get_revision(r).inventory_id
822
 
                             for r in revision_ids]
 
833
            inventory_ids = []
 
834
            for rev_id in revision_ids:
 
835
                try:
 
836
                    revision = other.get_revision(rev_id).inventory_id
 
837
                    inventory_ids.append(revision)
 
838
                except bzrlib.errors.NoSuchRevision:
 
839
                    pass
823
840
            other.inventory_store.prefetch(inventory_ids)
824
841
 
825
842
        if pb is None:
868
885
        from bzrlib.commit import commit
869
886
        commit(self, *args, **kw)
870
887
        
871
 
 
872
 
    def lookup_revision(self, revision):
873
 
        """Return the revision identifier for a given revision information."""
874
 
        revno, info = self._get_revision_info(revision)
875
 
        return info
876
 
 
877
 
 
878
888
    def revision_id_to_revno(self, revision_id):
879
889
        """Given a revision id, return its revno"""
880
890
        history = self.revision_history()
883
893
        except ValueError:
884
894
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
885
895
 
886
 
 
887
 
    def get_revision_info(self, revision):
888
 
        """Return (revno, revision id) for revision identifier.
889
 
 
890
 
        revision can be an integer, in which case it is assumed to be revno (though
891
 
            this will translate negative values into positive ones)
892
 
        revision can also be a string, in which case it is parsed for something like
893
 
            'date:' or 'revid:' etc.
894
 
        """
895
 
        revno, rev_id = self._get_revision_info(revision)
896
 
        if revno is None:
897
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
898
 
        return revno, rev_id
899
 
 
900
896
    def get_rev_id(self, revno, history=None):
901
897
        """Find the revision id of the specified revno."""
902
898
        if revno == 0:
907
903
            raise bzrlib.errors.NoSuchRevision(self, revno)
908
904
        return history[revno - 1]
909
905
 
910
 
    def _get_revision_info(self, revision):
911
 
        """Return (revno, revision id) for revision specifier.
912
 
 
913
 
        revision can be an integer, in which case it is assumed to be revno
914
 
        (though this will translate negative values into positive ones)
915
 
        revision can also be a string, in which case it is parsed for something
916
 
        like 'date:' or 'revid:' etc.
917
 
 
918
 
        A revid is always returned.  If it is None, the specifier referred to
919
 
        the null revision.  If the revid does not occur in the revision
920
 
        history, revno will be None.
921
 
        """
922
 
        
923
 
        if revision is None:
924
 
            return 0, None
925
 
        revno = None
926
 
        try:# Convert to int if possible
927
 
            revision = int(revision)
928
 
        except ValueError:
929
 
            pass
930
 
        revs = self.revision_history()
931
 
        if isinstance(revision, int):
932
 
            if revision < 0:
933
 
                revno = len(revs) + revision + 1
934
 
            else:
935
 
                revno = revision
936
 
            rev_id = self.get_rev_id(revno, revs)
937
 
        elif isinstance(revision, basestring):
938
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
939
 
                if revision.startswith(prefix):
940
 
                    result = func(self, revs, revision)
941
 
                    if len(result) > 1:
942
 
                        revno, rev_id = result
943
 
                    else:
944
 
                        revno = result[0]
945
 
                        rev_id = self.get_rev_id(revno, revs)
946
 
                    break
947
 
            else:
948
 
                raise BzrError('No namespace registered for string: %r' %
949
 
                               revision)
950
 
        else:
951
 
            raise TypeError('Unhandled revision type %s' % revision)
952
 
 
953
 
        if revno is None:
954
 
            if rev_id is None:
955
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
956
 
        return revno, rev_id
957
 
 
958
 
    def _namespace_revno(self, revs, revision):
959
 
        """Lookup a revision by revision number"""
960
 
        assert revision.startswith('revno:')
961
 
        try:
962
 
            return (int(revision[6:]),)
963
 
        except ValueError:
964
 
            return None
965
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
966
 
 
967
 
    def _namespace_revid(self, revs, revision):
968
 
        assert revision.startswith('revid:')
969
 
        rev_id = revision[len('revid:'):]
970
 
        try:
971
 
            return revs.index(rev_id) + 1, rev_id
972
 
        except ValueError:
973
 
            return None, rev_id
974
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
975
 
 
976
 
    def _namespace_last(self, revs, revision):
977
 
        assert revision.startswith('last:')
978
 
        try:
979
 
            offset = int(revision[5:])
980
 
        except ValueError:
981
 
            return (None,)
982
 
        else:
983
 
            if offset <= 0:
984
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
985
 
            return (len(revs) - offset + 1,)
986
 
    REVISION_NAMESPACES['last:'] = _namespace_last
987
 
 
988
 
    def _namespace_tag(self, revs, revision):
989
 
        assert revision.startswith('tag:')
990
 
        raise BzrError('tag: namespace registered, but not implemented.')
991
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
992
 
 
993
 
    def _namespace_date(self, revs, revision):
994
 
        assert revision.startswith('date:')
995
 
        import datetime
996
 
        # Spec for date revisions:
997
 
        #   date:value
998
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
999
 
        #   it can also start with a '+/-/='. '+' says match the first
1000
 
        #   entry after the given date. '-' is match the first entry before the date
1001
 
        #   '=' is match the first entry after, but still on the given date.
1002
 
        #
1003
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1004
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1005
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1006
 
        #       May 13th, 2005 at 0:00
1007
 
        #
1008
 
        #   So the proper way of saying 'give me all entries for today' is:
1009
 
        #       -r {date:+today}:{date:-tomorrow}
1010
 
        #   The default is '=' when not supplied
1011
 
        val = revision[5:]
1012
 
        match_style = '='
1013
 
        if val[:1] in ('+', '-', '='):
1014
 
            match_style = val[:1]
1015
 
            val = val[1:]
1016
 
 
1017
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1018
 
        if val.lower() == 'yesterday':
1019
 
            dt = today - datetime.timedelta(days=1)
1020
 
        elif val.lower() == 'today':
1021
 
            dt = today
1022
 
        elif val.lower() == 'tomorrow':
1023
 
            dt = today + datetime.timedelta(days=1)
1024
 
        else:
1025
 
            import re
1026
 
            # This should be done outside the function to avoid recompiling it.
1027
 
            _date_re = re.compile(
1028
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1029
 
                    r'(,|T)?\s*'
1030
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1031
 
                )
1032
 
            m = _date_re.match(val)
1033
 
            if not m or (not m.group('date') and not m.group('time')):
1034
 
                raise BzrError('Invalid revision date %r' % revision)
1035
 
 
1036
 
            if m.group('date'):
1037
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1038
 
            else:
1039
 
                year, month, day = today.year, today.month, today.day
1040
 
            if m.group('time'):
1041
 
                hour = int(m.group('hour'))
1042
 
                minute = int(m.group('minute'))
1043
 
                if m.group('second'):
1044
 
                    second = int(m.group('second'))
1045
 
                else:
1046
 
                    second = 0
1047
 
            else:
1048
 
                hour, minute, second = 0,0,0
1049
 
 
1050
 
            dt = datetime.datetime(year=year, month=month, day=day,
1051
 
                    hour=hour, minute=minute, second=second)
1052
 
        first = dt
1053
 
        last = None
1054
 
        reversed = False
1055
 
        if match_style == '-':
1056
 
            reversed = True
1057
 
        elif match_style == '=':
1058
 
            last = dt + datetime.timedelta(days=1)
1059
 
 
1060
 
        if reversed:
1061
 
            for i in range(len(revs)-1, -1, -1):
1062
 
                r = self.get_revision(revs[i])
1063
 
                # TODO: Handle timezone.
1064
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1065
 
                if first >= dt and (last is None or dt >= last):
1066
 
                    return (i+1,)
1067
 
        else:
1068
 
            for i in range(len(revs)):
1069
 
                r = self.get_revision(revs[i])
1070
 
                # TODO: Handle timezone.
1071
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1072
 
                if first <= dt and (last is None or dt <= last):
1073
 
                    return (i+1,)
1074
 
    REVISION_NAMESPACES['date:'] = _namespace_date
1075
 
 
1076
906
    def revision_tree(self, revision_id):
1077
907
        """Return Tree for a revision on this branch.
1078
908
 
1089
919
 
1090
920
    def working_tree(self):
1091
921
        """Return a `Tree` for the working copy."""
1092
 
        from workingtree import WorkingTree
 
922
        from bzrlib.workingtree import WorkingTree
1093
923
        return WorkingTree(self.base, self.read_working_inventory())
1094
924
 
1095
925
 
1144
974
            from_abs = self.abspath(from_rel)
1145
975
            to_abs = self.abspath(to_rel)
1146
976
            try:
1147
 
                os.rename(from_abs, to_abs)
 
977
                rename(from_abs, to_abs)
1148
978
            except OSError, e:
1149
979
                raise BzrError("failed to rename %r to %r: %s"
1150
980
                        % (from_abs, to_abs, e[1]),
1213
1043
                result.append((f, dest_path))
1214
1044
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1215
1045
                try:
1216
 
                    os.rename(self.abspath(f), self.abspath(dest_path))
 
1046
                    rename(self.abspath(f), self.abspath(dest_path))
1217
1047
                except OSError, e:
1218
1048
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1219
1049
                            ["rename rolled back"])
1360
1190
            raise InvalidRevisionNumber(revno)
1361
1191
        
1362
1192
        
1363
 
 
1364
 
 
1365
 
class ScratchBranch(Branch):
 
1193
        
 
1194
 
 
1195
 
 
1196
class ScratchBranch(LocalBranch):
1366
1197
    """Special test class: a branch that cleans up after itself.
1367
1198
 
1368
1199
    >>> b = ScratchBranch()
1385
1216
        if base is None:
1386
1217
            base = mkdtemp()
1387
1218
            init = True
1388
 
        Branch.__init__(self, base, init=init)
 
1219
        LocalBranch.__init__(self, base, init=init)
1389
1220
        for d in dirs:
1390
1221
            os.mkdir(self.abspath(d))
1391
1222
            
1397
1228
        """
1398
1229
        >>> orig = ScratchBranch(files=["file1", "file2"])
1399
1230
        >>> clone = orig.clone()
1400
 
        >>> os.path.samefile(orig.base, clone.base)
 
1231
        >>> if os.name != 'nt':
 
1232
        ...   os.path.samefile(orig.base, clone.base)
 
1233
        ... else:
 
1234
        ...   orig.base == clone.base
 
1235
        ...
1401
1236
        False
1402
1237
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1403
1238
        True
1486
1321
    return gen_file_id('TREE_ROOT')
1487
1322
 
1488
1323
 
1489
 
def pull_loc(branch):
1490
 
    # TODO: Should perhaps just make attribute be 'base' in
1491
 
    # RemoteBranch and Branch?
1492
 
    if hasattr(branch, "baseurl"):
1493
 
        return branch.baseurl
1494
 
    else:
1495
 
        return branch.base
1496
 
 
1497
 
 
1498
 
def copy_branch(branch_from, to_location, revision=None):
 
1324
def copy_branch(branch_from, to_location, revno=None):
1499
1325
    """Copy branch_from into the existing directory to_location.
1500
1326
 
1501
1327
    revision
1506
1332
        The name of a local directory that exists but is empty.
1507
1333
    """
1508
1334
    from bzrlib.merge import merge
1509
 
    from bzrlib.branch import Branch
1510
1335
 
1511
1336
    assert isinstance(branch_from, Branch)
1512
1337
    assert isinstance(to_location, basestring)
1513
1338
    
1514
 
    br_to = Branch(to_location, init=True)
 
1339
    br_to = Branch.initialize(to_location)
1515
1340
    br_to.set_root_id(branch_from.get_root_id())
1516
 
    if revision is None:
 
1341
    if revno is None:
1517
1342
        revno = branch_from.revno()
1518
 
    else:
1519
 
        revno, rev_id = branch_from.get_revision_info(revision)
1520
1343
    br_to.update_revisions(branch_from, stop_revision=revno)
1521
1344
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1522
1345
          check_clean=False, ignore_zero=True)
1523
 
    
1524
 
    from_location = pull_loc(branch_from)
1525
 
    br_to.set_parent(pull_loc(branch_from))
 
1346
    br_to.set_parent(branch_from.base)
 
1347
    return br_to