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