[MERGE] DWIM revspecs

Matthew D. Fuller fullermd at over-yonder.net
Sat Jul 25 16:41:11 BST 2009


[ This is not actually ready for merging; BB tag added just for
  tracking ]

This bundle adds the ability for revspec's to DWIM for the more common
types, rather than requiring extra verbosity for everything but
revnos.

The lookup order is:
1) If numeric (same test as previously), try revno
2) Tag
3) Revid
4) Date
5) Branch
6) Fail!

I decided on the lookup order both in terms of relative commonality
(and thus a good guess at to likelihood of what the user wanted, if
multiple hits were possible), and in terms of relative expense of
calculation.  Revno is no more expensive than before (well, maybe a
funciton call or two more overhead, but...).  Tags are cheap to fail;
if it's not in the file, it ain't there.  Not 'till revid do we have
to pull the whole history to necessarily fail.

  Possible performance point: we may end up with multiple
  branch.revision_history() calls from RevisionSpec.in_history()
  because of the checking over options.  We may want to create a
  global to cache that across calls, though maybe b.r_h() does that
  itself?  I don't know, and I'm not quite sure if there would be side
  effects, so I left that alone.


The changes to the User Guide are untested, because I don't have the
tools to build the docs.  They're probably not as good as they should
be; somebody with the markup etc swapped into their brains could
probably clean that up a bit.  There may be better wording for the
online docs too.


One additional reason merging it would fail would be that there are
some test failures I didn't clean up.  In the RevisionSpec tests, the
TestOddRevisionSpec.test_unregistered_spec() and
TestRevnoFromString.test_from_string_dotted_decimal() currently fail
because the errors thrown are different.

I think the Rightest Answer is probably just to remove those bits of
testing.  The reason is because they think they're testing bad
revno's, but with this change what they're ACTUALLY testing now is
something we can't find among the DWIM choices (which I'm already
testing in the TRS_dwim tests).  I'm not quite certain of the right
thing to do, though, so I'd like some second opinions.  It may be that
I should instead be throwing a different error when we fall off the
end of the DWIM tests.


-- 
Matthew Fuller     (MF4839)   |  fullermd at over-yonder.net
Systems/Network Administrator |  http://www.over-yonder.net/~fullermd/
           On the Internet, nobody can hear you scream.
-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: fullermd at over-yonder.net-20090725153625-\
#   ypua3hcon5a2e44a
# target_branch: file:///home/fullermd/src/bzr/bzr.dev/
# testament_sha1: 87fedd5f77c487ec0b45e5188eac8082f81e179c
# timestamp: 2009-07-25 10:36:58 -0500
# base_revision_id: pqm at pqm.ubuntu.com-20090725034012-2fnpljnq7uvk9ss2
# 
# Begin patch
=== modified file 'bzrlib/help_topics/__init__.py'
--- bzrlib/help_topics/__init__.py	2009-06-19 09:06:56 +0000
+++ bzrlib/help_topics/__init__.py	2009-07-25 15:14:03 +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/revisionspec.py'
--- bzrlib/revisionspec.py	2009-03-23 14:59:43 +0000
+++ bzrlib/revisionspec.py	2009-07-25 15:14:03 +0000
@@ -123,10 +123,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
@@ -165,17 +165,8 @@
                              spectype.__name__, spec)
                 if spec.startswith(spectype.prefix):
                     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
+            return RevisionSpec_dwim(spec, _internal=True)
 
     def __init__(self, spec, _internal=False):
         """Create a RevisionSpec referring to the Null revision.
@@ -291,16 +282,79 @@
 
 # private API
 
+class RevisionSpec_dwim(RevisionSpec):
+    """Provides a DWIMish revision specifier lookup.
+
+    Note that this does not go in the revspec_registry.  It's solely
+    called from RevisionSpec.from_string().
+    """
+
+    help_txt = None
+    # Default to False to save building the history in the revno case
+    wants_revision_history = False
+
+    # Util
+    def __try_spectype(self, rstype, spec, branch):
+        rs = rstype(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."""
+        spec = self.spec
+
+        # 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(spec) is not None:
+            try:
+                return self.__try_spectype(RevisionSpec_revno, spec, branch)
+            except errors.InvalidRevisionSpec:
+                pass
+
+        # It's not a revno, so now we need this
+        self.wants_revision_history = True
+
+        # OK, next let's try for a tag
+        try:
+            return self.__try_spectype(RevisionSpec_tag, spec, branch)
+        except (errors.NoSuchTag, errors.TagsNotSupported):
+            pass
+
+        # Maybe it's a revid?
+        try:
+            return self.__try_spectype(RevisionSpec_revid, spec, branch)
+        except errors.InvalidRevisionSpec:
+            pass
+
+        # Perhaps a date?
+        try:
+            return self.__try_spectype(RevisionSpec_date, spec, branch)
+        except errors.InvalidRevisionSpec:
+            pass
+
+        # OK, last try, maybe it's a branch
+        try:
+            return self.__try_spectype(RevisionSpec_branch, spec, branch)
+        except errors.NotBranchError:
+            pass
+
+        # Well, I dunno what it is.
+        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

=== modified file 'bzrlib/tests/test_revisionspec.py'
--- bzrlib/tests/test_revisionspec.py	2009-03-23 14:59:43 +0000
+++ bzrlib/tests/test_revisionspec.py	2009-07-25 15:36:25 +0000
@@ -153,6 +153,30 @@
                           RevisionSpec.from_string, '123a')
 
 
+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_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_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)
+
 
 class TestRevnoFromString(TestCase):
 

=== modified file 'doc/en/user-guide/specifying_revisions.txt'
--- doc/en/user-guide/specifying_revisions.txt	2009-04-04 02:57:47 +0000
+++ 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 |
  +----------------------+------------------------------------+

# Begin bundle
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


More information about the bazaar mailing list