/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: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
 
from .lazy_import import lazy_import
 
18
import re
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
19
21
lazy_import(globals(), """
20
22
import bisect
21
23
import datetime
22
 
 
23
 
from breezy import (
24
 
    branch as _mod_branch,
25
 
    cache_utf8,
26
 
    revision,
27
 
    workingtree,
28
 
    )
29
 
from breezy.i18n import gettext
30
24
""")
31
25
 
32
 
from . import (
 
26
from bzrlib import (
33
27
    errors,
34
 
    lazy_regex,
 
28
    osutils,
35
29
    registry,
 
30
    revision,
 
31
    symbol_versioning,
36
32
    trace,
37
33
    )
38
34
 
39
35
 
40
 
class InvalidRevisionSpec(errors.BzrError):
41
 
 
42
 
    _fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
43
 
            " %(branch_url)s%(extra)s")
44
 
 
45
 
    def __init__(self, spec, branch, extra=None):
46
 
        errors.BzrError.__init__(self, branch=branch, spec=spec)
47
 
        self.branch_url = getattr(branch, 'user_url', str(branch))
48
 
        if extra:
49
 
            self.extra = '\n' + str(extra)
50
 
        else:
51
 
            self.extra = ''
52
 
 
53
 
 
54
 
class InvalidRevisionSpec(errors.BzrError):
55
 
 
56
 
    _fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
57
 
            " %(branch_url)s%(extra)s")
58
 
 
59
 
    def __init__(self, spec, branch, extra=None):
60
 
        errors.BzrError.__init__(self, branch=branch, spec=spec)
61
 
        self.branch_url = getattr(branch, 'user_url', str(branch))
62
 
        if extra:
63
 
            self.extra = '\n' + str(extra)
64
 
        else:
65
 
            self.extra = ''
 
36
_marker = []
66
37
 
67
38
 
68
39
class RevisionInfo(object):
82
53
    or treat the result as a tuple.
83
54
    """
84
55
 
85
 
    def __init__(self, branch, revno=None, rev_id=None):
 
56
    def __init__(self, branch, revno, rev_id=_marker):
86
57
        self.branch = branch
87
 
        self._has_revno = (revno is not None)
88
 
        self._revno = revno
89
 
        self.rev_id = rev_id
90
 
        if self.rev_id is None and self._revno is not None:
 
58
        self.revno = revno
 
59
        if rev_id is _marker:
91
60
            # allow caller to be lazy
92
 
            self.rev_id = branch.get_rev_id(self._revno)
93
 
 
94
 
    @property
95
 
    def revno(self):
96
 
        if not self._has_revno and self.rev_id is not None:
97
 
            try:
98
 
                self._revno = self.branch.revision_id_to_revno(self.rev_id)
99
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
100
 
                self._revno = None
101
 
            self._has_revno = True
102
 
        return self._revno
103
 
 
104
 
    def __bool__(self):
 
61
            if self.revno is None:
 
62
                self.rev_id = None
 
63
            else:
 
64
                self.rev_id = branch.get_rev_id(self.revno)
 
65
        else:
 
66
            self.rev_id = rev_id
 
67
 
 
68
    def __nonzero__(self):
 
69
        # first the easy ones...
105
70
        if self.rev_id is None:
106
71
            return False
 
72
        if self.revno is not None:
 
73
            return True
107
74
        # TODO: otherwise, it should depend on how I was built -
108
75
        # if it's in_history(branch), then check revision_history(),
109
76
        # if it's in_store(branch), do the check below
110
77
        return self.branch.repository.has_revision(self.rev_id)
111
78
 
112
 
    __nonzero__ = __bool__
113
 
 
114
79
    def __len__(self):
115
80
        return 2
116
81
 
117
82
    def __getitem__(self, index):
118
 
        if index == 0:
119
 
            return self.revno
120
 
        if index == 1:
121
 
            return self.rev_id
 
83
        if index == 0: return self.revno
 
84
        if index == 1: return self.rev_id
122
85
        raise IndexError(index)
123
86
 
124
87
    def get(self):
127
90
    def __eq__(self, other):
128
91
        if type(other) not in (tuple, list, type(self)):
129
92
            return False
130
 
        if isinstance(other, type(self)) and self.branch is not other.branch:
 
93
        if type(other) is type(self) and self.branch is not other.branch:
131
94
            return False
132
95
        return tuple(self) == tuple(other)
133
96
 
134
97
    def __repr__(self):
135
 
        return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
98
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
136
99
            self.revno, self.rev_id, self.branch)
137
100
 
138
101
    @staticmethod
139
 
    def from_revision_id(branch, revision_id):
 
102
    def from_revision_id(branch, revision_id, revs):
140
103
        """Construct a RevisionInfo given just the id.
141
104
 
142
105
        Use this if you don't know or care what the revno is.
143
106
        """
144
 
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
 
107
        if revision_id == revision.NULL_REVISION:
 
108
            return RevisionInfo(branch, 0, revision_id)
 
109
        try:
 
110
            revno = revs.index(revision_id) + 1
 
111
        except ValueError:
 
112
            revno = None
 
113
        return RevisionInfo(branch, revno, revision_id)
 
114
 
 
115
 
 
116
# classes in this list should have a "prefix" attribute, against which
 
117
# string specs are matched
 
118
_revno_regex = None
145
119
 
146
120
 
147
121
class RevisionSpec(object):
149
123
 
150
124
    help_txt = """A parsed revision specification.
151
125
 
152
 
    A revision specification is a string, which may be unambiguous about
153
 
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
154
 
    or it may have no prefix, in which case it's tried against several
155
 
    specifier types in sequence to determine what the user meant.
 
126
    A revision specification can be an integer, in which case it is
 
127
    assumed to be a revno (though this will translate negative values
 
128
    into positive ones); or it can be a string, in which case it is
 
129
    parsed for something like 'date:' or 'revid:' etc.
156
130
 
157
131
    Revision specs are an UI element, and they have been moved out
158
132
    of the branch class to leave "back-end" classes unaware of such
164
138
    """
165
139
 
166
140
    prefix = None
167
 
    dwim_catchable_exceptions = (InvalidRevisionSpec,)
168
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
169
 
 
170
 
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
171
 
    invalid revspec and raises some exception. The exceptions mentioned here
172
 
    will not be reported to the user but simply ignored without stopping the
173
 
    dwim processing.
174
 
    """
 
141
    wants_revision_history = True
175
142
 
176
143
    @staticmethod
177
144
    def from_string(spec):
181
148
        :return: A RevisionSpec object that understands how to parse the
182
149
            supplied notation.
183
150
        """
 
151
        if not isinstance(spec, (type(None), basestring)):
 
152
            raise TypeError('error')
 
153
 
184
154
        if spec is None:
185
155
            return RevisionSpec(None, _internal=True)
186
 
        if not isinstance(spec, str):
187
 
            raise TypeError("revision spec needs to be text")
188
156
        match = revspec_registry.get_prefix(spec)
189
157
        if match is not None:
190
158
            spectype, specsuffix = match
192
160
                         spectype.__name__, spec)
193
161
            return spectype(spec, _internal=True)
194
162
        else:
195
 
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
196
 
            # wait for _match_on to be called.
197
 
            return RevisionSpec_dwim(spec, _internal=True)
 
163
            for spectype in SPEC_TYPES:
 
164
                if spec.startswith(spectype.prefix):
 
165
                    trace.mutter('Returning RevisionSpec %s for %s',
 
166
                                 spectype.__name__, spec)
 
167
                    return spectype(spec, _internal=True)
 
168
            # RevisionSpec_revno is special cased, because it is the only
 
169
            # one that directly handles plain integers
 
170
            # TODO: This should not be special cased rather it should be
 
171
            # a method invocation on spectype.canparse()
 
172
            global _revno_regex
 
173
            if _revno_regex is None:
 
174
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
175
            if _revno_regex.match(spec) is not None:
 
176
                return RevisionSpec_revno(spec, _internal=True)
 
177
 
 
178
            raise errors.NoSuchRevisionSpec(spec)
198
179
 
199
180
    def __init__(self, spec, _internal=False):
200
181
        """Create a RevisionSpec referring to the Null revision.
204
185
            called directly. Only from RevisionSpec.from_string()
205
186
        """
206
187
        if not _internal:
207
 
            raise AssertionError(
208
 
                'Creating a RevisionSpec directly is not supported. '
209
 
                'Use RevisionSpec.from_string() instead.')
 
188
            symbol_versioning.warn('Creating a RevisionSpec directly has'
 
189
                                   ' been deprecated in version 0.11. Use'
 
190
                                   ' RevisionSpec.from_string()'
 
191
                                   ' instead.',
 
192
                                   DeprecationWarning, stacklevel=2)
210
193
        self.user_spec = spec
211
194
        if self.prefix and spec.startswith(self.prefix):
212
195
            spec = spec[len(self.prefix):]
224
207
            # special case - nothing supplied
225
208
            return info
226
209
        elif self.prefix:
227
 
            raise InvalidRevisionSpec(self.user_spec, branch)
 
210
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
228
211
        else:
229
 
            raise InvalidRevisionSpec(self.spec, branch)
 
212
            raise errors.InvalidRevisionSpec(self.spec, branch)
230
213
 
231
214
    def in_history(self, branch):
232
 
        return self._match_on_and_check(branch, revs=None)
 
215
        if branch:
 
216
            if self.wants_revision_history:
 
217
                revs = branch.revision_history()
 
218
            else:
 
219
                revs = None
 
220
        else:
 
221
            # this should never trigger.
 
222
            # TODO: make it a deprecated code path. RBC 20060928
 
223
            revs = None
 
224
        return self._match_on_and_check(branch, revs)
233
225
 
234
226
        # FIXME: in_history is somewhat broken,
235
227
        # it will return non-history revisions in many
279
271
    def __repr__(self):
280
272
        # this is mostly for helping with testing
281
273
        return '<%s %s>' % (self.__class__.__name__,
282
 
                            self.user_spec)
 
274
                              self.user_spec)
283
275
 
284
276
    def needs_branch(self):
285
277
        """Whether this revision spec needs a branch.
298
290
 
299
291
# private API
300
292
 
301
 
class RevisionSpec_dwim(RevisionSpec):
302
 
    """Provides a DWIMish revision specifier lookup.
303
 
 
304
 
    Note that this does not go in the revspec_registry because by definition
305
 
    there is no prefix to identify it.  It's solely called from
306
 
    RevisionSpec.from_string() because the DWIMification happen when _match_on
307
 
    is called so the string describing the revision is kept here until needed.
308
 
    """
309
 
 
310
 
    help_txt = None
311
 
 
312
 
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
313
 
 
314
 
    # The revspecs to try
315
 
    _possible_revspecs = []
316
 
 
317
 
    def _try_spectype(self, rstype, branch):
318
 
        rs = rstype(self.spec, _internal=True)
319
 
        # Hit in_history to find out if it exists, or we need to try the
320
 
        # next type.
321
 
        return rs.in_history(branch)
322
 
 
323
 
    def _match_on(self, branch, revs):
324
 
        """Run the lookup and see what we can get."""
325
 
 
326
 
        # First, see if it's a revno
327
 
        if self._revno_regex.match(self.spec) is not None:
328
 
            try:
329
 
                return self._try_spectype(RevisionSpec_revno, branch)
330
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
331
 
                pass
332
 
 
333
 
        # Next see what has been registered
334
 
        for objgetter in self._possible_revspecs:
335
 
            rs_class = objgetter.get_obj()
336
 
            try:
337
 
                return self._try_spectype(rs_class, branch)
338
 
            except rs_class.dwim_catchable_exceptions:
339
 
                pass
340
 
 
341
 
        # Well, I dunno what it is. Note that we don't try to keep track of the
342
 
        # first of last exception raised during the DWIM tries as none seems
343
 
        # really relevant.
344
 
        raise InvalidRevisionSpec(self.spec, branch)
345
 
 
346
 
    @classmethod
347
 
    def append_possible_revspec(cls, revspec):
348
 
        """Append a possible DWIM revspec.
349
 
 
350
 
        :param revspec: Revision spec to try.
351
 
        """
352
 
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
353
 
 
354
 
    @classmethod
355
 
    def append_possible_lazy_revspec(cls, module_name, member_name):
356
 
        """Append a possible lazily loaded DWIM revspec.
357
 
 
358
 
        :param module_name: Name of the module with the revspec
359
 
        :param member_name: Name of the revspec within the module
360
 
        """
361
 
        cls._possible_revspecs.append(
362
 
            registry._LazyObjectGetter(module_name, member_name))
363
 
 
364
 
 
365
293
class RevisionSpec_revno(RevisionSpec):
366
294
    """Selects a revision using a number."""
367
295
 
368
296
    help_txt = """Selects a revision using a number.
369
297
 
370
298
    Use an integer to specify a revision in the history of the branch.
371
 
    Optionally a branch can be specified.  A negative number will count
372
 
    from the end of the branch (-1 is the last revision, -2 the previous
373
 
    one). If the negative number is larger than the branch's history, the
374
 
    first revision is returned.
 
299
    Optionally a branch can be specified. The 'revno:' prefix is optional.
 
300
    A negative number will count from the end of the branch (-1 is the
 
301
    last revision, -2 the previous one). If the negative number is larger
 
302
    than the branch's history, the first revision is returned.
375
303
    Examples::
376
304
 
377
305
      revno:1                   -> return the first revision of this branch
384
312
                                   your history is very long.
385
313
    """
386
314
    prefix = 'revno:'
 
315
    wants_revision_history = False
387
316
 
388
317
    def _match_on(self, branch, revs):
389
318
        """Lookup a revision by revision number"""
390
 
        branch, revno, revision_id = self._lookup(branch)
 
319
        branch, revno, revision_id = self._lookup(branch, revs)
391
320
        return RevisionInfo(branch, revno, revision_id)
392
321
 
393
 
    def _lookup(self, branch):
 
322
    def _lookup(self, branch, revs_or_none):
394
323
        loc = self.spec.find(':')
395
324
        if loc == -1:
396
325
            revno_spec = self.spec
397
326
            branch_spec = None
398
327
        else:
399
328
            revno_spec = self.spec[:loc]
400
 
            branch_spec = self.spec[loc + 1:]
 
329
            branch_spec = self.spec[loc+1:]
401
330
 
402
331
        if revno_spec == '':
403
332
            if not branch_spec:
404
 
                raise InvalidRevisionSpec(
405
 
                    self.user_spec, branch,
406
 
                    'cannot have an empty revno and no branch')
 
333
                raise errors.InvalidRevisionSpec(self.user_spec,
 
334
                        branch, 'cannot have an empty revno and no branch')
407
335
            revno = None
408
336
        else:
409
337
            try:
414
342
                # but the from_string method is a little primitive
415
343
                # right now - RBC 20060928
416
344
                try:
417
 
                    match_revno = tuple((int(number)
418
 
                                         for number in revno_spec.split('.')))
419
 
                except ValueError as e:
420
 
                    raise InvalidRevisionSpec(self.user_spec, branch, e)
 
345
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
 
346
                except ValueError, e:
 
347
                    raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
421
348
 
422
349
                dotted = True
423
350
 
424
351
        if branch_spec:
425
 
            # the user has overriden the branch to look in.
426
 
            branch = _mod_branch.Branch.open(branch_spec)
 
352
            # the user has override the branch to look in.
 
353
            # we need to refresh the revision_history map and
 
354
            # the branch object.
 
355
            from bzrlib.branch import Branch
 
356
            branch = Branch.open(branch_spec)
 
357
            revs_or_none = None
427
358
 
428
359
        if dotted:
429
360
            try:
430
361
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
431
 
                                                                 _cache_reverse=True)
432
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
362
                    _cache_reverse=True)
 
363
            except errors.NoSuchRevision:
 
364
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
434
365
            else:
435
366
                # there is no traditional 'revno' for dotted-decimal revnos.
436
 
                # so for API compatibility we return None.
 
367
                # so for  API compatability we return None.
437
368
                return branch, None, revision_id
438
369
        else:
439
370
            last_revno, last_revision_id = branch.last_revision_info()
445
376
                else:
446
377
                    revno = last_revno + revno + 1
447
378
            try:
448
 
                revision_id = branch.get_rev_id(revno)
449
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
450
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
379
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
380
            except errors.NoSuchRevision:
 
381
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
451
382
        return branch, revno, revision_id
452
383
 
453
384
    def _as_revision_id(self, context_branch):
454
385
        # We would have the revno here, but we don't really care
455
 
        branch, revno, revision_id = self._lookup(context_branch)
 
386
        branch, revno, revision_id = self._lookup(context_branch, None)
456
387
        return revision_id
457
388
 
458
389
    def needs_branch(self):
462
393
        if self.spec.find(':') == -1:
463
394
            return None
464
395
        else:
465
 
            return self.spec[self.spec.find(':') + 1:]
466
 
 
 
396
            return self.spec[self.spec.find(':')+1:]
467
397
 
468
398
# Old compatibility
469
399
RevisionSpec_int = RevisionSpec_revno
470
400
 
471
401
 
472
 
class RevisionIDSpec(RevisionSpec):
473
 
 
474
 
    def _match_on(self, branch, revs):
475
 
        revision_id = self.as_revision_id(branch)
476
 
        return RevisionInfo.from_revision_id(branch, revision_id)
477
 
 
478
 
 
479
 
class RevisionSpec_revid(RevisionIDSpec):
 
402
 
 
403
class RevisionSpec_revid(RevisionSpec):
480
404
    """Selects a revision using the revision id."""
481
405
 
482
406
    help_txt = """Selects a revision using the revision id.
491
415
 
492
416
    prefix = 'revid:'
493
417
 
494
 
    def _as_revision_id(self, context_branch):
 
418
    def _match_on(self, branch, revs):
495
419
        # self.spec comes straight from parsing the command line arguments,
496
420
        # so we expect it to be a Unicode string. Switch it to the internal
497
421
        # representation.
498
 
        if isinstance(self.spec, str):
499
 
            return cache_utf8.encode(self.spec)
500
 
        return self.spec
 
422
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
423
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
424
 
 
425
    def _as_revision_id(self, context_branch):
 
426
        return osutils.safe_revision_id(self.spec, warn=False)
 
427
 
501
428
 
502
429
 
503
430
class RevisionSpec_last(RevisionSpec):
516
443
    prefix = 'last:'
517
444
 
518
445
    def _match_on(self, branch, revs):
519
 
        revno, revision_id = self._revno_and_revision_id(branch)
 
446
        revno, revision_id = self._revno_and_revision_id(branch, revs)
520
447
        return RevisionInfo(branch, revno, revision_id)
521
448
 
522
 
    def _revno_and_revision_id(self, context_branch):
 
449
    def _revno_and_revision_id(self, context_branch, revs_or_none):
523
450
        last_revno, last_revision_id = context_branch.last_revision_info()
524
451
 
525
452
        if self.spec == '':
529
456
 
530
457
        try:
531
458
            offset = int(self.spec)
532
 
        except ValueError as e:
533
 
            raise InvalidRevisionSpec(self.user_spec, context_branch, e)
 
459
        except ValueError, e:
 
460
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
534
461
 
535
462
        if offset <= 0:
536
 
            raise InvalidRevisionSpec(
537
 
                self.user_spec, context_branch,
538
 
                'you must supply a positive value')
 
463
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
464
                                             'you must supply a positive value')
539
465
 
540
466
        revno = last_revno - offset + 1
541
467
        try:
542
 
            revision_id = context_branch.get_rev_id(revno)
543
 
        except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
544
 
            raise InvalidRevisionSpec(self.user_spec, context_branch)
 
468
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
469
        except errors.NoSuchRevision:
 
470
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
545
471
        return revno, revision_id
546
472
 
547
473
    def _as_revision_id(self, context_branch):
548
474
        # We compute the revno as part of the process, but we don't really care
549
475
        # about it.
550
 
        revno, revision_id = self._revno_and_revision_id(context_branch)
 
476
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
551
477
        return revision_id
552
478
 
553
479
 
 
480
 
554
481
class RevisionSpec_before(RevisionSpec):
555
482
    """Selects the parent of the revision specified."""
556
483
 
578
505
    def _match_on(self, branch, revs):
579
506
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
580
507
        if r.revno == 0:
581
 
            raise InvalidRevisionSpec(
582
 
                self.user_spec, branch,
583
 
                'cannot go before the null: revision')
 
508
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
509
                                         'cannot go before the null: revision')
584
510
        if r.revno is None:
585
511
            # We need to use the repository history here
586
512
            rev = branch.repository.get_revision(r.rev_id)
587
513
            if not rev.parent_ids:
 
514
                revno = 0
588
515
                revision_id = revision.NULL_REVISION
589
516
            else:
590
517
                revision_id = rev.parent_ids[0]
591
 
            revno = None
 
518
                try:
 
519
                    revno = revs.index(revision_id) + 1
 
520
                except ValueError:
 
521
                    revno = None
592
522
        else:
593
523
            revno = r.revno - 1
594
524
            try:
595
525
                revision_id = branch.get_rev_id(revno, revs)
596
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
526
            except errors.NoSuchRevision:
 
527
                raise errors.InvalidRevisionSpec(self.user_spec,
 
528
                                                 branch)
598
529
        return RevisionInfo(branch, revno, revision_id)
599
530
 
600
531
    def _as_revision_id(self, context_branch):
601
 
        base_revision_id = RevisionSpec.from_string(
602
 
            self.spec)._as_revision_id(context_branch)
 
532
        base_revspec = RevisionSpec.from_string(self.spec)
 
533
        base_revision_id = base_revspec.as_revision_id(context_branch)
603
534
        if base_revision_id == revision.NULL_REVISION:
604
 
            raise InvalidRevisionSpec(
605
 
                self.user_spec, context_branch,
606
 
                'cannot go before the null: revision')
 
535
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
536
                                         'cannot go before the null: revision')
607
537
        context_repo = context_branch.repository
608
 
        with context_repo.lock_read():
 
538
        context_repo.lock_read()
 
539
        try:
609
540
            parent_map = context_repo.get_parent_map([base_revision_id])
 
541
        finally:
 
542
            context_repo.unlock()
610
543
        if base_revision_id not in parent_map:
611
544
            # Ghost, or unknown revision id
612
 
            raise InvalidRevisionSpec(
613
 
                self.user_spec, context_branch, 'cannot find the matching revision')
 
545
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
546
                'cannot find the matching revision')
614
547
        parents = parent_map[base_revision_id]
615
548
        if len(parents) < 1:
616
 
            raise InvalidRevisionSpec(
617
 
                self.user_spec, context_branch, 'No parents for revision.')
 
549
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
550
                'No parents for revision.')
618
551
        return parents[0]
619
552
 
620
553
 
 
554
 
621
555
class RevisionSpec_tag(RevisionSpec):
622
556
    """Select a revision identified by tag name"""
623
557
 
627
561
    """
628
562
 
629
563
    prefix = 'tag:'
630
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
631
564
 
632
565
    def _match_on(self, branch, revs):
633
566
        # Can raise tags not supported, NoSuchTag, etc
634
567
        return RevisionInfo.from_revision_id(branch,
635
 
                                             branch.tags.lookup_tag(self.spec))
 
568
            branch.tags.lookup_tag(self.spec),
 
569
            revs)
636
570
 
637
571
    def _as_revision_id(self, context_branch):
638
572
        return context_branch.tags.lookup_tag(self.spec)
639
573
 
640
574
 
 
575
 
641
576
class _RevListToTimestamps(object):
642
577
    """This takes a list of revisions, and allows you to bisect by date"""
643
578
 
644
 
    __slots__ = ['branch']
 
579
    __slots__ = ['revs', 'branch']
645
580
 
646
 
    def __init__(self, branch):
 
581
    def __init__(self, revs, branch):
 
582
        self.revs = revs
647
583
        self.branch = branch
648
584
 
649
585
    def __getitem__(self, index):
650
586
        """Get the date of the index'd item"""
651
 
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
 
587
        r = self.branch.repository.get_revision(self.revs[index])
652
588
        # TODO: Handle timezone.
653
589
        return datetime.datetime.fromtimestamp(r.timestamp)
654
590
 
655
591
    def __len__(self):
656
 
        return self.branch.revno()
 
592
        return len(self.revs)
657
593
 
658
594
 
659
595
class RevisionSpec_date(RevisionSpec):
668
604
 
669
605
    One way to display all the changes since yesterday would be::
670
606
 
671
 
        brz log -r date:yesterday..
 
607
        bzr log -r date:yesterday..
672
608
 
673
609
    Examples::
674
610
 
677
613
                                   August 14th, 2006 at 5:10pm.
678
614
    """
679
615
    prefix = 'date:'
680
 
    _date_regex = lazy_regex.lazy_compile(
681
 
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
682
 
        r'(,|T)?\s*'
683
 
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
616
    _date_re = re.compile(
 
617
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
618
            r'(,|T)?\s*'
 
619
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
684
620
        )
685
621
 
686
622
    def _match_on(self, branch, revs):
693
629
        #  XXX: This doesn't actually work
694
630
        #  So the proper way of saying 'give me all entries for today' is:
695
631
        #      -r date:yesterday..date:today
696
 
        today = datetime.datetime.fromordinal(
697
 
            datetime.date.today().toordinal())
 
632
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
698
633
        if self.spec.lower() == 'yesterday':
699
634
            dt = today - datetime.timedelta(days=1)
700
635
        elif self.spec.lower() == 'today':
702
637
        elif self.spec.lower() == 'tomorrow':
703
638
            dt = today + datetime.timedelta(days=1)
704
639
        else:
705
 
            m = self._date_regex.match(self.spec)
 
640
            m = self._date_re.match(self.spec)
706
641
            if not m or (not m.group('date') and not m.group('time')):
707
 
                raise InvalidRevisionSpec(
708
 
                    self.user_spec, branch, 'invalid date')
 
642
                raise errors.InvalidRevisionSpec(self.user_spec,
 
643
                                                 branch, 'invalid date')
709
644
 
710
645
            try:
711
646
                if m.group('date'):
725
660
                    else:
726
661
                        second = 0
727
662
                else:
728
 
                    hour, minute, second = 0, 0, 0
 
663
                    hour, minute, second = 0,0,0
729
664
            except ValueError:
730
 
                raise InvalidRevisionSpec(
731
 
                    self.user_spec, branch, 'invalid date')
 
665
                raise errors.InvalidRevisionSpec(self.user_spec,
 
666
                                                 branch, 'invalid date')
732
667
 
733
668
            dt = datetime.datetime(year=year, month=month, day=day,
734
 
                                   hour=hour, minute=minute, second=second)
735
 
        with branch.lock_read():
736
 
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
737
 
        if rev == branch.revno():
738
 
            raise InvalidRevisionSpec(self.user_spec, branch)
739
 
        return RevisionInfo(branch, rev)
 
669
                    hour=hour, minute=minute, second=second)
 
670
        branch.lock_read()
 
671
        try:
 
672
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
673
        finally:
 
674
            branch.unlock()
 
675
        if rev == len(revs):
 
676
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
677
        else:
 
678
            return RevisionInfo(branch, rev + 1)
 
679
 
740
680
 
741
681
 
742
682
class RevisionSpec_ancestor(RevisionSpec):
772
712
    def _find_revision_info(branch, other_location):
773
713
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
774
714
                                                              other_location)
775
 
        return RevisionInfo(branch, None, revision_id)
 
715
        try:
 
716
            revno = branch.revision_id_to_revno(revision_id)
 
717
        except errors.NoSuchRevision:
 
718
            revno = None
 
719
        return RevisionInfo(branch, revno, revision_id)
776
720
 
777
721
    @staticmethod
778
722
    def _find_revision_id(branch, other_location):
779
 
        from .branch import Branch
 
723
        from bzrlib.branch import Branch
780
724
 
781
 
        with branch.lock_read():
 
725
        branch.lock_read()
 
726
        try:
782
727
            revision_a = revision.ensure_null(branch.last_revision())
783
728
            if revision_a == revision.NULL_REVISION:
784
729
                raise errors.NoCommits(branch)
785
730
            if other_location == '':
786
731
                other_location = branch.get_parent()
787
732
            other_branch = Branch.open(other_location)
788
 
            with other_branch.lock_read():
 
733
            other_branch.lock_read()
 
734
            try:
789
735
                revision_b = revision.ensure_null(other_branch.last_revision())
790
736
                if revision_b == revision.NULL_REVISION:
791
737
                    raise errors.NoCommits(other_branch)
792
738
                graph = branch.repository.get_graph(other_branch.repository)
793
739
                rev_id = graph.find_unique_lca(revision_a, revision_b)
 
740
            finally:
 
741
                other_branch.unlock()
794
742
            if rev_id == revision.NULL_REVISION:
795
743
                raise errors.NoCommonAncestor(revision_a, revision_b)
796
744
            return rev_id
 
745
        finally:
 
746
            branch.unlock()
 
747
 
 
748
 
797
749
 
798
750
 
799
751
class RevisionSpec_branch(RevisionSpec):
808
760
      branch:/path/to/branch
809
761
    """
810
762
    prefix = 'branch:'
811
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
812
763
 
813
764
    def _match_on(self, branch, revs):
814
 
        from .branch import Branch
 
765
        from bzrlib.branch import Branch
815
766
        other_branch = Branch.open(self.spec)
816
767
        revision_b = other_branch.last_revision()
817
768
        if revision_b in (None, revision.NULL_REVISION):
818
769
            raise errors.NoCommits(other_branch)
819
 
        if branch is None:
820
 
            branch = other_branch
821
 
        else:
822
 
            try:
823
 
                # pull in the remote revisions so we can diff
824
 
                branch.fetch(other_branch, revision_b)
825
 
            except errors.ReadOnlyError:
826
 
                branch = other_branch
827
 
        return RevisionInfo(branch, None, revision_b)
 
770
        # pull in the remote revisions so we can diff
 
771
        branch.fetch(other_branch, revision_b)
 
772
        try:
 
773
            revno = branch.revision_id_to_revno(revision_b)
 
774
        except errors.NoSuchRevision:
 
775
            revno = None
 
776
        return RevisionInfo(branch, revno, revision_b)
828
777
 
829
778
    def _as_revision_id(self, context_branch):
830
 
        from .branch import Branch
 
779
        from bzrlib.branch import Branch
831
780
        other_branch = Branch.open(self.spec)
832
781
        last_revision = other_branch.last_revision()
833
782
        last_revision = revision.ensure_null(last_revision)
837
786
        return last_revision
838
787
 
839
788
    def _as_tree(self, context_branch):
840
 
        from .branch import Branch
 
789
        from bzrlib.branch import Branch
841
790
        other_branch = Branch.open(self.spec)
842
791
        last_revision = other_branch.last_revision()
843
792
        last_revision = revision.ensure_null(last_revision)
845
794
            raise errors.NoCommits(other_branch)
846
795
        return other_branch.repository.revision_tree(last_revision)
847
796
 
848
 
    def needs_branch(self):
849
 
        return False
850
 
 
851
 
    def get_branch(self):
852
 
        return self.spec
853
797
 
854
798
 
855
799
class RevisionSpec_submit(RevisionSpec_ancestor):
881
825
            location_type = 'parent branch'
882
826
        if submit_location is None:
883
827
            raise errors.NoSubmitBranch(branch)
884
 
        trace.note(gettext('Using {0} {1}').format(location_type,
885
 
                                                   submit_location))
 
828
        trace.note('Using %s %s', location_type, submit_location)
886
829
        return submit_location
887
830
 
888
831
    def _match_on(self, branch, revs):
889
832
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
890
833
        return self._find_revision_info(branch,
891
 
                                        self._get_submit_location(branch))
 
834
            self._get_submit_location(branch))
892
835
 
893
836
    def _as_revision_id(self, context_branch):
894
837
        return self._find_revision_id(context_branch,
895
 
                                      self._get_submit_location(context_branch))
896
 
 
897
 
 
898
 
class RevisionSpec_annotate(RevisionIDSpec):
899
 
 
900
 
    prefix = 'annotate:'
901
 
 
902
 
    help_txt = """Select the revision that last modified the specified line.
903
 
 
904
 
    Select the revision that last modified the specified line.  Line is
905
 
    specified as path:number.  Path is a relative path to the file.  Numbers
906
 
    start at 1, and are relative to the current version, not the last-
907
 
    committed version of the file.
908
 
    """
909
 
 
910
 
    def _raise_invalid(self, numstring, context_branch):
911
 
        raise InvalidRevisionSpec(
912
 
            self.user_spec, context_branch,
913
 
            'No such line: %s' % numstring)
914
 
 
915
 
    def _as_revision_id(self, context_branch):
916
 
        path, numstring = self.spec.rsplit(':', 1)
917
 
        try:
918
 
            index = int(numstring) - 1
919
 
        except ValueError:
920
 
            self._raise_invalid(numstring, context_branch)
921
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
922
 
        with tree.lock_read():
923
 
            if not tree.has_filename(file_path):
924
 
                raise InvalidRevisionSpec(
925
 
                    self.user_spec, context_branch,
926
 
                    "File '%s' is not versioned." % file_path)
927
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
928
 
        try:
929
 
            revision_id = revision_ids[index]
930
 
        except IndexError:
931
 
            self._raise_invalid(numstring, context_branch)
932
 
        if revision_id == revision.CURRENT_REVISION:
933
 
            raise InvalidRevisionSpec(
934
 
                self.user_spec, context_branch,
935
 
                'Line %s has not been committed.' % numstring)
936
 
        return revision_id
937
 
 
938
 
 
939
 
class RevisionSpec_mainline(RevisionIDSpec):
940
 
 
941
 
    help_txt = """Select mainline revision that merged the specified revision.
942
 
 
943
 
    Select the revision that merged the specified revision into mainline.
944
 
    """
945
 
 
946
 
    prefix = 'mainline:'
947
 
 
948
 
    def _as_revision_id(self, context_branch):
949
 
        revspec = RevisionSpec.from_string(self.spec)
950
 
        if revspec.get_branch() is None:
951
 
            spec_branch = context_branch
952
 
        else:
953
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
954
 
        revision_id = revspec.as_revision_id(spec_branch)
955
 
        graph = context_branch.repository.get_graph()
956
 
        result = graph.find_lefthand_merger(revision_id,
957
 
                                            context_branch.last_revision())
958
 
        if result is None:
959
 
            raise InvalidRevisionSpec(self.user_spec, context_branch)
960
 
        return result
961
 
 
962
 
 
963
 
# The order in which we want to DWIM a revision spec without any prefix.
964
 
# revno is always tried first and isn't listed here, this is used by
965
 
# RevisionSpec_dwim._match_on
966
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
967
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
968
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
969
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
 
838
            self._get_submit_location(context_branch))
 
839
 
970
840
 
971
841
revspec_registry = registry.Registry()
972
 
 
973
 
 
974
842
def _register_revspec(revspec):
975
843
    revspec_registry.register(revspec.prefix, revspec)
976
844
 
977
 
 
978
845
_register_revspec(RevisionSpec_revno)
979
846
_register_revspec(RevisionSpec_revid)
980
847
_register_revspec(RevisionSpec_last)
984
851
_register_revspec(RevisionSpec_ancestor)
985
852
_register_revspec(RevisionSpec_branch)
986
853
_register_revspec(RevisionSpec_submit)
987
 
_register_revspec(RevisionSpec_annotate)
988
 
_register_revspec(RevisionSpec_mainline)
 
854
 
 
855
SPEC_TYPES = symbol_versioning.deprecated_list(
 
856
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])