~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-08-04 19:31:20 UTC
  • Revision ID: mbp@sourcefrog.net-20050804193119-5be8d5233c4bef96
- move in tutorial from web site
  needs more updates

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
 
 
27
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
27
import bzrlib.errors
29
28
from bzrlib.textui import show_status
31
30
from bzrlib.xml import unpack_xml
32
31
from bzrlib.delta import compare_trees
33
32
from bzrlib.tree import EmptyTree, RevisionTree
34
 
import bzrlib.ui
35
 
 
36
 
 
37
 
 
 
33
        
38
34
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
35
## TODO: Maybe include checks for common corruption of newlines, etc?
40
36
 
43
39
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
40
# cache in memory to make this faster.
45
41
 
46
 
# TODO: please move the revision-string syntax stuff out of the branch
47
 
# object; it's clutter
48
 
 
49
42
 
50
43
def find_branch(f, **args):
51
44
    if f and (f.startswith('http://') or f.startswith('https://')):
108
101
    It is not necessary that f exists.
109
102
 
110
103
    Basically we keep looking up until we find the control directory or
111
 
    run into the root.  If there isn't one, raises NotBranchError.
112
 
    """
 
104
    run into the root."""
113
105
    if f == None:
114
106
        f = os.getcwd()
115
107
    elif hasattr(os.path, 'realpath'):
128
120
        head, tail = os.path.split(f)
129
121
        if head == f:
130
122
            # reached the root, whatever that may be
131
 
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
 
123
            raise BzrError('%r is not in a branch' % orig_f)
132
124
        f = head
133
 
 
134
 
 
135
 
 
136
 
# XXX: move into bzrlib.errors; subclass BzrError    
 
125
    
137
126
class DivergedBranches(Exception):
138
127
    def __init__(self, branch1, branch2):
139
128
        self.branch1 = branch1
141
130
        Exception.__init__(self, "These branches have diverged.")
142
131
 
143
132
 
 
133
class NoSuchRevision(BzrError):
 
134
    def __init__(self, branch, revision):
 
135
        self.branch = branch
 
136
        self.revision = revision
 
137
        msg = "Branch %s has no revision %d" % (branch, revision)
 
138
        BzrError.__init__(self, msg)
 
139
 
 
140
 
144
141
######################################################################
145
142
# branch objects
146
143
 
219
216
            self._lock.unlock()
220
217
 
221
218
 
 
219
 
222
220
    def lock_write(self):
223
221
        if self._lock_mode:
224
222
            if self._lock_mode != 'w':
234
232
            self._lock_count = 1
235
233
 
236
234
 
 
235
 
237
236
    def lock_read(self):
238
237
        if self._lock_mode:
239
238
            assert self._lock_mode in ('r', 'w'), \
246
245
            self._lock_mode = 'r'
247
246
            self._lock_count = 1
248
247
                        
 
248
 
 
249
            
249
250
    def unlock(self):
250
251
        if not self._lock_mode:
251
252
            from errors import LockError
258
259
            self._lock = None
259
260
            self._lock_mode = self._lock_count = None
260
261
 
 
262
 
261
263
    def abspath(self, name):
262
264
        """Return absolute filename for something in the branch"""
263
265
        return os.path.join(self.base, name)
264
266
 
 
267
 
265
268
    def relpath(self, path):
266
269
        """Return path relative to this branch of something inside it.
267
270
 
268
271
        Raises an error if path is not in this branch."""
269
272
        return _relpath(self.base, path)
270
273
 
 
274
 
271
275
    def controlfilename(self, file_or_path):
272
276
        """Return location relative to branch."""
273
277
        if isinstance(file_or_path, basestring):
300
304
        else:
301
305
            raise BzrError("invalid controlfile mode %r" % mode)
302
306
 
 
307
 
 
308
 
303
309
    def _make_control(self):
304
310
        from bzrlib.inventory import Inventory
305
311
        from bzrlib.xml import pack_xml
318
324
            self.controlfile(f, 'w').write('')
319
325
        mutter('created control directory in ' + self.base)
320
326
 
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
 
        pack_xml(Inventory(), self.controlfile('inventory','w'))
 
327
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
 
328
 
325
329
 
326
330
    def _check_format(self):
327
331
        """Check this branch format is supported.
598
602
            try:
599
603
                return self.revision_store[revision_id]
600
604
            except IndexError:
601
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
605
                raise bzrlib.errors.RevisionNotPresent(revision_id)
602
606
        finally:
603
607
            self.unlock()
604
608
 
661
665
        from bzrlib.inventory import Inventory
662
666
        from bzrlib.xml import unpack_xml
663
667
 
664
 
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
665
 
 
666
 
 
667
 
    def get_inventory_xml(self, inventory_id):
668
 
        """Get inventory XML as a file object."""
669
 
        return self.inventory_store[inventory_id]
 
668
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
670
669
            
671
670
 
672
671
    def get_inventory_sha1(self, inventory_id):
673
672
        """Return the sha1 hash of the inventory entry
674
673
        """
675
 
        return sha_file(self.get_inventory_xml(inventory_id))
 
674
        return sha_file(self.inventory_store[inventory_id])
676
675
 
677
676
 
678
677
    def get_revision_inventory(self, revision_id):
764
763
            return None
765
764
 
766
765
 
767
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
766
    def missing_revisions(self, other, stop_revision=None):
768
767
        """
769
768
        If self and other have not diverged, return a list of the revisions
770
769
        present in other, but missing from self.
803
802
        if stop_revision is None:
804
803
            stop_revision = other_len
805
804
        elif stop_revision > other_len:
806
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
805
            raise NoSuchRevision(self, stop_revision)
807
806
        
808
807
        return other_history[self_len:stop_revision]
809
808
 
810
809
 
811
810
    def update_revisions(self, other, stop_revision=None):
812
811
        """Pull in all new revisions from other branch.
 
812
        
 
813
        >>> from bzrlib.commit import commit
 
814
        >>> bzrlib.trace.silent = True
 
815
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
 
816
        >>> br1.add('foo')
 
817
        >>> br1.add('bar')
 
818
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
 
819
        >>> br2 = ScratchBranch()
 
820
        >>> br2.update_revisions(br1)
 
821
        Added 2 texts.
 
822
        Added 1 inventories.
 
823
        Added 1 revisions.
 
824
        >>> br2.revision_history()
 
825
        [u'REVISION-ID-1']
 
826
        >>> br2.update_revisions(br1)
 
827
        Added 0 texts.
 
828
        Added 0 inventories.
 
829
        Added 0 revisions.
 
830
        >>> br1.text_store.total_size() == br2.text_store.total_size()
 
831
        True
813
832
        """
814
 
        from bzrlib.fetch import greedy_fetch
815
 
 
816
 
        pb = bzrlib.ui.ui_factory.progress_bar()
 
833
        from bzrlib.progress import ProgressBar
 
834
 
 
835
        pb = ProgressBar()
 
836
 
817
837
        pb.update('comparing histories')
818
 
 
819
838
        revision_ids = self.missing_revisions(other, stop_revision)
820
839
 
821
 
        if len(revision_ids) > 0:
822
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
823
 
        else:
824
 
            count = 0
825
 
        self.append_revision(*revision_ids)
826
 
        ## note("Added %d revisions." % count)
827
 
        pb.clear()
828
 
 
829
 
    def install_revisions(self, other, revision_ids, pb):
830
840
        if hasattr(other.revision_store, "prefetch"):
831
841
            other.revision_store.prefetch(revision_ids)
832
842
        if hasattr(other.inventory_store, "prefetch"):
833
843
            inventory_ids = [other.get_revision(r).inventory_id
834
844
                             for r in revision_ids]
835
845
            other.inventory_store.prefetch(inventory_ids)
836
 
 
837
 
        if pb is None:
838
 
            pb = bzrlib.ui.ui_factory.progress_bar()
839
846
                
840
847
        revisions = []
841
848
        needed_texts = set()
842
849
        i = 0
843
 
 
844
 
        failures = set()
845
 
        for i, rev_id in enumerate(revision_ids):
846
 
            pb.update('fetching revision', i+1, len(revision_ids))
847
 
            try:
848
 
                rev = other.get_revision(rev_id)
849
 
            except bzrlib.errors.NoSuchRevision:
850
 
                failures.add(rev_id)
851
 
                continue
852
 
 
 
850
        for rev_id in revision_ids:
 
851
            i += 1
 
852
            pb.update('fetching revision', i, len(revision_ids))
 
853
            rev = other.get_revision(rev_id)
853
854
            revisions.append(rev)
854
855
            inv = other.get_inventory(str(rev.inventory_id))
855
856
            for key, entry in inv.iter_entries():
860
861
 
861
862
        pb.clear()
862
863
                    
863
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
864
 
                                                    needed_texts)
865
 
        #print "Added %d texts." % count 
 
864
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
865
        print "Added %d texts." % count 
866
866
        inventory_ids = [ f.inventory_id for f in revisions ]
867
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
868
 
                                                         inventory_ids)
869
 
        #print "Added %d inventories." % count 
 
867
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
868
                                                inventory_ids)
 
869
        print "Added %d inventories." % count 
870
870
        revision_ids = [ f.revision_id for f in revisions]
871
 
 
872
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
873
 
                                                          revision_ids,
874
 
                                                          permit_failure=True)
875
 
        assert len(cp_fail) == 0 
876
 
        return count, failures
877
 
       
878
 
 
 
871
        count = self.revision_store.copy_multi(other.revision_store, 
 
872
                                               revision_ids)
 
873
        for revision_id in revision_ids:
 
874
            self.append_revision(revision_id)
 
875
        print "Added %d revisions." % count
 
876
                    
 
877
        
879
878
    def commit(self, *args, **kw):
880
879
        from bzrlib.commit import commit
881
880
        commit(self, *args, **kw)
883
882
 
884
883
    def lookup_revision(self, revision):
885
884
        """Return the revision identifier for a given revision information."""
886
 
        revno, info = self._get_revision_info(revision)
 
885
        revno, info = self.get_revision_info(revision)
887
886
        return info
888
887
 
889
 
 
890
 
    def revision_id_to_revno(self, revision_id):
891
 
        """Given a revision id, return its revno"""
892
 
        history = self.revision_history()
893
 
        try:
894
 
            return history.index(revision_id) + 1
895
 
        except ValueError:
896
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
897
 
 
898
 
 
899
888
    def get_revision_info(self, revision):
900
889
        """Return (revno, revision id) for revision identifier.
901
890
 
904
893
        revision can also be a string, in which case it is parsed for something like
905
894
            'date:' or 'revid:' etc.
906
895
        """
907
 
        revno, rev_id = self._get_revision_info(revision)
908
 
        if revno is None:
909
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
910
 
        return revno, rev_id
911
 
 
912
 
    def get_rev_id(self, revno, history=None):
913
 
        """Find the revision id of the specified revno."""
914
 
        if revno == 0:
915
 
            return None
916
 
        if history is None:
917
 
            history = self.revision_history()
918
 
        elif revno <= 0 or revno > len(history):
919
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
920
 
        return history[revno - 1]
921
 
 
922
 
    def _get_revision_info(self, revision):
923
 
        """Return (revno, revision id) for revision specifier.
924
 
 
925
 
        revision can be an integer, in which case it is assumed to be revno
926
 
        (though this will translate negative values into positive ones)
927
 
        revision can also be a string, in which case it is parsed for something
928
 
        like 'date:' or 'revid:' etc.
929
 
 
930
 
        A revid is always returned.  If it is None, the specifier referred to
931
 
        the null revision.  If the revid does not occur in the revision
932
 
        history, revno will be None.
933
 
        """
934
 
        
935
896
        if revision is None:
936
897
            return 0, None
937
898
        revno = None
941
902
            pass
942
903
        revs = self.revision_history()
943
904
        if isinstance(revision, int):
 
905
            if revision == 0:
 
906
                return 0, None
 
907
            # Mabye we should do this first, but we don't need it if revision == 0
944
908
            if revision < 0:
945
909
                revno = len(revs) + revision + 1
946
910
            else:
947
911
                revno = revision
948
 
            rev_id = self.get_rev_id(revno, revs)
949
912
        elif isinstance(revision, basestring):
950
913
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
951
914
                if revision.startswith(prefix):
952
 
                    result = func(self, revs, revision)
953
 
                    if len(result) > 1:
954
 
                        revno, rev_id = result
955
 
                    else:
956
 
                        revno = result[0]
957
 
                        rev_id = self.get_rev_id(revno, revs)
 
915
                    revno = func(self, revs, revision)
958
916
                    break
959
917
            else:
960
 
                raise BzrError('No namespace registered for string: %r' %
961
 
                               revision)
962
 
        else:
963
 
            raise TypeError('Unhandled revision type %s' % revision)
 
918
                raise BzrError('No namespace registered for string: %r' % revision)
964
919
 
965
 
        if revno is None:
966
 
            if rev_id is None:
967
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
968
 
        return revno, rev_id
 
920
        if revno is None or revno <= 0 or revno > len(revs):
 
921
            raise BzrError("no such revision %s" % revision)
 
922
        return revno, revs[revno-1]
969
923
 
970
924
    def _namespace_revno(self, revs, revision):
971
925
        """Lookup a revision by revision number"""
972
926
        assert revision.startswith('revno:')
973
927
        try:
974
 
            return (int(revision[6:]),)
 
928
            return int(revision[6:])
975
929
        except ValueError:
976
930
            return None
977
931
    REVISION_NAMESPACES['revno:'] = _namespace_revno
978
932
 
979
933
    def _namespace_revid(self, revs, revision):
980
934
        assert revision.startswith('revid:')
981
 
        rev_id = revision[len('revid:'):]
982
935
        try:
983
 
            return revs.index(rev_id) + 1, rev_id
 
936
            return revs.index(revision[6:]) + 1
984
937
        except ValueError:
985
 
            return None, rev_id
 
938
            return None
986
939
    REVISION_NAMESPACES['revid:'] = _namespace_revid
987
940
 
988
941
    def _namespace_last(self, revs, revision):
990
943
        try:
991
944
            offset = int(revision[5:])
992
945
        except ValueError:
993
 
            return (None,)
 
946
            return None
994
947
        else:
995
948
            if offset <= 0:
996
949
                raise BzrError('You must supply a positive value for --revision last:XXX')
997
 
            return (len(revs) - offset + 1,)
 
950
            return len(revs) - offset + 1
998
951
    REVISION_NAMESPACES['last:'] = _namespace_last
999
952
 
1000
953
    def _namespace_tag(self, revs, revision):
1075
1028
                # TODO: Handle timezone.
1076
1029
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1077
1030
                if first >= dt and (last is None or dt >= last):
1078
 
                    return (i+1,)
 
1031
                    return i+1
1079
1032
        else:
1080
1033
            for i in range(len(revs)):
1081
1034
                r = self.get_revision(revs[i])
1082
1035
                # TODO: Handle timezone.
1083
1036
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1084
1037
                if first <= dt and (last is None or dt <= last):
1085
 
                    return (i+1,)
 
1038
                    return i+1
1086
1039
    REVISION_NAMESPACES['date:'] = _namespace_date
1087
1040
 
1088
1041
    def revision_tree(self, revision_id):
1441
1394
    """Return a new tree-root file id."""
1442
1395
    return gen_file_id('TREE_ROOT')
1443
1396
 
1444
 
 
1445
 
def pull_loc(branch):
1446
 
    # TODO: Should perhaps just make attribute be 'base' in
1447
 
    # RemoteBranch and Branch?
1448
 
    if hasattr(branch, "baseurl"):
1449
 
        return branch.baseurl
1450
 
    else:
1451
 
        return branch.base
1452
 
 
1453
 
 
1454
 
def copy_branch(branch_from, to_location, revision=None):
1455
 
    """Copy branch_from into the existing directory to_location.
1456
 
 
1457
 
    If revision is not None, the head of the new branch will be revision.
1458
 
    """
1459
 
    from bzrlib.merge import merge
1460
 
    from bzrlib.branch import Branch
1461
 
    br_to = Branch(to_location, init=True)
1462
 
    br_to.set_root_id(branch_from.get_root_id())
1463
 
    if revision is None:
1464
 
        revno = branch_from.revno()
1465
 
    else:
1466
 
        revno, rev_id = branch_from.get_revision_info(revision)
1467
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1468
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1469
 
          check_clean=False, ignore_zero=True)
1470
 
    from_location = pull_loc(branch_from)
1471
 
    br_to.controlfile("x-pull", "wb").write(from_location + "\n")
1472