33
31
revno, rev_id = RevisionSpec(branch, spec)
34
32
although this is probably going to be deprecated later.
36
Revision specs are an UI element, and they have been moved out
37
of the branch class to leave "back-end" classes unaware of such
38
details. Code that gets a revno or rev_id from other code should
39
not be using revision specs - revnos and revision ids are the
40
accepted ways to refer to revisions internally.
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.
42
def __init__(self, branch, spec):
43
"""Parse a revision specifier.
45
spec can be an integer, in which case it is assumed to be revno
46
(though this will translate negative values into positive ones)
47
spec can also be a string, in which case it is parsed for something
48
like 'date:' or 'revid:' etc.
39
def __init__(self, branch, revno, rev_id=_marker):
50
40
self.branch = branch
57
try:# Convert to int if possible
61
revs = branch.revision_history()
62
if isinstance(spec, int):
64
self.revno = len(revs) + spec + 1
67
self.rev_id = branch.get_rev_id(self.revno, revs)
68
elif isinstance(spec, basestring):
69
for prefix, func in REVISION_NAMESPACES.iteritems():
70
if spec.startswith(prefix):
71
result = func(branch, revs, spec)
73
self.revno, self.rev_id = result
75
self.revno = result[0]
76
self.rev_id = branch.get_rev_id(self.revno, revs)
79
raise BzrError('No namespace registered for string: %r' %
43
# allow caller to be lazy
44
if self.revno is None:
47
self.rev_id = branch.get_rev_id(self.revno)
82
raise TypeError('Unhandled revision type %s' % spec)
84
if self.revno is None or self.rev_id is None:
85
raise NoSuchRevision(branch, spec)
51
def __nonzero__(self):
52
return not (self.revno is None or self.rev_id is None)
103
70
return tuple(self) == tuple(other)
105
72
def __repr__(self):
106
return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
73
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
107
74
self.revno, self.rev_id, self.branch)
76
# classes in this list should have a "prefix" attribute, against which
77
# string specs are matched
80
class RevisionSpec(object):
81
"""A parsed revision specification.
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.
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.
94
(Equivalent to the old Branch method get_revision_info())
99
def __new__(cls, spec, foo=_marker):
100
"""Parse a revision specifier.
103
return object.__new__(RevisionSpec, spec)
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)
117
raise BzrError('No namespace registered for string: %r' %
120
raise TypeError('Unhandled revision type %s' % spec)
122
def __init__(self, spec):
123
if self.prefix and spec.startswith(self.prefix):
124
spec = spec[len(self.prefix):]
127
def _match_on(self, branch, revs):
128
return RevisionInfo(branch, 0, None)
130
def _match_on_and_check(self, branch, revs):
131
info = self._match_on(branch, revs)
134
elif info == (0, None):
135
# special case - the empty tree
138
raise NoSuchRevision(branch, self.prefix + str(self.spec))
140
raise NoSuchRevision(branch, str(self.spec))
142
def in_history(self, branch):
143
revs = branch.revision_history()
144
return self._match_on_and_check(branch, revs)
112
def _namespace_revno(branch, revs, spec):
113
"""Lookup a revision by revision number"""
114
assert spec.startswith('revno:')
116
return (int(spec[len('revno:'):]),)
119
REVISION_NAMESPACES['revno:'] = _namespace_revno
122
def _namespace_revid(branch, revs, spec):
123
assert spec.startswith('revid:')
124
rev_id = spec[len('revid:'):]
126
return revs.index(rev_id) + 1, rev_id
129
REVISION_NAMESPACES['revid:'] = _namespace_revid
132
def _namespace_last(branch, revs, spec):
133
assert spec.startswith('last:')
135
offset = int(spec[5:])
140
raise BzrError('You must supply a positive value for --revision last:XXX')
141
return (len(revs) - offset + 1,)
142
REVISION_NAMESPACES['last:'] = _namespace_last
145
def _namespace_tag(branch, revs, spec):
146
assert spec.startswith('tag:')
147
raise BzrError('tag: namespace registered, but not implemented.')
148
REVISION_NAMESPACES['tag:'] = _namespace_tag
151
_date_re = re.compile(
152
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
154
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
157
def _namespace_date(branch, revs, spec):
159
Spec for date revisions:
161
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
162
it can also start with a '+/-/='. '+' says match the first
163
entry after the given date. '-' is match the first entry before the date
164
'=' is match the first entry after, but still on the given date.
166
+2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
167
-2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
168
=2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
169
May 13th, 2005 at 0:00
171
So the proper way of saying 'give me all entries for today' is:
172
-r {date:+today}:{date:-tomorrow}
173
The default is '=' when not supplied
175
assert spec.startswith('date:')
178
if val[:1] in ('+', '-', '='):
179
match_style = val[:1]
182
# XXX: this should probably be using datetime.date instead
183
today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
185
if val.lower() == 'yesterday':
186
dt = today - datetime.timedelta(days=1)
187
elif val.lower() == 'today':
189
elif val.lower() == 'tomorrow':
190
dt = today + datetime.timedelta(days=1)
192
m = _date_re.match(val)
193
if not m or (not m.group('date') and not m.group('time')):
194
raise BzrError('Invalid revision date %r' % spec)
197
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
199
year, month, day = today.year, today.month, today.day
201
hour = int(m.group('hour'))
202
minute = int(m.group('minute'))
203
if m.group('second'):
204
second = int(m.group('second'))
208
hour, minute, second = 0,0,0
210
dt = datetime.datetime(year=year, month=month, day=day,
211
hour=hour, minute=minute, second=second)
215
if match_style == '-':
217
elif match_style == '=':
218
last = dt + datetime.timedelta(days=1)
221
for i in range(len(revs)-1, -1, -1):
222
r = branch.get_revision(revs[i])
223
# TODO: Handle timezone.
224
dt = datetime.datetime.fromtimestamp(r.timestamp)
225
if first >= dt and (last is None or dt >= last):
228
for i in range(len(revs)):
229
r = branch.get_revision(revs[i])
230
# TODO: Handle timezone.
231
dt = datetime.datetime.fromtimestamp(r.timestamp)
232
if first <= dt and (last is None or dt <= last):
234
REVISION_NAMESPACES['date:'] = _namespace_date
149
class RevisionSpec_int(RevisionSpec):
150
"""Spec is a number. Special case."""
151
def __init__(self, spec):
152
self.spec = int(spec)
154
def _match_on(self, branch, revs):
156
revno = len(revs) + self.spec + 1
159
rev_id = branch.get_rev_id(revno, revs)
160
return RevisionInfo(branch, revno, rev_id)
163
class RevisionSpec_revno(RevisionSpec):
166
def _match_on(self, branch, revs):
167
"""Lookup a revision by revision number"""
169
return RevisionInfo(branch, int(self.spec))
171
return RevisionInfo(branch, None)
173
SPEC_TYPES.append(RevisionSpec_revno)
176
class RevisionSpec_revid(RevisionSpec):
179
def _match_on(self, branch, revs):
181
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
183
return RevisionInfo(branch, None)
185
SPEC_TYPES.append(RevisionSpec_revid)
188
class RevisionSpec_last(RevisionSpec):
191
def _match_on(self, branch, revs):
193
offset = int(self.spec)
195
return RevisionInfo(branch, None)
198
raise BzrError('You must supply a positive value for --revision last:XXX')
199
return RevisionInfo(branch, len(revs) - offset + 1)
201
SPEC_TYPES.append(RevisionSpec_last)
204
class RevisionSpec_tag(RevisionSpec):
207
def _match_on(self, branch, revs):
208
raise BzrError('tag: namespace registered, but not implemented.')
210
SPEC_TYPES.append(RevisionSpec_tag)
213
class RevisionSpec_date(RevisionSpec):
215
_date_re = re.compile(
216
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
218
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
221
def _match_on(self, branch, revs):
223
Spec for date revisions:
225
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
226
it can also start with a '+/-/='. '+' says match the first
227
entry after the given date. '-' is match the first entry before the date
228
'=' is match the first entry after, but still on the given date.
230
+2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
231
-2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
232
=2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
233
May 13th, 2005 at 0:00
235
So the proper way of saying 'give me all entries for today' is:
236
-r {date:+today}:{date:-tomorrow}
237
The default is '=' when not supplied
240
if self.spec[:1] in ('+', '-', '='):
241
match_style = self.spec[:1]
242
self.spec = self.spec[1:]
244
# XXX: this should probably be using datetime.date instead
245
today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
247
if self.spec.lower() == 'yesterday':
248
dt = today - datetime.timedelta(days=1)
249
elif self.spec.lower() == 'today':
251
elif self.spec.lower() == 'tomorrow':
252
dt = today + datetime.timedelta(days=1)
254
m = self._date_re.match(self.spec)
255
if not m or (not m.group('date') and not m.group('time')):
256
raise BzrError('Invalid revision date %r' % self.spec)
259
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
261
year, month, day = today.year, today.month, today.day
263
hour = int(m.group('hour'))
264
minute = int(m.group('minute'))
265
if m.group('second'):
266
second = int(m.group('second'))
270
hour, minute, second = 0,0,0
272
dt = datetime.datetime(year=year, month=month, day=day,
273
hour=hour, minute=minute, second=second)
277
if match_style == '-':
279
elif match_style == '=':
280
last = dt + datetime.timedelta(days=1)
283
for i in range(len(revs)-1, -1, -1):
284
r = branch.get_revision(revs[i])
285
# TODO: Handle timezone.
286
dt = datetime.datetime.fromtimestamp(r.timestamp)
287
if first >= dt and (last is None or dt >= last):
288
return RevisionInfo(branch, i+1,)
290
for i in range(len(revs)):
291
r = branch.get_revision(revs[i])
292
# TODO: Handle timezone.
293
dt = datetime.datetime.fromtimestamp(r.timestamp)
294
if first <= dt and (last is None or dt <= last):
295
return RevisionInfo(branch, i+1,)
297
SPEC_TYPES.append(RevisionSpec_date)