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