1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
20
from bzrlib.errors import BzrError, NoSuchRevision
22
# Map some sort of prefix into a namespace
23
# stuff like "revno:10", "revid:", etc.
24
# This should match a prefix with a function which accepts it
25
REVISION_NAMESPACES = {}
27
class RevisionSpec(object):
28
"""Equivalent to the old get_revision_info().
29
An instance has two useful attributes: revno, and rev_id.
31
They can also be accessed as spec[0] and spec[1] respectively,
32
so that you can write code like:
33
revno, rev_id = RevisionSpec(branch, spec)
34
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.
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.
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' %
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)
90
def __getitem__(self, index):
91
if index == 0: return self.revno
92
if index == 1: return self.rev_id
93
raise IndexError(index)
96
return self.branch.get_revision(self.rev_id)
98
def __eq__(self, other):
99
if type(other) not in (tuple, list, type(self)):
101
if type(other) is type(self) and self.branch is not other.branch:
103
return tuple(self) == tuple(other)
106
return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
107
self.revno, self.rev_id, self.branch)
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