76
101
if type(other) is type(self) and self.branch is not other.branch:
103
print 'comparing', tuple(self), tuple(other)
78
104
return tuple(self) == tuple(other)
80
106
def __repr__(self):
81
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
107
return '<bzrlib.revisionspec.RevisionSpec object %s, %s for %r>' % (
82
108
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.
96
Revision specs are an UI element, and they have been moved out
97
of the branch class to leave "back-end" classes unaware of such
98
details. Code that gets a revno or rev_id from other code should
99
not be using revision specs - revnos and revision ids are the
100
accepted ways to refer to revisions internally.
102
(Equivalent to the old Branch method get_revision_info())
107
def __new__(cls, spec, foo=_marker):
108
"""Parse a revision specifier.
111
return object.__new__(RevisionSpec, spec)
118
if isinstance(spec, int):
119
return object.__new__(RevisionSpec_int, spec)
120
elif isinstance(spec, basestring):
121
for spectype in SPEC_TYPES:
122
if spec.startswith(spectype.prefix):
123
return object.__new__(spectype, spec)
125
raise BzrError('No namespace registered for string: %r' %
128
raise TypeError('Unhandled revision type %s' % spec)
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)
154
# FIXME: in_history is somewhat broken,
155
# it will return non-history revisions in many
156
# circumstances. The expected facility is that
157
# in_history only returns revision-history revs,
158
# in_store returns any rev. RBC 20051010
159
# aliases for now, when we fix the core logic, then they
160
# will do what you expect.
161
in_store = in_history
165
# this is mostly for helping with testing
166
return '<%s %s%s>' % (self.__class__.__name__,
173
class RevisionSpec_int(RevisionSpec):
174
"""Spec is a number. Special case."""
175
def __init__(self, spec):
176
self.spec = int(spec)
178
def _match_on(self, branch, revs):
180
revno = len(revs) + self.spec + 1
183
rev_id = branch.get_rev_id(revno, revs)
184
return RevisionInfo(branch, revno, rev_id)
187
class RevisionSpec_revno(RevisionSpec):
190
def _match_on(self, branch, revs):
191
"""Lookup a revision by revision number"""
193
return RevisionInfo(branch, int(self.spec))
195
return RevisionInfo(branch, None)
197
SPEC_TYPES.append(RevisionSpec_revno)
200
class RevisionSpec_revid(RevisionSpec):
203
def _match_on(self, branch, revs):
205
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
207
return RevisionInfo(branch, None)
209
SPEC_TYPES.append(RevisionSpec_revid)
212
class RevisionSpec_last(RevisionSpec):
216
def _match_on(self, branch, revs):
218
offset = int(self.spec)
220
return RevisionInfo(branch, None)
223
raise BzrError('You must supply a positive value for --revision last:XXX')
224
return RevisionInfo(branch, len(revs) - offset + 1)
226
SPEC_TYPES.append(RevisionSpec_last)
229
class RevisionSpec_before(RevisionSpec):
233
def _match_on(self, branch, revs):
234
r = RevisionSpec(self.spec)._match_on(branch, revs)
235
if (r.revno is None) or (r.revno == 0):
237
return RevisionInfo(branch, r.revno - 1)
239
SPEC_TYPES.append(RevisionSpec_before)
242
class RevisionSpec_tag(RevisionSpec):
245
def _match_on(self, branch, revs):
246
raise BzrError('tag: namespace registered, but not implemented.')
248
SPEC_TYPES.append(RevisionSpec_tag)
251
class RevisionSpec_date(RevisionSpec):
253
_date_re = re.compile(
254
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
256
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
259
def _match_on(self, branch, revs):
261
Spec for date revisions:
263
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
264
matches the first entry after a given date (either at midnight or
265
at a specified time).
267
So the proper way of saying 'give me all entries for today' is:
268
-r date:today..date:tomorrow
270
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
271
if self.spec.lower() == 'yesterday':
272
dt = today - datetime.timedelta(days=1)
273
elif self.spec.lower() == 'today':
275
elif self.spec.lower() == 'tomorrow':
276
dt = today + datetime.timedelta(days=1)
278
m = self._date_re.match(self.spec)
279
if not m or (not m.group('date') and not m.group('time')):
280
raise BzrError('Invalid revision date %r' % self.spec)
283
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
285
year, month, day = today.year, today.month, today.day
287
hour = int(m.group('hour'))
288
minute = int(m.group('minute'))
289
if m.group('second'):
290
second = int(m.group('second'))
294
hour, minute, second = 0,0,0
296
dt = datetime.datetime(year=year, month=month, day=day,
297
hour=hour, minute=minute, second=second)
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):
299
229
for i in range(len(revs)):
300
230
r = branch.get_revision(revs[i])
301
231
# TODO: Handle timezone.
302
232
dt = datetime.datetime.fromtimestamp(r.timestamp)
304
return RevisionInfo(branch, i+1)
305
return RevisionInfo(branch, None)
307
SPEC_TYPES.append(RevisionSpec_date)
310
class RevisionSpec_ancestor(RevisionSpec):
313
def _match_on(self, branch, revs):
314
from branch import Branch
315
from revision import common_ancestor, MultipleRevisionSources
316
other_branch = Branch.open_containing(self.spec)[0]
317
revision_a = branch.last_revision()
318
revision_b = other_branch.last_revision()
319
for r, b in ((revision_a, branch), (revision_b, other_branch)):
322
revision_source = MultipleRevisionSources(branch, other_branch)
323
rev_id = common_ancestor(revision_a, revision_b, revision_source)
325
revno = branch.revision_id_to_revno(rev_id)
326
except NoSuchRevision:
328
return RevisionInfo(branch, revno, rev_id)
330
SPEC_TYPES.append(RevisionSpec_ancestor)
332
class RevisionSpec_branch(RevisionSpec):
333
"""A branch: revision specifier.
335
This takes the path to a branch and returns its tip revision id.
339
def _match_on(self, branch, revs):
340
from branch import Branch
341
from fetch import greedy_fetch
342
other_branch = Branch.open_containing(self.spec)[0]
343
revision_b = other_branch.last_revision()
344
if revision_b is None:
345
raise NoCommits(other_branch)
346
# pull in the remote revisions so we can diff
347
greedy_fetch(branch, other_branch, revision=revision_b)
349
revno = branch.revision_id_to_revno(revision_b)
350
except NoSuchRevision:
352
return RevisionInfo(branch, revno, revision_b)
354
SPEC_TYPES.append(RevisionSpec_branch)
233
if first <= dt and (last is None or dt <= last):
235
REVISION_NAMESPACES['date:'] = _namespace_date