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
print 'comparing', tuple(self), tuple(other)
104
return tuple(self) == tuple(other)
107
return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
108
self.revno, self.rev_id, self.branch)
113
def _namespace_revno(branch, revs, spec):
114
"""Lookup a revision by revision number"""
115
assert spec.startswith('revno:')
117
return (int(spec[len('revno:'):]),)
120
REVISION_NAMESPACES['revno:'] = _namespace_revno
123
def _namespace_revid(branch, revs, spec):
124
assert spec.startswith('revid:')
125
rev_id = spec[len('revid:'):]
127
return revs.index(rev_id) + 1, rev_id
130
REVISION_NAMESPACES['revid:'] = _namespace_revid
133
def _namespace_last(branch, revs, spec):
134
assert spec.startswith('last:')
136
offset = int(spec[5:])
141
raise BzrError('You must supply a positive value for --revision last:XXX')
142
return (len(revs) - offset + 1,)
143
REVISION_NAMESPACES['last:'] = _namespace_last
146
def _namespace_tag(branch, revs, spec):
147
assert spec.startswith('tag:')
148
raise BzrError('tag: namespace registered, but not implemented.')
149
REVISION_NAMESPACES['tag:'] = _namespace_tag
152
_date_re = re.compile(
153
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
155
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
158
def _namespace_date(branch, revs, spec):
160
Spec for date revisions:
162
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
163
it can also start with a '+/-/='. '+' says match the first
164
entry after the given date. '-' is match the first entry before the date
165
'=' is match the first entry after, but still on the given date.
167
+2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
168
-2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
169
=2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
170
May 13th, 2005 at 0:00
172
So the proper way of saying 'give me all entries for today' is:
173
-r {date:+today}:{date:-tomorrow}
174
The default is '=' when not supplied
176
assert spec.startswith('date:')
179
if val[:1] in ('+', '-', '='):
180
match_style = val[:1]
183
# XXX: this should probably be using datetime.date instead
184
today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
186
if val.lower() == 'yesterday':
187
dt = today - datetime.timedelta(days=1)
188
elif val.lower() == 'today':
190
elif val.lower() == 'tomorrow':
191
dt = today + datetime.timedelta(days=1)
193
m = _date_re.match(val)
194
if not m or (not m.group('date') and not m.group('time')):
195
raise BzrError('Invalid revision date %r' % spec)
198
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
200
year, month, day = today.year, today.month, today.day
202
hour = int(m.group('hour'))
203
minute = int(m.group('minute'))
204
if m.group('second'):
205
second = int(m.group('second'))
209
hour, minute, second = 0,0,0
211
dt = datetime.datetime(year=year, month=month, day=day,
212
hour=hour, minute=minute, second=second)
216
if match_style == '-':
218
elif match_style == '=':
219
last = dt + datetime.timedelta(days=1)
222
for i in range(len(revs)-1, -1, -1):
223
r = branch.get_revision(revs[i])
224
# TODO: Handle timezone.
225
dt = datetime.datetime.fromtimestamp(r.timestamp)
226
if first >= dt and (last is None or dt >= last):
229
for i in range(len(revs)):
230
r = branch.get_revision(revs[i])
231
# TODO: Handle timezone.
232
dt = datetime.datetime.fromtimestamp(r.timestamp)
233
if first <= dt and (last is None or dt <= last):
235
REVISION_NAMESPACES['date:'] = _namespace_date