pushlog: expose pushlog revsets to hgweb (bug 1337946); r?glob draft
authorGregory Szorc <gps@mozilla.com>
Thu, 08 Jun 2017 18:39:25 -0700
changeset 11245 96cf69fe524aecfc95a093fabcd1c784d4f686d5
parent 11244 c23d48384dbaf1604c6cbe59290b6fe510067622
child 11246 9e9c834f20d75c458f735f2592c9b872d3edb8f4
push id1709
push usergszorc@mozilla.com
push dateMon, 19 Jun 2017 18:45:20 +0000
reviewersglob
bugs1337946
pushlog: expose pushlog revsets to hgweb (bug 1337946); r?glob Revsets must explicitly opt in to being exposed on hgweb for performance/security reasons. When we implemented the pushlog revsets, I'm pretty sure I just forgot to enable (at least some of) them on hgweb. Some of the pushlog revsets are a bit expensive to compute. pushhead(), pushdate(), and pushuser() each query the full database then iterate on results to do filtering. For a repo like mozilla-central which has ~32,000 pushes, `hg perfrevset` says this takes ~1.11s on my i7-6700k. Other built-in and enabled revsets are already more expensive. So I think this is tolerable. MozReview-Commit-ID: FKLEk4OlQnB
hgext/pushlog/__init__.py
hgext/pushlog/tests/test-hgweb.t
--- a/hgext/pushlog/__init__.py
+++ b/hgext/pushlog/__init__.py
@@ -618,17 +618,18 @@ def pretxnchangegrouphook(ui, repo, node
         repo.pushlog.recordpush(revs, pushuser, t)
         ui.write('recorded push in pushlog\n')
         return 0
     except Exception:
         ui.write('error recording into pushlog; please retry your push\n')
 
     return 1
 
-@revsetpredicate('pushhead()')
+
+@revsetpredicate('pushhead()', safe=True)
 def revset_pushhead(repo, subset, x):
     """Changesets that were heads when they were pushed.
 
     A push head is a changeset that was a head at the time it was pushed.
     """
     revset.getargs(x, 0, 0, 'pushhead takes no arguments')
 
     # Iterating over all pushlog data is unfortunate, as there is overhead
@@ -637,33 +638,35 @@ def revset_pushhead(repo, subset, x):
     # this optimal by batching SQL, but that adds complexity. For now,
     # simplicity wins.
     def getrevs():
         for push in repo.pushlog.pushes():
             yield repo[push.nodes[-1]].rev()
 
     return subset & revset.generatorset(getrevs())
 
-@revsetpredicate('pushdate(interval)')
+
+@revsetpredicate('pushdate(interval)', safe=True)
 def revset_pushdate(repo, subset, x):
     """Changesets that were pushed within the interval, see :hg:`help dates`."""
     l = revset.getargs(x, 1, 1, 'pushdate requires one argument')
 
     ds = revset.getstring(l[0], 'pushdate requires a string argument')
     dm = util.matchdate(ds)
 
     def getrevs():
         for push in repo.pushlog.pushes():
             if dm(push.when):
                 for node in push.nodes:
                     yield repo[node].rev()
 
     return subset & revset.generatorset(getrevs())
 
-@revsetpredicate('pushuser(string)')
+
+@revsetpredicate('pushuser(string)', safe=True)
 def revset_pushuser(repo, subset, x):
     """User name that pushed the changeset contains string.
 
     The match is case-insensitive.
 
     If `string` starts with `re:`, the remainder of the string is treated as
     a regular expression. To match a user that actually contains `re:`, use
     the prefix `literal:`.
@@ -675,17 +678,18 @@ def revset_pushuser(repo, subset, x):
     def getrevs():
         for push in repo.pushlog.pushes():
             if matcher(encoding.lower(push.user)):
                 for node in push.nodes:
                     yield repo[node].rev()
 
     return subset & revset.generatorset(getrevs())
 
-@revsetpredicate('pushid(int)')
+
+@revsetpredicate('pushid(int)', safe=True)
 def revset_pushid(repo, subset, x):
     """Changesets that were part of the specified numeric push id."""
     l = revset.getargs(x, 1, 1, 'pushid requires one argument')
     try:
         pushid = int(revset.getstring(l[0], 'pushid requires a number'))
     except (TypeError, ValueError):
         raise error.ParseError('pushid expects a number')
 
@@ -699,17 +703,18 @@ def revset_pushid(repo, subset, x):
     for node in push.nodes:
         try:
             pushrevs.add(repo[node].rev())
         except RepoLookupError:
             pass
 
     return subset & pushrevs
 
-@revsetpredicate('pushrev(set)')
+
+@revsetpredicate('pushrev(set)', safe=True)
 def revset_pushrev(repo, subset, x):
     """Changesets that were part of the same push as the specified changeset(s)."""
     l = revset.getset(repo, subset, x)
 
     # This isn't the most optimal implementation, especially if the input
     # set is large. But it gets the job done.
     revs = set()
     for rev in l:
--- a/hgext/pushlog/tests/test-hgweb.t
+++ b/hgext/pushlog/tests/test-hgweb.t
@@ -50,8 +50,258 @@ Push info should show up in changeset vi
   <tr><td>push date</td><td>*</td></tr> (glob)
 
   $ http http://localhost:$HGPORT/log --body-file body > /dev/null
   $ grep push body
   <a href="/pushloghtml">pushlog</a> |
   Push <a href="/pushloghtml?changeset=82f53df85e9f">2</a> by user2@example.com at *<br /> (glob)
   Push <a href="/pushloghtml?changeset=6c9721b3b4df">2</a> by user2@example.com at *<br /> (glob)
   Push <a href="/pushloghtml?changeset=55482a6fb4b1">1</a> by user1@example.com at *<br /> (glob)
+
+pushhead() works in search
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushhead()" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "third",
+              "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+              "parents": [
+                  "6c9721b3b4dfc8c1f2d3103595e8bb2ffe5b8ff2"
+              ],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 2,
+              "tags": [
+                  "tip"
+              ],
+              "user": "test"
+          },
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "initial",
+              "node": "55482a6fb4b1881fa8f746fd52cf6f096bb21c89",
+              "parents": [],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 1,
+              "tags": [],
+              "user": "test"
+          }
+      ],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushhead()"
+  }
+
+pushdate() works in search
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushdate('>2017')" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "third",
+              "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+              "parents": [
+                  "6c9721b3b4dfc8c1f2d3103595e8bb2ffe5b8ff2"
+              ],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 2,
+              "tags": [
+                  "tip"
+              ],
+              "user": "test"
+          },
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "second",
+              "node": "6c9721b3b4dfc8c1f2d3103595e8bb2ffe5b8ff2",
+              "parents": [
+                  "55482a6fb4b1881fa8f746fd52cf6f096bb21c89"
+              ],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 2,
+              "tags": [],
+              "user": "test"
+          },
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "initial",
+              "node": "55482a6fb4b1881fa8f746fd52cf6f096bb21c89",
+              "parents": [],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 1,
+              "tags": [],
+              "user": "test"
+          }
+      ],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushdate('>2017')"
+  }
+
+pushuser() works in search
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushuser(user1)" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "initial",
+              "node": "55482a6fb4b1881fa8f746fd52cf6f096bb21c89",
+              "parents": [],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 1,
+              "tags": [],
+              "user": "test"
+          }
+      ],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushuser(user1)"
+  }
+
+pushid() works in search
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushid(1)" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "initial",
+              "node": "55482a6fb4b1881fa8f746fd52cf6f096bb21c89",
+              "parents": [],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 1,
+              "tags": [],
+              "user": "test"
+          }
+      ],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushid(1)"
+  }
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushid(3)" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushid(3)"
+  }
+
+pushrev() works in search
+
+  $ http "http://localhost:$HGPORT/json-log?rev=pushrev(1)" --body-file body > /dev/null
+  $ python -m json.tool < body
+  {
+      "entries": [
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "third",
+              "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+              "parents": [
+                  "6c9721b3b4dfc8c1f2d3103595e8bb2ffe5b8ff2"
+              ],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 2,
+              "tags": [
+                  "tip"
+              ],
+              "user": "test"
+          },
+          {
+              "bookmarks": [],
+              "branch": "default",
+              "date": [
+                  0.0,
+                  0
+              ],
+              "desc": "second",
+              "node": "6c9721b3b4dfc8c1f2d3103595e8bb2ffe5b8ff2",
+              "parents": [
+                  "55482a6fb4b1881fa8f746fd52cf6f096bb21c89"
+              ],
+              "phase": "public",
+              "pushdate": [
+                  \d+, (re)
+                  0
+              ],
+              "pushid": 2,
+              "tags": [],
+              "user": "test"
+          }
+      ],
+      "node": "82f53df85e9f23d81dbcfbf7debf9900cdc1e2ce",
+      "query": "pushrev(1)"
+  }