##// END OF EJS Templates
stack: import Evolve stack test file...
Boris Feld -
r37019:407934a9 default
parent child Browse files
Show More
@@ -0,0 +1,253
1
2 This test test the low-level definition of stack, agnostic from all formatting
3
4 Initial setup
5
6 $ cat << EOF >> $HGRCPATH
7 > [ui]
8 > logtemplate = {rev} {branch} {phase} {desc|firstline}\n
9 > [extensions]
10 > rebase=
11 > [experimental]
12 > evolution=createmarkers,exchange,allowunstable
13 > EOF
14
15 $ hg init main
16 $ cd main
17 $ hg branch other
18 marked working directory as branch other
19 (branches are permanent and global, did you want a bookmark?)
20 $ echo aaa > aaa
21 $ hg add aaa
22 $ hg commit -m c_a
23 $ echo aaa > bbb
24 $ hg add bbb
25 $ hg commit -m c_b
26 $ hg branch foo
27 marked working directory as branch foo
28 $ echo aaa > ccc
29 $ hg add ccc
30 $ hg commit -m c_c
31 $ echo aaa > ddd
32 $ hg add ddd
33 $ hg commit -m c_d
34 $ echo aaa > eee
35 $ hg add eee
36 $ hg commit -m c_e
37 $ echo aaa > fff
38 $ hg add fff
39 $ hg commit -m c_f
40 $ hg log -G
41 @ 5 foo draft c_f
42 |
43 o 4 foo draft c_e
44 |
45 o 3 foo draft c_d
46 |
47 o 2 foo draft c_c
48 |
49 o 1 other draft c_b
50 |
51 o 0 other draft c_a
52
53
54 Check that stack doesn't include public changesets
55 --------------------------------------------------
56
57 $ hg up other
58 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
59 $ hg log -G -r "stack()"
60 @ 1 other draft c_b
61 |
62 o 0 other draft c_a
63
64 $ hg phase --public 'branch("other")'
65 $ hg log -G -r "stack()"
66 $ hg up foo
67 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
68
69 Simple test
70 -----------
71
72 'stack()' list all changeset in the branch
73
74 $ hg branch
75 foo
76 $ hg log -G -r "stack()"
77 @ 5 foo draft c_f
78 |
79 o 4 foo draft c_e
80 |
81 o 3 foo draft c_d
82 |
83 o 2 foo draft c_c
84 |
85 ~
86
87 Case with some of the branch unstable
88 ------------------------------------
89
90 $ hg up 3
91 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
92 $ echo bbb > ddd
93 $ hg commit --amend
94 2 new orphan changesets
95 $ hg log -G
96 @ 6 foo draft c_d
97 |
98 | * 5 foo draft c_f
99 | |
100 | * 4 foo draft c_e
101 | |
102 | x 3 foo draft c_d
103 |/
104 o 2 foo draft c_c
105 |
106 o 1 other public c_b
107 |
108 o 0 other public c_a
109
110 $ hg log -G -r "stack()"
111 @ 6 foo draft c_d
112 |
113 ~
114 $ hg up -r "desc(c_e)"
115 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 $ hg log -G -r "stack()"
117 @ 4 foo draft c_e
118 |
119 x 3 foo draft c_d
120 |
121 ~
122 $ hg up -r "desc(c_d)"
123 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
124
125 $ hg log -G -r "stack()"
126 @ 6 foo draft c_d
127 |
128 ~
129
130 Case with multiple topological heads
131 ------------------------------------
132
133 Make things linear again
134
135 $ hg rebase -s 'desc(c_e)' -d 'desc(c_d) - obsolete()'
136 rebasing 4:4f2a69f6d380 "c_e"
137 rebasing 5:913c298d8b0a "c_f"
138 $ hg log -G
139 o 8 foo draft c_f
140 |
141 o 7 foo draft c_e
142 |
143 @ 6 foo draft c_d
144 |
145 o 2 foo draft c_c
146 |
147 o 1 other public c_b
148 |
149 o 0 other public c_a
150
151
152 Create the second branch
153
154 $ hg up 'desc(c_d)'
155 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 $ echo aaa > ggg
157 $ hg add ggg
158 $ hg commit -m c_g
159 created new head
160 $ echo aaa > hhh
161 $ hg add hhh
162 $ hg commit -m c_h
163 $ hg log -G
164 @ 10 foo draft c_h
165 |
166 o 9 foo draft c_g
167 |
168 | o 8 foo draft c_f
169 | |
170 | o 7 foo draft c_e
171 |/
172 o 6 foo draft c_d
173 |
174 o 2 foo draft c_c
175 |
176 o 1 other public c_b
177 |
178 o 0 other public c_a
179
180
181 Test output
182
183 $ hg log -G -r "stack(10)"
184 @ 10 foo draft c_h
185 |
186 o 9 foo draft c_g
187 |
188 ~
189 $ hg log -G -r "stack(8)"
190 o 8 foo draft c_f
191 |
192 o 7 foo draft c_e
193 |
194 ~
195 $ hg log -G -r "stack(head())"
196 @ 10 foo draft c_h
197 |
198 o 9 foo draft c_g
199 |
200 ~
201 o 8 foo draft c_f
202 |
203 o 7 foo draft c_e
204 |
205 ~
206 Check the stack order
207 $ hg log -r "first(stack())"
208 10 foo draft c_h
209 $ hg log -r "first(stack(10))"
210 10 foo draft c_h
211 $ hg log -r "first(stack(8))"
212 8 foo draft c_f
213 $ hg log -r "first(stack(head()))"
214 8 foo draft c_f
215
216 Case with multiple heads with unstability involved
217 --------------------------------------------------
218
219 We amend the message to make sure the display base pick the right changeset
220
221 $ hg up 'desc(c_d)'
222 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
223 $ echo ccc > ddd
224 $ hg commit --amend -m 'c_D'
225 4 new orphan changesets
226 $ hg rebase -d . -s 'desc(c_g)'
227 rebasing 9:2ebb6e48ab8a "c_g"
228 rebasing 10:634f38e27a1d "c_h"
229 $ hg log -G
230 o 13 foo draft c_h
231 |
232 o 12 foo draft c_g
233 |
234 @ 11 foo draft c_D
235 |
236 | * 8 foo draft c_f
237 | |
238 | * 7 foo draft c_e
239 | |
240 | x 6 foo draft c_d
241 |/
242 o 2 foo draft c_c
243 |
244 o 1 other public c_b
245 |
246 o 0 other public c_a
247
248
249 We should improve stack definition to also show 12 and 13 here
250 $ hg log -G -r "stack()"
251 @ 11 foo draft c_D
252 |
253 ~
@@ -1,2218 +1,2234
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 pycompat,
26 26 registrar,
27 27 repoview,
28 28 revsetlang,
29 29 scmutil,
30 30 smartset,
31 stack,
31 32 util,
32 33 )
33 34 from .utils import dateutil
34 35
35 36 # helpers for processing parsed tree
36 37 getsymbol = revsetlang.getsymbol
37 38 getstring = revsetlang.getstring
38 39 getinteger = revsetlang.getinteger
39 40 getboolean = revsetlang.getboolean
40 41 getlist = revsetlang.getlist
41 42 getrange = revsetlang.getrange
42 43 getargs = revsetlang.getargs
43 44 getargsdict = revsetlang.getargsdict
44 45
45 46 baseset = smartset.baseset
46 47 generatorset = smartset.generatorset
47 48 spanset = smartset.spanset
48 49 fullreposet = smartset.fullreposet
49 50
50 51 # Constants for ordering requirement, used in getset():
51 52 #
52 53 # If 'define', any nested functions and operations MAY change the ordering of
53 54 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
54 55 # it). If 'follow', any nested functions and operations MUST take the ordering
55 56 # specified by the first operand to the '&' operator.
56 57 #
57 58 # For instance,
58 59 #
59 60 # X & (Y | Z)
60 61 # ^ ^^^^^^^
61 62 # | follow
62 63 # define
63 64 #
64 65 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
65 66 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
66 67 #
67 68 # 'any' means the order doesn't matter. For instance,
68 69 #
69 70 # (X & !Y) | ancestors(Z)
70 71 # ^ ^
71 72 # any any
72 73 #
73 74 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
74 75 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
75 76 # since 'ancestors' does not care about the order of its argument.
76 77 #
77 78 # Currently, most revsets do not care about the order, so 'define' is
78 79 # equivalent to 'follow' for them, and the resulting order is based on the
79 80 # 'subset' parameter passed down to them:
80 81 #
81 82 # m = revset.match(...)
82 83 # m(repo, subset, order=defineorder)
83 84 # ^^^^^^
84 85 # For most revsets, 'define' means using the order this subset provides
85 86 #
86 87 # There are a few revsets that always redefine the order if 'define' is
87 88 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
88 89 anyorder = 'any' # don't care the order, could be even random-shuffled
89 90 defineorder = 'define' # ALWAYS redefine, or ALWAYS follow the current order
90 91 followorder = 'follow' # MUST follow the current order
91 92
92 93 # helpers
93 94
94 95 def getset(repo, subset, x, order=defineorder):
95 96 if not x:
96 97 raise error.ParseError(_("missing argument"))
97 98 return methods[x[0]](repo, subset, *x[1:], order=order)
98 99
99 100 def _getrevsource(repo, r):
100 101 extra = repo[r].extra()
101 102 for label in ('source', 'transplant_source', 'rebase_source'):
102 103 if label in extra:
103 104 try:
104 105 return repo[extra[label]].rev()
105 106 except error.RepoLookupError:
106 107 pass
107 108 return None
108 109
109 110 def _sortedb(xs):
110 111 return sorted(util.rapply(pycompat.maybebytestr, xs))
111 112
112 113 # operator methods
113 114
114 115 def stringset(repo, subset, x, order):
115 116 x = scmutil.intrev(repo[x])
116 117 if (x in subset
117 118 or x == node.nullrev and isinstance(subset, fullreposet)):
118 119 return baseset([x])
119 120 return baseset()
120 121
121 122 def rangeset(repo, subset, x, y, order):
122 123 m = getset(repo, fullreposet(repo), x)
123 124 n = getset(repo, fullreposet(repo), y)
124 125
125 126 if not m or not n:
126 127 return baseset()
127 128 return _makerangeset(repo, subset, m.first(), n.last(), order)
128 129
129 130 def rangeall(repo, subset, x, order):
130 131 assert x is None
131 132 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
132 133
133 134 def rangepre(repo, subset, y, order):
134 135 # ':y' can't be rewritten to '0:y' since '0' may be hidden
135 136 n = getset(repo, fullreposet(repo), y)
136 137 if not n:
137 138 return baseset()
138 139 return _makerangeset(repo, subset, 0, n.last(), order)
139 140
140 141 def rangepost(repo, subset, x, order):
141 142 m = getset(repo, fullreposet(repo), x)
142 143 if not m:
143 144 return baseset()
144 145 return _makerangeset(repo, subset, m.first(), repo.changelog.tiprev(),
145 146 order)
146 147
147 148 def _makerangeset(repo, subset, m, n, order):
148 149 if m == n:
149 150 r = baseset([m])
150 151 elif n == node.wdirrev:
151 152 r = spanset(repo, m, len(repo)) + baseset([n])
152 153 elif m == node.wdirrev:
153 154 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
154 155 elif m < n:
155 156 r = spanset(repo, m, n + 1)
156 157 else:
157 158 r = spanset(repo, m, n - 1)
158 159
159 160 if order == defineorder:
160 161 return r & subset
161 162 else:
162 163 # carrying the sorting over when possible would be more efficient
163 164 return subset & r
164 165
165 166 def dagrange(repo, subset, x, y, order):
166 167 r = fullreposet(repo)
167 168 xs = dagop.reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
168 169 includepath=True)
169 170 return subset & xs
170 171
171 172 def andset(repo, subset, x, y, order):
172 173 if order == anyorder:
173 174 yorder = anyorder
174 175 else:
175 176 yorder = followorder
176 177 return getset(repo, getset(repo, subset, x, order), y, yorder)
177 178
178 179 def andsmallyset(repo, subset, x, y, order):
179 180 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
180 181 if order == anyorder:
181 182 yorder = anyorder
182 183 else:
183 184 yorder = followorder
184 185 return getset(repo, getset(repo, subset, y, yorder), x, order)
185 186
186 187 def differenceset(repo, subset, x, y, order):
187 188 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
188 189
189 190 def _orsetlist(repo, subset, xs, order):
190 191 assert xs
191 192 if len(xs) == 1:
192 193 return getset(repo, subset, xs[0], order)
193 194 p = len(xs) // 2
194 195 a = _orsetlist(repo, subset, xs[:p], order)
195 196 b = _orsetlist(repo, subset, xs[p:], order)
196 197 return a + b
197 198
198 199 def orset(repo, subset, x, order):
199 200 xs = getlist(x)
200 201 if order == followorder:
201 202 # slow path to take the subset order
202 203 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
203 204 else:
204 205 return _orsetlist(repo, subset, xs, order)
205 206
206 207 def notset(repo, subset, x, order):
207 208 return subset - getset(repo, subset, x, anyorder)
208 209
209 210 def relationset(repo, subset, x, y, order):
210 211 raise error.ParseError(_("can't use a relation in this context"))
211 212
212 213 def relsubscriptset(repo, subset, x, y, z, order):
213 214 # this is pretty basic implementation of 'x#y[z]' operator, still
214 215 # experimental so undocumented. see the wiki for further ideas.
215 216 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
216 217 rel = getsymbol(y)
217 218 n = getinteger(z, _("relation subscript must be an integer"))
218 219
219 220 # TODO: perhaps this should be a table of relation functions
220 221 if rel in ('g', 'generations'):
221 222 # TODO: support range, rewrite tests, and drop startdepth argument
222 223 # from ancestors() and descendants() predicates
223 224 if n <= 0:
224 225 n = -n
225 226 return _ancestors(repo, subset, x, startdepth=n, stopdepth=n + 1)
226 227 else:
227 228 return _descendants(repo, subset, x, startdepth=n, stopdepth=n + 1)
228 229
229 230 raise error.UnknownIdentifier(rel, ['generations'])
230 231
231 232 def subscriptset(repo, subset, x, y, order):
232 233 raise error.ParseError(_("can't use a subscript in this context"))
233 234
234 235 def listset(repo, subset, *xs, **opts):
235 236 raise error.ParseError(_("can't use a list in this context"),
236 237 hint=_('see hg help "revsets.x or y"'))
237 238
238 239 def keyvaluepair(repo, subset, k, v, order):
239 240 raise error.ParseError(_("can't use a key-value pair in this context"))
240 241
241 242 def func(repo, subset, a, b, order):
242 243 f = getsymbol(a)
243 244 if f in symbols:
244 245 func = symbols[f]
245 246 if getattr(func, '_takeorder', False):
246 247 return func(repo, subset, b, order)
247 248 return func(repo, subset, b)
248 249
249 250 keep = lambda fn: getattr(fn, '__doc__', None) is not None
250 251
251 252 syms = [s for (s, fn) in symbols.items() if keep(fn)]
252 253 raise error.UnknownIdentifier(f, syms)
253 254
254 255 # functions
255 256
256 257 # symbols are callables like:
257 258 # fn(repo, subset, x)
258 259 # with:
259 260 # repo - current repository instance
260 261 # subset - of revisions to be examined
261 262 # x - argument in tree form
262 263 symbols = revsetlang.symbols
263 264
264 265 # symbols which can't be used for a DoS attack for any given input
265 266 # (e.g. those which accept regexes as plain strings shouldn't be included)
266 267 # functions that just return a lot of changesets (like all) don't count here
267 268 safesymbols = set()
268 269
269 270 predicate = registrar.revsetpredicate()
270 271
271 272 @predicate('_destupdate')
272 273 def _destupdate(repo, subset, x):
273 274 # experimental revset for update destination
274 275 args = getargsdict(x, 'limit', 'clean')
275 276 return subset & baseset([destutil.destupdate(repo,
276 277 **pycompat.strkwargs(args))[0]])
277 278
278 279 @predicate('_destmerge')
279 280 def _destmerge(repo, subset, x):
280 281 # experimental revset for merge destination
281 282 sourceset = None
282 283 if x is not None:
283 284 sourceset = getset(repo, fullreposet(repo), x)
284 285 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
285 286
286 287 @predicate('adds(pattern)', safe=True, weight=30)
287 288 def adds(repo, subset, x):
288 289 """Changesets that add a file matching pattern.
289 290
290 291 The pattern without explicit kind like ``glob:`` is expected to be
291 292 relative to the current directory and match against a file or a
292 293 directory.
293 294 """
294 295 # i18n: "adds" is a keyword
295 296 pat = getstring(x, _("adds requires a pattern"))
296 297 return checkstatus(repo, subset, pat, 1)
297 298
298 299 @predicate('ancestor(*changeset)', safe=True, weight=0.5)
299 300 def ancestor(repo, subset, x):
300 301 """A greatest common ancestor of the changesets.
301 302
302 303 Accepts 0 or more changesets.
303 304 Will return empty list when passed no args.
304 305 Greatest common ancestor of a single changeset is that changeset.
305 306 """
306 307 # i18n: "ancestor" is a keyword
307 308 l = getlist(x)
308 309 rl = fullreposet(repo)
309 310 anc = None
310 311
311 312 # (getset(repo, rl, i) for i in l) generates a list of lists
312 313 for revs in (getset(repo, rl, i) for i in l):
313 314 for r in revs:
314 315 if anc is None:
315 316 anc = repo[r]
316 317 else:
317 318 anc = anc.ancestor(repo[r])
318 319
319 320 if anc is not None and anc.rev() in subset:
320 321 return baseset([anc.rev()])
321 322 return baseset()
322 323
323 324 def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
324 325 stopdepth=None):
325 326 heads = getset(repo, fullreposet(repo), x)
326 327 if not heads:
327 328 return baseset()
328 329 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
329 330 return subset & s
330 331
331 332 @predicate('ancestors(set[, depth])', safe=True)
332 333 def ancestors(repo, subset, x):
333 334 """Changesets that are ancestors of changesets in set, including the
334 335 given changesets themselves.
335 336
336 337 If depth is specified, the result only includes changesets up to
337 338 the specified generation.
338 339 """
339 340 # startdepth is for internal use only until we can decide the UI
340 341 args = getargsdict(x, 'ancestors', 'set depth startdepth')
341 342 if 'set' not in args:
342 343 # i18n: "ancestors" is a keyword
343 344 raise error.ParseError(_('ancestors takes at least 1 argument'))
344 345 startdepth = stopdepth = None
345 346 if 'startdepth' in args:
346 347 n = getinteger(args['startdepth'],
347 348 "ancestors expects an integer startdepth")
348 349 if n < 0:
349 350 raise error.ParseError("negative startdepth")
350 351 startdepth = n
351 352 if 'depth' in args:
352 353 # i18n: "ancestors" is a keyword
353 354 n = getinteger(args['depth'], _("ancestors expects an integer depth"))
354 355 if n < 0:
355 356 raise error.ParseError(_("negative depth"))
356 357 stopdepth = n + 1
357 358 return _ancestors(repo, subset, args['set'],
358 359 startdepth=startdepth, stopdepth=stopdepth)
359 360
360 361 @predicate('_firstancestors', safe=True)
361 362 def _firstancestors(repo, subset, x):
362 363 # ``_firstancestors(set)``
363 364 # Like ``ancestors(set)`` but follows only the first parents.
364 365 return _ancestors(repo, subset, x, followfirst=True)
365 366
366 367 def _childrenspec(repo, subset, x, n, order):
367 368 """Changesets that are the Nth child of a changeset
368 369 in set.
369 370 """
370 371 cs = set()
371 372 for r in getset(repo, fullreposet(repo), x):
372 373 for i in range(n):
373 374 c = repo[r].children()
374 375 if len(c) == 0:
375 376 break
376 377 if len(c) > 1:
377 378 raise error.RepoLookupError(
378 379 _("revision in set has more than one child"))
379 380 r = c[0].rev()
380 381 else:
381 382 cs.add(r)
382 383 return subset & cs
383 384
384 385 def ancestorspec(repo, subset, x, n, order):
385 386 """``set~n``
386 387 Changesets that are the Nth ancestor (first parents only) of a changeset
387 388 in set.
388 389 """
389 390 n = getinteger(n, _("~ expects a number"))
390 391 if n < 0:
391 392 # children lookup
392 393 return _childrenspec(repo, subset, x, -n, order)
393 394 ps = set()
394 395 cl = repo.changelog
395 396 for r in getset(repo, fullreposet(repo), x):
396 397 for i in range(n):
397 398 try:
398 399 r = cl.parentrevs(r)[0]
399 400 except error.WdirUnsupported:
400 401 r = repo[r].parents()[0].rev()
401 402 ps.add(r)
402 403 return subset & ps
403 404
404 405 @predicate('author(string)', safe=True, weight=10)
405 406 def author(repo, subset, x):
406 407 """Alias for ``user(string)``.
407 408 """
408 409 # i18n: "author" is a keyword
409 410 n = getstring(x, _("author requires a string"))
410 411 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
411 412 return subset.filter(lambda x: matcher(repo[x].user()),
412 413 condrepr=('<user %r>', n))
413 414
414 415 @predicate('bisect(string)', safe=True)
415 416 def bisect(repo, subset, x):
416 417 """Changesets marked in the specified bisect status:
417 418
418 419 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
419 420 - ``goods``, ``bads`` : csets topologically good/bad
420 421 - ``range`` : csets taking part in the bisection
421 422 - ``pruned`` : csets that are goods, bads or skipped
422 423 - ``untested`` : csets whose fate is yet unknown
423 424 - ``ignored`` : csets ignored due to DAG topology
424 425 - ``current`` : the cset currently being bisected
425 426 """
426 427 # i18n: "bisect" is a keyword
427 428 status = getstring(x, _("bisect requires a string")).lower()
428 429 state = set(hbisect.get(repo, status))
429 430 return subset & state
430 431
431 432 # Backward-compatibility
432 433 # - no help entry so that we do not advertise it any more
433 434 @predicate('bisected', safe=True)
434 435 def bisected(repo, subset, x):
435 436 return bisect(repo, subset, x)
436 437
437 438 @predicate('bookmark([name])', safe=True)
438 439 def bookmark(repo, subset, x):
439 440 """The named bookmark or all bookmarks.
440 441
441 442 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
442 443 """
443 444 # i18n: "bookmark" is a keyword
444 445 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
445 446 if args:
446 447 bm = getstring(args[0],
447 448 # i18n: "bookmark" is a keyword
448 449 _('the argument to bookmark must be a string'))
449 450 kind, pattern, matcher = util.stringmatcher(bm)
450 451 bms = set()
451 452 if kind == 'literal':
452 453 bmrev = repo._bookmarks.get(pattern, None)
453 454 if not bmrev:
454 455 raise error.RepoLookupError(_("bookmark '%s' does not exist")
455 456 % pattern)
456 457 bms.add(repo[bmrev].rev())
457 458 else:
458 459 matchrevs = set()
459 460 for name, bmrev in repo._bookmarks.iteritems():
460 461 if matcher(name):
461 462 matchrevs.add(bmrev)
462 463 if not matchrevs:
463 464 raise error.RepoLookupError(_("no bookmarks exist"
464 465 " that match '%s'") % pattern)
465 466 for bmrev in matchrevs:
466 467 bms.add(repo[bmrev].rev())
467 468 else:
468 469 bms = {repo[r].rev() for r in repo._bookmarks.values()}
469 470 bms -= {node.nullrev}
470 471 return subset & bms
471 472
472 473 @predicate('branch(string or set)', safe=True, weight=10)
473 474 def branch(repo, subset, x):
474 475 """
475 476 All changesets belonging to the given branch or the branches of the given
476 477 changesets.
477 478
478 479 Pattern matching is supported for `string`. See
479 480 :hg:`help revisions.patterns`.
480 481 """
481 482 getbi = repo.revbranchcache().branchinfo
482 483 def getbranch(r):
483 484 try:
484 485 return getbi(r)[0]
485 486 except error.WdirUnsupported:
486 487 return repo[r].branch()
487 488
488 489 try:
489 490 b = getstring(x, '')
490 491 except error.ParseError:
491 492 # not a string, but another revspec, e.g. tip()
492 493 pass
493 494 else:
494 495 kind, pattern, matcher = util.stringmatcher(b)
495 496 if kind == 'literal':
496 497 # note: falls through to the revspec case if no branch with
497 498 # this name exists and pattern kind is not specified explicitly
498 499 if pattern in repo.branchmap():
499 500 return subset.filter(lambda r: matcher(getbranch(r)),
500 501 condrepr=('<branch %r>', b))
501 502 if b.startswith('literal:'):
502 503 raise error.RepoLookupError(_("branch '%s' does not exist")
503 504 % pattern)
504 505 else:
505 506 return subset.filter(lambda r: matcher(getbranch(r)),
506 507 condrepr=('<branch %r>', b))
507 508
508 509 s = getset(repo, fullreposet(repo), x)
509 510 b = set()
510 511 for r in s:
511 512 b.add(getbranch(r))
512 513 c = s.__contains__
513 514 return subset.filter(lambda r: c(r) or getbranch(r) in b,
514 515 condrepr=lambda: '<branch %r>' % _sortedb(b))
515 516
516 517 @predicate('phasedivergent()', safe=True)
517 518 def phasedivergent(repo, subset, x):
518 519 """Mutable changesets marked as successors of public changesets.
519 520
520 521 Only non-public and non-obsolete changesets can be `phasedivergent`.
521 522 (EXPERIMENTAL)
522 523 """
523 524 # i18n: "phasedivergent" is a keyword
524 525 getargs(x, 0, 0, _("phasedivergent takes no arguments"))
525 526 phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
526 527 return subset & phasedivergent
527 528
528 529 @predicate('bundle()', safe=True)
529 530 def bundle(repo, subset, x):
530 531 """Changesets in the bundle.
531 532
532 533 Bundle must be specified by the -R option."""
533 534
534 535 try:
535 536 bundlerevs = repo.changelog.bundlerevs
536 537 except AttributeError:
537 538 raise error.Abort(_("no bundle provided - specify with -R"))
538 539 return subset & bundlerevs
539 540
540 541 def checkstatus(repo, subset, pat, field):
541 542 hasset = matchmod.patkind(pat) == 'set'
542 543
543 544 mcache = [None]
544 545 def matches(x):
545 546 c = repo[x]
546 547 if not mcache[0] or hasset:
547 548 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
548 549 m = mcache[0]
549 550 fname = None
550 551 if not m.anypats() and len(m.files()) == 1:
551 552 fname = m.files()[0]
552 553 if fname is not None:
553 554 if fname not in c.files():
554 555 return False
555 556 else:
556 557 for f in c.files():
557 558 if m(f):
558 559 break
559 560 else:
560 561 return False
561 562 files = repo.status(c.p1().node(), c.node())[field]
562 563 if fname is not None:
563 564 if fname in files:
564 565 return True
565 566 else:
566 567 for f in files:
567 568 if m(f):
568 569 return True
569 570
570 571 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
571 572
572 573 def _children(repo, subset, parentset):
573 574 if not parentset:
574 575 return baseset()
575 576 cs = set()
576 577 pr = repo.changelog.parentrevs
577 578 minrev = parentset.min()
578 579 nullrev = node.nullrev
579 580 for r in subset:
580 581 if r <= minrev:
581 582 continue
582 583 p1, p2 = pr(r)
583 584 if p1 in parentset:
584 585 cs.add(r)
585 586 if p2 != nullrev and p2 in parentset:
586 587 cs.add(r)
587 588 return baseset(cs)
588 589
589 590 @predicate('children(set)', safe=True)
590 591 def children(repo, subset, x):
591 592 """Child changesets of changesets in set.
592 593 """
593 594 s = getset(repo, fullreposet(repo), x)
594 595 cs = _children(repo, subset, s)
595 596 return subset & cs
596 597
597 598 @predicate('closed()', safe=True, weight=10)
598 599 def closed(repo, subset, x):
599 600 """Changeset is closed.
600 601 """
601 602 # i18n: "closed" is a keyword
602 603 getargs(x, 0, 0, _("closed takes no arguments"))
603 604 return subset.filter(lambda r: repo[r].closesbranch(),
604 605 condrepr='<branch closed>')
605 606
606 607 @predicate('contains(pattern)', weight=100)
607 608 def contains(repo, subset, x):
608 609 """The revision's manifest contains a file matching pattern (but might not
609 610 modify it). See :hg:`help patterns` for information about file patterns.
610 611
611 612 The pattern without explicit kind like ``glob:`` is expected to be
612 613 relative to the current directory and match against a file exactly
613 614 for efficiency.
614 615 """
615 616 # i18n: "contains" is a keyword
616 617 pat = getstring(x, _("contains requires a pattern"))
617 618
618 619 def matches(x):
619 620 if not matchmod.patkind(pat):
620 621 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
621 622 if pats in repo[x]:
622 623 return True
623 624 else:
624 625 c = repo[x]
625 626 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
626 627 for f in c.manifest():
627 628 if m(f):
628 629 return True
629 630 return False
630 631
631 632 return subset.filter(matches, condrepr=('<contains %r>', pat))
632 633
633 634 @predicate('converted([id])', safe=True)
634 635 def converted(repo, subset, x):
635 636 """Changesets converted from the given identifier in the old repository if
636 637 present, or all converted changesets if no identifier is specified.
637 638 """
638 639
639 640 # There is exactly no chance of resolving the revision, so do a simple
640 641 # string compare and hope for the best
641 642
642 643 rev = None
643 644 # i18n: "converted" is a keyword
644 645 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
645 646 if l:
646 647 # i18n: "converted" is a keyword
647 648 rev = getstring(l[0], _('converted requires a revision'))
648 649
649 650 def _matchvalue(r):
650 651 source = repo[r].extra().get('convert_revision', None)
651 652 return source is not None and (rev is None or source.startswith(rev))
652 653
653 654 return subset.filter(lambda r: _matchvalue(r),
654 655 condrepr=('<converted %r>', rev))
655 656
656 657 @predicate('date(interval)', safe=True, weight=10)
657 658 def date(repo, subset, x):
658 659 """Changesets within the interval, see :hg:`help dates`.
659 660 """
660 661 # i18n: "date" is a keyword
661 662 ds = getstring(x, _("date requires a string"))
662 663 dm = dateutil.matchdate(ds)
663 664 return subset.filter(lambda x: dm(repo[x].date()[0]),
664 665 condrepr=('<date %r>', ds))
665 666
666 667 @predicate('desc(string)', safe=True, weight=10)
667 668 def desc(repo, subset, x):
668 669 """Search commit message for string. The match is case-insensitive.
669 670
670 671 Pattern matching is supported for `string`. See
671 672 :hg:`help revisions.patterns`.
672 673 """
673 674 # i18n: "desc" is a keyword
674 675 ds = getstring(x, _("desc requires a string"))
675 676
676 677 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
677 678
678 679 return subset.filter(lambda r: matcher(repo[r].description()),
679 680 condrepr=('<desc %r>', ds))
680 681
681 682 def _descendants(repo, subset, x, followfirst=False, startdepth=None,
682 683 stopdepth=None):
683 684 roots = getset(repo, fullreposet(repo), x)
684 685 if not roots:
685 686 return baseset()
686 687 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
687 688 return subset & s
688 689
689 690 @predicate('descendants(set[, depth])', safe=True)
690 691 def descendants(repo, subset, x):
691 692 """Changesets which are descendants of changesets in set, including the
692 693 given changesets themselves.
693 694
694 695 If depth is specified, the result only includes changesets up to
695 696 the specified generation.
696 697 """
697 698 # startdepth is for internal use only until we can decide the UI
698 699 args = getargsdict(x, 'descendants', 'set depth startdepth')
699 700 if 'set' not in args:
700 701 # i18n: "descendants" is a keyword
701 702 raise error.ParseError(_('descendants takes at least 1 argument'))
702 703 startdepth = stopdepth = None
703 704 if 'startdepth' in args:
704 705 n = getinteger(args['startdepth'],
705 706 "descendants expects an integer startdepth")
706 707 if n < 0:
707 708 raise error.ParseError("negative startdepth")
708 709 startdepth = n
709 710 if 'depth' in args:
710 711 # i18n: "descendants" is a keyword
711 712 n = getinteger(args['depth'], _("descendants expects an integer depth"))
712 713 if n < 0:
713 714 raise error.ParseError(_("negative depth"))
714 715 stopdepth = n + 1
715 716 return _descendants(repo, subset, args['set'],
716 717 startdepth=startdepth, stopdepth=stopdepth)
717 718
718 719 @predicate('_firstdescendants', safe=True)
719 720 def _firstdescendants(repo, subset, x):
720 721 # ``_firstdescendants(set)``
721 722 # Like ``descendants(set)`` but follows only the first parents.
722 723 return _descendants(repo, subset, x, followfirst=True)
723 724
724 725 @predicate('destination([set])', safe=True, weight=10)
725 726 def destination(repo, subset, x):
726 727 """Changesets that were created by a graft, transplant or rebase operation,
727 728 with the given revisions specified as the source. Omitting the optional set
728 729 is the same as passing all().
729 730 """
730 731 if x is not None:
731 732 sources = getset(repo, fullreposet(repo), x)
732 733 else:
733 734 sources = fullreposet(repo)
734 735
735 736 dests = set()
736 737
737 738 # subset contains all of the possible destinations that can be returned, so
738 739 # iterate over them and see if their source(s) were provided in the arg set.
739 740 # Even if the immediate src of r is not in the arg set, src's source (or
740 741 # further back) may be. Scanning back further than the immediate src allows
741 742 # transitive transplants and rebases to yield the same results as transitive
742 743 # grafts.
743 744 for r in subset:
744 745 src = _getrevsource(repo, r)
745 746 lineage = None
746 747
747 748 while src is not None:
748 749 if lineage is None:
749 750 lineage = list()
750 751
751 752 lineage.append(r)
752 753
753 754 # The visited lineage is a match if the current source is in the arg
754 755 # set. Since every candidate dest is visited by way of iterating
755 756 # subset, any dests further back in the lineage will be tested by a
756 757 # different iteration over subset. Likewise, if the src was already
757 758 # selected, the current lineage can be selected without going back
758 759 # further.
759 760 if src in sources or src in dests:
760 761 dests.update(lineage)
761 762 break
762 763
763 764 r = src
764 765 src = _getrevsource(repo, r)
765 766
766 767 return subset.filter(dests.__contains__,
767 768 condrepr=lambda: '<destination %r>' % _sortedb(dests))
768 769
769 770 @predicate('contentdivergent()', safe=True)
770 771 def contentdivergent(repo, subset, x):
771 772 """
772 773 Final successors of changesets with an alternative set of final
773 774 successors. (EXPERIMENTAL)
774 775 """
775 776 # i18n: "contentdivergent" is a keyword
776 777 getargs(x, 0, 0, _("contentdivergent takes no arguments"))
777 778 contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
778 779 return subset & contentdivergent
779 780
780 781 @predicate('extdata(source)', safe=False, weight=100)
781 782 def extdata(repo, subset, x):
782 783 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
783 784 # i18n: "extdata" is a keyword
784 785 args = getargsdict(x, 'extdata', 'source')
785 786 source = getstring(args.get('source'),
786 787 # i18n: "extdata" is a keyword
787 788 _('extdata takes at least 1 string argument'))
788 789 data = scmutil.extdatasource(repo, source)
789 790 return subset & baseset(data)
790 791
791 792 @predicate('extinct()', safe=True)
792 793 def extinct(repo, subset, x):
793 794 """Obsolete changesets with obsolete descendants only.
794 795 """
795 796 # i18n: "extinct" is a keyword
796 797 getargs(x, 0, 0, _("extinct takes no arguments"))
797 798 extincts = obsmod.getrevs(repo, 'extinct')
798 799 return subset & extincts
799 800
800 801 @predicate('extra(label, [value])', safe=True)
801 802 def extra(repo, subset, x):
802 803 """Changesets with the given label in the extra metadata, with the given
803 804 optional value.
804 805
805 806 Pattern matching is supported for `value`. See
806 807 :hg:`help revisions.patterns`.
807 808 """
808 809 args = getargsdict(x, 'extra', 'label value')
809 810 if 'label' not in args:
810 811 # i18n: "extra" is a keyword
811 812 raise error.ParseError(_('extra takes at least 1 argument'))
812 813 # i18n: "extra" is a keyword
813 814 label = getstring(args['label'], _('first argument to extra must be '
814 815 'a string'))
815 816 value = None
816 817
817 818 if 'value' in args:
818 819 # i18n: "extra" is a keyword
819 820 value = getstring(args['value'], _('second argument to extra must be '
820 821 'a string'))
821 822 kind, value, matcher = util.stringmatcher(value)
822 823
823 824 def _matchvalue(r):
824 825 extra = repo[r].extra()
825 826 return label in extra and (value is None or matcher(extra[label]))
826 827
827 828 return subset.filter(lambda r: _matchvalue(r),
828 829 condrepr=('<extra[%r] %r>', label, value))
829 830
830 831 @predicate('filelog(pattern)', safe=True)
831 832 def filelog(repo, subset, x):
832 833 """Changesets connected to the specified filelog.
833 834
834 835 For performance reasons, visits only revisions mentioned in the file-level
835 836 filelog, rather than filtering through all changesets (much faster, but
836 837 doesn't include deletes or duplicate changes). For a slower, more accurate
837 838 result, use ``file()``.
838 839
839 840 The pattern without explicit kind like ``glob:`` is expected to be
840 841 relative to the current directory and match against a file exactly
841 842 for efficiency.
842 843
843 844 If some linkrev points to revisions filtered by the current repoview, we'll
844 845 work around it to return a non-filtered value.
845 846 """
846 847
847 848 # i18n: "filelog" is a keyword
848 849 pat = getstring(x, _("filelog requires a pattern"))
849 850 s = set()
850 851 cl = repo.changelog
851 852
852 853 if not matchmod.patkind(pat):
853 854 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
854 855 files = [f]
855 856 else:
856 857 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
857 858 files = (f for f in repo[None] if m(f))
858 859
859 860 for f in files:
860 861 fl = repo.file(f)
861 862 known = {}
862 863 scanpos = 0
863 864 for fr in list(fl):
864 865 fn = fl.node(fr)
865 866 if fn in known:
866 867 s.add(known[fn])
867 868 continue
868 869
869 870 lr = fl.linkrev(fr)
870 871 if lr in cl:
871 872 s.add(lr)
872 873 elif scanpos is not None:
873 874 # lowest matching changeset is filtered, scan further
874 875 # ahead in changelog
875 876 start = max(lr, scanpos) + 1
876 877 scanpos = None
877 878 for r in cl.revs(start):
878 879 # minimize parsing of non-matching entries
879 880 if f in cl.revision(r) and f in cl.readfiles(r):
880 881 try:
881 882 # try to use manifest delta fastpath
882 883 n = repo[r].filenode(f)
883 884 if n not in known:
884 885 if n == fn:
885 886 s.add(r)
886 887 scanpos = r
887 888 break
888 889 else:
889 890 known[n] = r
890 891 except error.ManifestLookupError:
891 892 # deletion in changelog
892 893 continue
893 894
894 895 return subset & s
895 896
896 897 @predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
897 898 def first(repo, subset, x, order):
898 899 """An alias for limit().
899 900 """
900 901 return limit(repo, subset, x, order)
901 902
902 903 def _follow(repo, subset, x, name, followfirst=False):
903 904 args = getargsdict(x, name, 'file startrev')
904 905 revs = None
905 906 if 'startrev' in args:
906 907 revs = getset(repo, fullreposet(repo), args['startrev'])
907 908 if 'file' in args:
908 909 x = getstring(args['file'], _("%s expected a pattern") % name)
909 910 if revs is None:
910 911 revs = [None]
911 912 fctxs = []
912 913 for r in revs:
913 914 ctx = mctx = repo[r]
914 915 if r is None:
915 916 ctx = repo['.']
916 917 m = matchmod.match(repo.root, repo.getcwd(), [x],
917 918 ctx=mctx, default='path')
918 919 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
919 920 s = dagop.filerevancestors(fctxs, followfirst)
920 921 else:
921 922 if revs is None:
922 923 revs = baseset([repo['.'].rev()])
923 924 s = dagop.revancestors(repo, revs, followfirst)
924 925
925 926 return subset & s
926 927
927 928 @predicate('follow([file[, startrev]])', safe=True)
928 929 def follow(repo, subset, x):
929 930 """
930 931 An alias for ``::.`` (ancestors of the working directory's first parent).
931 932 If file pattern is specified, the histories of files matching given
932 933 pattern in the revision given by startrev are followed, including copies.
933 934 """
934 935 return _follow(repo, subset, x, 'follow')
935 936
936 937 @predicate('_followfirst', safe=True)
937 938 def _followfirst(repo, subset, x):
938 939 # ``followfirst([file[, startrev]])``
939 940 # Like ``follow([file[, startrev]])`` but follows only the first parent
940 941 # of every revisions or files revisions.
941 942 return _follow(repo, subset, x, '_followfirst', followfirst=True)
942 943
943 944 @predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
944 945 safe=True)
945 946 def followlines(repo, subset, x):
946 947 """Changesets modifying `file` in line range ('fromline', 'toline').
947 948
948 949 Line range corresponds to 'file' content at 'startrev' and should hence be
949 950 consistent with file size. If startrev is not specified, working directory's
950 951 parent is used.
951 952
952 953 By default, ancestors of 'startrev' are returned. If 'descend' is True,
953 954 descendants of 'startrev' are returned though renames are (currently) not
954 955 followed in this direction.
955 956 """
956 957 args = getargsdict(x, 'followlines', 'file *lines startrev descend')
957 958 if len(args['lines']) != 1:
958 959 raise error.ParseError(_("followlines requires a line range"))
959 960
960 961 rev = '.'
961 962 if 'startrev' in args:
962 963 revs = getset(repo, fullreposet(repo), args['startrev'])
963 964 if len(revs) != 1:
964 965 raise error.ParseError(
965 966 # i18n: "followlines" is a keyword
966 967 _("followlines expects exactly one revision"))
967 968 rev = revs.last()
968 969
969 970 pat = getstring(args['file'], _("followlines requires a pattern"))
970 971 # i18n: "followlines" is a keyword
971 972 msg = _("followlines expects exactly one file")
972 973 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
973 974 # i18n: "followlines" is a keyword
974 975 lr = getrange(args['lines'][0], _("followlines expects a line range"))
975 976 fromline, toline = [getinteger(a, _("line range bounds must be integers"))
976 977 for a in lr]
977 978 fromline, toline = util.processlinerange(fromline, toline)
978 979
979 980 fctx = repo[rev].filectx(fname)
980 981 descend = False
981 982 if 'descend' in args:
982 983 descend = getboolean(args['descend'],
983 984 # i18n: "descend" is a keyword
984 985 _("descend argument must be a boolean"))
985 986 if descend:
986 987 rs = generatorset(
987 988 (c.rev() for c, _linerange
988 989 in dagop.blockdescendants(fctx, fromline, toline)),
989 990 iterasc=True)
990 991 else:
991 992 rs = generatorset(
992 993 (c.rev() for c, _linerange
993 994 in dagop.blockancestors(fctx, fromline, toline)),
994 995 iterasc=False)
995 996 return subset & rs
996 997
997 998 @predicate('all()', safe=True)
998 999 def getall(repo, subset, x):
999 1000 """All changesets, the same as ``0:tip``.
1000 1001 """
1001 1002 # i18n: "all" is a keyword
1002 1003 getargs(x, 0, 0, _("all takes no arguments"))
1003 1004 return subset & spanset(repo) # drop "null" if any
1004 1005
1005 1006 @predicate('grep(regex)', weight=10)
1006 1007 def grep(repo, subset, x):
1007 1008 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1008 1009 to ensure special escape characters are handled correctly. Unlike
1009 1010 ``keyword(string)``, the match is case-sensitive.
1010 1011 """
1011 1012 try:
1012 1013 # i18n: "grep" is a keyword
1013 1014 gr = re.compile(getstring(x, _("grep requires a string")))
1014 1015 except re.error as e:
1015 1016 raise error.ParseError(
1016 1017 _('invalid match pattern: %s') % util.forcebytestr(e))
1017 1018
1018 1019 def matches(x):
1019 1020 c = repo[x]
1020 1021 for e in c.files() + [c.user(), c.description()]:
1021 1022 if gr.search(e):
1022 1023 return True
1023 1024 return False
1024 1025
1025 1026 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1026 1027
1027 1028 @predicate('_matchfiles', safe=True)
1028 1029 def _matchfiles(repo, subset, x):
1029 1030 # _matchfiles takes a revset list of prefixed arguments:
1030 1031 #
1031 1032 # [p:foo, i:bar, x:baz]
1032 1033 #
1033 1034 # builds a match object from them and filters subset. Allowed
1034 1035 # prefixes are 'p:' for regular patterns, 'i:' for include
1035 1036 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1036 1037 # a revision identifier, or the empty string to reference the
1037 1038 # working directory, from which the match object is
1038 1039 # initialized. Use 'd:' to set the default matching mode, default
1039 1040 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1040 1041
1041 1042 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1042 1043 pats, inc, exc = [], [], []
1043 1044 rev, default = None, None
1044 1045 for arg in l:
1045 1046 s = getstring(arg, "_matchfiles requires string arguments")
1046 1047 prefix, value = s[:2], s[2:]
1047 1048 if prefix == 'p:':
1048 1049 pats.append(value)
1049 1050 elif prefix == 'i:':
1050 1051 inc.append(value)
1051 1052 elif prefix == 'x:':
1052 1053 exc.append(value)
1053 1054 elif prefix == 'r:':
1054 1055 if rev is not None:
1055 1056 raise error.ParseError('_matchfiles expected at most one '
1056 1057 'revision')
1057 1058 if value == '': # empty means working directory
1058 1059 rev = node.wdirrev
1059 1060 else:
1060 1061 rev = value
1061 1062 elif prefix == 'd:':
1062 1063 if default is not None:
1063 1064 raise error.ParseError('_matchfiles expected at most one '
1064 1065 'default mode')
1065 1066 default = value
1066 1067 else:
1067 1068 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1068 1069 if not default:
1069 1070 default = 'glob'
1070 1071 hasset = any(matchmod.patkind(p) == 'set' for p in pats + inc + exc)
1071 1072
1072 1073 mcache = [None]
1073 1074
1074 1075 # This directly read the changelog data as creating changectx for all
1075 1076 # revisions is quite expensive.
1076 1077 getfiles = repo.changelog.readfiles
1077 1078 wdirrev = node.wdirrev
1078 1079 def matches(x):
1079 1080 if x == wdirrev:
1080 1081 files = repo[x].files()
1081 1082 else:
1082 1083 files = getfiles(x)
1083 1084
1084 1085 if not mcache[0] or (hasset and rev is None):
1085 1086 r = x if rev is None else rev
1086 1087 mcache[0] = matchmod.match(repo.root, repo.getcwd(), pats,
1087 1088 include=inc, exclude=exc, ctx=repo[r],
1088 1089 default=default)
1089 1090 m = mcache[0]
1090 1091
1091 1092 for f in files:
1092 1093 if m(f):
1093 1094 return True
1094 1095 return False
1095 1096
1096 1097 return subset.filter(matches,
1097 1098 condrepr=('<matchfiles patterns=%r, include=%r '
1098 1099 'exclude=%r, default=%r, rev=%r>',
1099 1100 pats, inc, exc, default, rev))
1100 1101
1101 1102 @predicate('file(pattern)', safe=True, weight=10)
1102 1103 def hasfile(repo, subset, x):
1103 1104 """Changesets affecting files matched by pattern.
1104 1105
1105 1106 For a faster but less accurate result, consider using ``filelog()``
1106 1107 instead.
1107 1108
1108 1109 This predicate uses ``glob:`` as the default kind of pattern.
1109 1110 """
1110 1111 # i18n: "file" is a keyword
1111 1112 pat = getstring(x, _("file requires a pattern"))
1112 1113 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1113 1114
1114 1115 @predicate('head()', safe=True)
1115 1116 def head(repo, subset, x):
1116 1117 """Changeset is a named branch head.
1117 1118 """
1118 1119 # i18n: "head" is a keyword
1119 1120 getargs(x, 0, 0, _("head takes no arguments"))
1120 1121 hs = set()
1121 1122 cl = repo.changelog
1122 1123 for ls in repo.branchmap().itervalues():
1123 1124 hs.update(cl.rev(h) for h in ls)
1124 1125 return subset & baseset(hs)
1125 1126
1126 1127 @predicate('heads(set)', safe=True)
1127 1128 def heads(repo, subset, x):
1128 1129 """Members of set with no children in set.
1129 1130 """
1130 1131 s = getset(repo, subset, x)
1131 1132 ps = parents(repo, subset, x)
1132 1133 return s - ps
1133 1134
1134 1135 @predicate('hidden()', safe=True)
1135 1136 def hidden(repo, subset, x):
1136 1137 """Hidden changesets.
1137 1138 """
1138 1139 # i18n: "hidden" is a keyword
1139 1140 getargs(x, 0, 0, _("hidden takes no arguments"))
1140 1141 hiddenrevs = repoview.filterrevs(repo, 'visible')
1141 1142 return subset & hiddenrevs
1142 1143
1143 1144 @predicate('keyword(string)', safe=True, weight=10)
1144 1145 def keyword(repo, subset, x):
1145 1146 """Search commit message, user name, and names of changed files for
1146 1147 string. The match is case-insensitive.
1147 1148
1148 1149 For a regular expression or case sensitive search of these fields, use
1149 1150 ``grep(regex)``.
1150 1151 """
1151 1152 # i18n: "keyword" is a keyword
1152 1153 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1153 1154
1154 1155 def matches(r):
1155 1156 c = repo[r]
1156 1157 return any(kw in encoding.lower(t)
1157 1158 for t in c.files() + [c.user(), c.description()])
1158 1159
1159 1160 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1160 1161
1161 1162 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1162 1163 def limit(repo, subset, x, order):
1163 1164 """First n members of set, defaulting to 1, starting from offset.
1164 1165 """
1165 1166 args = getargsdict(x, 'limit', 'set n offset')
1166 1167 if 'set' not in args:
1167 1168 # i18n: "limit" is a keyword
1168 1169 raise error.ParseError(_("limit requires one to three arguments"))
1169 1170 # i18n: "limit" is a keyword
1170 1171 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1171 1172 if lim < 0:
1172 1173 raise error.ParseError(_("negative number to select"))
1173 1174 # i18n: "limit" is a keyword
1174 1175 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1175 1176 if ofs < 0:
1176 1177 raise error.ParseError(_("negative offset"))
1177 1178 os = getset(repo, fullreposet(repo), args['set'])
1178 1179 ls = os.slice(ofs, ofs + lim)
1179 1180 if order == followorder and lim > 1:
1180 1181 return subset & ls
1181 1182 return ls & subset
1182 1183
1183 1184 @predicate('last(set, [n])', safe=True, takeorder=True)
1184 1185 def last(repo, subset, x, order):
1185 1186 """Last n members of set, defaulting to 1.
1186 1187 """
1187 1188 # i18n: "last" is a keyword
1188 1189 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1189 1190 lim = 1
1190 1191 if len(l) == 2:
1191 1192 # i18n: "last" is a keyword
1192 1193 lim = getinteger(l[1], _("last expects a number"))
1193 1194 if lim < 0:
1194 1195 raise error.ParseError(_("negative number to select"))
1195 1196 os = getset(repo, fullreposet(repo), l[0])
1196 1197 os.reverse()
1197 1198 ls = os.slice(0, lim)
1198 1199 if order == followorder and lim > 1:
1199 1200 return subset & ls
1200 1201 ls.reverse()
1201 1202 return ls & subset
1202 1203
1203 1204 @predicate('max(set)', safe=True)
1204 1205 def maxrev(repo, subset, x):
1205 1206 """Changeset with highest revision number in set.
1206 1207 """
1207 1208 os = getset(repo, fullreposet(repo), x)
1208 1209 try:
1209 1210 m = os.max()
1210 1211 if m in subset:
1211 1212 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1212 1213 except ValueError:
1213 1214 # os.max() throws a ValueError when the collection is empty.
1214 1215 # Same as python's max().
1215 1216 pass
1216 1217 return baseset(datarepr=('<max %r, %r>', subset, os))
1217 1218
1218 1219 @predicate('merge()', safe=True)
1219 1220 def merge(repo, subset, x):
1220 1221 """Changeset is a merge changeset.
1221 1222 """
1222 1223 # i18n: "merge" is a keyword
1223 1224 getargs(x, 0, 0, _("merge takes no arguments"))
1224 1225 cl = repo.changelog
1225 1226 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1226 1227 condrepr='<merge>')
1227 1228
1228 1229 @predicate('branchpoint()', safe=True)
1229 1230 def branchpoint(repo, subset, x):
1230 1231 """Changesets with more than one child.
1231 1232 """
1232 1233 # i18n: "branchpoint" is a keyword
1233 1234 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1234 1235 cl = repo.changelog
1235 1236 if not subset:
1236 1237 return baseset()
1237 1238 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1238 1239 # (and if it is not, it should.)
1239 1240 baserev = min(subset)
1240 1241 parentscount = [0]*(len(repo) - baserev)
1241 1242 for r in cl.revs(start=baserev + 1):
1242 1243 for p in cl.parentrevs(r):
1243 1244 if p >= baserev:
1244 1245 parentscount[p - baserev] += 1
1245 1246 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1246 1247 condrepr='<branchpoint>')
1247 1248
1248 1249 @predicate('min(set)', safe=True)
1249 1250 def minrev(repo, subset, x):
1250 1251 """Changeset with lowest revision number in set.
1251 1252 """
1252 1253 os = getset(repo, fullreposet(repo), x)
1253 1254 try:
1254 1255 m = os.min()
1255 1256 if m in subset:
1256 1257 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1257 1258 except ValueError:
1258 1259 # os.min() throws a ValueError when the collection is empty.
1259 1260 # Same as python's min().
1260 1261 pass
1261 1262 return baseset(datarepr=('<min %r, %r>', subset, os))
1262 1263
1263 1264 @predicate('modifies(pattern)', safe=True, weight=30)
1264 1265 def modifies(repo, subset, x):
1265 1266 """Changesets modifying files matched by pattern.
1266 1267
1267 1268 The pattern without explicit kind like ``glob:`` is expected to be
1268 1269 relative to the current directory and match against a file or a
1269 1270 directory.
1270 1271 """
1271 1272 # i18n: "modifies" is a keyword
1272 1273 pat = getstring(x, _("modifies requires a pattern"))
1273 1274 return checkstatus(repo, subset, pat, 0)
1274 1275
1275 1276 @predicate('named(namespace)')
1276 1277 def named(repo, subset, x):
1277 1278 """The changesets in a given namespace.
1278 1279
1279 1280 Pattern matching is supported for `namespace`. See
1280 1281 :hg:`help revisions.patterns`.
1281 1282 """
1282 1283 # i18n: "named" is a keyword
1283 1284 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1284 1285
1285 1286 ns = getstring(args[0],
1286 1287 # i18n: "named" is a keyword
1287 1288 _('the argument to named must be a string'))
1288 1289 kind, pattern, matcher = util.stringmatcher(ns)
1289 1290 namespaces = set()
1290 1291 if kind == 'literal':
1291 1292 if pattern not in repo.names:
1292 1293 raise error.RepoLookupError(_("namespace '%s' does not exist")
1293 1294 % ns)
1294 1295 namespaces.add(repo.names[pattern])
1295 1296 else:
1296 1297 for name, ns in repo.names.iteritems():
1297 1298 if matcher(name):
1298 1299 namespaces.add(ns)
1299 1300 if not namespaces:
1300 1301 raise error.RepoLookupError(_("no namespace exists"
1301 1302 " that match '%s'") % pattern)
1302 1303
1303 1304 names = set()
1304 1305 for ns in namespaces:
1305 1306 for name in ns.listnames(repo):
1306 1307 if name not in ns.deprecated:
1307 1308 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1308 1309
1309 1310 names -= {node.nullrev}
1310 1311 return subset & names
1311 1312
1312 1313 @predicate('id(string)', safe=True)
1313 1314 def node_(repo, subset, x):
1314 1315 """Revision non-ambiguously specified by the given hex string prefix.
1315 1316 """
1316 1317 # i18n: "id" is a keyword
1317 1318 l = getargs(x, 1, 1, _("id requires one argument"))
1318 1319 # i18n: "id" is a keyword
1319 1320 n = getstring(l[0], _("id requires a string"))
1320 1321 if len(n) == 40:
1321 1322 try:
1322 1323 rn = repo.changelog.rev(node.bin(n))
1323 1324 except error.WdirUnsupported:
1324 1325 rn = node.wdirrev
1325 1326 except (LookupError, TypeError):
1326 1327 rn = None
1327 1328 else:
1328 1329 rn = None
1329 1330 try:
1330 1331 pm = repo.changelog._partialmatch(n)
1331 1332 if pm is not None:
1332 1333 rn = repo.changelog.rev(pm)
1333 1334 except error.WdirUnsupported:
1334 1335 rn = node.wdirrev
1335 1336
1336 1337 if rn is None:
1337 1338 return baseset()
1338 1339 result = baseset([rn])
1339 1340 return result & subset
1340 1341
1341 1342 @predicate('obsolete()', safe=True)
1342 1343 def obsolete(repo, subset, x):
1343 1344 """Mutable changeset with a newer version."""
1344 1345 # i18n: "obsolete" is a keyword
1345 1346 getargs(x, 0, 0, _("obsolete takes no arguments"))
1346 1347 obsoletes = obsmod.getrevs(repo, 'obsolete')
1347 1348 return subset & obsoletes
1348 1349
1349 1350 @predicate('only(set, [set])', safe=True)
1350 1351 def only(repo, subset, x):
1351 1352 """Changesets that are ancestors of the first set that are not ancestors
1352 1353 of any other head in the repo. If a second set is specified, the result
1353 1354 is ancestors of the first set that are not ancestors of the second set
1354 1355 (i.e. ::<set1> - ::<set2>).
1355 1356 """
1356 1357 cl = repo.changelog
1357 1358 # i18n: "only" is a keyword
1358 1359 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1359 1360 include = getset(repo, fullreposet(repo), args[0])
1360 1361 if len(args) == 1:
1361 1362 if not include:
1362 1363 return baseset()
1363 1364
1364 1365 descendants = set(dagop.revdescendants(repo, include, False))
1365 1366 exclude = [rev for rev in cl.headrevs()
1366 1367 if not rev in descendants and not rev in include]
1367 1368 else:
1368 1369 exclude = getset(repo, fullreposet(repo), args[1])
1369 1370
1370 1371 results = set(cl.findmissingrevs(common=exclude, heads=include))
1371 1372 # XXX we should turn this into a baseset instead of a set, smartset may do
1372 1373 # some optimizations from the fact this is a baseset.
1373 1374 return subset & results
1374 1375
1375 1376 @predicate('origin([set])', safe=True)
1376 1377 def origin(repo, subset, x):
1377 1378 """
1378 1379 Changesets that were specified as a source for the grafts, transplants or
1379 1380 rebases that created the given revisions. Omitting the optional set is the
1380 1381 same as passing all(). If a changeset created by these operations is itself
1381 1382 specified as a source for one of these operations, only the source changeset
1382 1383 for the first operation is selected.
1383 1384 """
1384 1385 if x is not None:
1385 1386 dests = getset(repo, fullreposet(repo), x)
1386 1387 else:
1387 1388 dests = fullreposet(repo)
1388 1389
1389 1390 def _firstsrc(rev):
1390 1391 src = _getrevsource(repo, rev)
1391 1392 if src is None:
1392 1393 return None
1393 1394
1394 1395 while True:
1395 1396 prev = _getrevsource(repo, src)
1396 1397
1397 1398 if prev is None:
1398 1399 return src
1399 1400 src = prev
1400 1401
1401 1402 o = {_firstsrc(r) for r in dests}
1402 1403 o -= {None}
1403 1404 # XXX we should turn this into a baseset instead of a set, smartset may do
1404 1405 # some optimizations from the fact this is a baseset.
1405 1406 return subset & o
1406 1407
1407 1408 @predicate('outgoing([path])', safe=False, weight=10)
1408 1409 def outgoing(repo, subset, x):
1409 1410 """Changesets not found in the specified destination repository, or the
1410 1411 default push location.
1411 1412 """
1412 1413 # Avoid cycles.
1413 1414 from . import (
1414 1415 discovery,
1415 1416 hg,
1416 1417 )
1417 1418 # i18n: "outgoing" is a keyword
1418 1419 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1419 1420 # i18n: "outgoing" is a keyword
1420 1421 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1421 1422 if not dest:
1422 1423 # ui.paths.getpath() explicitly tests for None, not just a boolean
1423 1424 dest = None
1424 1425 path = repo.ui.paths.getpath(dest, default=('default-push', 'default'))
1425 1426 if not path:
1426 1427 raise error.Abort(_('default repository not configured!'),
1427 1428 hint=_("see 'hg help config.paths'"))
1428 1429 dest = path.pushloc or path.loc
1429 1430 branches = path.branch, []
1430 1431
1431 1432 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1432 1433 if revs:
1433 1434 revs = [repo.lookup(rev) for rev in revs]
1434 1435 other = hg.peer(repo, {}, dest)
1435 1436 repo.ui.pushbuffer()
1436 1437 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1437 1438 repo.ui.popbuffer()
1438 1439 cl = repo.changelog
1439 1440 o = {cl.rev(r) for r in outgoing.missing}
1440 1441 return subset & o
1441 1442
1442 1443 @predicate('p1([set])', safe=True)
1443 1444 def p1(repo, subset, x):
1444 1445 """First parent of changesets in set, or the working directory.
1445 1446 """
1446 1447 if x is None:
1447 1448 p = repo[x].p1().rev()
1448 1449 if p >= 0:
1449 1450 return subset & baseset([p])
1450 1451 return baseset()
1451 1452
1452 1453 ps = set()
1453 1454 cl = repo.changelog
1454 1455 for r in getset(repo, fullreposet(repo), x):
1455 1456 try:
1456 1457 ps.add(cl.parentrevs(r)[0])
1457 1458 except error.WdirUnsupported:
1458 1459 ps.add(repo[r].parents()[0].rev())
1459 1460 ps -= {node.nullrev}
1460 1461 # XXX we should turn this into a baseset instead of a set, smartset may do
1461 1462 # some optimizations from the fact this is a baseset.
1462 1463 return subset & ps
1463 1464
1464 1465 @predicate('p2([set])', safe=True)
1465 1466 def p2(repo, subset, x):
1466 1467 """Second parent of changesets in set, or the working directory.
1467 1468 """
1468 1469 if x is None:
1469 1470 ps = repo[x].parents()
1470 1471 try:
1471 1472 p = ps[1].rev()
1472 1473 if p >= 0:
1473 1474 return subset & baseset([p])
1474 1475 return baseset()
1475 1476 except IndexError:
1476 1477 return baseset()
1477 1478
1478 1479 ps = set()
1479 1480 cl = repo.changelog
1480 1481 for r in getset(repo, fullreposet(repo), x):
1481 1482 try:
1482 1483 ps.add(cl.parentrevs(r)[1])
1483 1484 except error.WdirUnsupported:
1484 1485 parents = repo[r].parents()
1485 1486 if len(parents) == 2:
1486 1487 ps.add(parents[1])
1487 1488 ps -= {node.nullrev}
1488 1489 # XXX we should turn this into a baseset instead of a set, smartset may do
1489 1490 # some optimizations from the fact this is a baseset.
1490 1491 return subset & ps
1491 1492
1492 1493 def parentpost(repo, subset, x, order):
1493 1494 return p1(repo, subset, x)
1494 1495
1495 1496 @predicate('parents([set])', safe=True)
1496 1497 def parents(repo, subset, x):
1497 1498 """
1498 1499 The set of all parents for all changesets in set, or the working directory.
1499 1500 """
1500 1501 if x is None:
1501 1502 ps = set(p.rev() for p in repo[x].parents())
1502 1503 else:
1503 1504 ps = set()
1504 1505 cl = repo.changelog
1505 1506 up = ps.update
1506 1507 parentrevs = cl.parentrevs
1507 1508 for r in getset(repo, fullreposet(repo), x):
1508 1509 try:
1509 1510 up(parentrevs(r))
1510 1511 except error.WdirUnsupported:
1511 1512 up(p.rev() for p in repo[r].parents())
1512 1513 ps -= {node.nullrev}
1513 1514 return subset & ps
1514 1515
1515 1516 def _phase(repo, subset, *targets):
1516 1517 """helper to select all rev in <targets> phases"""
1517 1518 return repo._phasecache.getrevset(repo, targets, subset)
1518 1519
1519 1520 @predicate('draft()', safe=True)
1520 1521 def draft(repo, subset, x):
1521 1522 """Changeset in draft phase."""
1522 1523 # i18n: "draft" is a keyword
1523 1524 getargs(x, 0, 0, _("draft takes no arguments"))
1524 1525 target = phases.draft
1525 1526 return _phase(repo, subset, target)
1526 1527
1527 1528 @predicate('secret()', safe=True)
1528 1529 def secret(repo, subset, x):
1529 1530 """Changeset in secret phase."""
1530 1531 # i18n: "secret" is a keyword
1531 1532 getargs(x, 0, 0, _("secret takes no arguments"))
1532 1533 target = phases.secret
1533 1534 return _phase(repo, subset, target)
1534 1535
1536 @predicate('stack([revs])', safe=True)
1537 def _stack(repo, subset, x):
1538 # experimental revset for the stack of changesets or working directory
1539 # parent
1540 if x is None:
1541 stacks = stack.getstack(repo, x)
1542 else:
1543 stacks = smartset.baseset([])
1544 for revision in getset(repo, fullreposet(repo), x):
1545 currentstack = stack.getstack(repo, revision)
1546 stacks = stacks + currentstack
1547
1548 # Force to use the order of the stacks instead of the subset one
1549 return stacks & subset
1550
1535 1551 def parentspec(repo, subset, x, n, order):
1536 1552 """``set^0``
1537 1553 The set.
1538 1554 ``set^1`` (or ``set^``), ``set^2``
1539 1555 First or second parent, respectively, of all changesets in set.
1540 1556 """
1541 1557 try:
1542 1558 n = int(n[1])
1543 1559 if n not in (0, 1, 2):
1544 1560 raise ValueError
1545 1561 except (TypeError, ValueError):
1546 1562 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1547 1563 ps = set()
1548 1564 cl = repo.changelog
1549 1565 for r in getset(repo, fullreposet(repo), x):
1550 1566 if n == 0:
1551 1567 ps.add(r)
1552 1568 elif n == 1:
1553 1569 try:
1554 1570 ps.add(cl.parentrevs(r)[0])
1555 1571 except error.WdirUnsupported:
1556 1572 ps.add(repo[r].parents()[0].rev())
1557 1573 else:
1558 1574 try:
1559 1575 parents = cl.parentrevs(r)
1560 1576 if parents[1] != node.nullrev:
1561 1577 ps.add(parents[1])
1562 1578 except error.WdirUnsupported:
1563 1579 parents = repo[r].parents()
1564 1580 if len(parents) == 2:
1565 1581 ps.add(parents[1].rev())
1566 1582 return subset & ps
1567 1583
1568 1584 @predicate('present(set)', safe=True, takeorder=True)
1569 1585 def present(repo, subset, x, order):
1570 1586 """An empty set, if any revision in set isn't found; otherwise,
1571 1587 all revisions in set.
1572 1588
1573 1589 If any of specified revisions is not present in the local repository,
1574 1590 the query is normally aborted. But this predicate allows the query
1575 1591 to continue even in such cases.
1576 1592 """
1577 1593 try:
1578 1594 return getset(repo, subset, x, order)
1579 1595 except error.RepoLookupError:
1580 1596 return baseset()
1581 1597
1582 1598 # for internal use
1583 1599 @predicate('_notpublic', safe=True)
1584 1600 def _notpublic(repo, subset, x):
1585 1601 getargs(x, 0, 0, "_notpublic takes no arguments")
1586 1602 return _phase(repo, subset, phases.draft, phases.secret)
1587 1603
1588 1604 # for internal use
1589 1605 @predicate('_phaseandancestors(phasename, set)', safe=True)
1590 1606 def _phaseandancestors(repo, subset, x):
1591 1607 # equivalent to (phasename() & ancestors(set)) but more efficient
1592 1608 # phasename could be one of 'draft', 'secret', or '_notpublic'
1593 1609 args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
1594 1610 phasename = getsymbol(args[0])
1595 1611 s = getset(repo, fullreposet(repo), args[1])
1596 1612
1597 1613 draft = phases.draft
1598 1614 secret = phases.secret
1599 1615 phasenamemap = {
1600 1616 '_notpublic': draft,
1601 1617 'draft': draft, # follow secret's ancestors
1602 1618 'secret': secret,
1603 1619 }
1604 1620 if phasename not in phasenamemap:
1605 1621 raise error.ParseError('%r is not a valid phasename' % phasename)
1606 1622
1607 1623 minimalphase = phasenamemap[phasename]
1608 1624 getphase = repo._phasecache.phase
1609 1625
1610 1626 def cutfunc(rev):
1611 1627 return getphase(repo, rev) < minimalphase
1612 1628
1613 1629 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1614 1630
1615 1631 if phasename == 'draft': # need to remove secret changesets
1616 1632 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1617 1633 return subset & revs
1618 1634
1619 1635 @predicate('public()', safe=True)
1620 1636 def public(repo, subset, x):
1621 1637 """Changeset in public phase."""
1622 1638 # i18n: "public" is a keyword
1623 1639 getargs(x, 0, 0, _("public takes no arguments"))
1624 1640 return _phase(repo, subset, phases.public)
1625 1641
1626 1642 @predicate('remote([id [,path]])', safe=False)
1627 1643 def remote(repo, subset, x):
1628 1644 """Local revision that corresponds to the given identifier in a
1629 1645 remote repository, if present. Here, the '.' identifier is a
1630 1646 synonym for the current local branch.
1631 1647 """
1632 1648
1633 1649 from . import hg # avoid start-up nasties
1634 1650 # i18n: "remote" is a keyword
1635 1651 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1636 1652
1637 1653 q = '.'
1638 1654 if len(l) > 0:
1639 1655 # i18n: "remote" is a keyword
1640 1656 q = getstring(l[0], _("remote requires a string id"))
1641 1657 if q == '.':
1642 1658 q = repo['.'].branch()
1643 1659
1644 1660 dest = ''
1645 1661 if len(l) > 1:
1646 1662 # i18n: "remote" is a keyword
1647 1663 dest = getstring(l[1], _("remote requires a repository path"))
1648 1664 dest = repo.ui.expandpath(dest or 'default')
1649 1665 dest, branches = hg.parseurl(dest)
1650 1666 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1651 1667 if revs:
1652 1668 revs = [repo.lookup(rev) for rev in revs]
1653 1669 other = hg.peer(repo, {}, dest)
1654 1670 n = other.lookup(q)
1655 1671 if n in repo:
1656 1672 r = repo[n].rev()
1657 1673 if r in subset:
1658 1674 return baseset([r])
1659 1675 return baseset()
1660 1676
1661 1677 @predicate('removes(pattern)', safe=True, weight=30)
1662 1678 def removes(repo, subset, x):
1663 1679 """Changesets which remove files matching pattern.
1664 1680
1665 1681 The pattern without explicit kind like ``glob:`` is expected to be
1666 1682 relative to the current directory and match against a file or a
1667 1683 directory.
1668 1684 """
1669 1685 # i18n: "removes" is a keyword
1670 1686 pat = getstring(x, _("removes requires a pattern"))
1671 1687 return checkstatus(repo, subset, pat, 2)
1672 1688
1673 1689 @predicate('rev(number)', safe=True)
1674 1690 def rev(repo, subset, x):
1675 1691 """Revision with the given numeric identifier.
1676 1692 """
1677 1693 # i18n: "rev" is a keyword
1678 1694 l = getargs(x, 1, 1, _("rev requires one argument"))
1679 1695 try:
1680 1696 # i18n: "rev" is a keyword
1681 1697 l = int(getstring(l[0], _("rev requires a number")))
1682 1698 except (TypeError, ValueError):
1683 1699 # i18n: "rev" is a keyword
1684 1700 raise error.ParseError(_("rev expects a number"))
1685 1701 if l not in repo.changelog and l not in (node.nullrev, node.wdirrev):
1686 1702 return baseset()
1687 1703 return subset & baseset([l])
1688 1704
1689 1705 @predicate('matching(revision [, field])', safe=True)
1690 1706 def matching(repo, subset, x):
1691 1707 """Changesets in which a given set of fields match the set of fields in the
1692 1708 selected revision or set.
1693 1709
1694 1710 To match more than one field pass the list of fields to match separated
1695 1711 by spaces (e.g. ``author description``).
1696 1712
1697 1713 Valid fields are most regular revision fields and some special fields.
1698 1714
1699 1715 Regular revision fields are ``description``, ``author``, ``branch``,
1700 1716 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1701 1717 and ``diff``.
1702 1718 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1703 1719 contents of the revision. Two revisions matching their ``diff`` will
1704 1720 also match their ``files``.
1705 1721
1706 1722 Special fields are ``summary`` and ``metadata``:
1707 1723 ``summary`` matches the first line of the description.
1708 1724 ``metadata`` is equivalent to matching ``description user date``
1709 1725 (i.e. it matches the main metadata fields).
1710 1726
1711 1727 ``metadata`` is the default field which is used when no fields are
1712 1728 specified. You can match more than one field at a time.
1713 1729 """
1714 1730 # i18n: "matching" is a keyword
1715 1731 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1716 1732
1717 1733 revs = getset(repo, fullreposet(repo), l[0])
1718 1734
1719 1735 fieldlist = ['metadata']
1720 1736 if len(l) > 1:
1721 1737 fieldlist = getstring(l[1],
1722 1738 # i18n: "matching" is a keyword
1723 1739 _("matching requires a string "
1724 1740 "as its second argument")).split()
1725 1741
1726 1742 # Make sure that there are no repeated fields,
1727 1743 # expand the 'special' 'metadata' field type
1728 1744 # and check the 'files' whenever we check the 'diff'
1729 1745 fields = []
1730 1746 for field in fieldlist:
1731 1747 if field == 'metadata':
1732 1748 fields += ['user', 'description', 'date']
1733 1749 elif field == 'diff':
1734 1750 # a revision matching the diff must also match the files
1735 1751 # since matching the diff is very costly, make sure to
1736 1752 # also match the files first
1737 1753 fields += ['files', 'diff']
1738 1754 else:
1739 1755 if field == 'author':
1740 1756 field = 'user'
1741 1757 fields.append(field)
1742 1758 fields = set(fields)
1743 1759 if 'summary' in fields and 'description' in fields:
1744 1760 # If a revision matches its description it also matches its summary
1745 1761 fields.discard('summary')
1746 1762
1747 1763 # We may want to match more than one field
1748 1764 # Not all fields take the same amount of time to be matched
1749 1765 # Sort the selected fields in order of increasing matching cost
1750 1766 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1751 1767 'files', 'description', 'substate', 'diff']
1752 1768 def fieldkeyfunc(f):
1753 1769 try:
1754 1770 return fieldorder.index(f)
1755 1771 except ValueError:
1756 1772 # assume an unknown field is very costly
1757 1773 return len(fieldorder)
1758 1774 fields = list(fields)
1759 1775 fields.sort(key=fieldkeyfunc)
1760 1776
1761 1777 # Each field will be matched with its own "getfield" function
1762 1778 # which will be added to the getfieldfuncs array of functions
1763 1779 getfieldfuncs = []
1764 1780 _funcs = {
1765 1781 'user': lambda r: repo[r].user(),
1766 1782 'branch': lambda r: repo[r].branch(),
1767 1783 'date': lambda r: repo[r].date(),
1768 1784 'description': lambda r: repo[r].description(),
1769 1785 'files': lambda r: repo[r].files(),
1770 1786 'parents': lambda r: repo[r].parents(),
1771 1787 'phase': lambda r: repo[r].phase(),
1772 1788 'substate': lambda r: repo[r].substate,
1773 1789 'summary': lambda r: repo[r].description().splitlines()[0],
1774 1790 'diff': lambda r: list(repo[r].diff(git=True),)
1775 1791 }
1776 1792 for info in fields:
1777 1793 getfield = _funcs.get(info, None)
1778 1794 if getfield is None:
1779 1795 raise error.ParseError(
1780 1796 # i18n: "matching" is a keyword
1781 1797 _("unexpected field name passed to matching: %s") % info)
1782 1798 getfieldfuncs.append(getfield)
1783 1799 # convert the getfield array of functions into a "getinfo" function
1784 1800 # which returns an array of field values (or a single value if there
1785 1801 # is only one field to match)
1786 1802 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1787 1803
1788 1804 def matches(x):
1789 1805 for rev in revs:
1790 1806 target = getinfo(rev)
1791 1807 match = True
1792 1808 for n, f in enumerate(getfieldfuncs):
1793 1809 if target[n] != f(x):
1794 1810 match = False
1795 1811 if match:
1796 1812 return True
1797 1813 return False
1798 1814
1799 1815 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1800 1816
1801 1817 @predicate('reverse(set)', safe=True, takeorder=True, weight=0)
1802 1818 def reverse(repo, subset, x, order):
1803 1819 """Reverse order of set.
1804 1820 """
1805 1821 l = getset(repo, subset, x, order)
1806 1822 if order == defineorder:
1807 1823 l.reverse()
1808 1824 return l
1809 1825
1810 1826 @predicate('roots(set)', safe=True)
1811 1827 def roots(repo, subset, x):
1812 1828 """Changesets in set with no parent changeset in set.
1813 1829 """
1814 1830 s = getset(repo, fullreposet(repo), x)
1815 1831 parents = repo.changelog.parentrevs
1816 1832 def filter(r):
1817 1833 for p in parents(r):
1818 1834 if 0 <= p and p in s:
1819 1835 return False
1820 1836 return True
1821 1837 return subset & s.filter(filter, condrepr='<roots>')
1822 1838
1823 1839 _sortkeyfuncs = {
1824 1840 'rev': lambda c: c.rev(),
1825 1841 'branch': lambda c: c.branch(),
1826 1842 'desc': lambda c: c.description(),
1827 1843 'user': lambda c: c.user(),
1828 1844 'author': lambda c: c.user(),
1829 1845 'date': lambda c: c.date()[0],
1830 1846 }
1831 1847
1832 1848 def _getsortargs(x):
1833 1849 """Parse sort options into (set, [(key, reverse)], opts)"""
1834 1850 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1835 1851 if 'set' not in args:
1836 1852 # i18n: "sort" is a keyword
1837 1853 raise error.ParseError(_('sort requires one or two arguments'))
1838 1854 keys = "rev"
1839 1855 if 'keys' in args:
1840 1856 # i18n: "sort" is a keyword
1841 1857 keys = getstring(args['keys'], _("sort spec must be a string"))
1842 1858
1843 1859 keyflags = []
1844 1860 for k in keys.split():
1845 1861 fk = k
1846 1862 reverse = (k.startswith('-'))
1847 1863 if reverse:
1848 1864 k = k[1:]
1849 1865 if k not in _sortkeyfuncs and k != 'topo':
1850 1866 raise error.ParseError(
1851 1867 _("unknown sort key %r") % pycompat.bytestr(fk))
1852 1868 keyflags.append((k, reverse))
1853 1869
1854 1870 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1855 1871 # i18n: "topo" is a keyword
1856 1872 raise error.ParseError(_('topo sort order cannot be combined '
1857 1873 'with other sort keys'))
1858 1874
1859 1875 opts = {}
1860 1876 if 'topo.firstbranch' in args:
1861 1877 if any(k == 'topo' for k, reverse in keyflags):
1862 1878 opts['topo.firstbranch'] = args['topo.firstbranch']
1863 1879 else:
1864 1880 # i18n: "topo" and "topo.firstbranch" are keywords
1865 1881 raise error.ParseError(_('topo.firstbranch can only be used '
1866 1882 'when using the topo sort key'))
1867 1883
1868 1884 return args['set'], keyflags, opts
1869 1885
1870 1886 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
1871 1887 weight=10)
1872 1888 def sort(repo, subset, x, order):
1873 1889 """Sort set by keys. The default sort order is ascending, specify a key
1874 1890 as ``-key`` to sort in descending order.
1875 1891
1876 1892 The keys can be:
1877 1893
1878 1894 - ``rev`` for the revision number,
1879 1895 - ``branch`` for the branch name,
1880 1896 - ``desc`` for the commit message (description),
1881 1897 - ``user`` for user name (``author`` can be used as an alias),
1882 1898 - ``date`` for the commit date
1883 1899 - ``topo`` for a reverse topographical sort
1884 1900
1885 1901 The ``topo`` sort order cannot be combined with other sort keys. This sort
1886 1902 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1887 1903 specifies what topographical branches to prioritize in the sort.
1888 1904
1889 1905 """
1890 1906 s, keyflags, opts = _getsortargs(x)
1891 1907 revs = getset(repo, subset, s, order)
1892 1908
1893 1909 if not keyflags or order != defineorder:
1894 1910 return revs
1895 1911 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1896 1912 revs.sort(reverse=keyflags[0][1])
1897 1913 return revs
1898 1914 elif keyflags[0][0] == "topo":
1899 1915 firstbranch = ()
1900 1916 if 'topo.firstbranch' in opts:
1901 1917 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1902 1918 revs = baseset(dagop.toposort(revs, repo.changelog.parentrevs,
1903 1919 firstbranch),
1904 1920 istopo=True)
1905 1921 if keyflags[0][1]:
1906 1922 revs.reverse()
1907 1923 return revs
1908 1924
1909 1925 # sort() is guaranteed to be stable
1910 1926 ctxs = [repo[r] for r in revs]
1911 1927 for k, reverse in reversed(keyflags):
1912 1928 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1913 1929 return baseset([c.rev() for c in ctxs])
1914 1930
1915 1931 @predicate('subrepo([pattern])')
1916 1932 def subrepo(repo, subset, x):
1917 1933 """Changesets that add, modify or remove the given subrepo. If no subrepo
1918 1934 pattern is named, any subrepo changes are returned.
1919 1935 """
1920 1936 # i18n: "subrepo" is a keyword
1921 1937 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1922 1938 pat = None
1923 1939 if len(args) != 0:
1924 1940 pat = getstring(args[0], _("subrepo requires a pattern"))
1925 1941
1926 1942 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1927 1943
1928 1944 def submatches(names):
1929 1945 k, p, m = util.stringmatcher(pat)
1930 1946 for name in names:
1931 1947 if m(name):
1932 1948 yield name
1933 1949
1934 1950 def matches(x):
1935 1951 c = repo[x]
1936 1952 s = repo.status(c.p1().node(), c.node(), match=m)
1937 1953
1938 1954 if pat is None:
1939 1955 return s.added or s.modified or s.removed
1940 1956
1941 1957 if s.added:
1942 1958 return any(submatches(c.substate.keys()))
1943 1959
1944 1960 if s.modified:
1945 1961 subs = set(c.p1().substate.keys())
1946 1962 subs.update(c.substate.keys())
1947 1963
1948 1964 for path in submatches(subs):
1949 1965 if c.p1().substate.get(path) != c.substate.get(path):
1950 1966 return True
1951 1967
1952 1968 if s.removed:
1953 1969 return any(submatches(c.p1().substate.keys()))
1954 1970
1955 1971 return False
1956 1972
1957 1973 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1958 1974
1959 1975 def _mapbynodefunc(repo, s, f):
1960 1976 """(repo, smartset, [node] -> [node]) -> smartset
1961 1977
1962 1978 Helper method to map a smartset to another smartset given a function only
1963 1979 talking about nodes. Handles converting between rev numbers and nodes, and
1964 1980 filtering.
1965 1981 """
1966 1982 cl = repo.unfiltered().changelog
1967 1983 torev = cl.rev
1968 1984 tonode = cl.node
1969 1985 nodemap = cl.nodemap
1970 1986 result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
1971 1987 return smartset.baseset(result - repo.changelog.filteredrevs)
1972 1988
1973 1989 @predicate('successors(set)', safe=True)
1974 1990 def successors(repo, subset, x):
1975 1991 """All successors for set, including the given set themselves"""
1976 1992 s = getset(repo, fullreposet(repo), x)
1977 1993 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
1978 1994 d = _mapbynodefunc(repo, s, f)
1979 1995 return subset & d
1980 1996
1981 1997 def _substringmatcher(pattern, casesensitive=True):
1982 1998 kind, pattern, matcher = util.stringmatcher(pattern,
1983 1999 casesensitive=casesensitive)
1984 2000 if kind == 'literal':
1985 2001 if not casesensitive:
1986 2002 pattern = encoding.lower(pattern)
1987 2003 matcher = lambda s: pattern in encoding.lower(s)
1988 2004 else:
1989 2005 matcher = lambda s: pattern in s
1990 2006 return kind, pattern, matcher
1991 2007
1992 2008 @predicate('tag([name])', safe=True)
1993 2009 def tag(repo, subset, x):
1994 2010 """The specified tag by name, or all tagged revisions if no name is given.
1995 2011
1996 2012 Pattern matching is supported for `name`. See
1997 2013 :hg:`help revisions.patterns`.
1998 2014 """
1999 2015 # i18n: "tag" is a keyword
2000 2016 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2001 2017 cl = repo.changelog
2002 2018 if args:
2003 2019 pattern = getstring(args[0],
2004 2020 # i18n: "tag" is a keyword
2005 2021 _('the argument to tag must be a string'))
2006 2022 kind, pattern, matcher = util.stringmatcher(pattern)
2007 2023 if kind == 'literal':
2008 2024 # avoid resolving all tags
2009 2025 tn = repo._tagscache.tags.get(pattern, None)
2010 2026 if tn is None:
2011 2027 raise error.RepoLookupError(_("tag '%s' does not exist")
2012 2028 % pattern)
2013 2029 s = {repo[tn].rev()}
2014 2030 else:
2015 2031 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2016 2032 else:
2017 2033 s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
2018 2034 return subset & s
2019 2035
2020 2036 @predicate('tagged', safe=True)
2021 2037 def tagged(repo, subset, x):
2022 2038 return tag(repo, subset, x)
2023 2039
2024 2040 @predicate('orphan()', safe=True)
2025 2041 def orphan(repo, subset, x):
2026 2042 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2027 2043 """
2028 2044 # i18n: "orphan" is a keyword
2029 2045 getargs(x, 0, 0, _("orphan takes no arguments"))
2030 2046 orphan = obsmod.getrevs(repo, 'orphan')
2031 2047 return subset & orphan
2032 2048
2033 2049
2034 2050 @predicate('user(string)', safe=True, weight=10)
2035 2051 def user(repo, subset, x):
2036 2052 """User name contains string. The match is case-insensitive.
2037 2053
2038 2054 Pattern matching is supported for `string`. See
2039 2055 :hg:`help revisions.patterns`.
2040 2056 """
2041 2057 return author(repo, subset, x)
2042 2058
2043 2059 @predicate('wdir()', safe=True, weight=0)
2044 2060 def wdir(repo, subset, x):
2045 2061 """Working directory. (EXPERIMENTAL)"""
2046 2062 # i18n: "wdir" is a keyword
2047 2063 getargs(x, 0, 0, _("wdir takes no arguments"))
2048 2064 if node.wdirrev in subset or isinstance(subset, fullreposet):
2049 2065 return baseset([node.wdirrev])
2050 2066 return baseset()
2051 2067
2052 2068 def _orderedlist(repo, subset, x):
2053 2069 s = getstring(x, "internal error")
2054 2070 if not s:
2055 2071 return baseset()
2056 2072 # remove duplicates here. it's difficult for caller to deduplicate sets
2057 2073 # because different symbols can point to the same rev.
2058 2074 cl = repo.changelog
2059 2075 ls = []
2060 2076 seen = set()
2061 2077 for t in s.split('\0'):
2062 2078 try:
2063 2079 # fast path for integer revision
2064 2080 r = int(t)
2065 2081 if ('%d' % r) != t or r not in cl:
2066 2082 raise ValueError
2067 2083 revs = [r]
2068 2084 except ValueError:
2069 2085 revs = stringset(repo, subset, t, defineorder)
2070 2086
2071 2087 for r in revs:
2072 2088 if r in seen:
2073 2089 continue
2074 2090 if (r in subset
2075 2091 or r == node.nullrev and isinstance(subset, fullreposet)):
2076 2092 ls.append(r)
2077 2093 seen.add(r)
2078 2094 return baseset(ls)
2079 2095
2080 2096 # for internal use
2081 2097 @predicate('_list', safe=True, takeorder=True)
2082 2098 def _list(repo, subset, x, order):
2083 2099 if order == followorder:
2084 2100 # slow path to take the subset order
2085 2101 return subset & _orderedlist(repo, fullreposet(repo), x)
2086 2102 else:
2087 2103 return _orderedlist(repo, subset, x)
2088 2104
2089 2105 def _orderedintlist(repo, subset, x):
2090 2106 s = getstring(x, "internal error")
2091 2107 if not s:
2092 2108 return baseset()
2093 2109 ls = [int(r) for r in s.split('\0')]
2094 2110 s = subset
2095 2111 return baseset([r for r in ls if r in s])
2096 2112
2097 2113 # for internal use
2098 2114 @predicate('_intlist', safe=True, takeorder=True, weight=0)
2099 2115 def _intlist(repo, subset, x, order):
2100 2116 if order == followorder:
2101 2117 # slow path to take the subset order
2102 2118 return subset & _orderedintlist(repo, fullreposet(repo), x)
2103 2119 else:
2104 2120 return _orderedintlist(repo, subset, x)
2105 2121
2106 2122 def _orderedhexlist(repo, subset, x):
2107 2123 s = getstring(x, "internal error")
2108 2124 if not s:
2109 2125 return baseset()
2110 2126 cl = repo.changelog
2111 2127 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2112 2128 s = subset
2113 2129 return baseset([r for r in ls if r in s])
2114 2130
2115 2131 # for internal use
2116 2132 @predicate('_hexlist', safe=True, takeorder=True)
2117 2133 def _hexlist(repo, subset, x, order):
2118 2134 if order == followorder:
2119 2135 # slow path to take the subset order
2120 2136 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2121 2137 else:
2122 2138 return _orderedhexlist(repo, subset, x)
2123 2139
2124 2140 methods = {
2125 2141 "range": rangeset,
2126 2142 "rangeall": rangeall,
2127 2143 "rangepre": rangepre,
2128 2144 "rangepost": rangepost,
2129 2145 "dagrange": dagrange,
2130 2146 "string": stringset,
2131 2147 "symbol": stringset,
2132 2148 "and": andset,
2133 2149 "andsmally": andsmallyset,
2134 2150 "or": orset,
2135 2151 "not": notset,
2136 2152 "difference": differenceset,
2137 2153 "relation": relationset,
2138 2154 "relsubscript": relsubscriptset,
2139 2155 "subscript": subscriptset,
2140 2156 "list": listset,
2141 2157 "keyvalue": keyvaluepair,
2142 2158 "func": func,
2143 2159 "ancestor": ancestorspec,
2144 2160 "parent": parentspec,
2145 2161 "parentpost": parentpost,
2146 2162 }
2147 2163
2148 2164 def posttreebuilthook(tree, repo):
2149 2165 # hook for extensions to execute code on the optimized tree
2150 2166 pass
2151 2167
2152 2168 def match(ui, spec, repo=None):
2153 2169 """Create a matcher for a single revision spec"""
2154 2170 return matchany(ui, [spec], repo=repo)
2155 2171
2156 2172 def matchany(ui, specs, repo=None, localalias=None):
2157 2173 """Create a matcher that will include any revisions matching one of the
2158 2174 given specs
2159 2175
2160 2176 If localalias is not None, it is a dict {name: definitionstring}. It takes
2161 2177 precedence over [revsetalias] config section.
2162 2178 """
2163 2179 if not specs:
2164 2180 def mfunc(repo, subset=None):
2165 2181 return baseset()
2166 2182 return mfunc
2167 2183 if not all(specs):
2168 2184 raise error.ParseError(_("empty query"))
2169 2185 lookup = None
2170 2186 if repo:
2171 2187 lookup = repo.__contains__
2172 2188 if len(specs) == 1:
2173 2189 tree = revsetlang.parse(specs[0], lookup)
2174 2190 else:
2175 2191 tree = ('or',
2176 2192 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2177 2193
2178 2194 aliases = []
2179 2195 warn = None
2180 2196 if ui:
2181 2197 aliases.extend(ui.configitems('revsetalias'))
2182 2198 warn = ui.warn
2183 2199 if localalias:
2184 2200 aliases.extend(localalias.items())
2185 2201 if aliases:
2186 2202 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2187 2203 tree = revsetlang.foldconcat(tree)
2188 2204 tree = revsetlang.analyze(tree)
2189 2205 tree = revsetlang.optimize(tree)
2190 2206 posttreebuilthook(tree, repo)
2191 2207 return makematcher(tree)
2192 2208
2193 2209 def makematcher(tree):
2194 2210 """Create a matcher from an evaluatable tree"""
2195 2211 def mfunc(repo, subset=None, order=None):
2196 2212 if order is None:
2197 2213 if subset is None:
2198 2214 order = defineorder # 'x'
2199 2215 else:
2200 2216 order = followorder # 'subset & x'
2201 2217 if subset is None:
2202 2218 subset = fullreposet(repo)
2203 2219 return getset(repo, subset, tree, order)
2204 2220 return mfunc
2205 2221
2206 2222 def loadpredicate(ui, extname, registrarobj):
2207 2223 """Load revset predicates from specified registrarobj
2208 2224 """
2209 2225 for name, func in registrarobj._table.iteritems():
2210 2226 symbols[name] = func
2211 2227 if func._safe:
2212 2228 safesymbols.add(name)
2213 2229
2214 2230 # load built-in predicates explicitly to setup safesymbols
2215 2231 loadpredicate(None, None, predicate)
2216 2232
2217 2233 # tell hggettext to extract docstrings from these functions:
2218 2234 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now