comparison mercurial/revset.py @ 16096:b8be450638f6

revset: fix alias substitution recursion (issue3240) The revset aliases expansion worked like: expr = "some revset" for alias in aliases: expr = alias.process(expr) where "process" was replacing the alias with its *unexpanded* substitution, recursively. So it only worked when aliases were applied in proper dependency order. This patch rewrites the expansion process so all aliases are expanded recursively at every tree level, after parent alias rewriting and variable expansion.
author Patrick Mezard <patrick@mezard.eu>
date Thu, 09 Feb 2012 21:03:07 +0100
parents f06c53ca59a9
children 5a627b49b4d9
comparison
equal deleted inserted replaced
16095:3eab42088be4 16096:b8be450638f6
1069 '''Aliases like: 1069 '''Aliases like:
1070 1070
1071 h = heads(default) 1071 h = heads(default)
1072 b($1) = ancestors($1) - ancestors(default) 1072 b($1) = ancestors($1) - ancestors(default)
1073 ''' 1073 '''
1074 if isinstance(name, tuple): # parameter substitution 1074 m = self.funcre.search(name)
1075 self.tree = name 1075 if m:
1076 self.replacement = value 1076 self.name = m.group(1)
1077 else: # alias definition 1077 self.tree = ('func', ('symbol', m.group(1)))
1078 m = self.funcre.search(name) 1078 self.args = [x.strip() for x in m.group(2).split(',')]
1079 if m: 1079 for arg in self.args:
1080 self.tree = ('func', ('symbol', m.group(1))) 1080 value = value.replace(arg, repr(arg))
1081 self.args = [x.strip() for x in m.group(2).split(',')] 1081 else:
1082 for arg in self.args: 1082 self.name = name
1083 value = value.replace(arg, repr(arg)) 1083 self.tree = ('symbol', name)
1084 else: 1084
1085 self.tree = ('symbol', name) 1085 self.replacement, pos = parse(value)
1086 1086 if pos != len(value):
1087 self.replacement, pos = parse(value) 1087 raise error.ParseError(_('invalid token'), pos)
1088 if pos != len(value): 1088
1089 raise error.ParseError(_('invalid token'), pos) 1089 def _getalias(aliases, tree):
1090 1090 """If tree looks like an unexpanded alias, return it. Return None
1091 def process(self, tree): 1091 otherwise.
1092 if isinstance(tree, tuple): 1092 """
1093 if self.args is None: 1093 if isinstance(tree, tuple) and tree:
1094 if tree == self.tree: 1094 if tree[0] == 'symbol' and len(tree) == 2:
1095 return self.replacement 1095 name = tree[1]
1096 elif tree[:2] == self.tree: 1096 alias = aliases.get(name)
1097 l = getlist(tree[2]) 1097 if alias and alias.args is None and alias.tree == tree:
1098 if len(l) != len(self.args): 1098 return alias
1099 raise error.ParseError( 1099 if tree[0] == 'func' and len(tree) > 1:
1100 _('invalid number of arguments: %s') % len(l)) 1100 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1101 result = self.replacement 1101 name = tree[1][1]
1102 for a, v in zip(self.args, l): 1102 alias = aliases.get(name)
1103 valalias = revsetalias(('string', a), v) 1103 if alias and alias.args is not None and alias.tree == tree[:2]:
1104 result = valalias.process(result) 1104 return alias
1105 return result 1105 return None
1106 return tuple(map(self.process, tree)) 1106
1107 def _expandargs(tree, args):
1108 """Replace all occurences of ('string', name) with the
1109 substitution value of the same name in args, recursively.
1110 """
1111 if not isinstance(tree, tuple):
1107 return tree 1112 return tree
1113 if len(tree) == 2 and tree[0] == 'string':
1114 return args.get(tree[1], tree)
1115 return tuple(_expandargs(t, args) for t in tree)
1116
1117 def _expandaliases(aliases, tree, expanding):
1118 """Expand aliases in tree, recursively.
1119
1120 'aliases' is a dictionary mapping user defined aliases to
1121 revsetalias objects.
1122 """
1123 if not isinstance(tree, tuple):
1124 # Do not expand raw strings
1125 return tree
1126 alias = _getalias(aliases, tree)
1127 if alias is not None:
1128 if alias in expanding:
1129 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1130 'detected') % alias.name)
1131 expanding.append(alias)
1132 result = alias.replacement
1133 if alias.args is not None:
1134 l = getlist(tree[2])
1135 if len(l) != len(alias.args):
1136 raise error.ParseError(
1137 _('invalid number of arguments: %s') % len(l))
1138 result = _expandargs(result, dict(zip(alias.args, l)))
1139 # Recurse in place, the base expression may have been rewritten
1140 result = _expandaliases(aliases, result, expanding)
1141 expanding.pop()
1142 else:
1143 result = tuple(_expandaliases(aliases, t, expanding)
1144 for t in tree)
1145 return result
1108 1146
1109 def findaliases(ui, tree): 1147 def findaliases(ui, tree):
1148 aliases = {}
1110 for k, v in ui.configitems('revsetalias'): 1149 for k, v in ui.configitems('revsetalias'):
1111 alias = revsetalias(k, v) 1150 alias = revsetalias(k, v)
1112 tree = alias.process(tree) 1151 aliases[alias.name] = alias
1113 return tree 1152 return _expandaliases(aliases, tree, [])
1114 1153
1115 parse = parser.parser(tokenize, elements).parse 1154 parse = parser.parser(tokenize, elements).parse
1116 1155
1117 def match(ui, spec): 1156 def match(ui, spec):
1118 if not spec: 1157 if not spec: