/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 breezy/revisionspec.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
from .lazy_import import lazy_import
 
19
lazy_import(globals(), """
 
20
import bisect
18
21
import datetime
19
 
import re
20
 
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
21
 
 
22
 
_marker = []
 
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
""")
 
31
 
 
32
from . import (
 
33
    errors,
 
34
    lazy_regex,
 
35
    registry,
 
36
    trace,
 
37
    )
 
38
 
 
39
 
 
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 = ''
 
66
 
23
67
 
24
68
class RevisionInfo(object):
25
 
    """The results of applying a revision specification to a branch.
 
69
    """The results of applying a revision specification to a branch."""
 
70
 
 
71
    help_txt = """The results of applying a revision specification to a branch.
26
72
 
27
73
    An instance has two useful attributes: revno, and rev_id.
28
74
 
36
82
    or treat the result as a tuple.
37
83
    """
38
84
 
39
 
    def __init__(self, branch, revno, rev_id=_marker):
 
85
    def __init__(self, branch, revno=None, rev_id=None):
40
86
        self.branch = branch
41
 
        self.revno = revno
42
 
        if rev_id is _marker:
 
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:
43
91
            # allow caller to be lazy
44
 
            if self.revno is None:
45
 
                self.rev_id = None
46
 
            else:
47
 
                self.rev_id = branch.get_rev_id(self.revno)
48
 
        else:
49
 
            self.rev_id = rev_id
50
 
 
51
 
    def __nonzero__(self):
52
 
        # first the easy ones...
 
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):
53
105
        if self.rev_id is None:
54
106
            return False
55
 
        if self.revno is not None:
56
 
            return True
57
107
        # TODO: otherwise, it should depend on how I was built -
58
108
        # if it's in_history(branch), then check revision_history(),
59
109
        # if it's in_store(branch), do the check below
60
 
        return self.rev_id in self.branch.revision_store
 
110
        return self.branch.repository.has_revision(self.rev_id)
 
111
 
 
112
    __nonzero__ = __bool__
61
113
 
62
114
    def __len__(self):
63
115
        return 2
64
116
 
65
117
    def __getitem__(self, index):
66
 
        if index == 0: return self.revno
67
 
        if index == 1: return self.rev_id
 
118
        if index == 0:
 
119
            return self.revno
 
120
        if index == 1:
 
121
            return self.rev_id
68
122
        raise IndexError(index)
69
123
 
70
124
    def get(self):
71
 
        return self.branch.get_revision(self.rev_id)
 
125
        return self.branch.repository.get_revision(self.rev_id)
72
126
 
73
127
    def __eq__(self, other):
74
128
        if type(other) not in (tuple, list, type(self)):
75
129
            return False
76
 
        if type(other) is type(self) and self.branch is not other.branch:
 
130
        if isinstance(other, type(self)) and self.branch is not other.branch:
77
131
            return False
78
132
        return tuple(self) == tuple(other)
79
133
 
80
134
    def __repr__(self):
81
 
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
135
        return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
82
136
            self.revno, self.rev_id, self.branch)
83
137
 
84
 
# classes in this list should have a "prefix" attribute, against which
85
 
# string specs are matched
86
 
SPEC_TYPES = []
 
138
    @staticmethod
 
139
    def from_revision_id(branch, revision_id):
 
140
        """Construct a RevisionInfo given just the id.
 
141
 
 
142
        Use this if you don't know or care what the revno is.
 
143
        """
 
144
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
 
145
 
87
146
 
88
147
class RevisionSpec(object):
89
 
    """A parsed revision specification.
90
 
 
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.
 
148
    """A parsed revision specification."""
 
149
 
 
150
    help_txt = """A parsed revision specification.
 
151
 
 
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.
95
156
 
96
157
    Revision specs are an UI element, and they have been moved out
97
158
    of the branch class to leave "back-end" classes unaware of such
103
164
    """
104
165
 
105
166
    prefix = None
106
 
 
107
 
    def __new__(cls, spec, foo=_marker):
108
 
        """Parse a revision specifier.
 
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
    """
 
175
 
 
176
    @staticmethod
 
177
    def from_string(spec):
 
178
        """Parse a revision spec string into a RevisionSpec object.
 
179
 
 
180
        :param spec: A string specified by the user
 
181
        :return: A RevisionSpec object that understands how to parse the
 
182
            supplied notation.
109
183
        """
110
184
        if spec is None:
111
 
            return object.__new__(RevisionSpec, spec)
112
 
 
113
 
        try:
114
 
            spec = int(spec)
115
 
        except ValueError:
116
 
            pass
117
 
 
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)
124
 
            else:
125
 
                raise BzrError('No namespace registered for string: %r' %
126
 
                               spec)
 
185
            return RevisionSpec(None, _internal=True)
 
186
        if not isinstance(spec, str):
 
187
            raise TypeError("revision spec needs to be text")
 
188
        match = revspec_registry.get_prefix(spec)
 
189
        if match is not None:
 
190
            spectype, specsuffix = match
 
191
            trace.mutter('Returning RevisionSpec %s for %s',
 
192
                         spectype.__name__, spec)
 
193
            return spectype(spec, _internal=True)
127
194
        else:
128
 
            raise TypeError('Unhandled revision type %s' % spec)
129
 
 
130
 
    def __init__(self, spec):
 
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)
 
198
 
 
199
    def __init__(self, spec, _internal=False):
 
200
        """Create a RevisionSpec referring to the Null revision.
 
201
 
 
202
        :param spec: The original spec supplied by the user
 
203
        :param _internal: Used to ensure that RevisionSpec is not being
 
204
            called directly. Only from RevisionSpec.from_string()
 
205
        """
 
206
        if not _internal:
 
207
            raise AssertionError(
 
208
                'Creating a RevisionSpec directly is not supported. '
 
209
                'Use RevisionSpec.from_string() instead.')
 
210
        self.user_spec = spec
131
211
        if self.prefix and spec.startswith(self.prefix):
132
212
            spec = spec[len(self.prefix):]
133
213
        self.spec = spec
134
214
 
135
215
    def _match_on(self, branch, revs):
136
 
        return RevisionInfo(branch, 0, None)
 
216
        trace.mutter('Returning RevisionSpec._match_on: None')
 
217
        return RevisionInfo(branch, None, None)
137
218
 
138
219
    def _match_on_and_check(self, branch, revs):
139
220
        info = self._match_on(branch, revs)
140
221
        if info:
141
222
            return info
142
 
        elif info == (0, None):
143
 
            # special case - the empty tree
 
223
        elif info == (None, None):
 
224
            # special case - nothing supplied
144
225
            return info
145
226
        elif self.prefix:
146
 
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
 
227
            raise InvalidRevisionSpec(self.user_spec, branch)
147
228
        else:
148
 
            raise NoSuchRevision(branch, str(self.spec))
 
229
            raise InvalidRevisionSpec(self.spec, branch)
149
230
 
150
231
    def in_history(self, branch):
151
 
        revs = branch.revision_history()
152
 
        return self._match_on_and_check(branch, revs)
 
232
        return self._match_on_and_check(branch, revs=None)
 
233
 
 
234
        # FIXME: in_history is somewhat broken,
 
235
        # it will return non-history revisions in many
 
236
        # circumstances. The expected facility is that
 
237
        # in_history only returns revision-history revs,
 
238
        # in_store returns any rev. RBC 20051010
 
239
    # aliases for now, when we fix the core logic, then they
 
240
    # will do what you expect.
 
241
    in_store = in_history
 
242
    in_branch = in_store
 
243
 
 
244
    def as_revision_id(self, context_branch):
 
245
        """Return just the revision_id for this revisions spec.
 
246
 
 
247
        Some revision specs require a context_branch to be able to determine
 
248
        their value. Not all specs will make use of it.
 
249
        """
 
250
        return self._as_revision_id(context_branch)
 
251
 
 
252
    def _as_revision_id(self, context_branch):
 
253
        """Implementation of as_revision_id()
 
254
 
 
255
        Classes should override this function to provide appropriate
 
256
        functionality. The default is to just call '.in_history().rev_id'
 
257
        """
 
258
        return self.in_history(context_branch).rev_id
 
259
 
 
260
    def as_tree(self, context_branch):
 
261
        """Return the tree object for this revisions spec.
 
262
 
 
263
        Some revision specs require a context_branch to be able to determine
 
264
        the revision id and access the repository. Not all specs will make
 
265
        use of it.
 
266
        """
 
267
        return self._as_tree(context_branch)
 
268
 
 
269
    def _as_tree(self, context_branch):
 
270
        """Implementation of as_tree().
 
271
 
 
272
        Classes should override this function to provide appropriate
 
273
        functionality. The default is to just call '.as_revision_id()'
 
274
        and get the revision tree from context_branch's repository.
 
275
        """
 
276
        revision_id = self.as_revision_id(context_branch)
 
277
        return context_branch.repository.revision_tree(revision_id)
153
278
 
154
279
    def __repr__(self):
155
280
        # this is mostly for helping with testing
156
 
        return '<%s %s%s>' % (self.__class__.__name__,
157
 
                              self.prefix or '',
158
 
                              self.spec)
 
281
        return '<%s %s>' % (self.__class__.__name__,
 
282
                            self.user_spec)
 
283
 
 
284
    def needs_branch(self):
 
285
        """Whether this revision spec needs a branch.
 
286
 
 
287
        Set this to False the branch argument of _match_on is not used.
 
288
        """
 
289
        return True
 
290
 
 
291
    def get_branch(self):
 
292
        """When the revision specifier contains a branch location, return it.
 
293
 
 
294
        Otherwise, return None.
 
295
        """
 
296
        return None
159
297
 
160
298
 
161
299
# private API
162
300
 
163
 
class RevisionSpec_int(RevisionSpec):
164
 
    """Spec is a number.  Special case."""
165
 
    def __init__(self, spec):
166
 
        self.spec = int(spec)
 
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)
167
322
 
168
323
    def _match_on(self, branch, revs):
169
 
        if self.spec < 0:
170
 
            revno = len(revs) + self.spec + 1
171
 
        else:
172
 
            revno = self.spec
173
 
        rev_id = branch.get_rev_id(revno, revs)
174
 
        return RevisionInfo(branch, revno, rev_id)
 
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))
175
363
 
176
364
 
177
365
class RevisionSpec_revno(RevisionSpec):
 
366
    """Selects a revision using a number."""
 
367
 
 
368
    help_txt = """Selects a revision using a number.
 
369
 
 
370
    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.
 
375
    Examples::
 
376
 
 
377
      revno:1                   -> return the first revision of this branch
 
378
      revno:3:/path/to/branch   -> return the 3rd revision of
 
379
                                   the branch '/path/to/branch'
 
380
      revno:-1                  -> The last revision in a branch.
 
381
      -2:http://other/branch    -> The second to last revision in the
 
382
                                   remote branch.
 
383
      -1000000                  -> Most likely the first revision, unless
 
384
                                   your history is very long.
 
385
    """
178
386
    prefix = 'revno:'
179
387
 
180
388
    def _match_on(self, branch, revs):
181
389
        """Lookup a revision by revision number"""
182
 
        try:
183
 
            return RevisionInfo(branch, int(self.spec))
184
 
        except ValueError:
185
 
            return RevisionInfo(branch, None)
186
 
 
187
 
SPEC_TYPES.append(RevisionSpec_revno)
188
 
 
189
 
 
190
 
class RevisionSpec_revid(RevisionSpec):
 
390
        branch, revno, revision_id = self._lookup(branch)
 
391
        return RevisionInfo(branch, revno, revision_id)
 
392
 
 
393
    def _lookup(self, branch):
 
394
        loc = self.spec.find(':')
 
395
        if loc == -1:
 
396
            revno_spec = self.spec
 
397
            branch_spec = None
 
398
        else:
 
399
            revno_spec = self.spec[:loc]
 
400
            branch_spec = self.spec[loc + 1:]
 
401
 
 
402
        if revno_spec == '':
 
403
            if not branch_spec:
 
404
                raise InvalidRevisionSpec(
 
405
                    self.user_spec, branch,
 
406
                    'cannot have an empty revno and no branch')
 
407
            revno = None
 
408
        else:
 
409
            try:
 
410
                revno = int(revno_spec)
 
411
                dotted = False
 
412
            except ValueError:
 
413
                # dotted decimal. This arguably should not be here
 
414
                # but the from_string method is a little primitive
 
415
                # right now - RBC 20060928
 
416
                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)
 
421
 
 
422
                dotted = True
 
423
 
 
424
        if branch_spec:
 
425
            # the user has overriden the branch to look in.
 
426
            branch = _mod_branch.Branch.open(branch_spec)
 
427
 
 
428
        if dotted:
 
429
            try:
 
430
                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)
 
434
            else:
 
435
                # there is no traditional 'revno' for dotted-decimal revnos.
 
436
                # so for API compatibility we return None.
 
437
                return branch, None, revision_id
 
438
        else:
 
439
            last_revno, last_revision_id = branch.last_revision_info()
 
440
            if revno < 0:
 
441
                # if get_rev_id supported negative revnos, there would not be a
 
442
                # need for this special case.
 
443
                if (-revno) >= last_revno:
 
444
                    revno = 1
 
445
                else:
 
446
                    revno = last_revno + revno + 1
 
447
            try:
 
448
                revision_id = branch.get_rev_id(revno)
 
449
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
 
450
                raise InvalidRevisionSpec(self.user_spec, branch)
 
451
        return branch, revno, revision_id
 
452
 
 
453
    def _as_revision_id(self, context_branch):
 
454
        # We would have the revno here, but we don't really care
 
455
        branch, revno, revision_id = self._lookup(context_branch)
 
456
        return revision_id
 
457
 
 
458
    def needs_branch(self):
 
459
        return self.spec.find(':') == -1
 
460
 
 
461
    def get_branch(self):
 
462
        if self.spec.find(':') == -1:
 
463
            return None
 
464
        else:
 
465
            return self.spec[self.spec.find(':') + 1:]
 
466
 
 
467
 
 
468
# Old compatibility
 
469
RevisionSpec_int = RevisionSpec_revno
 
470
 
 
471
 
 
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):
 
480
    """Selects a revision using the revision id."""
 
481
 
 
482
    help_txt = """Selects a revision using the revision id.
 
483
 
 
484
    Supply a specific revision id, that can be used to specify any
 
485
    revision id in the ancestry of the branch.
 
486
    Including merges, and pending merges.
 
487
    Examples::
 
488
 
 
489
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
 
490
    """
 
491
 
191
492
    prefix = 'revid:'
192
493
 
193
 
    def _match_on(self, branch, revs):
194
 
        try:
195
 
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
196
 
        except ValueError:
197
 
            return RevisionInfo(branch, None)
198
 
 
199
 
SPEC_TYPES.append(RevisionSpec_revid)
 
494
    def _as_revision_id(self, context_branch):
 
495
        # self.spec comes straight from parsing the command line arguments,
 
496
        # so we expect it to be a Unicode string. Switch it to the internal
 
497
        # representation.
 
498
        if isinstance(self.spec, str):
 
499
            return cache_utf8.encode(self.spec)
 
500
        return self.spec
200
501
 
201
502
 
202
503
class RevisionSpec_last(RevisionSpec):
 
504
    """Selects the nth revision from the end."""
 
505
 
 
506
    help_txt = """Selects the nth revision from the end.
 
507
 
 
508
    Supply a positive number to get the nth revision from the end.
 
509
    This is the same as supplying negative numbers to the 'revno:' spec.
 
510
    Examples::
 
511
 
 
512
      last:1        -> return the last revision
 
513
      last:3        -> return the revision 2 before the end.
 
514
    """
 
515
 
203
516
    prefix = 'last:'
204
517
 
205
518
    def _match_on(self, branch, revs):
 
519
        revno, revision_id = self._revno_and_revision_id(branch)
 
520
        return RevisionInfo(branch, revno, revision_id)
 
521
 
 
522
    def _revno_and_revision_id(self, context_branch):
 
523
        last_revno, last_revision_id = context_branch.last_revision_info()
 
524
 
 
525
        if self.spec == '':
 
526
            if not last_revno:
 
527
                raise errors.NoCommits(context_branch)
 
528
            return last_revno, last_revision_id
 
529
 
206
530
        try:
207
531
            offset = int(self.spec)
208
 
        except ValueError:
209
 
            return RevisionInfo(branch, None)
 
532
        except ValueError as e:
 
533
            raise InvalidRevisionSpec(self.user_spec, context_branch, e)
 
534
 
 
535
        if offset <= 0:
 
536
            raise InvalidRevisionSpec(
 
537
                self.user_spec, context_branch,
 
538
                'you must supply a positive value')
 
539
 
 
540
        revno = last_revno - offset + 1
 
541
        try:
 
542
            revision_id = context_branch.get_rev_id(revno)
 
543
        except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
 
544
            raise InvalidRevisionSpec(self.user_spec, context_branch)
 
545
        return revno, revision_id
 
546
 
 
547
    def _as_revision_id(self, context_branch):
 
548
        # We compute the revno as part of the process, but we don't really care
 
549
        # about it.
 
550
        revno, revision_id = self._revno_and_revision_id(context_branch)
 
551
        return revision_id
 
552
 
 
553
 
 
554
class RevisionSpec_before(RevisionSpec):
 
555
    """Selects the parent of the revision specified."""
 
556
 
 
557
    help_txt = """Selects the parent of the revision specified.
 
558
 
 
559
    Supply any revision spec to return the parent of that revision.  This is
 
560
    mostly useful when inspecting revisions that are not in the revision history
 
561
    of a branch.
 
562
 
 
563
    It is an error to request the parent of the null revision (before:0).
 
564
 
 
565
    Examples::
 
566
 
 
567
      before:1913    -> Return the parent of revno 1913 (revno 1912)
 
568
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
 
569
                                            aaaa@bbbb-1234567890
 
570
      bzr diff -r before:1913..1913
 
571
            -> Find the changes between revision 1913 and its parent (1912).
 
572
               (What changes did revision 1913 introduce).
 
573
               This is equivalent to:  bzr diff -c 1913
 
574
    """
 
575
 
 
576
    prefix = 'before:'
 
577
 
 
578
    def _match_on(self, branch, revs):
 
579
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
580
        if r.revno == 0:
 
581
            raise InvalidRevisionSpec(
 
582
                self.user_spec, branch,
 
583
                'cannot go before the null: revision')
 
584
        if r.revno is None:
 
585
            # We need to use the repository history here
 
586
            rev = branch.repository.get_revision(r.rev_id)
 
587
            if not rev.parent_ids:
 
588
                revision_id = revision.NULL_REVISION
 
589
            else:
 
590
                revision_id = rev.parent_ids[0]
 
591
            revno = None
210
592
        else:
211
 
            if offset <= 0:
212
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
213
 
            return RevisionInfo(branch, len(revs) - offset + 1)
 
593
            revno = r.revno - 1
 
594
            try:
 
595
                revision_id = branch.get_rev_id(revno, revs)
 
596
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
 
597
                raise InvalidRevisionSpec(self.user_spec, branch)
 
598
        return RevisionInfo(branch, revno, revision_id)
214
599
 
215
 
SPEC_TYPES.append(RevisionSpec_last)
 
600
    def _as_revision_id(self, context_branch):
 
601
        base_revision_id = RevisionSpec.from_string(
 
602
            self.spec)._as_revision_id(context_branch)
 
603
        if base_revision_id == revision.NULL_REVISION:
 
604
            raise InvalidRevisionSpec(
 
605
                self.user_spec, context_branch,
 
606
                'cannot go before the null: revision')
 
607
        context_repo = context_branch.repository
 
608
        with context_repo.lock_read():
 
609
            parent_map = context_repo.get_parent_map([base_revision_id])
 
610
        if base_revision_id not in parent_map:
 
611
            # Ghost, or unknown revision id
 
612
            raise InvalidRevisionSpec(
 
613
                self.user_spec, context_branch, 'cannot find the matching revision')
 
614
        parents = parent_map[base_revision_id]
 
615
        if len(parents) < 1:
 
616
            raise InvalidRevisionSpec(
 
617
                self.user_spec, context_branch, 'No parents for revision.')
 
618
        return parents[0]
216
619
 
217
620
 
218
621
class RevisionSpec_tag(RevisionSpec):
 
622
    """Select a revision identified by tag name"""
 
623
 
 
624
    help_txt = """Selects a revision identified by a tag name.
 
625
 
 
626
    Tags are stored in the branch and created by the 'tag' command.
 
627
    """
 
628
 
219
629
    prefix = 'tag:'
 
630
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
220
631
 
221
632
    def _match_on(self, branch, revs):
222
 
        raise BzrError('tag: namespace registered, but not implemented.')
223
 
 
224
 
SPEC_TYPES.append(RevisionSpec_tag)
 
633
        # Can raise tags not supported, NoSuchTag, etc
 
634
        return RevisionInfo.from_revision_id(branch,
 
635
                                             branch.tags.lookup_tag(self.spec))
 
636
 
 
637
    def _as_revision_id(self, context_branch):
 
638
        return context_branch.tags.lookup_tag(self.spec)
 
639
 
 
640
 
 
641
class _RevListToTimestamps(object):
 
642
    """This takes a list of revisions, and allows you to bisect by date"""
 
643
 
 
644
    __slots__ = ['branch']
 
645
 
 
646
    def __init__(self, branch):
 
647
        self.branch = branch
 
648
 
 
649
    def __getitem__(self, index):
 
650
        """Get the date of the index'd item"""
 
651
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
 
652
        # TODO: Handle timezone.
 
653
        return datetime.datetime.fromtimestamp(r.timestamp)
 
654
 
 
655
    def __len__(self):
 
656
        return self.branch.revno()
225
657
 
226
658
 
227
659
class RevisionSpec_date(RevisionSpec):
 
660
    """Selects a revision on the basis of a datestamp."""
 
661
 
 
662
    help_txt = """Selects a revision on the basis of a datestamp.
 
663
 
 
664
    Supply a datestamp to select the first revision that matches the date.
 
665
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
666
    Matches the first entry after a given date (either at midnight or
 
667
    at a specified time).
 
668
 
 
669
    One way to display all the changes since yesterday would be::
 
670
 
 
671
        brz log -r date:yesterday..
 
672
 
 
673
    Examples::
 
674
 
 
675
      date:yesterday            -> select the first revision since yesterday
 
676
      date:2006-08-14,17:10:14  -> select the first revision after
 
677
                                   August 14th, 2006 at 5:10pm.
 
678
    """
228
679
    prefix = 'date:'
229
 
    _date_re = re.compile(
230
 
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
231
 
            r'(,|T)?\s*'
232
 
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
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))?)?'
233
684
        )
234
685
 
235
686
    def _match_on(self, branch, revs):
236
 
        """
237
 
        Spec for date revisions:
 
687
        """Spec for date revisions:
238
688
          date:value
239
689
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
240
 
          it can also start with a '+/-/='. '+' says match the first
241
 
          entry after the given date. '-' is match the first entry before the date
242
 
          '=' is match the first entry after, but still on the given date.
243
 
 
244
 
          +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
245
 
          -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
246
 
          =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
247
 
              May 13th, 2005 at 0:00
248
 
 
249
 
          So the proper way of saying 'give me all entries for today' is:
250
 
              -r {date:+today}:{date:-tomorrow}
251
 
          The default is '=' when not supplied
 
690
          matches the first entry after a given date (either at midnight or
 
691
          at a specified time).
252
692
        """
253
 
        match_style = '='
254
 
        if self.spec[:1] in ('+', '-', '='):
255
 
            match_style = self.spec[:1]
256
 
            self.spec = self.spec[1:]
257
 
 
258
 
        # XXX: this should probably be using datetime.date instead
259
 
        today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
260
 
                                                  microsecond=0)
 
693
        #  XXX: This doesn't actually work
 
694
        #  So the proper way of saying 'give me all entries for today' is:
 
695
        #      -r date:yesterday..date:today
 
696
        today = datetime.datetime.fromordinal(
 
697
            datetime.date.today().toordinal())
261
698
        if self.spec.lower() == 'yesterday':
262
699
            dt = today - datetime.timedelta(days=1)
263
700
        elif self.spec.lower() == 'today':
265
702
        elif self.spec.lower() == 'tomorrow':
266
703
            dt = today + datetime.timedelta(days=1)
267
704
        else:
268
 
            m = self._date_re.match(self.spec)
 
705
            m = self._date_regex.match(self.spec)
269
706
            if not m or (not m.group('date') and not m.group('time')):
270
 
                raise BzrError('Invalid revision date %r' % self.spec)
271
 
 
272
 
            if m.group('date'):
273
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
274
 
            else:
275
 
                year, month, day = today.year, today.month, today.day
276
 
            if m.group('time'):
277
 
                hour = int(m.group('hour'))
278
 
                minute = int(m.group('minute'))
279
 
                if m.group('second'):
280
 
                    second = int(m.group('second'))
281
 
                else:
282
 
                    second = 0
283
 
            else:
284
 
                hour, minute, second = 0,0,0
 
707
                raise InvalidRevisionSpec(
 
708
                    self.user_spec, branch, 'invalid date')
 
709
 
 
710
            try:
 
711
                if m.group('date'):
 
712
                    year = int(m.group('year'))
 
713
                    month = int(m.group('month'))
 
714
                    day = int(m.group('day'))
 
715
                else:
 
716
                    year = today.year
 
717
                    month = today.month
 
718
                    day = today.day
 
719
 
 
720
                if m.group('time'):
 
721
                    hour = int(m.group('hour'))
 
722
                    minute = int(m.group('minute'))
 
723
                    if m.group('second'):
 
724
                        second = int(m.group('second'))
 
725
                    else:
 
726
                        second = 0
 
727
                else:
 
728
                    hour, minute, second = 0, 0, 0
 
729
            except ValueError:
 
730
                raise InvalidRevisionSpec(
 
731
                    self.user_spec, branch, 'invalid date')
285
732
 
286
733
            dt = datetime.datetime(year=year, month=month, day=day,
287
 
                    hour=hour, minute=minute, second=second)
288
 
        first = dt
289
 
        last = None
290
 
        reversed = False
291
 
        if match_style == '-':
292
 
            reversed = True
293
 
        elif match_style == '=':
294
 
            last = dt + datetime.timedelta(days=1)
295
 
 
296
 
        if reversed:
297
 
            for i in range(len(revs)-1, -1, -1):
298
 
                r = branch.get_revision(revs[i])
299
 
                # TODO: Handle timezone.
300
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
301
 
                if first >= dt and (last is None or dt >= last):
302
 
                    return RevisionInfo(branch, i+1,)
303
 
        else:
304
 
            for i in range(len(revs)):
305
 
                r = branch.get_revision(revs[i])
306
 
                # TODO: Handle timezone.
307
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
308
 
                if first <= dt and (last is None or dt <= last):
309
 
                    return RevisionInfo(branch, i+1,)
310
 
 
311
 
SPEC_TYPES.append(RevisionSpec_date)
 
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)
312
740
 
313
741
 
314
742
class RevisionSpec_ancestor(RevisionSpec):
 
743
    """Selects a common ancestor with a second branch."""
 
744
 
 
745
    help_txt = """Selects a common ancestor with a second branch.
 
746
 
 
747
    Supply the path to a branch to select the common ancestor.
 
748
 
 
749
    The common ancestor is the last revision that existed in both
 
750
    branches. Usually this is the branch point, but it could also be
 
751
    a revision that was merged.
 
752
 
 
753
    This is frequently used with 'diff' to return all of the changes
 
754
    that your branch introduces, while excluding the changes that you
 
755
    have not merged from the remote branch.
 
756
 
 
757
    Examples::
 
758
 
 
759
      ancestor:/path/to/branch
 
760
      $ bzr diff -r ancestor:../../mainline/branch
 
761
    """
315
762
    prefix = 'ancestor:'
316
763
 
317
764
    def _match_on(self, branch, revs):
318
 
        from branch import Branch
319
 
        from revision import common_ancestor, MultipleRevisionSources
320
 
        other_branch = Branch.open_containing(self.spec)
321
 
        revision_a = branch.last_patch()
322
 
        revision_b = other_branch.last_patch()
323
 
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
324
 
            if r is None:
325
 
                raise NoCommits(b)
326
 
        revision_source = MultipleRevisionSources(branch, other_branch)
327
 
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
328
 
        try:
329
 
            revno = branch.revision_id_to_revno(rev_id)
330
 
        except NoSuchRevision:
331
 
            revno = None
332
 
        return RevisionInfo(branch, revno, rev_id)
333
 
        
334
 
SPEC_TYPES.append(RevisionSpec_ancestor)
 
765
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
766
        return self._find_revision_info(branch, self.spec)
 
767
 
 
768
    def _as_revision_id(self, context_branch):
 
769
        return self._find_revision_id(context_branch, self.spec)
 
770
 
 
771
    @staticmethod
 
772
    def _find_revision_info(branch, other_location):
 
773
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
 
774
                                                              other_location)
 
775
        return RevisionInfo(branch, None, revision_id)
 
776
 
 
777
    @staticmethod
 
778
    def _find_revision_id(branch, other_location):
 
779
        from .branch import Branch
 
780
 
 
781
        with branch.lock_read():
 
782
            revision_a = revision.ensure_null(branch.last_revision())
 
783
            if revision_a == revision.NULL_REVISION:
 
784
                raise errors.NoCommits(branch)
 
785
            if other_location == '':
 
786
                other_location = branch.get_parent()
 
787
            other_branch = Branch.open(other_location)
 
788
            with other_branch.lock_read():
 
789
                revision_b = revision.ensure_null(other_branch.last_revision())
 
790
                if revision_b == revision.NULL_REVISION:
 
791
                    raise errors.NoCommits(other_branch)
 
792
                graph = branch.repository.get_graph(other_branch.repository)
 
793
                rev_id = graph.find_unique_lca(revision_a, revision_b)
 
794
            if rev_id == revision.NULL_REVISION:
 
795
                raise errors.NoCommonAncestor(revision_a, revision_b)
 
796
            return rev_id
 
797
 
 
798
 
 
799
class RevisionSpec_branch(RevisionSpec):
 
800
    """Selects the last revision of a specified branch."""
 
801
 
 
802
    help_txt = """Selects the last revision of a specified branch.
 
803
 
 
804
    Supply the path to a branch to select its last revision.
 
805
 
 
806
    Examples::
 
807
 
 
808
      branch:/path/to/branch
 
809
    """
 
810
    prefix = 'branch:'
 
811
    dwim_catchable_exceptions = (errors.NotBranchError,)
 
812
 
 
813
    def _match_on(self, branch, revs):
 
814
        from .branch import Branch
 
815
        other_branch = Branch.open(self.spec)
 
816
        revision_b = other_branch.last_revision()
 
817
        if revision_b in (None, revision.NULL_REVISION):
 
818
            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)
 
828
 
 
829
    def _as_revision_id(self, context_branch):
 
830
        from .branch import Branch
 
831
        other_branch = Branch.open(self.spec)
 
832
        last_revision = other_branch.last_revision()
 
833
        last_revision = revision.ensure_null(last_revision)
 
834
        context_branch.fetch(other_branch, last_revision)
 
835
        if last_revision == revision.NULL_REVISION:
 
836
            raise errors.NoCommits(other_branch)
 
837
        return last_revision
 
838
 
 
839
    def _as_tree(self, context_branch):
 
840
        from .branch import Branch
 
841
        other_branch = Branch.open(self.spec)
 
842
        last_revision = other_branch.last_revision()
 
843
        last_revision = revision.ensure_null(last_revision)
 
844
        if last_revision == revision.NULL_REVISION:
 
845
            raise errors.NoCommits(other_branch)
 
846
        return other_branch.repository.revision_tree(last_revision)
 
847
 
 
848
    def needs_branch(self):
 
849
        return False
 
850
 
 
851
    def get_branch(self):
 
852
        return self.spec
 
853
 
 
854
 
 
855
class RevisionSpec_submit(RevisionSpec_ancestor):
 
856
    """Selects a common ancestor with a submit branch."""
 
857
 
 
858
    help_txt = """Selects a common ancestor with the submit branch.
 
859
 
 
860
    Diffing against this shows all the changes that were made in this branch,
 
861
    and is a good predictor of what merge will do.  The submit branch is
 
862
    used by the bundle and merge directive commands.  If no submit branch
 
863
    is specified, the parent branch is used instead.
 
864
 
 
865
    The common ancestor is the last revision that existed in both
 
866
    branches. Usually this is the branch point, but it could also be
 
867
    a revision that was merged.
 
868
 
 
869
    Examples::
 
870
 
 
871
      $ bzr diff -r submit:
 
872
    """
 
873
 
 
874
    prefix = 'submit:'
 
875
 
 
876
    def _get_submit_location(self, branch):
 
877
        submit_location = branch.get_submit_branch()
 
878
        location_type = 'submit branch'
 
879
        if submit_location is None:
 
880
            submit_location = branch.get_parent()
 
881
            location_type = 'parent branch'
 
882
        if submit_location is None:
 
883
            raise errors.NoSubmitBranch(branch)
 
884
        trace.note(gettext('Using {0} {1}').format(location_type,
 
885
                                                   submit_location))
 
886
        return submit_location
 
887
 
 
888
    def _match_on(self, branch, revs):
 
889
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
890
        return self._find_revision_info(branch,
 
891
                                        self._get_submit_location(branch))
 
892
 
 
893
    def _as_revision_id(self, context_branch):
 
894
        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)
 
970
 
 
971
revspec_registry = registry.Registry()
 
972
 
 
973
 
 
974
def _register_revspec(revspec):
 
975
    revspec_registry.register(revspec.prefix, revspec)
 
976
 
 
977
 
 
978
_register_revspec(RevisionSpec_revno)
 
979
_register_revspec(RevisionSpec_revid)
 
980
_register_revspec(RevisionSpec_last)
 
981
_register_revspec(RevisionSpec_before)
 
982
_register_revspec(RevisionSpec_tag)
 
983
_register_revspec(RevisionSpec_date)
 
984
_register_revspec(RevisionSpec_ancestor)
 
985
_register_revspec(RevisionSpec_branch)
 
986
_register_revspec(RevisionSpec_submit)
 
987
_register_revspec(RevisionSpec_annotate)
 
988
_register_revspec(RevisionSpec_mainline)