##// END OF EJS Templates
revset: support raw string literals...
Brodie Rao -
r12408:78a97859 default
parent child Browse files
Show More
@@ -1,173 +1,177
1 1 Mercurial supports a functional language for selecting a set of
2 2 revisions.
3 3
4 4 The language supports a number of predicates which are joined by infix
5 5 operators. Parenthesis can be used for grouping.
6 6
7 7 Identifiers such as branch names must be quoted with single or double
8 8 quotes if they contain characters outside of
9 9 ``[._a-zA-Z0-9\x80-\xff]`` or if they match one of the predefined
10 predicates. Special characters can be used in quoted identifiers by
11 escaping them, e.g., ``\n`` is interpreted as a newline.
10 predicates.
11
12 Special characters can be used in quoted identifiers by escaping them,
13 e.g., ``\n`` is interpreted as a newline. To prevent them from being
14 interpreted, strings can be prefixed with ``r``, e.g. ``r'...'``.
12 15
13 16 There is a single prefix operator:
14 17
15 18 ``not x``
16 19 Changesets not in x. Short form is ``! x``.
17 20
18 21 These are the supported infix operators:
19 22
20 23 ``x::y``
21 24 A DAG range, meaning all changesets that are descendants of x and
22 25 ancestors of y, including x and y themselves. If the first endpoint
23 26 is left out, this is equivalent to ``ancestors(y)``, if the second
24 27 is left out it is equivalent to ``descendants(x)``.
25 28
26 29 An alternative syntax is ``x..y``.
27 30
28 31 ``x:y``
29 32 All changesets with revision numbers between x and y, both
30 33 inclusive. Either endpoint can be left out, they default to 0 and
31 34 tip.
32 35
33 36 ``x and y``
34 37 The intersection of changesets in x and y. Short form is ``x & y``.
35 38
36 39 ``x or y``
37 40 The union of changesets in x and y. There are two alternative short
38 41 forms: ``x | y`` and ``x + y``.
39 42
40 43 ``x - y``
41 44 Changesets in x but not in y.
42 45
43 46 The following predicates are supported:
44 47
45 48 ``adds(pattern)``
46 49 Changesets that add a file matching pattern.
47 50
48 51 ``all()``
49 52 All changesets, the same as ``0:tip``.
50 53
51 54 ``ancestor(single, single)``
52 55 Greatest common ancestor of the two changesets.
53 56
54 57 ``ancestors(set)``
55 58 Changesets that are ancestors of a changeset in set.
56 59
57 60 ``author(string)``
58 61 Alias for ``user(string)``.
59 62
60 63 ``branch(set)``
61 64 All changesets belonging to the branches of changesets in set.
62 65
63 66 ``children(set)``
64 67 Child changesets of changesets in set.
65 68
66 69 ``closed()``
67 70 Changeset is closed.
68 71
69 72 ``contains(pattern)``
70 73 Revision contains pattern.
71 74
72 75 ``date(interval)``
73 76 Changesets within the interval, see :hg:`help dates`.
74 77
75 78 ``descendants(set)``
76 79 Changesets which are descendants of changesets in set.
77 80
78 81 ``file(pattern)``
79 82 Changesets affecting files matched by pattern.
80 83
81 84 ``follow()``
82 85 An alias for ``::.`` (ancestors of the working copy's first parent).
83 86
84 87 ``grep(regex)``
85 Like ``keyword(string)`` but accepts a regex.
88 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
89 to ensure special escape characters are handled correctly.
86 90
87 91 ``head()``
88 92 Changeset is a head.
89 93
90 94 ``heads(set)``
91 95 Members of set with no children in set.
92 96
93 97 ``keyword(string)``
94 98 Search commit message, user name, and names of changed files for
95 99 string.
96 100
97 101 ``limit(set, n)``
98 102 First n members of set.
99 103
100 104 ``max(set)``
101 105 Changeset with highest revision number in set.
102 106
103 107 ``min(set)``
104 108 Changeset with lowest revision number in set.
105 109
106 110 ``merge()``
107 111 Changeset is a merge changeset.
108 112
109 113 ``modifies(pattern)``
110 114 Changesets modifying files matched by pattern.
111 115
112 116 ``outgoing([path])``
113 117 Changesets not found in the specified destination repository, or the
114 118 default push location.
115 119
116 120 ``p1(set)``
117 121 First parent of changesets in set.
118 122
119 123 ``p2(set)``
120 124 Second parent of changesets in set.
121 125
122 126 ``parents(set)``
123 127 The set of all parents for all changesets in set.
124 128
125 129 ``present(set)``
126 130 An empty set, if any revision in set isn't found; otherwise,
127 131 all revisions in set.
128 132
129 133 ``removes(pattern)``
130 134 Changesets which remove files matching pattern.
131 135
132 136 ``reverse(set)``
133 137 Reverse order of set.
134 138
135 139 ``roots(set)``
136 140 Changesets with no parent changeset in set.
137 141
138 142 ``sort(set[, [-]key...])``
139 143 Sort set by keys. The default sort order is ascending, specify a key
140 144 as ``-key`` to sort in descending order.
141 145
142 146 The keys can be:
143 147
144 148 - ``rev`` for the revision number,
145 149 - ``branch`` for the branch name,
146 150 - ``desc`` for the commit message (description),
147 151 - ``user`` for user name (``author`` can be used as an alias),
148 152 - ``date`` for the commit date
149 153
150 154 ``tagged()``
151 155 Changeset is tagged.
152 156
153 157 ``user(string)``
154 158 User name is string.
155 159
156 160 Command line equivalents for :hg:`log`::
157 161
158 162 -f -> ::.
159 163 -d x -> date(x)
160 164 -k x -> keyword(x)
161 165 -m -> merge()
162 166 -u x -> user(x)
163 167 -b x -> branch(x)
164 168 -P x -> !::x
165 169 -l x -> limit(expr, x)
166 170
167 171 Some sample queries::
168 172
169 173 hg log -r 'branch(default)'
170 174 hg log -r 'branch(default) and 1.5:: and not merge()'
171 175 hg log -r '1.3::1.5 and keyword(bug) and file("hgext/*")'
172 176 hg log -r 'sort(date("May 2008"), user)'
173 177 hg log -r '(keyword(bug) or keyword(issue)) and not ancestors(tagged())'
@@ -1,591 +1,598
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery
10 10 import match as matchmod
11 11 from i18n import _
12 12
13 13 elements = {
14 14 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 15 "-": (19, ("negate", 19), ("minus", 19)),
16 16 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
17 17 ("dagrangepost", 17)),
18 18 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
19 19 ("dagrangepost", 17)),
20 20 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
21 21 "not": (10, ("not", 10)),
22 22 "!": (10, ("not", 10)),
23 23 "and": (5, None, ("and", 5)),
24 24 "&": (5, None, ("and", 5)),
25 25 "or": (4, None, ("or", 4)),
26 26 "|": (4, None, ("or", 4)),
27 27 "+": (4, None, ("or", 4)),
28 28 ",": (2, None, ("list", 2)),
29 29 ")": (0, None, None),
30 30 "symbol": (0, ("symbol",), None),
31 31 "string": (0, ("string",), None),
32 32 "end": (0, None, None),
33 33 }
34 34
35 35 keywords = set(['and', 'or', 'not'])
36 36
37 37 def tokenize(program):
38 38 pos, l = 0, len(program)
39 39 while pos < l:
40 40 c = program[pos]
41 41 if c.isspace(): # skip inter-token whitespace
42 42 pass
43 43 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
44 44 yield ('::', None, pos)
45 45 pos += 1 # skip ahead
46 46 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
47 47 yield ('..', None, pos)
48 48 pos += 1 # skip ahead
49 49 elif c in "():,-|&+!": # handle simple operators
50 50 yield (c, None, pos)
51 elif c in '"\'': # handle quoted strings
51 elif (c in '"\'' or c == 'r' and
52 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
53 if c == 'r':
54 pos += 1
55 c = program[pos]
56 decode = lambda x: x
57 else:
58 decode = lambda x: x.decode('string-escape')
52 59 pos += 1
53 60 s = pos
54 61 while pos < l: # find closing quote
55 62 d = program[pos]
56 63 if d == '\\': # skip over escaped characters
57 64 pos += 2
58 65 continue
59 66 if d == c:
60 yield ('string', program[s:pos].decode('string-escape'), s)
67 yield ('string', decode(program[s:pos]), s)
61 68 break
62 69 pos += 1
63 70 else:
64 71 raise error.ParseError(_("unterminated string"), s)
65 72 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
66 73 s = pos
67 74 pos += 1
68 75 while pos < l: # find end of symbol
69 76 d = program[pos]
70 77 if not (d.isalnum() or d in "._" or ord(d) > 127):
71 78 break
72 79 if d == '.' and program[pos - 1] == '.': # special case for ..
73 80 pos -= 1
74 81 break
75 82 pos += 1
76 83 sym = program[s:pos]
77 84 if sym in keywords: # operator keywords
78 85 yield (sym, None, s)
79 86 else:
80 87 yield ('symbol', sym, s)
81 88 pos -= 1
82 89 else:
83 90 raise error.ParseError(_("syntax error"), pos)
84 91 pos += 1
85 92 yield ('end', None, pos)
86 93
87 94 # helpers
88 95
89 96 def getstring(x, err):
90 97 if x and (x[0] == 'string' or x[0] == 'symbol'):
91 98 return x[1]
92 99 raise error.ParseError(err)
93 100
94 101 def getlist(x):
95 102 if not x:
96 103 return []
97 104 if x[0] == 'list':
98 105 return getlist(x[1]) + [x[2]]
99 106 return [x]
100 107
101 108 def getargs(x, min, max, err):
102 109 l = getlist(x)
103 110 if len(l) < min or len(l) > max:
104 111 raise error.ParseError(err)
105 112 return l
106 113
107 114 def getset(repo, subset, x):
108 115 if not x:
109 116 raise error.ParseError(_("missing argument"))
110 117 return methods[x[0]](repo, subset, *x[1:])
111 118
112 119 # operator methods
113 120
114 121 def stringset(repo, subset, x):
115 122 x = repo[x].rev()
116 123 if x == -1 and len(subset) == len(repo):
117 124 return [-1]
118 125 if x in subset:
119 126 return [x]
120 127 return []
121 128
122 129 def symbolset(repo, subset, x):
123 130 if x in symbols:
124 131 raise error.ParseError(_("can't use %s here") % x)
125 132 return stringset(repo, subset, x)
126 133
127 134 def rangeset(repo, subset, x, y):
128 135 m = getset(repo, subset, x)
129 136 if not m:
130 137 m = getset(repo, range(len(repo)), x)
131 138
132 139 n = getset(repo, subset, y)
133 140 if not n:
134 141 n = getset(repo, range(len(repo)), y)
135 142
136 143 if not m or not n:
137 144 return []
138 145 m, n = m[0], n[-1]
139 146
140 147 if m < n:
141 148 r = range(m, n + 1)
142 149 else:
143 150 r = range(m, n - 1, -1)
144 151 s = set(subset)
145 152 return [x for x in r if x in s]
146 153
147 154 def andset(repo, subset, x, y):
148 155 return getset(repo, getset(repo, subset, x), y)
149 156
150 157 def orset(repo, subset, x, y):
151 158 s = set(getset(repo, subset, x))
152 159 s |= set(getset(repo, [r for r in subset if r not in s], y))
153 160 return [r for r in subset if r in s]
154 161
155 162 def notset(repo, subset, x):
156 163 s = set(getset(repo, subset, x))
157 164 return [r for r in subset if r not in s]
158 165
159 166 def listset(repo, subset, a, b):
160 167 raise error.ParseError(_("can't use a list in this context"))
161 168
162 169 def func(repo, subset, a, b):
163 170 if a[0] == 'symbol' and a[1] in symbols:
164 171 return symbols[a[1]](repo, subset, b)
165 172 raise error.ParseError(_("not a function: %s") % a[1])
166 173
167 174 # functions
168 175
169 176 def p1(repo, subset, x):
170 177 ps = set()
171 178 cl = repo.changelog
172 179 for r in getset(repo, subset, x):
173 180 ps.add(cl.parentrevs(r)[0])
174 181 return [r for r in subset if r in ps]
175 182
176 183 def p2(repo, subset, x):
177 184 ps = set()
178 185 cl = repo.changelog
179 186 for r in getset(repo, subset, x):
180 187 ps.add(cl.parentrevs(r)[1])
181 188 return [r for r in subset if r in ps]
182 189
183 190 def parents(repo, subset, x):
184 191 ps = set()
185 192 cl = repo.changelog
186 193 for r in getset(repo, subset, x):
187 194 ps.update(cl.parentrevs(r))
188 195 return [r for r in subset if r in ps]
189 196
190 197 def maxrev(repo, subset, x):
191 198 s = getset(repo, subset, x)
192 199 if s:
193 200 m = max(s)
194 201 if m in subset:
195 202 return [m]
196 203 return []
197 204
198 205 def minrev(repo, subset, x):
199 206 s = getset(repo, subset, x)
200 207 if s:
201 208 m = min(s)
202 209 if m in subset:
203 210 return [m]
204 211 return []
205 212
206 213 def limit(repo, subset, x):
207 214 l = getargs(x, 2, 2, _("limit wants two arguments"))
208 215 try:
209 216 lim = int(getstring(l[1], _("limit wants a number")))
210 217 except ValueError:
211 218 raise error.ParseError(_("limit expects a number"))
212 219 return getset(repo, subset, l[0])[:lim]
213 220
214 221 def children(repo, subset, x):
215 222 cs = set()
216 223 cl = repo.changelog
217 224 s = set(getset(repo, subset, x))
218 225 for r in xrange(0, len(repo)):
219 226 for p in cl.parentrevs(r):
220 227 if p in s:
221 228 cs.add(r)
222 229 return [r for r in subset if r in cs]
223 230
224 231 def branch(repo, subset, x):
225 232 s = getset(repo, range(len(repo)), x)
226 233 b = set()
227 234 for r in s:
228 235 b.add(repo[r].branch())
229 236 s = set(s)
230 237 return [r for r in subset if r in s or repo[r].branch() in b]
231 238
232 239 def ancestor(repo, subset, x):
233 240 l = getargs(x, 2, 2, _("ancestor wants two arguments"))
234 241 r = range(len(repo))
235 242 a = getset(repo, r, l[0])
236 243 b = getset(repo, r, l[1])
237 244 if len(a) != 1 or len(b) != 1:
238 245 raise error.ParseError(_("ancestor arguments must be single revisions"))
239 246 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
240 247
241 248 return [r for r in an if r in subset]
242 249
243 250 def ancestors(repo, subset, x):
244 251 args = getset(repo, range(len(repo)), x)
245 252 if not args:
246 253 return []
247 254 s = set(repo.changelog.ancestors(*args)) | set(args)
248 255 return [r for r in subset if r in s]
249 256
250 257 def descendants(repo, subset, x):
251 258 args = getset(repo, range(len(repo)), x)
252 259 if not args:
253 260 return []
254 261 s = set(repo.changelog.descendants(*args)) | set(args)
255 262 return [r for r in subset if r in s]
256 263
257 264 def follow(repo, subset, x):
258 265 getargs(x, 0, 0, _("follow takes no arguments"))
259 266 p = repo['.'].rev()
260 267 s = set(repo.changelog.ancestors(p)) | set([p])
261 268 return [r for r in subset if r in s]
262 269
263 270 def date(repo, subset, x):
264 271 ds = getstring(x, _("date wants a string"))
265 272 dm = util.matchdate(ds)
266 273 return [r for r in subset if dm(repo[r].date()[0])]
267 274
268 275 def keyword(repo, subset, x):
269 276 kw = getstring(x, _("keyword wants a string")).lower()
270 277 l = []
271 278 for r in subset:
272 279 c = repo[r]
273 280 t = " ".join(c.files() + [c.user(), c.description()])
274 281 if kw in t.lower():
275 282 l.append(r)
276 283 return l
277 284
278 285 def grep(repo, subset, x):
279 286 try:
280 287 gr = re.compile(getstring(x, _("grep wants a string")))
281 288 except re.error, e:
282 289 raise error.ParseError(_('invalid match pattern: %s') % e)
283 290 l = []
284 291 for r in subset:
285 292 c = repo[r]
286 293 for e in c.files() + [c.user(), c.description()]:
287 294 if gr.search(e):
288 295 l.append(r)
289 296 continue
290 297 return l
291 298
292 299 def author(repo, subset, x):
293 300 n = getstring(x, _("author wants a string")).lower()
294 301 return [r for r in subset if n in repo[r].user().lower()]
295 302
296 303 def hasfile(repo, subset, x):
297 304 pat = getstring(x, _("file wants a pattern"))
298 305 m = matchmod.match(repo.root, repo.getcwd(), [pat])
299 306 s = []
300 307 for r in subset:
301 308 for f in repo[r].files():
302 309 if m(f):
303 310 s.append(r)
304 311 continue
305 312 return s
306 313
307 314 def contains(repo, subset, x):
308 315 pat = getstring(x, _("contains wants a pattern"))
309 316 m = matchmod.match(repo.root, repo.getcwd(), [pat])
310 317 s = []
311 318 if m.files() == [pat]:
312 319 for r in subset:
313 320 if pat in repo[r]:
314 321 s.append(r)
315 322 continue
316 323 else:
317 324 for r in subset:
318 325 for f in repo[r].manifest():
319 326 if m(f):
320 327 s.append(r)
321 328 continue
322 329 return s
323 330
324 331 def checkstatus(repo, subset, pat, field):
325 332 m = matchmod.match(repo.root, repo.getcwd(), [pat])
326 333 s = []
327 334 fast = (m.files() == [pat])
328 335 for r in subset:
329 336 c = repo[r]
330 337 if fast:
331 338 if pat not in c.files():
332 339 continue
333 340 else:
334 341 for f in c.files():
335 342 if m(f):
336 343 break
337 344 else:
338 345 continue
339 346 files = repo.status(c.p1().node(), c.node())[field]
340 347 if fast:
341 348 if pat in files:
342 349 s.append(r)
343 350 continue
344 351 else:
345 352 for f in files:
346 353 if m(f):
347 354 s.append(r)
348 355 continue
349 356 return s
350 357
351 358 def modifies(repo, subset, x):
352 359 pat = getstring(x, _("modifies wants a pattern"))
353 360 return checkstatus(repo, subset, pat, 0)
354 361
355 362 def adds(repo, subset, x):
356 363 pat = getstring(x, _("adds wants a pattern"))
357 364 return checkstatus(repo, subset, pat, 1)
358 365
359 366 def removes(repo, subset, x):
360 367 pat = getstring(x, _("removes wants a pattern"))
361 368 return checkstatus(repo, subset, pat, 2)
362 369
363 370 def merge(repo, subset, x):
364 371 getargs(x, 0, 0, _("merge takes no arguments"))
365 372 cl = repo.changelog
366 373 return [r for r in subset if cl.parentrevs(r)[1] != -1]
367 374
368 375 def closed(repo, subset, x):
369 376 getargs(x, 0, 0, _("closed takes no arguments"))
370 377 return [r for r in subset if repo[r].extra().get('close')]
371 378
372 379 def head(repo, subset, x):
373 380 getargs(x, 0, 0, _("head takes no arguments"))
374 381 hs = set()
375 382 for b, ls in repo.branchmap().iteritems():
376 383 hs.update(repo[h].rev() for h in ls)
377 384 return [r for r in subset if r in hs]
378 385
379 386 def reverse(repo, subset, x):
380 387 l = getset(repo, subset, x)
381 388 l.reverse()
382 389 return l
383 390
384 391 def present(repo, subset, x):
385 392 try:
386 393 return getset(repo, subset, x)
387 394 except error.RepoLookupError:
388 395 return []
389 396
390 397 def sort(repo, subset, x):
391 398 l = getargs(x, 1, 2, _("sort wants one or two arguments"))
392 399 keys = "rev"
393 400 if len(l) == 2:
394 401 keys = getstring(l[1], _("sort spec must be a string"))
395 402
396 403 s = l[0]
397 404 keys = keys.split()
398 405 l = []
399 406 def invert(s):
400 407 return "".join(chr(255 - ord(c)) for c in s)
401 408 for r in getset(repo, subset, s):
402 409 c = repo[r]
403 410 e = []
404 411 for k in keys:
405 412 if k == 'rev':
406 413 e.append(r)
407 414 elif k == '-rev':
408 415 e.append(-r)
409 416 elif k == 'branch':
410 417 e.append(c.branch())
411 418 elif k == '-branch':
412 419 e.append(invert(c.branch()))
413 420 elif k == 'desc':
414 421 e.append(c.description())
415 422 elif k == '-desc':
416 423 e.append(invert(c.description()))
417 424 elif k in 'user author':
418 425 e.append(c.user())
419 426 elif k in '-user -author':
420 427 e.append(invert(c.user()))
421 428 elif k == 'date':
422 429 e.append(c.date()[0])
423 430 elif k == '-date':
424 431 e.append(-c.date()[0])
425 432 else:
426 433 raise error.ParseError(_("unknown sort key %r") % k)
427 434 e.append(r)
428 435 l.append(e)
429 436 l.sort()
430 437 return [e[-1] for e in l]
431 438
432 439 def getall(repo, subset, x):
433 440 getargs(x, 0, 0, _("all takes no arguments"))
434 441 return subset
435 442
436 443 def heads(repo, subset, x):
437 444 s = getset(repo, subset, x)
438 445 ps = set(parents(repo, subset, x))
439 446 return [r for r in s if r not in ps]
440 447
441 448 def roots(repo, subset, x):
442 449 s = getset(repo, subset, x)
443 450 cs = set(children(repo, subset, x))
444 451 return [r for r in s if r not in cs]
445 452
446 453 def outgoing(repo, subset, x):
447 454 import hg # avoid start-up nasties
448 455 l = getargs(x, 0, 1, _("outgoing wants a repository path"))
449 456 dest = l and getstring(l[0], _("outgoing wants a repository path")) or ''
450 457 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
451 458 dest, branches = hg.parseurl(dest)
452 459 other = hg.repository(hg.remoteui(repo, {}), dest)
453 460 repo.ui.pushbuffer()
454 461 o = discovery.findoutgoing(repo, other)
455 462 repo.ui.popbuffer()
456 463 cl = repo.changelog
457 464 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, None)[0]])
458 465 return [r for r in subset if r in o]
459 466
460 467 def tagged(repo, subset, x):
461 468 getargs(x, 0, 0, _("tagged takes no arguments"))
462 469 cl = repo.changelog
463 470 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
464 471 return [r for r in subset if r in s]
465 472
466 473 symbols = {
467 474 "adds": adds,
468 475 "all": getall,
469 476 "ancestor": ancestor,
470 477 "ancestors": ancestors,
471 478 "author": author,
472 479 "branch": branch,
473 480 "children": children,
474 481 "closed": closed,
475 482 "contains": contains,
476 483 "date": date,
477 484 "descendants": descendants,
478 485 "file": hasfile,
479 486 "follow": follow,
480 487 "grep": grep,
481 488 "head": head,
482 489 "heads": heads,
483 490 "keyword": keyword,
484 491 "limit": limit,
485 492 "max": maxrev,
486 493 "min": minrev,
487 494 "merge": merge,
488 495 "modifies": modifies,
489 496 "outgoing": outgoing,
490 497 "p1": p1,
491 498 "p2": p2,
492 499 "parents": parents,
493 500 "present": present,
494 501 "removes": removes,
495 502 "reverse": reverse,
496 503 "roots": roots,
497 504 "sort": sort,
498 505 "tagged": tagged,
499 506 "user": author,
500 507 }
501 508
502 509 methods = {
503 510 "range": rangeset,
504 511 "string": stringset,
505 512 "symbol": symbolset,
506 513 "and": andset,
507 514 "or": orset,
508 515 "not": notset,
509 516 "list": listset,
510 517 "func": func,
511 518 }
512 519
513 520 def optimize(x, small):
514 521 if x == None:
515 522 return 0, x
516 523
517 524 smallbonus = 1
518 525 if small:
519 526 smallbonus = .5
520 527
521 528 op = x[0]
522 529 if op == 'minus':
523 530 return optimize(('and', x[1], ('not', x[2])), small)
524 531 elif op == 'dagrange':
525 532 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
526 533 ('func', ('symbol', 'ancestors'), x[2])), small)
527 534 elif op == 'dagrangepre':
528 535 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
529 536 elif op == 'dagrangepost':
530 537 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
531 538 elif op == 'rangepre':
532 539 return optimize(('range', ('string', '0'), x[1]), small)
533 540 elif op == 'rangepost':
534 541 return optimize(('range', x[1], ('string', 'tip')), small)
535 542 elif op == 'negate':
536 543 return optimize(('string',
537 544 '-' + getstring(x[1], _("can't negate that"))), small)
538 545 elif op in 'string symbol negate':
539 546 return smallbonus, x # single revisions are small
540 547 elif op == 'and' or op == 'dagrange':
541 548 wa, ta = optimize(x[1], True)
542 549 wb, tb = optimize(x[2], True)
543 550 w = min(wa, wb)
544 551 if wa > wb:
545 552 return w, (op, tb, ta)
546 553 return w, (op, ta, tb)
547 554 elif op == 'or':
548 555 wa, ta = optimize(x[1], False)
549 556 wb, tb = optimize(x[2], False)
550 557 if wb < wa:
551 558 wb, wa = wa, wb
552 559 return max(wa, wb), (op, ta, tb)
553 560 elif op == 'not':
554 561 o = optimize(x[1], not small)
555 562 return o[0], (op, o[1])
556 563 elif op == 'group':
557 564 return optimize(x[1], small)
558 565 elif op in 'range list':
559 566 wa, ta = optimize(x[1], small)
560 567 wb, tb = optimize(x[2], small)
561 568 return wa + wb, (op, ta, tb)
562 569 elif op == 'func':
563 570 f = getstring(x[1], _("not a symbol"))
564 571 wa, ta = optimize(x[2], small)
565 572 if f in "grep date user author keyword branch file outgoing":
566 573 w = 10 # slow
567 574 elif f in "modifies adds removes":
568 575 w = 30 # slower
569 576 elif f == "contains":
570 577 w = 100 # very slow
571 578 elif f == "ancestor":
572 579 w = 1 * smallbonus
573 580 elif f == "reverse limit":
574 581 w = 0
575 582 elif f in "sort":
576 583 w = 10 # assume most sorts look at changelog
577 584 else:
578 585 w = 1
579 586 return w + wa, (op, x[1], ta)
580 587 return 1, x
581 588
582 589 parse = parser.parser(tokenize, elements).parse
583 590
584 591 def match(spec):
585 592 if not spec:
586 593 raise error.ParseError(_("empty query"))
587 594 tree = parse(spec)
588 595 weight, tree = optimize(tree, True)
589 596 def mfunc(repo, subset):
590 597 return getset(repo, subset, tree)
591 598 return mfunc
@@ -1,321 +1,329
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3
4 4 $ try() {
5 5 > hg debugrevspec --debug $@
6 6 > }
7 7
8 8 $ log() {
9 9 > hg log --template '{rev}\n' -r "$1"
10 10 > }
11 11
12 12 $ hg init repo
13 13 $ cd repo
14 14
15 15 $ echo a > a
16 16 $ hg branch a
17 17 marked working directory as branch a
18 18 $ hg ci -Aqm0
19 19
20 20 $ echo b > b
21 21 $ hg branch b
22 22 marked working directory as branch b
23 23 $ hg ci -Aqm1
24 24
25 25 $ rm a
26 26 $ hg branch a-b-c-
27 27 marked working directory as branch a-b-c-
28 28 $ hg ci -Aqm2 -u Bob
29 29
30 30 $ hg co 1
31 31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 32 $ hg branch +a+b+c+
33 33 marked working directory as branch +a+b+c+
34 34 $ hg ci -Aqm3
35 35
36 36 $ hg co 2 # interleave
37 37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 38 $ echo bb > b
39 39 $ hg branch -- -a-b-c-
40 40 marked working directory as branch -a-b-c-
41 41 $ hg ci -Aqm4 -d "May 12 2005"
42 42
43 43 $ hg co 3
44 44 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 45 $ hg branch /a/b/c/
46 46 marked working directory as branch /a/b/c/
47 47 $ hg ci -Aqm"5 bug"
48 48
49 49 $ hg merge 4
50 50 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
51 51 (branch merge, don't forget to commit)
52 52 $ hg branch _a_b_c_
53 53 marked working directory as branch _a_b_c_
54 54 $ hg ci -Aqm"6 issue619"
55 55
56 56 $ hg branch .a.b.c.
57 57 marked working directory as branch .a.b.c.
58 58 $ hg ci -Aqm7
59 59
60 60 $ hg branch all
61 61 marked working directory as branch all
62 62 $ hg ci --close-branch -Aqm8
63 63 abort: can only close branch heads
64 64 [255]
65 65
66 66 $ hg co 4
67 67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 68 $ hg branch Γ©
69 69 marked working directory as branch Γ©
70 70 $ hg ci -Aqm9
71 71
72 72 $ hg tag -r6 1.0
73 73
74 74 $ hg clone --quiet -U -r 7 . ../remote1
75 75 $ hg clone --quiet -U -r 8 . ../remote2
76 76 $ echo "[paths]" >> .hg/hgrc
77 77 $ echo "default = ../remote1" >> .hg/hgrc
78 78
79 79 names that should work without quoting
80 80
81 81 $ try a
82 82 ('symbol', 'a')
83 83 0
84 84 $ try b-a
85 85 ('minus', ('symbol', 'b'), ('symbol', 'a'))
86 86 1
87 87 $ try _a_b_c_
88 88 ('symbol', '_a_b_c_')
89 89 6
90 90 $ try _a_b_c_-a
91 91 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
92 92 6
93 93 $ try .a.b.c.
94 94 ('symbol', '.a.b.c.')
95 95 7
96 96 $ try .a.b.c.-a
97 97 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
98 98 7
99 99 $ try -- '-a-b-c-' # complains
100 100 hg: parse error at 7: not a prefix: end
101 101 [255]
102 102 $ log -a-b-c- # succeeds with fallback
103 103 4
104 104 $ try -- -a-b-c--a # complains
105 105 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
106 106 abort: unknown revision '-a'!
107 107 [255]
108 108 $ try Γ©
109 109 ('symbol', '\xc3\xa9')
110 110 9
111 111
112 112 quoting needed
113 113
114 114 $ try '"-a-b-c-"-a'
115 115 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
116 116 4
117 117
118 118 $ log '1 or 2'
119 119 1
120 120 2
121 121 $ log '1|2'
122 122 1
123 123 2
124 124 $ log '1 and 2'
125 125 $ log '1&2'
126 126 $ try '1&2|3' # precedence - and is higher
127 127 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
128 128 3
129 129 $ try '1|2&3'
130 130 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
131 131 1
132 132 $ try '1&2&3' # associativity
133 133 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
134 134 $ try '1|(2|3)'
135 135 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
136 136 1
137 137 2
138 138 3
139 139 $ log '1.0' # tag
140 140 6
141 141 $ log 'a' # branch
142 142 0
143 143 $ log '2785f51ee'
144 144 0
145 145 $ log 'date(2005)'
146 146 4
147 147 $ log 'date(this is a test)'
148 148 hg: parse error at 10: unexpected token: symbol
149 149 [255]
150 150 $ log 'date()'
151 151 hg: parse error: date wants a string
152 152 [255]
153 153 $ log 'date'
154 154 hg: parse error: can't use date here
155 155 [255]
156 156 $ log 'date('
157 157 hg: parse error at 5: not a prefix: end
158 158 [255]
159 159 $ log 'date(tip)'
160 160 abort: invalid date: 'tip'
161 161 [255]
162 162 $ log '"date"'
163 163 abort: unknown revision 'date'!
164 164 [255]
165 165 $ log 'date(2005) and 1::'
166 166 4
167 167
168 168 $ log 'ancestor(1)'
169 169 hg: parse error: ancestor wants two arguments
170 170 [255]
171 171 $ log 'ancestor(4,5)'
172 172 1
173 173 $ log 'ancestor(4,5) and 4'
174 174 $ log 'ancestors(5)'
175 175 0
176 176 1
177 177 3
178 178 5
179 179 $ log 'author(bob)'
180 180 2
181 181 $ log 'branch(Γ©)'
182 182 8
183 183 9
184 184 $ log 'children(ancestor(4,5))'
185 185 2
186 186 3
187 187 $ log 'closed()'
188 188 $ log 'contains(a)'
189 189 0
190 190 1
191 191 3
192 192 5
193 193 $ log 'descendants(2 or 3)'
194 194 2
195 195 3
196 196 4
197 197 5
198 198 6
199 199 7
200 200 8
201 201 9
202 202 $ log 'file(b)'
203 203 1
204 204 4
205 205 $ log 'follow()'
206 206 0
207 207 1
208 208 2
209 209 4
210 210 8
211 211 9
212 212 $ log 'grep("issue\d+")'
213 213 6
214 214 $ try 'grep("(")' # invalid regular expression
215 215 ('func', ('symbol', 'grep'), ('string', '('))
216 216 hg: parse error: invalid match pattern: unbalanced parenthesis
217 217 [255]
218 $ try 'grep("\bissue\d+")'
219 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
220 $ try 'grep(r"\bissue\d+")'
221 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
222 6
223 $ try 'grep(r"\")'
224 hg: parse error at 7: unterminated string
225 [255]
218 226 $ log 'head()'
219 227 0
220 228 1
221 229 2
222 230 3
223 231 4
224 232 5
225 233 6
226 234 7
227 235 9
228 236 $ log 'heads(6::)'
229 237 7
230 238 $ log 'keyword(issue)'
231 239 6
232 240 $ log 'limit(head(), 1)'
233 241 0
234 242 $ log 'max(contains(a))'
235 243 5
236 244 $ log 'min(contains(a))'
237 245 0
238 246 $ log 'merge()'
239 247 6
240 248 $ log 'modifies(b)'
241 249 4
242 250 $ log 'outgoing()'
243 251 8
244 252 9
245 253 $ log 'outgoing("../remote1")'
246 254 8
247 255 9
248 256 $ log 'outgoing("../remote2")'
249 257 3
250 258 5
251 259 6
252 260 7
253 261 9
254 262 $ log 'p1(merge())'
255 263 5
256 264 $ log 'p2(merge())'
257 265 4
258 266 $ log 'parents(merge())'
259 267 4
260 268 5
261 269 $ log 'removes(a)'
262 270 2
263 271 6
264 272 $ log 'roots(all())'
265 273 0
266 274 $ log 'reverse(2 or 3 or 4 or 5)'
267 275 5
268 276 4
269 277 3
270 278 2
271 279 $ log 'sort(limit(reverse(all()), 3))'
272 280 7
273 281 8
274 282 9
275 283 $ log 'sort(2 or 3 or 4 or 5, date)'
276 284 2
277 285 3
278 286 5
279 287 4
280 288 $ log 'tagged()'
281 289 6
282 290 $ log 'user(bob)'
283 291 2
284 292
285 293 $ log '4::8'
286 294 4
287 295 8
288 296 $ log '4:8'
289 297 4
290 298 5
291 299 6
292 300 7
293 301 8
294 302
295 303 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
296 304 4
297 305 2
298 306 5
299 307
300 308 $ log 'not 0 and 0:2'
301 309 1
302 310 2
303 311 $ log 'not 1 and 0:2'
304 312 0
305 313 2
306 314 $ log 'not 2 and 0:2'
307 315 0
308 316 1
309 317 $ log '(1 and 2)::'
310 318 $ log '(1 and 2):'
311 319 $ log '(1 and 2):3'
312 320 $ log 'sort(head(), -rev)'
313 321 9
314 322 7
315 323 6
316 324 5
317 325 4
318 326 3
319 327 2
320 328 1
321 329 0
General Comments 0
You need to be logged in to leave comments. Login now