1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
39
class RevisionInfo(object):
40
"""The results of applying a revision specification to a branch."""
42
help_txt = """The results of applying a revision specification to a branch.
44
An instance has two useful attributes: revno, and rev_id.
46
They can also be accessed as spec[0] and spec[1] respectively,
47
so that you can write code like:
48
revno, rev_id = RevisionSpec(branch, spec)
49
although this is probably going to be deprecated later.
51
This class exists mostly to be the return value of a RevisionSpec,
52
so that you can access the member you're interested in (number or id)
53
or treat the result as a tuple.
56
def __init__(self, branch, revno, rev_id=_marker):
60
# allow caller to be lazy
61
if self.revno is None:
64
self.rev_id = branch.get_rev_id(self.revno)
68
def __nonzero__(self):
69
# first the easy ones...
70
if self.rev_id is None:
72
if self.revno is not None:
74
# TODO: otherwise, it should depend on how I was built -
75
# if it's in_history(branch), then check revision_history(),
76
# if it's in_store(branch), do the check below
77
return self.branch.repository.has_revision(self.rev_id)
82
def __getitem__(self, index):
83
if index == 0: return self.revno
84
if index == 1: return self.rev_id
85
raise IndexError(index)
88
return self.branch.repository.get_revision(self.rev_id)
90
def __eq__(self, other):
91
if type(other) not in (tuple, list, type(self)):
93
if type(other) is type(self) and self.branch is not other.branch:
95
return tuple(self) == tuple(other)
98
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
99
self.revno, self.rev_id, self.branch)
102
def from_revision_id(branch, revision_id, revs):
103
"""Construct a RevisionInfo given just the id.
105
Use this if you don't know or care what the revno is.
107
if revision_id == revision.NULL_REVISION:
108
return RevisionInfo(branch, 0, revision_id)
110
revno = revs.index(revision_id) + 1
113
return RevisionInfo(branch, revno, revision_id)
116
# classes in this list should have a "prefix" attribute, against which
117
# string specs are matched
121
class RevisionSpec(object):
122
"""A parsed revision specification."""
124
help_txt = """A parsed revision specification.
126
A revision specification is a string, which may be unambiguous about
127
what it represents by giving a prefix like 'date:' or 'revid:' etc,
128
or it may have no prefix, in which case it's tried against several
129
specifier types in sequence to determine what the user meant.
131
Revision specs are an UI element, and they have been moved out
132
of the branch class to leave "back-end" classes unaware of such
133
details. Code that gets a revno or rev_id from other code should
134
not be using revision specs - revnos and revision ids are the
135
accepted ways to refer to revisions internally.
137
(Equivalent to the old Branch method get_revision_info())
141
wants_revision_history = True
144
def from_string(spec):
145
"""Parse a revision spec string into a RevisionSpec object.
147
:param spec: A string specified by the user
148
:return: A RevisionSpec object that understands how to parse the
151
if not isinstance(spec, (type(None), basestring)):
152
raise TypeError('error')
155
return RevisionSpec(None, _internal=True)
156
match = revspec_registry.get_prefix(spec)
157
if match is not None:
158
spectype, specsuffix = match
159
trace.mutter('Returning RevisionSpec %s for %s',
160
spectype.__name__, spec)
161
return spectype(spec, _internal=True)
163
for spectype in SPEC_TYPES:
164
trace.mutter('Returning RevisionSpec %s for %s',
165
spectype.__name__, spec)
166
if spec.startswith(spectype.prefix):
167
return spectype(spec, _internal=True)
168
# Otherwise treat it as a DWIM
169
return RevisionSpec_dwim(spec, _internal=True)
171
def __init__(self, spec, _internal=False):
172
"""Create a RevisionSpec referring to the Null revision.
174
:param spec: The original spec supplied by the user
175
:param _internal: Used to ensure that RevisionSpec is not being
176
called directly. Only from RevisionSpec.from_string()
179
# XXX: Update this after 0.10 is released
180
symbol_versioning.warn('Creating a RevisionSpec directly has'
181
' been deprecated in version 0.11. Use'
182
' RevisionSpec.from_string()'
184
DeprecationWarning, stacklevel=2)
185
self.user_spec = spec
186
if self.prefix and spec.startswith(self.prefix):
187
spec = spec[len(self.prefix):]
190
def _match_on(self, branch, revs):
191
trace.mutter('Returning RevisionSpec._match_on: None')
192
return RevisionInfo(branch, None, None)
194
def _match_on_and_check(self, branch, revs):
195
info = self._match_on(branch, revs)
198
elif info == (None, None):
199
# special case - nothing supplied
202
raise errors.InvalidRevisionSpec(self.user_spec, branch)
204
raise errors.InvalidRevisionSpec(self.spec, branch)
206
def in_history(self, branch):
208
if self.wants_revision_history:
209
revs = branch.revision_history()
213
# this should never trigger.
214
# TODO: make it a deprecated code path. RBC 20060928
216
return self._match_on_and_check(branch, revs)
218
# FIXME: in_history is somewhat broken,
219
# it will return non-history revisions in many
220
# circumstances. The expected facility is that
221
# in_history only returns revision-history revs,
222
# in_store returns any rev. RBC 20051010
223
# aliases for now, when we fix the core logic, then they
224
# will do what you expect.
225
in_store = in_history
228
def as_revision_id(self, context_branch):
229
"""Return just the revision_id for this revisions spec.
231
Some revision specs require a context_branch to be able to determine
232
their value. Not all specs will make use of it.
234
return self._as_revision_id(context_branch)
236
def _as_revision_id(self, context_branch):
237
"""Implementation of as_revision_id()
239
Classes should override this function to provide appropriate
240
functionality. The default is to just call '.in_history().rev_id'
242
return self.in_history(context_branch).rev_id
244
def as_tree(self, context_branch):
245
"""Return the tree object for this revisions spec.
247
Some revision specs require a context_branch to be able to determine
248
the revision id and access the repository. Not all specs will make
251
return self._as_tree(context_branch)
253
def _as_tree(self, context_branch):
254
"""Implementation of as_tree().
256
Classes should override this function to provide appropriate
257
functionality. The default is to just call '.as_revision_id()'
258
and get the revision tree from context_branch's repository.
260
revision_id = self.as_revision_id(context_branch)
261
return context_branch.repository.revision_tree(revision_id)
264
# this is mostly for helping with testing
265
return '<%s %s>' % (self.__class__.__name__,
268
def needs_branch(self):
269
"""Whether this revision spec needs a branch.
271
Set this to False the branch argument of _match_on is not used.
275
def get_branch(self):
276
"""When the revision specifier contains a branch location, return it.
278
Otherwise, return None.
285
class RevisionSpec_dwim(RevisionSpec):
286
"""Provides a DWIMish revision specifier lookup.
288
Note that this does not go in the revspec_registry. It's solely
289
called from RevisionSpec.from_string().
293
# Default to False to save building the history in the revno case
294
wants_revision_history = False
297
def __try_spectype(self, rstype, spec, branch):
298
rs = rstype(spec, _internal=True)
299
# Hit in_history to find out if it exists, or we need to try the
301
return rs.in_history(branch)
303
def _match_on(self, branch, revs):
304
"""Run the lookup and see what we can get."""
307
# First, see if it's a revno
309
if _revno_regex is None:
310
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
311
if _revno_regex.match(spec) is not None:
313
return self.__try_spectype(RevisionSpec_revno, spec, branch)
314
except errors.InvalidRevisionSpec:
317
# It's not a revno, so now we need this
318
self.wants_revision_history = True
320
# OK, next let's try for a tag
322
return self.__try_spectype(RevisionSpec_tag, spec, branch)
323
except (errors.NoSuchTag, errors.TagsNotSupported):
326
# Maybe it's a revid?
328
return self.__try_spectype(RevisionSpec_revid, spec, branch)
329
except errors.InvalidRevisionSpec:
334
return self.__try_spectype(RevisionSpec_date, spec, branch)
335
except errors.InvalidRevisionSpec:
338
# OK, last try, maybe it's a branch
340
return self.__try_spectype(RevisionSpec_branch, spec, branch)
341
except errors.NotBranchError:
344
# Well, I dunno what it is.
345
raise errors.InvalidRevisionSpec(self.spec, branch)
348
class RevisionSpec_revno(RevisionSpec):
349
"""Selects a revision using a number."""
351
help_txt = """Selects a revision using a number.
353
Use an integer to specify a revision in the history of the branch.
354
Optionally a branch can be specified. A negative number will count
355
from the end of the branch (-1 is the last revision, -2 the previous
356
one). If the negative number is larger than the branch's history, the
357
first revision is returned.
360
revno:1 -> return the first revision of this branch
361
revno:3:/path/to/branch -> return the 3rd revision of
362
the branch '/path/to/branch'
363
revno:-1 -> The last revision in a branch.
364
-2:http://other/branch -> The second to last revision in the
366
-1000000 -> Most likely the first revision, unless
367
your history is very long.
370
wants_revision_history = False
372
def _match_on(self, branch, revs):
373
"""Lookup a revision by revision number"""
374
branch, revno, revision_id = self._lookup(branch, revs)
375
return RevisionInfo(branch, revno, revision_id)
377
def _lookup(self, branch, revs_or_none):
378
loc = self.spec.find(':')
380
revno_spec = self.spec
383
revno_spec = self.spec[:loc]
384
branch_spec = self.spec[loc+1:]
388
raise errors.InvalidRevisionSpec(self.user_spec,
389
branch, 'cannot have an empty revno and no branch')
393
revno = int(revno_spec)
396
# dotted decimal. This arguably should not be here
397
# but the from_string method is a little primitive
398
# right now - RBC 20060928
400
match_revno = tuple((int(number) for number in revno_spec.split('.')))
401
except ValueError, e:
402
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
407
# the user has override the branch to look in.
408
# we need to refresh the revision_history map and
410
from bzrlib.branch import Branch
411
branch = Branch.open(branch_spec)
416
revision_id = branch.dotted_revno_to_revision_id(match_revno,
418
except errors.NoSuchRevision:
419
raise errors.InvalidRevisionSpec(self.user_spec, branch)
421
# there is no traditional 'revno' for dotted-decimal revnos.
422
# so for API compatability we return None.
423
return branch, None, revision_id
425
last_revno, last_revision_id = branch.last_revision_info()
427
# if get_rev_id supported negative revnos, there would not be a
428
# need for this special case.
429
if (-revno) >= last_revno:
432
revno = last_revno + revno + 1
434
revision_id = branch.get_rev_id(revno, revs_or_none)
435
except errors.NoSuchRevision:
436
raise errors.InvalidRevisionSpec(self.user_spec, branch)
437
return branch, revno, revision_id
439
def _as_revision_id(self, context_branch):
440
# We would have the revno here, but we don't really care
441
branch, revno, revision_id = self._lookup(context_branch, None)
444
def needs_branch(self):
445
return self.spec.find(':') == -1
447
def get_branch(self):
448
if self.spec.find(':') == -1:
451
return self.spec[self.spec.find(':')+1:]
454
RevisionSpec_int = RevisionSpec_revno
458
class RevisionSpec_revid(RevisionSpec):
459
"""Selects a revision using the revision id."""
461
help_txt = """Selects a revision using the revision id.
463
Supply a specific revision id, that can be used to specify any
464
revision id in the ancestry of the branch.
465
Including merges, and pending merges.
468
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
473
def _match_on(self, branch, revs):
474
# self.spec comes straight from parsing the command line arguments,
475
# so we expect it to be a Unicode string. Switch it to the internal
477
revision_id = osutils.safe_revision_id(self.spec, warn=False)
478
return RevisionInfo.from_revision_id(branch, revision_id, revs)
480
def _as_revision_id(self, context_branch):
481
return osutils.safe_revision_id(self.spec, warn=False)
485
class RevisionSpec_last(RevisionSpec):
486
"""Selects the nth revision from the end."""
488
help_txt = """Selects the nth revision from the end.
490
Supply a positive number to get the nth revision from the end.
491
This is the same as supplying negative numbers to the 'revno:' spec.
494
last:1 -> return the last revision
495
last:3 -> return the revision 2 before the end.
500
def _match_on(self, branch, revs):
501
revno, revision_id = self._revno_and_revision_id(branch, revs)
502
return RevisionInfo(branch, revno, revision_id)
504
def _revno_and_revision_id(self, context_branch, revs_or_none):
505
last_revno, last_revision_id = context_branch.last_revision_info()
509
raise errors.NoCommits(context_branch)
510
return last_revno, last_revision_id
513
offset = int(self.spec)
514
except ValueError, e:
515
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
518
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
519
'you must supply a positive value')
521
revno = last_revno - offset + 1
523
revision_id = context_branch.get_rev_id(revno, revs_or_none)
524
except errors.NoSuchRevision:
525
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
526
return revno, revision_id
528
def _as_revision_id(self, context_branch):
529
# We compute the revno as part of the process, but we don't really care
531
revno, revision_id = self._revno_and_revision_id(context_branch, None)
536
class RevisionSpec_before(RevisionSpec):
537
"""Selects the parent of the revision specified."""
539
help_txt = """Selects the parent of the revision specified.
541
Supply any revision spec to return the parent of that revision. This is
542
mostly useful when inspecting revisions that are not in the revision history
545
It is an error to request the parent of the null revision (before:0).
549
before:1913 -> Return the parent of revno 1913 (revno 1912)
550
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
552
bzr diff -r before:1913..1913
553
-> Find the changes between revision 1913 and its parent (1912).
554
(What changes did revision 1913 introduce).
555
This is equivalent to: bzr diff -c 1913
560
def _match_on(self, branch, revs):
561
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
563
raise errors.InvalidRevisionSpec(self.user_spec, branch,
564
'cannot go before the null: revision')
566
# We need to use the repository history here
567
rev = branch.repository.get_revision(r.rev_id)
568
if not rev.parent_ids:
570
revision_id = revision.NULL_REVISION
572
revision_id = rev.parent_ids[0]
574
revno = revs.index(revision_id) + 1
580
revision_id = branch.get_rev_id(revno, revs)
581
except errors.NoSuchRevision:
582
raise errors.InvalidRevisionSpec(self.user_spec,
584
return RevisionInfo(branch, revno, revision_id)
586
def _as_revision_id(self, context_branch):
587
base_revspec = RevisionSpec.from_string(self.spec)
588
base_revision_id = base_revspec.as_revision_id(context_branch)
589
if base_revision_id == revision.NULL_REVISION:
590
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
591
'cannot go before the null: revision')
592
context_repo = context_branch.repository
593
context_repo.lock_read()
595
parent_map = context_repo.get_parent_map([base_revision_id])
597
context_repo.unlock()
598
if base_revision_id not in parent_map:
599
# Ghost, or unknown revision id
600
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
601
'cannot find the matching revision')
602
parents = parent_map[base_revision_id]
604
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
605
'No parents for revision.')
610
class RevisionSpec_tag(RevisionSpec):
611
"""Select a revision identified by tag name"""
613
help_txt = """Selects a revision identified by a tag name.
615
Tags are stored in the branch and created by the 'tag' command.
620
def _match_on(self, branch, revs):
621
# Can raise tags not supported, NoSuchTag, etc
622
return RevisionInfo.from_revision_id(branch,
623
branch.tags.lookup_tag(self.spec),
626
def _as_revision_id(self, context_branch):
627
return context_branch.tags.lookup_tag(self.spec)
631
class _RevListToTimestamps(object):
632
"""This takes a list of revisions, and allows you to bisect by date"""
634
__slots__ = ['revs', 'branch']
636
def __init__(self, revs, branch):
640
def __getitem__(self, index):
641
"""Get the date of the index'd item"""
642
r = self.branch.repository.get_revision(self.revs[index])
643
# TODO: Handle timezone.
644
return datetime.datetime.fromtimestamp(r.timestamp)
647
return len(self.revs)
650
class RevisionSpec_date(RevisionSpec):
651
"""Selects a revision on the basis of a datestamp."""
653
help_txt = """Selects a revision on the basis of a datestamp.
655
Supply a datestamp to select the first revision that matches the date.
656
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
657
Matches the first entry after a given date (either at midnight or
658
at a specified time).
660
One way to display all the changes since yesterday would be::
662
bzr log -r date:yesterday..
666
date:yesterday -> select the first revision since yesterday
667
date:2006-08-14,17:10:14 -> select the first revision after
668
August 14th, 2006 at 5:10pm.
671
_date_re = re.compile(
672
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
674
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
677
def _match_on(self, branch, revs):
678
"""Spec for date revisions:
680
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
681
matches the first entry after a given date (either at midnight or
682
at a specified time).
684
# XXX: This doesn't actually work
685
# So the proper way of saying 'give me all entries for today' is:
686
# -r date:yesterday..date:today
687
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
688
if self.spec.lower() == 'yesterday':
689
dt = today - datetime.timedelta(days=1)
690
elif self.spec.lower() == 'today':
692
elif self.spec.lower() == 'tomorrow':
693
dt = today + datetime.timedelta(days=1)
695
m = self._date_re.match(self.spec)
696
if not m or (not m.group('date') and not m.group('time')):
697
raise errors.InvalidRevisionSpec(self.user_spec,
698
branch, 'invalid date')
702
year = int(m.group('year'))
703
month = int(m.group('month'))
704
day = int(m.group('day'))
711
hour = int(m.group('hour'))
712
minute = int(m.group('minute'))
713
if m.group('second'):
714
second = int(m.group('second'))
718
hour, minute, second = 0,0,0
720
raise errors.InvalidRevisionSpec(self.user_spec,
721
branch, 'invalid date')
723
dt = datetime.datetime(year=year, month=month, day=day,
724
hour=hour, minute=minute, second=second)
727
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
731
raise errors.InvalidRevisionSpec(self.user_spec, branch)
733
return RevisionInfo(branch, rev + 1)
737
class RevisionSpec_ancestor(RevisionSpec):
738
"""Selects a common ancestor with a second branch."""
740
help_txt = """Selects a common ancestor with a second branch.
742
Supply the path to a branch to select the common ancestor.
744
The common ancestor is the last revision that existed in both
745
branches. Usually this is the branch point, but it could also be
746
a revision that was merged.
748
This is frequently used with 'diff' to return all of the changes
749
that your branch introduces, while excluding the changes that you
750
have not merged from the remote branch.
754
ancestor:/path/to/branch
755
$ bzr diff -r ancestor:../../mainline/branch
759
def _match_on(self, branch, revs):
760
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
761
return self._find_revision_info(branch, self.spec)
763
def _as_revision_id(self, context_branch):
764
return self._find_revision_id(context_branch, self.spec)
767
def _find_revision_info(branch, other_location):
768
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
771
revno = branch.revision_id_to_revno(revision_id)
772
except errors.NoSuchRevision:
774
return RevisionInfo(branch, revno, revision_id)
777
def _find_revision_id(branch, other_location):
778
from bzrlib.branch import Branch
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
other_branch.lock_read()
790
revision_b = revision.ensure_null(other_branch.last_revision())
791
if revision_b == revision.NULL_REVISION:
792
raise errors.NoCommits(other_branch)
793
graph = branch.repository.get_graph(other_branch.repository)
794
rev_id = graph.find_unique_lca(revision_a, revision_b)
796
other_branch.unlock()
797
if rev_id == revision.NULL_REVISION:
798
raise errors.NoCommonAncestor(revision_a, revision_b)
806
class RevisionSpec_branch(RevisionSpec):
807
"""Selects the last revision of a specified branch."""
809
help_txt = """Selects the last revision of a specified branch.
811
Supply the path to a branch to select its last revision.
815
branch:/path/to/branch
819
def _match_on(self, branch, revs):
820
from bzrlib.branch import Branch
821
other_branch = Branch.open(self.spec)
822
revision_b = other_branch.last_revision()
823
if revision_b in (None, revision.NULL_REVISION):
824
raise errors.NoCommits(other_branch)
825
# pull in the remote revisions so we can diff
826
branch.fetch(other_branch, revision_b)
828
revno = branch.revision_id_to_revno(revision_b)
829
except errors.NoSuchRevision:
831
return RevisionInfo(branch, revno, revision_b)
833
def _as_revision_id(self, context_branch):
834
from bzrlib.branch import Branch
835
other_branch = Branch.open(self.spec)
836
last_revision = other_branch.last_revision()
837
last_revision = revision.ensure_null(last_revision)
838
context_branch.fetch(other_branch, last_revision)
839
if last_revision == revision.NULL_REVISION:
840
raise errors.NoCommits(other_branch)
843
def _as_tree(self, context_branch):
844
from bzrlib.branch import Branch
845
other_branch = Branch.open(self.spec)
846
last_revision = other_branch.last_revision()
847
last_revision = revision.ensure_null(last_revision)
848
if last_revision == revision.NULL_REVISION:
849
raise errors.NoCommits(other_branch)
850
return other_branch.repository.revision_tree(last_revision)
854
class RevisionSpec_submit(RevisionSpec_ancestor):
855
"""Selects a common ancestor with a submit branch."""
857
help_txt = """Selects a common ancestor with the submit branch.
859
Diffing against this shows all the changes that were made in this branch,
860
and is a good predictor of what merge will do. The submit branch is
861
used by the bundle and merge directive commands. If no submit branch
862
is specified, the parent branch is used instead.
864
The common ancestor is the last revision that existed in both
865
branches. Usually this is the branch point, but it could also be
866
a revision that was merged.
870
$ bzr diff -r submit:
875
def _get_submit_location(self, branch):
876
submit_location = branch.get_submit_branch()
877
location_type = 'submit branch'
878
if submit_location is None:
879
submit_location = branch.get_parent()
880
location_type = 'parent branch'
881
if submit_location is None:
882
raise errors.NoSubmitBranch(branch)
883
trace.note('Using %s %s', location_type, submit_location)
884
return submit_location
886
def _match_on(self, branch, revs):
887
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
888
return self._find_revision_info(branch,
889
self._get_submit_location(branch))
891
def _as_revision_id(self, context_branch):
892
return self._find_revision_id(context_branch,
893
self._get_submit_location(context_branch))
896
revspec_registry = registry.Registry()
897
def _register_revspec(revspec):
898
revspec_registry.register(revspec.prefix, revspec)
900
_register_revspec(RevisionSpec_revno)
901
_register_revspec(RevisionSpec_revid)
902
_register_revspec(RevisionSpec_last)
903
_register_revspec(RevisionSpec_before)
904
_register_revspec(RevisionSpec_tag)
905
_register_revspec(RevisionSpec_date)
906
_register_revspec(RevisionSpec_ancestor)
907
_register_revspec(RevisionSpec_branch)
908
_register_revspec(RevisionSpec_submit)
910
SPEC_TYPES = symbol_versioning.deprecated_list(
911
symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])