##// END OF EJS Templates
spelling: further
timeless@mozdev.org -
r17494:74801685 default
parent child Browse files
Show More
@@ -1,285 +1,285 b''
1 1 # bzr.py - bzr support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 9 # it cannot access 'bar' repositories, but they were never used very much
10 10
11 11 import os
12 12 from mercurial import demandimport
13 13 # these do not work with demandimport, blacklist
14 14 demandimport.ignore.extend([
15 15 'bzrlib.transactions',
16 16 'bzrlib.urlutils',
17 17 'ElementPath',
18 18 ])
19 19
20 20 from mercurial.i18n import _
21 21 from mercurial import util
22 22 from common import NoRepo, commit, converter_source
23 23
24 24 try:
25 25 # bazaar imports
26 26 from bzrlib import bzrdir, revision, errors
27 27 from bzrlib.revisionspec import RevisionSpec
28 28 except ImportError:
29 29 pass
30 30
31 31 supportedkinds = ('file', 'symlink')
32 32
33 33 class bzr_source(converter_source):
34 34 """Reads Bazaar repositories by using the Bazaar Python libraries"""
35 35
36 36 def __init__(self, ui, path, rev=None):
37 37 super(bzr_source, self).__init__(ui, path, rev=rev)
38 38
39 39 if not os.path.exists(os.path.join(path, '.bzr')):
40 40 raise NoRepo(_('%s does not look like a Bazaar repository')
41 41 % path)
42 42
43 43 try:
44 44 # access bzrlib stuff
45 45 bzrdir
46 46 except NameError:
47 47 raise NoRepo(_('Bazaar modules could not be loaded'))
48 48
49 49 path = os.path.abspath(path)
50 50 self._checkrepotype(path)
51 51 try:
52 52 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
53 53 except errors.NoRepositoryPresent:
54 54 raise NoRepo(_('%s does not look like a Bazaar repository')
55 55 % path)
56 56 self._parentids = {}
57 57
58 58 def _checkrepotype(self, path):
59 59 # Lightweight checkouts detection is informational but probably
60 60 # fragile at API level. It should not terminate the conversion.
61 61 try:
62 62 from bzrlib import bzrdir
63 63 dir = bzrdir.BzrDir.open_containing(path)[0]
64 64 try:
65 65 tree = dir.open_workingtree(recommend_upgrade=False)
66 66 branch = tree.branch
67 67 except (errors.NoWorkingTree, errors.NotLocalUrl):
68 68 tree = None
69 69 branch = dir.open_branch()
70 70 if (tree is not None and tree.bzrdir.root_transport.base !=
71 71 branch.bzrdir.root_transport.base):
72 72 self.ui.warn(_('warning: lightweight checkouts may cause '
73 73 'conversion failures, try with a regular '
74 74 'branch instead.\n'))
75 75 except Exception:
76 76 self.ui.note(_('bzr source type could not be determined\n'))
77 77
78 78 def before(self):
79 79 """Before the conversion begins, acquire a read lock
80 80 for all the operations that might need it. Fortunately
81 81 read locks don't block other reads or writes to the
82 82 repository, so this shouldn't have any impact on the usage of
83 83 the source repository.
84 84
85 85 The alternative would be locking on every operation that
86 86 needs locks (there are currently two: getting the file and
87 87 getting the parent map) and releasing immediately after,
88 88 but this approach can take even 40% longer."""
89 89 self.sourcerepo.lock_read()
90 90
91 91 def after(self):
92 92 self.sourcerepo.unlock()
93 93
94 94 def _bzrbranches(self):
95 95 return self.sourcerepo.find_branches(using=True)
96 96
97 97 def getheads(self):
98 98 if not self.rev:
99 99 # Set using=True to avoid nested repositories (see issue3254)
100 100 heads = sorted([b.last_revision() for b in self._bzrbranches()])
101 101 else:
102 102 revid = None
103 103 for branch in self._bzrbranches():
104 104 try:
105 105 r = RevisionSpec.from_string(self.rev)
106 106 info = r.in_history(branch)
107 107 except errors.BzrError:
108 108 pass
109 109 revid = info.rev_id
110 110 if revid is None:
111 111 raise util.Abort(_('%s is not a valid revision') % self.rev)
112 112 heads = [revid]
113 113 # Empty repositories return 'null:', which cannot be retrieved
114 114 heads = [h for h in heads if h != 'null:']
115 115 return heads
116 116
117 117 def getfile(self, name, rev):
118 118 revtree = self.sourcerepo.revision_tree(rev)
119 119 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
120 120 kind = None
121 121 if fileid is not None:
122 122 kind = revtree.kind(fileid)
123 123 if kind not in supportedkinds:
124 124 # the file is not available anymore - was deleted
125 125 raise IOError(_('%s is not available in %s anymore') %
126 126 (name, rev))
127 127 mode = self._modecache[(name, rev)]
128 128 if kind == 'symlink':
129 129 target = revtree.get_symlink_target(fileid)
130 130 if target is None:
131 131 raise util.Abort(_('%s.%s symlink has no target')
132 132 % (name, rev))
133 133 return target, mode
134 134 else:
135 135 sio = revtree.get_file(fileid)
136 136 return sio.read(), mode
137 137
138 138 def getchanges(self, version):
139 139 # set up caches: modecache and revtree
140 140 self._modecache = {}
141 141 self._revtree = self.sourcerepo.revision_tree(version)
142 142 # get the parentids from the cache
143 143 parentids = self._parentids.pop(version)
144 144 # only diff against first parent id
145 145 prevtree = self.sourcerepo.revision_tree(parentids[0])
146 146 return self._gettreechanges(self._revtree, prevtree)
147 147
148 148 def getcommit(self, version):
149 149 rev = self.sourcerepo.get_revision(version)
150 150 # populate parent id cache
151 151 if not rev.parent_ids:
152 152 parents = []
153 153 self._parentids[version] = (revision.NULL_REVISION,)
154 154 else:
155 155 parents = self._filterghosts(rev.parent_ids)
156 156 self._parentids[version] = parents
157 157
158 158 branch = self.recode(rev.properties.get('branch-nick', u'default'))
159 159 if branch == 'trunk':
160 160 branch = 'default'
161 161 return commit(parents=parents,
162 162 date='%d %d' % (rev.timestamp, -rev.timezone),
163 163 author=self.recode(rev.committer),
164 164 desc=self.recode(rev.message),
165 165 branch=branch,
166 166 rev=version)
167 167
168 168 def gettags(self):
169 169 bytetags = {}
170 170 for branch in self._bzrbranches():
171 171 if not branch.supports_tags():
172 172 return {}
173 173 tagdict = branch.tags.get_tag_dict()
174 174 for name, rev in tagdict.iteritems():
175 175 bytetags[self.recode(name)] = rev
176 176 return bytetags
177 177
178 178 def getchangedfiles(self, rev, i):
179 179 self._modecache = {}
180 180 curtree = self.sourcerepo.revision_tree(rev)
181 181 if i is not None:
182 182 parentid = self._parentids[rev][i]
183 183 else:
184 184 # no parent id, get the empty revision
185 185 parentid = revision.NULL_REVISION
186 186
187 187 prevtree = self.sourcerepo.revision_tree(parentid)
188 188 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
189 189 return changes
190 190
191 191 def _gettreechanges(self, current, origin):
192 192 revid = current._revision_id
193 193 changes = []
194 194 renames = {}
195 195 seen = set()
196 196 # Process the entries by reverse lexicographic name order to
197 197 # handle nested renames correctly, most specific first.
198 198 curchanges = sorted(current.iter_changes(origin),
199 199 key=lambda c: c[1][0] or c[1][1],
200 200 reverse=True)
201 201 for (fileid, paths, changed_content, versioned, parent, name,
202 202 kind, executable) in curchanges:
203 203
204 204 if paths[0] == u'' or paths[1] == u'':
205 205 # ignore changes to tree root
206 206 continue
207 207
208 208 # bazaar tracks directories, mercurial does not, so
209 209 # we have to rename the directory contents
210 210 if kind[1] == 'directory':
211 211 if kind[0] not in (None, 'directory'):
212 212 # Replacing 'something' with a directory, record it
213 213 # so it can be removed.
214 214 changes.append((self.recode(paths[0]), revid))
215 215
216 216 if kind[0] == 'directory' and None not in paths:
217 217 renaming = paths[0] != paths[1]
218 218 # neither an add nor an delete - a move
219 219 # rename all directory contents manually
220 220 subdir = origin.inventory.path2id(paths[0])
221 221 # get all child-entries of the directory
222 222 for name, entry in origin.inventory.iter_entries(subdir):
223 223 # hg does not track directory renames
224 224 if entry.kind == 'directory':
225 225 continue
226 226 frompath = self.recode(paths[0] + '/' + name)
227 227 if frompath in seen:
228 228 # Already handled by a more specific change entry
229 229 # This is important when you have:
230 230 # a => b
231 231 # a/c => a/c
232 232 # Here a/c must not be renamed into b/c
233 233 continue
234 234 seen.add(frompath)
235 235 if not renaming:
236 236 continue
237 237 topath = self.recode(paths[1] + '/' + name)
238 238 # register the files as changed
239 239 changes.append((frompath, revid))
240 240 changes.append((topath, revid))
241 241 # add to mode cache
242 242 mode = ((entry.executable and 'x')
243 243 or (entry.kind == 'symlink' and 's')
244 244 or '')
245 245 self._modecache[(topath, revid)] = mode
246 246 # register the change as move
247 247 renames[topath] = frompath
248 248
249 # no futher changes, go to the next change
249 # no further changes, go to the next change
250 250 continue
251 251
252 252 # we got unicode paths, need to convert them
253 253 path, topath = paths
254 254 if path is not None:
255 255 path = self.recode(path)
256 256 if topath is not None:
257 257 topath = self.recode(topath)
258 258 seen.add(path or topath)
259 259
260 260 if topath is None:
261 261 # file deleted
262 262 changes.append((path, revid))
263 263 continue
264 264
265 265 # renamed
266 266 if path and path != topath:
267 267 renames[topath] = path
268 268 changes.append((path, revid))
269 269
270 270 # populate the mode cache
271 271 kind, executable = [e[1] for e in (kind, executable)]
272 272 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
273 273 or '')
274 274 self._modecache[(topath, revid)] = mode
275 275 changes.append((topath, revid))
276 276
277 277 return changes, renames
278 278
279 279 def _filterghosts(self, ids):
280 280 """Filters out ghost revisions which hg does not support, see
281 281 <http://bazaar-vcs.org/GhostRevision>
282 282 """
283 283 parentmap = self.sourcerepo.get_parent_map(ids)
284 284 parents = tuple([parent for parent in ids if parent in parentmap])
285 285 return parents
@@ -1,1856 +1,1856 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = util.deque(revs)
21 21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 24 if parent not in seen:
25 25 visit.append(parent)
26 26 seen.add(parent)
27 27 yield parent
28 28
29 29 def _revdescendants(repo, revs, followfirst):
30 30 """Like revlog.descendants() but supports followfirst."""
31 31 cut = followfirst and 1 or None
32 32 cl = repo.changelog
33 33 first = min(revs)
34 34 nullrev = node.nullrev
35 35 if first == nullrev:
36 36 # Are there nodes with a null first parent and a non-null
37 37 # second one? Maybe. Do we care? Probably not.
38 38 for i in cl:
39 39 yield i
40 40 return
41 41
42 42 seen = set(revs)
43 43 for i in xrange(first + 1, len(cl)):
44 44 for x in cl.parentrevs(i)[:cut]:
45 45 if x != nullrev and x in seen:
46 46 seen.add(i)
47 47 yield i
48 48 break
49 49
50 50 def _revsbetween(repo, roots, heads):
51 51 """Return all paths between roots and heads, inclusive of both endpoint
52 52 sets."""
53 53 if not roots:
54 54 return []
55 55 parentrevs = repo.changelog.parentrevs
56 56 visit = heads[:]
57 57 reachable = set()
58 58 seen = {}
59 59 minroot = min(roots)
60 60 roots = set(roots)
61 61 # open-code the post-order traversal due to the tiny size of
62 62 # sys.getrecursionlimit()
63 63 while visit:
64 64 rev = visit.pop()
65 65 if rev in roots:
66 66 reachable.add(rev)
67 67 parents = parentrevs(rev)
68 68 seen[rev] = parents
69 69 for parent in parents:
70 70 if parent >= minroot and parent not in seen:
71 71 visit.append(parent)
72 72 if not reachable:
73 73 return []
74 74 for rev in sorted(seen):
75 75 for parent in seen[rev]:
76 76 if parent in reachable:
77 77 reachable.add(rev)
78 78 return sorted(reachable)
79 79
80 80 elements = {
81 81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 82 "~": (18, None, ("ancestor", 18)),
83 83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 84 "-": (5, ("negate", 19), ("minus", 5)),
85 85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 86 ("dagrangepost", 17)),
87 87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 88 ("dagrangepost", 17)),
89 89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 90 "not": (10, ("not", 10)),
91 91 "!": (10, ("not", 10)),
92 92 "and": (5, None, ("and", 5)),
93 93 "&": (5, None, ("and", 5)),
94 94 "or": (4, None, ("or", 4)),
95 95 "|": (4, None, ("or", 4)),
96 96 "+": (4, None, ("or", 4)),
97 97 ",": (2, None, ("list", 2)),
98 98 ")": (0, None, None),
99 99 "symbol": (0, ("symbol",), None),
100 100 "string": (0, ("string",), None),
101 101 "end": (0, None, None),
102 102 }
103 103
104 104 keywords = set(['and', 'or', 'not'])
105 105
106 106 def tokenize(program):
107 107 pos, l = 0, len(program)
108 108 while pos < l:
109 109 c = program[pos]
110 110 if c.isspace(): # skip inter-token whitespace
111 111 pass
112 112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 113 yield ('::', None, pos)
114 114 pos += 1 # skip ahead
115 115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 116 yield ('..', None, pos)
117 117 pos += 1 # skip ahead
118 118 elif c in "():,-|&+!~^": # handle simple operators
119 119 yield (c, None, pos)
120 120 elif (c in '"\'' or c == 'r' and
121 121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 122 if c == 'r':
123 123 pos += 1
124 124 c = program[pos]
125 125 decode = lambda x: x
126 126 else:
127 127 decode = lambda x: x.decode('string-escape')
128 128 pos += 1
129 129 s = pos
130 130 while pos < l: # find closing quote
131 131 d = program[pos]
132 132 if d == '\\': # skip over escaped characters
133 133 pos += 2
134 134 continue
135 135 if d == c:
136 136 yield ('string', decode(program[s:pos]), s)
137 137 break
138 138 pos += 1
139 139 else:
140 140 raise error.ParseError(_("unterminated string"), s)
141 141 # gather up a symbol/keyword
142 142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 143 s = pos
144 144 pos += 1
145 145 while pos < l: # find end of symbol
146 146 d = program[pos]
147 147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 148 break
149 149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 150 pos -= 1
151 151 break
152 152 pos += 1
153 153 sym = program[s:pos]
154 154 if sym in keywords: # operator keywords
155 155 yield (sym, None, s)
156 156 else:
157 157 yield ('symbol', sym, s)
158 158 pos -= 1
159 159 else:
160 160 raise error.ParseError(_("syntax error"), pos)
161 161 pos += 1
162 162 yield ('end', None, pos)
163 163
164 164 # helpers
165 165
166 166 def getstring(x, err):
167 167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 168 return x[1]
169 169 raise error.ParseError(err)
170 170
171 171 def getlist(x):
172 172 if not x:
173 173 return []
174 174 if x[0] == 'list':
175 175 return getlist(x[1]) + [x[2]]
176 176 return [x]
177 177
178 178 def getargs(x, min, max, err):
179 179 l = getlist(x)
180 180 if len(l) < min or (max >= 0 and len(l) > max):
181 181 raise error.ParseError(err)
182 182 return l
183 183
184 184 def getset(repo, subset, x):
185 185 if not x:
186 186 raise error.ParseError(_("missing argument"))
187 187 return methods[x[0]](repo, subset, *x[1:])
188 188
189 189 def _getrevsource(repo, r):
190 190 extra = repo[r].extra()
191 191 for label in ('source', 'transplant_source', 'rebase_source'):
192 192 if label in extra:
193 193 try:
194 194 return repo[extra[label]].rev()
195 195 except error.RepoLookupError:
196 196 pass
197 197 return None
198 198
199 199 # operator methods
200 200
201 201 def stringset(repo, subset, x):
202 202 x = repo[x].rev()
203 203 if x == -1 and len(subset) == len(repo):
204 204 return [-1]
205 205 if len(subset) == len(repo) or x in subset:
206 206 return [x]
207 207 return []
208 208
209 209 def symbolset(repo, subset, x):
210 210 if x in symbols:
211 211 raise error.ParseError(_("can't use %s here") % x)
212 212 return stringset(repo, subset, x)
213 213
214 214 def rangeset(repo, subset, x, y):
215 215 m = getset(repo, subset, x)
216 216 if not m:
217 217 m = getset(repo, range(len(repo)), x)
218 218
219 219 n = getset(repo, subset, y)
220 220 if not n:
221 221 n = getset(repo, range(len(repo)), y)
222 222
223 223 if not m or not n:
224 224 return []
225 225 m, n = m[0], n[-1]
226 226
227 227 if m < n:
228 228 r = range(m, n + 1)
229 229 else:
230 230 r = range(m, n - 1, -1)
231 231 s = set(subset)
232 232 return [x for x in r if x in s]
233 233
234 234 def dagrange(repo, subset, x, y):
235 235 if subset:
236 236 r = range(len(repo))
237 237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 238 s = set(subset)
239 239 return [r for r in xs if r in s]
240 240 return []
241 241
242 242 def andset(repo, subset, x, y):
243 243 return getset(repo, getset(repo, subset, x), y)
244 244
245 245 def orset(repo, subset, x, y):
246 246 xl = getset(repo, subset, x)
247 247 s = set(xl)
248 248 yl = getset(repo, [r for r in subset if r not in s], y)
249 249 return xl + yl
250 250
251 251 def notset(repo, subset, x):
252 252 s = set(getset(repo, subset, x))
253 253 return [r for r in subset if r not in s]
254 254
255 255 def listset(repo, subset, a, b):
256 256 raise error.ParseError(_("can't use a list in this context"))
257 257
258 258 def func(repo, subset, a, b):
259 259 if a[0] == 'symbol' and a[1] in symbols:
260 260 return symbols[a[1]](repo, subset, b)
261 261 raise error.ParseError(_("not a function: %s") % a[1])
262 262
263 263 # functions
264 264
265 265 def adds(repo, subset, x):
266 266 """``adds(pattern)``
267 267 Changesets that add a file matching pattern.
268 268 """
269 269 # i18n: "adds" is a keyword
270 270 pat = getstring(x, _("adds requires a pattern"))
271 271 return checkstatus(repo, subset, pat, 1)
272 272
273 273 def ancestor(repo, subset, x):
274 274 """``ancestor(single, single)``
275 275 Greatest common ancestor of the two changesets.
276 276 """
277 277 # i18n: "ancestor" is a keyword
278 278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 279 r = range(len(repo))
280 280 a = getset(repo, r, l[0])
281 281 b = getset(repo, r, l[1])
282 282 if len(a) != 1 or len(b) != 1:
283 283 # i18n: "ancestor" is a keyword
284 284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286 286
287 287 return [r for r in an if r in subset]
288 288
289 289 def _ancestors(repo, subset, x, followfirst=False):
290 290 args = getset(repo, range(len(repo)), x)
291 291 if not args:
292 292 return []
293 293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 294 return [r for r in subset if r in s]
295 295
296 296 def ancestors(repo, subset, x):
297 297 """``ancestors(set)``
298 298 Changesets that are ancestors of a changeset in set.
299 299 """
300 300 return _ancestors(repo, subset, x)
301 301
302 302 def _firstancestors(repo, subset, x):
303 303 # ``_firstancestors(set)``
304 304 # Like ``ancestors(set)`` but follows only the first parents.
305 305 return _ancestors(repo, subset, x, followfirst=True)
306 306
307 307 def ancestorspec(repo, subset, x, n):
308 308 """``set~n``
309 309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 310 in set.
311 311 """
312 312 try:
313 313 n = int(n[1])
314 314 except (TypeError, ValueError):
315 315 raise error.ParseError(_("~ expects a number"))
316 316 ps = set()
317 317 cl = repo.changelog
318 318 for r in getset(repo, subset, x):
319 319 for i in range(n):
320 320 r = cl.parentrevs(r)[0]
321 321 ps.add(r)
322 322 return [r for r in subset if r in ps]
323 323
324 324 def author(repo, subset, x):
325 325 """``author(string)``
326 326 Alias for ``user(string)``.
327 327 """
328 328 # i18n: "author" is a keyword
329 329 n = encoding.lower(getstring(x, _("author requires a string")))
330 330 kind, pattern, matcher = _substringmatcher(n)
331 331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332 332
333 333 def bisect(repo, subset, x):
334 334 """``bisect(string)``
335 335 Changesets marked in the specified bisect status:
336 336
337 337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 338 - ``goods``, ``bads`` : csets topologicaly good/bad
339 339 - ``range`` : csets taking part in the bisection
340 340 - ``pruned`` : csets that are goods, bads or skipped
341 341 - ``untested`` : csets whose fate is yet unknown
342 342 - ``ignored`` : csets ignored due to DAG topology
343 343 - ``current`` : the cset currently being bisected
344 344 """
345 345 # i18n: "bisect" is a keyword
346 346 status = getstring(x, _("bisect requires a string")).lower()
347 347 state = set(hbisect.get(repo, status))
348 348 return [r for r in subset if r in state]
349 349
350 350 # Backward-compatibility
351 351 # - no help entry so that we do not advertise it any more
352 352 def bisected(repo, subset, x):
353 353 return bisect(repo, subset, x)
354 354
355 355 def bookmark(repo, subset, x):
356 356 """``bookmark([name])``
357 357 The named bookmark or all bookmarks.
358 358
359 359 If `name` starts with `re:`, the remainder of the name is treated as
360 360 a regular expression. To match a bookmark that actually starts with `re:`,
361 361 use the prefix `literal:`.
362 362 """
363 363 # i18n: "bookmark" is a keyword
364 364 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
365 365 if args:
366 366 bm = getstring(args[0],
367 367 # i18n: "bookmark" is a keyword
368 368 _('the argument to bookmark must be a string'))
369 369 kind, pattern, matcher = _stringmatcher(bm)
370 370 if kind == 'literal':
371 371 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
372 372 if not bmrev:
373 373 raise util.Abort(_("bookmark '%s' does not exist") % bm)
374 374 bmrev = repo[bmrev].rev()
375 375 return [r for r in subset if r == bmrev]
376 376 else:
377 377 matchrevs = set()
378 378 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
379 379 if matcher(name):
380 380 matchrevs.add(bmrev)
381 381 if not matchrevs:
382 382 raise util.Abort(_("no bookmarks exist that match '%s'")
383 383 % pattern)
384 384 bmrevs = set()
385 385 for bmrev in matchrevs:
386 386 bmrevs.add(repo[bmrev].rev())
387 387 return [r for r in subset if r in bmrevs]
388 388
389 389 bms = set([repo[r].rev()
390 390 for r in bookmarksmod.listbookmarks(repo).values()])
391 391 return [r for r in subset if r in bms]
392 392
393 393 def branch(repo, subset, x):
394 394 """``branch(string or set)``
395 395 All changesets belonging to the given branch or the branches of the given
396 396 changesets.
397 397
398 398 If `string` starts with `re:`, the remainder of the name is treated as
399 399 a regular expression. To match a branch that actually starts with `re:`,
400 400 use the prefix `literal:`.
401 401 """
402 402 try:
403 403 b = getstring(x, '')
404 404 except error.ParseError:
405 405 # not a string, but another revspec, e.g. tip()
406 406 pass
407 407 else:
408 408 kind, pattern, matcher = _stringmatcher(b)
409 409 if kind == 'literal':
410 410 # note: falls through to the revspec case if no branch with
411 411 # this name exists
412 412 if pattern in repo.branchmap():
413 413 return [r for r in subset if matcher(repo[r].branch())]
414 414 else:
415 415 return [r for r in subset if matcher(repo[r].branch())]
416 416
417 417 s = getset(repo, range(len(repo)), x)
418 418 b = set()
419 419 for r in s:
420 420 b.add(repo[r].branch())
421 421 s = set(s)
422 422 return [r for r in subset if r in s or repo[r].branch() in b]
423 423
424 424 def checkstatus(repo, subset, pat, field):
425 425 m = None
426 426 s = []
427 427 hasset = matchmod.patkind(pat) == 'set'
428 428 fname = None
429 429 for r in subset:
430 430 c = repo[r]
431 431 if not m or hasset:
432 432 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
433 433 if not m.anypats() and len(m.files()) == 1:
434 434 fname = m.files()[0]
435 435 if fname is not None:
436 436 if fname not in c.files():
437 437 continue
438 438 else:
439 439 for f in c.files():
440 440 if m(f):
441 441 break
442 442 else:
443 443 continue
444 444 files = repo.status(c.p1().node(), c.node())[field]
445 445 if fname is not None:
446 446 if fname in files:
447 447 s.append(r)
448 448 else:
449 449 for f in files:
450 450 if m(f):
451 451 s.append(r)
452 452 break
453 453 return s
454 454
455 455 def _children(repo, narrow, parentset):
456 456 cs = set()
457 457 pr = repo.changelog.parentrevs
458 458 for r in narrow:
459 459 for p in pr(r):
460 460 if p in parentset:
461 461 cs.add(r)
462 462 return cs
463 463
464 464 def children(repo, subset, x):
465 465 """``children(set)``
466 466 Child changesets of changesets in set.
467 467 """
468 468 s = set(getset(repo, range(len(repo)), x))
469 469 cs = _children(repo, subset, s)
470 470 return [r for r in subset if r in cs]
471 471
472 472 def closed(repo, subset, x):
473 473 """``closed()``
474 474 Changeset is closed.
475 475 """
476 476 # i18n: "closed" is a keyword
477 477 getargs(x, 0, 0, _("closed takes no arguments"))
478 478 return [r for r in subset if repo[r].closesbranch()]
479 479
480 480 def contains(repo, subset, x):
481 481 """``contains(pattern)``
482 482 Revision contains a file matching pattern. See :hg:`help patterns`
483 483 for information about file patterns.
484 484 """
485 485 # i18n: "contains" is a keyword
486 486 pat = getstring(x, _("contains requires a pattern"))
487 487 m = None
488 488 s = []
489 489 if not matchmod.patkind(pat):
490 490 for r in subset:
491 491 if pat in repo[r]:
492 492 s.append(r)
493 493 else:
494 494 for r in subset:
495 495 c = repo[r]
496 496 if not m or matchmod.patkind(pat) == 'set':
497 497 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
498 498 for f in c.manifest():
499 499 if m(f):
500 500 s.append(r)
501 501 break
502 502 return s
503 503
504 504 def converted(repo, subset, x):
505 505 """``converted([id])``
506 506 Changesets converted from the given identifier in the old repository if
507 507 present, or all converted changesets if no identifier is specified.
508 508 """
509 509
510 510 # There is exactly no chance of resolving the revision, so do a simple
511 511 # string compare and hope for the best
512 512
513 513 rev = None
514 514 # i18n: "converted" is a keyword
515 515 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
516 516 if l:
517 517 # i18n: "converted" is a keyword
518 518 rev = getstring(l[0], _('converted requires a revision'))
519 519
520 520 def _matchvalue(r):
521 521 source = repo[r].extra().get('convert_revision', None)
522 522 return source is not None and (rev is None or source.startswith(rev))
523 523
524 524 return [r for r in subset if _matchvalue(r)]
525 525
526 526 def date(repo, subset, x):
527 527 """``date(interval)``
528 528 Changesets within the interval, see :hg:`help dates`.
529 529 """
530 530 # i18n: "date" is a keyword
531 531 ds = getstring(x, _("date requires a string"))
532 532 dm = util.matchdate(ds)
533 533 return [r for r in subset if dm(repo[r].date()[0])]
534 534
535 535 def desc(repo, subset, x):
536 536 """``desc(string)``
537 537 Search commit message for string. The match is case-insensitive.
538 538 """
539 539 # i18n: "desc" is a keyword
540 540 ds = encoding.lower(getstring(x, _("desc requires a string")))
541 541 l = []
542 542 for r in subset:
543 543 c = repo[r]
544 544 if ds in encoding.lower(c.description()):
545 545 l.append(r)
546 546 return l
547 547
548 548 def _descendants(repo, subset, x, followfirst=False):
549 549 args = getset(repo, range(len(repo)), x)
550 550 if not args:
551 551 return []
552 552 s = set(_revdescendants(repo, args, followfirst)) | set(args)
553 553 return [r for r in subset if r in s]
554 554
555 555 def descendants(repo, subset, x):
556 556 """``descendants(set)``
557 557 Changesets which are descendants of changesets in set.
558 558 """
559 559 return _descendants(repo, subset, x)
560 560
561 561 def _firstdescendants(repo, subset, x):
562 562 # ``_firstdescendants(set)``
563 563 # Like ``descendants(set)`` but follows only the first parents.
564 564 return _descendants(repo, subset, x, followfirst=True)
565 565
566 566 def destination(repo, subset, x):
567 567 """``destination([set])``
568 568 Changesets that were created by a graft, transplant or rebase operation,
569 569 with the given revisions specified as the source. Omitting the optional set
570 570 is the same as passing all().
571 571 """
572 572 if x is not None:
573 573 args = set(getset(repo, range(len(repo)), x))
574 574 else:
575 575 args = set(getall(repo, range(len(repo)), x))
576 576
577 577 dests = set()
578 578
579 579 # subset contains all of the possible destinations that can be returned, so
580 580 # iterate over them and see if their source(s) were provided in the args.
581 581 # Even if the immediate src of r is not in the args, src's source (or
582 582 # further back) may be. Scanning back further than the immediate src allows
583 583 # transitive transplants and rebases to yield the same results as transitive
584 584 # grafts.
585 585 for r in subset:
586 586 src = _getrevsource(repo, r)
587 587 lineage = None
588 588
589 589 while src is not None:
590 590 if lineage is None:
591 591 lineage = list()
592 592
593 593 lineage.append(r)
594 594
595 595 # The visited lineage is a match if the current source is in the arg
596 596 # set. Since every candidate dest is visited by way of iterating
597 # subset, any dests futher back in the lineage will be tested by a
597 # subset, any dests further back in the lineage will be tested by a
598 598 # different iteration over subset. Likewise, if the src was already
599 599 # selected, the current lineage can be selected without going back
600 600 # further.
601 601 if src in args or src in dests:
602 602 dests.update(lineage)
603 603 break
604 604
605 605 r = src
606 606 src = _getrevsource(repo, r)
607 607
608 608 return [r for r in subset if r in dests]
609 609
610 610 def draft(repo, subset, x):
611 611 """``draft()``
612 612 Changeset in draft phase."""
613 613 # i18n: "draft" is a keyword
614 614 getargs(x, 0, 0, _("draft takes no arguments"))
615 615 pc = repo._phasecache
616 616 return [r for r in subset if pc.phase(repo, r) == phases.draft]
617 617
618 618 def extinct(repo, subset, x):
619 619 """``extinct()``
620 620 Obsolete changesets with obsolete descendants only.
621 621 """
622 622 # i18n: "extinct" is a keyword
623 623 getargs(x, 0, 0, _("extinct takes no arguments"))
624 624 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
625 625 return [r for r in subset if r in extinctset]
626 626
627 627 def extra(repo, subset, x):
628 628 """``extra(label, [value])``
629 629 Changesets with the given label in the extra metadata, with the given
630 630 optional value.
631 631
632 632 If `value` starts with `re:`, the remainder of the value is treated as
633 633 a regular expression. To match a value that actually starts with `re:`,
634 634 use the prefix `literal:`.
635 635 """
636 636
637 637 # i18n: "extra" is a keyword
638 638 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
639 639 # i18n: "extra" is a keyword
640 640 label = getstring(l[0], _('first argument to extra must be a string'))
641 641 value = None
642 642
643 643 if len(l) > 1:
644 644 # i18n: "extra" is a keyword
645 645 value = getstring(l[1], _('second argument to extra must be a string'))
646 646 kind, value, matcher = _stringmatcher(value)
647 647
648 648 def _matchvalue(r):
649 649 extra = repo[r].extra()
650 650 return label in extra and (value is None or matcher(extra[label]))
651 651
652 652 return [r for r in subset if _matchvalue(r)]
653 653
654 654 def filelog(repo, subset, x):
655 655 """``filelog(pattern)``
656 656 Changesets connected to the specified filelog.
657 657
658 658 For performance reasons, ``filelog()`` does not show every changeset
659 659 that affects the requested file(s). See :hg:`help log` for details. For
660 660 a slower, more accurate result, use ``file()``.
661 661 """
662 662
663 663 # i18n: "filelog" is a keyword
664 664 pat = getstring(x, _("filelog requires a pattern"))
665 665 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
666 666 ctx=repo[None])
667 667 s = set()
668 668
669 669 if not matchmod.patkind(pat):
670 670 for f in m.files():
671 671 fl = repo.file(f)
672 672 for fr in fl:
673 673 s.add(fl.linkrev(fr))
674 674 else:
675 675 for f in repo[None]:
676 676 if m(f):
677 677 fl = repo.file(f)
678 678 for fr in fl:
679 679 s.add(fl.linkrev(fr))
680 680
681 681 return [r for r in subset if r in s]
682 682
683 683 def first(repo, subset, x):
684 684 """``first(set, [n])``
685 685 An alias for limit().
686 686 """
687 687 return limit(repo, subset, x)
688 688
689 689 def _follow(repo, subset, x, name, followfirst=False):
690 690 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
691 691 c = repo['.']
692 692 if l:
693 693 x = getstring(l[0], _("%s expected a filename") % name)
694 694 if x in c:
695 695 cx = c[x]
696 696 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
697 697 # include the revision responsible for the most recent version
698 698 s.add(cx.linkrev())
699 699 else:
700 700 return []
701 701 else:
702 702 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
703 703
704 704 return [r for r in subset if r in s]
705 705
706 706 def follow(repo, subset, x):
707 707 """``follow([file])``
708 708 An alias for ``::.`` (ancestors of the working copy's first parent).
709 709 If a filename is specified, the history of the given file is followed,
710 710 including copies.
711 711 """
712 712 return _follow(repo, subset, x, 'follow')
713 713
714 714 def _followfirst(repo, subset, x):
715 715 # ``followfirst([file])``
716 716 # Like ``follow([file])`` but follows only the first parent of
717 717 # every revision or file revision.
718 718 return _follow(repo, subset, x, '_followfirst', followfirst=True)
719 719
720 720 def getall(repo, subset, x):
721 721 """``all()``
722 722 All changesets, the same as ``0:tip``.
723 723 """
724 724 # i18n: "all" is a keyword
725 725 getargs(x, 0, 0, _("all takes no arguments"))
726 726 return subset
727 727
728 728 def grep(repo, subset, x):
729 729 """``grep(regex)``
730 730 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
731 731 to ensure special escape characters are handled correctly. Unlike
732 732 ``keyword(string)``, the match is case-sensitive.
733 733 """
734 734 try:
735 735 # i18n: "grep" is a keyword
736 736 gr = re.compile(getstring(x, _("grep requires a string")))
737 737 except re.error, e:
738 738 raise error.ParseError(_('invalid match pattern: %s') % e)
739 739 l = []
740 740 for r in subset:
741 741 c = repo[r]
742 742 for e in c.files() + [c.user(), c.description()]:
743 743 if gr.search(e):
744 744 l.append(r)
745 745 break
746 746 return l
747 747
748 748 def _matchfiles(repo, subset, x):
749 749 # _matchfiles takes a revset list of prefixed arguments:
750 750 #
751 751 # [p:foo, i:bar, x:baz]
752 752 #
753 753 # builds a match object from them and filters subset. Allowed
754 754 # prefixes are 'p:' for regular patterns, 'i:' for include
755 755 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
756 756 # a revision identifier, or the empty string to reference the
757 757 # working directory, from which the match object is
758 758 # initialized. Use 'd:' to set the default matching mode, default
759 759 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
760 760
761 761 # i18n: "_matchfiles" is a keyword
762 762 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
763 763 pats, inc, exc = [], [], []
764 764 hasset = False
765 765 rev, default = None, None
766 766 for arg in l:
767 767 # i18n: "_matchfiles" is a keyword
768 768 s = getstring(arg, _("_matchfiles requires string arguments"))
769 769 prefix, value = s[:2], s[2:]
770 770 if prefix == 'p:':
771 771 pats.append(value)
772 772 elif prefix == 'i:':
773 773 inc.append(value)
774 774 elif prefix == 'x:':
775 775 exc.append(value)
776 776 elif prefix == 'r:':
777 777 if rev is not None:
778 778 # i18n: "_matchfiles" is a keyword
779 779 raise error.ParseError(_('_matchfiles expected at most one '
780 780 'revision'))
781 781 rev = value
782 782 elif prefix == 'd:':
783 783 if default is not None:
784 784 # i18n: "_matchfiles" is a keyword
785 785 raise error.ParseError(_('_matchfiles expected at most one '
786 786 'default mode'))
787 787 default = value
788 788 else:
789 789 # i18n: "_matchfiles" is a keyword
790 790 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
791 791 if not hasset and matchmod.patkind(value) == 'set':
792 792 hasset = True
793 793 if not default:
794 794 default = 'glob'
795 795 m = None
796 796 s = []
797 797 for r in subset:
798 798 c = repo[r]
799 799 if not m or (hasset and rev is None):
800 800 ctx = c
801 801 if rev is not None:
802 802 ctx = repo[rev or None]
803 803 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
804 804 exclude=exc, ctx=ctx, default=default)
805 805 for f in c.files():
806 806 if m(f):
807 807 s.append(r)
808 808 break
809 809 return s
810 810
811 811 def hasfile(repo, subset, x):
812 812 """``file(pattern)``
813 813 Changesets affecting files matched by pattern.
814 814
815 815 For a faster but less accurate result, consider using ``filelog()``
816 816 instead.
817 817 """
818 818 # i18n: "file" is a keyword
819 819 pat = getstring(x, _("file requires a pattern"))
820 820 return _matchfiles(repo, subset, ('string', 'p:' + pat))
821 821
822 822 def head(repo, subset, x):
823 823 """``head()``
824 824 Changeset is a named branch head.
825 825 """
826 826 # i18n: "head" is a keyword
827 827 getargs(x, 0, 0, _("head takes no arguments"))
828 828 hs = set()
829 829 for b, ls in repo.branchmap().iteritems():
830 830 hs.update(repo[h].rev() for h in ls)
831 831 return [r for r in subset if r in hs]
832 832
833 833 def heads(repo, subset, x):
834 834 """``heads(set)``
835 835 Members of set with no children in set.
836 836 """
837 837 s = getset(repo, subset, x)
838 838 ps = set(parents(repo, subset, x))
839 839 return [r for r in s if r not in ps]
840 840
841 841 def keyword(repo, subset, x):
842 842 """``keyword(string)``
843 843 Search commit message, user name, and names of changed files for
844 844 string. The match is case-insensitive.
845 845 """
846 846 # i18n: "keyword" is a keyword
847 847 kw = encoding.lower(getstring(x, _("keyword requires a string")))
848 848 l = []
849 849 for r in subset:
850 850 c = repo[r]
851 851 t = " ".join(c.files() + [c.user(), c.description()])
852 852 if kw in encoding.lower(t):
853 853 l.append(r)
854 854 return l
855 855
856 856 def limit(repo, subset, x):
857 857 """``limit(set, [n])``
858 858 First n members of set, defaulting to 1.
859 859 """
860 860 # i18n: "limit" is a keyword
861 861 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
862 862 try:
863 863 lim = 1
864 864 if len(l) == 2:
865 865 # i18n: "limit" is a keyword
866 866 lim = int(getstring(l[1], _("limit requires a number")))
867 867 except (TypeError, ValueError):
868 868 # i18n: "limit" is a keyword
869 869 raise error.ParseError(_("limit expects a number"))
870 870 ss = set(subset)
871 871 os = getset(repo, range(len(repo)), l[0])[:lim]
872 872 return [r for r in os if r in ss]
873 873
874 874 def last(repo, subset, x):
875 875 """``last(set, [n])``
876 876 Last n members of set, defaulting to 1.
877 877 """
878 878 # i18n: "last" is a keyword
879 879 l = getargs(x, 1, 2, _("last requires one or two arguments"))
880 880 try:
881 881 lim = 1
882 882 if len(l) == 2:
883 883 # i18n: "last" is a keyword
884 884 lim = int(getstring(l[1], _("last requires a number")))
885 885 except (TypeError, ValueError):
886 886 # i18n: "last" is a keyword
887 887 raise error.ParseError(_("last expects a number"))
888 888 ss = set(subset)
889 889 os = getset(repo, range(len(repo)), l[0])[-lim:]
890 890 return [r for r in os if r in ss]
891 891
892 892 def maxrev(repo, subset, x):
893 893 """``max(set)``
894 894 Changeset with highest revision number in set.
895 895 """
896 896 os = getset(repo, range(len(repo)), x)
897 897 if os:
898 898 m = max(os)
899 899 if m in subset:
900 900 return [m]
901 901 return []
902 902
903 903 def merge(repo, subset, x):
904 904 """``merge()``
905 905 Changeset is a merge changeset.
906 906 """
907 907 # i18n: "merge" is a keyword
908 908 getargs(x, 0, 0, _("merge takes no arguments"))
909 909 cl = repo.changelog
910 910 return [r for r in subset if cl.parentrevs(r)[1] != -1]
911 911
912 912 def minrev(repo, subset, x):
913 913 """``min(set)``
914 914 Changeset with lowest revision number in set.
915 915 """
916 916 os = getset(repo, range(len(repo)), x)
917 917 if os:
918 918 m = min(os)
919 919 if m in subset:
920 920 return [m]
921 921 return []
922 922
923 923 def modifies(repo, subset, x):
924 924 """``modifies(pattern)``
925 925 Changesets modifying files matched by pattern.
926 926 """
927 927 # i18n: "modifies" is a keyword
928 928 pat = getstring(x, _("modifies requires a pattern"))
929 929 return checkstatus(repo, subset, pat, 0)
930 930
931 931 def node_(repo, subset, x):
932 932 """``id(string)``
933 933 Revision non-ambiguously specified by the given hex string prefix.
934 934 """
935 935 # i18n: "id" is a keyword
936 936 l = getargs(x, 1, 1, _("id requires one argument"))
937 937 # i18n: "id" is a keyword
938 938 n = getstring(l[0], _("id requires a string"))
939 939 if len(n) == 40:
940 940 rn = repo[n].rev()
941 941 else:
942 942 rn = None
943 943 pm = repo.changelog._partialmatch(n)
944 944 if pm is not None:
945 945 rn = repo.changelog.rev(pm)
946 946
947 947 return [r for r in subset if r == rn]
948 948
949 949 def obsolete(repo, subset, x):
950 950 """``obsolete()``
951 951 Mutable changeset with a newer version."""
952 952 # i18n: "obsolete" is a keyword
953 953 getargs(x, 0, 0, _("obsolete takes no arguments"))
954 954 return [r for r in subset if repo[r].obsolete()]
955 955
956 956 def origin(repo, subset, x):
957 957 """``origin([set])``
958 958 Changesets that were specified as a source for the grafts, transplants or
959 959 rebases that created the given revisions. Omitting the optional set is the
960 960 same as passing all(). If a changeset created by these operations is itself
961 961 specified as a source for one of these operations, only the source changeset
962 962 for the first operation is selected.
963 963 """
964 964 if x is not None:
965 965 args = set(getset(repo, range(len(repo)), x))
966 966 else:
967 967 args = set(getall(repo, range(len(repo)), x))
968 968
969 969 def _firstsrc(rev):
970 970 src = _getrevsource(repo, rev)
971 971 if src is None:
972 972 return None
973 973
974 974 while True:
975 975 prev = _getrevsource(repo, src)
976 976
977 977 if prev is None:
978 978 return src
979 979 src = prev
980 980
981 981 o = set([_firstsrc(r) for r in args])
982 982 return [r for r in subset if r in o]
983 983
984 984 def outgoing(repo, subset, x):
985 985 """``outgoing([path])``
986 986 Changesets not found in the specified destination repository, or the
987 987 default push location.
988 988 """
989 989 import hg # avoid start-up nasties
990 990 # i18n: "outgoing" is a keyword
991 991 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
992 992 # i18n: "outgoing" is a keyword
993 993 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
994 994 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
995 995 dest, branches = hg.parseurl(dest)
996 996 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
997 997 if revs:
998 998 revs = [repo.lookup(rev) for rev in revs]
999 999 other = hg.peer(repo, {}, dest)
1000 1000 repo.ui.pushbuffer()
1001 1001 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1002 1002 repo.ui.popbuffer()
1003 1003 cl = repo.changelog
1004 1004 o = set([cl.rev(r) for r in outgoing.missing])
1005 1005 return [r for r in subset if r in o]
1006 1006
1007 1007 def p1(repo, subset, x):
1008 1008 """``p1([set])``
1009 1009 First parent of changesets in set, or the working directory.
1010 1010 """
1011 1011 if x is None:
1012 1012 p = repo[x].p1().rev()
1013 1013 return [r for r in subset if r == p]
1014 1014
1015 1015 ps = set()
1016 1016 cl = repo.changelog
1017 1017 for r in getset(repo, range(len(repo)), x):
1018 1018 ps.add(cl.parentrevs(r)[0])
1019 1019 return [r for r in subset if r in ps]
1020 1020
1021 1021 def p2(repo, subset, x):
1022 1022 """``p2([set])``
1023 1023 Second parent of changesets in set, or the working directory.
1024 1024 """
1025 1025 if x is None:
1026 1026 ps = repo[x].parents()
1027 1027 try:
1028 1028 p = ps[1].rev()
1029 1029 return [r for r in subset if r == p]
1030 1030 except IndexError:
1031 1031 return []
1032 1032
1033 1033 ps = set()
1034 1034 cl = repo.changelog
1035 1035 for r in getset(repo, range(len(repo)), x):
1036 1036 ps.add(cl.parentrevs(r)[1])
1037 1037 return [r for r in subset if r in ps]
1038 1038
1039 1039 def parents(repo, subset, x):
1040 1040 """``parents([set])``
1041 1041 The set of all parents for all changesets in set, or the working directory.
1042 1042 """
1043 1043 if x is None:
1044 1044 ps = tuple(p.rev() for p in repo[x].parents())
1045 1045 return [r for r in subset if r in ps]
1046 1046
1047 1047 ps = set()
1048 1048 cl = repo.changelog
1049 1049 for r in getset(repo, range(len(repo)), x):
1050 1050 ps.update(cl.parentrevs(r))
1051 1051 return [r for r in subset if r in ps]
1052 1052
1053 1053 def parentspec(repo, subset, x, n):
1054 1054 """``set^0``
1055 1055 The set.
1056 1056 ``set^1`` (or ``set^``), ``set^2``
1057 1057 First or second parent, respectively, of all changesets in set.
1058 1058 """
1059 1059 try:
1060 1060 n = int(n[1])
1061 1061 if n not in (0, 1, 2):
1062 1062 raise ValueError
1063 1063 except (TypeError, ValueError):
1064 1064 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1065 1065 ps = set()
1066 1066 cl = repo.changelog
1067 1067 for r in getset(repo, subset, x):
1068 1068 if n == 0:
1069 1069 ps.add(r)
1070 1070 elif n == 1:
1071 1071 ps.add(cl.parentrevs(r)[0])
1072 1072 elif n == 2:
1073 1073 parents = cl.parentrevs(r)
1074 1074 if len(parents) > 1:
1075 1075 ps.add(parents[1])
1076 1076 return [r for r in subset if r in ps]
1077 1077
1078 1078 def present(repo, subset, x):
1079 1079 """``present(set)``
1080 1080 An empty set, if any revision in set isn't found; otherwise,
1081 1081 all revisions in set.
1082 1082
1083 1083 If any of specified revisions is not present in the local repository,
1084 1084 the query is normally aborted. But this predicate allows the query
1085 1085 to continue even in such cases.
1086 1086 """
1087 1087 try:
1088 1088 return getset(repo, subset, x)
1089 1089 except error.RepoLookupError:
1090 1090 return []
1091 1091
1092 1092 def public(repo, subset, x):
1093 1093 """``public()``
1094 1094 Changeset in public phase."""
1095 1095 # i18n: "public" is a keyword
1096 1096 getargs(x, 0, 0, _("public takes no arguments"))
1097 1097 pc = repo._phasecache
1098 1098 return [r for r in subset if pc.phase(repo, r) == phases.public]
1099 1099
1100 1100 def remote(repo, subset, x):
1101 1101 """``remote([id [,path]])``
1102 1102 Local revision that corresponds to the given identifier in a
1103 1103 remote repository, if present. Here, the '.' identifier is a
1104 1104 synonym for the current local branch.
1105 1105 """
1106 1106
1107 1107 import hg # avoid start-up nasties
1108 1108 # i18n: "remote" is a keyword
1109 1109 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1110 1110
1111 1111 q = '.'
1112 1112 if len(l) > 0:
1113 1113 # i18n: "remote" is a keyword
1114 1114 q = getstring(l[0], _("remote requires a string id"))
1115 1115 if q == '.':
1116 1116 q = repo['.'].branch()
1117 1117
1118 1118 dest = ''
1119 1119 if len(l) > 1:
1120 1120 # i18n: "remote" is a keyword
1121 1121 dest = getstring(l[1], _("remote requires a repository path"))
1122 1122 dest = repo.ui.expandpath(dest or 'default')
1123 1123 dest, branches = hg.parseurl(dest)
1124 1124 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1125 1125 if revs:
1126 1126 revs = [repo.lookup(rev) for rev in revs]
1127 1127 other = hg.peer(repo, {}, dest)
1128 1128 n = other.lookup(q)
1129 1129 if n in repo:
1130 1130 r = repo[n].rev()
1131 1131 if r in subset:
1132 1132 return [r]
1133 1133 return []
1134 1134
1135 1135 def removes(repo, subset, x):
1136 1136 """``removes(pattern)``
1137 1137 Changesets which remove files matching pattern.
1138 1138 """
1139 1139 # i18n: "removes" is a keyword
1140 1140 pat = getstring(x, _("removes requires a pattern"))
1141 1141 return checkstatus(repo, subset, pat, 2)
1142 1142
1143 1143 def rev(repo, subset, x):
1144 1144 """``rev(number)``
1145 1145 Revision with the given numeric identifier.
1146 1146 """
1147 1147 # i18n: "rev" is a keyword
1148 1148 l = getargs(x, 1, 1, _("rev requires one argument"))
1149 1149 try:
1150 1150 # i18n: "rev" is a keyword
1151 1151 l = int(getstring(l[0], _("rev requires a number")))
1152 1152 except (TypeError, ValueError):
1153 1153 # i18n: "rev" is a keyword
1154 1154 raise error.ParseError(_("rev expects a number"))
1155 1155 return [r for r in subset if r == l]
1156 1156
1157 1157 def matching(repo, subset, x):
1158 1158 """``matching(revision [, field])``
1159 1159 Changesets in which a given set of fields match the set of fields in the
1160 1160 selected revision or set.
1161 1161
1162 1162 To match more than one field pass the list of fields to match separated
1163 1163 by spaces (e.g. ``author description``).
1164 1164
1165 1165 Valid fields are most regular revision fields and some special fields.
1166 1166
1167 1167 Regular revision fields are ``description``, ``author``, ``branch``,
1168 1168 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1169 1169 and ``diff``.
1170 1170 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1171 1171 contents of the revision. Two revisions matching their ``diff`` will
1172 1172 also match their ``files``.
1173 1173
1174 1174 Special fields are ``summary`` and ``metadata``:
1175 1175 ``summary`` matches the first line of the description.
1176 1176 ``metadata`` is equivalent to matching ``description user date``
1177 1177 (i.e. it matches the main metadata fields).
1178 1178
1179 1179 ``metadata`` is the default field which is used when no fields are
1180 1180 specified. You can match more than one field at a time.
1181 1181 """
1182 1182 # i18n: "matching" is a keyword
1183 1183 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1184 1184
1185 1185 revs = getset(repo, xrange(len(repo)), l[0])
1186 1186
1187 1187 fieldlist = ['metadata']
1188 1188 if len(l) > 1:
1189 1189 fieldlist = getstring(l[1],
1190 1190 # i18n: "matching" is a keyword
1191 1191 _("matching requires a string "
1192 1192 "as its second argument")).split()
1193 1193
1194 1194 # Make sure that there are no repeated fields,
1195 1195 # expand the 'special' 'metadata' field type
1196 1196 # and check the 'files' whenever we check the 'diff'
1197 1197 fields = []
1198 1198 for field in fieldlist:
1199 1199 if field == 'metadata':
1200 1200 fields += ['user', 'description', 'date']
1201 1201 elif field == 'diff':
1202 1202 # a revision matching the diff must also match the files
1203 1203 # since matching the diff is very costly, make sure to
1204 1204 # also match the files first
1205 1205 fields += ['files', 'diff']
1206 1206 else:
1207 1207 if field == 'author':
1208 1208 field = 'user'
1209 1209 fields.append(field)
1210 1210 fields = set(fields)
1211 1211 if 'summary' in fields and 'description' in fields:
1212 1212 # If a revision matches its description it also matches its summary
1213 1213 fields.discard('summary')
1214 1214
1215 1215 # We may want to match more than one field
1216 1216 # Not all fields take the same amount of time to be matched
1217 1217 # Sort the selected fields in order of increasing matching cost
1218 1218 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1219 1219 'files', 'description', 'substate', 'diff']
1220 1220 def fieldkeyfunc(f):
1221 1221 try:
1222 1222 return fieldorder.index(f)
1223 1223 except ValueError:
1224 1224 # assume an unknown field is very costly
1225 1225 return len(fieldorder)
1226 1226 fields = list(fields)
1227 1227 fields.sort(key=fieldkeyfunc)
1228 1228
1229 1229 # Each field will be matched with its own "getfield" function
1230 1230 # which will be added to the getfieldfuncs array of functions
1231 1231 getfieldfuncs = []
1232 1232 _funcs = {
1233 1233 'user': lambda r: repo[r].user(),
1234 1234 'branch': lambda r: repo[r].branch(),
1235 1235 'date': lambda r: repo[r].date(),
1236 1236 'description': lambda r: repo[r].description(),
1237 1237 'files': lambda r: repo[r].files(),
1238 1238 'parents': lambda r: repo[r].parents(),
1239 1239 'phase': lambda r: repo[r].phase(),
1240 1240 'substate': lambda r: repo[r].substate,
1241 1241 'summary': lambda r: repo[r].description().splitlines()[0],
1242 1242 'diff': lambda r: list(repo[r].diff(git=True),)
1243 1243 }
1244 1244 for info in fields:
1245 1245 getfield = _funcs.get(info, None)
1246 1246 if getfield is None:
1247 1247 raise error.ParseError(
1248 1248 # i18n: "matching" is a keyword
1249 1249 _("unexpected field name passed to matching: %s") % info)
1250 1250 getfieldfuncs.append(getfield)
1251 1251 # convert the getfield array of functions into a "getinfo" function
1252 1252 # which returns an array of field values (or a single value if there
1253 1253 # is only one field to match)
1254 1254 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1255 1255
1256 1256 matches = set()
1257 1257 for rev in revs:
1258 1258 target = getinfo(rev)
1259 1259 for r in subset:
1260 1260 match = True
1261 1261 for n, f in enumerate(getfieldfuncs):
1262 1262 if target[n] != f(r):
1263 1263 match = False
1264 1264 break
1265 1265 if match:
1266 1266 matches.add(r)
1267 1267 return [r for r in subset if r in matches]
1268 1268
1269 1269 def reverse(repo, subset, x):
1270 1270 """``reverse(set)``
1271 1271 Reverse order of set.
1272 1272 """
1273 1273 l = getset(repo, subset, x)
1274 1274 if not isinstance(l, list):
1275 1275 l = list(l)
1276 1276 l.reverse()
1277 1277 return l
1278 1278
1279 1279 def roots(repo, subset, x):
1280 1280 """``roots(set)``
1281 1281 Changesets in set with no parent changeset in set.
1282 1282 """
1283 1283 s = set(getset(repo, xrange(len(repo)), x))
1284 1284 subset = [r for r in subset if r in s]
1285 1285 cs = _children(repo, subset, s)
1286 1286 return [r for r in subset if r not in cs]
1287 1287
1288 1288 def secret(repo, subset, x):
1289 1289 """``secret()``
1290 1290 Changeset in secret phase."""
1291 1291 # i18n: "secret" is a keyword
1292 1292 getargs(x, 0, 0, _("secret takes no arguments"))
1293 1293 pc = repo._phasecache
1294 1294 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1295 1295
1296 1296 def sort(repo, subset, x):
1297 1297 """``sort(set[, [-]key...])``
1298 1298 Sort set by keys. The default sort order is ascending, specify a key
1299 1299 as ``-key`` to sort in descending order.
1300 1300
1301 1301 The keys can be:
1302 1302
1303 1303 - ``rev`` for the revision number,
1304 1304 - ``branch`` for the branch name,
1305 1305 - ``desc`` for the commit message (description),
1306 1306 - ``user`` for user name (``author`` can be used as an alias),
1307 1307 - ``date`` for the commit date
1308 1308 """
1309 1309 # i18n: "sort" is a keyword
1310 1310 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1311 1311 keys = "rev"
1312 1312 if len(l) == 2:
1313 1313 # i18n: "sort" is a keyword
1314 1314 keys = getstring(l[1], _("sort spec must be a string"))
1315 1315
1316 1316 s = l[0]
1317 1317 keys = keys.split()
1318 1318 l = []
1319 1319 def invert(s):
1320 1320 return "".join(chr(255 - ord(c)) for c in s)
1321 1321 for r in getset(repo, subset, s):
1322 1322 c = repo[r]
1323 1323 e = []
1324 1324 for k in keys:
1325 1325 if k == 'rev':
1326 1326 e.append(r)
1327 1327 elif k == '-rev':
1328 1328 e.append(-r)
1329 1329 elif k == 'branch':
1330 1330 e.append(c.branch())
1331 1331 elif k == '-branch':
1332 1332 e.append(invert(c.branch()))
1333 1333 elif k == 'desc':
1334 1334 e.append(c.description())
1335 1335 elif k == '-desc':
1336 1336 e.append(invert(c.description()))
1337 1337 elif k in 'user author':
1338 1338 e.append(c.user())
1339 1339 elif k in '-user -author':
1340 1340 e.append(invert(c.user()))
1341 1341 elif k == 'date':
1342 1342 e.append(c.date()[0])
1343 1343 elif k == '-date':
1344 1344 e.append(-c.date()[0])
1345 1345 else:
1346 1346 raise error.ParseError(_("unknown sort key %r") % k)
1347 1347 e.append(r)
1348 1348 l.append(e)
1349 1349 l.sort()
1350 1350 return [e[-1] for e in l]
1351 1351
1352 1352 def _stringmatcher(pattern):
1353 1353 """
1354 1354 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1355 1355 returns the matcher name, pattern, and matcher function.
1356 1356 missing or unknown prefixes are treated as literal matches.
1357 1357
1358 1358 helper for tests:
1359 1359 >>> def test(pattern, *tests):
1360 1360 ... kind, pattern, matcher = _stringmatcher(pattern)
1361 1361 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1362 1362
1363 1363 exact matching (no prefix):
1364 1364 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1365 1365 ('literal', 'abcdefg', [False, False, True])
1366 1366
1367 1367 regex matching ('re:' prefix)
1368 1368 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1369 1369 ('re', 'a.+b', [False, False, True])
1370 1370
1371 1371 force exact matches ('literal:' prefix)
1372 1372 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1373 1373 ('literal', 're:foobar', [False, True])
1374 1374
1375 1375 unknown prefixes are ignored and treated as literals
1376 1376 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1377 1377 ('literal', 'foo:bar', [False, False, True])
1378 1378 """
1379 1379 if pattern.startswith('re:'):
1380 1380 pattern = pattern[3:]
1381 1381 try:
1382 1382 regex = re.compile(pattern)
1383 1383 except re.error, e:
1384 1384 raise error.ParseError(_('invalid regular expression: %s')
1385 1385 % e)
1386 1386 return 're', pattern, regex.search
1387 1387 elif pattern.startswith('literal:'):
1388 1388 pattern = pattern[8:]
1389 1389 return 'literal', pattern, pattern.__eq__
1390 1390
1391 1391 def _substringmatcher(pattern):
1392 1392 kind, pattern, matcher = _stringmatcher(pattern)
1393 1393 if kind == 'literal':
1394 1394 matcher = lambda s: pattern in s
1395 1395 return kind, pattern, matcher
1396 1396
1397 1397 def tag(repo, subset, x):
1398 1398 """``tag([name])``
1399 1399 The specified tag by name, or all tagged revisions if no name is given.
1400 1400 """
1401 1401 # i18n: "tag" is a keyword
1402 1402 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1403 1403 cl = repo.changelog
1404 1404 if args:
1405 1405 pattern = getstring(args[0],
1406 1406 # i18n: "tag" is a keyword
1407 1407 _('the argument to tag must be a string'))
1408 1408 kind, pattern, matcher = _stringmatcher(pattern)
1409 1409 if kind == 'literal':
1410 1410 # avoid resolving all tags
1411 1411 tn = repo._tagscache.tags.get(pattern, None)
1412 1412 if tn is None:
1413 1413 raise util.Abort(_("tag '%s' does not exist") % pattern)
1414 1414 s = set([repo[tn].rev()])
1415 1415 else:
1416 1416 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1417 1417 if not s:
1418 1418 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1419 1419 else:
1420 1420 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1421 1421 return [r for r in subset if r in s]
1422 1422
1423 1423 def tagged(repo, subset, x):
1424 1424 return tag(repo, subset, x)
1425 1425
1426 1426 def unstable(repo, subset, x):
1427 1427 """``unstable()``
1428 1428 Non-obsolete changesets with obsolete ancestors.
1429 1429 """
1430 1430 # i18n: "unstable" is a keyword
1431 1431 getargs(x, 0, 0, _("unstable takes no arguments"))
1432 1432 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1433 1433 return [r for r in subset if r in unstableset]
1434 1434
1435 1435
1436 1436 def user(repo, subset, x):
1437 1437 """``user(string)``
1438 1438 User name contains string. The match is case-insensitive.
1439 1439
1440 1440 If `string` starts with `re:`, the remainder of the string is treated as
1441 1441 a regular expression. To match a user that actually contains `re:`, use
1442 1442 the prefix `literal:`.
1443 1443 """
1444 1444 return author(repo, subset, x)
1445 1445
1446 1446 # for internal use
1447 1447 def _list(repo, subset, x):
1448 1448 s = getstring(x, "internal error")
1449 1449 if not s:
1450 1450 return []
1451 1451 if not isinstance(subset, set):
1452 1452 subset = set(subset)
1453 1453 ls = [repo[r].rev() for r in s.split('\0')]
1454 1454 return [r for r in ls if r in subset]
1455 1455
1456 1456 symbols = {
1457 1457 "adds": adds,
1458 1458 "all": getall,
1459 1459 "ancestor": ancestor,
1460 1460 "ancestors": ancestors,
1461 1461 "_firstancestors": _firstancestors,
1462 1462 "author": author,
1463 1463 "bisect": bisect,
1464 1464 "bisected": bisected,
1465 1465 "bookmark": bookmark,
1466 1466 "branch": branch,
1467 1467 "children": children,
1468 1468 "closed": closed,
1469 1469 "contains": contains,
1470 1470 "converted": converted,
1471 1471 "date": date,
1472 1472 "desc": desc,
1473 1473 "descendants": descendants,
1474 1474 "_firstdescendants": _firstdescendants,
1475 1475 "destination": destination,
1476 1476 "draft": draft,
1477 1477 "extinct": extinct,
1478 1478 "extra": extra,
1479 1479 "file": hasfile,
1480 1480 "filelog": filelog,
1481 1481 "first": first,
1482 1482 "follow": follow,
1483 1483 "_followfirst": _followfirst,
1484 1484 "grep": grep,
1485 1485 "head": head,
1486 1486 "heads": heads,
1487 1487 "id": node_,
1488 1488 "keyword": keyword,
1489 1489 "last": last,
1490 1490 "limit": limit,
1491 1491 "_matchfiles": _matchfiles,
1492 1492 "max": maxrev,
1493 1493 "merge": merge,
1494 1494 "min": minrev,
1495 1495 "modifies": modifies,
1496 1496 "obsolete": obsolete,
1497 1497 "origin": origin,
1498 1498 "outgoing": outgoing,
1499 1499 "p1": p1,
1500 1500 "p2": p2,
1501 1501 "parents": parents,
1502 1502 "present": present,
1503 1503 "public": public,
1504 1504 "remote": remote,
1505 1505 "removes": removes,
1506 1506 "rev": rev,
1507 1507 "reverse": reverse,
1508 1508 "roots": roots,
1509 1509 "sort": sort,
1510 1510 "secret": secret,
1511 1511 "matching": matching,
1512 1512 "tag": tag,
1513 1513 "tagged": tagged,
1514 1514 "user": user,
1515 1515 "unstable": unstable,
1516 1516 "_list": _list,
1517 1517 }
1518 1518
1519 1519 methods = {
1520 1520 "range": rangeset,
1521 1521 "dagrange": dagrange,
1522 1522 "string": stringset,
1523 1523 "symbol": symbolset,
1524 1524 "and": andset,
1525 1525 "or": orset,
1526 1526 "not": notset,
1527 1527 "list": listset,
1528 1528 "func": func,
1529 1529 "ancestor": ancestorspec,
1530 1530 "parent": parentspec,
1531 1531 "parentpost": p1,
1532 1532 }
1533 1533
1534 1534 def optimize(x, small):
1535 1535 if x is None:
1536 1536 return 0, x
1537 1537
1538 1538 smallbonus = 1
1539 1539 if small:
1540 1540 smallbonus = .5
1541 1541
1542 1542 op = x[0]
1543 1543 if op == 'minus':
1544 1544 return optimize(('and', x[1], ('not', x[2])), small)
1545 1545 elif op == 'dagrangepre':
1546 1546 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1547 1547 elif op == 'dagrangepost':
1548 1548 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1549 1549 elif op == 'rangepre':
1550 1550 return optimize(('range', ('string', '0'), x[1]), small)
1551 1551 elif op == 'rangepost':
1552 1552 return optimize(('range', x[1], ('string', 'tip')), small)
1553 1553 elif op == 'negate':
1554 1554 return optimize(('string',
1555 1555 '-' + getstring(x[1], _("can't negate that"))), small)
1556 1556 elif op in 'string symbol negate':
1557 1557 return smallbonus, x # single revisions are small
1558 1558 elif op == 'and':
1559 1559 wa, ta = optimize(x[1], True)
1560 1560 wb, tb = optimize(x[2], True)
1561 1561 w = min(wa, wb)
1562 1562 if wa > wb:
1563 1563 return w, (op, tb, ta)
1564 1564 return w, (op, ta, tb)
1565 1565 elif op == 'or':
1566 1566 wa, ta = optimize(x[1], False)
1567 1567 wb, tb = optimize(x[2], False)
1568 1568 if wb < wa:
1569 1569 wb, wa = wa, wb
1570 1570 return max(wa, wb), (op, ta, tb)
1571 1571 elif op == 'not':
1572 1572 o = optimize(x[1], not small)
1573 1573 return o[0], (op, o[1])
1574 1574 elif op == 'parentpost':
1575 1575 o = optimize(x[1], small)
1576 1576 return o[0], (op, o[1])
1577 1577 elif op == 'group':
1578 1578 return optimize(x[1], small)
1579 1579 elif op in 'dagrange range list parent ancestorspec':
1580 1580 if op == 'parent':
1581 1581 # x^:y means (x^) : y, not x ^ (:y)
1582 1582 post = ('parentpost', x[1])
1583 1583 if x[2][0] == 'dagrangepre':
1584 1584 return optimize(('dagrange', post, x[2][1]), small)
1585 1585 elif x[2][0] == 'rangepre':
1586 1586 return optimize(('range', post, x[2][1]), small)
1587 1587
1588 1588 wa, ta = optimize(x[1], small)
1589 1589 wb, tb = optimize(x[2], small)
1590 1590 return wa + wb, (op, ta, tb)
1591 1591 elif op == 'func':
1592 1592 f = getstring(x[1], _("not a symbol"))
1593 1593 wa, ta = optimize(x[2], small)
1594 1594 if f in ("author branch closed date desc file grep keyword "
1595 1595 "outgoing user"):
1596 1596 w = 10 # slow
1597 1597 elif f in "modifies adds removes":
1598 1598 w = 30 # slower
1599 1599 elif f == "contains":
1600 1600 w = 100 # very slow
1601 1601 elif f == "ancestor":
1602 1602 w = 1 * smallbonus
1603 1603 elif f in "reverse limit first":
1604 1604 w = 0
1605 1605 elif f in "sort":
1606 1606 w = 10 # assume most sorts look at changelog
1607 1607 else:
1608 1608 w = 1
1609 1609 return w + wa, (op, x[1], ta)
1610 1610 return 1, x
1611 1611
1612 1612 _aliasarg = ('func', ('symbol', '_aliasarg'))
1613 1613 def _getaliasarg(tree):
1614 1614 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1615 1615 return X, None otherwise.
1616 1616 """
1617 1617 if (len(tree) == 3 and tree[:2] == _aliasarg
1618 1618 and tree[2][0] == 'string'):
1619 1619 return tree[2][1]
1620 1620 return None
1621 1621
1622 1622 def _checkaliasarg(tree, known=None):
1623 1623 """Check tree contains no _aliasarg construct or only ones which
1624 1624 value is in known. Used to avoid alias placeholders injection.
1625 1625 """
1626 1626 if isinstance(tree, tuple):
1627 1627 arg = _getaliasarg(tree)
1628 1628 if arg is not None and (not known or arg not in known):
1629 1629 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1630 1630 for t in tree:
1631 1631 _checkaliasarg(t, known)
1632 1632
1633 1633 class revsetalias(object):
1634 1634 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1635 1635 args = None
1636 1636
1637 1637 def __init__(self, name, value):
1638 1638 '''Aliases like:
1639 1639
1640 1640 h = heads(default)
1641 1641 b($1) = ancestors($1) - ancestors(default)
1642 1642 '''
1643 1643 m = self.funcre.search(name)
1644 1644 if m:
1645 1645 self.name = m.group(1)
1646 1646 self.tree = ('func', ('symbol', m.group(1)))
1647 1647 self.args = [x.strip() for x in m.group(2).split(',')]
1648 1648 for arg in self.args:
1649 1649 # _aliasarg() is an unknown symbol only used separate
1650 1650 # alias argument placeholders from regular strings.
1651 1651 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1652 1652 else:
1653 1653 self.name = name
1654 1654 self.tree = ('symbol', name)
1655 1655
1656 1656 self.replacement, pos = parse(value)
1657 1657 if pos != len(value):
1658 1658 raise error.ParseError(_('invalid token'), pos)
1659 1659 # Check for placeholder injection
1660 1660 _checkaliasarg(self.replacement, self.args)
1661 1661
1662 1662 def _getalias(aliases, tree):
1663 1663 """If tree looks like an unexpanded alias, return it. Return None
1664 1664 otherwise.
1665 1665 """
1666 1666 if isinstance(tree, tuple) and tree:
1667 1667 if tree[0] == 'symbol' and len(tree) == 2:
1668 1668 name = tree[1]
1669 1669 alias = aliases.get(name)
1670 1670 if alias and alias.args is None and alias.tree == tree:
1671 1671 return alias
1672 1672 if tree[0] == 'func' and len(tree) > 1:
1673 1673 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1674 1674 name = tree[1][1]
1675 1675 alias = aliases.get(name)
1676 1676 if alias and alias.args is not None and alias.tree == tree[:2]:
1677 1677 return alias
1678 1678 return None
1679 1679
1680 1680 def _expandargs(tree, args):
1681 1681 """Replace _aliasarg instances with the substitution value of the
1682 1682 same name in args, recursively.
1683 1683 """
1684 1684 if not tree or not isinstance(tree, tuple):
1685 1685 return tree
1686 1686 arg = _getaliasarg(tree)
1687 1687 if arg is not None:
1688 1688 return args[arg]
1689 1689 return tuple(_expandargs(t, args) for t in tree)
1690 1690
1691 1691 def _expandaliases(aliases, tree, expanding, cache):
1692 1692 """Expand aliases in tree, recursively.
1693 1693
1694 1694 'aliases' is a dictionary mapping user defined aliases to
1695 1695 revsetalias objects.
1696 1696 """
1697 1697 if not isinstance(tree, tuple):
1698 1698 # Do not expand raw strings
1699 1699 return tree
1700 1700 alias = _getalias(aliases, tree)
1701 1701 if alias is not None:
1702 1702 if alias in expanding:
1703 1703 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1704 1704 'detected') % alias.name)
1705 1705 expanding.append(alias)
1706 1706 if alias.name not in cache:
1707 1707 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1708 1708 expanding, cache)
1709 1709 result = cache[alias.name]
1710 1710 expanding.pop()
1711 1711 if alias.args is not None:
1712 1712 l = getlist(tree[2])
1713 1713 if len(l) != len(alias.args):
1714 1714 raise error.ParseError(
1715 1715 _('invalid number of arguments: %s') % len(l))
1716 1716 l = [_expandaliases(aliases, a, [], cache) for a in l]
1717 1717 result = _expandargs(result, dict(zip(alias.args, l)))
1718 1718 else:
1719 1719 result = tuple(_expandaliases(aliases, t, expanding, cache)
1720 1720 for t in tree)
1721 1721 return result
1722 1722
1723 1723 def findaliases(ui, tree):
1724 1724 _checkaliasarg(tree)
1725 1725 aliases = {}
1726 1726 for k, v in ui.configitems('revsetalias'):
1727 1727 alias = revsetalias(k, v)
1728 1728 aliases[alias.name] = alias
1729 1729 return _expandaliases(aliases, tree, [], {})
1730 1730
1731 1731 parse = parser.parser(tokenize, elements).parse
1732 1732
1733 1733 def match(ui, spec):
1734 1734 if not spec:
1735 1735 raise error.ParseError(_("empty query"))
1736 1736 tree, pos = parse(spec)
1737 1737 if (pos != len(spec)):
1738 1738 raise error.ParseError(_("invalid token"), pos)
1739 1739 if ui:
1740 1740 tree = findaliases(ui, tree)
1741 1741 weight, tree = optimize(tree, True)
1742 1742 def mfunc(repo, subset):
1743 1743 return getset(repo, subset, tree)
1744 1744 return mfunc
1745 1745
1746 1746 def formatspec(expr, *args):
1747 1747 '''
1748 1748 This is a convenience function for using revsets internally, and
1749 1749 escapes arguments appropriately. Aliases are intentionally ignored
1750 1750 so that intended expression behavior isn't accidentally subverted.
1751 1751
1752 1752 Supported arguments:
1753 1753
1754 1754 %r = revset expression, parenthesized
1755 1755 %d = int(arg), no quoting
1756 1756 %s = string(arg), escaped and single-quoted
1757 1757 %b = arg.branch(), escaped and single-quoted
1758 1758 %n = hex(arg), single-quoted
1759 1759 %% = a literal '%'
1760 1760
1761 1761 Prefixing the type with 'l' specifies a parenthesized list of that type.
1762 1762
1763 1763 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1764 1764 '(10 or 11):: and ((this()) or (that()))'
1765 1765 >>> formatspec('%d:: and not %d::', 10, 20)
1766 1766 '10:: and not 20::'
1767 1767 >>> formatspec('%ld or %ld', [], [1])
1768 1768 "_list('') or 1"
1769 1769 >>> formatspec('keyword(%s)', 'foo\\xe9')
1770 1770 "keyword('foo\\\\xe9')"
1771 1771 >>> b = lambda: 'default'
1772 1772 >>> b.branch = b
1773 1773 >>> formatspec('branch(%b)', b)
1774 1774 "branch('default')"
1775 1775 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1776 1776 "root(_list('a\\x00b\\x00c\\x00d'))"
1777 1777 '''
1778 1778
1779 1779 def quote(s):
1780 1780 return repr(str(s))
1781 1781
1782 1782 def argtype(c, arg):
1783 1783 if c == 'd':
1784 1784 return str(int(arg))
1785 1785 elif c == 's':
1786 1786 return quote(arg)
1787 1787 elif c == 'r':
1788 1788 parse(arg) # make sure syntax errors are confined
1789 1789 return '(%s)' % arg
1790 1790 elif c == 'n':
1791 1791 return quote(node.hex(arg))
1792 1792 elif c == 'b':
1793 1793 return quote(arg.branch())
1794 1794
1795 1795 def listexp(s, t):
1796 1796 l = len(s)
1797 1797 if l == 0:
1798 1798 return "_list('')"
1799 1799 elif l == 1:
1800 1800 return argtype(t, s[0])
1801 1801 elif t == 'd':
1802 1802 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1803 1803 elif t == 's':
1804 1804 return "_list('%s')" % "\0".join(s)
1805 1805 elif t == 'n':
1806 1806 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1807 1807 elif t == 'b':
1808 1808 return "_list('%s')" % "\0".join(a.branch() for a in s)
1809 1809
1810 1810 m = l // 2
1811 1811 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1812 1812
1813 1813 ret = ''
1814 1814 pos = 0
1815 1815 arg = 0
1816 1816 while pos < len(expr):
1817 1817 c = expr[pos]
1818 1818 if c == '%':
1819 1819 pos += 1
1820 1820 d = expr[pos]
1821 1821 if d == '%':
1822 1822 ret += d
1823 1823 elif d in 'dsnbr':
1824 1824 ret += argtype(d, args[arg])
1825 1825 arg += 1
1826 1826 elif d == 'l':
1827 1827 # a list of some type
1828 1828 pos += 1
1829 1829 d = expr[pos]
1830 1830 ret += listexp(list(args[arg]), d)
1831 1831 arg += 1
1832 1832 else:
1833 1833 raise util.Abort('unexpected revspec format character %s' % d)
1834 1834 else:
1835 1835 ret += c
1836 1836 pos += 1
1837 1837
1838 1838 return ret
1839 1839
1840 1840 def prettyformat(tree):
1841 1841 def _prettyformat(tree, level, lines):
1842 1842 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1843 1843 lines.append((level, str(tree)))
1844 1844 else:
1845 1845 lines.append((level, '(%s' % tree[0]))
1846 1846 for s in tree[1:]:
1847 1847 _prettyformat(s, level + 1, lines)
1848 1848 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1849 1849
1850 1850 lines = []
1851 1851 _prettyformat(tree, 0, lines)
1852 1852 output = '\n'.join((' '*l + s) for l, s in lines)
1853 1853 return output
1854 1854
1855 1855 # tell hggettext to extract docstrings from these functions:
1856 1856 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now