~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
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
113
        if isinstance(spec, RevisionSpec):
1185.1.28 by Robert Collins
revert out the revision spec from revision spec change
114
            import pdb;pdb.set_trace()
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
115
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
116
        try:
117
            spec = int(spec)
118
        except ValueError:
119
            pass
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
120
        except TypeError:
121
            raise TypeError('Unexpected type, got %s, %r' % (type(spec), spec))
122
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
123
124
        if isinstance(spec, int):
125
            return object.__new__(RevisionSpec_int, spec)
126
        elif isinstance(spec, basestring):
127
            for spectype in SPEC_TYPES:
128
                if spec.startswith(spectype.prefix):
129
                    return object.__new__(spectype, spec)
130
            else:
131
                raise BzrError('No namespace registered for string: %r' %
132
                               spec)
133
        else:
134
            raise TypeError('Unhandled revision type %s' % spec)
135
136
    def __init__(self, spec):
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
137
        if isinstance(spec, RevisionSpec):
138
            self.spec = spec.spec
139
        else:
140
            if self.prefix and spec.startswith(self.prefix):
141
                spec = spec[len(self.prefix):]
142
            self.spec = spec
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
143
144
    def _match_on(self, branch, revs):
145
        return RevisionInfo(branch, 0, None)
146
147
    def _match_on_and_check(self, branch, revs):
148
        info = self._match_on(branch, revs)
149
        if info:
150
            return info
151
        elif info == (0, None):
152
            # special case - the empty tree
153
            return info
154
        elif self.prefix:
155
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
156
        else:
157
            raise NoSuchRevision(branch, str(self.spec))
158
159
    def in_history(self, branch):
160
        revs = branch.revision_history()
161
        return self._match_on_and_check(branch, revs)
162
1185.2.14 by Lalo Martins
fixing up users of RevisionSpec, and moving it over to commands.py
163
    def __repr__(self):
164
        # this is mostly for helping with testing
165
        return '<%s %s%s>' % (self.__class__.__name__,
166
                              self.prefix or '',
167
                              self.spec)
168
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
169
    def __eq__(self, other):
1185.5.8 by John Arbash Meinel
Fixed bzr revert with the new RevisionSpec code.
170
        if isinstance(other, RevisionSpec):
171
            return self.spec == other.spec
172
        return other == self.spec
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
173
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
174
175
# private API
176
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
177
class RevisionSpec_int(RevisionSpec):
178
    """Spec is a number.  Special case."""
179
    def __init__(self, spec):
1185.5.5 by John Arbash Meinel
RevisionSpec can be instantiated from another revision spec.
180
        if isinstance(spec, RevisionSpec):
181
            self.spec = int(spec.spec)
182
        else:
183
            self.spec = int(spec)
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
184
185
    def _match_on(self, branch, revs):
186
        if self.spec < 0:
187
            revno = len(revs) + self.spec + 1
188
        else:
189
            revno = self.spec
190
        rev_id = branch.get_rev_id(revno, revs)
191
        return RevisionInfo(branch, revno, rev_id)
192
193
194
class RevisionSpec_revno(RevisionSpec):
195
    prefix = 'revno:'
196
197
    def _match_on(self, branch, revs):
198
        """Lookup a revision by revision number"""
199
        try:
200
            return RevisionInfo(branch, int(self.spec))
201
        except ValueError:
202
            return RevisionInfo(branch, None)
203
204
SPEC_TYPES.append(RevisionSpec_revno)
205
206
207
class RevisionSpec_revid(RevisionSpec):
208
    prefix = 'revid:'
209
210
    def _match_on(self, branch, revs):
211
        try:
212
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
213
        except ValueError:
214
            return RevisionInfo(branch, None)
215
216
SPEC_TYPES.append(RevisionSpec_revid)
217
218
219
class RevisionSpec_last(RevisionSpec):
220
    prefix = 'last:'
221
222
    def _match_on(self, branch, revs):
223
        try:
224
            offset = int(self.spec)
225
        except ValueError:
226
            return RevisionInfo(branch, None)
227
        else:
228
            if offset <= 0:
229
                raise BzrError('You must supply a positive value for --revision last:XXX')
230
            return RevisionInfo(branch, len(revs) - offset + 1)
231
232
SPEC_TYPES.append(RevisionSpec_last)
233
234
235
class RevisionSpec_tag(RevisionSpec):
236
    prefix = 'tag:'
237
238
    def _match_on(self, branch, revs):
239
        raise BzrError('tag: namespace registered, but not implemented.')
240
241
SPEC_TYPES.append(RevisionSpec_tag)
242
243
244
class RevisionSpec_date(RevisionSpec):
245
    prefix = 'date:'
246
    _date_re = re.compile(
247
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
248
            r'(,|T)?\s*'
249
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
250
        )
251
252
    def _match_on(self, branch, revs):
253
        """
254
        Spec for date revisions:
255
          date:value
256
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
257
          it can also start with a '+/-/='. '+' says match the first
258
          entry after the given date. '-' is match the first entry before the date
259
          '=' is match the first entry after, but still on the given date.
260
261
          +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
262
          -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
263
          =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
264
              May 13th, 2005 at 0:00
265
266
          So the proper way of saying 'give me all entries for today' is:
267
              -r {date:+today}:{date:-tomorrow}
268
          The default is '=' when not supplied
269
        """
270
        match_style = '='
271
        if self.spec[:1] in ('+', '-', '='):
272
            match_style = self.spec[:1]
273
            self.spec = self.spec[1:]
274
275
        # XXX: this should probably be using datetime.date instead
276
        today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
277
                                                  microsecond=0)
278
        if self.spec.lower() == 'yesterday':
279
            dt = today - datetime.timedelta(days=1)
280
        elif self.spec.lower() == 'today':
281
            dt = today
282
        elif self.spec.lower() == 'tomorrow':
283
            dt = today + datetime.timedelta(days=1)
284
        else:
285
            m = self._date_re.match(self.spec)
286
            if not m or (not m.group('date') and not m.group('time')):
287
                raise BzrError('Invalid revision date %r' % self.spec)
288
289
            if m.group('date'):
290
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
291
            else:
292
                year, month, day = today.year, today.month, today.day
293
            if m.group('time'):
294
                hour = int(m.group('hour'))
295
                minute = int(m.group('minute'))
296
                if m.group('second'):
297
                    second = int(m.group('second'))
298
                else:
299
                    second = 0
300
            else:
301
                hour, minute, second = 0,0,0
302
303
            dt = datetime.datetime(year=year, month=month, day=day,
304
                    hour=hour, minute=minute, second=second)
305
        first = dt
306
        last = None
307
        reversed = False
308
        if match_style == '-':
309
            reversed = True
310
        elif match_style == '=':
311
            last = dt + datetime.timedelta(days=1)
312
313
        if reversed:
314
            for i in range(len(revs)-1, -1, -1):
315
                r = branch.get_revision(revs[i])
316
                # TODO: Handle timezone.
317
                dt = datetime.datetime.fromtimestamp(r.timestamp)
318
                if first >= dt and (last is None or dt >= last):
319
                    return RevisionInfo(branch, i+1,)
320
        else:
321
            for i in range(len(revs)):
322
                r = branch.get_revision(revs[i])
323
                # TODO: Handle timezone.
324
                dt = datetime.datetime.fromtimestamp(r.timestamp)
325
                if first <= dt and (last is None or dt <= last):
326
                    return RevisionInfo(branch, i+1,)
327
328
SPEC_TYPES.append(RevisionSpec_date)
1185.2.18 by Lalo Martins
merging from integration again.
329
330
331
class RevisionSpec_ancestor(RevisionSpec):
332
    prefix = 'ancestor:'
333
334
    def _match_on(self, branch, revs):
335
        from branch import Branch
336
        from revision import common_ancestor, MultipleRevisionSources
337
        other_branch = Branch.open_containing(self.spec)
338
        revision_a = branch.last_patch()
339
        revision_b = other_branch.last_patch()
340
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
341
            if r is None:
342
                raise NoCommits(b)
343
        revision_source = MultipleRevisionSources(branch, other_branch)
344
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
345
        try:
346
            revno = branch.revision_id_to_revno(rev_id)
347
        except NoSuchRevision:
348
            revno = None
349
        return RevisionInfo(branch, revno, rev_id)
350
        
351
SPEC_TYPES.append(RevisionSpec_ancestor)