/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-09-12 12:39:58 UTC
  • mfrom: (1185.3.4)
  • mto: (1185.1.10)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: robertc@robertcollins.net-20050912123958-7982e808f291f439
merge up with head

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
24
     splitpath, \
25
25
     sha_file, appendpath, file_kind
 
26
 
26
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
28
import bzrlib.errors
28
29
from bzrlib.textui import show_status
29
30
from bzrlib.revision import Revision
30
 
from bzrlib.xml import unpack_xml
31
31
from bzrlib.delta import compare_trees
32
32
from bzrlib.tree import EmptyTree, RevisionTree
33
 
        
 
33
import bzrlib.xml
 
34
import bzrlib.ui
 
35
 
 
36
 
 
37
 
34
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
39
## TODO: Maybe include checks for common corruption of newlines, etc?
36
40
 
39
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
40
44
# cache in memory to make this faster.
41
45
 
 
46
# TODO: please move the revision-string syntax stuff out of the branch
 
47
# object; it's clutter
 
48
 
42
49
 
43
50
def find_branch(f, **args):
44
51
    if f and (f.startswith('http://') or f.startswith('https://')):
101
108
    It is not necessary that f exists.
102
109
 
103
110
    Basically we keep looking up until we find the control directory or
104
 
    run into the root."""
 
111
    run into the root.  If there isn't one, raises NotBranchError.
 
112
    """
105
113
    if f == None:
106
114
        f = os.getcwd()
107
115
    elif hasattr(os.path, 'realpath'):
120
128
        head, tail = os.path.split(f)
121
129
        if head == f:
122
130
            # reached the root, whatever that may be
123
 
            raise BzrError('%r is not in a branch' % orig_f)
 
131
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
124
132
        f = head
125
 
    
 
133
 
 
134
 
 
135
 
 
136
# XXX: move into bzrlib.errors; subclass BzrError    
126
137
class DivergedBranches(Exception):
127
138
    def __init__(self, branch1, branch2):
128
139
        self.branch1 = branch1
162
173
    def __init__(self, base, init=False, find_root=True):
163
174
        """Create new branch object at a particular location.
164
175
 
165
 
        base -- Base directory for the branch.
 
176
        base -- Base directory for the branch. May be a file:// url.
166
177
        
167
178
        init -- If True, create new control files in a previously
168
179
             unversioned directory.  If False, the branch must already
181
192
        elif find_root:
182
193
            self.base = find_branch_root(base)
183
194
        else:
 
195
            if base.startswith("file://"):
 
196
                base = base[7:]
184
197
            self.base = os.path.realpath(base)
185
198
            if not isdir(self.controlfilename('.')):
186
199
                from errors import NotBranchError
207
220
            warn("branch %r was not explicitly unlocked" % self)
208
221
            self._lock.unlock()
209
222
 
210
 
 
211
 
 
212
223
    def lock_write(self):
213
224
        if self._lock_mode:
214
225
            if self._lock_mode != 'w':
224
235
            self._lock_count = 1
225
236
 
226
237
 
227
 
 
228
238
    def lock_read(self):
229
239
        if self._lock_mode:
230
240
            assert self._lock_mode in ('r', 'w'), \
237
247
            self._lock_mode = 'r'
238
248
            self._lock_count = 1
239
249
                        
240
 
 
241
 
            
242
250
    def unlock(self):
243
251
        if not self._lock_mode:
244
252
            from errors import LockError
251
259
            self._lock = None
252
260
            self._lock_mode = self._lock_count = None
253
261
 
254
 
 
255
262
    def abspath(self, name):
256
263
        """Return absolute filename for something in the branch"""
257
264
        return os.path.join(self.base, name)
258
265
 
259
 
 
260
266
    def relpath(self, path):
261
267
        """Return path relative to this branch of something inside it.
262
268
 
263
269
        Raises an error if path is not in this branch."""
264
270
        return _relpath(self.base, path)
265
271
 
266
 
 
267
272
    def controlfilename(self, file_or_path):
268
273
        """Return location relative to branch."""
269
274
        if isinstance(file_or_path, basestring):
296
301
        else:
297
302
            raise BzrError("invalid controlfile mode %r" % mode)
298
303
 
299
 
 
300
 
 
301
304
    def _make_control(self):
302
305
        from bzrlib.inventory import Inventory
303
 
        from bzrlib.xml import pack_xml
304
306
        
305
307
        os.mkdir(self.controlfilename([]))
306
308
        self.controlfile('README', 'w').write(
316
318
            self.controlfile(f, 'w').write('')
317
319
        mutter('created control directory in ' + self.base)
318
320
 
319
 
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
 
321
        # if we want per-tree root ids then this is the place to set
 
322
        # them; they're not needed for now and so ommitted for
 
323
        # simplicity.
 
324
        f = self.controlfile('inventory','w')
 
325
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
320
326
 
321
327
 
322
328
    def _check_format(self):
331
337
        # on Windows from Linux and so on.  I think it might be better
332
338
        # to always make all internal files in unix format.
333
339
        fmt = self.controlfile('branch-format', 'r').read()
334
 
        fmt.replace('\r\n', '')
 
340
        fmt = fmt.replace('\r\n', '\n')
335
341
        if fmt != BZR_BRANCH_FORMAT:
336
342
            raise BzrError('sorry, branch format %r not supported' % fmt,
337
343
                           ['use a different bzr version',
357
363
    def read_working_inventory(self):
358
364
        """Read the working inventory."""
359
365
        from bzrlib.inventory import Inventory
360
 
        from bzrlib.xml import unpack_xml
361
 
        from time import time
362
 
        before = time()
363
366
        self.lock_read()
364
367
        try:
365
368
            # ElementTree does its own conversion from UTF-8, so open in
366
369
            # binary.
367
 
            inv = unpack_xml(Inventory,
368
 
                             self.controlfile('inventory', 'rb'))
369
 
            mutter("loaded inventory of %d items in %f"
370
 
                   % (len(inv), time() - before))
371
 
            return inv
 
370
            f = self.controlfile('inventory', 'rb')
 
371
            return bzrlib.xml.serializer_v4.read_inventory(f)
372
372
        finally:
373
373
            self.unlock()
374
374
            
380
380
        will be committed to the next revision.
381
381
        """
382
382
        from bzrlib.atomicfile import AtomicFile
383
 
        from bzrlib.xml import pack_xml
384
383
        
385
384
        self.lock_write()
386
385
        try:
387
386
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
388
387
            try:
389
 
                pack_xml(inv, f)
 
388
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
390
389
                f.commit()
391
390
            finally:
392
391
                f.close()
400
399
                         """Inventory for the working copy.""")
401
400
 
402
401
 
403
 
    def add(self, files, verbose=False, ids=None):
 
402
    def add(self, files, ids=None):
404
403
        """Make files versioned.
405
404
 
406
 
        Note that the command line normally calls smart_add instead.
 
405
        Note that the command line normally calls smart_add instead,
 
406
        which can automatically recurse.
407
407
 
408
408
        This puts the files in the Added state, so that they will be
409
409
        recorded by the next commit.
419
419
        TODO: Perhaps have an option to add the ids even if the files do
420
420
              not (yet) exist.
421
421
 
422
 
        TODO: Perhaps return the ids of the files?  But then again it
423
 
              is easy to retrieve them if they're needed.
424
 
 
425
 
        TODO: Adding a directory should optionally recurse down and
426
 
              add all non-ignored children.  Perhaps do that in a
427
 
              higher-level method.
 
422
        TODO: Perhaps yield the ids and paths as they're added.
428
423
        """
429
424
        # TODO: Re-adding a file that is removed in the working copy
430
425
        # should probably put it back with the previous ID.
466
461
                    file_id = gen_file_id(f)
467
462
                inv.add_path(f, kind=kind, file_id=file_id)
468
463
 
469
 
                if verbose:
470
 
                    print 'added', quotefn(f)
471
 
 
472
464
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
473
465
 
474
466
            self._write_inventory(inv)
584
576
            f.close()
585
577
 
586
578
 
587
 
    def get_revision_xml(self, revision_id):
 
579
    def get_revision_xml_file(self, revision_id):
588
580
        """Return XML file object for revision object."""
589
581
        if not revision_id or not isinstance(revision_id, basestring):
590
582
            raise InvalidRevisionId(revision_id)
594
586
            try:
595
587
                return self.revision_store[revision_id]
596
588
            except IndexError:
597
 
                raise bzrlib.errors.NoSuchRevision(revision_id)
 
589
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
598
590
        finally:
599
591
            self.unlock()
600
592
 
601
593
 
 
594
    #deprecated
 
595
    get_revision_xml = get_revision_xml_file
 
596
 
 
597
 
602
598
    def get_revision(self, revision_id):
603
599
        """Return the Revision object for a named revision"""
604
 
        xml_file = self.get_revision_xml(revision_id)
 
600
        xml_file = self.get_revision_xml_file(revision_id)
605
601
 
606
602
        try:
607
 
            r = unpack_xml(Revision, xml_file)
 
603
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
608
604
        except SyntaxError, e:
609
605
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
610
606
                                         [revision_id,
655
651
               parameter which can be either an integer revno or a
656
652
               string hash."""
657
653
        from bzrlib.inventory import Inventory
658
 
        from bzrlib.xml import unpack_xml
659
 
 
660
 
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
 
654
 
 
655
        f = self.get_inventory_xml_file(inventory_id)
 
656
        return bzrlib.xml.serializer_v4.read_inventory(f)
 
657
 
 
658
 
 
659
    def get_inventory_xml(self, inventory_id):
 
660
        """Get inventory XML as a file object."""
 
661
        return self.inventory_store[inventory_id]
 
662
 
 
663
    get_inventory_xml_file = get_inventory_xml
661
664
            
662
665
 
663
666
    def get_inventory_sha1(self, inventory_id):
664
667
        """Return the sha1 hash of the inventory entry
665
668
        """
666
 
        return sha_file(self.inventory_store[inventory_id])
 
669
        return sha_file(self.get_inventory_xml(inventory_id))
667
670
 
668
671
 
669
672
    def get_revision_inventory(self, revision_id):
755
758
            return None
756
759
 
757
760
 
758
 
    def missing_revisions(self, other, stop_revision=None):
 
761
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
759
762
        """
760
763
        If self and other have not diverged, return a list of the revisions
761
764
        present in other, but missing from self.
794
797
        if stop_revision is None:
795
798
            stop_revision = other_len
796
799
        elif stop_revision > other_len:
797
 
            raise NoSuchRevision(self, stop_revision)
 
800
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
798
801
        
799
802
        return other_history[self_len:stop_revision]
800
803
 
801
804
 
802
805
    def update_revisions(self, other, stop_revision=None):
803
806
        """Pull in all new revisions from other branch.
804
 
        
805
 
        >>> from bzrlib.commit import commit
806
 
        >>> bzrlib.trace.silent = True
807
 
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
808
 
        >>> br1.add('foo')
809
 
        >>> br1.add('bar')
810
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
811
 
        >>> br2 = ScratchBranch()
812
 
        >>> br2.update_revisions(br1)
813
 
        Added 2 texts.
814
 
        Added 1 inventories.
815
 
        Added 1 revisions.
816
 
        >>> br2.revision_history()
817
 
        [u'REVISION-ID-1']
818
 
        >>> br2.update_revisions(br1)
819
 
        Added 0 texts.
820
 
        Added 0 inventories.
821
 
        Added 0 revisions.
822
 
        >>> br1.text_store.total_size() == br2.text_store.total_size()
823
 
        True
824
807
        """
825
 
        from bzrlib.progress import ProgressBar
826
 
 
827
 
        pb = ProgressBar()
828
 
 
 
808
        from bzrlib.fetch import greedy_fetch
 
809
        from bzrlib.revision import get_intervening_revisions
 
810
 
 
811
        pb = bzrlib.ui.ui_factory.progress_bar()
829
812
        pb.update('comparing histories')
830
 
        revision_ids = self.missing_revisions(other, stop_revision)
831
 
 
 
813
 
 
814
        try:
 
815
            revision_ids = self.missing_revisions(other, stop_revision)
 
816
        except DivergedBranches, e:
 
817
            try:
 
818
                if stop_revision is None:
 
819
                    end_revision = other.last_patch()
 
820
                revision_ids = get_intervening_revisions(self.last_patch(), 
 
821
                                                         end_revision, other)
 
822
                assert self.last_patch() not in revision_ids
 
823
            except bzrlib.errors.NotAncestor:
 
824
                raise e
 
825
 
 
826
        if len(revision_ids) > 0:
 
827
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
 
828
        else:
 
829
            count = 0
 
830
        self.append_revision(*revision_ids)
 
831
        ## note("Added %d revisions." % count)
 
832
        pb.clear()
 
833
 
 
834
    def install_revisions(self, other, revision_ids, pb):
832
835
        if hasattr(other.revision_store, "prefetch"):
833
836
            other.revision_store.prefetch(revision_ids)
834
837
        if hasattr(other.inventory_store, "prefetch"):
835
838
            inventory_ids = [other.get_revision(r).inventory_id
836
839
                             for r in revision_ids]
837
840
            other.inventory_store.prefetch(inventory_ids)
 
841
 
 
842
        if pb is None:
 
843
            pb = bzrlib.ui.ui_factory.progress_bar()
838
844
                
839
845
        revisions = []
840
846
        needed_texts = set()
841
847
        i = 0
842
 
        for rev_id in revision_ids:
843
 
            i += 1
844
 
            pb.update('fetching revision', i, len(revision_ids))
845
 
            rev = other.get_revision(rev_id)
 
848
 
 
849
        failures = set()
 
850
        for i, rev_id in enumerate(revision_ids):
 
851
            pb.update('fetching revision', i+1, len(revision_ids))
 
852
            try:
 
853
                rev = other.get_revision(rev_id)
 
854
            except bzrlib.errors.NoSuchRevision:
 
855
                failures.add(rev_id)
 
856
                continue
 
857
 
846
858
            revisions.append(rev)
847
859
            inv = other.get_inventory(str(rev.inventory_id))
848
860
            for key, entry in inv.iter_entries():
853
865
 
854
866
        pb.clear()
855
867
                    
856
 
        count = self.text_store.copy_multi(other.text_store, needed_texts)
857
 
        print "Added %d texts." % count 
 
868
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
869
                                                    needed_texts)
 
870
        #print "Added %d texts." % count 
858
871
        inventory_ids = [ f.inventory_id for f in revisions ]
859
 
        count = self.inventory_store.copy_multi(other.inventory_store, 
860
 
                                                inventory_ids)
861
 
        print "Added %d inventories." % count 
 
872
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
873
                                                         inventory_ids)
 
874
        #print "Added %d inventories." % count 
862
875
        revision_ids = [ f.revision_id for f in revisions]
863
 
        count = self.revision_store.copy_multi(other.revision_store, 
864
 
                                               revision_ids)
865
 
        for revision_id in revision_ids:
866
 
            self.append_revision(revision_id)
867
 
        print "Added %d revisions." % count
868
 
                    
869
 
        
 
876
 
 
877
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
878
                                                          revision_ids,
 
879
                                                          permit_failure=True)
 
880
        assert len(cp_fail) == 0 
 
881
        return count, failures
 
882
       
 
883
 
870
884
    def commit(self, *args, **kw):
871
885
        from bzrlib.commit import commit
872
886
        commit(self, *args, **kw)
874
888
 
875
889
    def lookup_revision(self, revision):
876
890
        """Return the revision identifier for a given revision information."""
877
 
        revno, info = self.get_revision_info(revision)
 
891
        revno, info = self._get_revision_info(revision)
878
892
        return info
879
893
 
 
894
 
 
895
    def revision_id_to_revno(self, revision_id):
 
896
        """Given a revision id, return its revno"""
 
897
        history = self.revision_history()
 
898
        try:
 
899
            return history.index(revision_id) + 1
 
900
        except ValueError:
 
901
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
902
 
 
903
 
880
904
    def get_revision_info(self, revision):
881
905
        """Return (revno, revision id) for revision identifier.
882
906
 
885
909
        revision can also be a string, in which case it is parsed for something like
886
910
            'date:' or 'revid:' etc.
887
911
        """
 
912
        revno, rev_id = self._get_revision_info(revision)
 
913
        if revno is None:
 
914
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
915
        return revno, rev_id
 
916
 
 
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]
 
926
 
 
927
    def _get_revision_info(self, revision):
 
928
        """Return (revno, revision id) for revision specifier.
 
929
 
 
930
        revision can be an integer, in which case it is assumed to be revno
 
931
        (though this will translate negative values into positive ones)
 
932
        revision can also be a string, in which case it is parsed for something
 
933
        like 'date:' or 'revid:' etc.
 
934
 
 
935
        A revid is always returned.  If it is None, the specifier referred to
 
936
        the null revision.  If the revid does not occur in the revision
 
937
        history, revno will be None.
 
938
        """
 
939
        
888
940
        if revision is None:
889
941
            return 0, None
890
942
        revno = None
894
946
            pass
895
947
        revs = self.revision_history()
896
948
        if isinstance(revision, int):
897
 
            if revision == 0:
898
 
                return 0, None
899
 
            # Mabye we should do this first, but we don't need it if revision == 0
900
949
            if revision < 0:
901
950
                revno = len(revs) + revision + 1
902
951
            else:
903
952
                revno = revision
 
953
            rev_id = self.get_rev_id(revno, revs)
904
954
        elif isinstance(revision, basestring):
905
955
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
956
                if revision.startswith(prefix):
907
 
                    revno = func(self, revs, revision)
 
957
                    result = func(self, revs, revision)
 
958
                    if len(result) > 1:
 
959
                        revno, rev_id = result
 
960
                    else:
 
961
                        revno = result[0]
 
962
                        rev_id = self.get_rev_id(revno, revs)
908
963
                    break
909
964
            else:
910
 
                raise BzrError('No namespace registered for string: %r' % revision)
 
965
                raise BzrError('No namespace registered for string: %r' %
 
966
                               revision)
 
967
        else:
 
968
            raise TypeError('Unhandled revision type %s' % revision)
911
969
 
912
 
        if revno is None or revno <= 0 or revno > len(revs):
913
 
            raise BzrError("no such revision %s" % revision)
914
 
        return revno, revs[revno-1]
 
970
        if revno is None:
 
971
            if rev_id is None:
 
972
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
973
        return revno, rev_id
915
974
 
916
975
    def _namespace_revno(self, revs, revision):
917
976
        """Lookup a revision by revision number"""
918
977
        assert revision.startswith('revno:')
919
978
        try:
920
 
            return int(revision[6:])
 
979
            return (int(revision[6:]),)
921
980
        except ValueError:
922
981
            return None
923
982
    REVISION_NAMESPACES['revno:'] = _namespace_revno
924
983
 
925
984
    def _namespace_revid(self, revs, revision):
926
985
        assert revision.startswith('revid:')
 
986
        rev_id = revision[len('revid:'):]
927
987
        try:
928
 
            return revs.index(revision[6:]) + 1
 
988
            return revs.index(rev_id) + 1, rev_id
929
989
        except ValueError:
930
 
            return None
 
990
            return None, rev_id
931
991
    REVISION_NAMESPACES['revid:'] = _namespace_revid
932
992
 
933
993
    def _namespace_last(self, revs, revision):
935
995
        try:
936
996
            offset = int(revision[5:])
937
997
        except ValueError:
938
 
            return None
 
998
            return (None,)
939
999
        else:
940
1000
            if offset <= 0:
941
1001
                raise BzrError('You must supply a positive value for --revision last:XXX')
942
 
            return len(revs) - offset + 1
 
1002
            return (len(revs) - offset + 1,)
943
1003
    REVISION_NAMESPACES['last:'] = _namespace_last
944
1004
 
945
1005
    def _namespace_tag(self, revs, revision):
1020
1080
                # TODO: Handle timezone.
1021
1081
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1022
1082
                if first >= dt and (last is None or dt >= last):
1023
 
                    return i+1
 
1083
                    return (i+1,)
1024
1084
        else:
1025
1085
            for i in range(len(revs)):
1026
1086
                r = self.get_revision(revs[i])
1027
1087
                # TODO: Handle timezone.
1028
1088
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1029
1089
                if first <= dt and (last is None or dt <= last):
1030
 
                    return i+1
 
1090
                    return (i+1,)
1031
1091
    REVISION_NAMESPACES['date:'] = _namespace_date
1032
1092
 
1033
1093
    def revision_tree(self, revision_id):
1098
1158
 
1099
1159
            inv.rename(file_id, to_dir_id, to_tail)
1100
1160
 
1101
 
            print "%s => %s" % (from_rel, to_rel)
1102
 
 
1103
1161
            from_abs = self.abspath(from_rel)
1104
1162
            to_abs = self.abspath(to_rel)
1105
1163
            try:
1124
1182
 
1125
1183
        Note that to_name is only the last component of the new name;
1126
1184
        this doesn't change the directory.
 
1185
 
 
1186
        This returns a list of (from_path, to_path) pairs for each
 
1187
        entry that is moved.
1127
1188
        """
 
1189
        result = []
1128
1190
        self.lock_write()
1129
1191
        try:
1130
1192
            ## TODO: Option to move IDs only
1165
1227
            for f in from_paths:
1166
1228
                name_tail = splitpath(f)[-1]
1167
1229
                dest_path = appendpath(to_name, name_tail)
1168
 
                print "%s => %s" % (f, dest_path)
 
1230
                result.append((f, dest_path))
1169
1231
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1170
1232
                try:
1171
1233
                    os.rename(self.abspath(f), self.abspath(dest_path))
1177
1239
        finally:
1178
1240
            self.unlock()
1179
1241
 
 
1242
        return result
 
1243
 
1180
1244
 
1181
1245
    def revert(self, filenames, old_tree=None, backups=True):
1182
1246
        """Restore selected files to the versions from a previous tree.
1264
1328
            self.unlock()
1265
1329
 
1266
1330
 
 
1331
    def get_parent(self):
 
1332
        """Return the parent location of the branch.
 
1333
 
 
1334
        This is the default location for push/pull/missing.  The usual
 
1335
        pattern is that the user can override it by specifying a
 
1336
        location.
 
1337
        """
 
1338
        import errno
 
1339
        _locs = ['parent', 'pull', 'x-pull']
 
1340
        for l in _locs:
 
1341
            try:
 
1342
                return self.controlfile(l, 'r').read().strip('\n')
 
1343
            except IOError, e:
 
1344
                if e.errno != errno.ENOENT:
 
1345
                    raise
 
1346
        return None
 
1347
 
 
1348
 
 
1349
    def set_parent(self, url):
 
1350
        # TODO: Maybe delete old location files?
 
1351
        from bzrlib.atomicfile import AtomicFile
 
1352
        self.lock_write()
 
1353
        try:
 
1354
            f = AtomicFile(self.controlfilename('parent'))
 
1355
            try:
 
1356
                f.write(url + '\n')
 
1357
                f.commit()
 
1358
            finally:
 
1359
                f.close()
 
1360
        finally:
 
1361
            self.unlock()
 
1362
 
 
1363
    def check_revno(self, revno):
 
1364
        """\
 
1365
        Check whether a revno corresponds to any revision.
 
1366
        Zero (the NULL revision) is considered valid.
 
1367
        """
 
1368
        if revno != 0:
 
1369
            self.check_real_revno(revno)
 
1370
            
 
1371
    def check_real_revno(self, revno):
 
1372
        """\
 
1373
        Check whether a revno corresponds to a real revision.
 
1374
        Zero (the NULL revision) is considered invalid
 
1375
        """
 
1376
        if revno < 1 or revno > self.revno():
 
1377
            raise InvalidRevisionNumber(revno)
 
1378
        
 
1379
        
 
1380
 
1267
1381
 
1268
1382
class ScratchBranch(Branch):
1269
1383
    """Special test class: a branch that cleans up after itself.
1311
1425
        os.rmdir(base)
1312
1426
        copytree(self.base, base, symlinks=True)
1313
1427
        return ScratchBranch(base=base)
 
1428
 
 
1429
 
1314
1430
        
1315
1431
    def __del__(self):
1316
1432
        self.destroy()
1386
1502
    """Return a new tree-root file id."""
1387
1503
    return gen_file_id('TREE_ROOT')
1388
1504
 
 
1505
 
 
1506
def pull_loc(branch):
 
1507
    # TODO: Should perhaps just make attribute be 'base' in
 
1508
    # RemoteBranch and Branch?
 
1509
    if hasattr(branch, "baseurl"):
 
1510
        return branch.baseurl
 
1511
    else:
 
1512
        return branch.base
 
1513
 
 
1514
 
 
1515
def copy_branch(branch_from, to_location, revision=None):
 
1516
    """Copy branch_from into the existing directory to_location.
 
1517
 
 
1518
    revision
 
1519
        If not None, only revisions up to this point will be copied.
 
1520
        The head of the new branch will be that revision.
 
1521
 
 
1522
    to_location
 
1523
        The name of a local directory that exists but is empty.
 
1524
    """
 
1525
    from bzrlib.merge import merge
 
1526
    from bzrlib.branch import Branch
 
1527
 
 
1528
    assert isinstance(branch_from, Branch)
 
1529
    assert isinstance(to_location, basestring)
 
1530
    
 
1531
    br_to = Branch(to_location, init=True)
 
1532
    br_to.set_root_id(branch_from.get_root_id())
 
1533
    if revision is None:
 
1534
        revno = branch_from.revno()
 
1535
    else:
 
1536
        revno, rev_id = branch_from.get_revision_info(revision)
 
1537
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1538
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1539
          check_clean=False, ignore_zero=True)
 
1540
    
 
1541
    from_location = pull_loc(branch_from)
 
1542
    br_to.set_parent(pull_loc(branch_from))