/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
20
from bzrlib.errors import BzrError, NoSuchRevision
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):
52
        return not (self.revno is None or self.rev_id is None)
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
53
54
    def __len__(self):
55
        return 2
56
57
    def __getitem__(self, index):
58
        if index == 0: return self.revno
59
        if index == 1: return self.rev_id
60
        raise IndexError(index)
61
62
    def get(self):
63
        return self.branch.get_revision(self.rev_id)
64
65
    def __eq__(self, other):
66
        if type(other) not in (tuple, list, type(self)):
67
            return False
68
        if type(other) is type(self) and self.branch is not other.branch:
69
            return False
70
        return tuple(self) == tuple(other)
71
72
    def __repr__(self):
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
73
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
1185.2.6 by Lalo Martins
turned get_revision_info into a RevisionSpec class
74
            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
75
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
76
# classes in this list should have a "prefix" attribute, against which
77
# string specs are matched
78
SPEC_TYPES = []
79
80
class RevisionSpec(object):
81
    """A parsed revision specification.
82
83
    A revision specification can be an integer, in which case it is
84
    assumed to be a revno (though this will translate negative values
85
    into positive ones); or it can be a string, in which case it is
86
    parsed for something like 'date:' or 'revid:' etc.
87
88
    Revision specs are an UI element, and they have been moved out
89
    of the branch class to leave "back-end" classes unaware of such
90
    details.  Code that gets a revno or rev_id from other code should
91
    not be using revision specs - revnos and revision ids are the
92
    accepted ways to refer to revisions internally.
93
94
    (Equivalent to the old Branch method get_revision_info())
95
    """
96
97
    prefix = None
98
99
    def __new__(cls, spec, foo=_marker):
100
        """Parse a revision specifier.
101
        """
102
        if spec is None:
103
            return object.__new__(RevisionSpec, spec)
104
105
        try:
106
            spec = int(spec)
107
        except ValueError:
108
            pass
109
110
        if isinstance(spec, int):
111
            return object.__new__(RevisionSpec_int, spec)
112
        elif isinstance(spec, basestring):
113
            for spectype in SPEC_TYPES:
114
                if spec.startswith(spectype.prefix):
115
                    return object.__new__(spectype, spec)
116
            else:
117
                raise BzrError('No namespace registered for string: %r' %
118
                               spec)
119
        else:
120
            raise TypeError('Unhandled revision type %s' % spec)
121
122
    def __init__(self, spec):
123
        if self.prefix and spec.startswith(self.prefix):
124
            spec = spec[len(self.prefix):]
125
        self.spec = spec
126
127
    def _match_on(self, branch, revs):
128
        return RevisionInfo(branch, 0, None)
129
130
    def _match_on_and_check(self, branch, revs):
131
        info = self._match_on(branch, revs)
132
        if info:
133
            return info
134
        elif info == (0, None):
135
            # special case - the empty tree
136
            return info
137
        elif self.prefix:
138
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
139
        else:
140
            raise NoSuchRevision(branch, str(self.spec))
141
142
    def in_history(self, branch):
143
        revs = branch.revision_history()
144
        return self._match_on_and_check(branch, revs)
145
1185.2.14 by Lalo Martins
fixing up users of RevisionSpec, and moving it over to commands.py
146
    def __repr__(self):
147
        # this is mostly for helping with testing
148
        return '<%s %s%s>' % (self.__class__.__name__,
149
                              self.prefix or '',
150
                              self.spec)
151
1185.2.5 by Lalo Martins
moving the 'revision spec' stuff out of the Branch class and into a new
152
153
# private API
154
1185.2.13 by Lalo Martins
polishing up the RevisionSpec api, as per suggestions from Robert
155
class RevisionSpec_int(RevisionSpec):
156
    """Spec is a number.  Special case."""
157
    def __init__(self, spec):
158
        self.spec = int(spec)
159
160
    def _match_on(self, branch, revs):
161
        if self.spec < 0:
162
            revno = len(revs) + self.spec + 1
163
        else:
164
            revno = self.spec
165
        rev_id = branch.get_rev_id(revno, revs)
166
        return RevisionInfo(branch, revno, rev_id)
167
168
169
class RevisionSpec_revno(RevisionSpec):
170
    prefix = 'revno:'
171
172
    def _match_on(self, branch, revs):
173
        """Lookup a revision by revision number"""
174
        try:
175
            return RevisionInfo(branch, int(self.spec))
176
        except ValueError:
177
            return RevisionInfo(branch, None)
178
179
SPEC_TYPES.append(RevisionSpec_revno)
180
181
182
class RevisionSpec_revid(RevisionSpec):
183
    prefix = 'revid:'
184
185
    def _match_on(self, branch, revs):
186
        try:
187
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
188
        except ValueError:
189
            return RevisionInfo(branch, None)
190
191
SPEC_TYPES.append(RevisionSpec_revid)
192
193
194
class RevisionSpec_last(RevisionSpec):
195
    prefix = 'last:'
196
197
    def _match_on(self, branch, revs):
198
        try:
199
            offset = int(self.spec)
200
        except ValueError:
201
            return RevisionInfo(branch, None)
202
        else:
203
            if offset <= 0:
204
                raise BzrError('You must supply a positive value for --revision last:XXX')
205
            return RevisionInfo(branch, len(revs) - offset + 1)
206
207
SPEC_TYPES.append(RevisionSpec_last)
208
209
210
class RevisionSpec_tag(RevisionSpec):
211
    prefix = 'tag:'
212
213
    def _match_on(self, branch, revs):
214
        raise BzrError('tag: namespace registered, but not implemented.')
215
216
SPEC_TYPES.append(RevisionSpec_tag)
217
218
219
class RevisionSpec_date(RevisionSpec):
220
    prefix = 'date:'
221
    _date_re = re.compile(
222
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
223
            r'(,|T)?\s*'
224
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
225
        )
226
227
    def _match_on(self, branch, revs):
228
        """
229
        Spec for date revisions:
230
          date:value
231
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
232
          it can also start with a '+/-/='. '+' says match the first
233
          entry after the given date. '-' is match the first entry before the date
234
          '=' is match the first entry after, but still on the given date.
235
236
          +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
237
          -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
238
          =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
239
              May 13th, 2005 at 0:00
240
241
          So the proper way of saying 'give me all entries for today' is:
242
              -r {date:+today}:{date:-tomorrow}
243
          The default is '=' when not supplied
244
        """
245
        match_style = '='
246
        if self.spec[:1] in ('+', '-', '='):
247
            match_style = self.spec[:1]
248
            self.spec = self.spec[1:]
249
250
        # XXX: this should probably be using datetime.date instead
251
        today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
252
                                                  microsecond=0)
253
        if self.spec.lower() == 'yesterday':
254
            dt = today - datetime.timedelta(days=1)
255
        elif self.spec.lower() == 'today':
256
            dt = today
257
        elif self.spec.lower() == 'tomorrow':
258
            dt = today + datetime.timedelta(days=1)
259
        else:
260
            m = self._date_re.match(self.spec)
261
            if not m or (not m.group('date') and not m.group('time')):
262
                raise BzrError('Invalid revision date %r' % self.spec)
263
264
            if m.group('date'):
265
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
266
            else:
267
                year, month, day = today.year, today.month, today.day
268
            if m.group('time'):
269
                hour = int(m.group('hour'))
270
                minute = int(m.group('minute'))
271
                if m.group('second'):
272
                    second = int(m.group('second'))
273
                else:
274
                    second = 0
275
            else:
276
                hour, minute, second = 0,0,0
277
278
            dt = datetime.datetime(year=year, month=month, day=day,
279
                    hour=hour, minute=minute, second=second)
280
        first = dt
281
        last = None
282
        reversed = False
283
        if match_style == '-':
284
            reversed = True
285
        elif match_style == '=':
286
            last = dt + datetime.timedelta(days=1)
287
288
        if reversed:
289
            for i in range(len(revs)-1, -1, -1):
290
                r = branch.get_revision(revs[i])
291
                # TODO: Handle timezone.
292
                dt = datetime.datetime.fromtimestamp(r.timestamp)
293
                if first >= dt and (last is None or dt >= last):
294
                    return RevisionInfo(branch, i+1,)
295
        else:
296
            for i in range(len(revs)):
297
                r = branch.get_revision(revs[i])
298
                # TODO: Handle timezone.
299
                dt = datetime.datetime.fromtimestamp(r.timestamp)
300
                if first <= dt and (last is None or dt <= last):
301
                    return RevisionInfo(branch, i+1,)
302
303
SPEC_TYPES.append(RevisionSpec_date)