##// END OF EJS Templates
revset: fix up tests
Matt Mackall -
r11282:e581f3ac default
parent child Browse files
Show More
@@ -1,554 +1,553 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, hg
10 10 import match as _match
11 11
12 12 elements = {
13 13 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
14 14 "-": (19, ("negate", 19), ("minus", 19)),
15 15 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
16 16 ("dagrangepost", 17)),
17 17 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
18 18 ("dagrangepost", 17)),
19 19 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
20 20 "not": (10, ("not", 10)),
21 21 "!": (10, ("not", 10)),
22 22 "and": (5, None, ("and", 5)),
23 23 "&": (5, None, ("and", 5)),
24 24 "or": (4, None, ("or", 4)),
25 25 "|": (4, None, ("or", 4)),
26 26 "+": (4, None, ("or", 4)),
27 27 ",": (2, None, ("list", 2)),
28 28 ")": (0, None, None),
29 29 "symbol": (0, ("symbol",), None),
30 30 "string": (0, ("string",), None),
31 31 "end": (0, None, None),
32 32 }
33 33
34 34 keywords = set(['and', 'or', 'not'])
35 35
36 36 def tokenize(program):
37 37 pos, l = 0, len(program)
38 38 while pos < l:
39 39 c = program[pos]
40 40 if c.isspace(): # skip inter-token whitespace
41 41 pass
42 42 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
43 43 yield ('::', None)
44 44 pos += 1 # skip ahead
45 45 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
46 46 yield ('..', None)
47 47 pos += 1 # skip ahead
48 48 elif c in "():,-|&+!": # handle simple operators
49 49 yield (c, None)
50 50 elif c in '"\'': # handle quoted strings
51 51 pos += 1
52 52 s = pos
53 53 while pos < l: # find closing quote
54 54 d = program[pos]
55 55 if d == '\\': # skip over escaped characters
56 56 pos += 2
57 57 continue
58 58 if d == c:
59 59 yield ('string', program[s:pos].decode('string-escape'))
60 60 break
61 61 pos += 1
62 62 else:
63 63 raise "unterminated string"
64 64 elif c.isalnum() or c in '.': # gather up a symbol/keyword
65 65 s = pos
66 66 pos += 1
67 67 while pos < l: # find end of symbol
68 68 d = program[pos]
69 69 if not (d.isalnum() or d in "._"):
70 70 break
71 71 if d == '.' and program[pos - 1] == '.': # special case for ..
72 72 pos -= 1
73 73 break
74 74 pos += 1
75 75 sym = program[s:pos]
76 76 if sym in keywords: # operator keywords
77 77 yield (sym, None)
78 78 else:
79 79 yield ('symbol', sym)
80 80 pos -= 1
81 81 else:
82 82 raise "syntax error at %d" % pos
83 83 pos += 1
84 84 yield ('end', None)
85 85
86 86 # helpers
87 87
88 88 def getstring(x, err):
89 89 if x[0] == 'string' or x[0] == 'symbol':
90 90 return x[1]
91 91 raise err
92 92
93 93 def getlist(x):
94 94 if not x:
95 95 return []
96 96 if x[0] == 'list':
97 97 return getlist(x[1]) + [x[2]]
98 98 return [x]
99 99
100 100 def getpair(x, err):
101 101 l = getlist(x)
102 102 if len(l) != 2:
103 103 raise err
104 104 return l
105 105
106 106 def getset(repo, subset, x):
107 107 if not x:
108 108 raise "missing argument"
109 109 return methods[x[0]](repo, subset, *x[1:])
110 110
111 111 # operator methods
112 112
113 113 def negate(repo, subset, x):
114 114 return getset(repo, subset,
115 115 ('string', '-' + getstring(x, "can't negate that")))
116 116
117 117 def stringset(repo, subset, x):
118 118 x = repo[x].rev()
119 if x == -1 and len(subset) == len(repo):
120 return [-1]
119 121 if x in subset:
120 122 return [x]
121 123 return []
122 124
123 125 def symbolset(repo, subset, x):
124 126 if x in symbols:
125 127 raise "can't use %s here" % x
126 128 return stringset(repo, subset, x)
127 129
128 130 def rangeset(repo, subset, x, y):
129 131 m = getset(repo, subset, x)[0]
130 132 n = getset(repo, subset, y)[-1]
131 133 if m < n:
132 134 return range(m, n + 1)
133 135 return range(m, n - 1, -1)
134 136
135 137 def andset(repo, subset, x, y):
136 138 return getset(repo, getset(repo, subset, x), y)
137 139
138 140 def orset(repo, subset, x, y):
139 141 s = set(getset(repo, subset, x))
140 142 s |= set(getset(repo, [r for r in subset if r not in s], y))
141 143 return [r for r in subset if r in s]
142 144
143 145 def notset(repo, subset, x):
144 146 s = set(getset(repo, subset, x))
145 147 return [r for r in subset if r not in s]
146 148
147 149 def listset(repo, subset, a, b):
148 150 raise "can't use a list in this context"
149 151
150 152 def func(repo, subset, a, b):
151 153 if a[0] == 'symbol' and a[1] in symbols:
152 154 return symbols[a[1]](repo, subset, b)
153 155 raise "that's not a function: %s" % a[1]
154 156
155 157 # functions
156 158
157 159 def p1(repo, subset, x):
158 160 ps = set()
159 161 cl = repo.changelog
160 162 for r in getset(repo, subset, x):
161 163 ps.add(cl.parentrevs(r)[0])
162 164 return [r for r in subset if r in ps]
163 165
164 166 def p2(repo, subset, x):
165 167 ps = set()
166 168 cl = repo.changelog
167 169 for r in getset(repo, subset, x):
168 170 ps.add(cl.parentrevs(r)[1])
169 171 return [r for r in subset if r in ps]
170 172
171 173 def parents(repo, subset, x):
172 174 ps = set()
173 175 cl = repo.changelog
174 176 for r in getset(repo, subset, x):
175 177 ps.update(cl.parentrevs(r))
176 178 return [r for r in subset if r in ps]
177 179
178 180 def maxrev(repo, subset, x):
179 181 s = getset(repo, subset, x)
180 182 if s:
181 183 m = max(s)
182 184 if m in subset:
183 185 return [m]
184 186 return []
185 187
186 188 def limit(repo, subset, x):
187 189 l = getpair(x, "limit wants two args")
188 190 try:
189 191 lim = int(getstring(l[1], "limit wants a number"))
190 192 except ValueError:
191 193 raise "wants a number"
192 194 return getset(repo, subset, l[0])[:lim]
193 195
194 196 def children(repo, subset, x):
195 197 cs = set()
196 198 cl = repo.changelog
197 199 s = set(getset(repo, subset, x))
198 200 for r in xrange(0, len(repo)):
199 201 for p in cl.parentrevs(r):
200 202 if p in s:
201 203 cs.add(r)
202 204 return [r for r in subset if r in cs]
203 205
204 206 def branch(repo, subset, x):
205 207 s = getset(repo, range(len(repo)), x)
206 208 b = set()
207 209 for r in s:
208 210 b.add(repo[r].branch())
209 211 s = set(s)
210 212 return [r for r in subset if r in s or repo[r].branch() in b]
211 213
212 214 def ancestor(repo, subset, x):
213 215 l = getpair(x, "ancestor wants two args")
214 216 a = getset(repo, subset, l[0])
215 217 b = getset(repo, subset, l[1])
216 218 if len(a) > 1 or len(b) > 1:
217 219 raise "arguments to ancestor must be single revisions"
218 220 return [repo[a[0]].ancestor(repo[b[0]]).rev()]
219 221
220 222 def ancestors(repo, subset, x):
221 223 args = getset(repo, range(len(repo)), x)
222 224 s = set(repo.changelog.ancestors(*args)) | set(args)
223 225 return [r for r in subset if r in s]
224 226
225 227 def descendants(repo, subset, x):
226 228 args = getset(repo, range(len(repo)), x)
227 229 s = set(repo.changelog.descendants(*args)) | set(args)
228 230 return [r for r in subset if r in s]
229 231
230 232 def follow(repo, subset, x):
231 233 if x:
232 234 raise "follow takes no args"
233 235 p = repo['.'].rev()
234 236 s = set(repo.changelog.ancestors(p)) | set([p])
235 237 return [r for r in subset if r in s]
236 238
237 239 def date(repo, subset, x):
238 240 ds = getstring(x, 'date wants a string')
239 241 dm = util.matchdate(ds)
240 242 return [r for r in subset if dm(repo[r].date()[0])]
241 243
242 244 def keyword(repo, subset, x):
243 245 kw = getstring(x, "keyword wants a string").lower()
244 246 l = []
245 247 for r in subset:
246 248 c = repo[r]
247 249 t = " ".join(c.files() + [c.user(), c.description()])
248 250 if kw in t.lower():
249 251 l.append(r)
250 252 return l
251 253
252 254 def grep(repo, subset, x):
253 255 gr = re.compile(getstring(x, "grep wants a string"))
254 256 l = []
255 257 for r in subset:
256 258 c = repo[r]
257 259 for e in c.files() + [c.user(), c.description()]:
258 260 if gr.search(e):
259 261 l.append(r)
260 262 continue
261 263 return l
262 264
263 265 def author(repo, subset, x):
264 266 n = getstring(x, "author wants a string").lower()
265 267 return [r for r in subset if n in repo[r].user().lower()]
266 268
267 269 def hasfile(repo, subset, x):
268 270 pat = getstring(x, "file wants a pattern")
269 271 m = _match.match(repo.root, repo.getcwd(), [pat])
270 272 s = []
271 273 for r in subset:
272 274 for f in repo[r].files():
273 275 if m(f):
274 276 s.append(r)
275 277 continue
276 278 return s
277 279
278 280 def contains(repo, subset, x):
279 281 pat = getstring(x, "file wants a pattern")
280 282 m = _match.match(repo.root, repo.getcwd(), [pat])
281 283 s = []
282 284 if m.files() == [pat]:
283 285 for r in subset:
284 286 if pat in repo[r]:
285 287 s.append(r)
286 288 continue
287 289 else:
288 290 for r in subset:
289 291 c = repo[r]
290 292 for f in repo[r].manifest():
291 293 if m(f):
292 294 s.append(r)
293 295 continue
294 296 return s
295 297
296 298 def checkstatus(repo, subset, pat, field):
297 299 m = _match.match(repo.root, repo.getcwd(), [pat])
298 300 s = []
299 301 fast = (m.files() == [pat])
300 302 for r in subset:
301 303 c = repo[r]
302 304 if fast:
303 305 if pat not in c.files():
304 306 continue
305 307 else:
306 308 for f in c.files():
307 309 if m(f):
308 310 break
309 311 else:
310 312 continue
311 313 files = repo.status(c.p1().node(), c.node())[field]
312 314 if fast:
313 315 if pat in files:
314 316 s.append(r)
315 317 continue
316 318 else:
317 319 for f in files:
318 320 if m(f):
319 321 s.append(r)
320 322 continue
321 323 return s
322 324
323 325 def modifies(repo, subset, x):
324 326 pat = getstring(x, "modifies wants a pattern")
325 327 return checkstatus(repo, subset, pat, 0)
326 328
327 329 def adds(repo, subset, x):
328 330 pat = getstring(x, "adds wants a pattern")
329 331 return checkstatus(repo, subset, pat, 1)
330 332
331 333 def removes(repo, subset, x):
332 334 pat = getstring(x, "removes wants a pattern")
333 335 return checkstatus(repo, subset, pat, 2)
334 336
335 337 def merge(repo, subset, x):
336 338 if x:
337 339 raise "merge takes no args"
338 340 cl = repo.changelog
339 341 return [r for r in subset if cl.parentrevs(r)[1] != -1]
340 342
341 343 def closed(repo, subset, x):
342 344 return [r for r in subset if repo[r].extra('close')]
343 345
344 346 def head(repo, subset, x):
345 347 hs = set()
346 348 for b, ls in repo.branchmap().iteritems():
347 349 hs.update(repo[h].rev() for h in ls)
348 350 return [r for r in subset if r in hs]
349 351
350 352 def reverse(repo, subset, x):
351 353 l = getset(repo, subset, x)
352 354 l.reverse()
353 355 return l
354 356
355 357 def sort(repo, subset, x):
356 358 l = getlist(x)
357 359 keys = "rev"
358 360 if len(l) == 2:
359 361 keys = getstring(l[1], "sort spec must be a string")
360 362
361 363 s = l[0]
362 364 keys = keys.split()
363 365 l = []
364 366 def invert(s):
365 367 return "".join(chr(255 - ord(c)) for c in s)
366 368 for r in getset(repo, subset, s):
367 369 c = repo[r]
368 370 e = []
369 371 for k in keys:
370 372 if k == 'rev':
371 373 e.append(r)
372 374 elif k == '-rev':
373 375 e.append(-r)
374 376 elif k == 'branch':
375 377 e.append(c.branch())
376 378 elif k == '-branch':
377 379 e.append(invert(c.branch()))
378 380 elif k == 'desc':
379 381 e.append(c.description())
380 382 elif k == '-desc':
381 383 e.append(invert(c.description()))
382 384 elif k in 'user author':
383 385 e.append(c.user())
384 386 elif k in '-user -author':
385 387 e.append(invert(c.user()))
386 388 elif k == 'date':
387 389 e.append(c.date()[0])
388 390 elif k == '-date':
389 391 e.append(-c.date()[0])
390 392 else:
391 393 raise "unknown sort key %r" % k
392 394 e.append(r)
393 395 l.append(e)
394 396 l.sort()
395 397 return [e[-1] for e in l]
396 398
397 399 def getall(repo, subset, x):
398 400 return subset
399 401
400 402 def heads(repo, subset, x):
401 403 s = getset(repo, subset, x)
402 404 ps = set(parents(repo, subset, x))
403 405 return [r for r in s if r not in ps]
404 406
405 407 def roots(repo, subset, x):
406 408 s = getset(repo, subset, x)
407 409 cs = set(children(repo, subset, x))
408 410 return [r for r in s if r not in cs]
409 411
410 412 def outgoing(repo, subset, x):
411 413 l = getlist(x)
412 414 if len(l) == 1:
413 415 dest = getstring(l[0], "outgoing wants a repo path")
414 416 else:
415 417 dest = ''
416 418 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
417 419 dest, branches = hg.parseurl(dest)
418 420 other = hg.repository(hg.remoteui(repo, {}), dest)
419 421 repo.ui.pushbuffer()
420 422 o = repo.findoutgoing(other)
421 423 repo.ui.popbuffer()
422 424 cl = repo.changelog
423 425 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, None)[0]])
424 426 print 'out', dest, o
425 427 return [r for r in subset if r in o]
426 428
427 429 def tagged(repo, subset, x):
428 430 cl = repo.changelog
429 431 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
430 432 return [r for r in subset if r in s]
431 433
432 434 symbols = {
433 435 "ancestor": ancestor,
434 436 "ancestors": ancestors,
435 437 "descendants": descendants,
436 438 "follow": follow,
437 439 "merge": merge,
438 440 "reverse": reverse,
439 441 "sort": sort,
440 442 "branch": branch,
441 443 "keyword": keyword,
442 444 "author": author,
443 445 "user": author,
444 446 "date": date,
445 447 "grep": grep,
446 448 "p1": p1,
447 449 "p2": p2,
448 450 "parents": parents,
449 451 "children": children,
450 452 "max": maxrev,
451 453 "limit": limit,
452 454 "file": hasfile,
453 455 "contains": contains,
454 456 "heads": heads,
455 457 "roots": roots,
456 458 "all": getall,
457 459 "closed": closed,
458 460 "head": head,
459 461 "modifies": modifies,
460 462 "adds": adds,
461 463 "removes": removes,
462 464 "outgoing": outgoing,
463 465 "tagged": tagged,
464 466 }
465 467
466 468 methods = {
467 469 "negate": negate,
468 470 "range": rangeset,
469 471 "string": stringset,
470 472 "symbol": symbolset,
471 473 "and": andset,
472 474 "or": orset,
473 475 "not": notset,
474 476 "list": listset,
475 477 "func": func,
476 478 }
477 479
478 480 def optimize(x, small):
479 481 if x == None:
480 482 return 0, x
481 483
482 484 smallbonus = 1
483 485 if small:
484 486 smallbonus = .5
485 487
486 488 op = x[0]
487 489 if op == '-':
488 490 return optimize(('and', x[1], ('not', x[2])), small)
489 491 elif op == 'dagrange':
490 492 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
491 493 ('func', ('symbol', 'ancestors'), x[2])), small)
492 494 elif op == 'dagrangepre':
493 495 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
494 496 elif op == 'dagrangepost':
495 497 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
496 498 elif op == 'rangepre':
497 499 return optimize(('range', ('string', '0'), x[1]), small)
498 500 elif op == 'rangepost':
499 501 return optimize(('range', x[1], ('string', 'tip')), small)
500 502 elif op in 'string symbol negate':
501 503 return smallbonus, x # single revisions are small
502 504 elif op == 'and' or op == 'dagrange':
503 505 wa, ta = optimize(x[1], True)
504 506 wb, tb = optimize(x[2], True)
505 507 w = min(wa, wb)
506 508 if wa > wb:
507 509 return w, (op, tb, ta)
508 510 return w, (op, ta, tb)
509 511 elif op == 'or':
510 512 wa, ta = optimize(x[1], False)
511 513 wb, tb = optimize(x[2], False)
512 514 if wb < wa:
513 515 wb, wa = wa, wb
514 516 return max(wa, wb), (op, ta, tb)
515 517 elif op == 'not':
516 518 o = optimize(x[1], not small)
517 519 return o[0], (op, o[1])
518 520 elif op == 'group':
519 521 return optimize(x[1], small)
520 elif op in 'rangepre rangepost dagrangepre dagrangepost':
521 wa, ta = optimize(x[1], small)
522 return wa + 1, (op, ta)
523 522 elif op in 'range list':
524 523 wa, ta = optimize(x[1], small)
525 524 wb, tb = optimize(x[2], small)
526 525 return wa + wb, (op, ta, tb)
527 526 elif op == 'func':
528 527 f = getstring(x[1], "not a symbol")
529 528 wa, ta = optimize(x[2], small)
530 529 if f in "grep date user author keyword branch file":
531 530 w = 10 # slow
532 531 elif f in "modifies adds removes outgoing":
533 532 w = 30 # slower
534 533 elif f == "contains":
535 534 w = 100 # very slow
536 535 elif f == "ancestor":
537 536 w = 1 * smallbonus
538 537 elif f == "reverse limit":
539 538 w = 0
540 539 elif f in "sort":
541 540 w = 10 # assume most sorts look at changelog
542 541 else:
543 542 w = 1
544 543 return w + wa, (op, x[1], ta)
545 544 return 1, x
546 545
547 546 parse = parser.parser(tokenize, elements).parse
548 547
549 548 def match(spec):
550 549 tree = parse(spec)
551 550 weight, tree = optimize(tree, True)
552 551 def mfunc(repo, subset):
553 552 return getset(repo, subset, tree)
554 553 return mfunc
@@ -1,103 +1,103 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "bookmarks=" >> $HGRCPATH
5 5
6 6 hg init
7 7
8 8 echo % no bookmarks
9 9 hg bookmarks
10 10
11 11 echo % bookmark rev -1
12 12 hg bookmark X
13 13
14 14 echo % list bookmarks
15 15 hg bookmarks
16 16
17 17 echo % list bookmarks with color
18 18 hg --config extensions.color= --config color.mode=ansi \
19 19 bookmarks --color=always
20 20
21 21 echo a > a
22 22 hg add a
23 23 hg commit -m 0
24 24
25 25 echo % bookmark X moved to rev 0
26 26 hg bookmarks
27 27
28 28 echo % look up bookmark
29 29 hg log -r X
30 30
31 31 echo % second bookmark for rev 0
32 32 hg bookmark X2
33 33
34 34 echo % bookmark rev -1 again
35 35 hg bookmark -r null Y
36 36
37 37 echo % list bookmarks
38 38 hg bookmarks
39 39
40 40 echo b > b
41 41 hg add b
42 42 hg commit -m 1
43 43
44 44 echo % bookmarks X and X2 moved to rev 1, Y at rev -1
45 45 hg bookmarks
46 46
47 47 echo % bookmark rev 0 again
48 48 hg bookmark -r 0 Z
49 49
50 50 echo c > c
51 51 hg add c
52 52 hg commit -m 2
53 53
54 54 echo % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
55 55 hg bookmarks
56 56
57 57 echo % rename nonexistent bookmark
58 58 hg bookmark -m A B
59 59
60 60 echo % rename to existent bookmark
61 61 hg bookmark -m X Y
62 62
63 63 echo % force rename to existent bookmark
64 64 hg bookmark -f -m X Y
65 65
66 66 echo % list bookmarks
67 67 hg bookmark
68 68
69 69 echo % rename without new name
70 70 hg bookmark -m Y
71 71
72 72 echo % delete without name
73 73 hg bookmark -d
74 74
75 75 echo % delete nonexistent bookmark
76 76 hg bookmark -d A
77 77
78 78 echo % bookmark name with spaces should be stripped
79 79 hg bookmark ' x y '
80 80
81 81 echo % list bookmarks
82 82 hg bookmarks
83 83
84 84 echo % look up stripped bookmark name
85 hg log -r 'x y'
85 hg log -r '"x y"'
86 86
87 87 echo % reject bookmark name with newline
88 88 hg bookmark '
89 89 '
90 90
91 91 echo % bookmark with existing name
92 92 hg bookmark Z
93 93
94 94 echo % force bookmark with existing name
95 95 hg bookmark -f Z
96 96
97 97 echo % list bookmarks
98 98 hg bookmark
99 99
100 100 echo % revision but no bookmark name
101 101 hg bookmark -r .
102 102
103 103 true
General Comments 0
You need to be logged in to leave comments. Login now