D125: phabricator: add a small language to query Differential Revisions
quark updated this revision to Diff 487. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=237&id=487 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsolete, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -434,11 +438,77 @@ _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'), (r'node', 'Node ID'), (r'parent', 'Parent ')]) -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -475,51 +545,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetched[key] -visited =
D125: phabricator: add a small language to query Differential Revisions
quark updated this revision to Diff 555. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=487&id=555 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsolete, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -451,11 +455,77 @@ _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'), (r'node', 'Node ID'), (r'parent', 'Parent ')]) -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -492,51 +562,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetched[key] -visited =
D125: phabricator: add a small language to query Differential Revisions
quark updated this revision to Diff 570. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=555&id=570 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsolete, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -485,11 +489,77 @@ return True -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -526,51 +596,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetched[key] -visited = set() -result = [] -queue = [params] -while queue: -params = queue.pop() -drev = fetch(params) -
D125: phabricator: add a small language to query Differential Revisions
quark updated this revision to Diff 743. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=570&id=743 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsutil, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -485,11 +489,77 @@ return True -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -526,51 +596,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetched[key] -visited = set() -result = [] -queue = [params] -while queue: -params = queue.pop() -drev = fetch(params) -
D125: phabricator: add a small language to query Differential Revisions
quark updated this revision to Diff 840. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=743&id=840 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsutil, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -484,11 +488,77 @@ return True -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -525,51 +595,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetched[key] -visited = set() -result = [] -queue = [params] -while queue: -params = queue.pop() -drev = fetch(params) -
D125: phabricator: add a small language to query Differential Revisions
This revision was automatically updated to reflect the committed changes. Closed by commit rHG539541779010: phabricator: add a small language to query Differential Revisions (authored by quark). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D125?vs=840&id=961 REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsutil, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -484,11 +488,77 @@ return True -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -525,51 +595,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: return prefetched[key] -# Otherwise, send the request. If we're fetching a stack, be smarter -# and fetch more ids in one batch, even if it could be unnecessary. -batchparams = params -if stack and len(params.get(r'ids', [])) == 1: -i = int(params[r'ids'][0]) -# developer config: phabricator.batchsize -batchsize = repo.ui.configint('phabricator', 'batchsize', 12) -batchparams = {'ids': range(max(1, i - batchsize), i + 1)} -drevs = callconduit(repo, 'differential.query', batchparams) +drevs = callconduit(repo, 'differential.query', params) # Fill prefetched with the result for drev in drevs: prefetched[drev[r'phid']] = drev prefetched[int(drev[r'id'])] = drev if key not in prefetched: raise error.Abort(_('cannot get Differential Revision %r') % params) return prefetc
Re: D125: phabricator: add a small language to query Differential Revisions
quark created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Previously, `phabread` can only be used to read a single patch, or a single stack of patches. In the future, we want to have more complex queries like filtering with status (open, accepted, closed, etc), or maybe more complex like filtering by reviewers etc. The command line flag approach won't scale with that. Besides, we might want to have other commands to update Differential Revision status in batch, like accepting a stack using a single command. Therefore, this patch adds a small language. It has basic set operations: `&`, `+`, `-` and an ancestor operator to support `--stack`. TEST PLAN Try querying this Phabricator instance: hg phabread 1+2 # 1, 2 hg phabread D2+D1 # 2, 1 hg phabread ':118-115+:2-1' # 114, 116, 117, 118, 2 hg phabread '((:118-(D115+117)))&:117' # 114, 116 hg phabread ':2&:117' --debug # differential.query is called only once Make sure the output is expected and prefetch works. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D125 AFFECTED FILES contrib/phabricator.py CHANGE DETAILS diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -32,7 +32,9 @@ from __future__ import absolute_import +import itertools import json +import operator import re from mercurial.node import bin, nullid @@ -44,9 +46,11 @@ error, mdiff, obsolete, +parser, patch, registrar, scmutil, +smartset, tags, url as urlmod, util, @@ -435,11 +439,77 @@ _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'), (r'node', 'Node ID'), (r'parent', 'Parent ')]) -def querydrev(repo, params, stack=False): +# Small language to specify differential revisions. Support symbols: (), :X, +# +, and -. + +_elements = { +# token-type: binding-strength, primary, prefix, infix, suffix +'(': (12, None, ('group', 1, ')'), None, None), +':': (8, None, ('ancestors', 8), None, None), +'&': (5, None, None, ('and_', 5), None), +'+': (4, None, None, ('add', 4), None), +'-': (4, None, None, ('sub', 4), None), +')': (0, None, None, None, None), +'symbol': (0, 'symbol', None, None, None), +'end':(0, None, None, None, None), +} + +def _tokenize(text): +text = text.replace(' ', '') # remove space +view = memoryview(text) # zero-copy slice +special = '():+-&' +pos = 0 +length = len(text) +while pos < length: +symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, + view[pos:])) +if symbol: +yield ('symbol', symbol, pos) +pos += len(symbol) +else: # special char +yield (text[pos], None, pos) +pos += 1 +yield ('end', None, pos) + +def _parse(text): +tree, pos = parser.parser(_elements).parse(_tokenize(text)) +if pos != len(text): +raise error.ParseError('invalid token', pos) +return tree + +def _parsedrev(symbol): +"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" +if symbol.startswith('D') and symbol[1:].isdigit(): +return int(symbol[1:]) +if symbol.isdigit(): +return int(symbol) + +def _prefetchdrevs(tree): +"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" +drevs = set() +ancestordrevs = set() +op = tree[0] +if op == 'symbol': +r = _parsedrev(tree[1]) +if r: +drevs.add(r) +elif op == 'ancestors': +r, a = _prefetchdrevs(tree[1]) +drevs.update(r) +ancestordrevs.update(r) +ancestordrevs.update(a) +else: +for t in tree[1:]: +r, a = _prefetchdrevs(t) +drevs.update(r) +ancestordrevs.update(a) +return drevs, ancestordrevs + +def querydrev(repo, spec): """return a list of "Differential Revision" dicts -params is the input of "differential.query" API, and is expected to match -just a single Differential Revision. +spec is a string using a simple query language, see docstring in phabread +for details. A "Differential Revision dict" looks like: @@ -476,51 +546,77 @@ "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", "sourcePath": null } - -If stack is True, return a list of "Differential Revision dict"s in an -order that the latter ones depend on the former ones. Otherwise, return a -list of a unique "Differential Revision dict". """ -prefetched = {} # {id or phid: drev} def fetch(params): """params -> single drev or None""" key = (params.get(r'ids') or params.get(r'phids') or [None])[0] if key in prefetched: