~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-08-25 06:20:21 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050825062021-d7d1b19582ceb297
should run tests before committing, tsk. move an import to fix

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
     splitpath, \
25
25
     sha_file, appendpath, file_kind
26
26
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
 
28
import bzrlib.errors
29
29
from bzrlib.textui import show_status
30
30
from bzrlib.revision import Revision
 
31
from bzrlib.xml import unpack_xml
31
32
from bzrlib.delta import compare_trees
32
33
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
34
34
import bzrlib.ui
35
35
 
36
36
 
49
49
 
50
50
def find_branch(f, **args):
51
51
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        from bzrlib.remotebranch import RemoteBranch
53
 
        return RemoteBranch(f, **args)
 
52
        import remotebranch 
 
53
        return remotebranch.RemoteBranch(f, **args)
54
54
    else:
55
 
        return LocalBranch(f, **args)
 
55
        return Branch(f, **args)
56
56
 
57
57
 
58
58
def find_cached_branch(f, cache_root, **args):
59
 
    from bzrlib.remotebranch import RemoteBranch
 
59
    from remotebranch import RemoteBranch
60
60
    br = find_branch(f, **args)
61
61
    def cacheify(br, store_name):
62
 
        from bzrlib.meta_store import CachedStore
 
62
        from meta_store import CachedStore
63
63
        cache_path = os.path.join(cache_root, store_name)
64
64
        os.mkdir(cache_path)
65
65
        new_store = CachedStore(getattr(br, store_name), cache_path)
94
94
        if tail:
95
95
            s.insert(0, tail)
96
96
    else:
 
97
        from errors import NotBranchError
97
98
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
98
99
 
99
100
    return os.sep.join(s)
127
128
        head, tail = os.path.split(f)
128
129
        if head == f:
129
130
            # reached the root, whatever that may be
130
 
            raise NotBranchError('%s is not in a branch' % orig_f)
 
131
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
131
132
        f = head
132
133
 
133
134
 
134
135
 
 
136
# XXX: move into bzrlib.errors; subclass BzrError    
 
137
class DivergedBranches(Exception):
 
138
    def __init__(self, branch1, branch2):
 
139
        self.branch1 = branch1
 
140
        self.branch2 = branch2
 
141
        Exception.__init__(self, "These branches have diverged.")
 
142
 
135
143
 
136
144
######################################################################
137
145
# branch objects
140
148
    """Branch holding a history of revisions.
141
149
 
142
150
    base
143
 
        Base directory/url of the branch.
144
 
    """
145
 
    base = None
146
 
 
147
 
    def __new__(cls, *a, **kw):
148
 
        """this is temporary, till we get rid of all code that does
149
 
        b = Branch()
150
 
        """
151
 
        # XXX: AAARGH!  MY EYES!  UUUUGLY!!!
152
 
        if cls == Branch:
153
 
            cls = LocalBranch
154
 
        b = object.__new__(cls)
155
 
        return b
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.
 
151
        Base directory of the branch.
164
152
 
165
153
    _lock_mode
166
154
        None, or 'r' or 'w'
172
160
    _lock
173
161
        Lock object from bzrlib.lock.
174
162
    """
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.
 
163
    base = None
179
164
    _lock_mode = None
180
165
    _lock_count = None
181
166
    _lock = None
 
167
    
 
168
    # Map some sort of prefix into a namespace
 
169
    # stuff like "revno:10", "revid:", etc.
 
170
    # This should match a prefix with a function which accepts
 
171
    REVISION_NAMESPACES = {}
182
172
 
183
173
    def __init__(self, base, init=False, find_root=True):
184
174
        """Create new branch object at a particular location.
204
194
        else:
205
195
            self.base = os.path.realpath(base)
206
196
            if not isdir(self.controlfilename('.')):
 
197
                from errors import NotBranchError
207
198
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
208
199
                                     ['use "bzr init" to initialize a new working tree',
209
200
                                      'current bzr can only operate from top-of-tree'])
223
214
 
224
215
    def __del__(self):
225
216
        if self._lock_mode or self._lock:
226
 
            from bzrlib.warnings import warn
 
217
            from warnings import warn
227
218
            warn("branch %r was not explicitly unlocked" % self)
228
219
            self._lock.unlock()
229
220
 
231
222
    def lock_write(self):
232
223
        if self._lock_mode:
233
224
            if self._lock_mode != 'w':
234
 
                from bzrlib.errors import LockError
 
225
                from errors import LockError
235
226
                raise LockError("can't upgrade to a write lock from %r" %
236
227
                                self._lock_mode)
237
228
            self._lock_count += 1
257
248
                        
258
249
    def unlock(self):
259
250
        if not self._lock_mode:
260
 
            from bzrlib.errors import LockError
 
251
            from errors import LockError
261
252
            raise LockError('branch %r is not locked' % (self))
262
253
 
263
254
        if self._lock_count > 1:
311
302
 
312
303
    def _make_control(self):
313
304
        from bzrlib.inventory import Inventory
 
305
        from bzrlib.xml import pack_xml
314
306
        
315
307
        os.mkdir(self.controlfilename([]))
316
308
        self.controlfile('README', 'w').write(
329
321
        # if we want per-tree root ids then this is the place to set
330
322
        # them; they're not needed for now and so ommitted for
331
323
        # simplicity.
332
 
        f = self.controlfile('inventory','w')
333
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
334
 
 
 
324
        pack_xml(Inventory(), self.controlfile('inventory','w'))
335
325
 
336
326
    def _check_format(self):
337
327
        """Check this branch format is supported.
345
335
        # on Windows from Linux and so on.  I think it might be better
346
336
        # to always make all internal files in unix format.
347
337
        fmt = self.controlfile('branch-format', 'r').read()
348
 
        fmt = fmt.replace('\r\n', '\n')
 
338
        fmt.replace('\r\n', '')
349
339
        if fmt != BZR_BRANCH_FORMAT:
350
340
            raise BzrError('sorry, branch format %r not supported' % fmt,
351
341
                           ['use a different bzr version',
371
361
    def read_working_inventory(self):
372
362
        """Read the working inventory."""
373
363
        from bzrlib.inventory import Inventory
 
364
        from bzrlib.xml import unpack_xml
 
365
        from time import time
 
366
        before = time()
374
367
        self.lock_read()
375
368
        try:
376
369
            # ElementTree does its own conversion from UTF-8, so open in
377
370
            # binary.
378
 
            f = self.controlfile('inventory', 'rb')
379
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
 
371
            inv = unpack_xml(Inventory,
 
372
                             self.controlfile('inventory', 'rb'))
 
373
            mutter("loaded inventory of %d items in %f"
 
374
                   % (len(inv), time() - before))
 
375
            return inv
380
376
        finally:
381
377
            self.unlock()
382
378
            
388
384
        will be committed to the next revision.
389
385
        """
390
386
        from bzrlib.atomicfile import AtomicFile
 
387
        from bzrlib.xml import pack_xml
391
388
        
392
389
        self.lock_write()
393
390
        try:
394
391
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
395
392
            try:
396
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
 
393
                pack_xml(inv, f)
397
394
                f.commit()
398
395
            finally:
399
396
                f.close()
407
404
                         """Inventory for the working copy.""")
408
405
 
409
406
 
410
 
    def add(self, files, ids=None):
 
407
    def add(self, files, verbose=False, ids=None):
411
408
        """Make files versioned.
412
409
 
413
 
        Note that the command line normally calls smart_add instead,
414
 
        which can automatically recurse.
 
410
        Note that the command line normally calls smart_add instead.
415
411
 
416
412
        This puts the files in the Added state, so that they will be
417
413
        recorded by the next commit.
427
423
        TODO: Perhaps have an option to add the ids even if the files do
428
424
              not (yet) exist.
429
425
 
430
 
        TODO: Perhaps yield the ids and paths as they're added.
 
426
        TODO: Perhaps return the ids of the files?  But then again it
 
427
              is easy to retrieve them if they're needed.
 
428
 
 
429
        TODO: Adding a directory should optionally recurse down and
 
430
              add all non-ignored children.  Perhaps do that in a
 
431
              higher-level method.
431
432
        """
432
433
        # TODO: Re-adding a file that is removed in the working copy
433
434
        # should probably put it back with the previous ID.
469
470
                    file_id = gen_file_id(f)
470
471
                inv.add_path(f, kind=kind, file_id=file_id)
471
472
 
 
473
                if verbose:
 
474
                    print 'added', quotefn(f)
 
475
 
472
476
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
473
477
 
474
478
            self._write_inventory(inv)
480
484
        """Print `file` to stdout."""
481
485
        self.lock_read()
482
486
        try:
483
 
            tree = self.revision_tree(self.get_rev_id(revno))
 
487
            tree = self.revision_tree(self.lookup_revision(revno))
484
488
            # use inventory as it was in that revision
485
489
            file_id = tree.inventory.path2id(file)
486
490
            if not file_id:
584
588
            f.close()
585
589
 
586
590
 
587
 
    def get_revision_xml_file(self, revision_id):
 
591
    def get_revision_xml(self, revision_id):
588
592
        """Return XML file object for revision object."""
589
593
        if not revision_id or not isinstance(revision_id, basestring):
590
594
            raise InvalidRevisionId(revision_id)
599
603
            self.unlock()
600
604
 
601
605
 
602
 
    #deprecated
603
 
    get_revision_xml = get_revision_xml_file
604
 
 
605
 
 
606
606
    def get_revision(self, revision_id):
607
607
        """Return the Revision object for a named revision"""
608
 
        xml_file = self.get_revision_xml_file(revision_id)
 
608
        xml_file = self.get_revision_xml(revision_id)
609
609
 
610
610
        try:
611
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
611
            r = unpack_xml(Revision, xml_file)
612
612
        except SyntaxError, e:
613
613
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
614
614
                                         [revision_id,
659
659
               parameter which can be either an integer revno or a
660
660
               string hash."""
661
661
        from bzrlib.inventory import Inventory
 
662
        from bzrlib.xml import unpack_xml
662
663
 
663
 
        f = self.get_inventory_xml_file(inventory_id)
664
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
 
664
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
665
665
 
666
666
 
667
667
    def get_inventory_xml(self, inventory_id):
668
668
        """Get inventory XML as a file object."""
669
669
        return self.inventory_store[inventory_id]
670
 
 
671
 
    get_inventory_xml_file = get_inventory_xml
672
670
            
673
671
 
674
672
    def get_inventory_sha1(self, inventory_id):
704
702
 
705
703
    def common_ancestor(self, other, self_revno=None, other_revno=None):
706
704
        """
707
 
        >>> from bzrlib.commit import commit
 
705
        >>> import commit
708
706
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
709
707
        >>> sb.common_ancestor(sb) == (None, None)
710
708
        True
711
 
        >>> commit(sb, "Committing first revision", verbose=False)
 
709
        >>> commit.commit(sb, "Committing first revision", verbose=False)
712
710
        >>> sb.common_ancestor(sb)[0]
713
711
        1
714
712
        >>> clone = sb.clone()
715
 
        >>> commit(sb, "Committing second revision", verbose=False)
 
713
        >>> commit.commit(sb, "Committing second revision", verbose=False)
716
714
        >>> sb.common_ancestor(sb)[0]
717
715
        2
718
716
        >>> sb.common_ancestor(clone)[0]
719
717
        1
720
 
        >>> commit(clone, "Committing divergent second revision", 
 
718
        >>> commit.commit(clone, "Committing divergent second revision", 
721
719
        ...               verbose=False)
722
720
        >>> sb.common_ancestor(clone)[0]
723
721
        1
826
824
            count = 0
827
825
        self.append_revision(*revision_ids)
828
826
        ## note("Added %d revisions." % count)
829
 
        pb.clear()
830
827
 
 
828
        
831
829
    def install_revisions(self, other, revision_ids, pb):
832
830
        if hasattr(other.revision_store, "prefetch"):
833
831
            other.revision_store.prefetch(revision_ids)
864
862
                    
865
863
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
866
864
                                                    needed_texts)
867
 
        #print "Added %d texts." % count 
 
865
        print "Added %d texts." % count 
868
866
        inventory_ids = [ f.inventory_id for f in revisions ]
869
867
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
870
868
                                                         inventory_ids)
871
 
        #print "Added %d inventories." % count 
 
869
        print "Added %d inventories." % count 
872
870
        revision_ids = [ f.revision_id for f in revisions]
873
871
 
874
872
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
884
882
        
885
883
 
886
884
    def lookup_revision(self, revision):
887
 
        """Return the revision identifier for a given revision specifier."""
888
 
        # XXX: I'm not sure this method belongs here; I'd rather have the
889
 
        # revision spec stuff be an UI thing, and branch blissfully unaware
890
 
        # of it.
891
 
        # Also, I'm not entirely happy with this method returning None
892
 
        # when the revision doesn't exist.
893
 
        # But I'm keeping the contract I found, because this seems to be
894
 
        # used in a lot of places - and when I do change these, I'd rather
895
 
        # figure out case-by-case which ones actually want to care about
896
 
        # revision specs (eg, they are UI-level) and which ones should trust
897
 
        # that they have a revno/revid.
898
 
        #   -- lalo@exoweb.net, 2005-09-07
899
 
        from bzrlib.errors import NoSuchRevision
900
 
        from bzrlib.revisionspec import RevisionSpec
901
 
        try:
902
 
            spec = RevisionSpec(self, revision)
903
 
        except NoSuchRevision:
904
 
            return None
905
 
        return spec.rev_id
 
885
        """Return the revision identifier for a given revision information."""
 
886
        revno, info = self.get_revision_info(revision)
 
887
        return info
906
888
 
907
889
 
908
890
    def revision_id_to_revno(self, revision_id):
914
896
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
915
897
 
916
898
 
917
 
    def get_rev_id(self, revno, history=None):
918
 
        """Find the revision id of the specified revno."""
919
 
        if revno == 0:
920
 
            return None
921
 
        if history is None:
922
 
            history = self.revision_history()
923
 
        elif revno <= 0 or revno > len(history):
924
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
925
 
        return history[revno - 1]
 
899
    def get_revision_info(self, revision):
 
900
        """Return (revno, revision id) for revision identifier.
 
901
 
 
902
        revision can be an integer, in which case it is assumed to be revno (though
 
903
            this will translate negative values into positive ones)
 
904
        revision can also be a string, in which case it is parsed for something like
 
905
            'date:' or 'revid:' etc.
 
906
        """
 
907
        if revision is None:
 
908
            return 0, None
 
909
        revno = None
 
910
        try:# Convert to int if possible
 
911
            revision = int(revision)
 
912
        except ValueError:
 
913
            pass
 
914
        revs = self.revision_history()
 
915
        if isinstance(revision, int):
 
916
            if revision == 0:
 
917
                return 0, None
 
918
            # Mabye we should do this first, but we don't need it if revision == 0
 
919
            if revision < 0:
 
920
                revno = len(revs) + revision + 1
 
921
            else:
 
922
                revno = revision
 
923
        elif isinstance(revision, basestring):
 
924
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
925
                if revision.startswith(prefix):
 
926
                    revno = func(self, revs, revision)
 
927
                    break
 
928
            else:
 
929
                raise BzrError('No namespace registered for string: %r' % revision)
 
930
 
 
931
        if revno is None or revno <= 0 or revno > len(revs):
 
932
            raise BzrError("no such revision %s" % revision)
 
933
        return revno, revs[revno-1]
 
934
 
 
935
    def _namespace_revno(self, revs, revision):
 
936
        """Lookup a revision by revision number"""
 
937
        assert revision.startswith('revno:')
 
938
        try:
 
939
            return int(revision[6:])
 
940
        except ValueError:
 
941
            return None
 
942
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
943
 
 
944
    def _namespace_revid(self, revs, revision):
 
945
        assert revision.startswith('revid:')
 
946
        try:
 
947
            return revs.index(revision[6:]) + 1
 
948
        except ValueError:
 
949
            return None
 
950
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
951
 
 
952
    def _namespace_last(self, revs, revision):
 
953
        assert revision.startswith('last:')
 
954
        try:
 
955
            offset = int(revision[5:])
 
956
        except ValueError:
 
957
            return None
 
958
        else:
 
959
            if offset <= 0:
 
960
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
961
            return len(revs) - offset + 1
 
962
    REVISION_NAMESPACES['last:'] = _namespace_last
 
963
 
 
964
    def _namespace_tag(self, revs, revision):
 
965
        assert revision.startswith('tag:')
 
966
        raise BzrError('tag: namespace registered, but not implemented.')
 
967
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
968
 
 
969
    def _namespace_date(self, revs, revision):
 
970
        assert revision.startswith('date:')
 
971
        import datetime
 
972
        # Spec for date revisions:
 
973
        #   date:value
 
974
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
975
        #   it can also start with a '+/-/='. '+' says match the first
 
976
        #   entry after the given date. '-' is match the first entry before the date
 
977
        #   '=' is match the first entry after, but still on the given date.
 
978
        #
 
979
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
980
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
981
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
982
        #       May 13th, 2005 at 0:00
 
983
        #
 
984
        #   So the proper way of saying 'give me all entries for today' is:
 
985
        #       -r {date:+today}:{date:-tomorrow}
 
986
        #   The default is '=' when not supplied
 
987
        val = revision[5:]
 
988
        match_style = '='
 
989
        if val[:1] in ('+', '-', '='):
 
990
            match_style = val[:1]
 
991
            val = val[1:]
 
992
 
 
993
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
994
        if val.lower() == 'yesterday':
 
995
            dt = today - datetime.timedelta(days=1)
 
996
        elif val.lower() == 'today':
 
997
            dt = today
 
998
        elif val.lower() == 'tomorrow':
 
999
            dt = today + datetime.timedelta(days=1)
 
1000
        else:
 
1001
            import re
 
1002
            # This should be done outside the function to avoid recompiling it.
 
1003
            _date_re = re.compile(
 
1004
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
1005
                    r'(,|T)?\s*'
 
1006
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
1007
                )
 
1008
            m = _date_re.match(val)
 
1009
            if not m or (not m.group('date') and not m.group('time')):
 
1010
                raise BzrError('Invalid revision date %r' % revision)
 
1011
 
 
1012
            if m.group('date'):
 
1013
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
1014
            else:
 
1015
                year, month, day = today.year, today.month, today.day
 
1016
            if m.group('time'):
 
1017
                hour = int(m.group('hour'))
 
1018
                minute = int(m.group('minute'))
 
1019
                if m.group('second'):
 
1020
                    second = int(m.group('second'))
 
1021
                else:
 
1022
                    second = 0
 
1023
            else:
 
1024
                hour, minute, second = 0,0,0
 
1025
 
 
1026
            dt = datetime.datetime(year=year, month=month, day=day,
 
1027
                    hour=hour, minute=minute, second=second)
 
1028
        first = dt
 
1029
        last = None
 
1030
        reversed = False
 
1031
        if match_style == '-':
 
1032
            reversed = True
 
1033
        elif match_style == '=':
 
1034
            last = dt + datetime.timedelta(days=1)
 
1035
 
 
1036
        if reversed:
 
1037
            for i in range(len(revs)-1, -1, -1):
 
1038
                r = self.get_revision(revs[i])
 
1039
                # TODO: Handle timezone.
 
1040
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1041
                if first >= dt and (last is None or dt >= last):
 
1042
                    return i+1
 
1043
        else:
 
1044
            for i in range(len(revs)):
 
1045
                r = self.get_revision(revs[i])
 
1046
                # TODO: Handle timezone.
 
1047
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1048
                if first <= dt and (last is None or dt <= last):
 
1049
                    return i+1
 
1050
    REVISION_NAMESPACES['date:'] = _namespace_date
926
1051
 
927
1052
    def revision_tree(self, revision_id):
928
1053
        """Return Tree for a revision on this branch.
940
1065
 
941
1066
    def working_tree(self):
942
1067
        """Return a `Tree` for the working copy."""
943
 
        from bzrlib.workingtree import WorkingTree
 
1068
        from workingtree import WorkingTree
944
1069
        return WorkingTree(self.base, self.read_working_inventory())
945
1070
 
946
1071
 
992
1117
 
993
1118
            inv.rename(file_id, to_dir_id, to_tail)
994
1119
 
 
1120
            print "%s => %s" % (from_rel, to_rel)
 
1121
 
995
1122
            from_abs = self.abspath(from_rel)
996
1123
            to_abs = self.abspath(to_rel)
997
1124
            try:
1016
1143
 
1017
1144
        Note that to_name is only the last component of the new name;
1018
1145
        this doesn't change the directory.
1019
 
 
1020
 
        This returns a list of (from_path, to_path) pairs for each
1021
 
        entry that is moved.
1022
1146
        """
1023
 
        result = []
1024
1147
        self.lock_write()
1025
1148
        try:
1026
1149
            ## TODO: Option to move IDs only
1061
1184
            for f in from_paths:
1062
1185
                name_tail = splitpath(f)[-1]
1063
1186
                dest_path = appendpath(to_name, name_tail)
1064
 
                result.append((f, dest_path))
 
1187
                print "%s => %s" % (f, dest_path)
1065
1188
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1066
1189
                try:
1067
1190
                    os.rename(self.abspath(f), self.abspath(dest_path))
1073
1196
        finally:
1074
1197
            self.unlock()
1075
1198
 
1076
 
        return result
1077
 
 
1078
1199
 
1079
1200
    def revert(self, filenames, old_tree=None, backups=True):
1080
1201
        """Restore selected files to the versions from a previous tree.
1162
1283
            self.unlock()
1163
1284
 
1164
1285
 
1165
 
    def get_parent(self):
1166
 
        """Return the parent location of the branch.
1167
 
 
1168
 
        This is the default location for push/pull/missing.  The usual
1169
 
        pattern is that the user can override it by specifying a
1170
 
        location.
1171
 
        """
1172
 
        import errno
1173
 
        _locs = ['parent', 'pull', 'x-pull']
1174
 
        for l in _locs:
1175
 
            try:
1176
 
                return self.controlfile(l, 'r').read().strip('\n')
1177
 
            except IOError, e:
1178
 
                if e.errno != errno.ENOENT:
1179
 
                    raise
1180
 
        return None
1181
 
 
1182
 
 
1183
 
    def set_parent(self, url):
1184
 
        # TODO: Maybe delete old location files?
1185
 
        from bzrlib.atomicfile import AtomicFile
1186
 
        self.lock_write()
1187
 
        try:
1188
 
            f = AtomicFile(self.controlfilename('parent'))
1189
 
            try:
1190
 
                f.write(url + '\n')
1191
 
                f.commit()
1192
 
            finally:
1193
 
                f.close()
1194
 
        finally:
1195
 
            self.unlock()
1196
 
 
1197
 
    def check_revno(self, revno):
1198
 
        """\
1199
 
        Check whether a revno corresponds to any revision.
1200
 
        Zero (the NULL revision) is considered valid.
1201
 
        """
1202
 
        if revno != 0:
1203
 
            self.check_real_revno(revno)
1204
 
            
1205
 
    def check_real_revno(self, revno):
1206
 
        """\
1207
 
        Check whether a revno corresponds to a real revision.
1208
 
        Zero (the NULL revision) is considered invalid
1209
 
        """
1210
 
        if revno < 1 or revno > self.revno():
1211
 
            raise InvalidRevisionNumber(revno)
1212
 
        
1213
 
        
1214
 
 
1215
 
 
1216
 
class ScratchBranch(LocalBranch):
 
1286
 
 
1287
class ScratchBranch(Branch):
1217
1288
    """Special test class: a branch that cleans up after itself.
1218
1289
 
1219
1290
    >>> b = ScratchBranch()
1236
1307
        if base is None:
1237
1308
            base = mkdtemp()
1238
1309
            init = True
1239
 
        LocalBranch.__init__(self, base, init=init)
 
1310
        Branch.__init__(self, base, init=init)
1240
1311
        for d in dirs:
1241
1312
            os.mkdir(self.abspath(d))
1242
1313
            
1259
1330
        os.rmdir(base)
1260
1331
        copytree(self.base, base, symlinks=True)
1261
1332
        return ScratchBranch(base=base)
1262
 
 
1263
 
 
1264
1333
        
1265
1334
    def __del__(self):
1266
1335
        self.destroy()
1337
1406
    return gen_file_id('TREE_ROOT')
1338
1407
 
1339
1408
 
 
1409
def pull_loc(branch):
 
1410
    # TODO: Should perhaps just make attribute be 'base' in
 
1411
    # RemoteBranch and Branch?
 
1412
    if hasattr(branch, "baseurl"):
 
1413
        return branch.baseurl
 
1414
    else:
 
1415
        return branch.base
 
1416
 
 
1417
 
1340
1418
def copy_branch(branch_from, to_location, revision=None):
1341
1419
    """Copy branch_from into the existing directory to_location.
1342
1420
 
1343
 
    revision
1344
 
        If not None, only revisions up to this point will be copied.
1345
 
        The head of the new branch will be that revision.
1346
 
 
1347
 
    to_location
1348
 
        The name of a local directory that exists but is empty.
 
1421
    If revision is not None, the head of the new branch will be revision.
1349
1422
    """
1350
1423
    from bzrlib.merge import merge
1351
 
    from bzrlib.revisionspec import RevisionSpec
1352
 
 
1353
 
    assert isinstance(branch_from, Branch)
1354
 
    assert isinstance(to_location, basestring)
1355
 
    
 
1424
    from bzrlib.branch import Branch
1356
1425
    br_to = Branch(to_location, init=True)
1357
1426
    br_to.set_root_id(branch_from.get_root_id())
1358
1427
    if revision is None:
1359
1428
        revno = branch_from.revno()
1360
1429
    else:
1361
 
        revno, rev_id = RevisionSpec(branch_from, revision)
 
1430
        revno, rev_id = branch_from.get_revision_info(revision)
1362
1431
    br_to.update_revisions(branch_from, stop_revision=revno)
1363
1432
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1364
1433
          check_clean=False, ignore_zero=True)
1365
 
    
1366
 
    from_location = branch_from.base
1367
 
    br_to.set_parent(branch_from.base)
 
1434
    from_location = pull_loc(branch_from)
 
1435
    br_to.controlfile("x-pull", "wb").write(from_location + "\n")
1368
1436