~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

lalos branch constructor patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
44
# cache in memory to make this faster.
45
45
 
46
 
# TODO: please move the revision-string syntax stuff out of the branch
47
 
# object; it's clutter
48
 
 
49
 
 
50
 
def find_branch(f, **args):
51
 
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        from bzrlib.remotebranch import RemoteBranch
53
 
        return RemoteBranch(f, **args)
54
 
    else:
55
 
        return Branch(f, **args)
56
 
 
57
 
 
58
 
def find_cached_branch(f, cache_root, **args):
59
 
    from bzrlib.remotebranch import RemoteBranch
60
 
    br = find_branch(f, **args)
61
 
    def cacheify(br, store_name):
62
 
        from bzrlib.meta_store import CachedStore
63
 
        cache_path = os.path.join(cache_root, store_name)
64
 
        os.mkdir(cache_path)
65
 
        new_store = CachedStore(getattr(br, store_name), cache_path)
66
 
        setattr(br, store_name, new_store)
67
 
 
68
 
    if isinstance(br, RemoteBranch):
69
 
        cacheify(br, 'inventory_store')
70
 
        cacheify(br, 'text_store')
71
 
        cacheify(br, 'revision_store')
72
 
    return br
73
 
 
 
46
def find_branch(*ignored, **ignored_too):
 
47
    # XXX: leave this here for about one release, then remove it
 
48
    raise NotImplementedError('find_branch() is not supported anymore, '
 
49
                              'please use one of the new branch constructors')
74
50
 
75
51
def _relpath(base, path):
76
52
    """Return path relative to base, or raise exception.
140
116
    """Branch holding a history of revisions.
141
117
 
142
118
    base
143
 
        Base directory of the branch.
 
119
        Base directory/url of the branch.
 
120
    """
 
121
    base = None
 
122
 
 
123
    def __init__(self, *ignored, **ignored_too):
 
124
        raise NotImplementedError('The Branch class is abstract')
 
125
 
 
126
    @staticmethod
 
127
    def open(base):
 
128
        """Open an existing branch, rooted at 'base' (url)"""
 
129
        if base and (base.startswith('http://') or base.startswith('https://')):
 
130
            from bzrlib.remotebranch import RemoteBranch
 
131
            return RemoteBranch(base, find_root=False)
 
132
        else:
 
133
            return LocalBranch(base, find_root=False)
 
134
 
 
135
    @staticmethod
 
136
    def open_containing(url):
 
137
        """Open an existing branch, containing url (search upwards for the root)
 
138
        """
 
139
        if url and (url.startswith('http://') or url.startswith('https://')):
 
140
            from bzrlib.remotebranch import RemoteBranch
 
141
            return RemoteBranch(url)
 
142
        else:
 
143
            return LocalBranch(url)
 
144
 
 
145
    @staticmethod
 
146
    def initialize(base):
 
147
        """Create a new branch, rooted at 'base' (url)"""
 
148
        if base and (base.startswith('http://') or base.startswith('https://')):
 
149
            from bzrlib.remotebranch import RemoteBranch
 
150
            return RemoteBranch(base, init=True)
 
151
        else:
 
152
            return LocalBranch(base, init=True)
 
153
 
 
154
    def setup_caching(self, cache_root):
 
155
        """Subclasses that care about caching should override this, and set
 
156
        up cached stores located under cache_root.
 
157
        """
 
158
 
 
159
 
 
160
class LocalBranch(Branch):
 
161
    """A branch stored in the actual filesystem.
 
162
 
 
163
    Note that it's "local" in the context of the filesystem; it doesn't
 
164
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
165
    it's writable, and can be accessed via the normal filesystem API.
144
166
 
145
167
    _lock_mode
146
168
        None, or 'r' or 'w'
152
174
    _lock
153
175
        Lock object from bzrlib.lock.
154
176
    """
155
 
    base = None
 
177
    # We actually expect this class to be somewhat short-lived; part of its
 
178
    # purpose is to try to isolate what bits of the branch logic are tied to
 
179
    # filesystem access, so that in a later step, we can extricate them to
 
180
    # a separarte ("storage") class.
156
181
    _lock_mode = None
157
182
    _lock_count = None
158
183
    _lock = None
159
 
    
160
 
    # Map some sort of prefix into a namespace
161
 
    # stuff like "revno:10", "revid:", etc.
162
 
    # This should match a prefix with a function which accepts
163
 
    REVISION_NAMESPACES = {}
164
184
 
165
185
    def __init__(self, base, init=False, find_root=True):
166
186
        """Create new branch object at a particular location.
463
483
        """Print `file` to stdout."""
464
484
        self.lock_read()
465
485
        try:
466
 
            tree = self.revision_tree(self.lookup_revision(revno))
 
486
            tree = self.revision_tree(self.get_rev_id(revno))
467
487
            # use inventory as it was in that revision
468
488
            file_id = tree.inventory.path2id(file)
469
489
            if not file_id:
804
824
        if stop_revision is None:
805
825
            other_revision = other.last_patch()
806
826
        else:
807
 
            other_revision = other.lookup_revision(stop_revision)
 
827
            other_revision = other.get_rev_id(stop_revision)
808
828
        count = greedy_fetch(self, other, other_revision, pb)[0]
809
829
        try:
810
830
            revision_ids = self.missing_revisions(other, stop_revision)
879
899
        commit(self, *args, **kw)
880
900
        
881
901
 
882
 
    def lookup_revision(self, revision):
883
 
        """Return the revision identifier for a given revision information."""
884
 
        revno, info = self._get_revision_info(revision)
885
 
        return info
886
 
 
887
 
 
888
902
    def revision_id_to_revno(self, revision_id):
889
903
        """Given a revision id, return its revno"""
890
904
        history = self.revision_history()
894
908
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
895
909
 
896
910
 
897
 
    def get_revision_info(self, revision):
898
 
        """Return (revno, revision id) for revision identifier.
899
 
 
900
 
        revision can be an integer, in which case it is assumed to be revno (though
901
 
            this will translate negative values into positive ones)
902
 
        revision can also be a string, in which case it is parsed for something like
903
 
            'date:' or 'revid:' etc.
904
 
        """
905
 
        revno, rev_id = self._get_revision_info(revision)
906
 
        if revno is None:
907
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
908
 
        return revno, rev_id
909
 
 
910
911
    def get_rev_id(self, revno, history=None):
911
912
        """Find the revision id of the specified revno."""
912
913
        if revno == 0:
917
918
            raise bzrlib.errors.NoSuchRevision(self, revno)
918
919
        return history[revno - 1]
919
920
 
920
 
    def _get_revision_info(self, revision):
921
 
        """Return (revno, revision id) for revision specifier.
922
 
 
923
 
        revision can be an integer, in which case it is assumed to be revno
924
 
        (though this will translate negative values into positive ones)
925
 
        revision can also be a string, in which case it is parsed for something
926
 
        like 'date:' or 'revid:' etc.
927
 
 
928
 
        A revid is always returned.  If it is None, the specifier referred to
929
 
        the null revision.  If the revid does not occur in the revision
930
 
        history, revno will be None.
931
 
        """
932
 
        
933
 
        if revision is None:
934
 
            return 0, None
935
 
        revno = None
936
 
        try:# Convert to int if possible
937
 
            revision = int(revision)
938
 
        except ValueError:
939
 
            pass
940
 
        revs = self.revision_history()
941
 
        if isinstance(revision, int):
942
 
            if revision < 0:
943
 
                revno = len(revs) + revision + 1
944
 
            else:
945
 
                revno = revision
946
 
            rev_id = self.get_rev_id(revno, revs)
947
 
        elif isinstance(revision, basestring):
948
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
949
 
                if revision.startswith(prefix):
950
 
                    result = func(self, revs, revision)
951
 
                    if len(result) > 1:
952
 
                        revno, rev_id = result
953
 
                    else:
954
 
                        revno = result[0]
955
 
                        rev_id = self.get_rev_id(revno, revs)
956
 
                    break
957
 
            else:
958
 
                raise BzrError('No namespace registered for string: %r' %
959
 
                               revision)
960
 
        else:
961
 
            raise TypeError('Unhandled revision type %s' % revision)
962
 
 
963
 
        if revno is None:
964
 
            if rev_id is None:
965
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
966
 
        return revno, rev_id
967
 
 
968
 
    def _namespace_revno(self, revs, revision):
969
 
        """Lookup a revision by revision number"""
970
 
        assert revision.startswith('revno:')
971
 
        try:
972
 
            return (int(revision[6:]),)
973
 
        except ValueError:
974
 
            return None
975
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
976
 
 
977
 
    def _namespace_revid(self, revs, revision):
978
 
        assert revision.startswith('revid:')
979
 
        rev_id = revision[len('revid:'):]
980
 
        try:
981
 
            return revs.index(rev_id) + 1, rev_id
982
 
        except ValueError:
983
 
            return None, rev_id
984
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
985
 
 
986
 
    def _namespace_last(self, revs, revision):
987
 
        assert revision.startswith('last:')
988
 
        try:
989
 
            offset = int(revision[5:])
990
 
        except ValueError:
991
 
            return (None,)
992
 
        else:
993
 
            if offset <= 0:
994
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
995
 
            return (len(revs) - offset + 1,)
996
 
    REVISION_NAMESPACES['last:'] = _namespace_last
997
 
 
998
 
    def _namespace_tag(self, revs, revision):
999
 
        assert revision.startswith('tag:')
1000
 
        raise BzrError('tag: namespace registered, but not implemented.')
1001
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
1002
 
 
1003
 
    def _namespace_date(self, revs, revision):
1004
 
        assert revision.startswith('date:')
1005
 
        import datetime
1006
 
        # Spec for date revisions:
1007
 
        #   date:value
1008
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1009
 
        #   it can also start with a '+/-/='. '+' says match the first
1010
 
        #   entry after the given date. '-' is match the first entry before the date
1011
 
        #   '=' is match the first entry after, but still on the given date.
1012
 
        #
1013
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
1014
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
1015
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
1016
 
        #       May 13th, 2005 at 0:00
1017
 
        #
1018
 
        #   So the proper way of saying 'give me all entries for today' is:
1019
 
        #       -r {date:+today}:{date:-tomorrow}
1020
 
        #   The default is '=' when not supplied
1021
 
        val = revision[5:]
1022
 
        match_style = '='
1023
 
        if val[:1] in ('+', '-', '='):
1024
 
            match_style = val[:1]
1025
 
            val = val[1:]
1026
 
 
1027
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
1028
 
        if val.lower() == 'yesterday':
1029
 
            dt = today - datetime.timedelta(days=1)
1030
 
        elif val.lower() == 'today':
1031
 
            dt = today
1032
 
        elif val.lower() == 'tomorrow':
1033
 
            dt = today + datetime.timedelta(days=1)
1034
 
        else:
1035
 
            import re
1036
 
            # This should be done outside the function to avoid recompiling it.
1037
 
            _date_re = re.compile(
1038
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1039
 
                    r'(,|T)?\s*'
1040
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1041
 
                )
1042
 
            m = _date_re.match(val)
1043
 
            if not m or (not m.group('date') and not m.group('time')):
1044
 
                raise BzrError('Invalid revision date %r' % revision)
1045
 
 
1046
 
            if m.group('date'):
1047
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1048
 
            else:
1049
 
                year, month, day = today.year, today.month, today.day
1050
 
            if m.group('time'):
1051
 
                hour = int(m.group('hour'))
1052
 
                minute = int(m.group('minute'))
1053
 
                if m.group('second'):
1054
 
                    second = int(m.group('second'))
1055
 
                else:
1056
 
                    second = 0
1057
 
            else:
1058
 
                hour, minute, second = 0,0,0
1059
 
 
1060
 
            dt = datetime.datetime(year=year, month=month, day=day,
1061
 
                    hour=hour, minute=minute, second=second)
1062
 
        first = dt
1063
 
        last = None
1064
 
        reversed = False
1065
 
        if match_style == '-':
1066
 
            reversed = True
1067
 
        elif match_style == '=':
1068
 
            last = dt + datetime.timedelta(days=1)
1069
 
 
1070
 
        if reversed:
1071
 
            for i in range(len(revs)-1, -1, -1):
1072
 
                r = self.get_revision(revs[i])
1073
 
                # TODO: Handle timezone.
1074
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1075
 
                if first >= dt and (last is None or dt >= last):
1076
 
                    return (i+1,)
1077
 
        else:
1078
 
            for i in range(len(revs)):
1079
 
                r = self.get_revision(revs[i])
1080
 
                # TODO: Handle timezone.
1081
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1082
 
                if first <= dt and (last is None or dt <= last):
1083
 
                    return (i+1,)
1084
 
    REVISION_NAMESPACES['date:'] = _namespace_date
1085
 
 
1086
 
 
1087
 
    def _namespace_ancestor(self, revs, revision):
1088
 
        from revision import common_ancestor, MultipleRevisionSources
1089
 
        other_branch = find_branch(_trim_namespace('ancestor', revision))
1090
 
        revision_a = self.last_patch()
1091
 
        revision_b = other_branch.last_patch()
1092
 
        for r, b in ((revision_a, self), (revision_b, other_branch)):
1093
 
            if r is None:
1094
 
                raise bzrlib.errors.NoCommits(b)
1095
 
        revision_source = MultipleRevisionSources(self, other_branch)
1096
 
        result = common_ancestor(revision_a, revision_b, revision_source)
1097
 
        try:
1098
 
            revno = self.revision_id_to_revno(result)
1099
 
        except bzrlib.errors.NoSuchRevision:
1100
 
            revno = None
1101
 
        return revno,result
1102
 
        
1103
 
 
1104
 
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1105
921
 
1106
922
    def revision_tree(self, revision_id):
1107
923
        """Return Tree for a revision on this branch.
1390
1206
            raise InvalidRevisionNumber(revno)
1391
1207
        
1392
1208
        
1393
 
 
1394
 
 
1395
 
class ScratchBranch(Branch):
 
1209
        
 
1210
 
 
1211
 
 
1212
class ScratchBranch(LocalBranch):
1396
1213
    """Special test class: a branch that cleans up after itself.
1397
1214
 
1398
1215
    >>> b = ScratchBranch()
1415
1232
        if base is None:
1416
1233
            base = mkdtemp()
1417
1234
            init = True
1418
 
        Branch.__init__(self, base, init=init)
 
1235
        LocalBranch.__init__(self, base, init=init)
1419
1236
        for d in dirs:
1420
1237
            os.mkdir(self.abspath(d))
1421
1238
            
1527
1344
        The name of a local directory that exists but is empty.
1528
1345
    """
1529
1346
    from bzrlib.merge import merge
 
1347
    from bzrlib.revisionspec import RevisionSpec
1530
1348
 
1531
1349
    assert isinstance(branch_from, Branch)
1532
1350
    assert isinstance(to_location, basestring)
1533
1351
    
1534
 
    br_to = Branch(to_location, init=True)
 
1352
    br_to = Branch.initialize(to_location)
1535
1353
    br_to.set_root_id(branch_from.get_root_id())
1536
1354
    if revision is None:
1537
1355
        revno = branch_from.revno()
1538
1356
    else:
1539
 
        revno, rev_id = branch_from.get_revision_info(revision)
 
1357
        revno, rev_id = RevisionSpec(revision).in_history(branch_from)
1540
1358
    br_to.update_revisions(branch_from, stop_revision=revno)
1541
1359
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1542
1360
          check_clean=False, ignore_zero=True)
1543
1361
    br_to.set_parent(branch_from.base)
1544
1362
    return br_to
1545
 
 
1546
 
def _trim_namespace(namespace, spec):
1547
 
    full_namespace = namespace + ':'
1548
 
    assert spec.startswith(full_namespace)
1549
 
    return spec[len(full_namespace):]