Patchwork [resend] revset: use '%' as an operator for 'only'

login
register
mail settings
Submitter Sean Farley
Date Jan. 7, 2015, 11:06 p.m.
Message ID <d4ab4ecbea8dfc6ae0f7.1420672013@laptop.local>
Download mbox | patch
Permalink /patch/7366/
State Superseded
Headers show

Comments

Sean Farley - Jan. 7, 2015, 11:06 p.m.
# HG changeset patch
# User Sean Farley <sean.michael.farley@gmail.com>
# Date 1415314518 28800
#      Thu Nov 06 14:55:18 2014 -0800
# Node ID d4ab4ecbea8dfc6ae0f77a0f46f1da8c197b9d18
# Parent  f82173a90c2c9d0d32216fe7243ec51fc6d44ff7
revset: use '%' as an operator for 'only'

With this patch, we can make it much easier to specify 'only(A,B)' ->
A%B. Similarly, 'only(A)' -> A%.

On Windows, '%' is a semi-reserved symbol in the following way: using non-bash
shells (e.g. cmd.exe but NOT PowerShell, ConEmu, and cmder), %var% is only
expanded when 'var' exists and is surrounded by '%'.

That only leaves batch scripts which could prove to be problematic. I posit
that this isn't a big issue because any developer of batch scripts already
knows that to use '%' one needs to escape it by using a double '%%'.

Alternatives to '%' could be '=' but that might be limiting our future if we
ever decide to use temporary assignments in a revset.
Matt Mackall - Jan. 7, 2015, 11:37 p.m.
On Wed, 2015-01-07 at 15:06 -0800, Sean Farley wrote:
> # HG changeset patch
> # User Sean Farley <sean.michael.farley@gmail.com>
> # Date 1415314518 28800
> #      Thu Nov 06 14:55:18 2014 -0800
> # Node ID d4ab4ecbea8dfc6ae0f77a0f46f1da8c197b9d18
> # Parent  f82173a90c2c9d0d32216fe7243ec51fc6d44ff7
> revset: use '%' as an operator for 'only'

This has some issues with the optimizer that we discussed on IRC.
Dropping for now.

Patch

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -114,10 +114,11 @@  elements = {
     ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
     "not": (10, ("not", 10)),
     "!": (10, ("not", 10)),
     "and": (5, None, ("and", 5)),
     "&": (5, None, ("and", 5)),
+    "%": (5, None, ("only", 5), ("onlypost", 5)),
     "or": (4, None, ("or", 4)),
     "|": (4, None, ("or", 4)),
     "+": (4, None, ("or", 4)),
     ",": (2, None, ("list", 2)),
     ")": (0, None, None),
@@ -150,11 +151,11 @@  def tokenize(program, lookup=None):
             yield ('..', None, pos)
             pos += 1 # skip ahead
         elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
             yield ('##', None, pos)
             pos += 1 # skip ahead
-        elif c in "():,-|&+!~^": # handle simple operators
+        elif c in "():,-|&+!~^%": # handle simple operators
             yield (c, None, pos)
         elif (c in '"\'' or c == 'r' and
               program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
             if c == 'r':
                 pos += 1
@@ -1198,17 +1199,21 @@  def obsolete(repo, subset, x):
     # i18n: "obsolete" is a keyword
     getargs(x, 0, 0, _("obsolete takes no arguments"))
     obsoletes = obsmod.getrevs(repo, 'obsolete')
     return subset & obsoletes
 
-def only(repo, subset, x):
+def only(repo, subset, x, y=None):
     """``only(set, [set])``
     Changesets that are ancestors of the first set that are not ancestors
     of any other head in the repo. If a second set is specified, the result
     is ancestors of the first set that are not ancestors of the second set
     (i.e. ::<set1> - ::<set2>).
     """
+    # if y is not none then we need to combine the operators into a list
+    if y is not None:
+        x = ('list', x, y)
+
     cl = repo.changelog
     # i18n: "only" is a keyword
     args = getargs(x, 1, 2, _('only takes one or two arguments'))
     include = getset(repo, spanset(repo), args[0])
     if len(args) == 1:
@@ -1920,10 +1925,12 @@  methods = {
     "list": listset,
     "func": func,
     "ancestor": ancestorspec,
     "parent": parentspec,
     "parentpost": p1,
+    "only": only,
+    "onlypost": only,
 }
 
 def optimize(x, small):
     if x is None:
         return 0, x
@@ -1946,11 +1953,11 @@  def optimize(x, small):
     elif op == 'negate':
         return optimize(('string',
                          '-' + getstring(x[1], _("can't negate that"))), small)
     elif op in 'string symbol negate':
         return smallbonus, x # single revisions are small
-    elif op == 'and':
+    elif op in 'and only':
         wa, ta = optimize(x[1], True)
         wb, tb = optimize(x[2], True)
 
         # (::x and not ::y)/(not ::y and ::x) have a fast path
         def isonly(revs, bases):
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -436,10 +436,36 @@  Test empty set input
   2
   4
   8
   9
 
+Test '%' operator
+
+  $ log '9%'
+  8
+  9
+  $ log '9%5'
+  2
+  4
+  8
+  9
+  $ log '(7 + 9)%(5 + 2)'
+  4
+  6
+  7
+  8
+  9
+
+Test the order of operations
+
+  $ log '7 + 9%5 + 2'
+  7
+  2
+  4
+  8
+  9
+
 Test explicit numeric revision
   $ log 'rev(-1)'
   $ log 'rev(0)'
   0
   $ log 'rev(9)'