33
31
revno, rev_id = RevisionSpec(branch, spec)
34
32
although this is probably going to be deprecated later.
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.
39
def __init__(self, branch, revno, rev_id=_marker):
43
# allow caller to be lazy
44
if self.revno is None:
47
self.rev_id = branch.get_rev_id(self.revno)
51
def __nonzero__(self):
52
# first the easy ones...
53
if self.rev_id is None:
55
if self.revno is not None:
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
65
def __getitem__(self, index):
66
if index == 0: return self.revno
67
if index == 1: return self.rev_id
68
raise IndexError(index)
71
return self.branch.get_revision(self.rev_id)
73
def __eq__(self, other):
74
if type(other) not in (tuple, list, type(self)):
76
if type(other) is type(self) and self.branch is not other.branch:
78
return tuple(self) == tuple(other)
81
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
82
self.revno, self.rev_id, self.branch)
84
# classes in this list should have a "prefix" attribute, against which
85
# string specs are matched
88
class RevisionSpec(object):
89
"""A parsed revision specification.
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.
36
96
Revision specs are an UI element, and they have been moved out
37
97
of the branch class to leave "back-end" classes unaware of such
38
98
details. Code that gets a revno or rev_id from other code should
39
99
not be using revision specs - revnos and revision ids are the
40
100
accepted ways to refer to revisions internally.
102
(Equivalent to the old Branch method get_revision_info())
42
def __init__(self, branch, spec):
107
def __new__(cls, spec, foo=_marker):
43
108
"""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
111
return object.__new__(RevisionSpec, spec)
59
115
except ValueError:
61
revs = branch.revision_history()
62
118
if isinstance(spec, int):
64
self.revno = len(revs) + spec + 1
67
self.rev_id = branch.get_rev_id(self.revno, revs)
119
return object.__new__(RevisionSpec_int, spec)
68
120
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)
121
for spectype in SPEC_TYPES:
122
if spec.startswith(spectype.prefix):
123
return object.__new__(spectype, spec)
79
125
raise BzrError('No namespace registered for string: %r' %
82
128
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)
130
def __init__(self, spec):
131
if self.prefix and spec.startswith(self.prefix):
132
spec = spec[len(self.prefix):]
135
def _match_on(self, branch, revs):
136
return RevisionInfo(branch, 0, None)
138
def _match_on_and_check(self, branch, revs):
139
info = self._match_on(branch, revs)
142
elif info == (0, None):
143
# special case - the empty tree
146
raise NoSuchRevision(branch, self.prefix + str(self.spec))
148
raise NoSuchRevision(branch, str(self.spec))
150
def in_history(self, branch):
151
revs = branch.revision_history()
152
return self._match_on_and_check(branch, revs)
106
154
def __repr__(self):
107
return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
108
self.revno, self.rev_id, self.branch)
155
# this is mostly for helping with testing
156
return '<%s %s%s>' % (self.__class__.__name__,
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):
163
class RevisionSpec_int(RevisionSpec):
164
"""Spec is a number. Special case."""
165
def __init__(self, spec):
166
self.spec = int(spec)
168
def _match_on(self, branch, revs):
170
revno = len(revs) + self.spec + 1
173
rev_id = branch.get_rev_id(revno, revs)
174
return RevisionInfo(branch, revno, rev_id)
177
class RevisionSpec_revno(RevisionSpec):
180
def _match_on(self, branch, revs):
181
"""Lookup a revision by revision number"""
183
return RevisionInfo(branch, int(self.spec))
185
return RevisionInfo(branch, None)
187
SPEC_TYPES.append(RevisionSpec_revno)
190
class RevisionSpec_revid(RevisionSpec):
193
def _match_on(self, branch, revs):
195
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
197
return RevisionInfo(branch, None)
199
SPEC_TYPES.append(RevisionSpec_revid)
202
class RevisionSpec_last(RevisionSpec):
206
def _match_on(self, branch, revs):
208
offset = int(self.spec)
210
return RevisionInfo(branch, None)
213
raise BzrError('You must supply a positive value for --revision last:XXX')
214
return RevisionInfo(branch, len(revs) - offset + 1)
216
SPEC_TYPES.append(RevisionSpec_last)
219
class RevisionSpec_before(RevisionSpec):
223
def _match_on(self, branch, revs):
224
r = RevisionSpec(self.spec)._match_on(branch, revs)
225
if (r.revno is None) or (r.revno == 0):
227
return RevisionInfo(branch, r.revno - 1)
229
SPEC_TYPES.append(RevisionSpec_before)
232
class RevisionSpec_tag(RevisionSpec):
235
def _match_on(self, branch, revs):
236
raise BzrError('tag: namespace registered, but not implemented.')
238
SPEC_TYPES.append(RevisionSpec_tag)
241
class RevisionSpec_date(RevisionSpec):
243
_date_re = re.compile(
244
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
246
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
249
def _match_on(self, branch, revs):
251
Spec for date revisions:
253
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
254
matches the first entry after a given date (either at midnight or
255
at a specified time).
257
So the proper way of saying 'give me all entries for today' is:
258
-r date:today..date:tomorrow
260
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
261
if self.spec.lower() == 'yesterday':
262
dt = today - datetime.timedelta(days=1)
263
elif self.spec.lower() == 'today':
265
elif self.spec.lower() == 'tomorrow':
266
dt = today + datetime.timedelta(days=1)
268
m = self._date_re.match(self.spec)
269
if not m or (not m.group('date') and not m.group('time')):
270
raise BzrError('Invalid revision date %r' % self.spec)
273
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
275
year, month, day = today.year, today.month, today.day
277
hour = int(m.group('hour'))
278
minute = int(m.group('minute'))
279
if m.group('second'):
280
second = int(m.group('second'))
284
hour, minute, second = 0,0,0
286
dt = datetime.datetime(year=year, month=month, day=day,
287
hour=hour, minute=minute, second=second)
229
289
for i in range(len(revs)):
230
290
r = branch.get_revision(revs[i])
231
291
# TODO: Handle timezone.
232
292
dt = datetime.datetime.fromtimestamp(r.timestamp)
233
if first <= dt and (last is None or dt <= last):
235
REVISION_NAMESPACES['date:'] = _namespace_date
294
return RevisionInfo(branch, i+1)
295
return RevisionInfo(branch, None)
297
SPEC_TYPES.append(RevisionSpec_date)
300
class RevisionSpec_ancestor(RevisionSpec):
303
def _match_on(self, branch, revs):
304
from branch import Branch
305
from revision import common_ancestor, MultipleRevisionSources
306
other_branch = Branch.open_containing(self.spec)
307
revision_a = branch.last_revision()
308
revision_b = other_branch.last_revision()
309
for r, b in ((revision_a, branch), (revision_b, other_branch)):
312
revision_source = MultipleRevisionSources(branch, other_branch)
313
rev_id = common_ancestor(revision_a, revision_b, revision_source)
315
revno = branch.revision_id_to_revno(rev_id)
316
except NoSuchRevision:
318
return RevisionInfo(branch, revno, rev_id)
320
SPEC_TYPES.append(RevisionSpec_ancestor)