Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/revset.py @ 16771:2f3317d53d51 stable
revset: explicitely tag alias arguments for expansion
The current revset alias expansion code works like:
1- Get the replacement tree
2- Substitute the variables in the replacement tree
3- Expand the replacement tree
It makes it easy to substitute alias arguments because the placeholders
are always replaced before the updated replacement tree is expanded
again. Unfortunately, to fix other alias expansion issues, we need to
reorder the sequence and delay the argument substitution. To solve this,
a new "virtual" construct called _aliasarg() is introduced and injected
when parsing the aliases definitions. Only _aliasarg() will be
substituted in the argument expansion phase instead of all regular
matching string. We also check user inputs do not contain unexpected
_aliasarg() instances to avoid argument injections.
author | Patrick Mezard <patrick@mezard.eu> |
---|---|
date | Sat, 19 May 2012 17:18:29 +0200 |
parents | 0a730d3c5aae |
children | 30e46d7138de |
comparison
equal
deleted
inserted
replaced
16768:23a125545c3d | 16771:2f3317d53d51 |
---|---|
1281 else: | 1281 else: |
1282 w = 1 | 1282 w = 1 |
1283 return w + wa, (op, x[1], ta) | 1283 return w + wa, (op, x[1], ta) |
1284 return 1, x | 1284 return 1, x |
1285 | 1285 |
1286 _aliasarg = ('func', ('symbol', '_aliasarg')) | |
1287 def _getaliasarg(tree): | |
1288 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X)) | |
1289 return X, None otherwise. | |
1290 """ | |
1291 if (len(tree) == 3 and tree[:2] == _aliasarg | |
1292 and tree[2][0] == 'string'): | |
1293 return tree[2][1] | |
1294 return None | |
1295 | |
1296 def _checkaliasarg(tree, known=None): | |
1297 """Check tree contains no _aliasarg construct or only ones which | |
1298 value is in known. Used to avoid alias placeholders injection. | |
1299 """ | |
1300 if isinstance(tree, tuple): | |
1301 arg = _getaliasarg(tree) | |
1302 if arg is not None and (not known or arg not in known): | |
1303 raise error.ParseError(_("not a function: %s") % '_aliasarg') | |
1304 for t in tree: | |
1305 _checkaliasarg(t, known) | |
1306 | |
1286 class revsetalias(object): | 1307 class revsetalias(object): |
1287 funcre = re.compile('^([^(]+)\(([^)]+)\)$') | 1308 funcre = re.compile('^([^(]+)\(([^)]+)\)$') |
1288 args = None | 1309 args = None |
1289 | 1310 |
1290 def __init__(self, name, value): | 1311 def __init__(self, name, value): |
1297 if m: | 1318 if m: |
1298 self.name = m.group(1) | 1319 self.name = m.group(1) |
1299 self.tree = ('func', ('symbol', m.group(1))) | 1320 self.tree = ('func', ('symbol', m.group(1))) |
1300 self.args = [x.strip() for x in m.group(2).split(',')] | 1321 self.args = [x.strip() for x in m.group(2).split(',')] |
1301 for arg in self.args: | 1322 for arg in self.args: |
1302 value = value.replace(arg, repr(arg)) | 1323 # _aliasarg() is an unknown symbol only used separate |
1324 # alias argument placeholders from regular strings. | |
1325 value = value.replace(arg, '_aliasarg(%r)' % (arg,)) | |
1303 else: | 1326 else: |
1304 self.name = name | 1327 self.name = name |
1305 self.tree = ('symbol', name) | 1328 self.tree = ('symbol', name) |
1306 | 1329 |
1307 self.replacement, pos = parse(value) | 1330 self.replacement, pos = parse(value) |
1308 if pos != len(value): | 1331 if pos != len(value): |
1309 raise error.ParseError(_('invalid token'), pos) | 1332 raise error.ParseError(_('invalid token'), pos) |
1333 # Check for placeholder injection | |
1334 _checkaliasarg(self.replacement, self.args) | |
1310 | 1335 |
1311 def _getalias(aliases, tree): | 1336 def _getalias(aliases, tree): |
1312 """If tree looks like an unexpanded alias, return it. Return None | 1337 """If tree looks like an unexpanded alias, return it. Return None |
1313 otherwise. | 1338 otherwise. |
1314 """ | 1339 """ |
1325 if alias and alias.args is not None and alias.tree == tree[:2]: | 1350 if alias and alias.args is not None and alias.tree == tree[:2]: |
1326 return alias | 1351 return alias |
1327 return None | 1352 return None |
1328 | 1353 |
1329 def _expandargs(tree, args): | 1354 def _expandargs(tree, args): |
1330 """Replace all occurences of ('string', name) with the | 1355 """Replace _aliasarg instances with the substitution value of the |
1331 substitution value of the same name in args, recursively. | 1356 same name in args, recursively. |
1332 """ | 1357 """ |
1333 if not isinstance(tree, tuple): | 1358 if not tree or not isinstance(tree, tuple): |
1334 return tree | 1359 return tree |
1335 if len(tree) == 2 and tree[0] == 'string': | 1360 arg = _getaliasarg(tree) |
1336 return args.get(tree[1], tree) | 1361 if arg is not None: |
1362 return args[arg] | |
1337 return tuple(_expandargs(t, args) for t in tree) | 1363 return tuple(_expandargs(t, args) for t in tree) |
1338 | 1364 |
1339 def _expandaliases(aliases, tree, expanding): | 1365 def _expandaliases(aliases, tree, expanding): |
1340 """Expand aliases in tree, recursively. | 1366 """Expand aliases in tree, recursively. |
1341 | 1367 |
1365 result = tuple(_expandaliases(aliases, t, expanding) | 1391 result = tuple(_expandaliases(aliases, t, expanding) |
1366 for t in tree) | 1392 for t in tree) |
1367 return result | 1393 return result |
1368 | 1394 |
1369 def findaliases(ui, tree): | 1395 def findaliases(ui, tree): |
1396 _checkaliasarg(tree) | |
1370 aliases = {} | 1397 aliases = {} |
1371 for k, v in ui.configitems('revsetalias'): | 1398 for k, v in ui.configitems('revsetalias'): |
1372 alias = revsetalias(k, v) | 1399 alias = revsetalias(k, v) |
1373 aliases[alias.name] = alias | 1400 aliases[alias.name] = alias |
1374 return _expandaliases(aliases, tree, []) | 1401 return _expandaliases(aliases, tree, []) |