Rev 4773: Implement a DWIM revspec type in http://bazaar.launchpad.net/~vila/bzr/integration
Vincent Ladeuil
v.ladeuil+lp at free.fr
Tue Oct 27 14:14:01 GMT 2009
At http://bazaar.launchpad.net/~vila/bzr/integration
------------------------------------------------------------
revno: 4773 [merge]
revision-id: v.ladeuil+lp at free.fr-20091027141340-995wzkfrtwbahkrc
parent: pqm at pqm.ubuntu.com-20091027133256-ppg47nq6cvqdycze
parent: v.ladeuil+lp at free.fr-20091027140429-br7sqcmk3zstngmm
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: integration
timestamp: Tue 2009-10-27 15:13:40 +0100
message:
Implement a DWIM revspec type
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/help_topics/__init__.py help_topics.py-20060920210027-rnim90q9e0bwxvy4-1
bzrlib/option.py option.py-20051014052914-661fb36e76e7362f
bzrlib/revisionspec.py revisionspec.py-20050907152633-17567659fd5c0ddb
bzrlib/tests/test_revisionspec.py testrevisionnamespaces.py-20050711050225-8b4af89e6b1efe84
doc/en/user-guide/specifying_revisions.txt specifying_revisions.txt-20060314161707-19deb139101bea33
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS 2009-10-26 15:59:54 +0000
+++ b/NEWS 2009-10-27 14:13:40 +0000
@@ -254,6 +254,10 @@
Improvements
************
+* Revision specifiers can now be given in a more DWIM form, without
+ needing explicit prefixes for specifiers like tags or revision id's.
+ See ``bzr help revisionspec`` for full details. (Matthew Fuller)
+
* Bazaar gives a warning before exiting, and writes into ``.bzr.log``, if
compiled extensions can't be loaded. This typically indicates a
packaging or installation problem. In this case Bazaar will keep
=== modified file 'bzrlib/help_topics/__init__.py'
--- a/bzrlib/help_topics/__init__.py 2009-10-13 20:15:45 +0000
+++ b/bzrlib/help_topics/__init__.py 2009-10-27 14:13:40 +0000
@@ -152,10 +152,16 @@
out.append(
"""Revision Identifiers
-A revision identifier refers to a specific state of a branch's history. It can
-be a revision number, or a keyword followed by ':' and often other
-parameters. Some examples of identifiers are '3', 'last:1', 'before:yesterday'
-and 'submit:'.
+A revision identifier refers to a specific state of a branch's history. It
+can be expressed in several ways. It can begin with a keyword to
+unambiguously specify a given lookup type; some examples are 'last:1',
+'before:yesterday' and 'submit:'.
+
+Alternately, it can be given without a keyword, in which case it will be
+checked as a revision number, a tag, a revision id, a date specification, or a
+branch specification, in that order. For example, 'date:today' could be
+written as simply 'today', though if you have a tag called 'today' that will
+be found first.
If 'REV1' and 'REV2' are revision identifiers, then 'REV1..REV2' denotes a
revision range. Examples: '3647..3649', 'date:yesterday..-1' and
=== modified file 'bzrlib/option.py'
--- a/bzrlib/option.py 2009-04-03 20:05:25 +0000
+++ b/bzrlib/option.py 2009-08-18 08:10:44 +0000
@@ -40,25 +40,25 @@
each revision specifier supplied.
>>> _parse_revision_str('234')
- [<RevisionSpec_revno 234>]
+ [<RevisionSpec_dwim 234>]
>>> _parse_revision_str('234..567')
- [<RevisionSpec_revno 234>, <RevisionSpec_revno 567>]
+ [<RevisionSpec_dwim 234>, <RevisionSpec_dwim 567>]
>>> _parse_revision_str('..')
[<RevisionSpec None>, <RevisionSpec None>]
>>> _parse_revision_str('..234')
- [<RevisionSpec None>, <RevisionSpec_revno 234>]
+ [<RevisionSpec None>, <RevisionSpec_dwim 234>]
>>> _parse_revision_str('234..')
- [<RevisionSpec_revno 234>, <RevisionSpec None>]
+ [<RevisionSpec_dwim 234>, <RevisionSpec None>]
>>> _parse_revision_str('234..456..789') # Maybe this should be an error
- [<RevisionSpec_revno 234>, <RevisionSpec_revno 456>, <RevisionSpec_revno 789>]
+ [<RevisionSpec_dwim 234>, <RevisionSpec_dwim 456>, <RevisionSpec_dwim 789>]
>>> _parse_revision_str('234....789') #Error ?
- [<RevisionSpec_revno 234>, <RevisionSpec None>, <RevisionSpec_revno 789>]
+ [<RevisionSpec_dwim 234>, <RevisionSpec None>, <RevisionSpec_dwim 789>]
>>> _parse_revision_str('revid:test at other.com-234234')
[<RevisionSpec_revid revid:test at other.com-234234>]
>>> _parse_revision_str('revid:test at other.com-234234..revid:test at other.com-234235')
[<RevisionSpec_revid revid:test at other.com-234234>, <RevisionSpec_revid revid:test at other.com-234235>]
>>> _parse_revision_str('revid:test at other.com-234234..23')
- [<RevisionSpec_revid revid:test at other.com-234234>, <RevisionSpec_revno 23>]
+ [<RevisionSpec_revid revid:test at other.com-234234>, <RevisionSpec_dwim 23>]
>>> _parse_revision_str('date:2005-04-12')
[<RevisionSpec_date date:2005-04-12>]
>>> _parse_revision_str('date:2005-04-12 12:24:33')
@@ -68,27 +68,23 @@
>>> _parse_revision_str('date:2005-04-12,12:24:33')
[<RevisionSpec_date date:2005-04-12,12:24:33>]
>>> _parse_revision_str('-5..23')
- [<RevisionSpec_revno -5>, <RevisionSpec_revno 23>]
+ [<RevisionSpec_dwim -5>, <RevisionSpec_dwim 23>]
>>> _parse_revision_str('-5')
- [<RevisionSpec_revno -5>]
+ [<RevisionSpec_dwim -5>]
>>> _parse_revision_str('123a')
- Traceback (most recent call last):
- ...
- NoSuchRevisionSpec: No namespace registered for string: '123a'
+ [<RevisionSpec_dwim 123a>]
>>> _parse_revision_str('abc')
- Traceback (most recent call last):
- ...
- NoSuchRevisionSpec: No namespace registered for string: 'abc'
+ [<RevisionSpec_dwim abc>]
>>> _parse_revision_str('branch:../branch2')
[<RevisionSpec_branch branch:../branch2>]
>>> _parse_revision_str('branch:../../branch2')
[<RevisionSpec_branch branch:../../branch2>]
>>> _parse_revision_str('branch:../../branch2..23')
- [<RevisionSpec_branch branch:../../branch2>, <RevisionSpec_revno 23>]
+ [<RevisionSpec_branch branch:../../branch2>, <RevisionSpec_dwim 23>]
>>> _parse_revision_str('branch:..\\\\branch2')
[<RevisionSpec_branch branch:..\\branch2>]
>>> _parse_revision_str('branch:..\\\\..\\\\branch2..23')
- [<RevisionSpec_branch branch:..\\..\\branch2>, <RevisionSpec_revno 23>]
+ [<RevisionSpec_branch branch:..\\..\\branch2>, <RevisionSpec_dwim 23>]
"""
# TODO: Maybe move this into revisionspec.py
revs = []
@@ -104,7 +100,7 @@
parent of the revision.
>>> _parse_change_str('123')
- (<RevisionSpec_before before:123>, <RevisionSpec_revno 123>)
+ (<RevisionSpec_before before:123>, <RevisionSpec_dwim 123>)
>>> _parse_change_str('123..124')
Traceback (most recent call last):
...
=== modified file 'bzrlib/revisionspec.py'
--- a/bzrlib/revisionspec.py 2009-07-25 08:26:42 +0000
+++ b/bzrlib/revisionspec.py 2009-10-27 14:04:29 +0000
@@ -113,8 +113,6 @@
return RevisionInfo(branch, revno, revision_id)
-# classes in this list should have a "prefix" attribute, against which
-# string specs are matched
_revno_regex = None
@@ -123,10 +121,10 @@
help_txt = """A parsed revision specification.
- A revision specification can be an integer, in which case it is
- assumed to be a revno (though this will translate negative values
- into positive ones); or it can be a string, in which case it is
- parsed for something like 'date:' or 'revid:' etc.
+ A revision specification is a string, which may be unambiguous about
+ what it represents by giving a prefix like 'date:' or 'revid:' etc,
+ or it may have no prefix, in which case it's tried against several
+ specifier types in sequence to determine what the user meant.
Revision specs are an UI element, and they have been moved out
of the branch class to leave "back-end" classes unaware of such
@@ -139,6 +137,14 @@
prefix = None
wants_revision_history = True
+ dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
+ """Exceptions that RevisionSpec_dwim._match_on will catch.
+
+ If the revspec is part of ``dwim_revspecs``, it may be tried with an
+ invalid revspec and raises some exception. The exceptions mentioned here
+ will not be reported to the user but simply ignored without stopping the
+ dwim processing.
+ """
@staticmethod
def from_string(spec):
@@ -165,17 +171,9 @@
trace.mutter('Returning RevisionSpec %s for %s',
spectype.__name__, spec)
return spectype(spec, _internal=True)
- # RevisionSpec_revno is special cased, because it is the only
- # one that directly handles plain integers
- # TODO: This should not be special cased rather it should be
- # a method invocation on spectype.canparse()
- global _revno_regex
- if _revno_regex is None:
- _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
- if _revno_regex.match(spec) is not None:
- return RevisionSpec_revno(spec, _internal=True)
-
- raise errors.NoSuchRevisionSpec(spec)
+ # Otherwise treat it as a DWIM, build the RevisionSpec object and
+ # wait for _match_on to be called.
+ return RevisionSpec_dwim(spec, _internal=True)
def __init__(self, spec, _internal=False):
"""Create a RevisionSpec referring to the Null revision.
@@ -290,16 +288,62 @@
# private API
+class RevisionSpec_dwim(RevisionSpec):
+ """Provides a DWIMish revision specifier lookup.
+
+ Note that this does not go in the revspec_registry because by definition
+ there is no prefix to identify it. It's solely called from
+ RevisionSpec.from_string() because the DWIMification happen when _match_on
+ is called so the string describing the revision is kept here until needed.
+ """
+
+ help_txt = None
+ # We don't need to build the revision history ourself, that's delegated to
+ # each revspec we try.
+ wants_revision_history = False
+
+ def _try_spectype(self, rstype, branch):
+ rs = rstype(self.spec, _internal=True)
+ # Hit in_history to find out if it exists, or we need to try the
+ # next type.
+ return rs.in_history(branch)
+
+ def _match_on(self, branch, revs):
+ """Run the lookup and see what we can get."""
+
+ # First, see if it's a revno
+ global _revno_regex
+ if _revno_regex is None:
+ _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
+ if _revno_regex.match(self.spec) is not None:
+ try:
+ return self._try_spectype(RevisionSpec_revno, branch)
+ except RevisionSpec_revno.dwim_catchable_exceptions:
+ pass
+
+ # Next see what has been registered
+ for rs_class in dwim_revspecs:
+ try:
+ return self._try_spectype(rs_class, branch)
+ except rs_class.dwim_catchable_exceptions:
+ pass
+
+ # Well, I dunno what it is. Note that we don't try to keep track of the
+ # first of last exception raised during the DWIM tries as none seems
+ # really relevant.
+ raise errors.InvalidRevisionSpec(self.spec, branch)
+
+
class RevisionSpec_revno(RevisionSpec):
"""Selects a revision using a number."""
help_txt = """Selects a revision using a number.
Use an integer to specify a revision in the history of the branch.
- Optionally a branch can be specified. The 'revno:' prefix is optional.
- A negative number will count from the end of the branch (-1 is the
- last revision, -2 the previous one). If the negative number is larger
- than the branch's history, the first revision is returned.
+ Optionally a branch can be specified. A negative number will count
+ from the end of the branch (-1 is the last revision, -2 the previous
+ one). If the negative number is larger than the branch's history, the
+ first revision is returned.
Examples::
revno:1 -> return the first revision of this branch
@@ -561,6 +605,7 @@
"""
prefix = 'tag:'
+ dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
def _match_on(self, branch, revs):
# Can raise tags not supported, NoSuchTag, etc
@@ -760,6 +805,7 @@
branch:/path/to/branch
"""
prefix = 'branch:'
+ dwim_catchable_exceptions = (errors.NotBranchError,)
def _match_on(self, branch, revs):
from bzrlib.branch import Branch
@@ -838,6 +884,17 @@
self._get_submit_location(context_branch))
+# The order in which we want to DWIM a revision spec without any prefix.
+# revno is always tried first and isn't listed here, this is used by
+# RevisionSpec_dwim._match_on
+dwim_revspecs = [
+ RevisionSpec_tag, # Let's try for a tag
+ RevisionSpec_revid, # Maybe it's a revid?
+ RevisionSpec_date, # Perhaps a date?
+ RevisionSpec_branch, # OK, last try, maybe it's a branch
+ ]
+
+
revspec_registry = registry.Registry()
def _register_revspec(revspec):
revspec_registry.register(revspec.prefix, revspec)
@@ -852,5 +909,7 @@
_register_revspec(RevisionSpec_branch)
_register_revspec(RevisionSpec_submit)
+# classes in this list should have a "prefix" attribute, against which
+# string specs are matched
SPEC_TYPES = symbol_versioning.deprecated_list(
symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])
=== modified file 'bzrlib/tests/test_revisionspec.py'
--- a/bzrlib/tests/test_revisionspec.py 2009-03-23 14:59:43 +0000
+++ b/bzrlib/tests/test_revisionspec.py 2009-10-10 05:07:35 +0000
@@ -146,24 +146,49 @@
def test_object(self):
self.assertRaises(TypeError, RevisionSpec.from_string, object())
- def test_unregistered_spec(self):
- self.assertRaises(errors.NoSuchRevisionSpec,
- RevisionSpec.from_string, 'foo')
- self.assertRaises(errors.NoSuchRevisionSpec,
- RevisionSpec.from_string, '123a')
-
-
-
-class TestRevnoFromString(TestCase):
-
- def test_from_string_dotted_decimal(self):
- self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '-1.1')
- self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '.1')
- self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1..1')
- self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.2..1')
- self.assertRaises(errors.NoSuchRevisionSpec, RevisionSpec.from_string, '1.')
- self.assertIsInstance(RevisionSpec.from_string('1.1'), RevisionSpec_revno)
- self.assertIsInstance(RevisionSpec.from_string('1.1.3'), RevisionSpec_revno)
+
+class TestRevisionSpec_dwim(TestRevisionSpec):
+
+ # Don't need to test revno's explicitly since TRS_revno already
+ # covers that well for us
+ def test_dwim_spec_revno(self):
+ self.assertInHistoryIs(2, 'r2', '2')
+ self.assertAsRevisionId('alt_r2', '1.1.1')
+
+ def test_dwim_spec_revid(self):
+ self.assertInHistoryIs(2, 'r2', 'r2')
+
+ def test_dwim_spec_tag(self):
+ self.tree.branch.tags.set_tag('footag', 'r1')
+ self.assertAsRevisionId('r1', 'footag')
+ self.tree.branch.tags.delete_tag('footag')
+ self.assertRaises(errors.InvalidRevisionSpec,
+ self.get_in_history, 'footag')
+
+ def test_dwim_spec_tag_that_looks_like_revno(self):
+ # Test that we slip past revno with things that look like revnos,
+ # but aren't. Tags are convenient for testing this since we can
+ # make them look however we want.
+ self.tree.branch.tags.set_tag('3', 'r2')
+ self.assertAsRevisionId('r2', '3')
+ self.build_tree(['tree/b'])
+ self.tree.add(['b'])
+ self.tree.commit('b', rev_id='r3')
+ self.assertAsRevisionId('r3', '3')
+
+ def test_dwim_spec_date(self):
+ self.assertAsRevisionId('r1', 'today')
+
+ def test_dwim_spec_branch(self):
+ self.assertInHistoryIs(None, 'alt_r2', 'tree2')
+
+ def test_dwim_spec_nonexistent(self):
+ self.assertInvalid('somethingrandom', invalid_as_revision_id=False)
+ self.assertInvalid('-1.1', invalid_as_revision_id=False)
+ self.assertInvalid('.1', invalid_as_revision_id=False)
+ self.assertInvalid('1..1', invalid_as_revision_id=False)
+ self.assertInvalid('1.2..1', invalid_as_revision_id=False)
+ self.assertInvalid('1.', invalid_as_revision_id=False)
class TestRevisionSpec_revno(TestRevisionSpec):
=== modified file 'doc/en/user-guide/specifying_revisions.txt'
--- a/doc/en/user-guide/specifying_revisions.txt 2009-04-04 02:57:47 +0000
+++ b/doc/en/user-guide/specifying_revisions.txt 2009-07-25 15:21:25 +0000
@@ -36,17 +36,23 @@
+----------------------+------------------------------------+
| *number* | revision number |
+----------------------+------------------------------------+
- | **revno**:*number* | positive revision number |
+ | **revno**:*number* | revision number |
+----------------------+------------------------------------+
| **last**:*number* | negative revision number |
+----------------------+------------------------------------+
+ | *guid* | globally unique revision id |
+ +----------------------+------------------------------------+
| **revid**:*guid* | globally unique revision id |
+----------------------+------------------------------------+
| **before**:*rev* | leftmost parent of ''rev'' |
+----------------------+------------------------------------+
- | **date**:*value* | first entry after a given date |
- +----------------------+------------------------------------+
- | **tag**:*value* | revision matching a given tag |
+ | *date-value* | first entry after a given date |
+ +----------------------+------------------------------------+
+ | **date**:*date-value*| first entry after a given date |
+ +----------------------+------------------------------------+
+ | *tag-name* | revision matching a given tag |
+ +----------------------+------------------------------------+
+ | **tag**:*tag-name* | revision matching a given tag |
+----------------------+------------------------------------+
| **ancestor**:*path* | last merged revision from a branch |
+----------------------+------------------------------------+
More information about the bazaar-commits
mailing list