/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Aaron Bentley
  • Date: 2006-09-28 13:48:10 UTC
  • mfrom: (2049 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20060928134810-2c8ae086a4a70f43
Merge bzr.dev

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
    def get_branch(self):
 
228
        """When the revision specifier contains a branch location, return it.
 
229
        
 
230
        Otherwise, return None.
 
231
        """
 
232
        return None
 
233
 
 
234
 
181
235
# private API
182
236
 
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
237
class RevisionSpec_revno(RevisionSpec):
198
238
    prefix = 'revno:'
199
239
 
200
240
    def _match_on(self, branch, revs):
201
241
        """Lookup a revision by revision number"""
 
242
        loc = self.spec.find(':')
 
243
        if loc == -1:
 
244
            revno_spec = self.spec
 
245
            branch_spec = None
 
246
        else:
 
247
            revno_spec = self.spec[:loc]
 
248
            branch_spec = self.spec[loc+1:]
 
249
 
 
250
        if revno_spec == '':
 
251
            if not branch_spec:
 
252
                raise errors.InvalidRevisionSpec(self.user_spec,
 
253
                        branch, 'cannot have an empty revno and no branch')
 
254
            revno = None
 
255
        else:
 
256
            try:
 
257
                revno = int(revno_spec)
 
258
            except ValueError, e:
 
259
                raise errors.InvalidRevisionSpec(self.user_spec,
 
260
                                                 branch, e)
 
261
 
 
262
        if branch_spec:
 
263
            from bzrlib.branch import Branch
 
264
            branch = Branch.open(branch_spec)
 
265
            # Need to use a new revision history
 
266
            # because we are using a specific branch
 
267
            revs = branch.revision_history()
 
268
 
 
269
        if revno < 0:
 
270
            if (-revno) >= len(revs):
 
271
                revno = 1
 
272
            else:
 
273
                revno = len(revs) + revno + 1
 
274
        try:
 
275
            revision_id = branch.get_rev_id(revno, revs)
 
276
        except errors.NoSuchRevision:
 
277
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
278
        return RevisionInfo(branch, revno, revision_id)
 
279
        
 
280
    def needs_branch(self):
 
281
        return self.spec.find(':') == -1
 
282
 
 
283
    def get_branch(self):
202
284
        if self.spec.find(':') == -1:
203
 
            try:
204
 
                return RevisionInfo(branch, int(self.spec))
205
 
            except ValueError:
206
 
                return RevisionInfo(branch, None)
 
285
            return None
207
286
        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)
217
 
        
218
 
    def needs_branch(self):
219
 
        return self.spec.find(':') == -1
 
287
            return self.spec[self.spec.find(':')+1:]
 
288
 
 
289
# Old compatibility 
 
290
RevisionSpec_int = RevisionSpec_revno
220
291
 
221
292
SPEC_TYPES.append(RevisionSpec_revno)
222
293
 
226
297
 
227
298
    def _match_on(self, branch, revs):
228
299
        try:
229
 
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
 
300
            revno = revs.index(self.spec) + 1
230
301
        except ValueError:
231
 
            return RevisionInfo(branch, None, self.spec)
 
302
            revno = None
 
303
        return RevisionInfo(branch, revno, self.spec)
232
304
 
233
305
SPEC_TYPES.append(RevisionSpec_revid)
234
306
 
238
310
    prefix = 'last:'
239
311
 
240
312
    def _match_on(self, branch, revs):
 
313
        if self.spec == '':
 
314
            if not revs:
 
315
                raise errors.NoCommits(branch)
 
316
            return RevisionInfo(branch, len(revs), revs[-1])
 
317
 
241
318
        try:
242
319
            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)
 
320
        except ValueError, e:
 
321
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
322
 
 
323
        if offset <= 0:
 
324
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
325
                                             'you must supply a positive value')
 
326
        revno = len(revs) - offset + 1
 
327
        try:
 
328
            revision_id = branch.get_rev_id(revno, revs)
 
329
        except errors.NoSuchRevision:
 
330
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
331
        return RevisionInfo(branch, revno, revision_id)
249
332
 
250
333
SPEC_TYPES.append(RevisionSpec_last)
251
334
 
255
338
    prefix = 'before:'
256
339
    
257
340
    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)
 
341
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
342
        if r.revno == 0:
 
343
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
344
                                         'cannot go before the null: revision')
 
345
        if r.revno is None:
 
346
            # We need to use the repository history here
 
347
            rev = branch.repository.get_revision(r.rev_id)
 
348
            if not rev.parent_ids:
 
349
                revno = 0
 
350
                revision_id = None
 
351
            else:
 
352
                revision_id = rev.parent_ids[0]
 
353
                try:
 
354
                    revno = revs.index(revision_id) + 1
 
355
                except ValueError:
 
356
                    revno = None
 
357
        else:
 
358
            revno = r.revno - 1
 
359
            try:
 
360
                revision_id = branch.get_rev_id(revno, revs)
 
361
            except errors.NoSuchRevision:
 
362
                raise errors.InvalidRevisionSpec(self.user_spec,
 
363
                                                 branch)
 
364
        return RevisionInfo(branch, revno, revision_id)
262
365
 
263
366
SPEC_TYPES.append(RevisionSpec_before)
264
367
 
267
370
    prefix = 'tag:'
268
371
 
269
372
    def _match_on(self, branch, revs):
270
 
        raise BzrError('tag: namespace registered, but not implemented.')
 
373
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
374
                                         'tag: namespace registered,'
 
375
                                         ' but not implemented')
271
376
 
272
377
SPEC_TYPES.append(RevisionSpec_tag)
273
378
 
274
379
 
275
 
class RevisionSpec_revs:
 
380
class _RevListToTimestamps(object):
 
381
    """This takes a list of revisions, and allows you to bisect by date"""
 
382
 
 
383
    __slots__ = ['revs', 'branch']
 
384
 
276
385
    def __init__(self, revs, branch):
277
386
        self.revs = revs
278
387
        self.branch = branch
 
388
 
279
389
    def __getitem__(self, index):
 
390
        """Get the date of the index'd item"""
280
391
        r = self.branch.repository.get_revision(self.revs[index])
281
392
        # TODO: Handle timezone.
282
393
        return datetime.datetime.fromtimestamp(r.timestamp)
 
394
 
283
395
    def __len__(self):
284
396
        return len(self.revs)
285
397
 
313
425
        else:
314
426
            m = self._date_re.match(self.spec)
315
427
            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
 
428
                raise errors.InvalidRevisionSpec(self.user_spec,
 
429
                                                 branch, 'invalid date')
 
430
 
 
431
            try:
 
432
                if m.group('date'):
 
433
                    year = int(m.group('year'))
 
434
                    month = int(m.group('month'))
 
435
                    day = int(m.group('day'))
 
436
                else:
 
437
                    year = today.year
 
438
                    month = today.month
 
439
                    day = today.day
 
440
 
 
441
                if m.group('time'):
 
442
                    hour = int(m.group('hour'))
 
443
                    minute = int(m.group('minute'))
 
444
                    if m.group('second'):
 
445
                        second = int(m.group('second'))
 
446
                    else:
 
447
                        second = 0
 
448
                else:
 
449
                    hour, minute, second = 0,0,0
 
450
            except ValueError:
 
451
                raise errors.InvalidRevisionSpec(self.user_spec,
 
452
                                                 branch, 'invalid date')
331
453
 
332
454
            dt = datetime.datetime(year=year, month=month, day=day,
333
455
                    hour=hour, minute=minute, second=second)
334
456
        branch.lock_read()
335
457
        try:
336
 
            rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
 
458
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
337
459
        finally:
338
460
            branch.unlock()
339
461
        if rev == len(revs):
348
470
    prefix = 'ancestor:'
349
471
 
350
472
    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]
 
473
        from bzrlib.branch import Branch
 
474
 
 
475
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
476
        other_branch = Branch.open(self.spec)
354
477
        revision_a = branch.last_revision()
355
478
        revision_b = other_branch.last_revision()
356
479
        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)
 
480
            if r in (None, revision.NULL_REVISION):
 
481
                raise errors.NoCommits(b)
 
482
        revision_source = revision.MultipleRevisionSources(
 
483
                branch.repository, other_branch.repository)
 
484
        rev_id = revision.common_ancestor(revision_a, revision_b,
 
485
                                          revision_source)
362
486
        try:
363
487
            revno = branch.revision_id_to_revno(rev_id)
364
 
        except NoSuchRevision:
 
488
        except errors.NoSuchRevision:
365
489
            revno = None
366
490
        return RevisionInfo(branch, revno, rev_id)
367
491
        
368
492
SPEC_TYPES.append(RevisionSpec_ancestor)
369
493
 
 
494
 
370
495
class RevisionSpec_branch(RevisionSpec):
371
496
    """A branch: revision specifier.
372
497
 
375
500
    prefix = 'branch:'
376
501
 
377
502
    def _match_on(self, branch, revs):
378
 
        from branch import Branch
379
 
        other_branch = Branch.open_containing(self.spec)[0]
 
503
        from bzrlib.branch import Branch
 
504
        other_branch = Branch.open(self.spec)
380
505
        revision_b = other_branch.last_revision()
381
 
        if revision_b is None:
382
 
            raise NoCommits(other_branch)
 
506
        if revision_b in (None, revision.NULL_REVISION):
 
507
            raise errors.NoCommits(other_branch)
383
508
        # pull in the remote revisions so we can diff
384
509
        branch.fetch(other_branch, revision_b)
385
510
        try:
386
511
            revno = branch.revision_id_to_revno(revision_b)
387
 
        except NoSuchRevision:
 
512
        except errors.NoSuchRevision:
388
513
            revno = None
389
514
        return RevisionInfo(branch, revno, revision_b)
390
515