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