~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-31 18:27:04 UTC
  • mfrom: (1948.4.36 revspec-errors-55420)
  • Revision ID: pqm@pqm.ubuntu.com-20060831182704-2de4cd234a448ed1
(jam) improve revision spec errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
import bisect
18
19
import datetime
19
20
import re
20
 
import bisect
21
 
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    revision,
 
25
    symbol_versioning,
 
26
    trace,
 
27
    )
 
28
 
22
29
 
23
30
_marker = []
24
31
 
 
32
 
25
33
class RevisionInfo(object):
26
34
    """The results of applying a revision specification to a branch.
27
35
 
82
90
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
83
91
            self.revno, self.rev_id, self.branch)
84
92
 
 
93
 
85
94
# classes in this list should have a "prefix" attribute, against which
86
95
# string specs are matched
87
96
SPEC_TYPES = []
 
97
_revno_regex = None
 
98
 
88
99
 
89
100
class RevisionSpec(object):
90
101
    """A parsed revision specification.
105
116
 
106
117
    prefix = None
107
118
 
108
 
    def __new__(cls, spec, foo=_marker):
109
 
        """Parse a revision specifier.
 
119
    def __new__(cls, spec, _internal=False):
 
120
        if _internal:
 
121
            return object.__new__(cls, spec, _internal=_internal)
 
122
 
 
123
        symbol_versioning.warn('Creating a RevisionSpec directly has'
 
124
                               ' been deprecated in version 0.11. Use'
 
125
                               ' RevisionSpec.from_string()'
 
126
                               ' instead.',
 
127
                               DeprecationWarning, stacklevel=2)
 
128
        return RevisionSpec.from_string(spec)
 
129
 
 
130
    @staticmethod
 
131
    def from_string(spec):
 
132
        """Parse a revision spec string into a RevisionSpec object.
 
133
 
 
134
        :param spec: A string specified by the user
 
135
        :return: A RevisionSpec object that understands how to parse the
 
136
            supplied notation.
110
137
        """
 
138
        if not isinstance(spec, (type(None), basestring)):
 
139
            raise TypeError('error')
 
140
 
111
141
        if spec is None:
112
 
            return object.__new__(RevisionSpec, spec)
113
 
 
114
 
        try:
115
 
            spec = int(spec)
116
 
        except ValueError:
117
 
            pass
118
 
 
119
 
        if isinstance(spec, int):
120
 
            return object.__new__(RevisionSpec_int, spec)
121
 
        elif isinstance(spec, basestring):
122
 
            for spectype in SPEC_TYPES:
123
 
                if spec.startswith(spectype.prefix):
124
 
                    return object.__new__(spectype, spec)
125
 
            else:
126
 
                raise BzrError('No namespace registered for string: %r' %
127
 
                               spec)
 
142
            return RevisionSpec(None, _internal=True)
 
143
 
 
144
        assert isinstance(spec, basestring), \
 
145
            "You should only supply strings not %s" % (type(spec),)
 
146
 
 
147
        for spectype in SPEC_TYPES:
 
148
            if spec.startswith(spectype.prefix):
 
149
                trace.mutter('Returning RevisionSpec %s for %s',
 
150
                             spectype.__name__, spec)
 
151
                return spectype(spec, _internal=True)
128
152
        else:
129
 
            raise TypeError('Unhandled revision type %s' % spec)
130
 
 
131
 
    def __init__(self, spec):
 
153
            # RevisionSpec_revno is special cased, because it is the only
 
154
            # one that directly handles plain integers
 
155
            global _revno_regex
 
156
            if _revno_regex is None:
 
157
                _revno_regex = re.compile(r'-?\d+(:.*)?$')
 
158
            if _revno_regex.match(spec) is not None:
 
159
                return RevisionSpec_revno(spec, _internal=True)
 
160
 
 
161
            raise errors.NoSuchRevisionSpec(spec)
 
162
 
 
163
    def __init__(self, spec, _internal=False):
 
164
        """Create a RevisionSpec referring to the Null revision.
 
165
 
 
166
        :param spec: The original spec supplied by the user
 
167
        :param _internal: Used to ensure that RevisionSpec is not being
 
168
            called directly. Only from RevisionSpec.from_string()
 
169
        """
 
170
        if not _internal:
 
171
            # XXX: Update this after 0.10 is released
 
172
            symbol_versioning.warn('Creating a RevisionSpec directly has'
 
173
                                   ' been deprecated in version 0.11. Use'
 
174
                                   ' RevisionSpec.from_string()'
 
175
                                   ' instead.',
 
176
                                   DeprecationWarning, stacklevel=2)
 
177
        self.user_spec = spec
132
178
        if self.prefix and spec.startswith(self.prefix):
133
179
            spec = spec[len(self.prefix):]
134
180
        self.spec = spec
135
181
 
136
182
    def _match_on(self, branch, revs):
 
183
        trace.mutter('Returning RevisionSpec._match_on: None')
137
184
        return RevisionInfo(branch, 0, None)
138
185
 
139
186
    def _match_on_and_check(self, branch, revs):
144
191
            # special case - the empty tree
145
192
            return info
146
193
        elif self.prefix:
147
 
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
 
194
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
148
195
        else:
149
 
            raise NoSuchRevision(branch, str(self.spec))
 
196
            raise errors.InvalidRevisionSpec(self.spec, branch)
150
197
 
151
198
    def in_history(self, branch):
152
199
        if branch:
167
214
        
168
215
    def __repr__(self):
169
216
        # this is mostly for helping with testing
170
 
        return '<%s %s%s>' % (self.__class__.__name__,
171
 
                              self.prefix or '',
172
 
                              self.spec)
 
217
        return '<%s %s>' % (self.__class__.__name__,
 
218
                              self.user_spec)
173
219
    
174
220
    def needs_branch(self):
175
221
        """Whether this revision spec needs a branch.
178
224
        """
179
225
        return True
180
226
 
 
227
 
181
228
# private API
182
229
 
183
 
class RevisionSpec_int(RevisionSpec):
184
 
    """Spec is a number.  Special case."""
185
 
    def __init__(self, spec):
186
 
        self.spec = int(spec)
187
 
 
188
 
    def _match_on(self, branch, revs):
189
 
        if self.spec < 0:
190
 
            revno = len(revs) + self.spec + 1
191
 
        else:
192
 
            revno = self.spec
193
 
        rev_id = branch.get_rev_id(revno, revs)
194
 
        return RevisionInfo(branch, revno, rev_id)
195
 
 
196
 
 
197
230
class RevisionSpec_revno(RevisionSpec):
198
231
    prefix = 'revno:'
199
232
 
200
233
    def _match_on(self, branch, revs):
201
234
        """Lookup a revision by revision number"""
202
 
        if self.spec.find(':') == -1:
203
 
            try:
204
 
                return RevisionInfo(branch, int(self.spec))
205
 
            except ValueError:
206
 
                return RevisionInfo(branch, None)
207
 
        else:
208
 
            from branch import Branch
209
 
            revname = self.spec[self.spec.find(':')+1:]
210
 
            other_branch = Branch.open_containing(revname)[0]
211
 
            try:
212
 
                revno = int(self.spec[:self.spec.find(':')])
213
 
            except ValueError:
214
 
                return RevisionInfo(other_branch, None)
215
 
            revid = other_branch.get_rev_id(revno)
216
 
            return RevisionInfo(other_branch, revno)
 
235
        loc = self.spec.find(':')
 
236
        if loc == -1:
 
237
            revno_spec = self.spec
 
238
            branch_spec = None
 
239
        else:
 
240
            revno_spec = self.spec[:loc]
 
241
            branch_spec = self.spec[loc+1:]
 
242
 
 
243
        if revno_spec == '':
 
244
            if not branch_spec:
 
245
                raise errors.InvalidRevisionSpec(self.user_spec,
 
246
                        branch, 'cannot have an empty revno and no branch')
 
247
            revno = None
 
248
        else:
 
249
            try:
 
250
                revno = int(revno_spec)
 
251
            except ValueError, e:
 
252
                raise errors.InvalidRevisionSpec(self.user_spec,
 
253
                                                 branch, e)
 
254
 
 
255
        if branch_spec:
 
256
            from bzrlib.branch import Branch
 
257
            branch = Branch.open(branch_spec)
 
258
            # Need to use a new revision history
 
259
            # because we are using a specific branch
 
260
            revs = branch.revision_history()
 
261
 
 
262
        if revno < 0:
 
263
            if (-revno) >= len(revs):
 
264
                revno = 1
 
265
            else:
 
266
                revno = len(revs) + revno + 1
 
267
        try:
 
268
            revision_id = branch.get_rev_id(revno, revs)
 
269
        except errors.NoSuchRevision:
 
270
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
271
        return RevisionInfo(branch, revno, revision_id)
217
272
        
218
273
    def needs_branch(self):
219
274
        return self.spec.find(':') == -1
220
275
 
 
276
# Old compatibility 
 
277
RevisionSpec_int = RevisionSpec_revno
 
278
 
221
279
SPEC_TYPES.append(RevisionSpec_revno)
222
280
 
223
281
 
226
284
 
227
285
    def _match_on(self, branch, revs):
228
286
        try:
229
 
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
 
287
            revno = revs.index(self.spec) + 1
230
288
        except ValueError:
231
 
            return RevisionInfo(branch, None, self.spec)
 
289
            revno = None
 
290
        return RevisionInfo(branch, revno, self.spec)
232
291
 
233
292
SPEC_TYPES.append(RevisionSpec_revid)
234
293
 
238
297
    prefix = 'last:'
239
298
 
240
299
    def _match_on(self, branch, revs):
 
300
        if self.spec == '':
 
301
            if not revs:
 
302
                raise errors.NoCommits(branch)
 
303
            return RevisionInfo(branch, len(revs), revs[-1])
 
304
 
241
305
        try:
242
306
            offset = int(self.spec)
243
 
        except ValueError:
244
 
            return RevisionInfo(branch, None)
245
 
        else:
246
 
            if offset <= 0:
247
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
248
 
            return RevisionInfo(branch, len(revs) - offset + 1)
 
307
        except ValueError, e:
 
308
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
309
 
 
310
        if offset <= 0:
 
311
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
312
                                             'you must supply a positive value')
 
313
        revno = len(revs) - offset + 1
 
314
        try:
 
315
            revision_id = branch.get_rev_id(revno, revs)
 
316
        except errors.NoSuchRevision:
 
317
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
318
        return RevisionInfo(branch, revno, revision_id)
249
319
 
250
320
SPEC_TYPES.append(RevisionSpec_last)
251
321
 
255
325
    prefix = 'before:'
256
326
    
257
327
    def _match_on(self, branch, revs):
258
 
        r = RevisionSpec(self.spec)._match_on(branch, revs)
259
 
        if (r.revno is None) or (r.revno == 0):
260
 
            return r
261
 
        return RevisionInfo(branch, r.revno - 1)
 
328
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
329
        if r.revno == 0:
 
330
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
331
                                         'cannot go before the null: revision')
 
332
        if r.revno is None:
 
333
            # We need to use the repository history here
 
334
            rev = branch.repository.get_revision(r.rev_id)
 
335
            if not rev.parent_ids:
 
336
                revno = 0
 
337
                revision_id = None
 
338
            else:
 
339
                revision_id = rev.parent_ids[0]
 
340
                try:
 
341
                    revno = revs.index(revision_id) + 1
 
342
                except ValueError:
 
343
                    revno = None
 
344
        else:
 
345
            revno = r.revno - 1
 
346
            try:
 
347
                revision_id = branch.get_rev_id(revno, revs)
 
348
            except errors.NoSuchRevision:
 
349
                raise errors.InvalidRevisionSpec(self.user_spec,
 
350
                                                 branch)
 
351
        return RevisionInfo(branch, revno, revision_id)
262
352
 
263
353
SPEC_TYPES.append(RevisionSpec_before)
264
354
 
267
357
    prefix = 'tag:'
268
358
 
269
359
    def _match_on(self, branch, revs):
270
 
        raise BzrError('tag: namespace registered, but not implemented.')
 
360
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
361
                                         'tag: namespace registered,'
 
362
                                         ' but not implemented')
271
363
 
272
364
SPEC_TYPES.append(RevisionSpec_tag)
273
365
 
274
366
 
275
 
class RevisionSpec_revs:
 
367
class _RevListToTimestamps(object):
 
368
    """This takes a list of revisions, and allows you to bisect by date"""
 
369
 
 
370
    __slots__ = ['revs', 'branch']
 
371
 
276
372
    def __init__(self, revs, branch):
277
373
        self.revs = revs
278
374
        self.branch = branch
 
375
 
279
376
    def __getitem__(self, index):
 
377
        """Get the date of the index'd item"""
280
378
        r = self.branch.repository.get_revision(self.revs[index])
281
379
        # TODO: Handle timezone.
282
380
        return datetime.datetime.fromtimestamp(r.timestamp)
 
381
 
283
382
    def __len__(self):
284
383
        return len(self.revs)
285
384
 
313
412
        else:
314
413
            m = self._date_re.match(self.spec)
315
414
            if not m or (not m.group('date') and not m.group('time')):
316
 
                raise BzrError('Invalid revision date %r' % self.spec)
317
 
 
318
 
            if m.group('date'):
319
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
320
 
            else:
321
 
                year, month, day = today.year, today.month, today.day
322
 
            if m.group('time'):
323
 
                hour = int(m.group('hour'))
324
 
                minute = int(m.group('minute'))
325
 
                if m.group('second'):
326
 
                    second = int(m.group('second'))
327
 
                else:
328
 
                    second = 0
329
 
            else:
330
 
                hour, minute, second = 0,0,0
 
415
                raise errors.InvalidRevisionSpec(self.user_spec,
 
416
                                                 branch, 'invalid date')
 
417
 
 
418
            try:
 
419
                if m.group('date'):
 
420
                    year = int(m.group('year'))
 
421
                    month = int(m.group('month'))
 
422
                    day = int(m.group('day'))
 
423
                else:
 
424
                    year = today.year
 
425
                    month = today.month
 
426
                    day = today.day
 
427
 
 
428
                if m.group('time'):
 
429
                    hour = int(m.group('hour'))
 
430
                    minute = int(m.group('minute'))
 
431
                    if m.group('second'):
 
432
                        second = int(m.group('second'))
 
433
                    else:
 
434
                        second = 0
 
435
                else:
 
436
                    hour, minute, second = 0,0,0
 
437
            except ValueError:
 
438
                raise errors.InvalidRevisionSpec(self.user_spec,
 
439
                                                 branch, 'invalid date')
331
440
 
332
441
            dt = datetime.datetime(year=year, month=month, day=day,
333
442
                    hour=hour, minute=minute, second=second)
334
443
        branch.lock_read()
335
444
        try:
336
 
            rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
 
445
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
337
446
        finally:
338
447
            branch.unlock()
339
448
        if rev == len(revs):
348
457
    prefix = 'ancestor:'
349
458
 
350
459
    def _match_on(self, branch, revs):
351
 
        from branch import Branch
352
 
        from revision import common_ancestor, MultipleRevisionSources
353
 
        other_branch = Branch.open_containing(self.spec)[0]
 
460
        from bzrlib.branch import Branch
 
461
 
 
462
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
463
        other_branch = Branch.open(self.spec)
354
464
        revision_a = branch.last_revision()
355
465
        revision_b = other_branch.last_revision()
356
466
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
357
 
            if r is None:
358
 
                raise NoCommits(b)
359
 
        revision_source = MultipleRevisionSources(branch.repository,
360
 
                                                  other_branch.repository)
361
 
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
 
467
            if r in (None, revision.NULL_REVISION):
 
468
                raise errors.NoCommits(b)
 
469
        revision_source = revision.MultipleRevisionSources(
 
470
                branch.repository, other_branch.repository)
 
471
        rev_id = revision.common_ancestor(revision_a, revision_b,
 
472
                                          revision_source)
362
473
        try:
363
474
            revno = branch.revision_id_to_revno(rev_id)
364
 
        except NoSuchRevision:
 
475
        except errors.NoSuchRevision:
365
476
            revno = None
366
477
        return RevisionInfo(branch, revno, rev_id)
367
478
        
368
479
SPEC_TYPES.append(RevisionSpec_ancestor)
369
480
 
 
481
 
370
482
class RevisionSpec_branch(RevisionSpec):
371
483
    """A branch: revision specifier.
372
484
 
375
487
    prefix = 'branch:'
376
488
 
377
489
    def _match_on(self, branch, revs):
378
 
        from branch import Branch
379
 
        other_branch = Branch.open_containing(self.spec)[0]
 
490
        from bzrlib.branch import Branch
 
491
        other_branch = Branch.open(self.spec)
380
492
        revision_b = other_branch.last_revision()
381
 
        if revision_b is None:
382
 
            raise NoCommits(other_branch)
 
493
        if revision_b in (None, revision.NULL_REVISION):
 
494
            raise errors.NoCommits(other_branch)
383
495
        # pull in the remote revisions so we can diff
384
496
        branch.fetch(other_branch, revision_b)
385
497
        try:
386
498
            revno = branch.revision_id_to_revno(revision_b)
387
 
        except NoSuchRevision:
 
499
        except errors.NoSuchRevision:
388
500
            revno = None
389
501
        return RevisionInfo(branch, revno, revision_b)
390
502