~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-09-27 07:24:40 UTC
  • mfrom: (1185.1.41)
  • Revision ID: robertc@robertcollins.net-20050927072440-1bf4d99c3e1db5b3
pair programming worx... merge integration and weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
import bzrlib
25
25
from bzrlib.trace import mutter, note
26
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
27
 
     splitpath, \
28
 
     sha_file, appendpath, file_kind
29
 
 
 
26
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
 
27
                            rename, splitpath, sha_file, appendpath, 
 
28
                            file_kind)
30
29
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
31
30
                           NoSuchRevision, HistoryMissing, NotBranchError,
32
 
                           LockError)
 
31
                           DivergedBranches, LockError)
33
32
from bzrlib.textui import show_status
34
33
from bzrlib.revision import Revision, validate_revision_id
35
34
from bzrlib.delta import compare_trees
51
50
# cache in memory to make this faster.  In general anything can be
52
51
# cached in memory between lock and unlock operations.
53
52
 
54
 
# TODO: please move the revision-string syntax stuff out of the branch
55
 
# object; it's clutter
56
 
 
57
 
 
58
 
def find_branch(f, **args):
59
 
    if f and (f.startswith('http://') or f.startswith('https://')):
60
 
        import remotebranch 
61
 
        return remotebranch.RemoteBranch(f, **args)
62
 
    else:
63
 
        return Branch(f, **args)
64
 
 
65
 
 
66
 
def find_cached_branch(f, cache_root, **args):
67
 
    from remotebranch import RemoteBranch
68
 
    br = find_branch(f, **args)
69
 
    def cacheify(br, store_name):
70
 
        from meta_store import CachedStore
71
 
        cache_path = os.path.join(cache_root, store_name)
72
 
        os.mkdir(cache_path)
73
 
        new_store = CachedStore(getattr(br, store_name), cache_path)
74
 
        setattr(br, store_name, new_store)
75
 
 
76
 
    if isinstance(br, RemoteBranch):
77
 
        cacheify(br, 'inventory_store')
78
 
        cacheify(br, 'text_store')
79
 
        cacheify(br, 'revision_store')
80
 
    return br
81
 
 
 
53
def find_branch(*ignored, **ignored_too):
 
54
    # XXX: leave this here for about one release, then remove it
 
55
    raise NotImplementedError('find_branch() is not supported anymore, '
 
56
                              'please use one of the new branch constructors')
82
57
 
83
58
def _relpath(base, path):
84
59
    """Return path relative to base, or raise exception.
140
115
 
141
116
 
142
117
 
143
 
# XXX: move into bzrlib.errors; subclass BzrError    
144
 
class DivergedBranches(Exception):
145
 
    def __init__(self, branch1, branch2):
146
 
        self.branch1 = branch1
147
 
        self.branch2 = branch2
148
 
        Exception.__init__(self, "These branches have diverged.")
149
 
 
150
118
 
151
119
######################################################################
152
120
# branch objects
155
123
    """Branch holding a history of revisions.
156
124
 
157
125
    base
158
 
        Base directory of the branch.
 
126
        Base directory/url of the branch.
 
127
    """
 
128
    base = None
 
129
 
 
130
    def __init__(self, *ignored, **ignored_too):
 
131
        raise NotImplementedError('The Branch class is abstract')
 
132
 
 
133
    @staticmethod
 
134
    def open(base):
 
135
        """Open an existing branch, rooted at 'base' (url)"""
 
136
        if base and (base.startswith('http://') or base.startswith('https://')):
 
137
            from bzrlib.remotebranch import RemoteBranch
 
138
            return RemoteBranch(base, find_root=False)
 
139
        else:
 
140
            return LocalBranch(base, find_root=False)
 
141
 
 
142
    @staticmethod
 
143
    def open_containing(url):
 
144
        """Open an existing branch which contains url.
 
145
        
 
146
        This probes for a branch at url, and searches upwards from there.
 
147
        """
 
148
        if url and (url.startswith('http://') or url.startswith('https://')):
 
149
            from bzrlib.remotebranch import RemoteBranch
 
150
            return RemoteBranch(url)
 
151
        else:
 
152
            return LocalBranch(url)
 
153
 
 
154
    @staticmethod
 
155
    def initialize(base):
 
156
        """Create a new branch, rooted at 'base' (url)"""
 
157
        if base and (base.startswith('http://') or base.startswith('https://')):
 
158
            from bzrlib.remotebranch import RemoteBranch
 
159
            return RemoteBranch(base, init=True)
 
160
        else:
 
161
            return LocalBranch(base, init=True)
 
162
 
 
163
    def setup_caching(self, cache_root):
 
164
        """Subclasses that care about caching should override this, and set
 
165
        up cached stores located under cache_root.
 
166
        """
 
167
 
 
168
 
 
169
class LocalBranch(Branch):
 
170
    """A branch stored in the actual filesystem.
 
171
 
 
172
    Note that it's "local" in the context of the filesystem; it doesn't
 
173
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
174
    it's writable, and can be accessed via the normal filesystem API.
159
175
 
160
176
    _lock_mode
161
177
        None, or 'r' or 'w'
167
183
    _lock
168
184
        Lock object from bzrlib.lock.
169
185
    """
170
 
    base = None
 
186
    # We actually expect this class to be somewhat short-lived; part of its
 
187
    # purpose is to try to isolate what bits of the branch logic are tied to
 
188
    # filesystem access, so that in a later step, we can extricate them to
 
189
    # a separarte ("storage") class.
171
190
    _lock_mode = None
172
191
    _lock_count = None
173
192
    _lock = None
182
201
                 relax_version_check=False):
183
202
        """Create new branch object at a particular location.
184
203
 
185
 
        base -- Base directory for the branch.
 
204
        base -- Base directory for the branch. May be a file:// url.
186
205
        
187
206
        init -- If True, create new control files in a previously
188
207
             unversioned directory.  If False, the branch must already
205
224
        elif find_root:
206
225
            self.base = find_branch_root(base)
207
226
        else:
 
227
            if base.startswith("file://"):
 
228
                base = base[7:]
208
229
            self.base = os.path.realpath(base)
209
230
            if not isdir(self.controlfilename('.')):
210
231
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
211
232
                                     ['use "bzr init" to initialize a '
212
233
                                      'new working tree'])
213
234
        self._check_format(relax_version_check)
214
 
        cfn = self.controlfilename
 
235
        cfn = self.controlfilename
215
236
        if self._branch_format == 4:
216
237
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
217
238
            self.text_store = ImmutableStore(cfn('text-store'))
218
 
        elif self._branch_format == 5:
219
 
            self.control_weaves = WeaveStore(cfn([]))
220
 
            self.weave_store = WeaveStore(cfn('weaves'))
 
239
        elif self._branch_format == 5:
 
240
            self.control_weaves = WeaveStore(cfn([]))
 
241
            self.weave_store = WeaveStore(cfn('weaves'))
 
242
            if init:
 
243
                # FIXME: Unify with make_control_files
 
244
                self.control_weaves.put_empty_weave('inventory')
 
245
                self.control_weaves.put_empty_weave('ancestry')
221
246
        self.revision_store = ImmutableStore(cfn('revision-store'))
222
247
 
223
248
 
230
255
 
231
256
    def __del__(self):
232
257
        if self._lock_mode or self._lock:
 
258
            # XXX: This should show something every time, and be suitable for
 
259
            # headless operation and embedding
233
260
            warn("branch %r was not explicitly unlocked" % self)
234
261
            self._lock.unlock()
235
262
 
236
 
 
237
263
    def lock_write(self):
238
264
        if self._lock_mode:
239
265
            if self._lock_mode != 'w':
334
360
        # simplicity.
335
361
        f = self.controlfile('inventory','w')
336
362
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
337
 
        
338
363
 
339
364
 
340
365
    def _check_format(self, relax_version_check):
346
371
        In the future, we might need different in-memory Branch
347
372
        classes to support downlevel branches.  But not yet.
348
373
        """
349
 
        try:
350
 
            fmt = self.controlfile('branch-format', 'r').read()
 
374
        try:
 
375
            fmt = self.controlfile('branch-format', 'r').read()
351
376
        except IOError, e:
352
377
            if e.errno == errno.ENOENT:
353
378
                raise NotBranchError(self.base)
361
386
 
362
387
        if (not relax_version_check
363
388
            and self._branch_format != 5):
364
 
            raise BzrError('sorry, branch format "%s" not supported; ' 
365
 
                           'use a different bzr version, '
366
 
                           'or run "bzr upgrade"'
367
 
                           % fmt.rstrip('\n\r'))
368
 
        
 
389
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
390
                           ['use a different bzr version',
 
391
                            'or remove the .bzr directory and "bzr init" again'])
369
392
 
370
393
    def get_root_id(self):
371
394
        """Return the id of this branches root"""
495
518
        """Print `file` to stdout."""
496
519
        self.lock_read()
497
520
        try:
498
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
521
            tree = self.revision_tree(self.get_rev_id(revno))
499
522
            # use inventory as it was in that revision
500
523
            file_id = tree.inventory.path2id(file)
501
524
            if not file_id:
604
627
 
605
628
        This does not necessarily imply the revision is merge
606
629
        or on the mainline."""
607
 
        return revision_id in self.revision_store
 
630
        return (revision_id is None
 
631
                or revision_id in self.revision_store)
608
632
 
609
633
 
610
634
    def get_revision_xml_file(self, revision_id):
616
640
        try:
617
641
            try:
618
642
                return self.revision_store[revision_id]
619
 
            except IndexError:
 
643
            except (IndexError, KeyError):
620
644
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
621
645
        finally:
622
646
            self.unlock()
662
686
 
663
687
        return compare_trees(old_tree, new_tree)
664
688
 
665
 
        
666
689
 
667
690
    def get_revision_sha1(self, revision_id):
668
691
        """Hash the stored value of a revision, and return it."""
671
694
 
672
695
    def _get_ancestry_weave(self):
673
696
        return self.control_weaves.get_weave('ancestry')
674
 
        
 
697
        
675
698
 
676
699
    def get_ancestry(self, revision_id):
677
700
        """Return a list of revision-ids integrated by a revision.
678
701
        """
679
702
        # strip newlines
680
 
        w = self._get_ancestry_weave()
681
 
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
 
703
        if revision_id is None:
 
704
            return [None]
 
705
        w = self._get_ancestry_weave()
 
706
        return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
682
707
 
683
708
 
684
709
    def get_inventory_weave(self):
730
755
 
731
756
    def common_ancestor(self, other, self_revno=None, other_revno=None):
732
757
        """
733
 
        >>> import commit
 
758
        >>> from bzrlib.commit import commit
734
759
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
735
760
        >>> sb.common_ancestor(sb) == (None, None)
736
761
        True
737
 
        >>> commit.commit(sb, "Committing first revision")
 
762
        >>> commit(sb, "Committing first revision", verbose=False)
738
763
        >>> sb.common_ancestor(sb)[0]
739
764
        1
740
765
        >>> clone = sb.clone()
741
 
        >>> commit.commit(sb, "Committing second revision")
 
766
        >>> commit(sb, "Committing second revision", verbose=False)
742
767
        >>> sb.common_ancestor(sb)[0]
743
768
        2
744
769
        >>> sb.common_ancestor(clone)[0]
745
770
        1
746
 
        >>> commit.commit(clone, "Committing divergent second revision")
 
771
        >>> commit(clone, "Committing divergent second revision", 
 
772
        ...               verbose=False)
747
773
        >>> sb.common_ancestor(clone)[0]
748
774
        1
749
775
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
837
863
            assert isinstance(stop_revision, int)
838
864
            if stop_revision > other_len:
839
865
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
840
 
        
841
866
        return other_history[self_len:stop_revision]
842
867
 
843
 
 
844
 
    def update_revisions(self, other, stop_revno=None):
845
 
        """Pull in new perfect-fit revisions.
846
 
        """
 
868
    def update_revisions(self, other, stop_revision=None):
 
869
        """Pull in new perfect-fit revisions."""
847
870
        from bzrlib.fetch import greedy_fetch
848
 
 
849
 
        if stop_revno:
850
 
            stop_revision = other.lookup_revision(stop_revno)
851
 
        else:
852
 
            stop_revision = None
 
871
        from bzrlib.revision import get_intervening_revisions
 
872
        if stop_revision is None:
 
873
            stop_revision = other.last_revision()
853
874
        greedy_fetch(to_branch=self, from_branch=other,
854
875
                     revision=stop_revision)
855
 
 
856
 
        pullable_revs = self.missing_revisions(other, stop_revision)
857
 
 
 
876
        pullable_revs = self.missing_revisions(
 
877
            other, other.revision_id_to_revno(stop_revision))
858
878
        if pullable_revs:
859
879
            greedy_fetch(to_branch=self,
860
880
                         from_branch=other,
861
881
                         revision=pullable_revs[-1])
862
882
            self.append_revision(*pullable_revs)
863
 
 
864
 
 
 
883
    
865
884
    def commit(self, *args, **kw):
866
885
        from bzrlib.commit import Commit
867
886
        Commit().commit(self, *args, **kw)
868
 
        
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
 
 
 
887
    
876
888
    def revision_id_to_revno(self, revision_id):
877
889
        """Given a revision id, return its revno"""
 
890
        if revision_id is None:
 
891
            return 0
878
892
        history = self.revision_history()
879
893
        try:
880
894
            return history.index(revision_id) + 1
881
895
        except ValueError:
882
896
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
883
897
 
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
898
    def get_rev_id(self, revno, history=None):
899
899
        """Find the revision id of the specified revno."""
900
900
        if revno == 0:
905
905
            raise bzrlib.errors.NoSuchRevision(self, revno)
906
906
        return history[revno - 1]
907
907
 
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
908
    def revision_tree(self, revision_id):
1075
909
        """Return Tree for a revision on this branch.
1076
910
 
1087
921
 
1088
922
    def working_tree(self):
1089
923
        """Return a `Tree` for the working copy."""
1090
 
        from workingtree import WorkingTree
 
924
        from bzrlib.workingtree import WorkingTree
1091
925
        return WorkingTree(self.base, self.read_working_inventory())
1092
926
 
1093
927
 
1137
971
            from_abs = self.abspath(from_rel)
1138
972
            to_abs = self.abspath(to_rel)
1139
973
            try:
1140
 
                os.rename(from_abs, to_abs)
 
974
                rename(from_abs, to_abs)
1141
975
            except OSError, e:
1142
976
                raise BzrError("failed to rename %r to %r: %s"
1143
977
                        % (from_abs, to_abs, e[1]),
1206
1040
                result.append((f, dest_path))
1207
1041
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1208
1042
                try:
1209
 
                    os.rename(self.abspath(f), self.abspath(dest_path))
 
1043
                    rename(self.abspath(f), self.abspath(dest_path))
1210
1044
                except OSError, e:
1211
1045
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1212
1046
                            ["rename rolled back"])
1352
1186
            raise InvalidRevisionNumber(revno)
1353
1187
        
1354
1188
        
1355
 
 
1356
 
 
1357
 
class ScratchBranch(Branch):
 
1189
        
 
1190
 
 
1191
 
 
1192
class ScratchBranch(LocalBranch):
1358
1193
    """Special test class: a branch that cleans up after itself.
1359
1194
 
1360
1195
    >>> b = ScratchBranch()
1377
1212
        if base is None:
1378
1213
            base = mkdtemp()
1379
1214
            init = True
1380
 
        Branch.__init__(self, base, init=init)
 
1215
        LocalBranch.__init__(self, base, init=init)
1381
1216
        for d in dirs:
1382
1217
            os.mkdir(self.abspath(d))
1383
1218
            
1389
1224
        """
1390
1225
        >>> orig = ScratchBranch(files=["file1", "file2"])
1391
1226
        >>> clone = orig.clone()
1392
 
        >>> os.path.samefile(orig.base, clone.base)
 
1227
        >>> if os.name != 'nt':
 
1228
        ...   os.path.samefile(orig.base, clone.base)
 
1229
        ... else:
 
1230
        ...   orig.base == clone.base
 
1231
        ...
1393
1232
        False
1394
1233
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1395
1234
        True
1478
1317
    return gen_file_id('TREE_ROOT')
1479
1318
 
1480
1319
 
1481
 
def pull_loc(branch):
1482
 
    # TODO: Should perhaps just make attribute be 'base' in
1483
 
    # RemoteBranch and Branch?
1484
 
    if hasattr(branch, "baseurl"):
1485
 
        return branch.baseurl
1486
 
    else:
1487
 
        return branch.base
1488
 
 
1489
 
 
1490
1320
def copy_branch(branch_from, to_location, revision=None):
1491
1321
    """Copy branch_from into the existing directory to_location.
1492
1322
 
1493
1323
    revision
1494
1324
        If not None, only revisions up to this point will be copied.
1495
 
        The head of the new branch will be that revision.  Can be a
1496
 
        revno or revid.
 
1325
        The head of the new branch will be that revision.  Must be a
 
1326
        revid or None.
1497
1327
 
1498
1328
    to_location
1499
1329
        The name of a local directory that exists but is empty.
1502
1332
    # all the whole weaves and revisions, rather than getting one
1503
1333
    # revision at a time.
1504
1334
    from bzrlib.merge import merge
1505
 
    from bzrlib.branch import Branch
1506
1335
 
1507
1336
    assert isinstance(branch_from, Branch)
1508
1337
    assert isinstance(to_location, basestring)
1509
1338
    
1510
 
    br_to = Branch(to_location, init=True)
 
1339
    br_to = Branch.initialize(to_location)
1511
1340
    br_to.set_root_id(branch_from.get_root_id())
1512
1341
    if revision is None:
1513
 
        revno = None
1514
 
    else:
1515
 
        revno, rev_id = branch_from.get_revision_info(revision)
1516
 
    br_to.update_revisions(branch_from, stop_revno=revno)
 
1342
        revision = branch_from.last_revision()
 
1343
    br_to.update_revisions(branch_from, stop_revision=revision)
1517
1344
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1518
1345
          check_clean=False, ignore_zero=True)
1519
 
    
1520
 
    from_location = pull_loc(branch_from)
1521
 
    br_to.set_parent(pull_loc(branch_from))
1522
 
 
 
1346
    br_to.set_parent(branch_from.base)
 
1347
    return br_to