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
if spec.startswith(spectype.prefix):
165
trace.mutter('Returning RevisionSpec %s for %s',
166
spectype.__name__, spec)
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
symbol_versioning.warn('Creating a RevisionSpec directly has'
180
' been deprecated in version 0.11. Use'
181
' RevisionSpec.from_string()'
183
DeprecationWarning, stacklevel=2)
184
self.user_spec = spec
185
if self.prefix and spec.startswith(self.prefix):
186
spec = spec[len(self.prefix):]
189
def _match_on(self, branch, revs):
190
trace.mutter('Returning RevisionSpec._match_on: None')
191
return RevisionInfo(branch, None, None)
193
def _match_on_and_check(self, branch, revs):
194
info = self._match_on(branch, revs)
197
elif info == (None, None):
198
# special case - nothing supplied
201
raise errors.InvalidRevisionSpec(self.user_spec, branch)
203
raise errors.InvalidRevisionSpec(self.spec, branch)
205
def in_history(self, branch):
207
if self.wants_revision_history:
208
revs = branch.revision_history()
212
# this should never trigger.
213
# TODO: make it a deprecated code path. RBC 20060928
215
return self._match_on_and_check(branch, revs)
217
# FIXME: in_history is somewhat broken,
218
# it will return non-history revisions in many
219
# circumstances. The expected facility is that
220
# in_history only returns revision-history revs,
221
# in_store returns any rev. RBC 20051010
222
# aliases for now, when we fix the core logic, then they
223
# will do what you expect.
224
in_store = in_history
227
def as_revision_id(self, context_branch):
228
"""Return just the revision_id for this revisions spec.
230
Some revision specs require a context_branch to be able to determine
231
their value. Not all specs will make use of it.
233
return self._as_revision_id(context_branch)
235
def _as_revision_id(self, context_branch):
236
"""Implementation of as_revision_id()
238
Classes should override this function to provide appropriate
239
functionality. The default is to just call '.in_history().rev_id'
241
return self.in_history(context_branch).rev_id
243
def as_tree(self, context_branch):
244
"""Return the tree object for this revisions spec.
246
Some revision specs require a context_branch to be able to determine
247
the revision id and access the repository. Not all specs will make
250
return self._as_tree(context_branch)
252
def _as_tree(self, context_branch):
253
"""Implementation of as_tree().
255
Classes should override this function to provide appropriate
256
functionality. The default is to just call '.as_revision_id()'
257
and get the revision tree from context_branch's repository.
259
revision_id = self.as_revision_id(context_branch)
260
return context_branch.repository.revision_tree(revision_id)
263
# this is mostly for helping with testing
264
return '<%s %s>' % (self.__class__.__name__,
267
def needs_branch(self):
268
"""Whether this revision spec needs a branch.
270
Set this to False the branch argument of _match_on is not used.
274
def get_branch(self):
275
"""When the revision specifier contains a branch location, return it.
277
Otherwise, return None.
284
class RevisionSpec_dwim(RevisionSpec):
285
"""Provides a DWIMish revision specifier lookup.
287
Note that this does not go in the revspec_registry. It's solely
288
called from RevisionSpec.from_string().
292
# Default to False to save building the history in the revno case
293
wants_revision_history = False
296
def __try_spectype(self, rstype, spec, branch):
297
rs = rstype(spec, _internal=True)
298
# Hit in_history to find out if it exists, or we need to try the
300
return rs.in_history(branch)
302
def _match_on(self, branch, revs):
303
"""Run the lookup and see what we can get."""
306
# First, see if it's a revno
308
if _revno_regex is None:
309
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
310
if _revno_regex.match(spec) is not None:
312
return self.__try_spectype(RevisionSpec_revno, spec, branch)
313
except errors.InvalidRevisionSpec:
316
# It's not a revno, so now we need this
317
self.wants_revision_history = True
319
# OK, next let's try for a tag
321
return self.__try_spectype(RevisionSpec_tag, spec, branch)
322
except (errors.NoSuchTag, errors.TagsNotSupported):
325
# Maybe it's a revid?
327
return self.__try_spectype(RevisionSpec_revid, spec, branch)
328
except errors.InvalidRevisionSpec:
333
return self.__try_spectype(RevisionSpec_date, spec, branch)
334
except errors.InvalidRevisionSpec:
337
# OK, last try, maybe it's a branch
339
return self.__try_spectype(RevisionSpec_branch, spec, branch)
340
except errors.NotBranchError:
343
# Well, I dunno what it is.
344
raise errors.InvalidRevisionSpec(self.spec, branch)
347
class RevisionSpec_revno(RevisionSpec):
348
"""Selects a revision using a number."""
350
help_txt = """Selects a revision using a number.
352
Use an integer to specify a revision in the history of the branch.
353
Optionally a branch can be specified. A negative number will count
354
from the end of the branch (-1 is the last revision, -2 the previous
355
one). If the negative number is larger than the branch's history, the
356
first revision is returned.
359
revno:1 -> return the first revision of this branch
360
revno:3:/path/to/branch -> return the 3rd revision of
361
the branch '/path/to/branch'
362
revno:-1 -> The last revision in a branch.
363
-2:http://other/branch -> The second to last revision in the
365
-1000000 -> Most likely the first revision, unless
366
your history is very long.
369
wants_revision_history = False
371
def _match_on(self, branch, revs):
372
"""Lookup a revision by revision number"""
373
branch, revno, revision_id = self._lookup(branch, revs)
374
return RevisionInfo(branch, revno, revision_id)
376
def _lookup(self, branch, revs_or_none):
377
loc = self.spec.find(':')
379
revno_spec = self.spec
382
revno_spec = self.spec[:loc]
383
branch_spec = self.spec[loc+1:]
387
raise errors.InvalidRevisionSpec(self.user_spec,
388
branch, 'cannot have an empty revno and no branch')
392
revno = int(revno_spec)
395
# dotted decimal. This arguably should not be here
396
# but the from_string method is a little primitive
397
# right now - RBC 20060928
399
match_revno = tuple((int(number) for number in revno_spec.split('.')))
400
except ValueError, e:
401
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
406
# the user has override the branch to look in.
407
# we need to refresh the revision_history map and
409
from bzrlib.branch import Branch
410
branch = Branch.open(branch_spec)
415
revision_id = branch.dotted_revno_to_revision_id(match_revno,
417
except errors.NoSuchRevision:
418
raise errors.InvalidRevisionSpec(self.user_spec, branch)
420
# there is no traditional 'revno' for dotted-decimal revnos.
421
# so for API compatability we return None.
422
return branch, None, revision_id
424
last_revno, last_revision_id = branch.last_revision_info()
426
# if get_rev_id supported negative revnos, there would not be a
427
# need for this special case.
428
if (-revno) >= last_revno:
431
revno = last_revno + revno + 1
433
revision_id = branch.get_rev_id(revno, revs_or_none)
434
except errors.NoSuchRevision:
435
raise errors.InvalidRevisionSpec(self.user_spec, branch)
436
return branch, revno, revision_id
438
def _as_revision_id(self, context_branch):
439
# We would have the revno here, but we don't really care
440
branch, revno, revision_id = self._lookup(context_branch, None)
443
def needs_branch(self):
444
return self.spec.find(':') == -1
446
def get_branch(self):
447
if self.spec.find(':') == -1:
450
return self.spec[self.spec.find(':')+1:]
453
RevisionSpec_int = RevisionSpec_revno
457
class RevisionSpec_revid(RevisionSpec):
458
"""Selects a revision using the revision id."""
460
help_txt = """Selects a revision using the revision id.
462
Supply a specific revision id, that can be used to specify any
463
revision id in the ancestry of the branch.
464
Including merges, and pending merges.
467
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
472
def _match_on(self, branch, revs):
473
# self.spec comes straight from parsing the command line arguments,
474
# so we expect it to be a Unicode string. Switch it to the internal
476
revision_id = osutils.safe_revision_id(self.spec, warn=False)
477
return RevisionInfo.from_revision_id(branch, revision_id, revs)
479
def _as_revision_id(self, context_branch):
480
return osutils.safe_revision_id(self.spec, warn=False)
484
class RevisionSpec_last(RevisionSpec):
485
"""Selects the nth revision from the end."""
487
help_txt = """Selects the nth revision from the end.
489
Supply a positive number to get the nth revision from the end.
490
This is the same as supplying negative numbers to the 'revno:' spec.
493
last:1 -> return the last revision
494
last:3 -> return the revision 2 before the end.
499
def _match_on(self, branch, revs):
500
revno, revision_id = self._revno_and_revision_id(branch, revs)
501
return RevisionInfo(branch, revno, revision_id)
503
def _revno_and_revision_id(self, context_branch, revs_or_none):
504
last_revno, last_revision_id = context_branch.last_revision_info()
508
raise errors.NoCommits(context_branch)
509
return last_revno, last_revision_id
512
offset = int(self.spec)
513
except ValueError, e:
514
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
517
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
518
'you must supply a positive value')
520
revno = last_revno - offset + 1
522
revision_id = context_branch.get_rev_id(revno, revs_or_none)
523
except errors.NoSuchRevision:
524
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
525
return revno, revision_id
527
def _as_revision_id(self, context_branch):
528
# We compute the revno as part of the process, but we don't really care
530
revno, revision_id = self._revno_and_revision_id(context_branch, None)
535
class RevisionSpec_before(RevisionSpec):
536
"""Selects the parent of the revision specified."""
538
help_txt = """Selects the parent of the revision specified.
540
Supply any revision spec to return the parent of that revision. This is
541
mostly useful when inspecting revisions that are not in the revision history
544
It is an error to request the parent of the null revision (before:0).
548
before:1913 -> Return the parent of revno 1913 (revno 1912)
549
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
551
bzr diff -r before:1913..1913
552
-> Find the changes between revision 1913 and its parent (1912).
553
(What changes did revision 1913 introduce).
554
This is equivalent to: bzr diff -c 1913
559
def _match_on(self, branch, revs):
560
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
562
raise errors.InvalidRevisionSpec(self.user_spec, branch,
563
'cannot go before the null: revision')
565
# We need to use the repository history here
566
rev = branch.repository.get_revision(r.rev_id)
567
if not rev.parent_ids:
569
revision_id = revision.NULL_REVISION
571
revision_id = rev.parent_ids[0]
573
revno = revs.index(revision_id) + 1
579
revision_id = branch.get_rev_id(revno, revs)
580
except errors.NoSuchRevision:
581
raise errors.InvalidRevisionSpec(self.user_spec,
583
return RevisionInfo(branch, revno, revision_id)
585
def _as_revision_id(self, context_branch):
586
base_revspec = RevisionSpec.from_string(self.spec)
587
base_revision_id = base_revspec.as_revision_id(context_branch)
588
if base_revision_id == revision.NULL_REVISION:
589
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
590
'cannot go before the null: revision')
591
context_repo = context_branch.repository
592
context_repo.lock_read()
594
parent_map = context_repo.get_parent_map([base_revision_id])
596
context_repo.unlock()
597
if base_revision_id not in parent_map:
598
# Ghost, or unknown revision id
599
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
600
'cannot find the matching revision')
601
parents = parent_map[base_revision_id]
603
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
604
'No parents for revision.')
609
class RevisionSpec_tag(RevisionSpec):
610
"""Select a revision identified by tag name"""
612
help_txt = """Selects a revision identified by a tag name.
614
Tags are stored in the branch and created by the 'tag' command.
619
def _match_on(self, branch, revs):
620
# Can raise tags not supported, NoSuchTag, etc
621
return RevisionInfo.from_revision_id(branch,
622
branch.tags.lookup_tag(self.spec),
625
def _as_revision_id(self, context_branch):
626
return context_branch.tags.lookup_tag(self.spec)
630
class _RevListToTimestamps(object):
631
"""This takes a list of revisions, and allows you to bisect by date"""
633
__slots__ = ['revs', 'branch']
635
def __init__(self, revs, branch):
639
def __getitem__(self, index):
640
"""Get the date of the index'd item"""
641
r = self.branch.repository.get_revision(self.revs[index])
642
# TODO: Handle timezone.
643
return datetime.datetime.fromtimestamp(r.timestamp)
646
return len(self.revs)
649
class RevisionSpec_date(RevisionSpec):
650
"""Selects a revision on the basis of a datestamp."""
652
help_txt = """Selects a revision on the basis of a datestamp.
654
Supply a datestamp to select the first revision that matches the date.
655
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
656
Matches the first entry after a given date (either at midnight or
657
at a specified time).
659
One way to display all the changes since yesterday would be::
661
bzr log -r date:yesterday..
665
date:yesterday -> select the first revision since yesterday
666
date:2006-08-14,17:10:14 -> select the first revision after
667
August 14th, 2006 at 5:10pm.
670
_date_re = re.compile(
671
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
673
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
676
def _match_on(self, branch, revs):
677
"""Spec for date revisions:
679
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
680
matches the first entry after a given date (either at midnight or
681
at a specified time).
683
# XXX: This doesn't actually work
684
# So the proper way of saying 'give me all entries for today' is:
685
# -r date:yesterday..date:today
686
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
687
if self.spec.lower() == 'yesterday':
688
dt = today - datetime.timedelta(days=1)
689
elif self.spec.lower() == 'today':
691
elif self.spec.lower() == 'tomorrow':
692
dt = today + datetime.timedelta(days=1)
694
m = self._date_re.match(self.spec)
695
if not m or (not m.group('date') and not m.group('time')):
696
raise errors.InvalidRevisionSpec(self.user_spec,
697
branch, 'invalid date')
701
year = int(m.group('year'))
702
month = int(m.group('month'))
703
day = int(m.group('day'))
710
hour = int(m.group('hour'))
711
minute = int(m.group('minute'))
712
if m.group('second'):
713
second = int(m.group('second'))
717
hour, minute, second = 0,0,0
719
raise errors.InvalidRevisionSpec(self.user_spec,
720
branch, 'invalid date')
722
dt = datetime.datetime(year=year, month=month, day=day,
723
hour=hour, minute=minute, second=second)
726
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
730
raise errors.InvalidRevisionSpec(self.user_spec, branch)
732
return RevisionInfo(branch, rev + 1)
736
class RevisionSpec_ancestor(RevisionSpec):
737
"""Selects a common ancestor with a second branch."""
739
help_txt = """Selects a common ancestor with a second branch.
741
Supply the path to a branch to select the common ancestor.
743
The common ancestor is the last revision that existed in both
744
branches. Usually this is the branch point, but it could also be
745
a revision that was merged.
747
This is frequently used with 'diff' to return all of the changes
748
that your branch introduces, while excluding the changes that you
749
have not merged from the remote branch.
753
ancestor:/path/to/branch
754
$ bzr diff -r ancestor:../../mainline/branch
758
def _match_on(self, branch, revs):
759
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
760
return self._find_revision_info(branch, self.spec)
762
def _as_revision_id(self, context_branch):
763
return self._find_revision_id(context_branch, self.spec)
766
def _find_revision_info(branch, other_location):
767
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
770
revno = branch.revision_id_to_revno(revision_id)
771
except errors.NoSuchRevision:
773
return RevisionInfo(branch, revno, revision_id)
776
def _find_revision_id(branch, other_location):
777
from bzrlib.branch import Branch
781
revision_a = revision.ensure_null(branch.last_revision())
782
if revision_a == revision.NULL_REVISION:
783
raise errors.NoCommits(branch)
784
if other_location == '':
785
other_location = branch.get_parent()
786
other_branch = Branch.open(other_location)
787
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)
795
other_branch.unlock()
796
if rev_id == revision.NULL_REVISION:
797
raise errors.NoCommonAncestor(revision_a, revision_b)
805
class RevisionSpec_branch(RevisionSpec):
806
"""Selects the last revision of a specified branch."""
808
help_txt = """Selects the last revision of a specified branch.
810
Supply the path to a branch to select its last revision.
814
branch:/path/to/branch
818
def _match_on(self, branch, revs):
819
from bzrlib.branch import Branch
820
other_branch = Branch.open(self.spec)
821
revision_b = other_branch.last_revision()
822
if revision_b in (None, revision.NULL_REVISION):
823
raise errors.NoCommits(other_branch)
824
# pull in the remote revisions so we can diff
825
branch.fetch(other_branch, revision_b)
827
revno = branch.revision_id_to_revno(revision_b)
828
except errors.NoSuchRevision:
830
return RevisionInfo(branch, revno, revision_b)
832
def _as_revision_id(self, context_branch):
833
from bzrlib.branch import Branch
834
other_branch = Branch.open(self.spec)
835
last_revision = other_branch.last_revision()
836
last_revision = revision.ensure_null(last_revision)
837
context_branch.fetch(other_branch, last_revision)
838
if last_revision == revision.NULL_REVISION:
839
raise errors.NoCommits(other_branch)
842
def _as_tree(self, context_branch):
843
from bzrlib.branch import Branch
844
other_branch = Branch.open(self.spec)
845
last_revision = other_branch.last_revision()
846
last_revision = revision.ensure_null(last_revision)
847
if last_revision == revision.NULL_REVISION:
848
raise errors.NoCommits(other_branch)
849
return other_branch.repository.revision_tree(last_revision)
853
class RevisionSpec_submit(RevisionSpec_ancestor):
854
"""Selects a common ancestor with a submit branch."""
856
help_txt = """Selects a common ancestor with the submit branch.
858
Diffing against this shows all the changes that were made in this branch,
859
and is a good predictor of what merge will do. The submit branch is
860
used by the bundle and merge directive commands. If no submit branch
861
is specified, the parent branch is used instead.
863
The common ancestor is the last revision that existed in both
864
branches. Usually this is the branch point, but it could also be
865
a revision that was merged.
869
$ bzr diff -r submit:
874
def _get_submit_location(self, branch):
875
submit_location = branch.get_submit_branch()
876
location_type = 'submit branch'
877
if submit_location is None:
878
submit_location = branch.get_parent()
879
location_type = 'parent branch'
880
if submit_location is None:
881
raise errors.NoSubmitBranch(branch)
882
trace.note('Using %s %s', location_type, submit_location)
883
return submit_location
885
def _match_on(self, branch, revs):
886
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
887
return self._find_revision_info(branch,
888
self._get_submit_location(branch))
890
def _as_revision_id(self, context_branch):
891
return self._find_revision_id(context_branch,
892
self._get_submit_location(context_branch))
895
revspec_registry = registry.Registry()
896
def _register_revspec(revspec):
897
revspec_registry.register(revspec.prefix, revspec)
899
_register_revspec(RevisionSpec_revno)
900
_register_revspec(RevisionSpec_revid)
901
_register_revspec(RevisionSpec_last)
902
_register_revspec(RevisionSpec_before)
903
_register_revspec(RevisionSpec_tag)
904
_register_revspec(RevisionSpec_date)
905
_register_revspec(RevisionSpec_ancestor)
906
_register_revspec(RevisionSpec_branch)
907
_register_revspec(RevisionSpec_submit)
909
SPEC_TYPES = symbol_versioning.deprecated_list(
910
symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])