##// END OF EJS Templates
revset: optimize "draft() & ::x" pattern...
Jun Wu -
r34067:c6c8a52e default
parent child Browse files
Show More
@@ -1,497 +1,519 b''
1 1 # dagop.py - graph ancestry and topology algorithm for revset
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 from __future__ import absolute_import
9 9
10 10 import heapq
11 11
12 12 from . import (
13 13 error,
14 14 mdiff,
15 15 node,
16 16 patch,
17 17 smartset,
18 18 )
19 19
20 20 baseset = smartset.baseset
21 21 generatorset = smartset.generatorset
22 22
23 23 # possible maximum depth between null and wdir()
24 24 _maxlogdepth = 0x80000000
25 25
26 26 def _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse):
27 27 """Walk DAG using 'pfunc' from the given 'revs' nodes
28 28
29 29 'pfunc(rev)' should return the parent/child revisions of the given 'rev'
30 30 if 'reverse' is True/False respectively.
31 31
32 32 Scan ends at the stopdepth (exlusive) if specified. Revisions found
33 33 earlier than the startdepth are omitted.
34 34 """
35 35 if startdepth is None:
36 36 startdepth = 0
37 37 if stopdepth is None:
38 38 stopdepth = _maxlogdepth
39 39 if stopdepth == 0:
40 40 return
41 41 if stopdepth < 0:
42 42 raise error.ProgrammingError('negative stopdepth')
43 43 if reverse:
44 44 heapsign = -1 # max heap
45 45 else:
46 46 heapsign = +1 # min heap
47 47
48 48 # load input revs lazily to heap so earlier revisions can be yielded
49 49 # without fully computing the input revs
50 50 revs.sort(reverse)
51 51 irevs = iter(revs)
52 52 pendingheap = [] # [(heapsign * rev, depth), ...] (i.e. lower depth first)
53 53
54 54 inputrev = next(irevs, None)
55 55 if inputrev is not None:
56 56 heapq.heappush(pendingheap, (heapsign * inputrev, 0))
57 57
58 58 lastrev = None
59 59 while pendingheap:
60 60 currev, curdepth = heapq.heappop(pendingheap)
61 61 currev = heapsign * currev
62 62 if currev == inputrev:
63 63 inputrev = next(irevs, None)
64 64 if inputrev is not None:
65 65 heapq.heappush(pendingheap, (heapsign * inputrev, 0))
66 66 # rescan parents until curdepth >= startdepth because queued entries
67 67 # of the same revision are iterated from the lowest depth
68 68 foundnew = (currev != lastrev)
69 69 if foundnew and curdepth >= startdepth:
70 70 lastrev = currev
71 71 yield currev
72 72 pdepth = curdepth + 1
73 73 if foundnew and pdepth < stopdepth:
74 74 for prev in pfunc(currev):
75 75 if prev != node.nullrev:
76 76 heapq.heappush(pendingheap, (heapsign * prev, pdepth))
77 77
78 def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth):
78 def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth, cutfunc):
79 79 if followfirst:
80 80 cut = 1
81 81 else:
82 82 cut = None
83 83 cl = repo.changelog
84 def pfunc(rev):
84 def plainpfunc(rev):
85 85 try:
86 86 return cl.parentrevs(rev)[:cut]
87 87 except error.WdirUnsupported:
88 88 return (pctx.rev() for pctx in repo[rev].parents()[:cut])
89 if cutfunc is None:
90 pfunc = plainpfunc
91 else:
92 pfunc = lambda rev: [r for r in plainpfunc(rev) if not cutfunc(r)]
93 revs = revs.filter(lambda rev: not cutfunc(rev))
89 94 return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=True)
90 95
91 def revancestors(repo, revs, followfirst, startdepth=None, stopdepth=None):
96 def revancestors(repo, revs, followfirst=False, startdepth=None,
97 stopdepth=None, cutfunc=None):
92 98 """Like revlog.ancestors(), but supports additional options, includes
93 99 the given revs themselves, and returns a smartset
94 100
95 101 Scan ends at the stopdepth (exlusive) if specified. Revisions found
96 102 earlier than the startdepth are omitted.
103
104 If cutfunc is provided, it will be used to cut the traversal of the DAG.
105 When cutfunc(X) returns True, the DAG traversal stops - revision X and
106 X's ancestors in the traversal path will be skipped. This could be an
107 optimization sometimes.
108
109 Note: if Y is an ancestor of X, cutfunc(X) returning True does not
110 necessarily mean Y will also be cut. Usually cutfunc(Y) also wants to
111 return True in this case. For example,
112
113 D # revancestors(repo, D, cutfunc=lambda rev: rev == B)
114 |\ # will include "A", because the path D -> C -> A was not cut.
115 B C # If "B" gets cut, "A" might want to be cut too.
116 |/
117 A
97 118 """
98 gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth)
119 gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth,
120 cutfunc)
99 121 return generatorset(gen, iterasc=False)
100 122
101 123 def _genrevdescendants(repo, revs, followfirst):
102 124 if followfirst:
103 125 cut = 1
104 126 else:
105 127 cut = None
106 128
107 129 cl = repo.changelog
108 130 first = revs.min()
109 131 nullrev = node.nullrev
110 132 if first == nullrev:
111 133 # Are there nodes with a null first parent and a non-null
112 134 # second one? Maybe. Do we care? Probably not.
113 135 yield first
114 136 for i in cl:
115 137 yield i
116 138 else:
117 139 seen = set(revs)
118 140 for i in cl.revs(first):
119 141 if i in seen:
120 142 yield i
121 143 continue
122 144 for x in cl.parentrevs(i)[:cut]:
123 145 if x != nullrev and x in seen:
124 146 seen.add(i)
125 147 yield i
126 148 break
127 149
128 150 def _builddescendantsmap(repo, startrev, followfirst):
129 151 """Build map of 'rev -> child revs', offset from startrev"""
130 152 cl = repo.changelog
131 153 nullrev = node.nullrev
132 154 descmap = [[] for _rev in xrange(startrev, len(cl))]
133 155 for currev in cl.revs(startrev + 1):
134 156 p1rev, p2rev = cl.parentrevs(currev)
135 157 if p1rev >= startrev:
136 158 descmap[p1rev - startrev].append(currev)
137 159 if not followfirst and p2rev != nullrev and p2rev >= startrev:
138 160 descmap[p2rev - startrev].append(currev)
139 161 return descmap
140 162
141 163 def _genrevdescendantsofdepth(repo, revs, followfirst, startdepth, stopdepth):
142 164 startrev = revs.min()
143 165 descmap = _builddescendantsmap(repo, startrev, followfirst)
144 166 def pfunc(rev):
145 167 return descmap[rev - startrev]
146 168 return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=False)
147 169
148 170 def revdescendants(repo, revs, followfirst, startdepth=None, stopdepth=None):
149 171 """Like revlog.descendants() but supports additional options, includes
150 172 the given revs themselves, and returns a smartset
151 173
152 174 Scan ends at the stopdepth (exlusive) if specified. Revisions found
153 175 earlier than the startdepth are omitted.
154 176 """
155 177 if startdepth is None and stopdepth is None:
156 178 gen = _genrevdescendants(repo, revs, followfirst)
157 179 else:
158 180 gen = _genrevdescendantsofdepth(repo, revs, followfirst,
159 181 startdepth, stopdepth)
160 182 return generatorset(gen, iterasc=True)
161 183
162 184 def _reachablerootspure(repo, minroot, roots, heads, includepath):
163 185 """return (heads(::<roots> and ::<heads>))
164 186
165 187 If includepath is True, return (<roots>::<heads>)."""
166 188 if not roots:
167 189 return []
168 190 parentrevs = repo.changelog.parentrevs
169 191 roots = set(roots)
170 192 visit = list(heads)
171 193 reachable = set()
172 194 seen = {}
173 195 # prefetch all the things! (because python is slow)
174 196 reached = reachable.add
175 197 dovisit = visit.append
176 198 nextvisit = visit.pop
177 199 # open-code the post-order traversal due to the tiny size of
178 200 # sys.getrecursionlimit()
179 201 while visit:
180 202 rev = nextvisit()
181 203 if rev in roots:
182 204 reached(rev)
183 205 if not includepath:
184 206 continue
185 207 parents = parentrevs(rev)
186 208 seen[rev] = parents
187 209 for parent in parents:
188 210 if parent >= minroot and parent not in seen:
189 211 dovisit(parent)
190 212 if not reachable:
191 213 return baseset()
192 214 if not includepath:
193 215 return reachable
194 216 for rev in sorted(seen):
195 217 for parent in seen[rev]:
196 218 if parent in reachable:
197 219 reached(rev)
198 220 return reachable
199 221
200 222 def reachableroots(repo, roots, heads, includepath=False):
201 223 """return (heads(::<roots> and ::<heads>))
202 224
203 225 If includepath is True, return (<roots>::<heads>)."""
204 226 if not roots:
205 227 return baseset()
206 228 minroot = roots.min()
207 229 roots = list(roots)
208 230 heads = list(heads)
209 231 try:
210 232 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
211 233 except AttributeError:
212 234 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
213 235 revs = baseset(revs)
214 236 revs.sort()
215 237 return revs
216 238
217 239 def _changesrange(fctx1, fctx2, linerange2, diffopts):
218 240 """Return `(diffinrange, linerange1)` where `diffinrange` is True
219 241 if diff from fctx2 to fctx1 has changes in linerange2 and
220 242 `linerange1` is the new line range for fctx1.
221 243 """
222 244 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
223 245 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
224 246 diffinrange = any(stype == '!' for _, stype in filteredblocks)
225 247 return diffinrange, linerange1
226 248
227 249 def blockancestors(fctx, fromline, toline, followfirst=False):
228 250 """Yield ancestors of `fctx` with respect to the block of lines within
229 251 `fromline`-`toline` range.
230 252 """
231 253 diffopts = patch.diffopts(fctx._repo.ui)
232 254 introrev = fctx.introrev()
233 255 if fctx.rev() != introrev:
234 256 fctx = fctx.filectx(fctx.filenode(), changeid=introrev)
235 257 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
236 258 while visit:
237 259 c, linerange2 = visit.pop(max(visit))
238 260 pl = c.parents()
239 261 if followfirst:
240 262 pl = pl[:1]
241 263 if not pl:
242 264 # The block originates from the initial revision.
243 265 yield c, linerange2
244 266 continue
245 267 inrange = False
246 268 for p in pl:
247 269 inrangep, linerange1 = _changesrange(p, c, linerange2, diffopts)
248 270 inrange = inrange or inrangep
249 271 if linerange1[0] == linerange1[1]:
250 272 # Parent's linerange is empty, meaning that the block got
251 273 # introduced in this revision; no need to go futher in this
252 274 # branch.
253 275 continue
254 276 # Set _descendantrev with 'c' (a known descendant) so that, when
255 277 # _adjustlinkrev is called for 'p', it receives this descendant
256 278 # (as srcrev) instead possibly topmost introrev.
257 279 p._descendantrev = c.rev()
258 280 visit[p.linkrev(), p.filenode()] = p, linerange1
259 281 if inrange:
260 282 yield c, linerange2
261 283
262 284 def blockdescendants(fctx, fromline, toline):
263 285 """Yield descendants of `fctx` with respect to the block of lines within
264 286 `fromline`-`toline` range.
265 287 """
266 288 # First possibly yield 'fctx' if it has changes in range with respect to
267 289 # its parents.
268 290 try:
269 291 c, linerange1 = next(blockancestors(fctx, fromline, toline))
270 292 except StopIteration:
271 293 pass
272 294 else:
273 295 if c == fctx:
274 296 yield c, linerange1
275 297
276 298 diffopts = patch.diffopts(fctx._repo.ui)
277 299 fl = fctx.filelog()
278 300 seen = {fctx.filerev(): (fctx, (fromline, toline))}
279 301 for i in fl.descendants([fctx.filerev()]):
280 302 c = fctx.filectx(i)
281 303 inrange = False
282 304 for x in fl.parentrevs(i):
283 305 try:
284 306 p, linerange2 = seen[x]
285 307 except KeyError:
286 308 # nullrev or other branch
287 309 continue
288 310 inrangep, linerange1 = _changesrange(c, p, linerange2, diffopts)
289 311 inrange = inrange or inrangep
290 312 # If revision 'i' has been seen (it's a merge) and the line range
291 313 # previously computed differs from the one we just got, we take the
292 314 # surrounding interval. This is conservative but avoids loosing
293 315 # information.
294 316 if i in seen and seen[i][1] != linerange1:
295 317 lbs, ubs = zip(linerange1, seen[i][1])
296 318 linerange1 = min(lbs), max(ubs)
297 319 seen[i] = c, linerange1
298 320 if inrange:
299 321 yield c, linerange1
300 322
301 323 def toposort(revs, parentsfunc, firstbranch=()):
302 324 """Yield revisions from heads to roots one (topo) branch at a time.
303 325
304 326 This function aims to be used by a graph generator that wishes to minimize
305 327 the number of parallel branches and their interleaving.
306 328
307 329 Example iteration order (numbers show the "true" order in a changelog):
308 330
309 331 o 4
310 332 |
311 333 o 1
312 334 |
313 335 | o 3
314 336 | |
315 337 | o 2
316 338 |/
317 339 o 0
318 340
319 341 Note that the ancestors of merges are understood by the current
320 342 algorithm to be on the same branch. This means no reordering will
321 343 occur behind a merge.
322 344 """
323 345
324 346 ### Quick summary of the algorithm
325 347 #
326 348 # This function is based around a "retention" principle. We keep revisions
327 349 # in memory until we are ready to emit a whole branch that immediately
328 350 # "merges" into an existing one. This reduces the number of parallel
329 351 # branches with interleaved revisions.
330 352 #
331 353 # During iteration revs are split into two groups:
332 354 # A) revision already emitted
333 355 # B) revision in "retention". They are stored as different subgroups.
334 356 #
335 357 # for each REV, we do the following logic:
336 358 #
337 359 # 1) if REV is a parent of (A), we will emit it. If there is a
338 360 # retention group ((B) above) that is blocked on REV being
339 361 # available, we emit all the revisions out of that retention
340 362 # group first.
341 363 #
342 364 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
343 365 # available, if such subgroup exist, we add REV to it and the subgroup is
344 366 # now awaiting for REV.parents() to be available.
345 367 #
346 368 # 3) finally if no such group existed in (B), we create a new subgroup.
347 369 #
348 370 #
349 371 # To bootstrap the algorithm, we emit the tipmost revision (which
350 372 # puts it in group (A) from above).
351 373
352 374 revs.sort(reverse=True)
353 375
354 376 # Set of parents of revision that have been emitted. They can be considered
355 377 # unblocked as the graph generator is already aware of them so there is no
356 378 # need to delay the revisions that reference them.
357 379 #
358 380 # If someone wants to prioritize a branch over the others, pre-filling this
359 381 # set will force all other branches to wait until this branch is ready to be
360 382 # emitted.
361 383 unblocked = set(firstbranch)
362 384
363 385 # list of groups waiting to be displayed, each group is defined by:
364 386 #
365 387 # (revs: lists of revs waiting to be displayed,
366 388 # blocked: set of that cannot be displayed before those in 'revs')
367 389 #
368 390 # The second value ('blocked') correspond to parents of any revision in the
369 391 # group ('revs') that is not itself contained in the group. The main idea
370 392 # of this algorithm is to delay as much as possible the emission of any
371 393 # revision. This means waiting for the moment we are about to display
372 394 # these parents to display the revs in a group.
373 395 #
374 396 # This first implementation is smart until it encounters a merge: it will
375 397 # emit revs as soon as any parent is about to be emitted and can grow an
376 398 # arbitrary number of revs in 'blocked'. In practice this mean we properly
377 399 # retains new branches but gives up on any special ordering for ancestors
378 400 # of merges. The implementation can be improved to handle this better.
379 401 #
380 402 # The first subgroup is special. It corresponds to all the revision that
381 403 # were already emitted. The 'revs' lists is expected to be empty and the
382 404 # 'blocked' set contains the parents revisions of already emitted revision.
383 405 #
384 406 # You could pre-seed the <parents> set of groups[0] to a specific
385 407 # changesets to select what the first emitted branch should be.
386 408 groups = [([], unblocked)]
387 409 pendingheap = []
388 410 pendingset = set()
389 411
390 412 heapq.heapify(pendingheap)
391 413 heappop = heapq.heappop
392 414 heappush = heapq.heappush
393 415 for currentrev in revs:
394 416 # Heap works with smallest element, we want highest so we invert
395 417 if currentrev not in pendingset:
396 418 heappush(pendingheap, -currentrev)
397 419 pendingset.add(currentrev)
398 420 # iterates on pending rev until after the current rev have been
399 421 # processed.
400 422 rev = None
401 423 while rev != currentrev:
402 424 rev = -heappop(pendingheap)
403 425 pendingset.remove(rev)
404 426
405 427 # Seek for a subgroup blocked, waiting for the current revision.
406 428 matching = [i for i, g in enumerate(groups) if rev in g[1]]
407 429
408 430 if matching:
409 431 # The main idea is to gather together all sets that are blocked
410 432 # on the same revision.
411 433 #
412 434 # Groups are merged when a common blocking ancestor is
413 435 # observed. For example, given two groups:
414 436 #
415 437 # revs [5, 4] waiting for 1
416 438 # revs [3, 2] waiting for 1
417 439 #
418 440 # These two groups will be merged when we process
419 441 # 1. In theory, we could have merged the groups when
420 442 # we added 2 to the group it is now in (we could have
421 443 # noticed the groups were both blocked on 1 then), but
422 444 # the way it works now makes the algorithm simpler.
423 445 #
424 446 # We also always keep the oldest subgroup first. We can
425 447 # probably improve the behavior by having the longest set
426 448 # first. That way, graph algorithms could minimise the length
427 449 # of parallel lines their drawing. This is currently not done.
428 450 targetidx = matching.pop(0)
429 451 trevs, tparents = groups[targetidx]
430 452 for i in matching:
431 453 gr = groups[i]
432 454 trevs.extend(gr[0])
433 455 tparents |= gr[1]
434 456 # delete all merged subgroups (except the one we kept)
435 457 # (starting from the last subgroup for performance and
436 458 # sanity reasons)
437 459 for i in reversed(matching):
438 460 del groups[i]
439 461 else:
440 462 # This is a new head. We create a new subgroup for it.
441 463 targetidx = len(groups)
442 464 groups.append(([], {rev}))
443 465
444 466 gr = groups[targetidx]
445 467
446 468 # We now add the current nodes to this subgroups. This is done
447 469 # after the subgroup merging because all elements from a subgroup
448 470 # that relied on this rev must precede it.
449 471 #
450 472 # we also update the <parents> set to include the parents of the
451 473 # new nodes.
452 474 if rev == currentrev: # only display stuff in rev
453 475 gr[0].append(rev)
454 476 gr[1].remove(rev)
455 477 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
456 478 gr[1].update(parents)
457 479 for p in parents:
458 480 if p not in pendingset:
459 481 pendingset.add(p)
460 482 heappush(pendingheap, -p)
461 483
462 484 # Look for a subgroup to display
463 485 #
464 486 # When unblocked is empty (if clause), we were not waiting for any
465 487 # revisions during the first iteration (if no priority was given) or
466 488 # if we emitted a whole disconnected set of the graph (reached a
467 489 # root). In that case we arbitrarily take the oldest known
468 490 # subgroup. The heuristic could probably be better.
469 491 #
470 492 # Otherwise (elif clause) if the subgroup is blocked on
471 493 # a revision we just emitted, we can safely emit it as
472 494 # well.
473 495 if not unblocked:
474 496 if len(groups) > 1: # display other subset
475 497 targetidx = 1
476 498 gr = groups[1]
477 499 elif not gr[1] & unblocked:
478 500 gr = None
479 501
480 502 if gr is not None:
481 503 # update the set of awaited revisions with the one from the
482 504 # subgroup
483 505 unblocked |= gr[1]
484 506 # output all revisions in the subgroup
485 507 for r in gr[0]:
486 508 yield r
487 509 # delete the subgroup that you just output
488 510 # unless it is groups[0] in which case you just empty it.
489 511 if targetidx:
490 512 del groups[targetidx]
491 513 else:
492 514 gr[0][:] = []
493 515 # Check if we have some subgroup waiting for revisions we are not going to
494 516 # iterate over
495 517 for g in groups:
496 518 for r in g[0]:
497 519 yield r
@@ -1,2189 +1,2220 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 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 dagop,
15 15 destutil,
16 16 encoding,
17 17 error,
18 18 hbisect,
19 19 match as matchmod,
20 20 node,
21 21 obsolete as obsmod,
22 22 obsutil,
23 23 pathutil,
24 24 phases,
25 25 registrar,
26 26 repoview,
27 27 revsetlang,
28 28 scmutil,
29 29 smartset,
30 30 util,
31 31 )
32 32
33 33 # helpers for processing parsed tree
34 34 getsymbol = revsetlang.getsymbol
35 35 getstring = revsetlang.getstring
36 36 getinteger = revsetlang.getinteger
37 37 getboolean = revsetlang.getboolean
38 38 getlist = revsetlang.getlist
39 39 getrange = revsetlang.getrange
40 40 getargs = revsetlang.getargs
41 41 getargsdict = revsetlang.getargsdict
42 42
43 43 baseset = smartset.baseset
44 44 generatorset = smartset.generatorset
45 45 spanset = smartset.spanset
46 46 fullreposet = smartset.fullreposet
47 47
48 48 # Constants for ordering requirement, used in getset():
49 49 #
50 50 # If 'define', any nested functions and operations MAY change the ordering of
51 51 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
52 52 # it). If 'follow', any nested functions and operations MUST take the ordering
53 53 # specified by the first operand to the '&' operator.
54 54 #
55 55 # For instance,
56 56 #
57 57 # X & (Y | Z)
58 58 # ^ ^^^^^^^
59 59 # | follow
60 60 # define
61 61 #
62 62 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
63 63 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
64 64 #
65 65 # 'any' means the order doesn't matter. For instance,
66 66 #
67 67 # (X & !Y) | ancestors(Z)
68 68 # ^ ^
69 69 # any any
70 70 #
71 71 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
72 72 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
73 73 # since 'ancestors' does not care about the order of its argument.
74 74 #
75 75 # Currently, most revsets do not care about the order, so 'define' is
76 76 # equivalent to 'follow' for them, and the resulting order is based on the
77 77 # 'subset' parameter passed down to them:
78 78 #
79 79 # m = revset.match(...)
80 80 # m(repo, subset, order=defineorder)
81 81 # ^^^^^^
82 82 # For most revsets, 'define' means using the order this subset provides
83 83 #
84 84 # There are a few revsets that always redefine the order if 'define' is
85 85 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
86 86 anyorder = 'any' # don't care the order, could be even random-shuffled
87 87 defineorder = 'define' # ALWAYS redefine, or ALWAYS follow the current order
88 88 followorder = 'follow' # MUST follow the current order
89 89
90 90 # helpers
91 91
92 92 def getset(repo, subset, x, order=defineorder):
93 93 if not x:
94 94 raise error.ParseError(_("missing argument"))
95 95 return methods[x[0]](repo, subset, *x[1:], order=order)
96 96
97 97 def _getrevsource(repo, r):
98 98 extra = repo[r].extra()
99 99 for label in ('source', 'transplant_source', 'rebase_source'):
100 100 if label in extra:
101 101 try:
102 102 return repo[extra[label]].rev()
103 103 except error.RepoLookupError:
104 104 pass
105 105 return None
106 106
107 107 # operator methods
108 108
109 109 def stringset(repo, subset, x, order):
110 110 x = scmutil.intrev(repo[x])
111 111 if (x in subset
112 112 or x == node.nullrev and isinstance(subset, fullreposet)):
113 113 return baseset([x])
114 114 return baseset()
115 115
116 116 def rangeset(repo, subset, x, y, order):
117 117 m = getset(repo, fullreposet(repo), x)
118 118 n = getset(repo, fullreposet(repo), y)
119 119
120 120 if not m or not n:
121 121 return baseset()
122 122 return _makerangeset(repo, subset, m.first(), n.last(), order)
123 123
124 124 def rangeall(repo, subset, x, order):
125 125 assert x is None
126 126 return _makerangeset(repo, subset, 0, len(repo) - 1, order)
127 127
128 128 def rangepre(repo, subset, y, order):
129 129 # ':y' can't be rewritten to '0:y' since '0' may be hidden
130 130 n = getset(repo, fullreposet(repo), y)
131 131 if not n:
132 132 return baseset()
133 133 return _makerangeset(repo, subset, 0, n.last(), order)
134 134
135 135 def rangepost(repo, subset, x, order):
136 136 m = getset(repo, fullreposet(repo), x)
137 137 if not m:
138 138 return baseset()
139 139 return _makerangeset(repo, subset, m.first(), len(repo) - 1, order)
140 140
141 141 def _makerangeset(repo, subset, m, n, order):
142 142 if m == n:
143 143 r = baseset([m])
144 144 elif n == node.wdirrev:
145 145 r = spanset(repo, m, len(repo)) + baseset([n])
146 146 elif m == node.wdirrev:
147 147 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
148 148 elif m < n:
149 149 r = spanset(repo, m, n + 1)
150 150 else:
151 151 r = spanset(repo, m, n - 1)
152 152
153 153 if order == defineorder:
154 154 return r & subset
155 155 else:
156 156 # carrying the sorting over when possible would be more efficient
157 157 return subset & r
158 158
159 159 def dagrange(repo, subset, x, y, order):
160 160 r = fullreposet(repo)
161 161 xs = dagop.reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
162 162 includepath=True)
163 163 return subset & xs
164 164
165 165 def andset(repo, subset, x, y, order):
166 166 if order == anyorder:
167 167 yorder = anyorder
168 168 else:
169 169 yorder = followorder
170 170 return getset(repo, getset(repo, subset, x, order), y, yorder)
171 171
172 172 def andsmallyset(repo, subset, x, y, order):
173 173 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
174 174 if order == anyorder:
175 175 yorder = anyorder
176 176 else:
177 177 yorder = followorder
178 178 return getset(repo, getset(repo, subset, y, yorder), x, order)
179 179
180 180 def differenceset(repo, subset, x, y, order):
181 181 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
182 182
183 183 def _orsetlist(repo, subset, xs, order):
184 184 assert xs
185 185 if len(xs) == 1:
186 186 return getset(repo, subset, xs[0], order)
187 187 p = len(xs) // 2
188 188 a = _orsetlist(repo, subset, xs[:p], order)
189 189 b = _orsetlist(repo, subset, xs[p:], order)
190 190 return a + b
191 191
192 192 def orset(repo, subset, x, order):
193 193 xs = getlist(x)
194 194 if order == followorder:
195 195 # slow path to take the subset order
196 196 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
197 197 else:
198 198 return _orsetlist(repo, subset, xs, order)
199 199
200 200 def notset(repo, subset, x, order):
201 201 return subset - getset(repo, subset, x, anyorder)
202 202
203 203 def relationset(repo, subset, x, y, order):
204 204 raise error.ParseError(_("can't use a relation in this context"))
205 205
206 206 def relsubscriptset(repo, subset, x, y, z, order):
207 207 # this is pretty basic implementation of 'x#y[z]' operator, still
208 208 # experimental so undocumented. see the wiki for further ideas.
209 209 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
210 210 rel = getsymbol(y)
211 211 n = getinteger(z, _("relation subscript must be an integer"))
212 212
213 213 # TODO: perhaps this should be a table of relation functions
214 214 if rel in ('g', 'generations'):
215 215 # TODO: support range, rewrite tests, and drop startdepth argument
216 216 # from ancestors() and descendants() predicates
217 217 if n <= 0:
218 218 n = -n
219 219 return _ancestors(repo, subset, x, startdepth=n, stopdepth=n + 1)
220 220 else:
221 221 return _descendants(repo, subset, x, startdepth=n, stopdepth=n + 1)
222 222
223 223 raise error.UnknownIdentifier(rel, ['generations'])
224 224
225 225 def subscriptset(repo, subset, x, y, order):
226 226 raise error.ParseError(_("can't use a subscript in this context"))
227 227
228 228 def listset(repo, subset, *xs, **opts):
229 229 raise error.ParseError(_("can't use a list in this context"),
230 230 hint=_('see hg help "revsets.x or y"'))
231 231
232 232 def keyvaluepair(repo, subset, k, v, order):
233 233 raise error.ParseError(_("can't use a key-value pair in this context"))
234 234
235 235 def func(repo, subset, a, b, order):
236 236 f = getsymbol(a)
237 237 if f in symbols:
238 238 func = symbols[f]
239 239 if getattr(func, '_takeorder', False):
240 240 return func(repo, subset, b, order)
241 241 return func(repo, subset, b)
242 242
243 243 keep = lambda fn: getattr(fn, '__doc__', None) is not None
244 244
245 245 syms = [s for (s, fn) in symbols.items() if keep(fn)]
246 246 raise error.UnknownIdentifier(f, syms)
247 247
248 248 # functions
249 249
250 250 # symbols are callables like:
251 251 # fn(repo, subset, x)
252 252 # with:
253 253 # repo - current repository instance
254 254 # subset - of revisions to be examined
255 255 # x - argument in tree form
256 256 symbols = {}
257 257
258 258 # symbols which can't be used for a DoS attack for any given input
259 259 # (e.g. those which accept regexes as plain strings shouldn't be included)
260 260 # functions that just return a lot of changesets (like all) don't count here
261 261 safesymbols = set()
262 262
263 263 predicate = registrar.revsetpredicate()
264 264
265 265 @predicate('_destupdate')
266 266 def _destupdate(repo, subset, x):
267 267 # experimental revset for update destination
268 268 args = getargsdict(x, 'limit', 'clean')
269 269 return subset & baseset([destutil.destupdate(repo, **args)[0]])
270 270
271 271 @predicate('_destmerge')
272 272 def _destmerge(repo, subset, x):
273 273 # experimental revset for merge destination
274 274 sourceset = None
275 275 if x is not None:
276 276 sourceset = getset(repo, fullreposet(repo), x)
277 277 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
278 278
279 279 @predicate('adds(pattern)', safe=True)
280 280 def adds(repo, subset, x):
281 281 """Changesets that add a file matching pattern.
282 282
283 283 The pattern without explicit kind like ``glob:`` is expected to be
284 284 relative to the current directory and match against a file or a
285 285 directory.
286 286 """
287 287 # i18n: "adds" is a keyword
288 288 pat = getstring(x, _("adds requires a pattern"))
289 289 return checkstatus(repo, subset, pat, 1)
290 290
291 291 @predicate('ancestor(*changeset)', safe=True)
292 292 def ancestor(repo, subset, x):
293 293 """A greatest common ancestor of the changesets.
294 294
295 295 Accepts 0 or more changesets.
296 296 Will return empty list when passed no args.
297 297 Greatest common ancestor of a single changeset is that changeset.
298 298 """
299 299 # i18n: "ancestor" is a keyword
300 300 l = getlist(x)
301 301 rl = fullreposet(repo)
302 302 anc = None
303 303
304 304 # (getset(repo, rl, i) for i in l) generates a list of lists
305 305 for revs in (getset(repo, rl, i) for i in l):
306 306 for r in revs:
307 307 if anc is None:
308 308 anc = repo[r]
309 309 else:
310 310 anc = anc.ancestor(repo[r])
311 311
312 312 if anc is not None and anc.rev() in subset:
313 313 return baseset([anc.rev()])
314 314 return baseset()
315 315
316 316 def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
317 317 stopdepth=None):
318 318 heads = getset(repo, fullreposet(repo), x)
319 319 if not heads:
320 320 return baseset()
321 321 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
322 322 return subset & s
323 323
324 324 @predicate('ancestors(set[, depth])', safe=True)
325 325 def ancestors(repo, subset, x):
326 326 """Changesets that are ancestors of changesets in set, including the
327 327 given changesets themselves.
328 328
329 329 If depth is specified, the result only includes changesets up to
330 330 the specified generation.
331 331 """
332 332 # startdepth is for internal use only until we can decide the UI
333 333 args = getargsdict(x, 'ancestors', 'set depth startdepth')
334 334 if 'set' not in args:
335 335 # i18n: "ancestors" is a keyword
336 336 raise error.ParseError(_('ancestors takes at least 1 argument'))
337 337 startdepth = stopdepth = None
338 338 if 'startdepth' in args:
339 339 n = getinteger(args['startdepth'],
340 340 "ancestors expects an integer startdepth")
341 341 if n < 0:
342 342 raise error.ParseError("negative startdepth")
343 343 startdepth = n
344 344 if 'depth' in args:
345 345 # i18n: "ancestors" is a keyword
346 346 n = getinteger(args['depth'], _("ancestors expects an integer depth"))
347 347 if n < 0:
348 348 raise error.ParseError(_("negative depth"))
349 349 stopdepth = n + 1
350 350 return _ancestors(repo, subset, args['set'],
351 351 startdepth=startdepth, stopdepth=stopdepth)
352 352
353 353 @predicate('_firstancestors', safe=True)
354 354 def _firstancestors(repo, subset, x):
355 355 # ``_firstancestors(set)``
356 356 # Like ``ancestors(set)`` but follows only the first parents.
357 357 return _ancestors(repo, subset, x, followfirst=True)
358 358
359 359 def _childrenspec(repo, subset, x, n, order):
360 360 """Changesets that are the Nth child of a changeset
361 361 in set.
362 362 """
363 363 cs = set()
364 364 for r in getset(repo, fullreposet(repo), x):
365 365 for i in range(n):
366 366 c = repo[r].children()
367 367 if len(c) == 0:
368 368 break
369 369 if len(c) > 1:
370 370 raise error.RepoLookupError(
371 371 _("revision in set has more than one child"))
372 372 r = c[0].rev()
373 373 else:
374 374 cs.add(r)
375 375 return subset & cs
376 376
377 377 def ancestorspec(repo, subset, x, n, order):
378 378 """``set~n``
379 379 Changesets that are the Nth ancestor (first parents only) of a changeset
380 380 in set.
381 381 """
382 382 n = getinteger(n, _("~ expects a number"))
383 383 if n < 0:
384 384 # children lookup
385 385 return _childrenspec(repo, subset, x, -n, order)
386 386 ps = set()
387 387 cl = repo.changelog
388 388 for r in getset(repo, fullreposet(repo), x):
389 389 for i in range(n):
390 390 try:
391 391 r = cl.parentrevs(r)[0]
392 392 except error.WdirUnsupported:
393 393 r = repo[r].parents()[0].rev()
394 394 ps.add(r)
395 395 return subset & ps
396 396
397 397 @predicate('author(string)', safe=True)
398 398 def author(repo, subset, x):
399 399 """Alias for ``user(string)``.
400 400 """
401 401 # i18n: "author" is a keyword
402 402 n = getstring(x, _("author requires a string"))
403 403 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
404 404 return subset.filter(lambda x: matcher(repo[x].user()),
405 405 condrepr=('<user %r>', n))
406 406
407 407 @predicate('bisect(string)', safe=True)
408 408 def bisect(repo, subset, x):
409 409 """Changesets marked in the specified bisect status:
410 410
411 411 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
412 412 - ``goods``, ``bads`` : csets topologically good/bad
413 413 - ``range`` : csets taking part in the bisection
414 414 - ``pruned`` : csets that are goods, bads or skipped
415 415 - ``untested`` : csets whose fate is yet unknown
416 416 - ``ignored`` : csets ignored due to DAG topology
417 417 - ``current`` : the cset currently being bisected
418 418 """
419 419 # i18n: "bisect" is a keyword
420 420 status = getstring(x, _("bisect requires a string")).lower()
421 421 state = set(hbisect.get(repo, status))
422 422 return subset & state
423 423
424 424 # Backward-compatibility
425 425 # - no help entry so that we do not advertise it any more
426 426 @predicate('bisected', safe=True)
427 427 def bisected(repo, subset, x):
428 428 return bisect(repo, subset, x)
429 429
430 430 @predicate('bookmark([name])', safe=True)
431 431 def bookmark(repo, subset, x):
432 432 """The named bookmark or all bookmarks.
433 433
434 434 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
435 435 """
436 436 # i18n: "bookmark" is a keyword
437 437 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
438 438 if args:
439 439 bm = getstring(args[0],
440 440 # i18n: "bookmark" is a keyword
441 441 _('the argument to bookmark must be a string'))
442 442 kind, pattern, matcher = util.stringmatcher(bm)
443 443 bms = set()
444 444 if kind == 'literal':
445 445 bmrev = repo._bookmarks.get(pattern, None)
446 446 if not bmrev:
447 447 raise error.RepoLookupError(_("bookmark '%s' does not exist")
448 448 % pattern)
449 449 bms.add(repo[bmrev].rev())
450 450 else:
451 451 matchrevs = set()
452 452 for name, bmrev in repo._bookmarks.iteritems():
453 453 if matcher(name):
454 454 matchrevs.add(bmrev)
455 455 if not matchrevs:
456 456 raise error.RepoLookupError(_("no bookmarks exist"
457 457 " that match '%s'") % pattern)
458 458 for bmrev in matchrevs:
459 459 bms.add(repo[bmrev].rev())
460 460 else:
461 461 bms = {repo[r].rev() for r in repo._bookmarks.values()}
462 462 bms -= {node.nullrev}
463 463 return subset & bms
464 464
465 465 @predicate('branch(string or set)', safe=True)
466 466 def branch(repo, subset, x):
467 467 """
468 468 All changesets belonging to the given branch or the branches of the given
469 469 changesets.
470 470
471 471 Pattern matching is supported for `string`. See
472 472 :hg:`help revisions.patterns`.
473 473 """
474 474 getbi = repo.revbranchcache().branchinfo
475 475 def getbranch(r):
476 476 try:
477 477 return getbi(r)[0]
478 478 except error.WdirUnsupported:
479 479 return repo[r].branch()
480 480
481 481 try:
482 482 b = getstring(x, '')
483 483 except error.ParseError:
484 484 # not a string, but another revspec, e.g. tip()
485 485 pass
486 486 else:
487 487 kind, pattern, matcher = util.stringmatcher(b)
488 488 if kind == 'literal':
489 489 # note: falls through to the revspec case if no branch with
490 490 # this name exists and pattern kind is not specified explicitly
491 491 if pattern in repo.branchmap():
492 492 return subset.filter(lambda r: matcher(getbranch(r)),
493 493 condrepr=('<branch %r>', b))
494 494 if b.startswith('literal:'):
495 495 raise error.RepoLookupError(_("branch '%s' does not exist")
496 496 % pattern)
497 497 else:
498 498 return subset.filter(lambda r: matcher(getbranch(r)),
499 499 condrepr=('<branch %r>', b))
500 500
501 501 s = getset(repo, fullreposet(repo), x)
502 502 b = set()
503 503 for r in s:
504 504 b.add(getbranch(r))
505 505 c = s.__contains__
506 506 return subset.filter(lambda r: c(r) or getbranch(r) in b,
507 507 condrepr=lambda: '<branch %r>' % sorted(b))
508 508
509 509 @predicate('bumped()', safe=True)
510 510 def bumped(repo, subset, x):
511 511 msg = ("'bumped()' is deprecated, "
512 512 "use 'phasedivergent()'")
513 513 repo.ui.deprecwarn(msg, '4.4')
514 514
515 515 return phasedivergent(repo, subset, x)
516 516
517 517 @predicate('phasedivergent()', safe=True)
518 518 def phasedivergent(repo, subset, x):
519 519 """Mutable changesets marked as successors of public changesets.
520 520
521 521 Only non-public and non-obsolete changesets can be `phasedivergent`.
522 522 (EXPERIMENTAL)
523 523 """
524 524 # i18n: "phasedivergent" is a keyword
525 525 getargs(x, 0, 0, _("phasedivergent takes no arguments"))
526 526 phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
527 527 return subset & phasedivergent
528 528
529 529 @predicate('bundle()', safe=True)
530 530 def bundle(repo, subset, x):
531 531 """Changesets in the bundle.
532 532
533 533 Bundle must be specified by the -R option."""
534 534
535 535 try:
536 536 bundlerevs = repo.changelog.bundlerevs
537 537 except AttributeError:
538 538 raise error.Abort(_("no bundle provided - specify with -R"))
539 539 return subset & bundlerevs
540 540
541 541 def checkstatus(repo, subset, pat, field):
542 542 hasset = matchmod.patkind(pat) == 'set'
543 543
544 544 mcache = [None]
545 545 def matches(x):
546 546 c = repo[x]
547 547 if not mcache[0] or hasset:
548 548 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
549 549 m = mcache[0]
550 550 fname = None
551 551 if not m.anypats() and len(m.files()) == 1:
552 552 fname = m.files()[0]
553 553 if fname is not None:
554 554 if fname not in c.files():
555 555 return False
556 556 else:
557 557 for f in c.files():
558 558 if m(f):
559 559 break
560 560 else:
561 561 return False
562 562 files = repo.status(c.p1().node(), c.node())[field]
563 563 if fname is not None:
564 564 if fname in files:
565 565 return True
566 566 else:
567 567 for f in files:
568 568 if m(f):
569 569 return True
570 570
571 571 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
572 572
573 573 def _children(repo, subset, parentset):
574 574 if not parentset:
575 575 return baseset()
576 576 cs = set()
577 577 pr = repo.changelog.parentrevs
578 578 minrev = parentset.min()
579 579 nullrev = node.nullrev
580 580 for r in subset:
581 581 if r <= minrev:
582 582 continue
583 583 p1, p2 = pr(r)
584 584 if p1 in parentset:
585 585 cs.add(r)
586 586 if p2 != nullrev and p2 in parentset:
587 587 cs.add(r)
588 588 return baseset(cs)
589 589
590 590 @predicate('children(set)', safe=True)
591 591 def children(repo, subset, x):
592 592 """Child changesets of changesets in set.
593 593 """
594 594 s = getset(repo, fullreposet(repo), x)
595 595 cs = _children(repo, subset, s)
596 596 return subset & cs
597 597
598 598 @predicate('closed()', safe=True)
599 599 def closed(repo, subset, x):
600 600 """Changeset is closed.
601 601 """
602 602 # i18n: "closed" is a keyword
603 603 getargs(x, 0, 0, _("closed takes no arguments"))
604 604 return subset.filter(lambda r: repo[r].closesbranch(),
605 605 condrepr='<branch closed>')
606 606
607 607 @predicate('contains(pattern)')
608 608 def contains(repo, subset, x):
609 609 """The revision's manifest contains a file matching pattern (but might not
610 610 modify it). See :hg:`help patterns` for information about file patterns.
611 611
612 612 The pattern without explicit kind like ``glob:`` is expected to be
613 613 relative to the current directory and match against a file exactly
614 614 for efficiency.
615 615 """
616 616 # i18n: "contains" is a keyword
617 617 pat = getstring(x, _("contains requires a pattern"))
618 618
619 619 def matches(x):
620 620 if not matchmod.patkind(pat):
621 621 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
622 622 if pats in repo[x]:
623 623 return True
624 624 else:
625 625 c = repo[x]
626 626 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
627 627 for f in c.manifest():
628 628 if m(f):
629 629 return True
630 630 return False
631 631
632 632 return subset.filter(matches, condrepr=('<contains %r>', pat))
633 633
634 634 @predicate('converted([id])', safe=True)
635 635 def converted(repo, subset, x):
636 636 """Changesets converted from the given identifier in the old repository if
637 637 present, or all converted changesets if no identifier is specified.
638 638 """
639 639
640 640 # There is exactly no chance of resolving the revision, so do a simple
641 641 # string compare and hope for the best
642 642
643 643 rev = None
644 644 # i18n: "converted" is a keyword
645 645 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
646 646 if l:
647 647 # i18n: "converted" is a keyword
648 648 rev = getstring(l[0], _('converted requires a revision'))
649 649
650 650 def _matchvalue(r):
651 651 source = repo[r].extra().get('convert_revision', None)
652 652 return source is not None and (rev is None or source.startswith(rev))
653 653
654 654 return subset.filter(lambda r: _matchvalue(r),
655 655 condrepr=('<converted %r>', rev))
656 656
657 657 @predicate('date(interval)', safe=True)
658 658 def date(repo, subset, x):
659 659 """Changesets within the interval, see :hg:`help dates`.
660 660 """
661 661 # i18n: "date" is a keyword
662 662 ds = getstring(x, _("date requires a string"))
663 663 dm = util.matchdate(ds)
664 664 return subset.filter(lambda x: dm(repo[x].date()[0]),
665 665 condrepr=('<date %r>', ds))
666 666
667 667 @predicate('desc(string)', safe=True)
668 668 def desc(repo, subset, x):
669 669 """Search commit message for string. The match is case-insensitive.
670 670
671 671 Pattern matching is supported for `string`. See
672 672 :hg:`help revisions.patterns`.
673 673 """
674 674 # i18n: "desc" is a keyword
675 675 ds = getstring(x, _("desc requires a string"))
676 676
677 677 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
678 678
679 679 return subset.filter(lambda r: matcher(repo[r].description()),
680 680 condrepr=('<desc %r>', ds))
681 681
682 682 def _descendants(repo, subset, x, followfirst=False, startdepth=None,
683 683 stopdepth=None):
684 684 roots = getset(repo, fullreposet(repo), x)
685 685 if not roots:
686 686 return baseset()
687 687 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
688 688 return subset & s
689 689
690 690 @predicate('descendants(set[, depth])', safe=True)
691 691 def descendants(repo, subset, x):
692 692 """Changesets which are descendants of changesets in set, including the
693 693 given changesets themselves.
694 694
695 695 If depth is specified, the result only includes changesets up to
696 696 the specified generation.
697 697 """
698 698 # startdepth is for internal use only until we can decide the UI
699 699 args = getargsdict(x, 'descendants', 'set depth startdepth')
700 700 if 'set' not in args:
701 701 # i18n: "descendants" is a keyword
702 702 raise error.ParseError(_('descendants takes at least 1 argument'))
703 703 startdepth = stopdepth = None
704 704 if 'startdepth' in args:
705 705 n = getinteger(args['startdepth'],
706 706 "descendants expects an integer startdepth")
707 707 if n < 0:
708 708 raise error.ParseError("negative startdepth")
709 709 startdepth = n
710 710 if 'depth' in args:
711 711 # i18n: "descendants" is a keyword
712 712 n = getinteger(args['depth'], _("descendants expects an integer depth"))
713 713 if n < 0:
714 714 raise error.ParseError(_("negative depth"))
715 715 stopdepth = n + 1
716 716 return _descendants(repo, subset, args['set'],
717 717 startdepth=startdepth, stopdepth=stopdepth)
718 718
719 719 @predicate('_firstdescendants', safe=True)
720 720 def _firstdescendants(repo, subset, x):
721 721 # ``_firstdescendants(set)``
722 722 # Like ``descendants(set)`` but follows only the first parents.
723 723 return _descendants(repo, subset, x, followfirst=True)
724 724
725 725 @predicate('destination([set])', safe=True)
726 726 def destination(repo, subset, x):
727 727 """Changesets that were created by a graft, transplant or rebase operation,
728 728 with the given revisions specified as the source. Omitting the optional set
729 729 is the same as passing all().
730 730 """
731 731 if x is not None:
732 732 sources = getset(repo, fullreposet(repo), x)
733 733 else:
734 734 sources = fullreposet(repo)
735 735
736 736 dests = set()
737 737
738 738 # subset contains all of the possible destinations that can be returned, so
739 739 # iterate over them and see if their source(s) were provided in the arg set.
740 740 # Even if the immediate src of r is not in the arg set, src's source (or
741 741 # further back) may be. Scanning back further than the immediate src allows
742 742 # transitive transplants and rebases to yield the same results as transitive
743 743 # grafts.
744 744 for r in subset:
745 745 src = _getrevsource(repo, r)
746 746 lineage = None
747 747
748 748 while src is not None:
749 749 if lineage is None:
750 750 lineage = list()
751 751
752 752 lineage.append(r)
753 753
754 754 # The visited lineage is a match if the current source is in the arg
755 755 # set. Since every candidate dest is visited by way of iterating
756 756 # subset, any dests further back in the lineage will be tested by a
757 757 # different iteration over subset. Likewise, if the src was already
758 758 # selected, the current lineage can be selected without going back
759 759 # further.
760 760 if src in sources or src in dests:
761 761 dests.update(lineage)
762 762 break
763 763
764 764 r = src
765 765 src = _getrevsource(repo, r)
766 766
767 767 return subset.filter(dests.__contains__,
768 768 condrepr=lambda: '<destination %r>' % sorted(dests))
769 769
770 770 @predicate('divergent()', safe=True)
771 771 def divergent(repo, subset, x):
772 772 msg = ("'divergent()' is deprecated, "
773 773 "use 'contentdivergent()'")
774 774 repo.ui.deprecwarn(msg, '4.4')
775 775
776 776 return contentdivergent(repo, subset, x)
777 777
778 778 @predicate('contentdivergent()', safe=True)
779 779 def contentdivergent(repo, subset, x):
780 780 """
781 781 Final successors of changesets with an alternative set of final
782 782 successors. (EXPERIMENTAL)
783 783 """
784 784 # i18n: "contentdivergent" is a keyword
785 785 getargs(x, 0, 0, _("contentdivergent takes no arguments"))
786 786 contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
787 787 return subset & contentdivergent
788 788
789 789 @predicate('extinct()', safe=True)
790 790 def extinct(repo, subset, x):
791 791 """Obsolete changesets with obsolete descendants only.
792 792 """
793 793 # i18n: "extinct" is a keyword
794 794 getargs(x, 0, 0, _("extinct takes no arguments"))
795 795 extincts = obsmod.getrevs(repo, 'extinct')
796 796 return subset & extincts
797 797
798 798 @predicate('extra(label, [value])', safe=True)
799 799 def extra(repo, subset, x):
800 800 """Changesets with the given label in the extra metadata, with the given
801 801 optional value.
802 802
803 803 Pattern matching is supported for `value`. See
804 804 :hg:`help revisions.patterns`.
805 805 """
806 806 args = getargsdict(x, 'extra', 'label value')
807 807 if 'label' not in args:
808 808 # i18n: "extra" is a keyword
809 809 raise error.ParseError(_('extra takes at least 1 argument'))
810 810 # i18n: "extra" is a keyword
811 811 label = getstring(args['label'], _('first argument to extra must be '
812 812 'a string'))
813 813 value = None
814 814
815 815 if 'value' in args:
816 816 # i18n: "extra" is a keyword
817 817 value = getstring(args['value'], _('second argument to extra must be '
818 818 'a string'))
819 819 kind, value, matcher = util.stringmatcher(value)
820 820
821 821 def _matchvalue(r):
822 822 extra = repo[r].extra()
823 823 return label in extra and (value is None or matcher(extra[label]))
824 824
825 825 return subset.filter(lambda r: _matchvalue(r),
826 826 condrepr=('<extra[%r] %r>', label, value))
827 827
828 828 @predicate('filelog(pattern)', safe=True)
829 829 def filelog(repo, subset, x):
830 830 """Changesets connected to the specified filelog.
831 831
832 832 For performance reasons, visits only revisions mentioned in the file-level
833 833 filelog, rather than filtering through all changesets (much faster, but
834 834 doesn't include deletes or duplicate changes). For a slower, more accurate
835 835 result, use ``file()``.
836 836
837 837 The pattern without explicit kind like ``glob:`` is expected to be
838 838 relative to the current directory and match against a file exactly
839 839 for efficiency.
840 840
841 841 If some linkrev points to revisions filtered by the current repoview, we'll
842 842 work around it to return a non-filtered value.
843 843 """
844 844
845 845 # i18n: "filelog" is a keyword
846 846 pat = getstring(x, _("filelog requires a pattern"))
847 847 s = set()
848 848 cl = repo.changelog
849 849
850 850 if not matchmod.patkind(pat):
851 851 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
852 852 files = [f]
853 853 else:
854 854 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
855 855 files = (f for f in repo[None] if m(f))
856 856
857 857 for f in files:
858 858 fl = repo.file(f)
859 859 known = {}
860 860 scanpos = 0
861 861 for fr in list(fl):
862 862 fn = fl.node(fr)
863 863 if fn in known:
864 864 s.add(known[fn])
865 865 continue
866 866
867 867 lr = fl.linkrev(fr)
868 868 if lr in cl:
869 869 s.add(lr)
870 870 elif scanpos is not None:
871 871 # lowest matching changeset is filtered, scan further
872 872 # ahead in changelog
873 873 start = max(lr, scanpos) + 1
874 874 scanpos = None
875 875 for r in cl.revs(start):
876 876 # minimize parsing of non-matching entries
877 877 if f in cl.revision(r) and f in cl.readfiles(r):
878 878 try:
879 879 # try to use manifest delta fastpath
880 880 n = repo[r].filenode(f)
881 881 if n not in known:
882 882 if n == fn:
883 883 s.add(r)
884 884 scanpos = r
885 885 break
886 886 else:
887 887 known[n] = r
888 888 except error.ManifestLookupError:
889 889 # deletion in changelog
890 890 continue
891 891
892 892 return subset & s
893 893
894 894 @predicate('first(set, [n])', safe=True, takeorder=True)
895 895 def first(repo, subset, x, order):
896 896 """An alias for limit().
897 897 """
898 898 return limit(repo, subset, x, order)
899 899
900 900 def _follow(repo, subset, x, name, followfirst=False):
901 901 l = getargs(x, 0, 2, _("%s takes no arguments or a pattern "
902 902 "and an optional revset") % name)
903 903 c = repo['.']
904 904 if l:
905 905 x = getstring(l[0], _("%s expected a pattern") % name)
906 906 rev = None
907 907 if len(l) >= 2:
908 908 revs = getset(repo, fullreposet(repo), l[1])
909 909 if len(revs) != 1:
910 910 raise error.RepoLookupError(
911 911 _("%s expected one starting revision") % name)
912 912 rev = revs.last()
913 913 c = repo[rev]
914 914 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
915 915 ctx=repo[rev], default='path')
916 916
917 917 files = c.manifest().walk(matcher)
918 918
919 919 s = set()
920 920 for fname in files:
921 921 fctx = c[fname]
922 922 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
923 923 # include the revision responsible for the most recent version
924 924 s.add(fctx.introrev())
925 925 else:
926 926 s = dagop.revancestors(repo, baseset([c.rev()]), followfirst)
927 927
928 928 return subset & s
929 929
930 930 @predicate('follow([pattern[, startrev]])', safe=True)
931 931 def follow(repo, subset, x):
932 932 """
933 933 An alias for ``::.`` (ancestors of the working directory's first parent).
934 934 If pattern is specified, the histories of files matching given
935 935 pattern in the revision given by startrev are followed, including copies.
936 936 """
937 937 return _follow(repo, subset, x, 'follow')
938 938
939 939 @predicate('_followfirst', safe=True)
940 940 def _followfirst(repo, subset, x):
941 941 # ``followfirst([pattern[, startrev]])``
942 942 # Like ``follow([pattern[, startrev]])`` but follows only the first parent
943 943 # of every revisions or files revisions.
944 944 return _follow(repo, subset, x, '_followfirst', followfirst=True)
945 945
946 946 @predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
947 947 safe=True)
948 948 def followlines(repo, subset, x):
949 949 """Changesets modifying `file` in line range ('fromline', 'toline').
950 950
951 951 Line range corresponds to 'file' content at 'startrev' and should hence be
952 952 consistent with file size. If startrev is not specified, working directory's
953 953 parent is used.
954 954
955 955 By default, ancestors of 'startrev' are returned. If 'descend' is True,
956 956 descendants of 'startrev' are returned though renames are (currently) not
957 957 followed in this direction.
958 958 """
959 959 args = getargsdict(x, 'followlines', 'file *lines startrev descend')
960 960 if len(args['lines']) != 1:
961 961 raise error.ParseError(_("followlines requires a line range"))
962 962
963 963 rev = '.'
964 964 if 'startrev' in args:
965 965 revs = getset(repo, fullreposet(repo), args['startrev'])
966 966 if len(revs) != 1:
967 967 raise error.ParseError(
968 968 # i18n: "followlines" is a keyword
969 969 _("followlines expects exactly one revision"))
970 970 rev = revs.last()
971 971
972 972 pat = getstring(args['file'], _("followlines requires a pattern"))
973 973 if not matchmod.patkind(pat):
974 974 fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
975 975 else:
976 976 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
977 977 files = [f for f in repo[rev] if m(f)]
978 978 if len(files) != 1:
979 979 # i18n: "followlines" is a keyword
980 980 raise error.ParseError(_("followlines expects exactly one file"))
981 981 fname = files[0]
982 982
983 983 # i18n: "followlines" is a keyword
984 984 lr = getrange(args['lines'][0], _("followlines expects a line range"))
985 985 fromline, toline = [getinteger(a, _("line range bounds must be integers"))
986 986 for a in lr]
987 987 fromline, toline = util.processlinerange(fromline, toline)
988 988
989 989 fctx = repo[rev].filectx(fname)
990 990 descend = False
991 991 if 'descend' in args:
992 992 descend = getboolean(args['descend'],
993 993 # i18n: "descend" is a keyword
994 994 _("descend argument must be a boolean"))
995 995 if descend:
996 996 rs = generatorset(
997 997 (c.rev() for c, _linerange
998 998 in dagop.blockdescendants(fctx, fromline, toline)),
999 999 iterasc=True)
1000 1000 else:
1001 1001 rs = generatorset(
1002 1002 (c.rev() for c, _linerange
1003 1003 in dagop.blockancestors(fctx, fromline, toline)),
1004 1004 iterasc=False)
1005 1005 return subset & rs
1006 1006
1007 1007 @predicate('all()', safe=True)
1008 1008 def getall(repo, subset, x):
1009 1009 """All changesets, the same as ``0:tip``.
1010 1010 """
1011 1011 # i18n: "all" is a keyword
1012 1012 getargs(x, 0, 0, _("all takes no arguments"))
1013 1013 return subset & spanset(repo) # drop "null" if any
1014 1014
1015 1015 @predicate('grep(regex)')
1016 1016 def grep(repo, subset, x):
1017 1017 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1018 1018 to ensure special escape characters are handled correctly. Unlike
1019 1019 ``keyword(string)``, the match is case-sensitive.
1020 1020 """
1021 1021 try:
1022 1022 # i18n: "grep" is a keyword
1023 1023 gr = re.compile(getstring(x, _("grep requires a string")))
1024 1024 except re.error as e:
1025 1025 raise error.ParseError(_('invalid match pattern: %s') % e)
1026 1026
1027 1027 def matches(x):
1028 1028 c = repo[x]
1029 1029 for e in c.files() + [c.user(), c.description()]:
1030 1030 if gr.search(e):
1031 1031 return True
1032 1032 return False
1033 1033
1034 1034 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1035 1035
1036 1036 @predicate('_matchfiles', safe=True)
1037 1037 def _matchfiles(repo, subset, x):
1038 1038 # _matchfiles takes a revset list of prefixed arguments:
1039 1039 #
1040 1040 # [p:foo, i:bar, x:baz]
1041 1041 #
1042 1042 # builds a match object from them and filters subset. Allowed
1043 1043 # prefixes are 'p:' for regular patterns, 'i:' for include
1044 1044 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1045 1045 # a revision identifier, or the empty string to reference the
1046 1046 # working directory, from which the match object is
1047 1047 # initialized. Use 'd:' to set the default matching mode, default
1048 1048 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1049 1049
1050 1050 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1051 1051 pats, inc, exc = [], [], []
1052 1052 rev, default = None, None
1053 1053 for arg in l:
1054 1054 s = getstring(arg, "_matchfiles requires string arguments")
1055 1055 prefix, value = s[:2], s[2:]
1056 1056 if prefix == 'p:':
1057 1057 pats.append(value)
1058 1058 elif prefix == 'i:':
1059 1059 inc.append(value)
1060 1060 elif prefix == 'x:':
1061 1061 exc.append(value)
1062 1062 elif prefix == 'r:':
1063 1063 if rev is not None:
1064 1064 raise error.ParseError('_matchfiles expected at most one '
1065 1065 'revision')
1066 1066 if value != '': # empty means working directory; leave rev as None
1067 1067 rev = value
1068 1068 elif prefix == 'd:':
1069 1069 if default is not None:
1070 1070 raise error.ParseError('_matchfiles expected at most one '
1071 1071 'default mode')
1072 1072 default = value
1073 1073 else:
1074 1074 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1075 1075 if not default:
1076 1076 default = 'glob'
1077 1077
1078 1078 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1079 1079 exclude=exc, ctx=repo[rev], default=default)
1080 1080
1081 1081 # This directly read the changelog data as creating changectx for all
1082 1082 # revisions is quite expensive.
1083 1083 getfiles = repo.changelog.readfiles
1084 1084 wdirrev = node.wdirrev
1085 1085 def matches(x):
1086 1086 if x == wdirrev:
1087 1087 files = repo[x].files()
1088 1088 else:
1089 1089 files = getfiles(x)
1090 1090 for f in files:
1091 1091 if m(f):
1092 1092 return True
1093 1093 return False
1094 1094
1095 1095 return subset.filter(matches,
1096 1096 condrepr=('<matchfiles patterns=%r, include=%r '
1097 1097 'exclude=%r, default=%r, rev=%r>',
1098 1098 pats, inc, exc, default, rev))
1099 1099
1100 1100 @predicate('file(pattern)', safe=True)
1101 1101 def hasfile(repo, subset, x):
1102 1102 """Changesets affecting files matched by pattern.
1103 1103
1104 1104 For a faster but less accurate result, consider using ``filelog()``
1105 1105 instead.
1106 1106
1107 1107 This predicate uses ``glob:`` as the default kind of pattern.
1108 1108 """
1109 1109 # i18n: "file" is a keyword
1110 1110 pat = getstring(x, _("file requires a pattern"))
1111 1111 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1112 1112
1113 1113 @predicate('head()', safe=True)
1114 1114 def head(repo, subset, x):
1115 1115 """Changeset is a named branch head.
1116 1116 """
1117 1117 # i18n: "head" is a keyword
1118 1118 getargs(x, 0, 0, _("head takes no arguments"))
1119 1119 hs = set()
1120 1120 cl = repo.changelog
1121 1121 for ls in repo.branchmap().itervalues():
1122 1122 hs.update(cl.rev(h) for h in ls)
1123 1123 return subset & baseset(hs)
1124 1124
1125 1125 @predicate('heads(set)', safe=True)
1126 1126 def heads(repo, subset, x):
1127 1127 """Members of set with no children in set.
1128 1128 """
1129 1129 s = getset(repo, subset, x)
1130 1130 ps = parents(repo, subset, x)
1131 1131 return s - ps
1132 1132
1133 1133 @predicate('hidden()', safe=True)
1134 1134 def hidden(repo, subset, x):
1135 1135 """Hidden changesets.
1136 1136 """
1137 1137 # i18n: "hidden" is a keyword
1138 1138 getargs(x, 0, 0, _("hidden takes no arguments"))
1139 1139 hiddenrevs = repoview.filterrevs(repo, 'visible')
1140 1140 return subset & hiddenrevs
1141 1141
1142 1142 @predicate('keyword(string)', safe=True)
1143 1143 def keyword(repo, subset, x):
1144 1144 """Search commit message, user name, and names of changed files for
1145 1145 string. The match is case-insensitive.
1146 1146
1147 1147 For a regular expression or case sensitive search of these fields, use
1148 1148 ``grep(regex)``.
1149 1149 """
1150 1150 # i18n: "keyword" is a keyword
1151 1151 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1152 1152
1153 1153 def matches(r):
1154 1154 c = repo[r]
1155 1155 return any(kw in encoding.lower(t)
1156 1156 for t in c.files() + [c.user(), c.description()])
1157 1157
1158 1158 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1159 1159
1160 1160 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True)
1161 1161 def limit(repo, subset, x, order):
1162 1162 """First n members of set, defaulting to 1, starting from offset.
1163 1163 """
1164 1164 args = getargsdict(x, 'limit', 'set n offset')
1165 1165 if 'set' not in args:
1166 1166 # i18n: "limit" is a keyword
1167 1167 raise error.ParseError(_("limit requires one to three arguments"))
1168 1168 # i18n: "limit" is a keyword
1169 1169 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1170 1170 if lim < 0:
1171 1171 raise error.ParseError(_("negative number to select"))
1172 1172 # i18n: "limit" is a keyword
1173 1173 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1174 1174 if ofs < 0:
1175 1175 raise error.ParseError(_("negative offset"))
1176 1176 os = getset(repo, fullreposet(repo), args['set'])
1177 1177 ls = os.slice(ofs, ofs + lim)
1178 1178 if order == followorder and lim > 1:
1179 1179 return subset & ls
1180 1180 return ls & subset
1181 1181
1182 1182 @predicate('last(set, [n])', safe=True, takeorder=True)
1183 1183 def last(repo, subset, x, order):
1184 1184 """Last n members of set, defaulting to 1.
1185 1185 """
1186 1186 # i18n: "last" is a keyword
1187 1187 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1188 1188 lim = 1
1189 1189 if len(l) == 2:
1190 1190 # i18n: "last" is a keyword
1191 1191 lim = getinteger(l[1], _("last expects a number"))
1192 1192 if lim < 0:
1193 1193 raise error.ParseError(_("negative number to select"))
1194 1194 os = getset(repo, fullreposet(repo), l[0])
1195 1195 os.reverse()
1196 1196 ls = os.slice(0, lim)
1197 1197 if order == followorder and lim > 1:
1198 1198 return subset & ls
1199 1199 ls.reverse()
1200 1200 return ls & subset
1201 1201
1202 1202 @predicate('max(set)', safe=True)
1203 1203 def maxrev(repo, subset, x):
1204 1204 """Changeset with highest revision number in set.
1205 1205 """
1206 1206 os = getset(repo, fullreposet(repo), x)
1207 1207 try:
1208 1208 m = os.max()
1209 1209 if m in subset:
1210 1210 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1211 1211 except ValueError:
1212 1212 # os.max() throws a ValueError when the collection is empty.
1213 1213 # Same as python's max().
1214 1214 pass
1215 1215 return baseset(datarepr=('<max %r, %r>', subset, os))
1216 1216
1217 1217 @predicate('merge()', safe=True)
1218 1218 def merge(repo, subset, x):
1219 1219 """Changeset is a merge changeset.
1220 1220 """
1221 1221 # i18n: "merge" is a keyword
1222 1222 getargs(x, 0, 0, _("merge takes no arguments"))
1223 1223 cl = repo.changelog
1224 1224 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1225 1225 condrepr='<merge>')
1226 1226
1227 1227 @predicate('branchpoint()', safe=True)
1228 1228 def branchpoint(repo, subset, x):
1229 1229 """Changesets with more than one child.
1230 1230 """
1231 1231 # i18n: "branchpoint" is a keyword
1232 1232 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1233 1233 cl = repo.changelog
1234 1234 if not subset:
1235 1235 return baseset()
1236 1236 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1237 1237 # (and if it is not, it should.)
1238 1238 baserev = min(subset)
1239 1239 parentscount = [0]*(len(repo) - baserev)
1240 1240 for r in cl.revs(start=baserev + 1):
1241 1241 for p in cl.parentrevs(r):
1242 1242 if p >= baserev:
1243 1243 parentscount[p - baserev] += 1
1244 1244 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1245 1245 condrepr='<branchpoint>')
1246 1246
1247 1247 @predicate('min(set)', safe=True)
1248 1248 def minrev(repo, subset, x):
1249 1249 """Changeset with lowest revision number in set.
1250 1250 """
1251 1251 os = getset(repo, fullreposet(repo), x)
1252 1252 try:
1253 1253 m = os.min()
1254 1254 if m in subset:
1255 1255 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1256 1256 except ValueError:
1257 1257 # os.min() throws a ValueError when the collection is empty.
1258 1258 # Same as python's min().
1259 1259 pass
1260 1260 return baseset(datarepr=('<min %r, %r>', subset, os))
1261 1261
1262 1262 @predicate('modifies(pattern)', safe=True)
1263 1263 def modifies(repo, subset, x):
1264 1264 """Changesets modifying files matched by pattern.
1265 1265
1266 1266 The pattern without explicit kind like ``glob:`` is expected to be
1267 1267 relative to the current directory and match against a file or a
1268 1268 directory.
1269 1269 """
1270 1270 # i18n: "modifies" is a keyword
1271 1271 pat = getstring(x, _("modifies requires a pattern"))
1272 1272 return checkstatus(repo, subset, pat, 0)
1273 1273
1274 1274 @predicate('named(namespace)')
1275 1275 def named(repo, subset, x):
1276 1276 """The changesets in a given namespace.
1277 1277
1278 1278 Pattern matching is supported for `namespace`. See
1279 1279 :hg:`help revisions.patterns`.
1280 1280 """
1281 1281 # i18n: "named" is a keyword
1282 1282 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1283 1283
1284 1284 ns = getstring(args[0],
1285 1285 # i18n: "named" is a keyword
1286 1286 _('the argument to named must be a string'))
1287 1287 kind, pattern, matcher = util.stringmatcher(ns)
1288 1288 namespaces = set()
1289 1289 if kind == 'literal':
1290 1290 if pattern not in repo.names:
1291 1291 raise error.RepoLookupError(_("namespace '%s' does not exist")
1292 1292 % ns)
1293 1293 namespaces.add(repo.names[pattern])
1294 1294 else:
1295 1295 for name, ns in repo.names.iteritems():
1296 1296 if matcher(name):
1297 1297 namespaces.add(ns)
1298 1298 if not namespaces:
1299 1299 raise error.RepoLookupError(_("no namespace exists"
1300 1300 " that match '%s'") % pattern)
1301 1301
1302 1302 names = set()
1303 1303 for ns in namespaces:
1304 1304 for name in ns.listnames(repo):
1305 1305 if name not in ns.deprecated:
1306 1306 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1307 1307
1308 1308 names -= {node.nullrev}
1309 1309 return subset & names
1310 1310
1311 1311 @predicate('id(string)', safe=True)
1312 1312 def node_(repo, subset, x):
1313 1313 """Revision non-ambiguously specified by the given hex string prefix.
1314 1314 """
1315 1315 # i18n: "id" is a keyword
1316 1316 l = getargs(x, 1, 1, _("id requires one argument"))
1317 1317 # i18n: "id" is a keyword
1318 1318 n = getstring(l[0], _("id requires a string"))
1319 1319 if len(n) == 40:
1320 1320 try:
1321 1321 rn = repo.changelog.rev(node.bin(n))
1322 1322 except error.WdirUnsupported:
1323 1323 rn = node.wdirrev
1324 1324 except (LookupError, TypeError):
1325 1325 rn = None
1326 1326 else:
1327 1327 rn = None
1328 1328 try:
1329 1329 pm = repo.changelog._partialmatch(n)
1330 1330 if pm is not None:
1331 1331 rn = repo.changelog.rev(pm)
1332 1332 except error.WdirUnsupported:
1333 1333 rn = node.wdirrev
1334 1334
1335 1335 if rn is None:
1336 1336 return baseset()
1337 1337 result = baseset([rn])
1338 1338 return result & subset
1339 1339
1340 1340 @predicate('obsolete()', safe=True)
1341 1341 def obsolete(repo, subset, x):
1342 1342 """Mutable changeset with a newer version."""
1343 1343 # i18n: "obsolete" is a keyword
1344 1344 getargs(x, 0, 0, _("obsolete takes no arguments"))
1345 1345 obsoletes = obsmod.getrevs(repo, 'obsolete')
1346 1346 return subset & obsoletes
1347 1347
1348 1348 @predicate('only(set, [set])', safe=True)
1349 1349 def only(repo, subset, x):
1350 1350 """Changesets that are ancestors of the first set that are not ancestors
1351 1351 of any other head in the repo. If a second set is specified, the result
1352 1352 is ancestors of the first set that are not ancestors of the second set
1353 1353 (i.e. ::<set1> - ::<set2>).
1354 1354 """
1355 1355 cl = repo.changelog
1356 1356 # i18n: "only" is a keyword
1357 1357 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1358 1358 include = getset(repo, fullreposet(repo), args[0])
1359 1359 if len(args) == 1:
1360 1360 if not include:
1361 1361 return baseset()
1362 1362
1363 1363 descendants = set(dagop.revdescendants(repo, include, False))
1364 1364 exclude = [rev for rev in cl.headrevs()
1365 1365 if not rev in descendants and not rev in include]
1366 1366 else:
1367 1367 exclude = getset(repo, fullreposet(repo), args[1])
1368 1368
1369 1369 results = set(cl.findmissingrevs(common=exclude, heads=include))
1370 1370 # XXX we should turn this into a baseset instead of a set, smartset may do
1371 1371 # some optimizations from the fact this is a baseset.
1372 1372 return subset & results
1373 1373
1374 1374 @predicate('origin([set])', safe=True)
1375 1375 def origin(repo, subset, x):
1376 1376 """
1377 1377 Changesets that were specified as a source for the grafts, transplants or
1378 1378 rebases that created the given revisions. Omitting the optional set is the
1379 1379 same as passing all(). If a changeset created by these operations is itself
1380 1380 specified as a source for one of these operations, only the source changeset
1381 1381 for the first operation is selected.
1382 1382 """
1383 1383 if x is not None:
1384 1384 dests = getset(repo, fullreposet(repo), x)
1385 1385 else:
1386 1386 dests = fullreposet(repo)
1387 1387
1388 1388 def _firstsrc(rev):
1389 1389 src = _getrevsource(repo, rev)
1390 1390 if src is None:
1391 1391 return None
1392 1392
1393 1393 while True:
1394 1394 prev = _getrevsource(repo, src)
1395 1395
1396 1396 if prev is None:
1397 1397 return src
1398 1398 src = prev
1399 1399
1400 1400 o = {_firstsrc(r) for r in dests}
1401 1401 o -= {None}
1402 1402 # XXX we should turn this into a baseset instead of a set, smartset may do
1403 1403 # some optimizations from the fact this is a baseset.
1404 1404 return subset & o
1405 1405
1406 1406 @predicate('outgoing([path])', safe=False)
1407 1407 def outgoing(repo, subset, x):
1408 1408 """Changesets not found in the specified destination repository, or the
1409 1409 default push location.
1410 1410 """
1411 1411 # Avoid cycles.
1412 1412 from . import (
1413 1413 discovery,
1414 1414 hg,
1415 1415 )
1416 1416 # i18n: "outgoing" is a keyword
1417 1417 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1418 1418 # i18n: "outgoing" is a keyword
1419 1419 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1420 1420 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1421 1421 dest, branches = hg.parseurl(dest)
1422 1422 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1423 1423 if revs:
1424 1424 revs = [repo.lookup(rev) for rev in revs]
1425 1425 other = hg.peer(repo, {}, dest)
1426 1426 repo.ui.pushbuffer()
1427 1427 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1428 1428 repo.ui.popbuffer()
1429 1429 cl = repo.changelog
1430 1430 o = {cl.rev(r) for r in outgoing.missing}
1431 1431 return subset & o
1432 1432
1433 1433 @predicate('p1([set])', safe=True)
1434 1434 def p1(repo, subset, x):
1435 1435 """First parent of changesets in set, or the working directory.
1436 1436 """
1437 1437 if x is None:
1438 1438 p = repo[x].p1().rev()
1439 1439 if p >= 0:
1440 1440 return subset & baseset([p])
1441 1441 return baseset()
1442 1442
1443 1443 ps = set()
1444 1444 cl = repo.changelog
1445 1445 for r in getset(repo, fullreposet(repo), x):
1446 1446 try:
1447 1447 ps.add(cl.parentrevs(r)[0])
1448 1448 except error.WdirUnsupported:
1449 1449 ps.add(repo[r].parents()[0].rev())
1450 1450 ps -= {node.nullrev}
1451 1451 # XXX we should turn this into a baseset instead of a set, smartset may do
1452 1452 # some optimizations from the fact this is a baseset.
1453 1453 return subset & ps
1454 1454
1455 1455 @predicate('p2([set])', safe=True)
1456 1456 def p2(repo, subset, x):
1457 1457 """Second parent of changesets in set, or the working directory.
1458 1458 """
1459 1459 if x is None:
1460 1460 ps = repo[x].parents()
1461 1461 try:
1462 1462 p = ps[1].rev()
1463 1463 if p >= 0:
1464 1464 return subset & baseset([p])
1465 1465 return baseset()
1466 1466 except IndexError:
1467 1467 return baseset()
1468 1468
1469 1469 ps = set()
1470 1470 cl = repo.changelog
1471 1471 for r in getset(repo, fullreposet(repo), x):
1472 1472 try:
1473 1473 ps.add(cl.parentrevs(r)[1])
1474 1474 except error.WdirUnsupported:
1475 1475 parents = repo[r].parents()
1476 1476 if len(parents) == 2:
1477 1477 ps.add(parents[1])
1478 1478 ps -= {node.nullrev}
1479 1479 # XXX we should turn this into a baseset instead of a set, smartset may do
1480 1480 # some optimizations from the fact this is a baseset.
1481 1481 return subset & ps
1482 1482
1483 1483 def parentpost(repo, subset, x, order):
1484 1484 return p1(repo, subset, x)
1485 1485
1486 1486 @predicate('parents([set])', safe=True)
1487 1487 def parents(repo, subset, x):
1488 1488 """
1489 1489 The set of all parents for all changesets in set, or the working directory.
1490 1490 """
1491 1491 if x is None:
1492 1492 ps = set(p.rev() for p in repo[x].parents())
1493 1493 else:
1494 1494 ps = set()
1495 1495 cl = repo.changelog
1496 1496 up = ps.update
1497 1497 parentrevs = cl.parentrevs
1498 1498 for r in getset(repo, fullreposet(repo), x):
1499 1499 try:
1500 1500 up(parentrevs(r))
1501 1501 except error.WdirUnsupported:
1502 1502 up(p.rev() for p in repo[r].parents())
1503 1503 ps -= {node.nullrev}
1504 1504 return subset & ps
1505 1505
1506 1506 def _phase(repo, subset, *targets):
1507 1507 """helper to select all rev in <targets> phases"""
1508 1508 s = repo._phasecache.getrevset(repo, targets)
1509 1509 return subset & s
1510 1510
1511 1511 @predicate('draft()', safe=True)
1512 1512 def draft(repo, subset, x):
1513 1513 """Changeset in draft phase."""
1514 1514 # i18n: "draft" is a keyword
1515 1515 getargs(x, 0, 0, _("draft takes no arguments"))
1516 1516 target = phases.draft
1517 1517 return _phase(repo, subset, target)
1518 1518
1519 1519 @predicate('secret()', safe=True)
1520 1520 def secret(repo, subset, x):
1521 1521 """Changeset in secret phase."""
1522 1522 # i18n: "secret" is a keyword
1523 1523 getargs(x, 0, 0, _("secret takes no arguments"))
1524 1524 target = phases.secret
1525 1525 return _phase(repo, subset, target)
1526 1526
1527 1527 def parentspec(repo, subset, x, n, order):
1528 1528 """``set^0``
1529 1529 The set.
1530 1530 ``set^1`` (or ``set^``), ``set^2``
1531 1531 First or second parent, respectively, of all changesets in set.
1532 1532 """
1533 1533 try:
1534 1534 n = int(n[1])
1535 1535 if n not in (0, 1, 2):
1536 1536 raise ValueError
1537 1537 except (TypeError, ValueError):
1538 1538 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1539 1539 ps = set()
1540 1540 cl = repo.changelog
1541 1541 for r in getset(repo, fullreposet(repo), x):
1542 1542 if n == 0:
1543 1543 ps.add(r)
1544 1544 elif n == 1:
1545 1545 try:
1546 1546 ps.add(cl.parentrevs(r)[0])
1547 1547 except error.WdirUnsupported:
1548 1548 ps.add(repo[r].parents()[0].rev())
1549 1549 else:
1550 1550 try:
1551 1551 parents = cl.parentrevs(r)
1552 1552 if parents[1] != node.nullrev:
1553 1553 ps.add(parents[1])
1554 1554 except error.WdirUnsupported:
1555 1555 parents = repo[r].parents()
1556 1556 if len(parents) == 2:
1557 1557 ps.add(parents[1].rev())
1558 1558 return subset & ps
1559 1559
1560 1560 @predicate('present(set)', safe=True, takeorder=True)
1561 1561 def present(repo, subset, x, order):
1562 1562 """An empty set, if any revision in set isn't found; otherwise,
1563 1563 all revisions in set.
1564 1564
1565 1565 If any of specified revisions is not present in the local repository,
1566 1566 the query is normally aborted. But this predicate allows the query
1567 1567 to continue even in such cases.
1568 1568 """
1569 1569 try:
1570 1570 return getset(repo, subset, x, order)
1571 1571 except error.RepoLookupError:
1572 1572 return baseset()
1573 1573
1574 1574 # for internal use
1575 1575 @predicate('_notpublic', safe=True)
1576 1576 def _notpublic(repo, subset, x):
1577 1577 getargs(x, 0, 0, "_notpublic takes no arguments")
1578 1578 return _phase(repo, subset, phases.draft, phases.secret)
1579 1579
1580 # for internal use
1581 @predicate('_phaseandancestors(phasename, set)', safe=True)
1582 def _phaseandancestors(repo, subset, x):
1583 # equivalent to (phasename() & ancestors(set)) but more efficient
1584 # phasename could be one of 'draft', 'secret', or '_notpublic'
1585 args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
1586 phasename = getsymbol(args[0])
1587 s = getset(repo, fullreposet(repo), args[1])
1588
1589 draft = phases.draft
1590 secret = phases.secret
1591 phasenamemap = {
1592 '_notpublic': draft,
1593 'draft': draft, # follow secret's ancestors
1594 'secret': secret,
1595 }
1596 if phasename not in phasenamemap:
1597 raise error.ParseError('%r is not a valid phasename' % phasename)
1598
1599 minimalphase = phasenamemap[phasename]
1600 getphase = repo._phasecache.phase
1601
1602 def cutfunc(rev):
1603 return getphase(repo, rev) < minimalphase
1604
1605 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1606
1607 if phasename == 'draft': # need to remove secret changesets
1608 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1609 return subset & revs
1610
1580 1611 @predicate('public()', safe=True)
1581 1612 def public(repo, subset, x):
1582 1613 """Changeset in public phase."""
1583 1614 # i18n: "public" is a keyword
1584 1615 getargs(x, 0, 0, _("public takes no arguments"))
1585 1616 phase = repo._phasecache.phase
1586 1617 target = phases.public
1587 1618 condition = lambda r: phase(repo, r) == target
1588 1619 return subset.filter(condition, condrepr=('<phase %r>', target),
1589 1620 cache=False)
1590 1621
1591 1622 @predicate('remote([id [,path]])', safe=False)
1592 1623 def remote(repo, subset, x):
1593 1624 """Local revision that corresponds to the given identifier in a
1594 1625 remote repository, if present. Here, the '.' identifier is a
1595 1626 synonym for the current local branch.
1596 1627 """
1597 1628
1598 1629 from . import hg # avoid start-up nasties
1599 1630 # i18n: "remote" is a keyword
1600 1631 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1601 1632
1602 1633 q = '.'
1603 1634 if len(l) > 0:
1604 1635 # i18n: "remote" is a keyword
1605 1636 q = getstring(l[0], _("remote requires a string id"))
1606 1637 if q == '.':
1607 1638 q = repo['.'].branch()
1608 1639
1609 1640 dest = ''
1610 1641 if len(l) > 1:
1611 1642 # i18n: "remote" is a keyword
1612 1643 dest = getstring(l[1], _("remote requires a repository path"))
1613 1644 dest = repo.ui.expandpath(dest or 'default')
1614 1645 dest, branches = hg.parseurl(dest)
1615 1646 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1616 1647 if revs:
1617 1648 revs = [repo.lookup(rev) for rev in revs]
1618 1649 other = hg.peer(repo, {}, dest)
1619 1650 n = other.lookup(q)
1620 1651 if n in repo:
1621 1652 r = repo[n].rev()
1622 1653 if r in subset:
1623 1654 return baseset([r])
1624 1655 return baseset()
1625 1656
1626 1657 @predicate('removes(pattern)', safe=True)
1627 1658 def removes(repo, subset, x):
1628 1659 """Changesets which remove files matching pattern.
1629 1660
1630 1661 The pattern without explicit kind like ``glob:`` is expected to be
1631 1662 relative to the current directory and match against a file or a
1632 1663 directory.
1633 1664 """
1634 1665 # i18n: "removes" is a keyword
1635 1666 pat = getstring(x, _("removes requires a pattern"))
1636 1667 return checkstatus(repo, subset, pat, 2)
1637 1668
1638 1669 @predicate('rev(number)', safe=True)
1639 1670 def rev(repo, subset, x):
1640 1671 """Revision with the given numeric identifier.
1641 1672 """
1642 1673 # i18n: "rev" is a keyword
1643 1674 l = getargs(x, 1, 1, _("rev requires one argument"))
1644 1675 try:
1645 1676 # i18n: "rev" is a keyword
1646 1677 l = int(getstring(l[0], _("rev requires a number")))
1647 1678 except (TypeError, ValueError):
1648 1679 # i18n: "rev" is a keyword
1649 1680 raise error.ParseError(_("rev expects a number"))
1650 1681 if l not in repo.changelog and l not in (node.nullrev, node.wdirrev):
1651 1682 return baseset()
1652 1683 return subset & baseset([l])
1653 1684
1654 1685 @predicate('matching(revision [, field])', safe=True)
1655 1686 def matching(repo, subset, x):
1656 1687 """Changesets in which a given set of fields match the set of fields in the
1657 1688 selected revision or set.
1658 1689
1659 1690 To match more than one field pass the list of fields to match separated
1660 1691 by spaces (e.g. ``author description``).
1661 1692
1662 1693 Valid fields are most regular revision fields and some special fields.
1663 1694
1664 1695 Regular revision fields are ``description``, ``author``, ``branch``,
1665 1696 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1666 1697 and ``diff``.
1667 1698 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1668 1699 contents of the revision. Two revisions matching their ``diff`` will
1669 1700 also match their ``files``.
1670 1701
1671 1702 Special fields are ``summary`` and ``metadata``:
1672 1703 ``summary`` matches the first line of the description.
1673 1704 ``metadata`` is equivalent to matching ``description user date``
1674 1705 (i.e. it matches the main metadata fields).
1675 1706
1676 1707 ``metadata`` is the default field which is used when no fields are
1677 1708 specified. You can match more than one field at a time.
1678 1709 """
1679 1710 # i18n: "matching" is a keyword
1680 1711 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1681 1712
1682 1713 revs = getset(repo, fullreposet(repo), l[0])
1683 1714
1684 1715 fieldlist = ['metadata']
1685 1716 if len(l) > 1:
1686 1717 fieldlist = getstring(l[1],
1687 1718 # i18n: "matching" is a keyword
1688 1719 _("matching requires a string "
1689 1720 "as its second argument")).split()
1690 1721
1691 1722 # Make sure that there are no repeated fields,
1692 1723 # expand the 'special' 'metadata' field type
1693 1724 # and check the 'files' whenever we check the 'diff'
1694 1725 fields = []
1695 1726 for field in fieldlist:
1696 1727 if field == 'metadata':
1697 1728 fields += ['user', 'description', 'date']
1698 1729 elif field == 'diff':
1699 1730 # a revision matching the diff must also match the files
1700 1731 # since matching the diff is very costly, make sure to
1701 1732 # also match the files first
1702 1733 fields += ['files', 'diff']
1703 1734 else:
1704 1735 if field == 'author':
1705 1736 field = 'user'
1706 1737 fields.append(field)
1707 1738 fields = set(fields)
1708 1739 if 'summary' in fields and 'description' in fields:
1709 1740 # If a revision matches its description it also matches its summary
1710 1741 fields.discard('summary')
1711 1742
1712 1743 # We may want to match more than one field
1713 1744 # Not all fields take the same amount of time to be matched
1714 1745 # Sort the selected fields in order of increasing matching cost
1715 1746 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1716 1747 'files', 'description', 'substate', 'diff']
1717 1748 def fieldkeyfunc(f):
1718 1749 try:
1719 1750 return fieldorder.index(f)
1720 1751 except ValueError:
1721 1752 # assume an unknown field is very costly
1722 1753 return len(fieldorder)
1723 1754 fields = list(fields)
1724 1755 fields.sort(key=fieldkeyfunc)
1725 1756
1726 1757 # Each field will be matched with its own "getfield" function
1727 1758 # which will be added to the getfieldfuncs array of functions
1728 1759 getfieldfuncs = []
1729 1760 _funcs = {
1730 1761 'user': lambda r: repo[r].user(),
1731 1762 'branch': lambda r: repo[r].branch(),
1732 1763 'date': lambda r: repo[r].date(),
1733 1764 'description': lambda r: repo[r].description(),
1734 1765 'files': lambda r: repo[r].files(),
1735 1766 'parents': lambda r: repo[r].parents(),
1736 1767 'phase': lambda r: repo[r].phase(),
1737 1768 'substate': lambda r: repo[r].substate,
1738 1769 'summary': lambda r: repo[r].description().splitlines()[0],
1739 1770 'diff': lambda r: list(repo[r].diff(git=True),)
1740 1771 }
1741 1772 for info in fields:
1742 1773 getfield = _funcs.get(info, None)
1743 1774 if getfield is None:
1744 1775 raise error.ParseError(
1745 1776 # i18n: "matching" is a keyword
1746 1777 _("unexpected field name passed to matching: %s") % info)
1747 1778 getfieldfuncs.append(getfield)
1748 1779 # convert the getfield array of functions into a "getinfo" function
1749 1780 # which returns an array of field values (or a single value if there
1750 1781 # is only one field to match)
1751 1782 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1752 1783
1753 1784 def matches(x):
1754 1785 for rev in revs:
1755 1786 target = getinfo(rev)
1756 1787 match = True
1757 1788 for n, f in enumerate(getfieldfuncs):
1758 1789 if target[n] != f(x):
1759 1790 match = False
1760 1791 if match:
1761 1792 return True
1762 1793 return False
1763 1794
1764 1795 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1765 1796
1766 1797 @predicate('reverse(set)', safe=True, takeorder=True)
1767 1798 def reverse(repo, subset, x, order):
1768 1799 """Reverse order of set.
1769 1800 """
1770 1801 l = getset(repo, subset, x, order)
1771 1802 if order == defineorder:
1772 1803 l.reverse()
1773 1804 return l
1774 1805
1775 1806 @predicate('roots(set)', safe=True)
1776 1807 def roots(repo, subset, x):
1777 1808 """Changesets in set with no parent changeset in set.
1778 1809 """
1779 1810 s = getset(repo, fullreposet(repo), x)
1780 1811 parents = repo.changelog.parentrevs
1781 1812 def filter(r):
1782 1813 for p in parents(r):
1783 1814 if 0 <= p and p in s:
1784 1815 return False
1785 1816 return True
1786 1817 return subset & s.filter(filter, condrepr='<roots>')
1787 1818
1788 1819 _sortkeyfuncs = {
1789 1820 'rev': lambda c: c.rev(),
1790 1821 'branch': lambda c: c.branch(),
1791 1822 'desc': lambda c: c.description(),
1792 1823 'user': lambda c: c.user(),
1793 1824 'author': lambda c: c.user(),
1794 1825 'date': lambda c: c.date()[0],
1795 1826 }
1796 1827
1797 1828 def _getsortargs(x):
1798 1829 """Parse sort options into (set, [(key, reverse)], opts)"""
1799 1830 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1800 1831 if 'set' not in args:
1801 1832 # i18n: "sort" is a keyword
1802 1833 raise error.ParseError(_('sort requires one or two arguments'))
1803 1834 keys = "rev"
1804 1835 if 'keys' in args:
1805 1836 # i18n: "sort" is a keyword
1806 1837 keys = getstring(args['keys'], _("sort spec must be a string"))
1807 1838
1808 1839 keyflags = []
1809 1840 for k in keys.split():
1810 1841 fk = k
1811 1842 reverse = (k[0] == '-')
1812 1843 if reverse:
1813 1844 k = k[1:]
1814 1845 if k not in _sortkeyfuncs and k != 'topo':
1815 1846 raise error.ParseError(_("unknown sort key %r") % fk)
1816 1847 keyflags.append((k, reverse))
1817 1848
1818 1849 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1819 1850 # i18n: "topo" is a keyword
1820 1851 raise error.ParseError(_('topo sort order cannot be combined '
1821 1852 'with other sort keys'))
1822 1853
1823 1854 opts = {}
1824 1855 if 'topo.firstbranch' in args:
1825 1856 if any(k == 'topo' for k, reverse in keyflags):
1826 1857 opts['topo.firstbranch'] = args['topo.firstbranch']
1827 1858 else:
1828 1859 # i18n: "topo" and "topo.firstbranch" are keywords
1829 1860 raise error.ParseError(_('topo.firstbranch can only be used '
1830 1861 'when using the topo sort key'))
1831 1862
1832 1863 return args['set'], keyflags, opts
1833 1864
1834 1865 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
1835 1866 def sort(repo, subset, x, order):
1836 1867 """Sort set by keys. The default sort order is ascending, specify a key
1837 1868 as ``-key`` to sort in descending order.
1838 1869
1839 1870 The keys can be:
1840 1871
1841 1872 - ``rev`` for the revision number,
1842 1873 - ``branch`` for the branch name,
1843 1874 - ``desc`` for the commit message (description),
1844 1875 - ``user`` for user name (``author`` can be used as an alias),
1845 1876 - ``date`` for the commit date
1846 1877 - ``topo`` for a reverse topographical sort
1847 1878
1848 1879 The ``topo`` sort order cannot be combined with other sort keys. This sort
1849 1880 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1850 1881 specifies what topographical branches to prioritize in the sort.
1851 1882
1852 1883 """
1853 1884 s, keyflags, opts = _getsortargs(x)
1854 1885 revs = getset(repo, subset, s, order)
1855 1886
1856 1887 if not keyflags or order != defineorder:
1857 1888 return revs
1858 1889 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1859 1890 revs.sort(reverse=keyflags[0][1])
1860 1891 return revs
1861 1892 elif keyflags[0][0] == "topo":
1862 1893 firstbranch = ()
1863 1894 if 'topo.firstbranch' in opts:
1864 1895 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1865 1896 revs = baseset(dagop.toposort(revs, repo.changelog.parentrevs,
1866 1897 firstbranch),
1867 1898 istopo=True)
1868 1899 if keyflags[0][1]:
1869 1900 revs.reverse()
1870 1901 return revs
1871 1902
1872 1903 # sort() is guaranteed to be stable
1873 1904 ctxs = [repo[r] for r in revs]
1874 1905 for k, reverse in reversed(keyflags):
1875 1906 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1876 1907 return baseset([c.rev() for c in ctxs])
1877 1908
1878 1909 @predicate('subrepo([pattern])')
1879 1910 def subrepo(repo, subset, x):
1880 1911 """Changesets that add, modify or remove the given subrepo. If no subrepo
1881 1912 pattern is named, any subrepo changes are returned.
1882 1913 """
1883 1914 # i18n: "subrepo" is a keyword
1884 1915 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1885 1916 pat = None
1886 1917 if len(args) != 0:
1887 1918 pat = getstring(args[0], _("subrepo requires a pattern"))
1888 1919
1889 1920 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1890 1921
1891 1922 def submatches(names):
1892 1923 k, p, m = util.stringmatcher(pat)
1893 1924 for name in names:
1894 1925 if m(name):
1895 1926 yield name
1896 1927
1897 1928 def matches(x):
1898 1929 c = repo[x]
1899 1930 s = repo.status(c.p1().node(), c.node(), match=m)
1900 1931
1901 1932 if pat is None:
1902 1933 return s.added or s.modified or s.removed
1903 1934
1904 1935 if s.added:
1905 1936 return any(submatches(c.substate.keys()))
1906 1937
1907 1938 if s.modified:
1908 1939 subs = set(c.p1().substate.keys())
1909 1940 subs.update(c.substate.keys())
1910 1941
1911 1942 for path in submatches(subs):
1912 1943 if c.p1().substate.get(path) != c.substate.get(path):
1913 1944 return True
1914 1945
1915 1946 if s.removed:
1916 1947 return any(submatches(c.p1().substate.keys()))
1917 1948
1918 1949 return False
1919 1950
1920 1951 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1921 1952
1922 1953 def _mapbynodefunc(repo, s, f):
1923 1954 """(repo, smartset, [node] -> [node]) -> smartset
1924 1955
1925 1956 Helper method to map a smartset to another smartset given a function only
1926 1957 talking about nodes. Handles converting between rev numbers and nodes, and
1927 1958 filtering.
1928 1959 """
1929 1960 cl = repo.unfiltered().changelog
1930 1961 torev = cl.rev
1931 1962 tonode = cl.node
1932 1963 nodemap = cl.nodemap
1933 1964 result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
1934 1965 return smartset.baseset(result - repo.changelog.filteredrevs)
1935 1966
1936 1967 @predicate('successors(set)', safe=True)
1937 1968 def successors(repo, subset, x):
1938 1969 """All successors for set, including the given set themselves"""
1939 1970 s = getset(repo, fullreposet(repo), x)
1940 1971 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
1941 1972 d = _mapbynodefunc(repo, s, f)
1942 1973 return subset & d
1943 1974
1944 1975 def _substringmatcher(pattern, casesensitive=True):
1945 1976 kind, pattern, matcher = util.stringmatcher(pattern,
1946 1977 casesensitive=casesensitive)
1947 1978 if kind == 'literal':
1948 1979 if not casesensitive:
1949 1980 pattern = encoding.lower(pattern)
1950 1981 matcher = lambda s: pattern in encoding.lower(s)
1951 1982 else:
1952 1983 matcher = lambda s: pattern in s
1953 1984 return kind, pattern, matcher
1954 1985
1955 1986 @predicate('tag([name])', safe=True)
1956 1987 def tag(repo, subset, x):
1957 1988 """The specified tag by name, or all tagged revisions if no name is given.
1958 1989
1959 1990 Pattern matching is supported for `name`. See
1960 1991 :hg:`help revisions.patterns`.
1961 1992 """
1962 1993 # i18n: "tag" is a keyword
1963 1994 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1964 1995 cl = repo.changelog
1965 1996 if args:
1966 1997 pattern = getstring(args[0],
1967 1998 # i18n: "tag" is a keyword
1968 1999 _('the argument to tag must be a string'))
1969 2000 kind, pattern, matcher = util.stringmatcher(pattern)
1970 2001 if kind == 'literal':
1971 2002 # avoid resolving all tags
1972 2003 tn = repo._tagscache.tags.get(pattern, None)
1973 2004 if tn is None:
1974 2005 raise error.RepoLookupError(_("tag '%s' does not exist")
1975 2006 % pattern)
1976 2007 s = {repo[tn].rev()}
1977 2008 else:
1978 2009 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
1979 2010 else:
1980 2011 s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
1981 2012 return subset & s
1982 2013
1983 2014 @predicate('tagged', safe=True)
1984 2015 def tagged(repo, subset, x):
1985 2016 return tag(repo, subset, x)
1986 2017
1987 2018 @predicate('unstable()', safe=True)
1988 2019 def unstable(repo, subset, x):
1989 2020 msg = ("'unstable()' is deprecated, "
1990 2021 "use 'orphan()'")
1991 2022 repo.ui.deprecwarn(msg, '4.4')
1992 2023
1993 2024 return orphan(repo, subset, x)
1994 2025
1995 2026 @predicate('orphan()', safe=True)
1996 2027 def orphan(repo, subset, x):
1997 2028 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
1998 2029 """
1999 2030 # i18n: "orphan" is a keyword
2000 2031 getargs(x, 0, 0, _("orphan takes no arguments"))
2001 2032 orphan = obsmod.getrevs(repo, 'orphan')
2002 2033 return subset & orphan
2003 2034
2004 2035
2005 2036 @predicate('user(string)', safe=True)
2006 2037 def user(repo, subset, x):
2007 2038 """User name contains string. The match is case-insensitive.
2008 2039
2009 2040 Pattern matching is supported for `string`. See
2010 2041 :hg:`help revisions.patterns`.
2011 2042 """
2012 2043 return author(repo, subset, x)
2013 2044
2014 2045 @predicate('wdir()', safe=True)
2015 2046 def wdir(repo, subset, x):
2016 2047 """Working directory. (EXPERIMENTAL)"""
2017 2048 # i18n: "wdir" is a keyword
2018 2049 getargs(x, 0, 0, _("wdir takes no arguments"))
2019 2050 if node.wdirrev in subset or isinstance(subset, fullreposet):
2020 2051 return baseset([node.wdirrev])
2021 2052 return baseset()
2022 2053
2023 2054 def _orderedlist(repo, subset, x):
2024 2055 s = getstring(x, "internal error")
2025 2056 if not s:
2026 2057 return baseset()
2027 2058 # remove duplicates here. it's difficult for caller to deduplicate sets
2028 2059 # because different symbols can point to the same rev.
2029 2060 cl = repo.changelog
2030 2061 ls = []
2031 2062 seen = set()
2032 2063 for t in s.split('\0'):
2033 2064 try:
2034 2065 # fast path for integer revision
2035 2066 r = int(t)
2036 2067 if str(r) != t or r not in cl:
2037 2068 raise ValueError
2038 2069 revs = [r]
2039 2070 except ValueError:
2040 2071 revs = stringset(repo, subset, t, defineorder)
2041 2072
2042 2073 for r in revs:
2043 2074 if r in seen:
2044 2075 continue
2045 2076 if (r in subset
2046 2077 or r == node.nullrev and isinstance(subset, fullreposet)):
2047 2078 ls.append(r)
2048 2079 seen.add(r)
2049 2080 return baseset(ls)
2050 2081
2051 2082 # for internal use
2052 2083 @predicate('_list', safe=True, takeorder=True)
2053 2084 def _list(repo, subset, x, order):
2054 2085 if order == followorder:
2055 2086 # slow path to take the subset order
2056 2087 return subset & _orderedlist(repo, fullreposet(repo), x)
2057 2088 else:
2058 2089 return _orderedlist(repo, subset, x)
2059 2090
2060 2091 def _orderedintlist(repo, subset, x):
2061 2092 s = getstring(x, "internal error")
2062 2093 if not s:
2063 2094 return baseset()
2064 2095 ls = [int(r) for r in s.split('\0')]
2065 2096 s = subset
2066 2097 return baseset([r for r in ls if r in s])
2067 2098
2068 2099 # for internal use
2069 2100 @predicate('_intlist', safe=True, takeorder=True)
2070 2101 def _intlist(repo, subset, x, order):
2071 2102 if order == followorder:
2072 2103 # slow path to take the subset order
2073 2104 return subset & _orderedintlist(repo, fullreposet(repo), x)
2074 2105 else:
2075 2106 return _orderedintlist(repo, subset, x)
2076 2107
2077 2108 def _orderedhexlist(repo, subset, x):
2078 2109 s = getstring(x, "internal error")
2079 2110 if not s:
2080 2111 return baseset()
2081 2112 cl = repo.changelog
2082 2113 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2083 2114 s = subset
2084 2115 return baseset([r for r in ls if r in s])
2085 2116
2086 2117 # for internal use
2087 2118 @predicate('_hexlist', safe=True, takeorder=True)
2088 2119 def _hexlist(repo, subset, x, order):
2089 2120 if order == followorder:
2090 2121 # slow path to take the subset order
2091 2122 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2092 2123 else:
2093 2124 return _orderedhexlist(repo, subset, x)
2094 2125
2095 2126 methods = {
2096 2127 "range": rangeset,
2097 2128 "rangeall": rangeall,
2098 2129 "rangepre": rangepre,
2099 2130 "rangepost": rangepost,
2100 2131 "dagrange": dagrange,
2101 2132 "string": stringset,
2102 2133 "symbol": stringset,
2103 2134 "and": andset,
2104 2135 "andsmally": andsmallyset,
2105 2136 "or": orset,
2106 2137 "not": notset,
2107 2138 "difference": differenceset,
2108 2139 "relation": relationset,
2109 2140 "relsubscript": relsubscriptset,
2110 2141 "subscript": subscriptset,
2111 2142 "list": listset,
2112 2143 "keyvalue": keyvaluepair,
2113 2144 "func": func,
2114 2145 "ancestor": ancestorspec,
2115 2146 "parent": parentspec,
2116 2147 "parentpost": parentpost,
2117 2148 }
2118 2149
2119 2150 def posttreebuilthook(tree, repo):
2120 2151 # hook for extensions to execute code on the optimized tree
2121 2152 pass
2122 2153
2123 2154 def match(ui, spec, repo=None):
2124 2155 """Create a matcher for a single revision spec"""
2125 2156 return matchany(ui, [spec], repo=repo)
2126 2157
2127 2158 def matchany(ui, specs, repo=None, localalias=None):
2128 2159 """Create a matcher that will include any revisions matching one of the
2129 2160 given specs
2130 2161
2131 2162 If localalias is not None, it is a dict {name: definitionstring}. It takes
2132 2163 precedence over [revsetalias] config section.
2133 2164 """
2134 2165 if not specs:
2135 2166 def mfunc(repo, subset=None):
2136 2167 return baseset()
2137 2168 return mfunc
2138 2169 if not all(specs):
2139 2170 raise error.ParseError(_("empty query"))
2140 2171 lookup = None
2141 2172 if repo:
2142 2173 lookup = repo.__contains__
2143 2174 if len(specs) == 1:
2144 2175 tree = revsetlang.parse(specs[0], lookup)
2145 2176 else:
2146 2177 tree = ('or',
2147 2178 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2148 2179
2149 2180 aliases = []
2150 2181 warn = None
2151 2182 if ui:
2152 2183 aliases.extend(ui.configitems('revsetalias'))
2153 2184 warn = ui.warn
2154 2185 if localalias:
2155 2186 aliases.extend(localalias.items())
2156 2187 if aliases:
2157 2188 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2158 2189 tree = revsetlang.foldconcat(tree)
2159 2190 tree = revsetlang.analyze(tree)
2160 2191 tree = revsetlang.optimize(tree)
2161 2192 posttreebuilthook(tree, repo)
2162 2193 return makematcher(tree)
2163 2194
2164 2195 def makematcher(tree):
2165 2196 """Create a matcher from an evaluatable tree"""
2166 2197 def mfunc(repo, subset=None, order=None):
2167 2198 if order is None:
2168 2199 if subset is None:
2169 2200 order = defineorder # 'x'
2170 2201 else:
2171 2202 order = followorder # 'subset & x'
2172 2203 if subset is None:
2173 2204 subset = fullreposet(repo)
2174 2205 return getset(repo, subset, tree, order)
2175 2206 return mfunc
2176 2207
2177 2208 def loadpredicate(ui, extname, registrarobj):
2178 2209 """Load revset predicates from specified registrarobj
2179 2210 """
2180 2211 for name, func in registrarobj._table.iteritems():
2181 2212 symbols[name] = func
2182 2213 if func._safe:
2183 2214 safesymbols.add(name)
2184 2215
2185 2216 # load built-in predicates explicitly to setup safesymbols
2186 2217 loadpredicate(None, None, predicate)
2187 2218
2188 2219 # tell hggettext to extract docstrings from these functions:
2189 2220 i18nfunctions = symbols.values()
@@ -1,674 +1,679 b''
1 1 # revsetlang.py - parser, tokenizer and utility for revision set language
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 from __future__ import absolute_import
9 9
10 10 import string
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 node,
16 16 parser,
17 17 pycompat,
18 18 util,
19 19 )
20 20
21 21 elements = {
22 22 # token-type: binding-strength, primary, prefix, infix, suffix
23 23 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
24 24 "[": (21, None, None, ("subscript", 1, "]"), None),
25 25 "#": (21, None, None, ("relation", 21), None),
26 26 "##": (20, None, None, ("_concat", 20), None),
27 27 "~": (18, None, None, ("ancestor", 18), None),
28 28 "^": (18, None, None, ("parent", 18), "parentpost"),
29 29 "-": (5, None, ("negate", 19), ("minus", 5), None),
30 30 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
31 31 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
32 32 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
33 33 "not": (10, None, ("not", 10), None, None),
34 34 "!": (10, None, ("not", 10), None, None),
35 35 "and": (5, None, None, ("and", 5), None),
36 36 "&": (5, None, None, ("and", 5), None),
37 37 "%": (5, None, None, ("only", 5), "onlypost"),
38 38 "or": (4, None, None, ("or", 4), None),
39 39 "|": (4, None, None, ("or", 4), None),
40 40 "+": (4, None, None, ("or", 4), None),
41 41 "=": (3, None, None, ("keyvalue", 3), None),
42 42 ",": (2, None, None, ("list", 2), None),
43 43 ")": (0, None, None, None, None),
44 44 "]": (0, None, None, None, None),
45 45 "symbol": (0, "symbol", None, None, None),
46 46 "string": (0, "string", None, None, None),
47 47 "end": (0, None, None, None, None),
48 48 }
49 49
50 50 keywords = {'and', 'or', 'not'}
51 51
52 52 _quoteletters = {'"', "'"}
53 53 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%"))
54 54
55 55 # default set of valid characters for the initial letter of symbols
56 56 _syminitletters = set(pycompat.iterbytestr(
57 57 string.ascii_letters.encode('ascii') +
58 58 string.digits.encode('ascii') +
59 59 '._@')) | set(map(pycompat.bytechr, xrange(128, 256)))
60 60
61 61 # default set of valid characters for non-initial letters of symbols
62 62 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
63 63
64 64 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
65 65 '''
66 66 Parse a revset statement into a stream of tokens
67 67
68 68 ``syminitletters`` is the set of valid characters for the initial
69 69 letter of symbols.
70 70
71 71 By default, character ``c`` is recognized as valid for initial
72 72 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
73 73
74 74 ``symletters`` is the set of valid characters for non-initial
75 75 letters of symbols.
76 76
77 77 By default, character ``c`` is recognized as valid for non-initial
78 78 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
79 79
80 80 Check that @ is a valid unquoted token character (issue3686):
81 81 >>> list(tokenize("@::"))
82 82 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
83 83
84 84 '''
85 85 program = pycompat.bytestr(program)
86 86 if syminitletters is None:
87 87 syminitletters = _syminitletters
88 88 if symletters is None:
89 89 symletters = _symletters
90 90
91 91 if program and lookup:
92 92 # attempt to parse old-style ranges first to deal with
93 93 # things like old-tag which contain query metacharacters
94 94 parts = program.split(':', 1)
95 95 if all(lookup(sym) for sym in parts if sym):
96 96 if parts[0]:
97 97 yield ('symbol', parts[0], 0)
98 98 if len(parts) > 1:
99 99 s = len(parts[0])
100 100 yield (':', None, s)
101 101 if parts[1]:
102 102 yield ('symbol', parts[1], s + 1)
103 103 yield ('end', None, len(program))
104 104 return
105 105
106 106 pos, l = 0, len(program)
107 107 while pos < l:
108 108 c = program[pos]
109 109 if c.isspace(): # skip inter-token whitespace
110 110 pass
111 111 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
112 112 yield ('::', None, pos)
113 113 pos += 1 # skip ahead
114 114 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
115 115 yield ('..', None, pos)
116 116 pos += 1 # skip ahead
117 117 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
118 118 yield ('##', None, pos)
119 119 pos += 1 # skip ahead
120 120 elif c in _simpleopletters: # handle simple operators
121 121 yield (c, None, pos)
122 122 elif (c in _quoteletters or c == 'r' and
123 123 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
124 124 if c == 'r':
125 125 pos += 1
126 126 c = program[pos]
127 127 decode = lambda x: x
128 128 else:
129 129 decode = parser.unescapestr
130 130 pos += 1
131 131 s = pos
132 132 while pos < l: # find closing quote
133 133 d = program[pos]
134 134 if d == '\\': # skip over escaped characters
135 135 pos += 2
136 136 continue
137 137 if d == c:
138 138 yield ('string', decode(program[s:pos]), s)
139 139 break
140 140 pos += 1
141 141 else:
142 142 raise error.ParseError(_("unterminated string"), s)
143 143 # gather up a symbol/keyword
144 144 elif c in syminitletters:
145 145 s = pos
146 146 pos += 1
147 147 while pos < l: # find end of symbol
148 148 d = program[pos]
149 149 if d not in symletters:
150 150 break
151 151 if d == '.' and program[pos - 1] == '.': # special case for ..
152 152 pos -= 1
153 153 break
154 154 pos += 1
155 155 sym = program[s:pos]
156 156 if sym in keywords: # operator keywords
157 157 yield (sym, None, s)
158 158 elif '-' in sym:
159 159 # some jerk gave us foo-bar-baz, try to check if it's a symbol
160 160 if lookup and lookup(sym):
161 161 # looks like a real symbol
162 162 yield ('symbol', sym, s)
163 163 else:
164 164 # looks like an expression
165 165 parts = sym.split('-')
166 166 for p in parts[:-1]:
167 167 if p: # possible consecutive -
168 168 yield ('symbol', p, s)
169 169 s += len(p)
170 170 yield ('-', None, pos)
171 171 s += 1
172 172 if parts[-1]: # possible trailing -
173 173 yield ('symbol', parts[-1], s)
174 174 else:
175 175 yield ('symbol', sym, s)
176 176 pos -= 1
177 177 else:
178 178 raise error.ParseError(_("syntax error in revset '%s'") %
179 179 program, pos)
180 180 pos += 1
181 181 yield ('end', None, pos)
182 182
183 183 # helpers
184 184
185 185 _notset = object()
186 186
187 187 def getsymbol(x):
188 188 if x and x[0] == 'symbol':
189 189 return x[1]
190 190 raise error.ParseError(_('not a symbol'))
191 191
192 192 def getstring(x, err):
193 193 if x and (x[0] == 'string' or x[0] == 'symbol'):
194 194 return x[1]
195 195 raise error.ParseError(err)
196 196
197 197 def getinteger(x, err, default=_notset):
198 198 if not x and default is not _notset:
199 199 return default
200 200 try:
201 201 return int(getstring(x, err))
202 202 except ValueError:
203 203 raise error.ParseError(err)
204 204
205 205 def getboolean(x, err):
206 206 value = util.parsebool(getsymbol(x))
207 207 if value is not None:
208 208 return value
209 209 raise error.ParseError(err)
210 210
211 211 def getlist(x):
212 212 if not x:
213 213 return []
214 214 if x[0] == 'list':
215 215 return list(x[1:])
216 216 return [x]
217 217
218 218 def getrange(x, err):
219 219 if not x:
220 220 raise error.ParseError(err)
221 221 op = x[0]
222 222 if op == 'range':
223 223 return x[1], x[2]
224 224 elif op == 'rangepre':
225 225 return None, x[1]
226 226 elif op == 'rangepost':
227 227 return x[1], None
228 228 elif op == 'rangeall':
229 229 return None, None
230 230 raise error.ParseError(err)
231 231
232 232 def getargs(x, min, max, err):
233 233 l = getlist(x)
234 234 if len(l) < min or (max >= 0 and len(l) > max):
235 235 raise error.ParseError(err)
236 236 return l
237 237
238 238 def getargsdict(x, funcname, keys):
239 239 return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
240 240 keyvaluenode='keyvalue', keynode='symbol')
241 241
242 242 # cache of {spec: raw parsed tree} built internally
243 243 _treecache = {}
244 244
245 245 def _cachedtree(spec):
246 246 # thread safe because parse() is reentrant and dict.__setitem__() is atomic
247 247 tree = _treecache.get(spec)
248 248 if tree is None:
249 249 _treecache[spec] = tree = parse(spec)
250 250 return tree
251 251
252 252 def _build(tmplspec, *repls):
253 253 """Create raw parsed tree from a template revset statement
254 254
255 255 >>> _build('f(_) and _', ('string', '1'), ('symbol', '2'))
256 256 ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
257 257 """
258 258 template = _cachedtree(tmplspec)
259 259 return parser.buildtree(template, ('symbol', '_'), *repls)
260 260
261 261 def _match(patspec, tree):
262 262 """Test if a tree matches the given pattern statement; return the matches
263 263
264 264 >>> _match('f(_)', parse('f()'))
265 265 >>> _match('f(_)', parse('f(1)'))
266 266 [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
267 267 >>> _match('f(_)', parse('f(1, 2)'))
268 268 """
269 269 pattern = _cachedtree(patspec)
270 270 return parser.matchtree(pattern, tree, ('symbol', '_'),
271 271 {'keyvalue', 'list'})
272 272
273 273 def _matchonly(revs, bases):
274 274 return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
275 275
276 276 def _fixops(x):
277 277 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
278 278 handled well by our simple top-down parser"""
279 279 if not isinstance(x, tuple):
280 280 return x
281 281
282 282 op = x[0]
283 283 if op == 'parent':
284 284 # x^:y means (x^) : y, not x ^ (:y)
285 285 # x^: means (x^) :, not x ^ (:)
286 286 post = ('parentpost', x[1])
287 287 if x[2][0] == 'dagrangepre':
288 288 return _fixops(('dagrange', post, x[2][1]))
289 289 elif x[2][0] == 'rangepre':
290 290 return _fixops(('range', post, x[2][1]))
291 291 elif x[2][0] == 'rangeall':
292 292 return _fixops(('rangepost', post))
293 293 elif op == 'or':
294 294 # make number of arguments deterministic:
295 295 # x + y + z -> (or x y z) -> (or (list x y z))
296 296 return (op, _fixops(('list',) + x[1:]))
297 297 elif op == 'subscript' and x[1][0] == 'relation':
298 298 # x#y[z] ternary
299 299 return _fixops(('relsubscript', x[1][1], x[1][2], x[2]))
300 300
301 301 return (op,) + tuple(_fixops(y) for y in x[1:])
302 302
303 303 def _analyze(x):
304 304 if x is None:
305 305 return x
306 306
307 307 op = x[0]
308 308 if op == 'minus':
309 309 return _analyze(_build('_ and not _', *x[1:]))
310 310 elif op == 'only':
311 311 return _analyze(_build('only(_, _)', *x[1:]))
312 312 elif op == 'onlypost':
313 313 return _analyze(_build('only(_)', x[1]))
314 314 elif op == 'dagrangepre':
315 315 return _analyze(_build('ancestors(_)', x[1]))
316 316 elif op == 'dagrangepost':
317 317 return _analyze(_build('descendants(_)', x[1]))
318 318 elif op == 'negate':
319 319 s = getstring(x[1], _("can't negate that"))
320 320 return _analyze(('string', '-' + s))
321 321 elif op in ('string', 'symbol'):
322 322 return x
323 323 elif op == 'rangeall':
324 324 return (op, None)
325 325 elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}:
326 326 return (op, _analyze(x[1]))
327 327 elif op == 'group':
328 328 return _analyze(x[1])
329 329 elif op in {'and', 'dagrange', 'range', 'parent', 'ancestor', 'relation',
330 330 'subscript'}:
331 331 ta = _analyze(x[1])
332 332 tb = _analyze(x[2])
333 333 return (op, ta, tb)
334 334 elif op == 'relsubscript':
335 335 ta = _analyze(x[1])
336 336 tb = _analyze(x[2])
337 337 tc = _analyze(x[3])
338 338 return (op, ta, tb, tc)
339 339 elif op == 'list':
340 340 return (op,) + tuple(_analyze(y) for y in x[1:])
341 341 elif op == 'keyvalue':
342 342 return (op, x[1], _analyze(x[2]))
343 343 elif op == 'func':
344 344 return (op, x[1], _analyze(x[2]))
345 345 raise ValueError('invalid operator %r' % op)
346 346
347 347 def analyze(x):
348 348 """Transform raw parsed tree to evaluatable tree which can be fed to
349 349 optimize() or getset()
350 350
351 351 All pseudo operations should be mapped to real operations or functions
352 352 defined in methods or symbols table respectively.
353 353 """
354 354 return _analyze(x)
355 355
356 356 def _optimize(x, small):
357 357 if x is None:
358 358 return 0, x
359 359
360 360 smallbonus = 1
361 361 if small:
362 362 smallbonus = .5
363 363
364 364 op = x[0]
365 365 if op in ('string', 'symbol'):
366 366 return smallbonus, x # single revisions are small
367 367 elif op == 'and':
368 368 wa, ta = _optimize(x[1], True)
369 369 wb, tb = _optimize(x[2], True)
370 370 w = min(wa, wb)
371 371
372 # (draft/secret/_notpublic() & ::x) have a fast path
373 m = _match('_() & ancestors(_)', ('and', ta, tb))
374 if m and getsymbol(m[1]) in {'draft', 'secret', '_notpublic'}:
375 return w, _build('_phaseandancestors(_, _)', m[1], m[2])
376
372 377 # (::x and not ::y)/(not ::y and ::x) have a fast path
373 378 m = _matchonly(ta, tb) or _matchonly(tb, ta)
374 379 if m:
375 380 return w, _build('only(_, _)', *m[1:])
376 381
377 382 m = _match('not _', tb)
378 383 if m:
379 384 return wa, ('difference', ta, m[1])
380 385 if wa > wb:
381 386 op = 'andsmally'
382 387 return w, (op, ta, tb)
383 388 elif op == 'or':
384 389 # fast path for machine-generated expression, that is likely to have
385 390 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
386 391 ws, ts, ss = [], [], []
387 392 def flushss():
388 393 if not ss:
389 394 return
390 395 if len(ss) == 1:
391 396 w, t = ss[0]
392 397 else:
393 398 s = '\0'.join(t[1] for w, t in ss)
394 399 y = _build('_list(_)', ('string', s))
395 400 w, t = _optimize(y, False)
396 401 ws.append(w)
397 402 ts.append(t)
398 403 del ss[:]
399 404 for y in getlist(x[1]):
400 405 w, t = _optimize(y, False)
401 406 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
402 407 ss.append((w, t))
403 408 continue
404 409 flushss()
405 410 ws.append(w)
406 411 ts.append(t)
407 412 flushss()
408 413 if len(ts) == 1:
409 414 return ws[0], ts[0] # 'or' operation is fully optimized out
410 415 return max(ws), (op, ('list',) + tuple(ts))
411 416 elif op == 'not':
412 417 # Optimize not public() to _notpublic() because we have a fast version
413 418 if _match('public()', x[1]):
414 419 o = _optimize(_build('_notpublic()'), not small)
415 420 return o[0], o[1]
416 421 else:
417 422 o = _optimize(x[1], not small)
418 423 return o[0], (op, o[1])
419 424 elif op == 'rangeall':
420 425 return smallbonus, x
421 426 elif op in ('rangepre', 'rangepost', 'parentpost'):
422 427 o = _optimize(x[1], small)
423 428 return o[0], (op, o[1])
424 429 elif op in ('dagrange', 'range'):
425 430 wa, ta = _optimize(x[1], small)
426 431 wb, tb = _optimize(x[2], small)
427 432 return wa + wb, (op, ta, tb)
428 433 elif op in ('parent', 'ancestor', 'relation', 'subscript'):
429 434 w, t = _optimize(x[1], small)
430 435 return w, (op, t, x[2])
431 436 elif op == 'relsubscript':
432 437 w, t = _optimize(x[1], small)
433 438 return w, (op, t, x[2], x[3])
434 439 elif op == 'list':
435 440 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
436 441 return sum(ws), (op,) + ts
437 442 elif op == 'keyvalue':
438 443 w, t = _optimize(x[2], small)
439 444 return w, (op, x[1], t)
440 445 elif op == 'func':
441 446 f = getsymbol(x[1])
442 447 wa, ta = _optimize(x[2], small)
443 448 if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
444 449 'keyword', 'outgoing', 'user', 'destination'):
445 450 w = 10 # slow
446 451 elif f in ('modifies', 'adds', 'removes'):
447 452 w = 30 # slower
448 453 elif f == "contains":
449 454 w = 100 # very slow
450 455 elif f == "ancestor":
451 456 w = 1 * smallbonus
452 457 elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
453 458 w = 0
454 459 elif f == "sort":
455 460 w = 10 # assume most sorts look at changelog
456 461 else:
457 462 w = 1
458 463 return w + wa, (op, x[1], ta)
459 464 raise ValueError('invalid operator %r' % op)
460 465
461 466 def optimize(tree):
462 467 """Optimize evaluatable tree
463 468
464 469 All pseudo operations should be transformed beforehand.
465 470 """
466 471 _weight, newtree = _optimize(tree, small=True)
467 472 return newtree
468 473
469 474 # the set of valid characters for the initial letter of symbols in
470 475 # alias declarations and definitions
471 476 _aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
472 477
473 478 def _parsewith(spec, lookup=None, syminitletters=None):
474 479 """Generate a parse tree of given spec with given tokenizing options
475 480
476 481 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
477 482 ('func', ('symbol', 'foo'), ('symbol', '$1'))
478 483 >>> _parsewith('$1')
479 484 Traceback (most recent call last):
480 485 ...
481 486 ParseError: ("syntax error in revset '$1'", 0)
482 487 >>> _parsewith('foo bar')
483 488 Traceback (most recent call last):
484 489 ...
485 490 ParseError: ('invalid token', 4)
486 491 """
487 492 p = parser.parser(elements)
488 493 tree, pos = p.parse(tokenize(spec, lookup=lookup,
489 494 syminitletters=syminitletters))
490 495 if pos != len(spec):
491 496 raise error.ParseError(_('invalid token'), pos)
492 497 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
493 498
494 499 class _aliasrules(parser.basealiasrules):
495 500 """Parsing and expansion rule set of revset aliases"""
496 501 _section = _('revset alias')
497 502
498 503 @staticmethod
499 504 def _parse(spec):
500 505 """Parse alias declaration/definition ``spec``
501 506
502 507 This allows symbol names to use also ``$`` as an initial letter
503 508 (for backward compatibility), and callers of this function should
504 509 examine whether ``$`` is used also for unexpected symbols or not.
505 510 """
506 511 return _parsewith(spec, syminitletters=_aliassyminitletters)
507 512
508 513 @staticmethod
509 514 def _trygetfunc(tree):
510 515 if tree[0] == 'func' and tree[1][0] == 'symbol':
511 516 return tree[1][1], getlist(tree[2])
512 517
513 518 def expandaliases(tree, aliases, warn=None):
514 519 """Expand aliases in a tree, aliases is a list of (name, value) tuples"""
515 520 aliases = _aliasrules.buildmap(aliases)
516 521 tree = _aliasrules.expand(aliases, tree)
517 522 # warn about problematic (but not referred) aliases
518 523 if warn is not None:
519 524 for name, alias in sorted(aliases.iteritems()):
520 525 if alias.error and not alias.warned:
521 526 warn(_('warning: %s\n') % (alias.error))
522 527 alias.warned = True
523 528 return tree
524 529
525 530 def foldconcat(tree):
526 531 """Fold elements to be concatenated by `##`
527 532 """
528 533 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
529 534 return tree
530 535 if tree[0] == '_concat':
531 536 pending = [tree]
532 537 l = []
533 538 while pending:
534 539 e = pending.pop()
535 540 if e[0] == '_concat':
536 541 pending.extend(reversed(e[1:]))
537 542 elif e[0] in ('string', 'symbol'):
538 543 l.append(e[1])
539 544 else:
540 545 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
541 546 raise error.ParseError(msg)
542 547 return ('string', ''.join(l))
543 548 else:
544 549 return tuple(foldconcat(t) for t in tree)
545 550
546 551 def parse(spec, lookup=None):
547 552 return _parsewith(spec, lookup=lookup)
548 553
549 554 def _quote(s):
550 555 r"""Quote a value in order to make it safe for the revset engine.
551 556
552 557 >>> _quote('asdf')
553 558 "'asdf'"
554 559 >>> _quote("asdf'\"")
555 560 '\'asdf\\\'"\''
556 561 >>> _quote('asdf\'')
557 562 "'asdf\\''"
558 563 >>> _quote(1)
559 564 "'1'"
560 565 """
561 566 return "'%s'" % util.escapestr(pycompat.bytestr(s))
562 567
563 568 def formatspec(expr, *args):
564 569 '''
565 570 This is a convenience function for using revsets internally, and
566 571 escapes arguments appropriately. Aliases are intentionally ignored
567 572 so that intended expression behavior isn't accidentally subverted.
568 573
569 574 Supported arguments:
570 575
571 576 %r = revset expression, parenthesized
572 577 %d = int(arg), no quoting
573 578 %s = string(arg), escaped and single-quoted
574 579 %b = arg.branch(), escaped and single-quoted
575 580 %n = hex(arg), single-quoted
576 581 %% = a literal '%'
577 582
578 583 Prefixing the type with 'l' specifies a parenthesized list of that type.
579 584
580 585 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
581 586 '(10 or 11):: and ((this()) or (that()))'
582 587 >>> formatspec('%d:: and not %d::', 10, 20)
583 588 '10:: and not 20::'
584 589 >>> formatspec('%ld or %ld', [], [1])
585 590 "_list('') or 1"
586 591 >>> formatspec('keyword(%s)', 'foo\\xe9')
587 592 "keyword('foo\\\\xe9')"
588 593 >>> b = lambda: 'default'
589 594 >>> b.branch = b
590 595 >>> formatspec('branch(%b)', b)
591 596 "branch('default')"
592 597 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
593 598 "root(_list('a\\x00b\\x00c\\x00d'))"
594 599 '''
595 600
596 601 def argtype(c, arg):
597 602 if c == 'd':
598 603 return '%d' % int(arg)
599 604 elif c == 's':
600 605 return _quote(arg)
601 606 elif c == 'r':
602 607 parse(arg) # make sure syntax errors are confined
603 608 return '(%s)' % arg
604 609 elif c == 'n':
605 610 return _quote(node.hex(arg))
606 611 elif c == 'b':
607 612 return _quote(arg.branch())
608 613
609 614 def listexp(s, t):
610 615 l = len(s)
611 616 if l == 0:
612 617 return "_list('')"
613 618 elif l == 1:
614 619 return argtype(t, s[0])
615 620 elif t == 'd':
616 621 return "_intlist('%s')" % "\0".join('%d' % int(a) for a in s)
617 622 elif t == 's':
618 623 return "_list('%s')" % "\0".join(s)
619 624 elif t == 'n':
620 625 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
621 626 elif t == 'b':
622 627 return "_list('%s')" % "\0".join(a.branch() for a in s)
623 628
624 629 m = l // 2
625 630 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
626 631
627 632 expr = pycompat.bytestr(expr)
628 633 ret = ''
629 634 pos = 0
630 635 arg = 0
631 636 while pos < len(expr):
632 637 c = expr[pos]
633 638 if c == '%':
634 639 pos += 1
635 640 d = expr[pos]
636 641 if d == '%':
637 642 ret += d
638 643 elif d in 'dsnbr':
639 644 ret += argtype(d, args[arg])
640 645 arg += 1
641 646 elif d == 'l':
642 647 # a list of some type
643 648 pos += 1
644 649 d = expr[pos]
645 650 ret += listexp(list(args[arg]), d)
646 651 arg += 1
647 652 else:
648 653 raise error.Abort(_('unexpected revspec format character %s')
649 654 % d)
650 655 else:
651 656 ret += c
652 657 pos += 1
653 658
654 659 return ret
655 660
656 661 def prettyformat(tree):
657 662 return parser.prettyformat(tree, ('string', 'symbol'))
658 663
659 664 def depth(tree):
660 665 if isinstance(tree, tuple):
661 666 return max(map(depth, tree)) + 1
662 667 else:
663 668 return 0
664 669
665 670 def funcsused(tree):
666 671 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
667 672 return set()
668 673 else:
669 674 funcs = set()
670 675 for s in tree[1:]:
671 676 funcs |= funcsused(s)
672 677 if tree[0] == 'func':
673 678 funcs.add(tree[1][1])
674 679 return funcs
@@ -1,4323 +1,4467 b''
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3 $ cat > testrevset.py << EOF
4 4 > import mercurial.revset
5 5 >
6 6 > baseset = mercurial.revset.baseset
7 7 >
8 8 > def r3232(repo, subset, x):
9 9 > """"simple revset that return [3,2,3,2]
10 10 >
11 11 > revisions duplicated on purpose.
12 12 > """
13 13 > if 3 not in subset:
14 14 > if 2 in subset:
15 15 > return baseset([2,2])
16 16 > return baseset()
17 17 > return baseset([3,3,2,2])
18 18 >
19 19 > mercurial.revset.symbols['r3232'] = r3232
20 20 > EOF
21 21 $ cat >> $HGRCPATH << EOF
22 22 > [extensions]
23 23 > drawdag=$TESTDIR/drawdag.py
24 24 > testrevset=$TESTTMP/testrevset.py
25 25 > EOF
26 26
27 27 $ try() {
28 28 > hg debugrevspec --debug "$@"
29 29 > }
30 30
31 31 $ log() {
32 32 > hg log --template '{rev}\n' -r "$1"
33 33 > }
34 34
35 35 extension to build '_intlist()' and '_hexlist()', which is necessary because
36 36 these predicates use '\0' as a separator:
37 37
38 38 $ cat <<EOF > debugrevlistspec.py
39 39 > from __future__ import absolute_import
40 40 > from mercurial import (
41 41 > node as nodemod,
42 42 > registrar,
43 43 > revset,
44 44 > revsetlang,
45 45 > smartset,
46 46 > )
47 47 > cmdtable = {}
48 48 > command = registrar.command(cmdtable)
49 49 > @command(b'debugrevlistspec',
50 50 > [('', 'optimize', None, 'print parsed tree after optimizing'),
51 51 > ('', 'bin', None, 'unhexlify arguments')])
52 52 > def debugrevlistspec(ui, repo, fmt, *args, **opts):
53 53 > if opts['bin']:
54 54 > args = map(nodemod.bin, args)
55 55 > expr = revsetlang.formatspec(fmt, list(args))
56 56 > if ui.verbose:
57 57 > tree = revsetlang.parse(expr, lookup=repo.__contains__)
58 58 > ui.note(revsetlang.prettyformat(tree), "\n")
59 59 > if opts["optimize"]:
60 60 > opttree = revsetlang.optimize(revsetlang.analyze(tree))
61 61 > ui.note("* optimized:\n", revsetlang.prettyformat(opttree),
62 62 > "\n")
63 63 > func = revset.match(ui, expr, repo)
64 64 > revs = func(repo)
65 65 > if ui.verbose:
66 66 > ui.note("* set:\n", smartset.prettyformat(revs), "\n")
67 67 > for c in revs:
68 68 > ui.write("%s\n" % c)
69 69 > EOF
70 70 $ cat <<EOF >> $HGRCPATH
71 71 > [extensions]
72 72 > debugrevlistspec = $TESTTMP/debugrevlistspec.py
73 73 > EOF
74 74 $ trylist() {
75 75 > hg debugrevlistspec --debug "$@"
76 76 > }
77 77
78 78 $ hg init repo
79 79 $ cd repo
80 80
81 81 $ echo a > a
82 82 $ hg branch a
83 83 marked working directory as branch a
84 84 (branches are permanent and global, did you want a bookmark?)
85 85 $ hg ci -Aqm0
86 86
87 87 $ echo b > b
88 88 $ hg branch b
89 89 marked working directory as branch b
90 90 $ hg ci -Aqm1
91 91
92 92 $ rm a
93 93 $ hg branch a-b-c-
94 94 marked working directory as branch a-b-c-
95 95 $ hg ci -Aqm2 -u Bob
96 96
97 97 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
98 98 2
99 99 $ hg log -r "extra('branch')" --template '{rev}\n'
100 100 0
101 101 1
102 102 2
103 103 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
104 104 0 a
105 105 2 a-b-c-
106 106
107 107 $ hg co 1
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 $ hg branch +a+b+c+
110 110 marked working directory as branch +a+b+c+
111 111 $ hg ci -Aqm3
112 112
113 113 $ hg co 2 # interleave
114 114 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
115 115 $ echo bb > b
116 116 $ hg branch -- -a-b-c-
117 117 marked working directory as branch -a-b-c-
118 118 $ hg ci -Aqm4 -d "May 12 2005"
119 119
120 120 $ hg co 3
121 121 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 $ hg branch !a/b/c/
123 123 marked working directory as branch !a/b/c/
124 124 $ hg ci -Aqm"5 bug"
125 125
126 126 $ hg merge 4
127 127 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
128 128 (branch merge, don't forget to commit)
129 129 $ hg branch _a_b_c_
130 130 marked working directory as branch _a_b_c_
131 131 $ hg ci -Aqm"6 issue619"
132 132
133 133 $ hg branch .a.b.c.
134 134 marked working directory as branch .a.b.c.
135 135 $ hg ci -Aqm7
136 136
137 137 $ hg branch all
138 138 marked working directory as branch all
139 139
140 140 $ hg co 4
141 141 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ hg branch Γ©
143 143 marked working directory as branch \xc3\xa9 (esc)
144 144 $ hg ci -Aqm9
145 145
146 146 $ hg tag -r6 1.0
147 147 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
148 148
149 149 $ hg clone --quiet -U -r 7 . ../remote1
150 150 $ hg clone --quiet -U -r 8 . ../remote2
151 151 $ echo "[paths]" >> .hg/hgrc
152 152 $ echo "default = ../remote1" >> .hg/hgrc
153 153
154 154 trivial
155 155
156 156 $ try 0:1
157 157 (range
158 158 ('symbol', '0')
159 159 ('symbol', '1'))
160 160 * set:
161 161 <spanset+ 0:2>
162 162 0
163 163 1
164 164 $ try --optimize :
165 165 (rangeall
166 166 None)
167 167 * optimized:
168 168 (rangeall
169 169 None)
170 170 * set:
171 171 <spanset+ 0:10>
172 172 0
173 173 1
174 174 2
175 175 3
176 176 4
177 177 5
178 178 6
179 179 7
180 180 8
181 181 9
182 182 $ try 3::6
183 183 (dagrange
184 184 ('symbol', '3')
185 185 ('symbol', '6'))
186 186 * set:
187 187 <baseset+ [3, 5, 6]>
188 188 3
189 189 5
190 190 6
191 191 $ try '0|1|2'
192 192 (or
193 193 (list
194 194 ('symbol', '0')
195 195 ('symbol', '1')
196 196 ('symbol', '2')))
197 197 * set:
198 198 <baseset [0, 1, 2]>
199 199 0
200 200 1
201 201 2
202 202
203 203 names that should work without quoting
204 204
205 205 $ try a
206 206 ('symbol', 'a')
207 207 * set:
208 208 <baseset [0]>
209 209 0
210 210 $ try b-a
211 211 (minus
212 212 ('symbol', 'b')
213 213 ('symbol', 'a'))
214 214 * set:
215 215 <filteredset
216 216 <baseset [1]>,
217 217 <not
218 218 <baseset [0]>>>
219 219 1
220 220 $ try _a_b_c_
221 221 ('symbol', '_a_b_c_')
222 222 * set:
223 223 <baseset [6]>
224 224 6
225 225 $ try _a_b_c_-a
226 226 (minus
227 227 ('symbol', '_a_b_c_')
228 228 ('symbol', 'a'))
229 229 * set:
230 230 <filteredset
231 231 <baseset [6]>,
232 232 <not
233 233 <baseset [0]>>>
234 234 6
235 235 $ try .a.b.c.
236 236 ('symbol', '.a.b.c.')
237 237 * set:
238 238 <baseset [7]>
239 239 7
240 240 $ try .a.b.c.-a
241 241 (minus
242 242 ('symbol', '.a.b.c.')
243 243 ('symbol', 'a'))
244 244 * set:
245 245 <filteredset
246 246 <baseset [7]>,
247 247 <not
248 248 <baseset [0]>>>
249 249 7
250 250
251 251 names that should be caught by fallback mechanism
252 252
253 253 $ try -- '-a-b-c-'
254 254 ('symbol', '-a-b-c-')
255 255 * set:
256 256 <baseset [4]>
257 257 4
258 258 $ log -a-b-c-
259 259 4
260 260 $ try '+a+b+c+'
261 261 ('symbol', '+a+b+c+')
262 262 * set:
263 263 <baseset [3]>
264 264 3
265 265 $ try '+a+b+c+:'
266 266 (rangepost
267 267 ('symbol', '+a+b+c+'))
268 268 * set:
269 269 <spanset+ 3:10>
270 270 3
271 271 4
272 272 5
273 273 6
274 274 7
275 275 8
276 276 9
277 277 $ try ':+a+b+c+'
278 278 (rangepre
279 279 ('symbol', '+a+b+c+'))
280 280 * set:
281 281 <spanset+ 0:4>
282 282 0
283 283 1
284 284 2
285 285 3
286 286 $ try -- '-a-b-c-:+a+b+c+'
287 287 (range
288 288 ('symbol', '-a-b-c-')
289 289 ('symbol', '+a+b+c+'))
290 290 * set:
291 291 <spanset- 3:5>
292 292 4
293 293 3
294 294 $ log '-a-b-c-:+a+b+c+'
295 295 4
296 296 3
297 297
298 298 $ try -- -a-b-c--a # complains
299 299 (minus
300 300 (minus
301 301 (minus
302 302 (negate
303 303 ('symbol', 'a'))
304 304 ('symbol', 'b'))
305 305 ('symbol', 'c'))
306 306 (negate
307 307 ('symbol', 'a')))
308 308 abort: unknown revision '-a'!
309 309 [255]
310 310 $ try Γ©
311 311 ('symbol', '\xc3\xa9')
312 312 * set:
313 313 <baseset [9]>
314 314 9
315 315
316 316 no quoting needed
317 317
318 318 $ log ::a-b-c-
319 319 0
320 320 1
321 321 2
322 322
323 323 quoting needed
324 324
325 325 $ try '"-a-b-c-"-a'
326 326 (minus
327 327 ('string', '-a-b-c-')
328 328 ('symbol', 'a'))
329 329 * set:
330 330 <filteredset
331 331 <baseset [4]>,
332 332 <not
333 333 <baseset [0]>>>
334 334 4
335 335
336 336 $ log '1 or 2'
337 337 1
338 338 2
339 339 $ log '1|2'
340 340 1
341 341 2
342 342 $ log '1 and 2'
343 343 $ log '1&2'
344 344 $ try '1&2|3' # precedence - and is higher
345 345 (or
346 346 (list
347 347 (and
348 348 ('symbol', '1')
349 349 ('symbol', '2'))
350 350 ('symbol', '3')))
351 351 * set:
352 352 <addset
353 353 <baseset []>,
354 354 <baseset [3]>>
355 355 3
356 356 $ try '1|2&3'
357 357 (or
358 358 (list
359 359 ('symbol', '1')
360 360 (and
361 361 ('symbol', '2')
362 362 ('symbol', '3'))))
363 363 * set:
364 364 <addset
365 365 <baseset [1]>,
366 366 <baseset []>>
367 367 1
368 368 $ try '1&2&3' # associativity
369 369 (and
370 370 (and
371 371 ('symbol', '1')
372 372 ('symbol', '2'))
373 373 ('symbol', '3'))
374 374 * set:
375 375 <baseset []>
376 376 $ try '1|(2|3)'
377 377 (or
378 378 (list
379 379 ('symbol', '1')
380 380 (group
381 381 (or
382 382 (list
383 383 ('symbol', '2')
384 384 ('symbol', '3'))))))
385 385 * set:
386 386 <addset
387 387 <baseset [1]>,
388 388 <baseset [2, 3]>>
389 389 1
390 390 2
391 391 3
392 392 $ log '1.0' # tag
393 393 6
394 394 $ log 'a' # branch
395 395 0
396 396 $ log '2785f51ee'
397 397 0
398 398 $ log 'date(2005)'
399 399 4
400 400 $ log 'date(this is a test)'
401 401 hg: parse error at 10: unexpected token: symbol
402 402 [255]
403 403 $ log 'date()'
404 404 hg: parse error: date requires a string
405 405 [255]
406 406 $ log 'date'
407 407 abort: unknown revision 'date'!
408 408 [255]
409 409 $ log 'date('
410 410 hg: parse error at 5: not a prefix: end
411 411 [255]
412 412 $ log 'date("\xy")'
413 413 hg: parse error: invalid \x escape
414 414 [255]
415 415 $ log 'date(tip)'
416 416 hg: parse error: invalid date: 'tip'
417 417 [255]
418 418 $ log '0:date'
419 419 abort: unknown revision 'date'!
420 420 [255]
421 421 $ log '::"date"'
422 422 abort: unknown revision 'date'!
423 423 [255]
424 424 $ hg book date -r 4
425 425 $ log '0:date'
426 426 0
427 427 1
428 428 2
429 429 3
430 430 4
431 431 $ log '::date'
432 432 0
433 433 1
434 434 2
435 435 4
436 436 $ log '::"date"'
437 437 0
438 438 1
439 439 2
440 440 4
441 441 $ log 'date(2005) and 1::'
442 442 4
443 443 $ hg book -d date
444 444
445 445 function name should be a symbol
446 446
447 447 $ log '"date"(2005)'
448 448 hg: parse error: not a symbol
449 449 [255]
450 450
451 451 keyword arguments
452 452
453 453 $ log 'extra(branch, value=a)'
454 454 0
455 455
456 456 $ log 'extra(branch, a, b)'
457 457 hg: parse error: extra takes at most 2 positional arguments
458 458 [255]
459 459 $ log 'extra(a, label=b)'
460 460 hg: parse error: extra got multiple values for keyword argument 'label'
461 461 [255]
462 462 $ log 'extra(label=branch, default)'
463 463 hg: parse error: extra got an invalid argument
464 464 [255]
465 465 $ log 'extra(branch, foo+bar=baz)'
466 466 hg: parse error: extra got an invalid argument
467 467 [255]
468 468 $ log 'extra(unknown=branch)'
469 469 hg: parse error: extra got an unexpected keyword argument 'unknown'
470 470 [255]
471 471
472 472 $ try 'foo=bar|baz'
473 473 (keyvalue
474 474 ('symbol', 'foo')
475 475 (or
476 476 (list
477 477 ('symbol', 'bar')
478 478 ('symbol', 'baz'))))
479 479 hg: parse error: can't use a key-value pair in this context
480 480 [255]
481 481
482 482 right-hand side should be optimized recursively
483 483
484 484 $ try --optimize 'foo=(not public())'
485 485 (keyvalue
486 486 ('symbol', 'foo')
487 487 (group
488 488 (not
489 489 (func
490 490 ('symbol', 'public')
491 491 None))))
492 492 * optimized:
493 493 (keyvalue
494 494 ('symbol', 'foo')
495 495 (func
496 496 ('symbol', '_notpublic')
497 497 None))
498 498 hg: parse error: can't use a key-value pair in this context
499 499 [255]
500 500
501 501 relation-subscript operator has the highest binding strength (as function call):
502 502
503 503 $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
504 504 * parsed:
505 505 (range
506 506 ('symbol', 'tip')
507 507 (relsubscript
508 508 (parentpost
509 509 ('symbol', 'tip'))
510 510 ('symbol', 'generations')
511 511 (negate
512 512 ('symbol', '1'))))
513 513 9
514 514 8
515 515 7
516 516 6
517 517 5
518 518 4
519 519
520 520 $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
521 521 * parsed:
522 522 (not
523 523 (relsubscript
524 524 (func
525 525 ('symbol', 'public')
526 526 None)
527 527 ('symbol', 'generations')
528 528 ('symbol', '0')))
529 529
530 530 left-hand side of relation-subscript operator should be optimized recursively:
531 531
532 532 $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
533 533 > '(not public())#generations[0]'
534 534 * analyzed:
535 535 (relsubscript
536 536 (not
537 537 (func
538 538 ('symbol', 'public')
539 539 None))
540 540 ('symbol', 'generations')
541 541 ('symbol', '0'))
542 542 * optimized:
543 543 (relsubscript
544 544 (func
545 545 ('symbol', '_notpublic')
546 546 None)
547 547 ('symbol', 'generations')
548 548 ('symbol', '0'))
549 549
550 550 resolution of subscript and relation-subscript ternary operators:
551 551
552 552 $ hg debugrevspec -p analyzed 'tip[0]'
553 553 * analyzed:
554 554 (subscript
555 555 ('symbol', 'tip')
556 556 ('symbol', '0'))
557 557 hg: parse error: can't use a subscript in this context
558 558 [255]
559 559
560 560 $ hg debugrevspec -p analyzed 'tip#rel[0]'
561 561 * analyzed:
562 562 (relsubscript
563 563 ('symbol', 'tip')
564 564 ('symbol', 'rel')
565 565 ('symbol', '0'))
566 566 hg: parse error: unknown identifier: rel
567 567 [255]
568 568
569 569 $ hg debugrevspec -p analyzed '(tip#rel)[0]'
570 570 * analyzed:
571 571 (subscript
572 572 (relation
573 573 ('symbol', 'tip')
574 574 ('symbol', 'rel'))
575 575 ('symbol', '0'))
576 576 hg: parse error: can't use a subscript in this context
577 577 [255]
578 578
579 579 $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
580 580 * analyzed:
581 581 (subscript
582 582 (relsubscript
583 583 ('symbol', 'tip')
584 584 ('symbol', 'rel')
585 585 ('symbol', '0'))
586 586 ('symbol', '1'))
587 587 hg: parse error: can't use a subscript in this context
588 588 [255]
589 589
590 590 $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
591 591 * analyzed:
592 592 (relsubscript
593 593 (relation
594 594 ('symbol', 'tip')
595 595 ('symbol', 'rel0'))
596 596 ('symbol', 'rel1')
597 597 ('symbol', '1'))
598 598 hg: parse error: unknown identifier: rel1
599 599 [255]
600 600
601 601 $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
602 602 * analyzed:
603 603 (relsubscript
604 604 (relsubscript
605 605 ('symbol', 'tip')
606 606 ('symbol', 'rel0')
607 607 ('symbol', '0'))
608 608 ('symbol', 'rel1')
609 609 ('symbol', '1'))
610 610 hg: parse error: unknown identifier: rel1
611 611 [255]
612 612
613 613 parse errors of relation, subscript and relation-subscript operators:
614 614
615 615 $ hg debugrevspec '[0]'
616 616 hg: parse error at 0: not a prefix: [
617 617 [255]
618 618 $ hg debugrevspec '.#'
619 619 hg: parse error at 2: not a prefix: end
620 620 [255]
621 621 $ hg debugrevspec '#rel'
622 622 hg: parse error at 0: not a prefix: #
623 623 [255]
624 624 $ hg debugrevspec '.#rel[0'
625 625 hg: parse error at 7: unexpected token: end
626 626 [255]
627 627 $ hg debugrevspec '.]'
628 628 hg: parse error at 1: invalid token
629 629 [255]
630 630
631 631 $ hg debugrevspec '.#generations[a]'
632 632 hg: parse error: relation subscript must be an integer
633 633 [255]
634 634 $ hg debugrevspec '.#generations[1-2]'
635 635 hg: parse error: relation subscript must be an integer
636 636 [255]
637 637
638 638 parsed tree at stages:
639 639
640 640 $ hg debugrevspec -p all '()'
641 641 * parsed:
642 642 (group
643 643 None)
644 644 * expanded:
645 645 (group
646 646 None)
647 647 * concatenated:
648 648 (group
649 649 None)
650 650 * analyzed:
651 651 None
652 652 * optimized:
653 653 None
654 654 hg: parse error: missing argument
655 655 [255]
656 656
657 657 $ hg debugrevspec --no-optimized -p all '()'
658 658 * parsed:
659 659 (group
660 660 None)
661 661 * expanded:
662 662 (group
663 663 None)
664 664 * concatenated:
665 665 (group
666 666 None)
667 667 * analyzed:
668 668 None
669 669 hg: parse error: missing argument
670 670 [255]
671 671
672 672 $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
673 673 * parsed:
674 674 (minus
675 675 (group
676 676 (or
677 677 (list
678 678 ('symbol', '0')
679 679 ('symbol', '1'))))
680 680 ('symbol', '1'))
681 681 * analyzed:
682 682 (and
683 683 (or
684 684 (list
685 685 ('symbol', '0')
686 686 ('symbol', '1')))
687 687 (not
688 688 ('symbol', '1')))
689 689 * optimized:
690 690 (difference
691 691 (func
692 692 ('symbol', '_list')
693 693 ('string', '0\x001'))
694 694 ('symbol', '1'))
695 695 0
696 696
697 697 $ hg debugrevspec -p unknown '0'
698 698 abort: invalid stage name: unknown
699 699 [255]
700 700
701 701 $ hg debugrevspec -p all --optimize '0'
702 702 abort: cannot use --optimize with --show-stage
703 703 [255]
704 704
705 705 verify optimized tree:
706 706
707 707 $ hg debugrevspec --verify '0|1'
708 708
709 709 $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
710 710 * analyzed:
711 711 (and
712 712 (func
713 713 ('symbol', 'r3232')
714 714 None)
715 715 ('symbol', '2'))
716 716 * optimized:
717 717 (andsmally
718 718 (func
719 719 ('symbol', 'r3232')
720 720 None)
721 721 ('symbol', '2'))
722 722 * analyzed set:
723 723 <baseset [2]>
724 724 * optimized set:
725 725 <baseset [2, 2]>
726 726 --- analyzed
727 727 +++ optimized
728 728 2
729 729 +2
730 730 [1]
731 731
732 732 $ hg debugrevspec --no-optimized --verify-optimized '0'
733 733 abort: cannot use --verify-optimized with --no-optimized
734 734 [255]
735 735
736 736 Test that symbols only get parsed as functions if there's an opening
737 737 parenthesis.
738 738
739 739 $ hg book only -r 9
740 740 $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark
741 741 8
742 742 9
743 743
744 744 ':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
745 745 may be hidden (issue5385)
746 746
747 747 $ try -p parsed -p analyzed ':'
748 748 * parsed:
749 749 (rangeall
750 750 None)
751 751 * analyzed:
752 752 (rangeall
753 753 None)
754 754 * set:
755 755 <spanset+ 0:10>
756 756 0
757 757 1
758 758 2
759 759 3
760 760 4
761 761 5
762 762 6
763 763 7
764 764 8
765 765 9
766 766 $ try -p analyzed ':1'
767 767 * analyzed:
768 768 (rangepre
769 769 ('symbol', '1'))
770 770 * set:
771 771 <spanset+ 0:2>
772 772 0
773 773 1
774 774 $ try -p analyzed ':(1|2)'
775 775 * analyzed:
776 776 (rangepre
777 777 (or
778 778 (list
779 779 ('symbol', '1')
780 780 ('symbol', '2'))))
781 781 * set:
782 782 <spanset+ 0:3>
783 783 0
784 784 1
785 785 2
786 786 $ try -p analyzed ':(1&2)'
787 787 * analyzed:
788 788 (rangepre
789 789 (and
790 790 ('symbol', '1')
791 791 ('symbol', '2')))
792 792 * set:
793 793 <baseset []>
794 794
795 795 infix/suffix resolution of ^ operator (issue2884):
796 796
797 797 x^:y means (x^):y
798 798
799 799 $ try '1^:2'
800 800 (range
801 801 (parentpost
802 802 ('symbol', '1'))
803 803 ('symbol', '2'))
804 804 * set:
805 805 <spanset+ 0:3>
806 806 0
807 807 1
808 808 2
809 809
810 810 $ try '1^::2'
811 811 (dagrange
812 812 (parentpost
813 813 ('symbol', '1'))
814 814 ('symbol', '2'))
815 815 * set:
816 816 <baseset+ [0, 1, 2]>
817 817 0
818 818 1
819 819 2
820 820
821 821 $ try '9^:'
822 822 (rangepost
823 823 (parentpost
824 824 ('symbol', '9')))
825 825 * set:
826 826 <spanset+ 8:10>
827 827 8
828 828 9
829 829
830 830 x^:y should be resolved before omitting group operators
831 831
832 832 $ try '1^(:2)'
833 833 (parent
834 834 ('symbol', '1')
835 835 (group
836 836 (rangepre
837 837 ('symbol', '2'))))
838 838 hg: parse error: ^ expects a number 0, 1, or 2
839 839 [255]
840 840
841 841 x^:y should be resolved recursively
842 842
843 843 $ try 'sort(1^:2)'
844 844 (func
845 845 ('symbol', 'sort')
846 846 (range
847 847 (parentpost
848 848 ('symbol', '1'))
849 849 ('symbol', '2')))
850 850 * set:
851 851 <spanset+ 0:3>
852 852 0
853 853 1
854 854 2
855 855
856 856 $ try '(3^:4)^:2'
857 857 (range
858 858 (parentpost
859 859 (group
860 860 (range
861 861 (parentpost
862 862 ('symbol', '3'))
863 863 ('symbol', '4'))))
864 864 ('symbol', '2'))
865 865 * set:
866 866 <spanset+ 0:3>
867 867 0
868 868 1
869 869 2
870 870
871 871 $ try '(3^::4)^::2'
872 872 (dagrange
873 873 (parentpost
874 874 (group
875 875 (dagrange
876 876 (parentpost
877 877 ('symbol', '3'))
878 878 ('symbol', '4'))))
879 879 ('symbol', '2'))
880 880 * set:
881 881 <baseset+ [0, 1, 2]>
882 882 0
883 883 1
884 884 2
885 885
886 886 $ try '(9^:)^:'
887 887 (rangepost
888 888 (parentpost
889 889 (group
890 890 (rangepost
891 891 (parentpost
892 892 ('symbol', '9'))))))
893 893 * set:
894 894 <spanset+ 4:10>
895 895 4
896 896 5
897 897 6
898 898 7
899 899 8
900 900 9
901 901
902 902 x^ in alias should also be resolved
903 903
904 904 $ try 'A' --config 'revsetalias.A=1^:2'
905 905 ('symbol', 'A')
906 906 * expanded:
907 907 (range
908 908 (parentpost
909 909 ('symbol', '1'))
910 910 ('symbol', '2'))
911 911 * set:
912 912 <spanset+ 0:3>
913 913 0
914 914 1
915 915 2
916 916
917 917 $ try 'A:2' --config 'revsetalias.A=1^'
918 918 (range
919 919 ('symbol', 'A')
920 920 ('symbol', '2'))
921 921 * expanded:
922 922 (range
923 923 (parentpost
924 924 ('symbol', '1'))
925 925 ('symbol', '2'))
926 926 * set:
927 927 <spanset+ 0:3>
928 928 0
929 929 1
930 930 2
931 931
932 932 but not beyond the boundary of alias expansion, because the resolution should
933 933 be made at the parsing stage
934 934
935 935 $ try '1^A' --config 'revsetalias.A=:2'
936 936 (parent
937 937 ('symbol', '1')
938 938 ('symbol', 'A'))
939 939 * expanded:
940 940 (parent
941 941 ('symbol', '1')
942 942 (rangepre
943 943 ('symbol', '2')))
944 944 hg: parse error: ^ expects a number 0, 1, or 2
945 945 [255]
946 946
947 947 ancestor can accept 0 or more arguments
948 948
949 949 $ log 'ancestor()'
950 950 $ log 'ancestor(1)'
951 951 1
952 952 $ log 'ancestor(4,5)'
953 953 1
954 954 $ log 'ancestor(4,5) and 4'
955 955 $ log 'ancestor(0,0,1,3)'
956 956 0
957 957 $ log 'ancestor(3,1,5,3,5,1)'
958 958 1
959 959 $ log 'ancestor(0,1,3,5)'
960 960 0
961 961 $ log 'ancestor(1,2,3,4,5)'
962 962 1
963 963
964 964 test ancestors
965 965
966 966 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
967 967 @ 9
968 968 o 8
969 969 | o 7
970 970 | o 6
971 971 |/|
972 972 | o 5
973 973 o | 4
974 974 | o 3
975 975 o | 2
976 976 |/
977 977 o 1
978 978 o 0
979 979
980 980 $ log 'ancestors(5)'
981 981 0
982 982 1
983 983 3
984 984 5
985 985 $ log 'ancestor(ancestors(5))'
986 986 0
987 987 $ log '::r3232()'
988 988 0
989 989 1
990 990 2
991 991 3
992 992
993 993 test ancestors with depth limit
994 994
995 995 (depth=0 selects the node itself)
996 996
997 997 $ log 'reverse(ancestors(9, depth=0))'
998 998 9
999 999
1000 1000 (interleaved: '4' would be missing if heap queue were higher depth first)
1001 1001
1002 1002 $ log 'reverse(ancestors(8:9, depth=1))'
1003 1003 9
1004 1004 8
1005 1005 4
1006 1006
1007 1007 (interleaved: '2' would be missing if heap queue were higher depth first)
1008 1008
1009 1009 $ log 'reverse(ancestors(7+8, depth=2))'
1010 1010 8
1011 1011 7
1012 1012 6
1013 1013 5
1014 1014 4
1015 1015 2
1016 1016
1017 1017 (walk example above by separate queries)
1018 1018
1019 1019 $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
1020 1020 8
1021 1021 4
1022 1022 2
1023 1023 7
1024 1024 6
1025 1025 5
1026 1026
1027 1027 (walk 2nd and 3rd ancestors)
1028 1028
1029 1029 $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
1030 1030 5
1031 1031 4
1032 1032 3
1033 1033 2
1034 1034
1035 1035 (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
1036 1036
1037 1037 $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
1038 1038 5
1039 1039 4
1040 1040 2
1041 1041
1042 1042 (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
1043 1043 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
1044 1044 multiple depths)
1045 1045
1046 1046 $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
1047 1047 5
1048 1048 2
1049 1049
1050 1050 test bad arguments passed to ancestors()
1051 1051
1052 1052 $ log 'ancestors(., depth=-1)'
1053 1053 hg: parse error: negative depth
1054 1054 [255]
1055 1055 $ log 'ancestors(., depth=foo)'
1056 1056 hg: parse error: ancestors expects an integer depth
1057 1057 [255]
1058 1058
1059 1059 test descendants
1060 1060
1061 1061 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1062 1062 @ 9
1063 1063 o 8
1064 1064 | o 7
1065 1065 | o 6
1066 1066 |/|
1067 1067 | o 5
1068 1068 o | 4
1069 1069 | o 3
1070 1070 o | 2
1071 1071 |/
1072 1072 o 1
1073 1073 o 0
1074 1074
1075 1075 (null is ultimate root and has optimized path)
1076 1076
1077 1077 $ log 'null:4 & descendants(null)'
1078 1078 -1
1079 1079 0
1080 1080 1
1081 1081 2
1082 1082 3
1083 1083 4
1084 1084
1085 1085 (including merge)
1086 1086
1087 1087 $ log ':8 & descendants(2)'
1088 1088 2
1089 1089 4
1090 1090 6
1091 1091 7
1092 1092 8
1093 1093
1094 1094 (multiple roots)
1095 1095
1096 1096 $ log ':8 & descendants(2+5)'
1097 1097 2
1098 1098 4
1099 1099 5
1100 1100 6
1101 1101 7
1102 1102 8
1103 1103
1104 1104 test descendants with depth limit
1105 1105
1106 1106 (depth=0 selects the node itself)
1107 1107
1108 1108 $ log 'descendants(0, depth=0)'
1109 1109 0
1110 1110 $ log 'null: & descendants(null, depth=0)'
1111 1111 -1
1112 1112
1113 1113 (p2 = null should be ignored)
1114 1114
1115 1115 $ log 'null: & descendants(null, depth=2)'
1116 1116 -1
1117 1117 0
1118 1118 1
1119 1119
1120 1120 (multiple paths: depth(6) = (2, 3))
1121 1121
1122 1122 $ log 'descendants(1+3, depth=2)'
1123 1123 1
1124 1124 2
1125 1125 3
1126 1126 4
1127 1127 5
1128 1128 6
1129 1129
1130 1130 (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
1131 1131
1132 1132 $ log 'descendants(3+1, depth=2, startdepth=2)'
1133 1133 4
1134 1134 5
1135 1135 6
1136 1136
1137 1137 (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
1138 1138
1139 1139 $ log 'descendants(0+3+6, depth=3, startdepth=1)'
1140 1140 1
1141 1141 2
1142 1142 3
1143 1143 4
1144 1144 5
1145 1145 6
1146 1146 7
1147 1147
1148 1148 (multiple depths: depth(6) = (0, 4), no match)
1149 1149
1150 1150 $ log 'descendants(0+6, depth=3, startdepth=1)'
1151 1151 1
1152 1152 2
1153 1153 3
1154 1154 4
1155 1155 5
1156 1156 7
1157 1157
1158 1158 test ancestors/descendants relation subscript:
1159 1159
1160 1160 $ log 'tip#generations[0]'
1161 1161 9
1162 1162 $ log '.#generations[-1]'
1163 1163 8
1164 1164 $ log '.#g[(-1)]'
1165 1165 8
1166 1166
1167 1167 $ hg debugrevspec -p parsed 'roots(:)#g[2]'
1168 1168 * parsed:
1169 1169 (relsubscript
1170 1170 (func
1171 1171 ('symbol', 'roots')
1172 1172 (rangeall
1173 1173 None))
1174 1174 ('symbol', 'g')
1175 1175 ('symbol', '2'))
1176 1176 2
1177 1177 3
1178 1178
1179 1179 test author
1180 1180
1181 1181 $ log 'author(bob)'
1182 1182 2
1183 1183 $ log 'author("re:bob|test")'
1184 1184 0
1185 1185 1
1186 1186 2
1187 1187 3
1188 1188 4
1189 1189 5
1190 1190 6
1191 1191 7
1192 1192 8
1193 1193 9
1194 1194 $ log 'author(r"re:\S")'
1195 1195 0
1196 1196 1
1197 1197 2
1198 1198 3
1199 1199 4
1200 1200 5
1201 1201 6
1202 1202 7
1203 1203 8
1204 1204 9
1205 1205 $ log 'branch(Γ©)'
1206 1206 8
1207 1207 9
1208 1208 $ log 'branch(a)'
1209 1209 0
1210 1210 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
1211 1211 0 a
1212 1212 2 a-b-c-
1213 1213 3 +a+b+c+
1214 1214 4 -a-b-c-
1215 1215 5 !a/b/c/
1216 1216 6 _a_b_c_
1217 1217 7 .a.b.c.
1218 1218 $ log 'children(ancestor(4,5))'
1219 1219 2
1220 1220 3
1221 1221
1222 1222 $ log 'children(4)'
1223 1223 6
1224 1224 8
1225 1225 $ log 'children(null)'
1226 1226 0
1227 1227
1228 1228 $ log 'closed()'
1229 1229 $ log 'contains(a)'
1230 1230 0
1231 1231 1
1232 1232 3
1233 1233 5
1234 1234 $ log 'contains("../repo/a")'
1235 1235 0
1236 1236 1
1237 1237 3
1238 1238 5
1239 1239 $ log 'desc(B)'
1240 1240 5
1241 1241 $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
1242 1242 5 5 bug
1243 1243 6 6 issue619
1244 1244 $ log 'descendants(2 or 3)'
1245 1245 2
1246 1246 3
1247 1247 4
1248 1248 5
1249 1249 6
1250 1250 7
1251 1251 8
1252 1252 9
1253 1253 $ log 'file("b*")'
1254 1254 1
1255 1255 4
1256 1256 $ log 'filelog("b")'
1257 1257 1
1258 1258 4
1259 1259 $ log 'filelog("../repo/b")'
1260 1260 1
1261 1261 4
1262 1262 $ log 'follow()'
1263 1263 0
1264 1264 1
1265 1265 2
1266 1266 4
1267 1267 8
1268 1268 9
1269 1269 $ log 'grep("issue\d+")'
1270 1270 6
1271 1271 $ try 'grep("(")' # invalid regular expression
1272 1272 (func
1273 1273 ('symbol', 'grep')
1274 1274 ('string', '('))
1275 1275 hg: parse error: invalid match pattern: unbalanced parenthesis
1276 1276 [255]
1277 1277 $ try 'grep("\bissue\d+")'
1278 1278 (func
1279 1279 ('symbol', 'grep')
1280 1280 ('string', '\x08issue\\d+'))
1281 1281 * set:
1282 1282 <filteredset
1283 1283 <fullreposet+ 0:10>,
1284 1284 <grep '\x08issue\\d+'>>
1285 1285 $ try 'grep(r"\bissue\d+")'
1286 1286 (func
1287 1287 ('symbol', 'grep')
1288 1288 ('string', '\\bissue\\d+'))
1289 1289 * set:
1290 1290 <filteredset
1291 1291 <fullreposet+ 0:10>,
1292 1292 <grep '\\bissue\\d+'>>
1293 1293 6
1294 1294 $ try 'grep(r"\")'
1295 1295 hg: parse error at 7: unterminated string
1296 1296 [255]
1297 1297 $ log 'head()'
1298 1298 0
1299 1299 1
1300 1300 2
1301 1301 3
1302 1302 4
1303 1303 5
1304 1304 6
1305 1305 7
1306 1306 9
1307 1307 $ log 'heads(6::)'
1308 1308 7
1309 1309 $ log 'keyword(issue)'
1310 1310 6
1311 1311 $ log 'keyword("test a")'
1312 1312
1313 1313 Test first (=limit) and last
1314 1314
1315 1315 $ log 'limit(head(), 1)'
1316 1316 0
1317 1317 $ log 'limit(author("re:bob|test"), 3, 5)'
1318 1318 5
1319 1319 6
1320 1320 7
1321 1321 $ log 'limit(author("re:bob|test"), offset=6)'
1322 1322 6
1323 1323 $ log 'limit(author("re:bob|test"), offset=10)'
1324 1324 $ log 'limit(all(), 1, -1)'
1325 1325 hg: parse error: negative offset
1326 1326 [255]
1327 1327 $ log 'limit(all(), -1)'
1328 1328 hg: parse error: negative number to select
1329 1329 [255]
1330 1330 $ log 'limit(all(), 0)'
1331 1331
1332 1332 $ log 'last(all(), -1)'
1333 1333 hg: parse error: negative number to select
1334 1334 [255]
1335 1335 $ log 'last(all(), 0)'
1336 1336 $ log 'last(all(), 1)'
1337 1337 9
1338 1338 $ log 'last(all(), 2)'
1339 1339 8
1340 1340 9
1341 1341
1342 1342 Test smartset.slice() by first/last()
1343 1343
1344 1344 (using unoptimized set, filteredset as example)
1345 1345
1346 1346 $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
1347 1347 * set:
1348 1348 <filteredset
1349 1349 <spanset+ 0:8>,
1350 1350 <branch 're:'>>
1351 1351 $ log 'limit(0:7 & branch("re:"), 3, 4)'
1352 1352 4
1353 1353 5
1354 1354 6
1355 1355 $ log 'limit(7:0 & branch("re:"), 3, 4)'
1356 1356 3
1357 1357 2
1358 1358 1
1359 1359 $ log 'last(0:7 & branch("re:"), 2)'
1360 1360 6
1361 1361 7
1362 1362
1363 1363 (using baseset)
1364 1364
1365 1365 $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
1366 1366 * set:
1367 1367 <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
1368 1368 $ hg debugrevspec --no-show-revs -s 0::7
1369 1369 * set:
1370 1370 <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
1371 1371 $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
1372 1372 4
1373 1373 5
1374 1374 6
1375 1375 $ log 'limit(sort(0::7, rev), 3, 4)'
1376 1376 4
1377 1377 5
1378 1378 6
1379 1379 $ log 'limit(sort(0::7, -rev), 3, 4)'
1380 1380 3
1381 1381 2
1382 1382 1
1383 1383 $ log 'last(sort(0::7, rev), 2)'
1384 1384 6
1385 1385 7
1386 1386 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
1387 1387 * set:
1388 1388 <baseset+ [6, 7]>
1389 1389 6
1390 1390 7
1391 1391 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
1392 1392 * set:
1393 1393 <baseset+ []>
1394 1394 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
1395 1395 * set:
1396 1396 <baseset- [0, 1]>
1397 1397 1
1398 1398 0
1399 1399 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
1400 1400 * set:
1401 1401 <baseset- []>
1402 1402 $ hg debugrevspec -s 'limit(0::7, 0)'
1403 1403 * set:
1404 1404 <baseset+ []>
1405 1405
1406 1406 (using spanset)
1407 1407
1408 1408 $ hg debugrevspec --no-show-revs -s 0:7
1409 1409 * set:
1410 1410 <spanset+ 0:8>
1411 1411 $ log 'limit(0:7, 3, 4)'
1412 1412 4
1413 1413 5
1414 1414 6
1415 1415 $ log 'limit(7:0, 3, 4)'
1416 1416 3
1417 1417 2
1418 1418 1
1419 1419 $ log 'limit(0:7, 3, 6)'
1420 1420 6
1421 1421 7
1422 1422 $ log 'limit(7:0, 3, 6)'
1423 1423 1
1424 1424 0
1425 1425 $ log 'last(0:7, 2)'
1426 1426 6
1427 1427 7
1428 1428 $ hg debugrevspec -s 'limit(0:7, 3, 6)'
1429 1429 * set:
1430 1430 <spanset+ 6:8>
1431 1431 6
1432 1432 7
1433 1433 $ hg debugrevspec -s 'limit(0:7, 3, 9)'
1434 1434 * set:
1435 1435 <spanset+ 8:8>
1436 1436 $ hg debugrevspec -s 'limit(7:0, 3, 6)'
1437 1437 * set:
1438 1438 <spanset- 0:2>
1439 1439 1
1440 1440 0
1441 1441 $ hg debugrevspec -s 'limit(7:0, 3, 9)'
1442 1442 * set:
1443 1443 <spanset- 0:0>
1444 1444 $ hg debugrevspec -s 'limit(0:7, 0)'
1445 1445 * set:
1446 1446 <spanset+ 0:0>
1447 1447
1448 1448 Test order of first/last revisions
1449 1449
1450 1450 $ hg debugrevspec -s 'first(4:0, 3) & 3:'
1451 1451 * set:
1452 1452 <filteredset
1453 1453 <spanset- 2:5>,
1454 1454 <spanset+ 3:10>>
1455 1455 4
1456 1456 3
1457 1457
1458 1458 $ hg debugrevspec -s '3: & first(4:0, 3)'
1459 1459 * set:
1460 1460 <filteredset
1461 1461 <spanset+ 3:10>,
1462 1462 <spanset- 2:5>>
1463 1463 3
1464 1464 4
1465 1465
1466 1466 $ hg debugrevspec -s 'last(4:0, 3) & :1'
1467 1467 * set:
1468 1468 <filteredset
1469 1469 <spanset- 0:3>,
1470 1470 <spanset+ 0:2>>
1471 1471 1
1472 1472 0
1473 1473
1474 1474 $ hg debugrevspec -s ':1 & last(4:0, 3)'
1475 1475 * set:
1476 1476 <filteredset
1477 1477 <spanset+ 0:2>,
1478 1478 <spanset+ 0:3>>
1479 1479 0
1480 1480 1
1481 1481
1482 1482 Test scmutil.revsingle() should return the last revision
1483 1483
1484 1484 $ hg debugrevspec -s 'last(0::)'
1485 1485 * set:
1486 1486 <baseset slice=0:1
1487 1487 <generatorset->>
1488 1488 9
1489 1489 $ hg identify -r '0::' --num
1490 1490 9
1491 1491
1492 1492 Test matching
1493 1493
1494 1494 $ log 'matching(6)'
1495 1495 6
1496 1496 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
1497 1497 6
1498 1498 7
1499 1499
1500 1500 Testing min and max
1501 1501
1502 1502 max: simple
1503 1503
1504 1504 $ log 'max(contains(a))'
1505 1505 5
1506 1506
1507 1507 max: simple on unordered set)
1508 1508
1509 1509 $ log 'max((4+0+2+5+7) and contains(a))'
1510 1510 5
1511 1511
1512 1512 max: no result
1513 1513
1514 1514 $ log 'max(contains(stringthatdoesnotappearanywhere))'
1515 1515
1516 1516 max: no result on unordered set
1517 1517
1518 1518 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1519 1519
1520 1520 min: simple
1521 1521
1522 1522 $ log 'min(contains(a))'
1523 1523 0
1524 1524
1525 1525 min: simple on unordered set
1526 1526
1527 1527 $ log 'min((4+0+2+5+7) and contains(a))'
1528 1528 0
1529 1529
1530 1530 min: empty
1531 1531
1532 1532 $ log 'min(contains(stringthatdoesnotappearanywhere))'
1533 1533
1534 1534 min: empty on unordered set
1535 1535
1536 1536 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1537 1537
1538 1538
1539 1539 $ log 'merge()'
1540 1540 6
1541 1541 $ log 'branchpoint()'
1542 1542 1
1543 1543 4
1544 1544 $ log 'modifies(b)'
1545 1545 4
1546 1546 $ log 'modifies("path:b")'
1547 1547 4
1548 1548 $ log 'modifies("*")'
1549 1549 4
1550 1550 6
1551 1551 $ log 'modifies("set:modified()")'
1552 1552 4
1553 1553 $ log 'id(5)'
1554 1554 2
1555 1555 $ log 'only(9)'
1556 1556 8
1557 1557 9
1558 1558 $ log 'only(8)'
1559 1559 8
1560 1560 $ log 'only(9, 5)'
1561 1561 2
1562 1562 4
1563 1563 8
1564 1564 9
1565 1565 $ log 'only(7 + 9, 5 + 2)'
1566 1566 4
1567 1567 6
1568 1568 7
1569 1569 8
1570 1570 9
1571 1571
1572 1572 Test empty set input
1573 1573 $ log 'only(p2())'
1574 1574 $ log 'only(p1(), p2())'
1575 1575 0
1576 1576 1
1577 1577 2
1578 1578 4
1579 1579 8
1580 1580 9
1581 1581
1582 1582 Test '%' operator
1583 1583
1584 1584 $ log '9%'
1585 1585 8
1586 1586 9
1587 1587 $ log '9%5'
1588 1588 2
1589 1589 4
1590 1590 8
1591 1591 9
1592 1592 $ log '(7 + 9)%(5 + 2)'
1593 1593 4
1594 1594 6
1595 1595 7
1596 1596 8
1597 1597 9
1598 1598
1599 1599 Test operand of '%' is optimized recursively (issue4670)
1600 1600
1601 1601 $ try --optimize '8:9-8%'
1602 1602 (onlypost
1603 1603 (minus
1604 1604 (range
1605 1605 ('symbol', '8')
1606 1606 ('symbol', '9'))
1607 1607 ('symbol', '8')))
1608 1608 * optimized:
1609 1609 (func
1610 1610 ('symbol', 'only')
1611 1611 (difference
1612 1612 (range
1613 1613 ('symbol', '8')
1614 1614 ('symbol', '9'))
1615 1615 ('symbol', '8')))
1616 1616 * set:
1617 1617 <baseset+ [8, 9]>
1618 1618 8
1619 1619 9
1620 1620 $ try --optimize '(9)%(5)'
1621 1621 (only
1622 1622 (group
1623 1623 ('symbol', '9'))
1624 1624 (group
1625 1625 ('symbol', '5')))
1626 1626 * optimized:
1627 1627 (func
1628 1628 ('symbol', 'only')
1629 1629 (list
1630 1630 ('symbol', '9')
1631 1631 ('symbol', '5')))
1632 1632 * set:
1633 1633 <baseset+ [2, 4, 8, 9]>
1634 1634 2
1635 1635 4
1636 1636 8
1637 1637 9
1638 1638
1639 1639 Test the order of operations
1640 1640
1641 1641 $ log '7 + 9%5 + 2'
1642 1642 7
1643 1643 2
1644 1644 4
1645 1645 8
1646 1646 9
1647 1647
1648 1648 Test explicit numeric revision
1649 1649 $ log 'rev(-2)'
1650 1650 $ log 'rev(-1)'
1651 1651 -1
1652 1652 $ log 'rev(0)'
1653 1653 0
1654 1654 $ log 'rev(9)'
1655 1655 9
1656 1656 $ log 'rev(10)'
1657 1657 $ log 'rev(tip)'
1658 1658 hg: parse error: rev expects a number
1659 1659 [255]
1660 1660
1661 1661 Test hexadecimal revision
1662 1662 $ log 'id(2)'
1663 1663 abort: 00changelog.i@2: ambiguous identifier!
1664 1664 [255]
1665 1665 $ log 'id(23268)'
1666 1666 4
1667 1667 $ log 'id(2785f51eece)'
1668 1668 0
1669 1669 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
1670 1670 8
1671 1671 $ log 'id(d5d0dcbdc4a)'
1672 1672 $ log 'id(d5d0dcbdc4w)'
1673 1673 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
1674 1674 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
1675 1675 $ log 'id(1.0)'
1676 1676 $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1677 1677
1678 1678 Test null revision
1679 1679 $ log '(null)'
1680 1680 -1
1681 1681 $ log '(null:0)'
1682 1682 -1
1683 1683 0
1684 1684 $ log '(0:null)'
1685 1685 0
1686 1686 -1
1687 1687 $ log 'null::0'
1688 1688 -1
1689 1689 0
1690 1690 $ log 'null:tip - 0:'
1691 1691 -1
1692 1692 $ log 'null: and null::' | head -1
1693 1693 -1
1694 1694 $ log 'null: or 0:' | head -2
1695 1695 -1
1696 1696 0
1697 1697 $ log 'ancestors(null)'
1698 1698 -1
1699 1699 $ log 'reverse(null:)' | tail -2
1700 1700 0
1701 1701 -1
1702 1702 $ log 'first(null:)'
1703 1703 -1
1704 1704 $ log 'min(null:)'
1705 1705 BROKEN: should be '-1'
1706 1706 $ log 'tip:null and all()' | tail -2
1707 1707 1
1708 1708 0
1709 1709
1710 1710 Test working-directory revision
1711 1711 $ hg debugrevspec 'wdir()'
1712 1712 2147483647
1713 1713 $ hg debugrevspec 'wdir()^'
1714 1714 9
1715 1715 $ hg up 7
1716 1716 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1717 1717 $ hg debugrevspec 'wdir()^'
1718 1718 7
1719 1719 $ hg debugrevspec 'wdir()^0'
1720 1720 2147483647
1721 1721 $ hg debugrevspec 'wdir()~3'
1722 1722 5
1723 1723 $ hg debugrevspec 'ancestors(wdir())'
1724 1724 0
1725 1725 1
1726 1726 2
1727 1727 3
1728 1728 4
1729 1729 5
1730 1730 6
1731 1731 7
1732 1732 2147483647
1733 1733 $ hg debugrevspec 'wdir()~0'
1734 1734 2147483647
1735 1735 $ hg debugrevspec 'p1(wdir())'
1736 1736 7
1737 1737 $ hg debugrevspec 'p2(wdir())'
1738 1738 $ hg debugrevspec 'parents(wdir())'
1739 1739 7
1740 1740 $ hg debugrevspec 'wdir()^1'
1741 1741 7
1742 1742 $ hg debugrevspec 'wdir()^2'
1743 1743 $ hg debugrevspec 'wdir()^3'
1744 1744 hg: parse error: ^ expects a number 0, 1, or 2
1745 1745 [255]
1746 1746 For tests consistency
1747 1747 $ hg up 9
1748 1748 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1749 1749 $ hg debugrevspec 'tip or wdir()'
1750 1750 9
1751 1751 2147483647
1752 1752 $ hg debugrevspec '0:tip and wdir()'
1753 1753 $ log '0:wdir()' | tail -3
1754 1754 8
1755 1755 9
1756 1756 2147483647
1757 1757 $ log 'wdir():0' | head -3
1758 1758 2147483647
1759 1759 9
1760 1760 8
1761 1761 $ log 'wdir():wdir()'
1762 1762 2147483647
1763 1763 $ log '(all() + wdir()) & min(. + wdir())'
1764 1764 9
1765 1765 $ log '(all() + wdir()) & max(. + wdir())'
1766 1766 2147483647
1767 1767 $ log 'first(wdir() + .)'
1768 1768 2147483647
1769 1769 $ log 'last(. + wdir())'
1770 1770 2147483647
1771 1771
1772 1772 Test working-directory integer revision and node id
1773 1773 (BUG: '0:wdir()' is still needed to populate wdir revision)
1774 1774
1775 1775 $ hg debugrevspec '0:wdir() & 2147483647'
1776 1776 2147483647
1777 1777 $ hg debugrevspec '0:wdir() & rev(2147483647)'
1778 1778 2147483647
1779 1779 $ hg debugrevspec '0:wdir() & ffffffffffffffffffffffffffffffffffffffff'
1780 1780 2147483647
1781 1781 $ hg debugrevspec '0:wdir() & ffffffffffff'
1782 1782 2147483647
1783 1783 $ hg debugrevspec '0:wdir() & id(ffffffffffffffffffffffffffffffffffffffff)'
1784 1784 2147483647
1785 1785 $ hg debugrevspec '0:wdir() & id(ffffffffffff)'
1786 1786 2147483647
1787 1787
1788 1788 $ cd ..
1789 1789
1790 1790 Test short 'ff...' hash collision
1791 1791 (BUG: '0:wdir()' is still needed to populate wdir revision)
1792 1792
1793 1793 $ hg init wdir-hashcollision
1794 1794 $ cd wdir-hashcollision
1795 1795 $ cat <<EOF >> .hg/hgrc
1796 1796 > [experimental]
1797 1797 > stabilization = createmarkers
1798 1798 > EOF
1799 1799 $ echo 0 > a
1800 1800 $ hg ci -qAm 0
1801 1801 $ for i in 2463 2961 6726 78127; do
1802 1802 > hg up -q 0
1803 1803 > echo $i > a
1804 1804 > hg ci -qm $i
1805 1805 > done
1806 1806 $ hg up -q null
1807 1807 $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
1808 1808 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
1809 1809 1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
1810 1810 2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
1811 1811 3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
1812 1812 4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
1813 1813 2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
1814 1814 $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
1815 1815 obsoleted 1 changesets
1816 1816
1817 1817 $ hg debugrevspec '0:wdir() & fff'
1818 1818 abort: 00changelog.i@fff: ambiguous identifier!
1819 1819 [255]
1820 1820 $ hg debugrevspec '0:wdir() & ffff'
1821 1821 abort: 00changelog.i@ffff: ambiguous identifier!
1822 1822 [255]
1823 1823 $ hg debugrevspec '0:wdir() & fffb'
1824 1824 abort: 00changelog.i@fffb: ambiguous identifier!
1825 1825 [255]
1826 1826 BROKEN should be '2' (node lookup uses unfiltered repo since dc25ed84bee8)
1827 1827 $ hg debugrevspec '0:wdir() & id(fffb)'
1828 1828 2
1829 1829 $ hg debugrevspec '0:wdir() & ffff8'
1830 1830 4
1831 1831 $ hg debugrevspec '0:wdir() & fffff'
1832 1832 2147483647
1833 1833
1834 1834 $ cd ..
1835 1835
1836 1836 Test branch() with wdir()
1837 1837
1838 1838 $ cd repo
1839 1839
1840 1840 $ log '0:wdir() & branch("literal:Γ©")'
1841 1841 8
1842 1842 9
1843 1843 2147483647
1844 1844 $ log '0:wdir() & branch("re:Γ©")'
1845 1845 8
1846 1846 9
1847 1847 2147483647
1848 1848 $ log '0:wdir() & branch("re:^a")'
1849 1849 0
1850 1850 2
1851 1851 $ log '0:wdir() & branch(8)'
1852 1852 8
1853 1853 9
1854 1854 2147483647
1855 1855
1856 1856 branch(wdir()) returns all revisions belonging to the working branch. The wdir
1857 1857 itself isn't returned unless it is explicitly populated.
1858 1858
1859 1859 $ log 'branch(wdir())'
1860 1860 8
1861 1861 9
1862 1862 $ log '0:wdir() & branch(wdir())'
1863 1863 8
1864 1864 9
1865 1865 2147483647
1866 1866
1867 1867 $ log 'outgoing()'
1868 1868 8
1869 1869 9
1870 1870 $ log 'outgoing("../remote1")'
1871 1871 8
1872 1872 9
1873 1873 $ log 'outgoing("../remote2")'
1874 1874 3
1875 1875 5
1876 1876 6
1877 1877 7
1878 1878 9
1879 1879 $ log 'p1(merge())'
1880 1880 5
1881 1881 $ log 'p2(merge())'
1882 1882 4
1883 1883 $ log 'parents(merge())'
1884 1884 4
1885 1885 5
1886 1886 $ log 'p1(branchpoint())'
1887 1887 0
1888 1888 2
1889 1889 $ log 'p2(branchpoint())'
1890 1890 $ log 'parents(branchpoint())'
1891 1891 0
1892 1892 2
1893 1893 $ log 'removes(a)'
1894 1894 2
1895 1895 6
1896 1896 $ log 'roots(all())'
1897 1897 0
1898 1898 $ log 'reverse(2 or 3 or 4 or 5)'
1899 1899 5
1900 1900 4
1901 1901 3
1902 1902 2
1903 1903 $ log 'reverse(all())'
1904 1904 9
1905 1905 8
1906 1906 7
1907 1907 6
1908 1908 5
1909 1909 4
1910 1910 3
1911 1911 2
1912 1912 1
1913 1913 0
1914 1914 $ log 'reverse(all()) & filelog(b)'
1915 1915 4
1916 1916 1
1917 1917 $ log 'rev(5)'
1918 1918 5
1919 1919 $ log 'sort(limit(reverse(all()), 3))'
1920 1920 7
1921 1921 8
1922 1922 9
1923 1923 $ log 'sort(2 or 3 or 4 or 5, date)'
1924 1924 2
1925 1925 3
1926 1926 5
1927 1927 4
1928 1928 $ log 'tagged()'
1929 1929 6
1930 1930 $ log 'tag()'
1931 1931 6
1932 1932 $ log 'tag(1.0)'
1933 1933 6
1934 1934 $ log 'tag(tip)'
1935 1935 9
1936 1936
1937 1937 Test order of revisions in compound expression
1938 1938 ----------------------------------------------
1939 1939
1940 1940 The general rule is that only the outermost (= leftmost) predicate can
1941 1941 enforce its ordering requirement. The other predicates should take the
1942 1942 ordering defined by it.
1943 1943
1944 1944 'A & B' should follow the order of 'A':
1945 1945
1946 1946 $ log '2:0 & 0::2'
1947 1947 2
1948 1948 1
1949 1949 0
1950 1950
1951 1951 'head()' combines sets in right order:
1952 1952
1953 1953 $ log '2:0 & head()'
1954 1954 2
1955 1955 1
1956 1956 0
1957 1957
1958 1958 'x:y' takes ordering parameter into account:
1959 1959
1960 1960 $ try -p optimized '3:0 & 0:3 & not 2:1'
1961 1961 * optimized:
1962 1962 (difference
1963 1963 (and
1964 1964 (range
1965 1965 ('symbol', '3')
1966 1966 ('symbol', '0'))
1967 1967 (range
1968 1968 ('symbol', '0')
1969 1969 ('symbol', '3')))
1970 1970 (range
1971 1971 ('symbol', '2')
1972 1972 ('symbol', '1')))
1973 1973 * set:
1974 1974 <filteredset
1975 1975 <filteredset
1976 1976 <spanset- 0:4>,
1977 1977 <spanset+ 0:4>>,
1978 1978 <not
1979 1979 <spanset+ 1:3>>>
1980 1980 3
1981 1981 0
1982 1982
1983 1983 'a + b', which is optimized to '_list(a b)', should take the ordering of
1984 1984 the left expression:
1985 1985
1986 1986 $ try --optimize '2:0 & (0 + 1 + 2)'
1987 1987 (and
1988 1988 (range
1989 1989 ('symbol', '2')
1990 1990 ('symbol', '0'))
1991 1991 (group
1992 1992 (or
1993 1993 (list
1994 1994 ('symbol', '0')
1995 1995 ('symbol', '1')
1996 1996 ('symbol', '2')))))
1997 1997 * optimized:
1998 1998 (and
1999 1999 (range
2000 2000 ('symbol', '2')
2001 2001 ('symbol', '0'))
2002 2002 (func
2003 2003 ('symbol', '_list')
2004 2004 ('string', '0\x001\x002')))
2005 2005 * set:
2006 2006 <filteredset
2007 2007 <spanset- 0:3>,
2008 2008 <baseset [0, 1, 2]>>
2009 2009 2
2010 2010 1
2011 2011 0
2012 2012
2013 2013 'A + B' should take the ordering of the left expression:
2014 2014
2015 2015 $ try --optimize '2:0 & (0:1 + 2)'
2016 2016 (and
2017 2017 (range
2018 2018 ('symbol', '2')
2019 2019 ('symbol', '0'))
2020 2020 (group
2021 2021 (or
2022 2022 (list
2023 2023 (range
2024 2024 ('symbol', '0')
2025 2025 ('symbol', '1'))
2026 2026 ('symbol', '2')))))
2027 2027 * optimized:
2028 2028 (and
2029 2029 (range
2030 2030 ('symbol', '2')
2031 2031 ('symbol', '0'))
2032 2032 (or
2033 2033 (list
2034 2034 (range
2035 2035 ('symbol', '0')
2036 2036 ('symbol', '1'))
2037 2037 ('symbol', '2'))))
2038 2038 * set:
2039 2039 <filteredset
2040 2040 <spanset- 0:3>,
2041 2041 <addset
2042 2042 <spanset+ 0:2>,
2043 2043 <baseset [2]>>>
2044 2044 2
2045 2045 1
2046 2046 0
2047 2047
2048 2048 '_intlist(a b)' should behave like 'a + b':
2049 2049
2050 2050 $ trylist --optimize '2:0 & %ld' 0 1 2
2051 2051 (and
2052 2052 (range
2053 2053 ('symbol', '2')
2054 2054 ('symbol', '0'))
2055 2055 (func
2056 2056 ('symbol', '_intlist')
2057 2057 ('string', '0\x001\x002')))
2058 2058 * optimized:
2059 2059 (andsmally
2060 2060 (range
2061 2061 ('symbol', '2')
2062 2062 ('symbol', '0'))
2063 2063 (func
2064 2064 ('symbol', '_intlist')
2065 2065 ('string', '0\x001\x002')))
2066 2066 * set:
2067 2067 <filteredset
2068 2068 <spanset- 0:3>,
2069 2069 <baseset+ [0, 1, 2]>>
2070 2070 2
2071 2071 1
2072 2072 0
2073 2073
2074 2074 $ trylist --optimize '%ld & 2:0' 0 2 1
2075 2075 (and
2076 2076 (func
2077 2077 ('symbol', '_intlist')
2078 2078 ('string', '0\x002\x001'))
2079 2079 (range
2080 2080 ('symbol', '2')
2081 2081 ('symbol', '0')))
2082 2082 * optimized:
2083 2083 (and
2084 2084 (func
2085 2085 ('symbol', '_intlist')
2086 2086 ('string', '0\x002\x001'))
2087 2087 (range
2088 2088 ('symbol', '2')
2089 2089 ('symbol', '0')))
2090 2090 * set:
2091 2091 <filteredset
2092 2092 <baseset [0, 2, 1]>,
2093 2093 <spanset- 0:3>>
2094 2094 0
2095 2095 2
2096 2096 1
2097 2097
2098 2098 '_hexlist(a b)' should behave like 'a + b':
2099 2099
2100 2100 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
2101 2101 (and
2102 2102 (range
2103 2103 ('symbol', '2')
2104 2104 ('symbol', '0'))
2105 2105 (func
2106 2106 ('symbol', '_hexlist')
2107 2107 ('string', '*'))) (glob)
2108 2108 * optimized:
2109 2109 (and
2110 2110 (range
2111 2111 ('symbol', '2')
2112 2112 ('symbol', '0'))
2113 2113 (func
2114 2114 ('symbol', '_hexlist')
2115 2115 ('string', '*'))) (glob)
2116 2116 * set:
2117 2117 <filteredset
2118 2118 <spanset- 0:3>,
2119 2119 <baseset [0, 1, 2]>>
2120 2120 2
2121 2121 1
2122 2122 0
2123 2123
2124 2124 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
2125 2125 (and
2126 2126 (func
2127 2127 ('symbol', '_hexlist')
2128 2128 ('string', '*')) (glob)
2129 2129 (range
2130 2130 ('symbol', '2')
2131 2131 ('symbol', '0')))
2132 2132 * optimized:
2133 2133 (andsmally
2134 2134 (func
2135 2135 ('symbol', '_hexlist')
2136 2136 ('string', '*')) (glob)
2137 2137 (range
2138 2138 ('symbol', '2')
2139 2139 ('symbol', '0')))
2140 2140 * set:
2141 2141 <baseset [0, 2, 1]>
2142 2142 0
2143 2143 2
2144 2144 1
2145 2145
2146 2146 '_list' should not go through the slow follow-order path if order doesn't
2147 2147 matter:
2148 2148
2149 2149 $ try -p optimized '2:0 & not (0 + 1)'
2150 2150 * optimized:
2151 2151 (difference
2152 2152 (range
2153 2153 ('symbol', '2')
2154 2154 ('symbol', '0'))
2155 2155 (func
2156 2156 ('symbol', '_list')
2157 2157 ('string', '0\x001')))
2158 2158 * set:
2159 2159 <filteredset
2160 2160 <spanset- 0:3>,
2161 2161 <not
2162 2162 <baseset [0, 1]>>>
2163 2163 2
2164 2164
2165 2165 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
2166 2166 * optimized:
2167 2167 (difference
2168 2168 (range
2169 2169 ('symbol', '2')
2170 2170 ('symbol', '0'))
2171 2171 (and
2172 2172 (range
2173 2173 ('symbol', '0')
2174 2174 ('symbol', '2'))
2175 2175 (func
2176 2176 ('symbol', '_list')
2177 2177 ('string', '0\x001'))))
2178 2178 * set:
2179 2179 <filteredset
2180 2180 <spanset- 0:3>,
2181 2181 <not
2182 2182 <baseset [0, 1]>>>
2183 2183 2
2184 2184
2185 2185 because 'present()' does nothing other than suppressing an error, the
2186 2186 ordering requirement should be forwarded to the nested expression
2187 2187
2188 2188 $ try -p optimized 'present(2 + 0 + 1)'
2189 2189 * optimized:
2190 2190 (func
2191 2191 ('symbol', 'present')
2192 2192 (func
2193 2193 ('symbol', '_list')
2194 2194 ('string', '2\x000\x001')))
2195 2195 * set:
2196 2196 <baseset [2, 0, 1]>
2197 2197 2
2198 2198 0
2199 2199 1
2200 2200
2201 2201 $ try --optimize '2:0 & present(0 + 1 + 2)'
2202 2202 (and
2203 2203 (range
2204 2204 ('symbol', '2')
2205 2205 ('symbol', '0'))
2206 2206 (func
2207 2207 ('symbol', 'present')
2208 2208 (or
2209 2209 (list
2210 2210 ('symbol', '0')
2211 2211 ('symbol', '1')
2212 2212 ('symbol', '2')))))
2213 2213 * optimized:
2214 2214 (and
2215 2215 (range
2216 2216 ('symbol', '2')
2217 2217 ('symbol', '0'))
2218 2218 (func
2219 2219 ('symbol', 'present')
2220 2220 (func
2221 2221 ('symbol', '_list')
2222 2222 ('string', '0\x001\x002'))))
2223 2223 * set:
2224 2224 <filteredset
2225 2225 <spanset- 0:3>,
2226 2226 <baseset [0, 1, 2]>>
2227 2227 2
2228 2228 1
2229 2229 0
2230 2230
2231 2231 'reverse()' should take effect only if it is the outermost expression:
2232 2232
2233 2233 $ try --optimize '0:2 & reverse(all())'
2234 2234 (and
2235 2235 (range
2236 2236 ('symbol', '0')
2237 2237 ('symbol', '2'))
2238 2238 (func
2239 2239 ('symbol', 'reverse')
2240 2240 (func
2241 2241 ('symbol', 'all')
2242 2242 None)))
2243 2243 * optimized:
2244 2244 (and
2245 2245 (range
2246 2246 ('symbol', '0')
2247 2247 ('symbol', '2'))
2248 2248 (func
2249 2249 ('symbol', 'reverse')
2250 2250 (func
2251 2251 ('symbol', 'all')
2252 2252 None)))
2253 2253 * set:
2254 2254 <filteredset
2255 2255 <spanset+ 0:3>,
2256 2256 <spanset+ 0:10>>
2257 2257 0
2258 2258 1
2259 2259 2
2260 2260
2261 2261 'sort()' should take effect only if it is the outermost expression:
2262 2262
2263 2263 $ try --optimize '0:2 & sort(all(), -rev)'
2264 2264 (and
2265 2265 (range
2266 2266 ('symbol', '0')
2267 2267 ('symbol', '2'))
2268 2268 (func
2269 2269 ('symbol', 'sort')
2270 2270 (list
2271 2271 (func
2272 2272 ('symbol', 'all')
2273 2273 None)
2274 2274 (negate
2275 2275 ('symbol', 'rev')))))
2276 2276 * optimized:
2277 2277 (and
2278 2278 (range
2279 2279 ('symbol', '0')
2280 2280 ('symbol', '2'))
2281 2281 (func
2282 2282 ('symbol', 'sort')
2283 2283 (list
2284 2284 (func
2285 2285 ('symbol', 'all')
2286 2286 None)
2287 2287 ('string', '-rev'))))
2288 2288 * set:
2289 2289 <filteredset
2290 2290 <spanset+ 0:3>,
2291 2291 <spanset+ 0:10>>
2292 2292 0
2293 2293 1
2294 2294 2
2295 2295
2296 2296 invalid argument passed to noop sort():
2297 2297
2298 2298 $ log '0:2 & sort()'
2299 2299 hg: parse error: sort requires one or two arguments
2300 2300 [255]
2301 2301 $ log '0:2 & sort(all(), -invalid)'
2302 2302 hg: parse error: unknown sort key '-invalid'
2303 2303 [255]
2304 2304
2305 2305 for 'A & f(B)', 'B' should not be affected by the order of 'A':
2306 2306
2307 2307 $ try --optimize '2:0 & first(1 + 0 + 2)'
2308 2308 (and
2309 2309 (range
2310 2310 ('symbol', '2')
2311 2311 ('symbol', '0'))
2312 2312 (func
2313 2313 ('symbol', 'first')
2314 2314 (or
2315 2315 (list
2316 2316 ('symbol', '1')
2317 2317 ('symbol', '0')
2318 2318 ('symbol', '2')))))
2319 2319 * optimized:
2320 2320 (and
2321 2321 (range
2322 2322 ('symbol', '2')
2323 2323 ('symbol', '0'))
2324 2324 (func
2325 2325 ('symbol', 'first')
2326 2326 (func
2327 2327 ('symbol', '_list')
2328 2328 ('string', '1\x000\x002'))))
2329 2329 * set:
2330 2330 <filteredset
2331 2331 <baseset [1]>,
2332 2332 <spanset- 0:3>>
2333 2333 1
2334 2334
2335 2335 $ try --optimize '2:0 & not last(0 + 2 + 1)'
2336 2336 (and
2337 2337 (range
2338 2338 ('symbol', '2')
2339 2339 ('symbol', '0'))
2340 2340 (not
2341 2341 (func
2342 2342 ('symbol', 'last')
2343 2343 (or
2344 2344 (list
2345 2345 ('symbol', '0')
2346 2346 ('symbol', '2')
2347 2347 ('symbol', '1'))))))
2348 2348 * optimized:
2349 2349 (difference
2350 2350 (range
2351 2351 ('symbol', '2')
2352 2352 ('symbol', '0'))
2353 2353 (func
2354 2354 ('symbol', 'last')
2355 2355 (func
2356 2356 ('symbol', '_list')
2357 2357 ('string', '0\x002\x001'))))
2358 2358 * set:
2359 2359 <filteredset
2360 2360 <spanset- 0:3>,
2361 2361 <not
2362 2362 <baseset [1]>>>
2363 2363 2
2364 2364 0
2365 2365
2366 2366 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
2367 2367
2368 2368 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
2369 2369 (and
2370 2370 (range
2371 2371 ('symbol', '2')
2372 2372 ('symbol', '0'))
2373 2373 (range
2374 2374 (group
2375 2375 (or
2376 2376 (list
2377 2377 ('symbol', '1')
2378 2378 ('symbol', '0')
2379 2379 ('symbol', '2'))))
2380 2380 (group
2381 2381 (or
2382 2382 (list
2383 2383 ('symbol', '0')
2384 2384 ('symbol', '2')
2385 2385 ('symbol', '1'))))))
2386 2386 * optimized:
2387 2387 (and
2388 2388 (range
2389 2389 ('symbol', '2')
2390 2390 ('symbol', '0'))
2391 2391 (range
2392 2392 (func
2393 2393 ('symbol', '_list')
2394 2394 ('string', '1\x000\x002'))
2395 2395 (func
2396 2396 ('symbol', '_list')
2397 2397 ('string', '0\x002\x001'))))
2398 2398 * set:
2399 2399 <filteredset
2400 2400 <spanset- 0:3>,
2401 2401 <baseset [1]>>
2402 2402 1
2403 2403
2404 2404 'A & B' can be rewritten as 'flipand(B, A)' by weight.
2405 2405
2406 2406 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
2407 2407 (and
2408 2408 (func
2409 2409 ('symbol', 'contains')
2410 2410 ('string', 'glob:*'))
2411 2411 (group
2412 2412 (or
2413 2413 (list
2414 2414 ('symbol', '2')
2415 2415 ('symbol', '0')
2416 2416 ('symbol', '1')))))
2417 2417 * optimized:
2418 2418 (andsmally
2419 2419 (func
2420 2420 ('symbol', 'contains')
2421 2421 ('string', 'glob:*'))
2422 2422 (func
2423 2423 ('symbol', '_list')
2424 2424 ('string', '2\x000\x001')))
2425 2425 * set:
2426 2426 <filteredset
2427 2427 <baseset+ [0, 1, 2]>,
2428 2428 <contains 'glob:*'>>
2429 2429 0
2430 2430 1
2431 2431 2
2432 2432
2433 2433 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
2434 2434 the order appropriately:
2435 2435
2436 2436 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
2437 2437 (and
2438 2438 (func
2439 2439 ('symbol', 'reverse')
2440 2440 (func
2441 2441 ('symbol', 'contains')
2442 2442 ('string', 'glob:*')))
2443 2443 (group
2444 2444 (or
2445 2445 (list
2446 2446 ('symbol', '0')
2447 2447 ('symbol', '2')
2448 2448 ('symbol', '1')))))
2449 2449 * optimized:
2450 2450 (andsmally
2451 2451 (func
2452 2452 ('symbol', 'reverse')
2453 2453 (func
2454 2454 ('symbol', 'contains')
2455 2455 ('string', 'glob:*')))
2456 2456 (func
2457 2457 ('symbol', '_list')
2458 2458 ('string', '0\x002\x001')))
2459 2459 * set:
2460 2460 <filteredset
2461 2461 <baseset- [0, 1, 2]>,
2462 2462 <contains 'glob:*'>>
2463 2463 2
2464 2464 1
2465 2465 0
2466 2466
2467 2467 test sort revset
2468 2468 --------------------------------------------
2469 2469
2470 2470 test when adding two unordered revsets
2471 2471
2472 2472 $ log 'sort(keyword(issue) or modifies(b))'
2473 2473 4
2474 2474 6
2475 2475
2476 2476 test when sorting a reversed collection in the same way it is
2477 2477
2478 2478 $ log 'sort(reverse(all()), -rev)'
2479 2479 9
2480 2480 8
2481 2481 7
2482 2482 6
2483 2483 5
2484 2484 4
2485 2485 3
2486 2486 2
2487 2487 1
2488 2488 0
2489 2489
2490 2490 test when sorting a reversed collection
2491 2491
2492 2492 $ log 'sort(reverse(all()), rev)'
2493 2493 0
2494 2494 1
2495 2495 2
2496 2496 3
2497 2497 4
2498 2498 5
2499 2499 6
2500 2500 7
2501 2501 8
2502 2502 9
2503 2503
2504 2504
2505 2505 test sorting two sorted collections in different orders
2506 2506
2507 2507 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
2508 2508 2
2509 2509 6
2510 2510 8
2511 2511 9
2512 2512
2513 2513 test sorting two sorted collections in different orders backwards
2514 2514
2515 2515 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
2516 2516 9
2517 2517 8
2518 2518 6
2519 2519 2
2520 2520
2521 2521 test empty sort key which is noop
2522 2522
2523 2523 $ log 'sort(0 + 2 + 1, "")'
2524 2524 0
2525 2525 2
2526 2526 1
2527 2527
2528 2528 test invalid sort keys
2529 2529
2530 2530 $ log 'sort(all(), -invalid)'
2531 2531 hg: parse error: unknown sort key '-invalid'
2532 2532 [255]
2533 2533
2534 2534 $ cd ..
2535 2535
2536 2536 test sorting by multiple keys including variable-length strings
2537 2537
2538 2538 $ hg init sorting
2539 2539 $ cd sorting
2540 2540 $ cat <<EOF >> .hg/hgrc
2541 2541 > [ui]
2542 2542 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
2543 2543 > [templatealias]
2544 2544 > p5(s) = pad(s, 5)
2545 2545 > EOF
2546 2546 $ hg branch -qf b12
2547 2547 $ hg ci -m m111 -u u112 -d '111 10800'
2548 2548 $ hg branch -qf b11
2549 2549 $ hg ci -m m12 -u u111 -d '112 7200'
2550 2550 $ hg branch -qf b111
2551 2551 $ hg ci -m m11 -u u12 -d '111 3600'
2552 2552 $ hg branch -qf b112
2553 2553 $ hg ci -m m111 -u u11 -d '120 0'
2554 2554 $ hg branch -qf b111
2555 2555 $ hg ci -m m112 -u u111 -d '110 14400'
2556 2556 created new head
2557 2557
2558 2558 compare revisions (has fast path):
2559 2559
2560 2560 $ hg log -r 'sort(all(), rev)'
2561 2561 0 b12 m111 u112 111 10800
2562 2562 1 b11 m12 u111 112 7200
2563 2563 2 b111 m11 u12 111 3600
2564 2564 3 b112 m111 u11 120 0
2565 2565 4 b111 m112 u111 110 14400
2566 2566
2567 2567 $ hg log -r 'sort(all(), -rev)'
2568 2568 4 b111 m112 u111 110 14400
2569 2569 3 b112 m111 u11 120 0
2570 2570 2 b111 m11 u12 111 3600
2571 2571 1 b11 m12 u111 112 7200
2572 2572 0 b12 m111 u112 111 10800
2573 2573
2574 2574 compare variable-length strings (issue5218):
2575 2575
2576 2576 $ hg log -r 'sort(all(), branch)'
2577 2577 1 b11 m12 u111 112 7200
2578 2578 2 b111 m11 u12 111 3600
2579 2579 4 b111 m112 u111 110 14400
2580 2580 3 b112 m111 u11 120 0
2581 2581 0 b12 m111 u112 111 10800
2582 2582
2583 2583 $ hg log -r 'sort(all(), -branch)'
2584 2584 0 b12 m111 u112 111 10800
2585 2585 3 b112 m111 u11 120 0
2586 2586 2 b111 m11 u12 111 3600
2587 2587 4 b111 m112 u111 110 14400
2588 2588 1 b11 m12 u111 112 7200
2589 2589
2590 2590 $ hg log -r 'sort(all(), desc)'
2591 2591 2 b111 m11 u12 111 3600
2592 2592 0 b12 m111 u112 111 10800
2593 2593 3 b112 m111 u11 120 0
2594 2594 4 b111 m112 u111 110 14400
2595 2595 1 b11 m12 u111 112 7200
2596 2596
2597 2597 $ hg log -r 'sort(all(), -desc)'
2598 2598 1 b11 m12 u111 112 7200
2599 2599 4 b111 m112 u111 110 14400
2600 2600 0 b12 m111 u112 111 10800
2601 2601 3 b112 m111 u11 120 0
2602 2602 2 b111 m11 u12 111 3600
2603 2603
2604 2604 $ hg log -r 'sort(all(), user)'
2605 2605 3 b112 m111 u11 120 0
2606 2606 1 b11 m12 u111 112 7200
2607 2607 4 b111 m112 u111 110 14400
2608 2608 0 b12 m111 u112 111 10800
2609 2609 2 b111 m11 u12 111 3600
2610 2610
2611 2611 $ hg log -r 'sort(all(), -user)'
2612 2612 2 b111 m11 u12 111 3600
2613 2613 0 b12 m111 u112 111 10800
2614 2614 1 b11 m12 u111 112 7200
2615 2615 4 b111 m112 u111 110 14400
2616 2616 3 b112 m111 u11 120 0
2617 2617
2618 2618 compare dates (tz offset should have no effect):
2619 2619
2620 2620 $ hg log -r 'sort(all(), date)'
2621 2621 4 b111 m112 u111 110 14400
2622 2622 0 b12 m111 u112 111 10800
2623 2623 2 b111 m11 u12 111 3600
2624 2624 1 b11 m12 u111 112 7200
2625 2625 3 b112 m111 u11 120 0
2626 2626
2627 2627 $ hg log -r 'sort(all(), -date)'
2628 2628 3 b112 m111 u11 120 0
2629 2629 1 b11 m12 u111 112 7200
2630 2630 0 b12 m111 u112 111 10800
2631 2631 2 b111 m11 u12 111 3600
2632 2632 4 b111 m112 u111 110 14400
2633 2633
2634 2634 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
2635 2635 because '-k' reverses the comparison, not the list itself:
2636 2636
2637 2637 $ hg log -r 'sort(0 + 2, date)'
2638 2638 0 b12 m111 u112 111 10800
2639 2639 2 b111 m11 u12 111 3600
2640 2640
2641 2641 $ hg log -r 'sort(0 + 2, -date)'
2642 2642 0 b12 m111 u112 111 10800
2643 2643 2 b111 m11 u12 111 3600
2644 2644
2645 2645 $ hg log -r 'reverse(sort(0 + 2, date))'
2646 2646 2 b111 m11 u12 111 3600
2647 2647 0 b12 m111 u112 111 10800
2648 2648
2649 2649 sort by multiple keys:
2650 2650
2651 2651 $ hg log -r 'sort(all(), "branch -rev")'
2652 2652 1 b11 m12 u111 112 7200
2653 2653 4 b111 m112 u111 110 14400
2654 2654 2 b111 m11 u12 111 3600
2655 2655 3 b112 m111 u11 120 0
2656 2656 0 b12 m111 u112 111 10800
2657 2657
2658 2658 $ hg log -r 'sort(all(), "-desc -date")'
2659 2659 1 b11 m12 u111 112 7200
2660 2660 4 b111 m112 u111 110 14400
2661 2661 3 b112 m111 u11 120 0
2662 2662 0 b12 m111 u112 111 10800
2663 2663 2 b111 m11 u12 111 3600
2664 2664
2665 2665 $ hg log -r 'sort(all(), "user -branch date rev")'
2666 2666 3 b112 m111 u11 120 0
2667 2667 4 b111 m112 u111 110 14400
2668 2668 1 b11 m12 u111 112 7200
2669 2669 0 b12 m111 u112 111 10800
2670 2670 2 b111 m11 u12 111 3600
2671 2671
2672 2672 toposort prioritises graph branches
2673 2673
2674 2674 $ hg up 2
2675 2675 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2676 2676 $ touch a
2677 2677 $ hg addremove
2678 2678 adding a
2679 2679 $ hg ci -m 't1' -u 'tu' -d '130 0'
2680 2680 created new head
2681 2681 $ echo 'a' >> a
2682 2682 $ hg ci -m 't2' -u 'tu' -d '130 0'
2683 2683 $ hg book book1
2684 2684 $ hg up 4
2685 2685 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2686 2686 (leaving bookmark book1)
2687 2687 $ touch a
2688 2688 $ hg addremove
2689 2689 adding a
2690 2690 $ hg ci -m 't3' -u 'tu' -d '130 0'
2691 2691
2692 2692 $ hg log -r 'sort(all(), topo)'
2693 2693 7 b111 t3 tu 130 0
2694 2694 4 b111 m112 u111 110 14400
2695 2695 3 b112 m111 u11 120 0
2696 2696 6 b111 t2 tu 130 0
2697 2697 5 b111 t1 tu 130 0
2698 2698 2 b111 m11 u12 111 3600
2699 2699 1 b11 m12 u111 112 7200
2700 2700 0 b12 m111 u112 111 10800
2701 2701
2702 2702 $ hg log -r 'sort(all(), -topo)'
2703 2703 0 b12 m111 u112 111 10800
2704 2704 1 b11 m12 u111 112 7200
2705 2705 2 b111 m11 u12 111 3600
2706 2706 5 b111 t1 tu 130 0
2707 2707 6 b111 t2 tu 130 0
2708 2708 3 b112 m111 u11 120 0
2709 2709 4 b111 m112 u111 110 14400
2710 2710 7 b111 t3 tu 130 0
2711 2711
2712 2712 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2713 2713 6 b111 t2 tu 130 0
2714 2714 5 b111 t1 tu 130 0
2715 2715 7 b111 t3 tu 130 0
2716 2716 4 b111 m112 u111 110 14400
2717 2717 3 b112 m111 u11 120 0
2718 2718 2 b111 m11 u12 111 3600
2719 2719 1 b11 m12 u111 112 7200
2720 2720 0 b12 m111 u112 111 10800
2721 2721
2722 2722 topographical sorting can't be combined with other sort keys, and you can't
2723 2723 use the topo.firstbranch option when topo sort is not active:
2724 2724
2725 2725 $ hg log -r 'sort(all(), "topo user")'
2726 2726 hg: parse error: topo sort order cannot be combined with other sort keys
2727 2727 [255]
2728 2728
2729 2729 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2730 2730 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2731 2731 [255]
2732 2732
2733 2733 topo.firstbranch should accept any kind of expressions:
2734 2734
2735 2735 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2736 2736 0 b12 m111 u112 111 10800
2737 2737
2738 2738 $ cd ..
2739 2739 $ cd repo
2740 2740
2741 2741 test subtracting something from an addset
2742 2742
2743 2743 $ log '(outgoing() or removes(a)) - removes(a)'
2744 2744 8
2745 2745 9
2746 2746
2747 2747 test intersecting something with an addset
2748 2748
2749 2749 $ log 'parents(outgoing() or removes(a))'
2750 2750 1
2751 2751 4
2752 2752 5
2753 2753 8
2754 2754
2755 2755 test that `or` operation combines elements in the right order:
2756 2756
2757 2757 $ log '3:4 or 2:5'
2758 2758 3
2759 2759 4
2760 2760 2
2761 2761 5
2762 2762 $ log '3:4 or 5:2'
2763 2763 3
2764 2764 4
2765 2765 5
2766 2766 2
2767 2767 $ log 'sort(3:4 or 2:5)'
2768 2768 2
2769 2769 3
2770 2770 4
2771 2771 5
2772 2772 $ log 'sort(3:4 or 5:2)'
2773 2773 2
2774 2774 3
2775 2775 4
2776 2776 5
2777 2777
2778 2778 test that more than one `-r`s are combined in the right order and deduplicated:
2779 2779
2780 2780 $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
2781 2781 3
2782 2782 4
2783 2783 5
2784 2784 2
2785 2785 0
2786 2786 1
2787 2787
2788 2788 test that `or` operation skips duplicated revisions from right-hand side
2789 2789
2790 2790 $ try 'reverse(1::5) or ancestors(4)'
2791 2791 (or
2792 2792 (list
2793 2793 (func
2794 2794 ('symbol', 'reverse')
2795 2795 (dagrange
2796 2796 ('symbol', '1')
2797 2797 ('symbol', '5')))
2798 2798 (func
2799 2799 ('symbol', 'ancestors')
2800 2800 ('symbol', '4'))))
2801 2801 * set:
2802 2802 <addset
2803 2803 <baseset- [1, 3, 5]>,
2804 2804 <generatorset+>>
2805 2805 5
2806 2806 3
2807 2807 1
2808 2808 0
2809 2809 2
2810 2810 4
2811 2811 $ try 'sort(ancestors(4) or reverse(1::5))'
2812 2812 (func
2813 2813 ('symbol', 'sort')
2814 2814 (or
2815 2815 (list
2816 2816 (func
2817 2817 ('symbol', 'ancestors')
2818 2818 ('symbol', '4'))
2819 2819 (func
2820 2820 ('symbol', 'reverse')
2821 2821 (dagrange
2822 2822 ('symbol', '1')
2823 2823 ('symbol', '5'))))))
2824 2824 * set:
2825 2825 <addset+
2826 2826 <generatorset+>,
2827 2827 <baseset- [1, 3, 5]>>
2828 2828 0
2829 2829 1
2830 2830 2
2831 2831 3
2832 2832 4
2833 2833 5
2834 2834
2835 2835 test optimization of trivial `or` operation
2836 2836
2837 2837 $ try --optimize '0|(1)|"2"|-2|tip|null'
2838 2838 (or
2839 2839 (list
2840 2840 ('symbol', '0')
2841 2841 (group
2842 2842 ('symbol', '1'))
2843 2843 ('string', '2')
2844 2844 (negate
2845 2845 ('symbol', '2'))
2846 2846 ('symbol', 'tip')
2847 2847 ('symbol', 'null')))
2848 2848 * optimized:
2849 2849 (func
2850 2850 ('symbol', '_list')
2851 2851 ('string', '0\x001\x002\x00-2\x00tip\x00null'))
2852 2852 * set:
2853 2853 <baseset [0, 1, 2, 8, 9, -1]>
2854 2854 0
2855 2855 1
2856 2856 2
2857 2857 8
2858 2858 9
2859 2859 -1
2860 2860
2861 2861 $ try --optimize '0|1|2:3'
2862 2862 (or
2863 2863 (list
2864 2864 ('symbol', '0')
2865 2865 ('symbol', '1')
2866 2866 (range
2867 2867 ('symbol', '2')
2868 2868 ('symbol', '3'))))
2869 2869 * optimized:
2870 2870 (or
2871 2871 (list
2872 2872 (func
2873 2873 ('symbol', '_list')
2874 2874 ('string', '0\x001'))
2875 2875 (range
2876 2876 ('symbol', '2')
2877 2877 ('symbol', '3'))))
2878 2878 * set:
2879 2879 <addset
2880 2880 <baseset [0, 1]>,
2881 2881 <spanset+ 2:4>>
2882 2882 0
2883 2883 1
2884 2884 2
2885 2885 3
2886 2886
2887 2887 $ try --optimize '0:1|2|3:4|5|6'
2888 2888 (or
2889 2889 (list
2890 2890 (range
2891 2891 ('symbol', '0')
2892 2892 ('symbol', '1'))
2893 2893 ('symbol', '2')
2894 2894 (range
2895 2895 ('symbol', '3')
2896 2896 ('symbol', '4'))
2897 2897 ('symbol', '5')
2898 2898 ('symbol', '6')))
2899 2899 * optimized:
2900 2900 (or
2901 2901 (list
2902 2902 (range
2903 2903 ('symbol', '0')
2904 2904 ('symbol', '1'))
2905 2905 ('symbol', '2')
2906 2906 (range
2907 2907 ('symbol', '3')
2908 2908 ('symbol', '4'))
2909 2909 (func
2910 2910 ('symbol', '_list')
2911 2911 ('string', '5\x006'))))
2912 2912 * set:
2913 2913 <addset
2914 2914 <addset
2915 2915 <spanset+ 0:2>,
2916 2916 <baseset [2]>>,
2917 2917 <addset
2918 2918 <spanset+ 3:5>,
2919 2919 <baseset [5, 6]>>>
2920 2920 0
2921 2921 1
2922 2922 2
2923 2923 3
2924 2924 4
2925 2925 5
2926 2926 6
2927 2927
2928 2928 unoptimized `or` looks like this
2929 2929
2930 2930 $ try --no-optimized -p analyzed '0|1|2|3|4'
2931 2931 * analyzed:
2932 2932 (or
2933 2933 (list
2934 2934 ('symbol', '0')
2935 2935 ('symbol', '1')
2936 2936 ('symbol', '2')
2937 2937 ('symbol', '3')
2938 2938 ('symbol', '4')))
2939 2939 * set:
2940 2940 <addset
2941 2941 <addset
2942 2942 <baseset [0]>,
2943 2943 <baseset [1]>>,
2944 2944 <addset
2945 2945 <baseset [2]>,
2946 2946 <addset
2947 2947 <baseset [3]>,
2948 2948 <baseset [4]>>>>
2949 2949 0
2950 2950 1
2951 2951 2
2952 2952 3
2953 2953 4
2954 2954
2955 2955 test that `_list` should be narrowed by provided `subset`
2956 2956
2957 2957 $ log '0:2 and (null|1|2|3)'
2958 2958 1
2959 2959 2
2960 2960
2961 2961 test that `_list` should remove duplicates
2962 2962
2963 2963 $ log '0|1|2|1|2|-1|tip'
2964 2964 0
2965 2965 1
2966 2966 2
2967 2967 9
2968 2968
2969 2969 test unknown revision in `_list`
2970 2970
2971 2971 $ log '0|unknown'
2972 2972 abort: unknown revision 'unknown'!
2973 2973 [255]
2974 2974
2975 2975 test integer range in `_list`
2976 2976
2977 2977 $ log '-1|-10'
2978 2978 9
2979 2979 0
2980 2980
2981 2981 $ log '-10|-11'
2982 2982 abort: unknown revision '-11'!
2983 2983 [255]
2984 2984
2985 2985 $ log '9|10'
2986 2986 abort: unknown revision '10'!
2987 2987 [255]
2988 2988
2989 2989 test '0000' != '0' in `_list`
2990 2990
2991 2991 $ log '0|0000'
2992 2992 0
2993 2993 -1
2994 2994
2995 2995 test ',' in `_list`
2996 2996 $ log '0,1'
2997 2997 hg: parse error: can't use a list in this context
2998 2998 (see hg help "revsets.x or y")
2999 2999 [255]
3000 3000 $ try '0,1,2'
3001 3001 (list
3002 3002 ('symbol', '0')
3003 3003 ('symbol', '1')
3004 3004 ('symbol', '2'))
3005 3005 hg: parse error: can't use a list in this context
3006 3006 (see hg help "revsets.x or y")
3007 3007 [255]
3008 3008
3009 3009 test that chained `or` operations make balanced addsets
3010 3010
3011 3011 $ try '0:1|1:2|2:3|3:4|4:5'
3012 3012 (or
3013 3013 (list
3014 3014 (range
3015 3015 ('symbol', '0')
3016 3016 ('symbol', '1'))
3017 3017 (range
3018 3018 ('symbol', '1')
3019 3019 ('symbol', '2'))
3020 3020 (range
3021 3021 ('symbol', '2')
3022 3022 ('symbol', '3'))
3023 3023 (range
3024 3024 ('symbol', '3')
3025 3025 ('symbol', '4'))
3026 3026 (range
3027 3027 ('symbol', '4')
3028 3028 ('symbol', '5'))))
3029 3029 * set:
3030 3030 <addset
3031 3031 <addset
3032 3032 <spanset+ 0:2>,
3033 3033 <spanset+ 1:3>>,
3034 3034 <addset
3035 3035 <spanset+ 2:4>,
3036 3036 <addset
3037 3037 <spanset+ 3:5>,
3038 3038 <spanset+ 4:6>>>>
3039 3039 0
3040 3040 1
3041 3041 2
3042 3042 3
3043 3043 4
3044 3044 5
3045 3045
3046 3046 no crash by empty group "()" while optimizing `or` operations
3047 3047
3048 3048 $ try --optimize '0|()'
3049 3049 (or
3050 3050 (list
3051 3051 ('symbol', '0')
3052 3052 (group
3053 3053 None)))
3054 3054 * optimized:
3055 3055 (or
3056 3056 (list
3057 3057 ('symbol', '0')
3058 3058 None))
3059 3059 hg: parse error: missing argument
3060 3060 [255]
3061 3061
3062 3062 test that chained `or` operations never eat up stack (issue4624)
3063 3063 (uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
3064 3064
3065 3065 $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
3066 3066 0
3067 3067 1
3068 3068
3069 3069 test that repeated `-r` options never eat up stack (issue4565)
3070 3070 (uses `-r 0::1` to avoid possible optimization at old-style parser)
3071 3071
3072 3072 $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"`
3073 3073 0
3074 3074 1
3075 3075
3076 3076 check that conversion to only works
3077 3077 $ try --optimize '::3 - ::1'
3078 3078 (minus
3079 3079 (dagrangepre
3080 3080 ('symbol', '3'))
3081 3081 (dagrangepre
3082 3082 ('symbol', '1')))
3083 3083 * optimized:
3084 3084 (func
3085 3085 ('symbol', 'only')
3086 3086 (list
3087 3087 ('symbol', '3')
3088 3088 ('symbol', '1')))
3089 3089 * set:
3090 3090 <baseset+ [3]>
3091 3091 3
3092 3092 $ try --optimize 'ancestors(1) - ancestors(3)'
3093 3093 (minus
3094 3094 (func
3095 3095 ('symbol', 'ancestors')
3096 3096 ('symbol', '1'))
3097 3097 (func
3098 3098 ('symbol', 'ancestors')
3099 3099 ('symbol', '3')))
3100 3100 * optimized:
3101 3101 (func
3102 3102 ('symbol', 'only')
3103 3103 (list
3104 3104 ('symbol', '1')
3105 3105 ('symbol', '3')))
3106 3106 * set:
3107 3107 <baseset+ []>
3108 3108 $ try --optimize 'not ::2 and ::6'
3109 3109 (and
3110 3110 (not
3111 3111 (dagrangepre
3112 3112 ('symbol', '2')))
3113 3113 (dagrangepre
3114 3114 ('symbol', '6')))
3115 3115 * optimized:
3116 3116 (func
3117 3117 ('symbol', 'only')
3118 3118 (list
3119 3119 ('symbol', '6')
3120 3120 ('symbol', '2')))
3121 3121 * set:
3122 3122 <baseset+ [3, 4, 5, 6]>
3123 3123 3
3124 3124 4
3125 3125 5
3126 3126 6
3127 3127 $ try --optimize 'ancestors(6) and not ancestors(4)'
3128 3128 (and
3129 3129 (func
3130 3130 ('symbol', 'ancestors')
3131 3131 ('symbol', '6'))
3132 3132 (not
3133 3133 (func
3134 3134 ('symbol', 'ancestors')
3135 3135 ('symbol', '4'))))
3136 3136 * optimized:
3137 3137 (func
3138 3138 ('symbol', 'only')
3139 3139 (list
3140 3140 ('symbol', '6')
3141 3141 ('symbol', '4')))
3142 3142 * set:
3143 3143 <baseset+ [3, 5, 6]>
3144 3144 3
3145 3145 5
3146 3146 6
3147 3147
3148 3148 no crash by empty group "()" while optimizing to "only()"
3149 3149
3150 3150 $ try --optimize '::1 and ()'
3151 3151 (and
3152 3152 (dagrangepre
3153 3153 ('symbol', '1'))
3154 3154 (group
3155 3155 None))
3156 3156 * optimized:
3157 3157 (andsmally
3158 3158 (func
3159 3159 ('symbol', 'ancestors')
3160 3160 ('symbol', '1'))
3161 3161 None)
3162 3162 hg: parse error: missing argument
3163 3163 [255]
3164 3164
3165 3165 optimization to only() works only if ancestors() takes only one argument
3166 3166
3167 3167 $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
3168 3168 * optimized:
3169 3169 (difference
3170 3170 (func
3171 3171 ('symbol', 'ancestors')
3172 3172 ('symbol', '6'))
3173 3173 (func
3174 3174 ('symbol', 'ancestors')
3175 3175 (list
3176 3176 ('symbol', '4')
3177 3177 ('symbol', '1'))))
3178 3178 0
3179 3179 1
3180 3180 3
3181 3181 5
3182 3182 6
3183 3183 $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
3184 3184 * optimized:
3185 3185 (difference
3186 3186 (func
3187 3187 ('symbol', 'ancestors')
3188 3188 (list
3189 3189 ('symbol', '6')
3190 3190 ('symbol', '1')))
3191 3191 (func
3192 3192 ('symbol', 'ancestors')
3193 3193 ('symbol', '4')))
3194 3194 5
3195 3195 6
3196 3196
3197 3197 optimization disabled if keyword arguments passed (because we're too lazy
3198 3198 to support it)
3199 3199
3200 3200 $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
3201 3201 * optimized:
3202 3202 (difference
3203 3203 (func
3204 3204 ('symbol', 'ancestors')
3205 3205 (keyvalue
3206 3206 ('symbol', 'set')
3207 3207 ('symbol', '6')))
3208 3208 (func
3209 3209 ('symbol', 'ancestors')
3210 3210 (keyvalue
3211 3211 ('symbol', 'set')
3212 3212 ('symbol', '4'))))
3213 3213 3
3214 3214 5
3215 3215 6
3216 3216
3217 3217 invalid function call should not be optimized to only()
3218 3218
3219 3219 $ log '"ancestors"(6) and not ancestors(4)'
3220 3220 hg: parse error: not a symbol
3221 3221 [255]
3222 3222
3223 3223 $ log 'ancestors(6) and not "ancestors"(4)'
3224 3224 hg: parse error: not a symbol
3225 3225 [255]
3226 3226
3227 3227 we can use patterns when searching for tags
3228 3228
3229 3229 $ log 'tag("1..*")'
3230 3230 abort: tag '1..*' does not exist!
3231 3231 [255]
3232 3232 $ log 'tag("re:1..*")'
3233 3233 6
3234 3234 $ log 'tag("re:[0-9].[0-9]")'
3235 3235 6
3236 3236 $ log 'tag("literal:1.0")'
3237 3237 6
3238 3238 $ log 'tag("re:0..*")'
3239 3239
3240 3240 $ log 'tag(unknown)'
3241 3241 abort: tag 'unknown' does not exist!
3242 3242 [255]
3243 3243 $ log 'tag("re:unknown")'
3244 3244 $ log 'present(tag("unknown"))'
3245 3245 $ log 'present(tag("re:unknown"))'
3246 3246 $ log 'branch(unknown)'
3247 3247 abort: unknown revision 'unknown'!
3248 3248 [255]
3249 3249 $ log 'branch("literal:unknown")'
3250 3250 abort: branch 'unknown' does not exist!
3251 3251 [255]
3252 3252 $ log 'branch("re:unknown")'
3253 3253 $ log 'present(branch("unknown"))'
3254 3254 $ log 'present(branch("re:unknown"))'
3255 3255 $ log 'user(bob)'
3256 3256 2
3257 3257
3258 3258 $ log '4::8'
3259 3259 4
3260 3260 8
3261 3261 $ log '4:8'
3262 3262 4
3263 3263 5
3264 3264 6
3265 3265 7
3266 3266 8
3267 3267
3268 3268 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
3269 3269 4
3270 3270 2
3271 3271 5
3272 3272
3273 3273 $ log 'not 0 and 0:2'
3274 3274 1
3275 3275 2
3276 3276 $ log 'not 1 and 0:2'
3277 3277 0
3278 3278 2
3279 3279 $ log 'not 2 and 0:2'
3280 3280 0
3281 3281 1
3282 3282 $ log '(1 and 2)::'
3283 3283 $ log '(1 and 2):'
3284 3284 $ log '(1 and 2):3'
3285 3285 $ log 'sort(head(), -rev)'
3286 3286 9
3287 3287 7
3288 3288 6
3289 3289 5
3290 3290 4
3291 3291 3
3292 3292 2
3293 3293 1
3294 3294 0
3295 3295 $ log '4::8 - 8'
3296 3296 4
3297 3297
3298 3298 matching() should preserve the order of the input set:
3299 3299
3300 3300 $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
3301 3301 2
3302 3302 3
3303 3303 1
3304 3304
3305 3305 $ log 'named("unknown")'
3306 3306 abort: namespace 'unknown' does not exist!
3307 3307 [255]
3308 3308 $ log 'named("re:unknown")'
3309 3309 abort: no namespace exists that match 'unknown'!
3310 3310 [255]
3311 3311 $ log 'present(named("unknown"))'
3312 3312 $ log 'present(named("re:unknown"))'
3313 3313
3314 3314 $ log 'tag()'
3315 3315 6
3316 3316 $ log 'named("tags")'
3317 3317 6
3318 3318
3319 3319 issue2437
3320 3320
3321 3321 $ log '3 and p1(5)'
3322 3322 3
3323 3323 $ log '4 and p2(6)'
3324 3324 4
3325 3325 $ log '1 and parents(:2)'
3326 3326 1
3327 3327 $ log '2 and children(1:)'
3328 3328 2
3329 3329 $ log 'roots(all()) or roots(all())'
3330 3330 0
3331 3331 $ hg debugrevspec 'roots(all()) or roots(all())'
3332 3332 0
3333 3333 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
3334 3334 9
3335 3335 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
3336 3336 4
3337 3337
3338 3338 issue2654: report a parse error if the revset was not completely parsed
3339 3339
3340 3340 $ log '1 OR 2'
3341 3341 hg: parse error at 2: invalid token
3342 3342 [255]
3343 3343
3344 3344 or operator should preserve ordering:
3345 3345 $ log 'reverse(2::4) or tip'
3346 3346 4
3347 3347 2
3348 3348 9
3349 3349
3350 3350 parentrevspec
3351 3351
3352 3352 $ log 'merge()^0'
3353 3353 6
3354 3354 $ log 'merge()^'
3355 3355 5
3356 3356 $ log 'merge()^1'
3357 3357 5
3358 3358 $ log 'merge()^2'
3359 3359 4
3360 3360 $ log '(not merge())^2'
3361 3361 $ log 'merge()^^'
3362 3362 3
3363 3363 $ log 'merge()^1^'
3364 3364 3
3365 3365 $ log 'merge()^^^'
3366 3366 1
3367 3367
3368 3368 $ hg debugrevspec -s '(merge() | 0)~-1'
3369 3369 * set:
3370 3370 <baseset+ [1, 7]>
3371 3371 1
3372 3372 7
3373 3373 $ log 'merge()~-1'
3374 3374 7
3375 3375 $ log 'tip~-1'
3376 3376 $ log '(tip | merge())~-1'
3377 3377 7
3378 3378 $ log 'merge()~0'
3379 3379 6
3380 3380 $ log 'merge()~1'
3381 3381 5
3382 3382 $ log 'merge()~2'
3383 3383 3
3384 3384 $ log 'merge()~2^1'
3385 3385 1
3386 3386 $ log 'merge()~3'
3387 3387 1
3388 3388
3389 3389 $ log '(-3:tip)^'
3390 3390 4
3391 3391 6
3392 3392 8
3393 3393
3394 3394 $ log 'tip^foo'
3395 3395 hg: parse error: ^ expects a number 0, 1, or 2
3396 3396 [255]
3397 3397
3398 3398 $ log 'branchpoint()~-1'
3399 3399 abort: revision in set has more than one child!
3400 3400 [255]
3401 3401
3402 3402 Bogus function gets suggestions
3403 3403 $ log 'add()'
3404 3404 hg: parse error: unknown identifier: add
3405 3405 (did you mean adds?)
3406 3406 [255]
3407 3407 $ log 'added()'
3408 3408 hg: parse error: unknown identifier: added
3409 3409 (did you mean adds?)
3410 3410 [255]
3411 3411 $ log 'remo()'
3412 3412 hg: parse error: unknown identifier: remo
3413 3413 (did you mean one of remote, removes?)
3414 3414 [255]
3415 3415 $ log 'babar()'
3416 3416 hg: parse error: unknown identifier: babar
3417 3417 [255]
3418 3418
3419 3419 Bogus function with a similar internal name doesn't suggest the internal name
3420 3420 $ log 'matches()'
3421 3421 hg: parse error: unknown identifier: matches
3422 3422 (did you mean matching?)
3423 3423 [255]
3424 3424
3425 3425 Undocumented functions aren't suggested as similar either
3426 3426 $ log 'tagged2()'
3427 3427 hg: parse error: unknown identifier: tagged2
3428 3428 [255]
3429 3429
3430 3430 multiple revspecs
3431 3431
3432 3432 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
3433 3433 8
3434 3434 9
3435 3435 4
3436 3436 5
3437 3437 6
3438 3438 7
3439 3439
3440 3440 test usage in revpair (with "+")
3441 3441
3442 3442 (real pair)
3443 3443
3444 3444 $ hg diff -r 'tip^^' -r 'tip'
3445 3445 diff -r 2326846efdab -r 24286f4ae135 .hgtags
3446 3446 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3447 3447 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
3448 3448 @@ -0,0 +1,1 @@
3449 3449 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
3450 3450 $ hg diff -r 'tip^^::tip'
3451 3451 diff -r 2326846efdab -r 24286f4ae135 .hgtags
3452 3452 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3453 3453 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
3454 3454 @@ -0,0 +1,1 @@
3455 3455 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
3456 3456
3457 3457 (single rev)
3458 3458
3459 3459 $ hg diff -r 'tip^' -r 'tip^'
3460 3460 $ hg diff -r 'tip^:tip^'
3461 3461
3462 3462 (single rev that does not looks like a range)
3463 3463
3464 3464 $ hg diff -r 'tip^::tip^ or tip^'
3465 3465 diff -r d5d0dcbdc4d9 .hgtags
3466 3466 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3467 3467 +++ b/.hgtags * (glob)
3468 3468 @@ -0,0 +1,1 @@
3469 3469 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
3470 3470 $ hg diff -r 'tip^ or tip^'
3471 3471 diff -r d5d0dcbdc4d9 .hgtags
3472 3472 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3473 3473 +++ b/.hgtags * (glob)
3474 3474 @@ -0,0 +1,1 @@
3475 3475 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
3476 3476
3477 3477 (no rev)
3478 3478
3479 3479 $ hg diff -r 'author("babar") or author("celeste")'
3480 3480 abort: empty revision range
3481 3481 [255]
3482 3482
3483 3483 aliases:
3484 3484
3485 3485 $ echo '[revsetalias]' >> .hg/hgrc
3486 3486 $ echo 'm = merge()' >> .hg/hgrc
3487 3487 (revset aliases can override builtin revsets)
3488 3488 $ echo 'p2($1) = p1($1)' >> .hg/hgrc
3489 3489 $ echo 'sincem = descendants(m)' >> .hg/hgrc
3490 3490 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
3491 3491 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
3492 3492 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
3493 3493
3494 3494 $ try m
3495 3495 ('symbol', 'm')
3496 3496 * expanded:
3497 3497 (func
3498 3498 ('symbol', 'merge')
3499 3499 None)
3500 3500 * set:
3501 3501 <filteredset
3502 3502 <fullreposet+ 0:10>,
3503 3503 <merge>>
3504 3504 6
3505 3505
3506 3506 $ HGPLAIN=1
3507 3507 $ export HGPLAIN
3508 3508 $ try m
3509 3509 ('symbol', 'm')
3510 3510 abort: unknown revision 'm'!
3511 3511 [255]
3512 3512
3513 3513 $ HGPLAINEXCEPT=revsetalias
3514 3514 $ export HGPLAINEXCEPT
3515 3515 $ try m
3516 3516 ('symbol', 'm')
3517 3517 * expanded:
3518 3518 (func
3519 3519 ('symbol', 'merge')
3520 3520 None)
3521 3521 * set:
3522 3522 <filteredset
3523 3523 <fullreposet+ 0:10>,
3524 3524 <merge>>
3525 3525 6
3526 3526
3527 3527 $ unset HGPLAIN
3528 3528 $ unset HGPLAINEXCEPT
3529 3529
3530 3530 $ try 'p2(.)'
3531 3531 (func
3532 3532 ('symbol', 'p2')
3533 3533 ('symbol', '.'))
3534 3534 * expanded:
3535 3535 (func
3536 3536 ('symbol', 'p1')
3537 3537 ('symbol', '.'))
3538 3538 * set:
3539 3539 <baseset+ [8]>
3540 3540 8
3541 3541
3542 3542 $ HGPLAIN=1
3543 3543 $ export HGPLAIN
3544 3544 $ try 'p2(.)'
3545 3545 (func
3546 3546 ('symbol', 'p2')
3547 3547 ('symbol', '.'))
3548 3548 * set:
3549 3549 <baseset+ []>
3550 3550
3551 3551 $ HGPLAINEXCEPT=revsetalias
3552 3552 $ export HGPLAINEXCEPT
3553 3553 $ try 'p2(.)'
3554 3554 (func
3555 3555 ('symbol', 'p2')
3556 3556 ('symbol', '.'))
3557 3557 * expanded:
3558 3558 (func
3559 3559 ('symbol', 'p1')
3560 3560 ('symbol', '.'))
3561 3561 * set:
3562 3562 <baseset+ [8]>
3563 3563 8
3564 3564
3565 3565 $ unset HGPLAIN
3566 3566 $ unset HGPLAINEXCEPT
3567 3567
3568 3568 test alias recursion
3569 3569
3570 3570 $ try sincem
3571 3571 ('symbol', 'sincem')
3572 3572 * expanded:
3573 3573 (func
3574 3574 ('symbol', 'descendants')
3575 3575 (func
3576 3576 ('symbol', 'merge')
3577 3577 None))
3578 3578 * set:
3579 3579 <generatorset+>
3580 3580 6
3581 3581 7
3582 3582
3583 3583 test infinite recursion
3584 3584
3585 3585 $ echo 'recurse1 = recurse2' >> .hg/hgrc
3586 3586 $ echo 'recurse2 = recurse1' >> .hg/hgrc
3587 3587 $ try recurse1
3588 3588 ('symbol', 'recurse1')
3589 3589 hg: parse error: infinite expansion of revset alias "recurse1" detected
3590 3590 [255]
3591 3591
3592 3592 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
3593 3593 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
3594 3594 $ try "level2(level1(1, 2), 3)"
3595 3595 (func
3596 3596 ('symbol', 'level2')
3597 3597 (list
3598 3598 (func
3599 3599 ('symbol', 'level1')
3600 3600 (list
3601 3601 ('symbol', '1')
3602 3602 ('symbol', '2')))
3603 3603 ('symbol', '3')))
3604 3604 * expanded:
3605 3605 (or
3606 3606 (list
3607 3607 ('symbol', '3')
3608 3608 (or
3609 3609 (list
3610 3610 ('symbol', '1')
3611 3611 ('symbol', '2')))))
3612 3612 * set:
3613 3613 <addset
3614 3614 <baseset [3]>,
3615 3615 <baseset [1, 2]>>
3616 3616 3
3617 3617 1
3618 3618 2
3619 3619
3620 3620 test nesting and variable passing
3621 3621
3622 3622 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
3623 3623 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
3624 3624 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
3625 3625 $ try 'nested(2:5)'
3626 3626 (func
3627 3627 ('symbol', 'nested')
3628 3628 (range
3629 3629 ('symbol', '2')
3630 3630 ('symbol', '5')))
3631 3631 * expanded:
3632 3632 (func
3633 3633 ('symbol', 'max')
3634 3634 (range
3635 3635 ('symbol', '2')
3636 3636 ('symbol', '5')))
3637 3637 * set:
3638 3638 <baseset
3639 3639 <max
3640 3640 <fullreposet+ 0:10>,
3641 3641 <spanset+ 2:6>>>
3642 3642 5
3643 3643
3644 3644 test chained `or` operations are flattened at parsing phase
3645 3645
3646 3646 $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
3647 3647 $ try 'chainedorops(0:1, 1:2, 2:3)'
3648 3648 (func
3649 3649 ('symbol', 'chainedorops')
3650 3650 (list
3651 3651 (range
3652 3652 ('symbol', '0')
3653 3653 ('symbol', '1'))
3654 3654 (range
3655 3655 ('symbol', '1')
3656 3656 ('symbol', '2'))
3657 3657 (range
3658 3658 ('symbol', '2')
3659 3659 ('symbol', '3'))))
3660 3660 * expanded:
3661 3661 (or
3662 3662 (list
3663 3663 (range
3664 3664 ('symbol', '0')
3665 3665 ('symbol', '1'))
3666 3666 (range
3667 3667 ('symbol', '1')
3668 3668 ('symbol', '2'))
3669 3669 (range
3670 3670 ('symbol', '2')
3671 3671 ('symbol', '3'))))
3672 3672 * set:
3673 3673 <addset
3674 3674 <spanset+ 0:2>,
3675 3675 <addset
3676 3676 <spanset+ 1:3>,
3677 3677 <spanset+ 2:4>>>
3678 3678 0
3679 3679 1
3680 3680 2
3681 3681 3
3682 3682
3683 3683 test variable isolation, variable placeholders are rewritten as string
3684 3684 then parsed and matched again as string. Check they do not leak too
3685 3685 far away.
3686 3686
3687 3687 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
3688 3688 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
3689 3689 $ try 'callinjection(2:5)'
3690 3690 (func
3691 3691 ('symbol', 'callinjection')
3692 3692 (range
3693 3693 ('symbol', '2')
3694 3694 ('symbol', '5')))
3695 3695 * expanded:
3696 3696 (func
3697 3697 ('symbol', 'descendants')
3698 3698 (func
3699 3699 ('symbol', 'max')
3700 3700 ('string', '$1')))
3701 3701 abort: unknown revision '$1'!
3702 3702 [255]
3703 3703
3704 3704 test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
3705 3705 but 'all()' should never be substituted to '0()'.
3706 3706
3707 3707 $ echo 'universe = all()' >> .hg/hgrc
3708 3708 $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
3709 3709 $ try 'shadowall(0)'
3710 3710 (func
3711 3711 ('symbol', 'shadowall')
3712 3712 ('symbol', '0'))
3713 3713 * expanded:
3714 3714 (and
3715 3715 ('symbol', '0')
3716 3716 (func
3717 3717 ('symbol', 'all')
3718 3718 None))
3719 3719 * set:
3720 3720 <filteredset
3721 3721 <baseset [0]>,
3722 3722 <spanset+ 0:10>>
3723 3723 0
3724 3724
3725 3725 test unknown reference:
3726 3726
3727 3727 $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
3728 3728 (func
3729 3729 ('symbol', 'unknownref')
3730 3730 ('symbol', '0'))
3731 3731 abort: bad definition of revset alias "unknownref": invalid symbol '$2'
3732 3732 [255]
3733 3733
3734 3734 $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
3735 3735 ('symbol', 'tip')
3736 3736 warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
3737 3737 * set:
3738 3738 <baseset [9]>
3739 3739 9
3740 3740
3741 3741 $ try 'tip'
3742 3742 ('symbol', 'tip')
3743 3743 * set:
3744 3744 <baseset [9]>
3745 3745 9
3746 3746
3747 3747 $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
3748 3748 ('symbol', 'tip')
3749 3749 warning: bad declaration of revset alias "bad name": at 4: invalid token
3750 3750 * set:
3751 3751 <baseset [9]>
3752 3752 9
3753 3753 $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
3754 3754 $ try 'strictreplacing("foo", tip)'
3755 3755 (func
3756 3756 ('symbol', 'strictreplacing')
3757 3757 (list
3758 3758 ('string', 'foo')
3759 3759 ('symbol', 'tip')))
3760 3760 * expanded:
3761 3761 (or
3762 3762 (list
3763 3763 ('symbol', 'tip')
3764 3764 (func
3765 3765 ('symbol', 'desc')
3766 3766 ('string', '$1'))))
3767 3767 * set:
3768 3768 <addset
3769 3769 <baseset [9]>,
3770 3770 <filteredset
3771 3771 <fullreposet+ 0:10>,
3772 3772 <desc '$1'>>>
3773 3773 9
3774 3774
3775 3775 $ try 'd(2:5)'
3776 3776 (func
3777 3777 ('symbol', 'd')
3778 3778 (range
3779 3779 ('symbol', '2')
3780 3780 ('symbol', '5')))
3781 3781 * expanded:
3782 3782 (func
3783 3783 ('symbol', 'reverse')
3784 3784 (func
3785 3785 ('symbol', 'sort')
3786 3786 (list
3787 3787 (range
3788 3788 ('symbol', '2')
3789 3789 ('symbol', '5'))
3790 3790 ('symbol', 'date'))))
3791 3791 * set:
3792 3792 <baseset [4, 5, 3, 2]>
3793 3793 4
3794 3794 5
3795 3795 3
3796 3796 2
3797 3797 $ try 'rs(2 or 3, date)'
3798 3798 (func
3799 3799 ('symbol', 'rs')
3800 3800 (list
3801 3801 (or
3802 3802 (list
3803 3803 ('symbol', '2')
3804 3804 ('symbol', '3')))
3805 3805 ('symbol', 'date')))
3806 3806 * expanded:
3807 3807 (func
3808 3808 ('symbol', 'reverse')
3809 3809 (func
3810 3810 ('symbol', 'sort')
3811 3811 (list
3812 3812 (or
3813 3813 (list
3814 3814 ('symbol', '2')
3815 3815 ('symbol', '3')))
3816 3816 ('symbol', 'date'))))
3817 3817 * set:
3818 3818 <baseset [3, 2]>
3819 3819 3
3820 3820 2
3821 3821 $ try 'rs()'
3822 3822 (func
3823 3823 ('symbol', 'rs')
3824 3824 None)
3825 3825 hg: parse error: invalid number of arguments: 0
3826 3826 [255]
3827 3827 $ try 'rs(2)'
3828 3828 (func
3829 3829 ('symbol', 'rs')
3830 3830 ('symbol', '2'))
3831 3831 hg: parse error: invalid number of arguments: 1
3832 3832 [255]
3833 3833 $ try 'rs(2, data, 7)'
3834 3834 (func
3835 3835 ('symbol', 'rs')
3836 3836 (list
3837 3837 ('symbol', '2')
3838 3838 ('symbol', 'data')
3839 3839 ('symbol', '7')))
3840 3840 hg: parse error: invalid number of arguments: 3
3841 3841 [255]
3842 3842 $ try 'rs4(2 or 3, x, x, date)'
3843 3843 (func
3844 3844 ('symbol', 'rs4')
3845 3845 (list
3846 3846 (or
3847 3847 (list
3848 3848 ('symbol', '2')
3849 3849 ('symbol', '3')))
3850 3850 ('symbol', 'x')
3851 3851 ('symbol', 'x')
3852 3852 ('symbol', 'date')))
3853 3853 * expanded:
3854 3854 (func
3855 3855 ('symbol', 'reverse')
3856 3856 (func
3857 3857 ('symbol', 'sort')
3858 3858 (list
3859 3859 (or
3860 3860 (list
3861 3861 ('symbol', '2')
3862 3862 ('symbol', '3')))
3863 3863 ('symbol', 'date'))))
3864 3864 * set:
3865 3865 <baseset [3, 2]>
3866 3866 3
3867 3867 2
3868 3868
3869 3869 issue4553: check that revset aliases override existing hash prefix
3870 3870
3871 3871 $ hg log -qr e
3872 3872 6:e0cc66ef77e8
3873 3873
3874 3874 $ hg log -qr e --config revsetalias.e="all()"
3875 3875 0:2785f51eece5
3876 3876 1:d75937da8da0
3877 3877 2:5ed5505e9f1c
3878 3878 3:8528aa5637f2
3879 3879 4:2326846efdab
3880 3880 5:904fa392b941
3881 3881 6:e0cc66ef77e8
3882 3882 7:013af1973af4
3883 3883 8:d5d0dcbdc4d9
3884 3884 9:24286f4ae135
3885 3885
3886 3886 $ hg log -qr e: --config revsetalias.e="0"
3887 3887 0:2785f51eece5
3888 3888 1:d75937da8da0
3889 3889 2:5ed5505e9f1c
3890 3890 3:8528aa5637f2
3891 3891 4:2326846efdab
3892 3892 5:904fa392b941
3893 3893 6:e0cc66ef77e8
3894 3894 7:013af1973af4
3895 3895 8:d5d0dcbdc4d9
3896 3896 9:24286f4ae135
3897 3897
3898 3898 $ hg log -qr :e --config revsetalias.e="9"
3899 3899 0:2785f51eece5
3900 3900 1:d75937da8da0
3901 3901 2:5ed5505e9f1c
3902 3902 3:8528aa5637f2
3903 3903 4:2326846efdab
3904 3904 5:904fa392b941
3905 3905 6:e0cc66ef77e8
3906 3906 7:013af1973af4
3907 3907 8:d5d0dcbdc4d9
3908 3908 9:24286f4ae135
3909 3909
3910 3910 $ hg log -qr e:
3911 3911 6:e0cc66ef77e8
3912 3912 7:013af1973af4
3913 3913 8:d5d0dcbdc4d9
3914 3914 9:24286f4ae135
3915 3915
3916 3916 $ hg log -qr :e
3917 3917 0:2785f51eece5
3918 3918 1:d75937da8da0
3919 3919 2:5ed5505e9f1c
3920 3920 3:8528aa5637f2
3921 3921 4:2326846efdab
3922 3922 5:904fa392b941
3923 3923 6:e0cc66ef77e8
3924 3924
3925 3925 issue2549 - correct optimizations
3926 3926
3927 3927 $ try 'limit(1 or 2 or 3, 2) and not 2'
3928 3928 (and
3929 3929 (func
3930 3930 ('symbol', 'limit')
3931 3931 (list
3932 3932 (or
3933 3933 (list
3934 3934 ('symbol', '1')
3935 3935 ('symbol', '2')
3936 3936 ('symbol', '3')))
3937 3937 ('symbol', '2')))
3938 3938 (not
3939 3939 ('symbol', '2')))
3940 3940 * set:
3941 3941 <filteredset
3942 3942 <baseset [1, 2]>,
3943 3943 <not
3944 3944 <baseset [2]>>>
3945 3945 1
3946 3946 $ try 'max(1 or 2) and not 2'
3947 3947 (and
3948 3948 (func
3949 3949 ('symbol', 'max')
3950 3950 (or
3951 3951 (list
3952 3952 ('symbol', '1')
3953 3953 ('symbol', '2'))))
3954 3954 (not
3955 3955 ('symbol', '2')))
3956 3956 * set:
3957 3957 <filteredset
3958 3958 <baseset
3959 3959 <max
3960 3960 <fullreposet+ 0:10>,
3961 3961 <baseset [1, 2]>>>,
3962 3962 <not
3963 3963 <baseset [2]>>>
3964 3964 $ try 'min(1 or 2) and not 1'
3965 3965 (and
3966 3966 (func
3967 3967 ('symbol', 'min')
3968 3968 (or
3969 3969 (list
3970 3970 ('symbol', '1')
3971 3971 ('symbol', '2'))))
3972 3972 (not
3973 3973 ('symbol', '1')))
3974 3974 * set:
3975 3975 <filteredset
3976 3976 <baseset
3977 3977 <min
3978 3978 <fullreposet+ 0:10>,
3979 3979 <baseset [1, 2]>>>,
3980 3980 <not
3981 3981 <baseset [1]>>>
3982 3982 $ try 'last(1 or 2, 1) and not 2'
3983 3983 (and
3984 3984 (func
3985 3985 ('symbol', 'last')
3986 3986 (list
3987 3987 (or
3988 3988 (list
3989 3989 ('symbol', '1')
3990 3990 ('symbol', '2')))
3991 3991 ('symbol', '1')))
3992 3992 (not
3993 3993 ('symbol', '2')))
3994 3994 * set:
3995 3995 <filteredset
3996 3996 <baseset [2]>,
3997 3997 <not
3998 3998 <baseset [2]>>>
3999 3999
4000 4000 issue4289 - ordering of built-ins
4001 4001 $ hg log -M -q -r 3:2
4002 4002 3:8528aa5637f2
4003 4003 2:5ed5505e9f1c
4004 4004
4005 4005 test revsets started with 40-chars hash (issue3669)
4006 4006
4007 4007 $ ISSUE3669_TIP=`hg tip --template '{node}'`
4008 4008 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
4009 4009 9
4010 4010 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
4011 4011 8
4012 4012
4013 4013 test or-ed indirect predicates (issue3775)
4014 4014
4015 4015 $ log '6 or 6^1' | sort
4016 4016 5
4017 4017 6
4018 4018 $ log '6^1 or 6' | sort
4019 4019 5
4020 4020 6
4021 4021 $ log '4 or 4~1' | sort
4022 4022 2
4023 4023 4
4024 4024 $ log '4~1 or 4' | sort
4025 4025 2
4026 4026 4
4027 4027 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
4028 4028 0
4029 4029 1
4030 4030 2
4031 4031 3
4032 4032 4
4033 4033 5
4034 4034 6
4035 4035 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
4036 4036 0
4037 4037 1
4038 4038 2
4039 4039 3
4040 4040 4
4041 4041 5
4042 4042 6
4043 4043
4044 4044 tests for 'remote()' predicate:
4045 4045 #. (csets in remote) (id) (remote)
4046 4046 1. less than local current branch "default"
4047 4047 2. same with local specified "default"
4048 4048 3. more than local specified specified
4049 4049
4050 4050 $ hg clone --quiet -U . ../remote3
4051 4051 $ cd ../remote3
4052 4052 $ hg update -q 7
4053 4053 $ echo r > r
4054 4054 $ hg ci -Aqm 10
4055 4055 $ log 'remote()'
4056 4056 7
4057 4057 $ log 'remote("a-b-c-")'
4058 4058 2
4059 4059 $ cd ../repo
4060 4060 $ log 'remote(".a.b.c.", "../remote3")'
4061 4061
4062 4062 tests for concatenation of strings/symbols by "##"
4063 4063
4064 4064 $ try "278 ## '5f5' ## 1ee ## 'ce5'"
4065 4065 (_concat
4066 4066 (_concat
4067 4067 (_concat
4068 4068 ('symbol', '278')
4069 4069 ('string', '5f5'))
4070 4070 ('symbol', '1ee'))
4071 4071 ('string', 'ce5'))
4072 4072 * concatenated:
4073 4073 ('string', '2785f51eece5')
4074 4074 * set:
4075 4075 <baseset [0]>
4076 4076 0
4077 4077
4078 4078 $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
4079 4079 $ try "cat4(278, '5f5', 1ee, 'ce5')"
4080 4080 (func
4081 4081 ('symbol', 'cat4')
4082 4082 (list
4083 4083 ('symbol', '278')
4084 4084 ('string', '5f5')
4085 4085 ('symbol', '1ee')
4086 4086 ('string', 'ce5')))
4087 4087 * expanded:
4088 4088 (_concat
4089 4089 (_concat
4090 4090 (_concat
4091 4091 ('symbol', '278')
4092 4092 ('string', '5f5'))
4093 4093 ('symbol', '1ee'))
4094 4094 ('string', 'ce5'))
4095 4095 * concatenated:
4096 4096 ('string', '2785f51eece5')
4097 4097 * set:
4098 4098 <baseset [0]>
4099 4099 0
4100 4100
4101 4101 (check concatenation in alias nesting)
4102 4102
4103 4103 $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
4104 4104 $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
4105 4105 $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
4106 4106 0
4107 4107
4108 4108 (check operator priority)
4109 4109
4110 4110 $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
4111 4111 $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
4112 4112 0
4113 4113 4
4114 4114
4115 4115 $ cd ..
4116 4116
4117 4117 prepare repository that has "default" branches of multiple roots
4118 4118
4119 4119 $ hg init namedbranch
4120 4120 $ cd namedbranch
4121 4121
4122 4122 $ echo default0 >> a
4123 4123 $ hg ci -Aqm0
4124 4124 $ echo default1 >> a
4125 4125 $ hg ci -m1
4126 4126
4127 4127 $ hg branch -q stable
4128 4128 $ echo stable2 >> a
4129 4129 $ hg ci -m2
4130 4130 $ echo stable3 >> a
4131 4131 $ hg ci -m3
4132 4132
4133 4133 $ hg update -q null
4134 4134 $ echo default4 >> a
4135 4135 $ hg ci -Aqm4
4136 4136 $ echo default5 >> a
4137 4137 $ hg ci -m5
4138 4138
4139 4139 "null" revision belongs to "default" branch (issue4683)
4140 4140
4141 4141 $ log 'branch(null)'
4142 4142 0
4143 4143 1
4144 4144 4
4145 4145 5
4146 4146
4147 4147 "null" revision belongs to "default" branch, but it shouldn't appear in set
4148 4148 unless explicitly specified (issue4682)
4149 4149
4150 4150 $ log 'children(branch(default))'
4151 4151 1
4152 4152 2
4153 4153 5
4154 4154
4155 4155 $ cd ..
4156 4156
4157 4157 test author/desc/keyword in problematic encoding
4158 4158 # unicode: cp932:
4159 4159 # u30A2 0x83 0x41(= 'A')
4160 4160 # u30C2 0x83 0x61(= 'a')
4161 4161
4162 4162 $ hg init problematicencoding
4163 4163 $ cd problematicencoding
4164 4164
4165 4165 $ $PYTHON > setup.sh <<EOF
4166 4166 > print u'''
4167 4167 > echo a > text
4168 4168 > hg add text
4169 4169 > hg --encoding utf-8 commit -u '\u30A2' -m none
4170 4170 > echo b > text
4171 4171 > hg --encoding utf-8 commit -u '\u30C2' -m none
4172 4172 > echo c > text
4173 4173 > hg --encoding utf-8 commit -u none -m '\u30A2'
4174 4174 > echo d > text
4175 4175 > hg --encoding utf-8 commit -u none -m '\u30C2'
4176 4176 > '''.encode('utf-8')
4177 4177 > EOF
4178 4178 $ sh < setup.sh
4179 4179
4180 4180 test in problematic encoding
4181 4181 $ $PYTHON > test.sh <<EOF
4182 4182 > print u'''
4183 4183 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
4184 4184 > echo ====
4185 4185 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
4186 4186 > echo ====
4187 4187 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
4188 4188 > echo ====
4189 4189 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
4190 4190 > echo ====
4191 4191 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
4192 4192 > echo ====
4193 4193 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
4194 4194 > '''.encode('cp932')
4195 4195 > EOF
4196 4196 $ sh < test.sh
4197 4197 0
4198 4198 ====
4199 4199 1
4200 4200 ====
4201 4201 2
4202 4202 ====
4203 4203 3
4204 4204 ====
4205 4205 0
4206 4206 2
4207 4207 ====
4208 4208 1
4209 4209 3
4210 4210
4211 4211 test error message of bad revset
4212 4212 $ hg log -r 'foo\\'
4213 4213 hg: parse error at 3: syntax error in revset 'foo\\'
4214 4214 [255]
4215 4215
4216 4216 $ cd ..
4217 4217
4218 4218 Test that revset predicate of extension isn't loaded at failure of
4219 4219 loading it
4220 4220
4221 4221 $ cd repo
4222 4222
4223 4223 $ cat <<EOF > $TESTTMP/custompredicate.py
4224 4224 > from mercurial import error, registrar, revset
4225 4225 >
4226 4226 > revsetpredicate = registrar.revsetpredicate()
4227 4227 >
4228 4228 > @revsetpredicate('custom1()')
4229 4229 > def custom1(repo, subset, x):
4230 4230 > return revset.baseset([1])
4231 4231 >
4232 4232 > raise error.Abort('intentional failure of loading extension')
4233 4233 > EOF
4234 4234 $ cat <<EOF > .hg/hgrc
4235 4235 > [extensions]
4236 4236 > custompredicate = $TESTTMP/custompredicate.py
4237 4237 > EOF
4238 4238
4239 4239 $ hg debugrevspec "custom1()"
4240 4240 *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
4241 4241 hg: parse error: unknown identifier: custom1
4242 4242 [255]
4243 4243
4244 4244 Test repo.anyrevs with customized revset overrides
4245 4245
4246 4246 $ cat > $TESTTMP/printprevset.py <<EOF
4247 4247 > from mercurial import encoding, registrar
4248 4248 > cmdtable = {}
4249 4249 > command = registrar.command(cmdtable)
4250 4250 > @command('printprevset')
4251 4251 > def printprevset(ui, repo):
4252 4252 > alias = {}
4253 4253 > p = encoding.environ.get('P')
4254 4254 > if p:
4255 4255 > alias['P'] = p
4256 4256 > revs = repo.anyrevs(['P'], user=True, localalias=alias)
4257 4257 > ui.write('P=%r\n' % list(revs))
4258 4258 > EOF
4259 4259
4260 4260 $ cat >> .hg/hgrc <<EOF
4261 4261 > custompredicate = !
4262 4262 > printprevset = $TESTTMP/printprevset.py
4263 4263 > EOF
4264 4264
4265 4265 $ hg --config revsetalias.P=1 printprevset
4266 4266 P=[1]
4267 4267 $ P=3 hg --config revsetalias.P=2 printprevset
4268 4268 P=[3]
4269 4269
4270 4270 $ cd ..
4271 4271
4272 4272 Test obsstore related revsets
4273 4273
4274 4274 $ hg init repo1
4275 4275 $ cd repo1
4276 4276 $ cat <<EOF >> .hg/hgrc
4277 4277 > [experimental]
4278 4278 > stabilization = createmarkers
4279 4279 > EOF
4280 4280
4281 4281 $ hg debugdrawdag <<'EOS'
4282 4282 > F G
4283 4283 > |/ # split: B -> E, F
4284 4284 > B C D E # amend: B -> C -> D
4285 4285 > \|/ | # amend: F -> G
4286 4286 > A A Z # amend: A -> Z
4287 4287 > EOS
4288 4288
4289 4289 $ hg log -r 'successors(Z)' -T '{desc}\n'
4290 4290 Z
4291 4291
4292 4292 $ hg log -r 'successors(F)' -T '{desc}\n'
4293 4293 F
4294 4294 G
4295 4295
4296 4296 $ hg tag --remove --local C D E F G
4297 4297
4298 4298 $ hg log -r 'successors(B)' -T '{desc}\n'
4299 4299 B
4300 4300 D
4301 4301 E
4302 4302 G
4303 4303
4304 4304 $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
4305 4305 B
4306 4306 C
4307 4307 D
4308 4308 E
4309 4309 F
4310 4310 G
4311 4311
4312 4312 $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
4313 4313 D
4314 4314 E
4315 4315 G
4316 4316
4317 4317 $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
4318 4318 A
4319 4319 Z
4320 4320 B
4321 4321
4322 4322 $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
4323 4323 Z
4324
4325 Test `draft() & ::x` optimization
4326
4327 $ hg init $TESTTMP/repo2
4328 $ cd $TESTTMP/repo2
4329 $ hg debugdrawdag <<'EOS'
4330 > P5 S1
4331 > | |
4332 > S2 | D3
4333 > \|/
4334 > P4
4335 > |
4336 > P3 D2
4337 > | |
4338 > P2 D1
4339 > |/
4340 > P1
4341 > |
4342 > P0
4343 > EOS
4344 $ hg phase --public -r P5
4345 $ hg phase --force --secret -r S1+S2
4346 $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
4347 o 8 P5 public
4348 |
4349 | o 10 S1 secret
4350 | |
4351 | o 7 D3 draft
4352 |/
4353 | o 9 S2 secret
4354 |/
4355 o 6 P4 public
4356 |
4357 o 5 P3 public
4358 |
4359 o 3 P2 public
4360 |
4361 | o 4 D2 draft
4362 | |
4363 | o 2 D1 draft
4364 |/
4365 o 1 P1 public
4366 |
4367 o 0 P0 public
4368
4369 $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
4370 * analyzed:
4371 (and
4372 (func
4373 ('symbol', 'draft')
4374 None)
4375 (func
4376 ('symbol', 'ancestors')
4377 (or
4378 (list
4379 (and
4380 (or
4381 (list
4382 ('symbol', 'S1')
4383 ('symbol', 'D1')
4384 ('symbol', 'P5')))
4385 (not
4386 ('symbol', 'D3')))
4387 ('symbol', 'S2')))))
4388 * optimized:
4389 (func
4390 ('symbol', '_phaseandancestors')
4391 (list
4392 ('symbol', 'draft')
4393 (or
4394 (list
4395 (difference
4396 (func
4397 ('symbol', '_list')
4398 ('string', 'S1\x00D1\x00P5'))
4399 ('symbol', 'D3'))
4400 ('symbol', 'S2')))))
4401 $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
4402 * analyzed:
4403 (and
4404 (func
4405 ('symbol', 'secret')
4406 None)
4407 (func
4408 ('symbol', 'ancestors')
4409 ('symbol', '9')))
4410 * optimized:
4411 (func
4412 ('symbol', '_phaseandancestors')
4413 (list
4414 ('symbol', 'secret')
4415 ('symbol', '9')))
4416 $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
4417 * analyzed:
4418 (and
4419 ('symbol', '7')
4420 (and
4421 (not
4422 (func
4423 ('symbol', 'public')
4424 None))
4425 (func
4426 ('symbol', 'ancestors')
4427 (func
4428 ('symbol', 'tag')
4429 None))))
4430 * optimized:
4431 (and
4432 ('symbol', '7')
4433 (func
4434 ('symbol', '_phaseandancestors')
4435 (list
4436 ('symbol', '_notpublic')
4437 (func
4438 ('symbol', 'tag')
4439 None))))
4440 $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
4441 * optimized:
4442 (and
4443 (func
4444 ('symbol', '_notpublic')
4445 None)
4446 (func
4447 ('symbol', 'ancestors')
4448 (list
4449 (func
4450 ('symbol', '_list')
4451 ('string', 'S1\x00D2\x00P5'))
4452 ('symbol', '1'))))
4453 $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
4454 * optimized:
4455 (and
4456 (func
4457 ('symbol', '_notpublic')
4458 None)
4459 (func
4460 ('symbol', 'ancestors')
4461 (list
4462 (func
4463 ('symbol', '_list')
4464 ('string', 'S1\x00D2\x00P5'))
4465 (keyvalue
4466 ('symbol', 'depth')
4467 ('symbol', '1')))))
General Comments 0
You need to be logged in to leave comments. Login now