~bzr-pqm/bzr/bzr.dev

1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
1
# Copyright (C) 2005 Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
18
import datetime
19
import re
1185.2.18 by Lalo Martins
merging from integration again.
20
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
21
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
22
_marker = []
23
24
class RevisionInfo(object):
25
    """The results of applying a revision specification to a branch.
26
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
27
    An instance has two useful attributes: revno, and rev_id.
28
29
    They can also be accessed as spec[0] and spec[1] respectively,
30
    so that you can write code like:
31
    revno, rev_id = RevisionSpec(branch, spec)
32
    although this is probably going to be deprecated later.
33
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
34
    This class exists mostly to be the return value of a RevisionSpec,
35
    so that you can access the member you're interested in (number or id)
36
    or treat the result as a tuple.
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
37
    """
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
38
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
39
    def __init__(self, branch, revno, rev_id=_marker):
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
40
        self.branch = branch
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
41
        self.revno = revno
42
        if rev_id is _marker:
43
            # allow caller to be lazy
44
            if self.revno is None:
45
                self.rev_id = None
46
            else:
47
                self.rev_id = branch.get_rev_id(self.revno)
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
48
        else:
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
49
            self.rev_id = rev_id
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
50
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
51
    def __nonzero__(self):
1185.2.18 by Lalo Martins
merging from integration again.
52
        # first the easy ones...
53
        if self.rev_id is None:
54
            return False
55
        if self.revno is not None:
56
            return True
57
        # TODO: otherwise, it should depend on how I was built -
58
        # if it's in_history(branch), then check revision_history(),
59
        # if it's in_store(branch), do the check below
60
        return self.rev_id in self.branch.revision_store
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
61
62
    def __len__(self):
63
        return 2
64
65
    def __getitem__(self, index):
66
        if index == 0: return self.revno
67
        if index == 1: return self.rev_id
68
        raise IndexError(index)
69
70
    def get(self):
71
        return self.branch.get_revision(self.rev_id)
72
73
    def __eq__(self, other):
74
        if type(other) not in (tuple, list, type(self)):
75
            return False
76
        if type(other) is type(self) and self.branch is not other.branch:
77
            return False
78
        return tuple(self) == tuple(other)
79
80
    def __repr__(self):
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
81
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
82
            self.revno, self.rev_id, self.branch)
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
83
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
84
# classes in this list should have a "prefix" attribute, against which
85
# string specs are matched
86
SPEC_TYPES = []
87
88
class RevisionSpec(object):
89
    """A parsed revision specification.
90
91
    A revision specification can be an integer, in which case it is
92
    assumed to be a revno (though this will translate negative values
93
    into positive ones); or it can be a string, in which case it is
94
    parsed for something like 'date:' or 'revid:' etc.
95
96
    Revision specs are an UI element, and they have been moved out
97
    of the branch class to leave "back-end" classes unaware of such
98
    details.  Code that gets a revno or rev_id from other code should
99
    not be using revision specs - revnos and revision ids are the
100
    accepted ways to refer to revisions internally.
101
102
    (Equivalent to the old Branch method get_revision_info())
103
    """
104
105
    prefix = None
106
107
    def __new__(cls, spec, foo=_marker):
108
        """Parse a revision specifier.
109
        """
110
        if spec is None:
111
            return object.__new__(RevisionSpec, spec)
112
113
        try:
114
            spec = int(spec)
115
        except ValueError:
116
            pass
117
118
        if isinstance(spec, int):
119
            return object.__new__(RevisionSpec_int, spec)
120
        elif isinstance(spec, basestring):
121
            for spectype in SPEC_TYPES:
122
                if spec.startswith(spectype.prefix):
123
                    return object.__new__(spectype, spec)
124
            else:
125
                raise BzrError('No namespace registered for string: %r' %
126
                               spec)
127
        else:
128
            raise TypeError('Unhandled revision type %s' % spec)
129
130
    def __init__(self, spec):
1185.1.29 by Robert Collins
merge merge tweaks from aaron, which includes latest .dev
131
        if self.prefix and spec.startswith(self.prefix):
132
            spec = spec[len(self.prefix):]
133
        self.spec = spec
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
134
135
    def _match_on(self, branch, revs):
136
        return RevisionInfo(branch, 0, None)
137
138
    def _match_on_and_check(self, branch, revs):
139
        info = self._match_on(branch, revs)
140
        if info:
141
            return info
142
        elif info == (0, None):
143
            # special case - the empty tree
144
            return info
145
        elif self.prefix:
146
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
147
        else:
148
            raise NoSuchRevision(branch, str(self.spec))
149
150
    def in_history(self, branch):
151
        revs = branch.revision_history()
152
        return self._match_on_and_check(branch, revs)
153
1185.2.14 by Lalo Martins
fixing up users of RevisionSpec, and moving it over to commands.py
154
    def __repr__(self):
155
        # this is mostly for helping with testing
156
        return '<%s %s%s>' % (self.__class__.__name__,
157
                              self.prefix or '',
158
                              self.spec)
159
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
160
161
# private API
162
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
163
class RevisionSpec_int(RevisionSpec):
164
    """Spec is a number.  Special case."""
165
    def __init__(self, spec):
1185.1.29 by Robert Collins
merge merge tweaks from aaron, which includes latest .dev
166
        self.spec = int(spec)
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
167
168
    def _match_on(self, branch, revs):
169
        if self.spec < 0:
170
            revno = len(revs) + self.spec + 1
171
        else:
172
            revno = self.spec
173
        rev_id = branch.get_rev_id(revno, revs)
174
        return RevisionInfo(branch, revno, rev_id)
175
176
177
class RevisionSpec_revno(RevisionSpec):
178
    prefix = 'revno:'
179
180
    def _match_on(self, branch, revs):
181
        """Lookup a revision by revision number"""
182
        try:
183
            return RevisionInfo(branch, int(self.spec))
184
        except ValueError:
185
            return RevisionInfo(branch, None)
186
187
SPEC_TYPES.append(RevisionSpec_revno)
188
189
190
class RevisionSpec_revid(RevisionSpec):
191
    prefix = 'revid:'
192
193
    def _match_on(self, branch, revs):
194
        try:
195
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
196
        except ValueError:
197
            return RevisionInfo(branch, None)
198
199
SPEC_TYPES.append(RevisionSpec_revid)
200
201
202
class RevisionSpec_last(RevisionSpec):
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
203
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
204
    prefix = 'last:'
205
206
    def _match_on(self, branch, revs):
207
        try:
208
            offset = int(self.spec)
209
        except ValueError:
210
            return RevisionInfo(branch, None)
211
        else:
212
            if offset <= 0:
213
                raise BzrError('You must supply a positive value for --revision last:XXX')
214
            return RevisionInfo(branch, len(revs) - offset + 1)
215
216
SPEC_TYPES.append(RevisionSpec_last)
217
218
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
219
class RevisionSpec_before(RevisionSpec):
220
221
    prefix = 'before:'
222
    
223
    def _match_on(self, branch, revs):
224
        r = RevisionSpec(self.spec)._match_on(branch, revs)
225
        if (r.revno is None) or (r.revno == 0):
226
            return r
227
        return RevisionInfo(branch, r.revno - 1)
228
229
SPEC_TYPES.append(RevisionSpec_before)
230
231
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
232
class RevisionSpec_tag(RevisionSpec):
233
    prefix = 'tag:'
234
235
    def _match_on(self, branch, revs):
236
        raise BzrError('tag: namespace registered, but not implemented.')
237
238
SPEC_TYPES.append(RevisionSpec_tag)
239
240
241
class RevisionSpec_date(RevisionSpec):
242
    prefix = 'date:'
243
    _date_re = re.compile(
244
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
245
            r'(,|T)?\s*'
246
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
247
        )
248
249
    def _match_on(self, branch, revs):
250
        """
251
        Spec for date revisions:
252
          date:value
253
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
254
          matches the first entry after a given date (either at midnight or
255
          at a specified time).
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
256
257
          So the proper way of saying 'give me all entries for today' is:
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
258
              -r date:today..date:tomorrow
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
259
        """
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
260
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
261
        if self.spec.lower() == 'yesterday':
262
            dt = today - datetime.timedelta(days=1)
263
        elif self.spec.lower() == 'today':
264
            dt = today
265
        elif self.spec.lower() == 'tomorrow':
266
            dt = today + datetime.timedelta(days=1)
267
        else:
268
            m = self._date_re.match(self.spec)
269
            if not m or (not m.group('date') and not m.group('time')):
270
                raise BzrError('Invalid revision date %r' % self.spec)
271
272
            if m.group('date'):
273
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
274
            else:
275
                year, month, day = today.year, today.month, today.day
276
            if m.group('time'):
277
                hour = int(m.group('hour'))
278
                minute = int(m.group('minute'))
279
                if m.group('second'):
280
                    second = int(m.group('second'))
281
                else:
282
                    second = 0
283
            else:
284
                hour, minute, second = 0,0,0
285
286
            dt = datetime.datetime(year=year, month=month, day=day,
287
                    hour=hour, minute=minute, second=second)
288
        first = dt
1185.1.39 by Robert Collins
Robey Pointers before: namespace to clear up usage of dates in revision parameters
289
        for i in range(len(revs)):
290
            r = branch.get_revision(revs[i])
291
            # TODO: Handle timezone.
292
            dt = datetime.datetime.fromtimestamp(r.timestamp)
293
            if first <= dt:
294
                return RevisionInfo(branch, i+1)
295
        return RevisionInfo(branch, None)
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
296
297
SPEC_TYPES.append(RevisionSpec_date)
1185.2.18 by Lalo Martins
merging from integration again.
298
299
300
class RevisionSpec_ancestor(RevisionSpec):
301
    prefix = 'ancestor:'
302
303
    def _match_on(self, branch, revs):
304
        from branch import Branch
305
        from revision import common_ancestor, MultipleRevisionSources
306
        other_branch = Branch.open_containing(self.spec)
1390 by Robert Collins
pair programming worx... merge integration and weave
307
        revision_a = branch.last_revision()
308
        revision_b = other_branch.last_revision()
1185.2.18 by Lalo Martins
merging from integration again.
309
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
310
            if r is None:
311
                raise NoCommits(b)
312
        revision_source = MultipleRevisionSources(branch, other_branch)
313
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
314
        try:
315
            revno = branch.revision_id_to_revno(rev_id)
316
        except NoSuchRevision:
317
            revno = None
318
        return RevisionInfo(branch, revno, rev_id)
319
        
320
SPEC_TYPES.append(RevisionSpec_ancestor)