##// END OF EJS Templates
revpair: smartset compatibility...
Pierre-Yves David -
r20862:97b2f26d default
parent child Browse files
Show More
@@ -1,932 +1,945 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 from mercurial.node import nullrev
9 from mercurial.node import nullrev
10 import util, error, osutil, revset, similar, encoding, phases, parsers
10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 import pathutil
11 import pathutil
12 import match as matchmod
12 import match as matchmod
13 import os, errno, re, glob
13 import os, errno, re, glob
14
14
15 if os.name == 'nt':
15 if os.name == 'nt':
16 import scmwindows as scmplatform
16 import scmwindows as scmplatform
17 else:
17 else:
18 import scmposix as scmplatform
18 import scmposix as scmplatform
19
19
20 systemrcpath = scmplatform.systemrcpath
20 systemrcpath = scmplatform.systemrcpath
21 userrcpath = scmplatform.userrcpath
21 userrcpath = scmplatform.userrcpath
22
22
23 def itersubrepos(ctx1, ctx2):
23 def itersubrepos(ctx1, ctx2):
24 """find subrepos in ctx1 or ctx2"""
24 """find subrepos in ctx1 or ctx2"""
25 # Create a (subpath, ctx) mapping where we prefer subpaths from
25 # Create a (subpath, ctx) mapping where we prefer subpaths from
26 # ctx1. The subpaths from ctx2 are important when the .hgsub file
26 # ctx1. The subpaths from ctx2 are important when the .hgsub file
27 # has been modified (in ctx2) but not yet committed (in ctx1).
27 # has been modified (in ctx2) but not yet committed (in ctx1).
28 subpaths = dict.fromkeys(ctx2.substate, ctx2)
28 subpaths = dict.fromkeys(ctx2.substate, ctx2)
29 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
29 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
30 for subpath, ctx in sorted(subpaths.iteritems()):
30 for subpath, ctx in sorted(subpaths.iteritems()):
31 yield subpath, ctx.sub(subpath)
31 yield subpath, ctx.sub(subpath)
32
32
33 def nochangesfound(ui, repo, excluded=None):
33 def nochangesfound(ui, repo, excluded=None):
34 '''Report no changes for push/pull, excluded is None or a list of
34 '''Report no changes for push/pull, excluded is None or a list of
35 nodes excluded from the push/pull.
35 nodes excluded from the push/pull.
36 '''
36 '''
37 secretlist = []
37 secretlist = []
38 if excluded:
38 if excluded:
39 for n in excluded:
39 for n in excluded:
40 if n not in repo:
40 if n not in repo:
41 # discovery should not have included the filtered revision,
41 # discovery should not have included the filtered revision,
42 # we have to explicitly exclude it until discovery is cleanup.
42 # we have to explicitly exclude it until discovery is cleanup.
43 continue
43 continue
44 ctx = repo[n]
44 ctx = repo[n]
45 if ctx.phase() >= phases.secret and not ctx.extinct():
45 if ctx.phase() >= phases.secret and not ctx.extinct():
46 secretlist.append(n)
46 secretlist.append(n)
47
47
48 if secretlist:
48 if secretlist:
49 ui.status(_("no changes found (ignored %d secret changesets)\n")
49 ui.status(_("no changes found (ignored %d secret changesets)\n")
50 % len(secretlist))
50 % len(secretlist))
51 else:
51 else:
52 ui.status(_("no changes found\n"))
52 ui.status(_("no changes found\n"))
53
53
54 def checknewlabel(repo, lbl, kind):
54 def checknewlabel(repo, lbl, kind):
55 # Do not use the "kind" parameter in ui output.
55 # Do not use the "kind" parameter in ui output.
56 # It makes strings difficult to translate.
56 # It makes strings difficult to translate.
57 if lbl in ['tip', '.', 'null']:
57 if lbl in ['tip', '.', 'null']:
58 raise util.Abort(_("the name '%s' is reserved") % lbl)
58 raise util.Abort(_("the name '%s' is reserved") % lbl)
59 for c in (':', '\0', '\n', '\r'):
59 for c in (':', '\0', '\n', '\r'):
60 if c in lbl:
60 if c in lbl:
61 raise util.Abort(_("%r cannot be used in a name") % c)
61 raise util.Abort(_("%r cannot be used in a name") % c)
62 try:
62 try:
63 int(lbl)
63 int(lbl)
64 raise util.Abort(_("cannot use an integer as a name"))
64 raise util.Abort(_("cannot use an integer as a name"))
65 except ValueError:
65 except ValueError:
66 pass
66 pass
67
67
68 def checkfilename(f):
68 def checkfilename(f):
69 '''Check that the filename f is an acceptable filename for a tracked file'''
69 '''Check that the filename f is an acceptable filename for a tracked file'''
70 if '\r' in f or '\n' in f:
70 if '\r' in f or '\n' in f:
71 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
71 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
72
72
73 def checkportable(ui, f):
73 def checkportable(ui, f):
74 '''Check if filename f is portable and warn or abort depending on config'''
74 '''Check if filename f is portable and warn or abort depending on config'''
75 checkfilename(f)
75 checkfilename(f)
76 abort, warn = checkportabilityalert(ui)
76 abort, warn = checkportabilityalert(ui)
77 if abort or warn:
77 if abort or warn:
78 msg = util.checkwinfilename(f)
78 msg = util.checkwinfilename(f)
79 if msg:
79 if msg:
80 msg = "%s: %r" % (msg, f)
80 msg = "%s: %r" % (msg, f)
81 if abort:
81 if abort:
82 raise util.Abort(msg)
82 raise util.Abort(msg)
83 ui.warn(_("warning: %s\n") % msg)
83 ui.warn(_("warning: %s\n") % msg)
84
84
85 def checkportabilityalert(ui):
85 def checkportabilityalert(ui):
86 '''check if the user's config requests nothing, a warning, or abort for
86 '''check if the user's config requests nothing, a warning, or abort for
87 non-portable filenames'''
87 non-portable filenames'''
88 val = ui.config('ui', 'portablefilenames', 'warn')
88 val = ui.config('ui', 'portablefilenames', 'warn')
89 lval = val.lower()
89 lval = val.lower()
90 bval = util.parsebool(val)
90 bval = util.parsebool(val)
91 abort = os.name == 'nt' or lval == 'abort'
91 abort = os.name == 'nt' or lval == 'abort'
92 warn = bval or lval == 'warn'
92 warn = bval or lval == 'warn'
93 if bval is None and not (warn or abort or lval == 'ignore'):
93 if bval is None and not (warn or abort or lval == 'ignore'):
94 raise error.ConfigError(
94 raise error.ConfigError(
95 _("ui.portablefilenames value is invalid ('%s')") % val)
95 _("ui.portablefilenames value is invalid ('%s')") % val)
96 return abort, warn
96 return abort, warn
97
97
98 class casecollisionauditor(object):
98 class casecollisionauditor(object):
99 def __init__(self, ui, abort, dirstate):
99 def __init__(self, ui, abort, dirstate):
100 self._ui = ui
100 self._ui = ui
101 self._abort = abort
101 self._abort = abort
102 allfiles = '\0'.join(dirstate._map)
102 allfiles = '\0'.join(dirstate._map)
103 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
103 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
104 self._dirstate = dirstate
104 self._dirstate = dirstate
105 # The purpose of _newfiles is so that we don't complain about
105 # The purpose of _newfiles is so that we don't complain about
106 # case collisions if someone were to call this object with the
106 # case collisions if someone were to call this object with the
107 # same filename twice.
107 # same filename twice.
108 self._newfiles = set()
108 self._newfiles = set()
109
109
110 def __call__(self, f):
110 def __call__(self, f):
111 if f in self._newfiles:
111 if f in self._newfiles:
112 return
112 return
113 fl = encoding.lower(f)
113 fl = encoding.lower(f)
114 if fl in self._loweredfiles and f not in self._dirstate:
114 if fl in self._loweredfiles and f not in self._dirstate:
115 msg = _('possible case-folding collision for %s') % f
115 msg = _('possible case-folding collision for %s') % f
116 if self._abort:
116 if self._abort:
117 raise util.Abort(msg)
117 raise util.Abort(msg)
118 self._ui.warn(_("warning: %s\n") % msg)
118 self._ui.warn(_("warning: %s\n") % msg)
119 self._loweredfiles.add(fl)
119 self._loweredfiles.add(fl)
120 self._newfiles.add(f)
120 self._newfiles.add(f)
121
121
122 class abstractvfs(object):
122 class abstractvfs(object):
123 """Abstract base class; cannot be instantiated"""
123 """Abstract base class; cannot be instantiated"""
124
124
125 def __init__(self, *args, **kwargs):
125 def __init__(self, *args, **kwargs):
126 '''Prevent instantiation; don't call this from subclasses.'''
126 '''Prevent instantiation; don't call this from subclasses.'''
127 raise NotImplementedError('attempted instantiating ' + str(type(self)))
127 raise NotImplementedError('attempted instantiating ' + str(type(self)))
128
128
129 def tryread(self, path):
129 def tryread(self, path):
130 '''gracefully return an empty string for missing files'''
130 '''gracefully return an empty string for missing files'''
131 try:
131 try:
132 return self.read(path)
132 return self.read(path)
133 except IOError, inst:
133 except IOError, inst:
134 if inst.errno != errno.ENOENT:
134 if inst.errno != errno.ENOENT:
135 raise
135 raise
136 return ""
136 return ""
137
137
138 def open(self, path, mode="r", text=False, atomictemp=False):
138 def open(self, path, mode="r", text=False, atomictemp=False):
139 self.open = self.__call__
139 self.open = self.__call__
140 return self.__call__(path, mode, text, atomictemp)
140 return self.__call__(path, mode, text, atomictemp)
141
141
142 def read(self, path):
142 def read(self, path):
143 fp = self(path, 'rb')
143 fp = self(path, 'rb')
144 try:
144 try:
145 return fp.read()
145 return fp.read()
146 finally:
146 finally:
147 fp.close()
147 fp.close()
148
148
149 def write(self, path, data):
149 def write(self, path, data):
150 fp = self(path, 'wb')
150 fp = self(path, 'wb')
151 try:
151 try:
152 return fp.write(data)
152 return fp.write(data)
153 finally:
153 finally:
154 fp.close()
154 fp.close()
155
155
156 def append(self, path, data):
156 def append(self, path, data):
157 fp = self(path, 'ab')
157 fp = self(path, 'ab')
158 try:
158 try:
159 return fp.write(data)
159 return fp.write(data)
160 finally:
160 finally:
161 fp.close()
161 fp.close()
162
162
163 def chmod(self, path, mode):
163 def chmod(self, path, mode):
164 return os.chmod(self.join(path), mode)
164 return os.chmod(self.join(path), mode)
165
165
166 def exists(self, path=None):
166 def exists(self, path=None):
167 return os.path.exists(self.join(path))
167 return os.path.exists(self.join(path))
168
168
169 def fstat(self, fp):
169 def fstat(self, fp):
170 return util.fstat(fp)
170 return util.fstat(fp)
171
171
172 def isdir(self, path=None):
172 def isdir(self, path=None):
173 return os.path.isdir(self.join(path))
173 return os.path.isdir(self.join(path))
174
174
175 def isfile(self, path=None):
175 def isfile(self, path=None):
176 return os.path.isfile(self.join(path))
176 return os.path.isfile(self.join(path))
177
177
178 def islink(self, path=None):
178 def islink(self, path=None):
179 return os.path.islink(self.join(path))
179 return os.path.islink(self.join(path))
180
180
181 def lstat(self, path=None):
181 def lstat(self, path=None):
182 return os.lstat(self.join(path))
182 return os.lstat(self.join(path))
183
183
184 def makedir(self, path=None, notindexed=True):
184 def makedir(self, path=None, notindexed=True):
185 return util.makedir(self.join(path), notindexed)
185 return util.makedir(self.join(path), notindexed)
186
186
187 def makedirs(self, path=None, mode=None):
187 def makedirs(self, path=None, mode=None):
188 return util.makedirs(self.join(path), mode)
188 return util.makedirs(self.join(path), mode)
189
189
190 def makelock(self, info, path):
190 def makelock(self, info, path):
191 return util.makelock(info, self.join(path))
191 return util.makelock(info, self.join(path))
192
192
193 def mkdir(self, path=None):
193 def mkdir(self, path=None):
194 return os.mkdir(self.join(path))
194 return os.mkdir(self.join(path))
195
195
196 def readdir(self, path=None, stat=None, skip=None):
196 def readdir(self, path=None, stat=None, skip=None):
197 return osutil.listdir(self.join(path), stat, skip)
197 return osutil.listdir(self.join(path), stat, skip)
198
198
199 def readlock(self, path):
199 def readlock(self, path):
200 return util.readlock(self.join(path))
200 return util.readlock(self.join(path))
201
201
202 def rename(self, src, dst):
202 def rename(self, src, dst):
203 return util.rename(self.join(src), self.join(dst))
203 return util.rename(self.join(src), self.join(dst))
204
204
205 def readlink(self, path):
205 def readlink(self, path):
206 return os.readlink(self.join(path))
206 return os.readlink(self.join(path))
207
207
208 def setflags(self, path, l, x):
208 def setflags(self, path, l, x):
209 return util.setflags(self.join(path), l, x)
209 return util.setflags(self.join(path), l, x)
210
210
211 def stat(self, path=None):
211 def stat(self, path=None):
212 return os.stat(self.join(path))
212 return os.stat(self.join(path))
213
213
214 def unlink(self, path=None):
214 def unlink(self, path=None):
215 return util.unlink(self.join(path))
215 return util.unlink(self.join(path))
216
216
217 def utime(self, path=None, t=None):
217 def utime(self, path=None, t=None):
218 return os.utime(self.join(path), t)
218 return os.utime(self.join(path), t)
219
219
220 class vfs(abstractvfs):
220 class vfs(abstractvfs):
221 '''Operate files relative to a base directory
221 '''Operate files relative to a base directory
222
222
223 This class is used to hide the details of COW semantics and
223 This class is used to hide the details of COW semantics and
224 remote file access from higher level code.
224 remote file access from higher level code.
225 '''
225 '''
226 def __init__(self, base, audit=True, expandpath=False, realpath=False):
226 def __init__(self, base, audit=True, expandpath=False, realpath=False):
227 if expandpath:
227 if expandpath:
228 base = util.expandpath(base)
228 base = util.expandpath(base)
229 if realpath:
229 if realpath:
230 base = os.path.realpath(base)
230 base = os.path.realpath(base)
231 self.base = base
231 self.base = base
232 self._setmustaudit(audit)
232 self._setmustaudit(audit)
233 self.createmode = None
233 self.createmode = None
234 self._trustnlink = None
234 self._trustnlink = None
235
235
236 def _getmustaudit(self):
236 def _getmustaudit(self):
237 return self._audit
237 return self._audit
238
238
239 def _setmustaudit(self, onoff):
239 def _setmustaudit(self, onoff):
240 self._audit = onoff
240 self._audit = onoff
241 if onoff:
241 if onoff:
242 self.audit = pathutil.pathauditor(self.base)
242 self.audit = pathutil.pathauditor(self.base)
243 else:
243 else:
244 self.audit = util.always
244 self.audit = util.always
245
245
246 mustaudit = property(_getmustaudit, _setmustaudit)
246 mustaudit = property(_getmustaudit, _setmustaudit)
247
247
248 @util.propertycache
248 @util.propertycache
249 def _cansymlink(self):
249 def _cansymlink(self):
250 return util.checklink(self.base)
250 return util.checklink(self.base)
251
251
252 @util.propertycache
252 @util.propertycache
253 def _chmod(self):
253 def _chmod(self):
254 return util.checkexec(self.base)
254 return util.checkexec(self.base)
255
255
256 def _fixfilemode(self, name):
256 def _fixfilemode(self, name):
257 if self.createmode is None or not self._chmod:
257 if self.createmode is None or not self._chmod:
258 return
258 return
259 os.chmod(name, self.createmode & 0666)
259 os.chmod(name, self.createmode & 0666)
260
260
261 def __call__(self, path, mode="r", text=False, atomictemp=False):
261 def __call__(self, path, mode="r", text=False, atomictemp=False):
262 if self._audit:
262 if self._audit:
263 r = util.checkosfilename(path)
263 r = util.checkosfilename(path)
264 if r:
264 if r:
265 raise util.Abort("%s: %r" % (r, path))
265 raise util.Abort("%s: %r" % (r, path))
266 self.audit(path)
266 self.audit(path)
267 f = self.join(path)
267 f = self.join(path)
268
268
269 if not text and "b" not in mode:
269 if not text and "b" not in mode:
270 mode += "b" # for that other OS
270 mode += "b" # for that other OS
271
271
272 nlink = -1
272 nlink = -1
273 if mode not in ('r', 'rb'):
273 if mode not in ('r', 'rb'):
274 dirname, basename = util.split(f)
274 dirname, basename = util.split(f)
275 # If basename is empty, then the path is malformed because it points
275 # If basename is empty, then the path is malformed because it points
276 # to a directory. Let the posixfile() call below raise IOError.
276 # to a directory. Let the posixfile() call below raise IOError.
277 if basename:
277 if basename:
278 if atomictemp:
278 if atomictemp:
279 util.ensuredirs(dirname, self.createmode)
279 util.ensuredirs(dirname, self.createmode)
280 return util.atomictempfile(f, mode, self.createmode)
280 return util.atomictempfile(f, mode, self.createmode)
281 try:
281 try:
282 if 'w' in mode:
282 if 'w' in mode:
283 util.unlink(f)
283 util.unlink(f)
284 nlink = 0
284 nlink = 0
285 else:
285 else:
286 # nlinks() may behave differently for files on Windows
286 # nlinks() may behave differently for files on Windows
287 # shares if the file is open.
287 # shares if the file is open.
288 fd = util.posixfile(f)
288 fd = util.posixfile(f)
289 nlink = util.nlinks(f)
289 nlink = util.nlinks(f)
290 if nlink < 1:
290 if nlink < 1:
291 nlink = 2 # force mktempcopy (issue1922)
291 nlink = 2 # force mktempcopy (issue1922)
292 fd.close()
292 fd.close()
293 except (OSError, IOError), e:
293 except (OSError, IOError), e:
294 if e.errno != errno.ENOENT:
294 if e.errno != errno.ENOENT:
295 raise
295 raise
296 nlink = 0
296 nlink = 0
297 util.ensuredirs(dirname, self.createmode)
297 util.ensuredirs(dirname, self.createmode)
298 if nlink > 0:
298 if nlink > 0:
299 if self._trustnlink is None:
299 if self._trustnlink is None:
300 self._trustnlink = nlink > 1 or util.checknlink(f)
300 self._trustnlink = nlink > 1 or util.checknlink(f)
301 if nlink > 1 or not self._trustnlink:
301 if nlink > 1 or not self._trustnlink:
302 util.rename(util.mktempcopy(f), f)
302 util.rename(util.mktempcopy(f), f)
303 fp = util.posixfile(f, mode)
303 fp = util.posixfile(f, mode)
304 if nlink == 0:
304 if nlink == 0:
305 self._fixfilemode(f)
305 self._fixfilemode(f)
306 return fp
306 return fp
307
307
308 def symlink(self, src, dst):
308 def symlink(self, src, dst):
309 self.audit(dst)
309 self.audit(dst)
310 linkname = self.join(dst)
310 linkname = self.join(dst)
311 try:
311 try:
312 os.unlink(linkname)
312 os.unlink(linkname)
313 except OSError:
313 except OSError:
314 pass
314 pass
315
315
316 util.ensuredirs(os.path.dirname(linkname), self.createmode)
316 util.ensuredirs(os.path.dirname(linkname), self.createmode)
317
317
318 if self._cansymlink:
318 if self._cansymlink:
319 try:
319 try:
320 os.symlink(src, linkname)
320 os.symlink(src, linkname)
321 except OSError, err:
321 except OSError, err:
322 raise OSError(err.errno, _('could not symlink to %r: %s') %
322 raise OSError(err.errno, _('could not symlink to %r: %s') %
323 (src, err.strerror), linkname)
323 (src, err.strerror), linkname)
324 else:
324 else:
325 self.write(dst, src)
325 self.write(dst, src)
326
326
327 def join(self, path):
327 def join(self, path):
328 if path:
328 if path:
329 return os.path.join(self.base, path)
329 return os.path.join(self.base, path)
330 else:
330 else:
331 return self.base
331 return self.base
332
332
333 opener = vfs
333 opener = vfs
334
334
335 class auditvfs(object):
335 class auditvfs(object):
336 def __init__(self, vfs):
336 def __init__(self, vfs):
337 self.vfs = vfs
337 self.vfs = vfs
338
338
339 def _getmustaudit(self):
339 def _getmustaudit(self):
340 return self.vfs.mustaudit
340 return self.vfs.mustaudit
341
341
342 def _setmustaudit(self, onoff):
342 def _setmustaudit(self, onoff):
343 self.vfs.mustaudit = onoff
343 self.vfs.mustaudit = onoff
344
344
345 mustaudit = property(_getmustaudit, _setmustaudit)
345 mustaudit = property(_getmustaudit, _setmustaudit)
346
346
347 class filtervfs(abstractvfs, auditvfs):
347 class filtervfs(abstractvfs, auditvfs):
348 '''Wrapper vfs for filtering filenames with a function.'''
348 '''Wrapper vfs for filtering filenames with a function.'''
349
349
350 def __init__(self, vfs, filter):
350 def __init__(self, vfs, filter):
351 auditvfs.__init__(self, vfs)
351 auditvfs.__init__(self, vfs)
352 self._filter = filter
352 self._filter = filter
353
353
354 def __call__(self, path, *args, **kwargs):
354 def __call__(self, path, *args, **kwargs):
355 return self.vfs(self._filter(path), *args, **kwargs)
355 return self.vfs(self._filter(path), *args, **kwargs)
356
356
357 def join(self, path):
357 def join(self, path):
358 if path:
358 if path:
359 return self.vfs.join(self._filter(path))
359 return self.vfs.join(self._filter(path))
360 else:
360 else:
361 return self.vfs.join(path)
361 return self.vfs.join(path)
362
362
363 filteropener = filtervfs
363 filteropener = filtervfs
364
364
365 class readonlyvfs(abstractvfs, auditvfs):
365 class readonlyvfs(abstractvfs, auditvfs):
366 '''Wrapper vfs preventing any writing.'''
366 '''Wrapper vfs preventing any writing.'''
367
367
368 def __init__(self, vfs):
368 def __init__(self, vfs):
369 auditvfs.__init__(self, vfs)
369 auditvfs.__init__(self, vfs)
370
370
371 def __call__(self, path, mode='r', *args, **kw):
371 def __call__(self, path, mode='r', *args, **kw):
372 if mode not in ('r', 'rb'):
372 if mode not in ('r', 'rb'):
373 raise util.Abort('this vfs is read only')
373 raise util.Abort('this vfs is read only')
374 return self.vfs(path, mode, *args, **kw)
374 return self.vfs(path, mode, *args, **kw)
375
375
376
376
377 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
377 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
378 '''yield every hg repository under path, always recursively.
378 '''yield every hg repository under path, always recursively.
379 The recurse flag will only control recursion into repo working dirs'''
379 The recurse flag will only control recursion into repo working dirs'''
380 def errhandler(err):
380 def errhandler(err):
381 if err.filename == path:
381 if err.filename == path:
382 raise err
382 raise err
383 samestat = getattr(os.path, 'samestat', None)
383 samestat = getattr(os.path, 'samestat', None)
384 if followsym and samestat is not None:
384 if followsym and samestat is not None:
385 def adddir(dirlst, dirname):
385 def adddir(dirlst, dirname):
386 match = False
386 match = False
387 dirstat = os.stat(dirname)
387 dirstat = os.stat(dirname)
388 for lstdirstat in dirlst:
388 for lstdirstat in dirlst:
389 if samestat(dirstat, lstdirstat):
389 if samestat(dirstat, lstdirstat):
390 match = True
390 match = True
391 break
391 break
392 if not match:
392 if not match:
393 dirlst.append(dirstat)
393 dirlst.append(dirstat)
394 return not match
394 return not match
395 else:
395 else:
396 followsym = False
396 followsym = False
397
397
398 if (seen_dirs is None) and followsym:
398 if (seen_dirs is None) and followsym:
399 seen_dirs = []
399 seen_dirs = []
400 adddir(seen_dirs, path)
400 adddir(seen_dirs, path)
401 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
401 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
402 dirs.sort()
402 dirs.sort()
403 if '.hg' in dirs:
403 if '.hg' in dirs:
404 yield root # found a repository
404 yield root # found a repository
405 qroot = os.path.join(root, '.hg', 'patches')
405 qroot = os.path.join(root, '.hg', 'patches')
406 if os.path.isdir(os.path.join(qroot, '.hg')):
406 if os.path.isdir(os.path.join(qroot, '.hg')):
407 yield qroot # we have a patch queue repo here
407 yield qroot # we have a patch queue repo here
408 if recurse:
408 if recurse:
409 # avoid recursing inside the .hg directory
409 # avoid recursing inside the .hg directory
410 dirs.remove('.hg')
410 dirs.remove('.hg')
411 else:
411 else:
412 dirs[:] = [] # don't descend further
412 dirs[:] = [] # don't descend further
413 elif followsym:
413 elif followsym:
414 newdirs = []
414 newdirs = []
415 for d in dirs:
415 for d in dirs:
416 fname = os.path.join(root, d)
416 fname = os.path.join(root, d)
417 if adddir(seen_dirs, fname):
417 if adddir(seen_dirs, fname):
418 if os.path.islink(fname):
418 if os.path.islink(fname):
419 for hgname in walkrepos(fname, True, seen_dirs):
419 for hgname in walkrepos(fname, True, seen_dirs):
420 yield hgname
420 yield hgname
421 else:
421 else:
422 newdirs.append(d)
422 newdirs.append(d)
423 dirs[:] = newdirs
423 dirs[:] = newdirs
424
424
425 def osrcpath():
425 def osrcpath():
426 '''return default os-specific hgrc search path'''
426 '''return default os-specific hgrc search path'''
427 path = systemrcpath()
427 path = systemrcpath()
428 path.extend(userrcpath())
428 path.extend(userrcpath())
429 path = [os.path.normpath(f) for f in path]
429 path = [os.path.normpath(f) for f in path]
430 return path
430 return path
431
431
432 _rcpath = None
432 _rcpath = None
433
433
434 def rcpath():
434 def rcpath():
435 '''return hgrc search path. if env var HGRCPATH is set, use it.
435 '''return hgrc search path. if env var HGRCPATH is set, use it.
436 for each item in path, if directory, use files ending in .rc,
436 for each item in path, if directory, use files ending in .rc,
437 else use item.
437 else use item.
438 make HGRCPATH empty to only look in .hg/hgrc of current repo.
438 make HGRCPATH empty to only look in .hg/hgrc of current repo.
439 if no HGRCPATH, use default os-specific path.'''
439 if no HGRCPATH, use default os-specific path.'''
440 global _rcpath
440 global _rcpath
441 if _rcpath is None:
441 if _rcpath is None:
442 if 'HGRCPATH' in os.environ:
442 if 'HGRCPATH' in os.environ:
443 _rcpath = []
443 _rcpath = []
444 for p in os.environ['HGRCPATH'].split(os.pathsep):
444 for p in os.environ['HGRCPATH'].split(os.pathsep):
445 if not p:
445 if not p:
446 continue
446 continue
447 p = util.expandpath(p)
447 p = util.expandpath(p)
448 if os.path.isdir(p):
448 if os.path.isdir(p):
449 for f, kind in osutil.listdir(p):
449 for f, kind in osutil.listdir(p):
450 if f.endswith('.rc'):
450 if f.endswith('.rc'):
451 _rcpath.append(os.path.join(p, f))
451 _rcpath.append(os.path.join(p, f))
452 else:
452 else:
453 _rcpath.append(p)
453 _rcpath.append(p)
454 else:
454 else:
455 _rcpath = osrcpath()
455 _rcpath = osrcpath()
456 return _rcpath
456 return _rcpath
457
457
458 def revsingle(repo, revspec, default='.'):
458 def revsingle(repo, revspec, default='.'):
459 if not revspec and revspec != 0:
459 if not revspec and revspec != 0:
460 return repo[default]
460 return repo[default]
461
461
462 l = revrange(repo, [revspec])
462 l = revrange(repo, [revspec])
463 if len(l) < 1:
463 if len(l) < 1:
464 raise util.Abort(_('empty revision set'))
464 raise util.Abort(_('empty revision set'))
465 return repo[l[-1]]
465 return repo[l[-1]]
466
466
467 def revpair(repo, revs):
467 def revpair(repo, revs):
468 if not revs:
468 if not revs:
469 return repo.dirstate.p1(), None
469 return repo.dirstate.p1(), None
470
470
471 l = revrange(repo, revs)
471 l = revrange(repo, revs)
472
472
473 if len(l) == 0:
473 if not l:
474 first = second = None
475 elif l.isascending():
476 first = l.min()
477 second = l.max()
478 elif l.isdescending():
479 first = l.max()
480 second = l.min()
481 else:
482 l = list(l)
483 first = l[0]
484 second = l[-1]
485
486 if first is None:
474 raise util.Abort(_('empty revision range'))
487 raise util.Abort(_('empty revision range'))
475
488
476 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
489 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
477 return repo.lookup(l[0]), None
490 return repo.lookup(first), None
478
491
479 return repo.lookup(l[0]), repo.lookup(l[-1])
492 return repo.lookup(first), repo.lookup(second)
480
493
481 _revrangesep = ':'
494 _revrangesep = ':'
482
495
483 def revrange(repo, revs):
496 def revrange(repo, revs):
484 """Yield revision as strings from a list of revision specifications."""
497 """Yield revision as strings from a list of revision specifications."""
485
498
486 def revfix(repo, val, defval):
499 def revfix(repo, val, defval):
487 if not val and val != 0 and defval is not None:
500 if not val and val != 0 and defval is not None:
488 return defval
501 return defval
489 return repo[val].rev()
502 return repo[val].rev()
490
503
491 seen, l = set(), revset.baseset([])
504 seen, l = set(), revset.baseset([])
492 for spec in revs:
505 for spec in revs:
493 if l and not seen:
506 if l and not seen:
494 seen = set(l)
507 seen = set(l)
495 # attempt to parse old-style ranges first to deal with
508 # attempt to parse old-style ranges first to deal with
496 # things like old-tag which contain query metacharacters
509 # things like old-tag which contain query metacharacters
497 try:
510 try:
498 if isinstance(spec, int):
511 if isinstance(spec, int):
499 seen.add(spec)
512 seen.add(spec)
500 l = l + revset.baseset([spec])
513 l = l + revset.baseset([spec])
501 continue
514 continue
502
515
503 if _revrangesep in spec:
516 if _revrangesep in spec:
504 start, end = spec.split(_revrangesep, 1)
517 start, end = spec.split(_revrangesep, 1)
505 start = revfix(repo, start, 0)
518 start = revfix(repo, start, 0)
506 end = revfix(repo, end, len(repo) - 1)
519 end = revfix(repo, end, len(repo) - 1)
507 if end == nullrev and start < 0:
520 if end == nullrev and start < 0:
508 start = nullrev
521 start = nullrev
509 rangeiter = repo.changelog.revs(start, end)
522 rangeiter = repo.changelog.revs(start, end)
510 if not seen and not l:
523 if not seen and not l:
511 # by far the most common case: revs = ["-1:0"]
524 # by far the most common case: revs = ["-1:0"]
512 l = revset.baseset(rangeiter)
525 l = revset.baseset(rangeiter)
513 # defer syncing seen until next iteration
526 # defer syncing seen until next iteration
514 continue
527 continue
515 newrevs = set(rangeiter)
528 newrevs = set(rangeiter)
516 if seen:
529 if seen:
517 newrevs.difference_update(seen)
530 newrevs.difference_update(seen)
518 seen.update(newrevs)
531 seen.update(newrevs)
519 else:
532 else:
520 seen = newrevs
533 seen = newrevs
521 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
534 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
522 continue
535 continue
523 elif spec and spec in repo: # single unquoted rev
536 elif spec and spec in repo: # single unquoted rev
524 rev = revfix(repo, spec, None)
537 rev = revfix(repo, spec, None)
525 if rev in seen:
538 if rev in seen:
526 continue
539 continue
527 seen.add(rev)
540 seen.add(rev)
528 l = l + revset.baseset([rev])
541 l = l + revset.baseset([rev])
529 continue
542 continue
530 except error.RepoLookupError:
543 except error.RepoLookupError:
531 pass
544 pass
532
545
533 # fall through to new-style queries if old-style fails
546 # fall through to new-style queries if old-style fails
534 m = revset.match(repo.ui, spec, repo)
547 m = revset.match(repo.ui, spec, repo)
535 if seen or l:
548 if seen or l:
536 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
549 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
537 l = l + revset.baseset(dl)
550 l = l + revset.baseset(dl)
538 seen.update(dl)
551 seen.update(dl)
539 else:
552 else:
540 l = m(repo, revset.spanset(repo))
553 l = m(repo, revset.spanset(repo))
541
554
542 return l
555 return l
543
556
544 def expandpats(pats):
557 def expandpats(pats):
545 if not util.expandglobs:
558 if not util.expandglobs:
546 return list(pats)
559 return list(pats)
547 ret = []
560 ret = []
548 for p in pats:
561 for p in pats:
549 kind, name = matchmod._patsplit(p, None)
562 kind, name = matchmod._patsplit(p, None)
550 if kind is None:
563 if kind is None:
551 try:
564 try:
552 globbed = glob.glob(name)
565 globbed = glob.glob(name)
553 except re.error:
566 except re.error:
554 globbed = [name]
567 globbed = [name]
555 if globbed:
568 if globbed:
556 ret.extend(globbed)
569 ret.extend(globbed)
557 continue
570 continue
558 ret.append(p)
571 ret.append(p)
559 return ret
572 return ret
560
573
561 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
574 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
562 if pats == ("",):
575 if pats == ("",):
563 pats = []
576 pats = []
564 if not globbed and default == 'relpath':
577 if not globbed and default == 'relpath':
565 pats = expandpats(pats or [])
578 pats = expandpats(pats or [])
566
579
567 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
580 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
568 default)
581 default)
569 def badfn(f, msg):
582 def badfn(f, msg):
570 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
583 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 m.bad = badfn
584 m.bad = badfn
572 return m, pats
585 return m, pats
573
586
574 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
587 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
575 return matchandpats(ctx, pats, opts, globbed, default)[0]
588 return matchandpats(ctx, pats, opts, globbed, default)[0]
576
589
577 def matchall(repo):
590 def matchall(repo):
578 return matchmod.always(repo.root, repo.getcwd())
591 return matchmod.always(repo.root, repo.getcwd())
579
592
580 def matchfiles(repo, files):
593 def matchfiles(repo, files):
581 return matchmod.exact(repo.root, repo.getcwd(), files)
594 return matchmod.exact(repo.root, repo.getcwd(), files)
582
595
583 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
596 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
584 if dry_run is None:
597 if dry_run is None:
585 dry_run = opts.get('dry_run')
598 dry_run = opts.get('dry_run')
586 if similarity is None:
599 if similarity is None:
587 similarity = float(opts.get('similarity') or 0)
600 similarity = float(opts.get('similarity') or 0)
588 # we'd use status here, except handling of symlinks and ignore is tricky
601 # we'd use status here, except handling of symlinks and ignore is tricky
589 m = match(repo[None], pats, opts)
602 m = match(repo[None], pats, opts)
590 rejected = []
603 rejected = []
591 m.bad = lambda x, y: rejected.append(x)
604 m.bad = lambda x, y: rejected.append(x)
592
605
593 added, unknown, deleted, removed = _interestingfiles(repo, m)
606 added, unknown, deleted, removed = _interestingfiles(repo, m)
594
607
595 unknownset = set(unknown)
608 unknownset = set(unknown)
596 toprint = unknownset.copy()
609 toprint = unknownset.copy()
597 toprint.update(deleted)
610 toprint.update(deleted)
598 for abs in sorted(toprint):
611 for abs in sorted(toprint):
599 if repo.ui.verbose or not m.exact(abs):
612 if repo.ui.verbose or not m.exact(abs):
600 rel = m.rel(abs)
613 rel = m.rel(abs)
601 if abs in unknownset:
614 if abs in unknownset:
602 status = _('adding %s\n') % ((pats and rel) or abs)
615 status = _('adding %s\n') % ((pats and rel) or abs)
603 else:
616 else:
604 status = _('removing %s\n') % ((pats and rel) or abs)
617 status = _('removing %s\n') % ((pats and rel) or abs)
605 repo.ui.status(status)
618 repo.ui.status(status)
606
619
607 renames = _findrenames(repo, m, added + unknown, removed + deleted,
620 renames = _findrenames(repo, m, added + unknown, removed + deleted,
608 similarity)
621 similarity)
609
622
610 if not dry_run:
623 if not dry_run:
611 _markchanges(repo, unknown, deleted, renames)
624 _markchanges(repo, unknown, deleted, renames)
612
625
613 for f in rejected:
626 for f in rejected:
614 if f in m.files():
627 if f in m.files():
615 return 1
628 return 1
616 return 0
629 return 0
617
630
618 def marktouched(repo, files, similarity=0.0):
631 def marktouched(repo, files, similarity=0.0):
619 '''Assert that files have somehow been operated upon. files are relative to
632 '''Assert that files have somehow been operated upon. files are relative to
620 the repo root.'''
633 the repo root.'''
621 m = matchfiles(repo, files)
634 m = matchfiles(repo, files)
622 rejected = []
635 rejected = []
623 m.bad = lambda x, y: rejected.append(x)
636 m.bad = lambda x, y: rejected.append(x)
624
637
625 added, unknown, deleted, removed = _interestingfiles(repo, m)
638 added, unknown, deleted, removed = _interestingfiles(repo, m)
626
639
627 if repo.ui.verbose:
640 if repo.ui.verbose:
628 unknownset = set(unknown)
641 unknownset = set(unknown)
629 toprint = unknownset.copy()
642 toprint = unknownset.copy()
630 toprint.update(deleted)
643 toprint.update(deleted)
631 for abs in sorted(toprint):
644 for abs in sorted(toprint):
632 if abs in unknownset:
645 if abs in unknownset:
633 status = _('adding %s\n') % abs
646 status = _('adding %s\n') % abs
634 else:
647 else:
635 status = _('removing %s\n') % abs
648 status = _('removing %s\n') % abs
636 repo.ui.status(status)
649 repo.ui.status(status)
637
650
638 renames = _findrenames(repo, m, added + unknown, removed + deleted,
651 renames = _findrenames(repo, m, added + unknown, removed + deleted,
639 similarity)
652 similarity)
640
653
641 _markchanges(repo, unknown, deleted, renames)
654 _markchanges(repo, unknown, deleted, renames)
642
655
643 for f in rejected:
656 for f in rejected:
644 if f in m.files():
657 if f in m.files():
645 return 1
658 return 1
646 return 0
659 return 0
647
660
648 def _interestingfiles(repo, matcher):
661 def _interestingfiles(repo, matcher):
649 '''Walk dirstate with matcher, looking for files that addremove would care
662 '''Walk dirstate with matcher, looking for files that addremove would care
650 about.
663 about.
651
664
652 This is different from dirstate.status because it doesn't care about
665 This is different from dirstate.status because it doesn't care about
653 whether files are modified or clean.'''
666 whether files are modified or clean.'''
654 added, unknown, deleted, removed = [], [], [], []
667 added, unknown, deleted, removed = [], [], [], []
655 audit_path = pathutil.pathauditor(repo.root)
668 audit_path = pathutil.pathauditor(repo.root)
656
669
657 ctx = repo[None]
670 ctx = repo[None]
658 dirstate = repo.dirstate
671 dirstate = repo.dirstate
659 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
672 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
660 full=False)
673 full=False)
661 for abs, st in walkresults.iteritems():
674 for abs, st in walkresults.iteritems():
662 dstate = dirstate[abs]
675 dstate = dirstate[abs]
663 if dstate == '?' and audit_path.check(abs):
676 if dstate == '?' and audit_path.check(abs):
664 unknown.append(abs)
677 unknown.append(abs)
665 elif dstate != 'r' and not st:
678 elif dstate != 'r' and not st:
666 deleted.append(abs)
679 deleted.append(abs)
667 # for finding renames
680 # for finding renames
668 elif dstate == 'r':
681 elif dstate == 'r':
669 removed.append(abs)
682 removed.append(abs)
670 elif dstate == 'a':
683 elif dstate == 'a':
671 added.append(abs)
684 added.append(abs)
672
685
673 return added, unknown, deleted, removed
686 return added, unknown, deleted, removed
674
687
675 def _findrenames(repo, matcher, added, removed, similarity):
688 def _findrenames(repo, matcher, added, removed, similarity):
676 '''Find renames from removed files to added ones.'''
689 '''Find renames from removed files to added ones.'''
677 renames = {}
690 renames = {}
678 if similarity > 0:
691 if similarity > 0:
679 for old, new, score in similar.findrenames(repo, added, removed,
692 for old, new, score in similar.findrenames(repo, added, removed,
680 similarity):
693 similarity):
681 if (repo.ui.verbose or not matcher.exact(old)
694 if (repo.ui.verbose or not matcher.exact(old)
682 or not matcher.exact(new)):
695 or not matcher.exact(new)):
683 repo.ui.status(_('recording removal of %s as rename to %s '
696 repo.ui.status(_('recording removal of %s as rename to %s '
684 '(%d%% similar)\n') %
697 '(%d%% similar)\n') %
685 (matcher.rel(old), matcher.rel(new),
698 (matcher.rel(old), matcher.rel(new),
686 score * 100))
699 score * 100))
687 renames[new] = old
700 renames[new] = old
688 return renames
701 return renames
689
702
690 def _markchanges(repo, unknown, deleted, renames):
703 def _markchanges(repo, unknown, deleted, renames):
691 '''Marks the files in unknown as added, the files in deleted as removed,
704 '''Marks the files in unknown as added, the files in deleted as removed,
692 and the files in renames as copied.'''
705 and the files in renames as copied.'''
693 wctx = repo[None]
706 wctx = repo[None]
694 wlock = repo.wlock()
707 wlock = repo.wlock()
695 try:
708 try:
696 wctx.forget(deleted)
709 wctx.forget(deleted)
697 wctx.add(unknown)
710 wctx.add(unknown)
698 for new, old in renames.iteritems():
711 for new, old in renames.iteritems():
699 wctx.copy(old, new)
712 wctx.copy(old, new)
700 finally:
713 finally:
701 wlock.release()
714 wlock.release()
702
715
703 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
716 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
704 """Update the dirstate to reflect the intent of copying src to dst. For
717 """Update the dirstate to reflect the intent of copying src to dst. For
705 different reasons it might not end with dst being marked as copied from src.
718 different reasons it might not end with dst being marked as copied from src.
706 """
719 """
707 origsrc = repo.dirstate.copied(src) or src
720 origsrc = repo.dirstate.copied(src) or src
708 if dst == origsrc: # copying back a copy?
721 if dst == origsrc: # copying back a copy?
709 if repo.dirstate[dst] not in 'mn' and not dryrun:
722 if repo.dirstate[dst] not in 'mn' and not dryrun:
710 repo.dirstate.normallookup(dst)
723 repo.dirstate.normallookup(dst)
711 else:
724 else:
712 if repo.dirstate[origsrc] == 'a' and origsrc == src:
725 if repo.dirstate[origsrc] == 'a' and origsrc == src:
713 if not ui.quiet:
726 if not ui.quiet:
714 ui.warn(_("%s has not been committed yet, so no copy "
727 ui.warn(_("%s has not been committed yet, so no copy "
715 "data will be stored for %s.\n")
728 "data will be stored for %s.\n")
716 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
729 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
717 if repo.dirstate[dst] in '?r' and not dryrun:
730 if repo.dirstate[dst] in '?r' and not dryrun:
718 wctx.add([dst])
731 wctx.add([dst])
719 elif not dryrun:
732 elif not dryrun:
720 wctx.copy(origsrc, dst)
733 wctx.copy(origsrc, dst)
721
734
722 def readrequires(opener, supported):
735 def readrequires(opener, supported):
723 '''Reads and parses .hg/requires and checks if all entries found
736 '''Reads and parses .hg/requires and checks if all entries found
724 are in the list of supported features.'''
737 are in the list of supported features.'''
725 requirements = set(opener.read("requires").splitlines())
738 requirements = set(opener.read("requires").splitlines())
726 missings = []
739 missings = []
727 for r in requirements:
740 for r in requirements:
728 if r not in supported:
741 if r not in supported:
729 if not r or not r[0].isalnum():
742 if not r or not r[0].isalnum():
730 raise error.RequirementError(_(".hg/requires file is corrupt"))
743 raise error.RequirementError(_(".hg/requires file is corrupt"))
731 missings.append(r)
744 missings.append(r)
732 missings.sort()
745 missings.sort()
733 if missings:
746 if missings:
734 raise error.RequirementError(
747 raise error.RequirementError(
735 _("repository requires features unknown to this Mercurial: %s")
748 _("repository requires features unknown to this Mercurial: %s")
736 % " ".join(missings),
749 % " ".join(missings),
737 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
750 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
738 " for more information"))
751 " for more information"))
739 return requirements
752 return requirements
740
753
741 class filecachesubentry(object):
754 class filecachesubentry(object):
742 def __init__(self, path, stat):
755 def __init__(self, path, stat):
743 self.path = path
756 self.path = path
744 self.cachestat = None
757 self.cachestat = None
745 self._cacheable = None
758 self._cacheable = None
746
759
747 if stat:
760 if stat:
748 self.cachestat = filecachesubentry.stat(self.path)
761 self.cachestat = filecachesubentry.stat(self.path)
749
762
750 if self.cachestat:
763 if self.cachestat:
751 self._cacheable = self.cachestat.cacheable()
764 self._cacheable = self.cachestat.cacheable()
752 else:
765 else:
753 # None means we don't know yet
766 # None means we don't know yet
754 self._cacheable = None
767 self._cacheable = None
755
768
756 def refresh(self):
769 def refresh(self):
757 if self.cacheable():
770 if self.cacheable():
758 self.cachestat = filecachesubentry.stat(self.path)
771 self.cachestat = filecachesubentry.stat(self.path)
759
772
760 def cacheable(self):
773 def cacheable(self):
761 if self._cacheable is not None:
774 if self._cacheable is not None:
762 return self._cacheable
775 return self._cacheable
763
776
764 # we don't know yet, assume it is for now
777 # we don't know yet, assume it is for now
765 return True
778 return True
766
779
767 def changed(self):
780 def changed(self):
768 # no point in going further if we can't cache it
781 # no point in going further if we can't cache it
769 if not self.cacheable():
782 if not self.cacheable():
770 return True
783 return True
771
784
772 newstat = filecachesubentry.stat(self.path)
785 newstat = filecachesubentry.stat(self.path)
773
786
774 # we may not know if it's cacheable yet, check again now
787 # we may not know if it's cacheable yet, check again now
775 if newstat and self._cacheable is None:
788 if newstat and self._cacheable is None:
776 self._cacheable = newstat.cacheable()
789 self._cacheable = newstat.cacheable()
777
790
778 # check again
791 # check again
779 if not self._cacheable:
792 if not self._cacheable:
780 return True
793 return True
781
794
782 if self.cachestat != newstat:
795 if self.cachestat != newstat:
783 self.cachestat = newstat
796 self.cachestat = newstat
784 return True
797 return True
785 else:
798 else:
786 return False
799 return False
787
800
788 @staticmethod
801 @staticmethod
789 def stat(path):
802 def stat(path):
790 try:
803 try:
791 return util.cachestat(path)
804 return util.cachestat(path)
792 except OSError, e:
805 except OSError, e:
793 if e.errno != errno.ENOENT:
806 if e.errno != errno.ENOENT:
794 raise
807 raise
795
808
796 class filecacheentry(object):
809 class filecacheentry(object):
797 def __init__(self, paths, stat=True):
810 def __init__(self, paths, stat=True):
798 self._entries = []
811 self._entries = []
799 for path in paths:
812 for path in paths:
800 self._entries.append(filecachesubentry(path, stat))
813 self._entries.append(filecachesubentry(path, stat))
801
814
802 def changed(self):
815 def changed(self):
803 '''true if any entry has changed'''
816 '''true if any entry has changed'''
804 for entry in self._entries:
817 for entry in self._entries:
805 if entry.changed():
818 if entry.changed():
806 return True
819 return True
807 return False
820 return False
808
821
809 def refresh(self):
822 def refresh(self):
810 for entry in self._entries:
823 for entry in self._entries:
811 entry.refresh()
824 entry.refresh()
812
825
813 class filecache(object):
826 class filecache(object):
814 '''A property like decorator that tracks files under .hg/ for updates.
827 '''A property like decorator that tracks files under .hg/ for updates.
815
828
816 Records stat info when called in _filecache.
829 Records stat info when called in _filecache.
817
830
818 On subsequent calls, compares old stat info with new info, and recreates the
831 On subsequent calls, compares old stat info with new info, and recreates the
819 object when any of the files changes, updating the new stat info in
832 object when any of the files changes, updating the new stat info in
820 _filecache.
833 _filecache.
821
834
822 Mercurial either atomic renames or appends for files under .hg,
835 Mercurial either atomic renames or appends for files under .hg,
823 so to ensure the cache is reliable we need the filesystem to be able
836 so to ensure the cache is reliable we need the filesystem to be able
824 to tell us if a file has been replaced. If it can't, we fallback to
837 to tell us if a file has been replaced. If it can't, we fallback to
825 recreating the object on every call (essentially the same behaviour as
838 recreating the object on every call (essentially the same behaviour as
826 propertycache).
839 propertycache).
827
840
828 '''
841 '''
829 def __init__(self, *paths):
842 def __init__(self, *paths):
830 self.paths = paths
843 self.paths = paths
831
844
832 def join(self, obj, fname):
845 def join(self, obj, fname):
833 """Used to compute the runtime path of a cached file.
846 """Used to compute the runtime path of a cached file.
834
847
835 Users should subclass filecache and provide their own version of this
848 Users should subclass filecache and provide their own version of this
836 function to call the appropriate join function on 'obj' (an instance
849 function to call the appropriate join function on 'obj' (an instance
837 of the class that its member function was decorated).
850 of the class that its member function was decorated).
838 """
851 """
839 return obj.join(fname)
852 return obj.join(fname)
840
853
841 def __call__(self, func):
854 def __call__(self, func):
842 self.func = func
855 self.func = func
843 self.name = func.__name__
856 self.name = func.__name__
844 return self
857 return self
845
858
846 def __get__(self, obj, type=None):
859 def __get__(self, obj, type=None):
847 # do we need to check if the file changed?
860 # do we need to check if the file changed?
848 if self.name in obj.__dict__:
861 if self.name in obj.__dict__:
849 assert self.name in obj._filecache, self.name
862 assert self.name in obj._filecache, self.name
850 return obj.__dict__[self.name]
863 return obj.__dict__[self.name]
851
864
852 entry = obj._filecache.get(self.name)
865 entry = obj._filecache.get(self.name)
853
866
854 if entry:
867 if entry:
855 if entry.changed():
868 if entry.changed():
856 entry.obj = self.func(obj)
869 entry.obj = self.func(obj)
857 else:
870 else:
858 paths = [self.join(obj, path) for path in self.paths]
871 paths = [self.join(obj, path) for path in self.paths]
859
872
860 # We stat -before- creating the object so our cache doesn't lie if
873 # We stat -before- creating the object so our cache doesn't lie if
861 # a writer modified between the time we read and stat
874 # a writer modified between the time we read and stat
862 entry = filecacheentry(paths, True)
875 entry = filecacheentry(paths, True)
863 entry.obj = self.func(obj)
876 entry.obj = self.func(obj)
864
877
865 obj._filecache[self.name] = entry
878 obj._filecache[self.name] = entry
866
879
867 obj.__dict__[self.name] = entry.obj
880 obj.__dict__[self.name] = entry.obj
868 return entry.obj
881 return entry.obj
869
882
870 def __set__(self, obj, value):
883 def __set__(self, obj, value):
871 if self.name not in obj._filecache:
884 if self.name not in obj._filecache:
872 # we add an entry for the missing value because X in __dict__
885 # we add an entry for the missing value because X in __dict__
873 # implies X in _filecache
886 # implies X in _filecache
874 paths = [self.join(obj, path) for path in self.paths]
887 paths = [self.join(obj, path) for path in self.paths]
875 ce = filecacheentry(paths, False)
888 ce = filecacheentry(paths, False)
876 obj._filecache[self.name] = ce
889 obj._filecache[self.name] = ce
877 else:
890 else:
878 ce = obj._filecache[self.name]
891 ce = obj._filecache[self.name]
879
892
880 ce.obj = value # update cached copy
893 ce.obj = value # update cached copy
881 obj.__dict__[self.name] = value # update copy returned by obj.x
894 obj.__dict__[self.name] = value # update copy returned by obj.x
882
895
883 def __delete__(self, obj):
896 def __delete__(self, obj):
884 try:
897 try:
885 del obj.__dict__[self.name]
898 del obj.__dict__[self.name]
886 except KeyError:
899 except KeyError:
887 raise AttributeError(self.name)
900 raise AttributeError(self.name)
888
901
889 class dirs(object):
902 class dirs(object):
890 '''a multiset of directory names from a dirstate or manifest'''
903 '''a multiset of directory names from a dirstate or manifest'''
891
904
892 def __init__(self, map, skip=None):
905 def __init__(self, map, skip=None):
893 self._dirs = {}
906 self._dirs = {}
894 addpath = self.addpath
907 addpath = self.addpath
895 if util.safehasattr(map, 'iteritems') and skip is not None:
908 if util.safehasattr(map, 'iteritems') and skip is not None:
896 for f, s in map.iteritems():
909 for f, s in map.iteritems():
897 if s[0] != skip:
910 if s[0] != skip:
898 addpath(f)
911 addpath(f)
899 else:
912 else:
900 for f in map:
913 for f in map:
901 addpath(f)
914 addpath(f)
902
915
903 def addpath(self, path):
916 def addpath(self, path):
904 dirs = self._dirs
917 dirs = self._dirs
905 for base in finddirs(path):
918 for base in finddirs(path):
906 if base in dirs:
919 if base in dirs:
907 dirs[base] += 1
920 dirs[base] += 1
908 return
921 return
909 dirs[base] = 1
922 dirs[base] = 1
910
923
911 def delpath(self, path):
924 def delpath(self, path):
912 dirs = self._dirs
925 dirs = self._dirs
913 for base in finddirs(path):
926 for base in finddirs(path):
914 if dirs[base] > 1:
927 if dirs[base] > 1:
915 dirs[base] -= 1
928 dirs[base] -= 1
916 return
929 return
917 del dirs[base]
930 del dirs[base]
918
931
919 def __iter__(self):
932 def __iter__(self):
920 return self._dirs.iterkeys()
933 return self._dirs.iterkeys()
921
934
922 def __contains__(self, d):
935 def __contains__(self, d):
923 return d in self._dirs
936 return d in self._dirs
924
937
925 if util.safehasattr(parsers, 'dirs'):
938 if util.safehasattr(parsers, 'dirs'):
926 dirs = parsers.dirs
939 dirs = parsers.dirs
927
940
928 def finddirs(path):
941 def finddirs(path):
929 pos = path.rfind('/')
942 pos = path.rfind('/')
930 while pos != -1:
943 while pos != -1:
931 yield path[:pos]
944 yield path[:pos]
932 pos = path.rfind('/', 0, pos)
945 pos = path.rfind('/', 0, pos)
@@ -1,1061 +1,1098 b''
1 $ HGENCODING=utf-8
1 $ HGENCODING=utf-8
2 $ export HGENCODING
2 $ export HGENCODING
3
3
4 $ try() {
4 $ try() {
5 > hg debugrevspec --debug "$@"
5 > hg debugrevspec --debug "$@"
6 > }
6 > }
7
7
8 $ log() {
8 $ log() {
9 > hg log --template '{rev}\n' -r "$1"
9 > hg log --template '{rev}\n' -r "$1"
10 > }
10 > }
11
11
12 $ hg init repo
12 $ hg init repo
13 $ cd repo
13 $ cd repo
14
14
15 $ echo a > a
15 $ echo a > a
16 $ hg branch a
16 $ hg branch a
17 marked working directory as branch a
17 marked working directory as branch a
18 (branches are permanent and global, did you want a bookmark?)
18 (branches are permanent and global, did you want a bookmark?)
19 $ hg ci -Aqm0
19 $ hg ci -Aqm0
20
20
21 $ echo b > b
21 $ echo b > b
22 $ hg branch b
22 $ hg branch b
23 marked working directory as branch b
23 marked working directory as branch b
24 (branches are permanent and global, did you want a bookmark?)
24 (branches are permanent and global, did you want a bookmark?)
25 $ hg ci -Aqm1
25 $ hg ci -Aqm1
26
26
27 $ rm a
27 $ rm a
28 $ hg branch a-b-c-
28 $ hg branch a-b-c-
29 marked working directory as branch a-b-c-
29 marked working directory as branch a-b-c-
30 (branches are permanent and global, did you want a bookmark?)
30 (branches are permanent and global, did you want a bookmark?)
31 $ hg ci -Aqm2 -u Bob
31 $ hg ci -Aqm2 -u Bob
32
32
33 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
33 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
34 2
34 2
35 $ hg log -r "extra('branch')" --template '{rev}\n'
35 $ hg log -r "extra('branch')" --template '{rev}\n'
36 0
36 0
37 1
37 1
38 2
38 2
39 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
39 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
40 0 a
40 0 a
41 2 a-b-c-
41 2 a-b-c-
42
42
43 $ hg co 1
43 $ hg co 1
44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 $ hg branch +a+b+c+
45 $ hg branch +a+b+c+
46 marked working directory as branch +a+b+c+
46 marked working directory as branch +a+b+c+
47 (branches are permanent and global, did you want a bookmark?)
47 (branches are permanent and global, did you want a bookmark?)
48 $ hg ci -Aqm3
48 $ hg ci -Aqm3
49
49
50 $ hg co 2 # interleave
50 $ hg co 2 # interleave
51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 $ echo bb > b
52 $ echo bb > b
53 $ hg branch -- -a-b-c-
53 $ hg branch -- -a-b-c-
54 marked working directory as branch -a-b-c-
54 marked working directory as branch -a-b-c-
55 (branches are permanent and global, did you want a bookmark?)
55 (branches are permanent and global, did you want a bookmark?)
56 $ hg ci -Aqm4 -d "May 12 2005"
56 $ hg ci -Aqm4 -d "May 12 2005"
57
57
58 $ hg co 3
58 $ hg co 3
59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 $ hg branch !a/b/c/
60 $ hg branch !a/b/c/
61 marked working directory as branch !a/b/c/
61 marked working directory as branch !a/b/c/
62 (branches are permanent and global, did you want a bookmark?)
62 (branches are permanent and global, did you want a bookmark?)
63 $ hg ci -Aqm"5 bug"
63 $ hg ci -Aqm"5 bug"
64
64
65 $ hg merge 4
65 $ hg merge 4
66 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 (branch merge, don't forget to commit)
67 (branch merge, don't forget to commit)
68 $ hg branch _a_b_c_
68 $ hg branch _a_b_c_
69 marked working directory as branch _a_b_c_
69 marked working directory as branch _a_b_c_
70 (branches are permanent and global, did you want a bookmark?)
70 (branches are permanent and global, did you want a bookmark?)
71 $ hg ci -Aqm"6 issue619"
71 $ hg ci -Aqm"6 issue619"
72
72
73 $ hg branch .a.b.c.
73 $ hg branch .a.b.c.
74 marked working directory as branch .a.b.c.
74 marked working directory as branch .a.b.c.
75 (branches are permanent and global, did you want a bookmark?)
75 (branches are permanent and global, did you want a bookmark?)
76 $ hg ci -Aqm7
76 $ hg ci -Aqm7
77
77
78 $ hg branch all
78 $ hg branch all
79 marked working directory as branch all
79 marked working directory as branch all
80 (branches are permanent and global, did you want a bookmark?)
80 (branches are permanent and global, did you want a bookmark?)
81
81
82 $ hg co 4
82 $ hg co 4
83 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 $ hg branch Γ©
84 $ hg branch Γ©
85 marked working directory as branch \xc3\xa9 (esc)
85 marked working directory as branch \xc3\xa9 (esc)
86 (branches are permanent and global, did you want a bookmark?)
86 (branches are permanent and global, did you want a bookmark?)
87 $ hg ci -Aqm9
87 $ hg ci -Aqm9
88
88
89 $ hg tag -r6 1.0
89 $ hg tag -r6 1.0
90
90
91 $ hg clone --quiet -U -r 7 . ../remote1
91 $ hg clone --quiet -U -r 7 . ../remote1
92 $ hg clone --quiet -U -r 8 . ../remote2
92 $ hg clone --quiet -U -r 8 . ../remote2
93 $ echo "[paths]" >> .hg/hgrc
93 $ echo "[paths]" >> .hg/hgrc
94 $ echo "default = ../remote1" >> .hg/hgrc
94 $ echo "default = ../remote1" >> .hg/hgrc
95
95
96 names that should work without quoting
96 names that should work without quoting
97
97
98 $ try a
98 $ try a
99 ('symbol', 'a')
99 ('symbol', 'a')
100 0
100 0
101 $ try b-a
101 $ try b-a
102 (minus
102 (minus
103 ('symbol', 'b')
103 ('symbol', 'b')
104 ('symbol', 'a'))
104 ('symbol', 'a'))
105 1
105 1
106 $ try _a_b_c_
106 $ try _a_b_c_
107 ('symbol', '_a_b_c_')
107 ('symbol', '_a_b_c_')
108 6
108 6
109 $ try _a_b_c_-a
109 $ try _a_b_c_-a
110 (minus
110 (minus
111 ('symbol', '_a_b_c_')
111 ('symbol', '_a_b_c_')
112 ('symbol', 'a'))
112 ('symbol', 'a'))
113 6
113 6
114 $ try .a.b.c.
114 $ try .a.b.c.
115 ('symbol', '.a.b.c.')
115 ('symbol', '.a.b.c.')
116 7
116 7
117 $ try .a.b.c.-a
117 $ try .a.b.c.-a
118 (minus
118 (minus
119 ('symbol', '.a.b.c.')
119 ('symbol', '.a.b.c.')
120 ('symbol', 'a'))
120 ('symbol', 'a'))
121 7
121 7
122 $ try -- '-a-b-c-' # complains
122 $ try -- '-a-b-c-' # complains
123 hg: parse error at 7: not a prefix: end
123 hg: parse error at 7: not a prefix: end
124 [255]
124 [255]
125 $ log -a-b-c- # succeeds with fallback
125 $ log -a-b-c- # succeeds with fallback
126 4
126 4
127
127
128 $ try -- -a-b-c--a # complains
128 $ try -- -a-b-c--a # complains
129 (minus
129 (minus
130 (minus
130 (minus
131 (minus
131 (minus
132 (negate
132 (negate
133 ('symbol', 'a'))
133 ('symbol', 'a'))
134 ('symbol', 'b'))
134 ('symbol', 'b'))
135 ('symbol', 'c'))
135 ('symbol', 'c'))
136 (negate
136 (negate
137 ('symbol', 'a')))
137 ('symbol', 'a')))
138 abort: unknown revision '-a'!
138 abort: unknown revision '-a'!
139 [255]
139 [255]
140 $ try Γ©
140 $ try Γ©
141 ('symbol', '\xc3\xa9')
141 ('symbol', '\xc3\xa9')
142 9
142 9
143
143
144 no quoting needed
144 no quoting needed
145
145
146 $ log ::a-b-c-
146 $ log ::a-b-c-
147 0
147 0
148 1
148 1
149 2
149 2
150
150
151 quoting needed
151 quoting needed
152
152
153 $ try '"-a-b-c-"-a'
153 $ try '"-a-b-c-"-a'
154 (minus
154 (minus
155 ('string', '-a-b-c-')
155 ('string', '-a-b-c-')
156 ('symbol', 'a'))
156 ('symbol', 'a'))
157 4
157 4
158
158
159 $ log '1 or 2'
159 $ log '1 or 2'
160 1
160 1
161 2
161 2
162 $ log '1|2'
162 $ log '1|2'
163 1
163 1
164 2
164 2
165 $ log '1 and 2'
165 $ log '1 and 2'
166 $ log '1&2'
166 $ log '1&2'
167 $ try '1&2|3' # precedence - and is higher
167 $ try '1&2|3' # precedence - and is higher
168 (or
168 (or
169 (and
169 (and
170 ('symbol', '1')
170 ('symbol', '1')
171 ('symbol', '2'))
171 ('symbol', '2'))
172 ('symbol', '3'))
172 ('symbol', '3'))
173 3
173 3
174 $ try '1|2&3'
174 $ try '1|2&3'
175 (or
175 (or
176 ('symbol', '1')
176 ('symbol', '1')
177 (and
177 (and
178 ('symbol', '2')
178 ('symbol', '2')
179 ('symbol', '3')))
179 ('symbol', '3')))
180 1
180 1
181 $ try '1&2&3' # associativity
181 $ try '1&2&3' # associativity
182 (and
182 (and
183 (and
183 (and
184 ('symbol', '1')
184 ('symbol', '1')
185 ('symbol', '2'))
185 ('symbol', '2'))
186 ('symbol', '3'))
186 ('symbol', '3'))
187 $ try '1|(2|3)'
187 $ try '1|(2|3)'
188 (or
188 (or
189 ('symbol', '1')
189 ('symbol', '1')
190 (group
190 (group
191 (or
191 (or
192 ('symbol', '2')
192 ('symbol', '2')
193 ('symbol', '3'))))
193 ('symbol', '3'))))
194 1
194 1
195 2
195 2
196 3
196 3
197 $ log '1.0' # tag
197 $ log '1.0' # tag
198 6
198 6
199 $ log 'a' # branch
199 $ log 'a' # branch
200 0
200 0
201 $ log '2785f51ee'
201 $ log '2785f51ee'
202 0
202 0
203 $ log 'date(2005)'
203 $ log 'date(2005)'
204 4
204 4
205 $ log 'date(this is a test)'
205 $ log 'date(this is a test)'
206 hg: parse error at 10: unexpected token: symbol
206 hg: parse error at 10: unexpected token: symbol
207 [255]
207 [255]
208 $ log 'date()'
208 $ log 'date()'
209 hg: parse error: date requires a string
209 hg: parse error: date requires a string
210 [255]
210 [255]
211 $ log 'date'
211 $ log 'date'
212 hg: parse error: can't use date here
212 hg: parse error: can't use date here
213 [255]
213 [255]
214 $ log 'date('
214 $ log 'date('
215 hg: parse error at 5: not a prefix: end
215 hg: parse error at 5: not a prefix: end
216 [255]
216 [255]
217 $ log 'date(tip)'
217 $ log 'date(tip)'
218 abort: invalid date: 'tip'
218 abort: invalid date: 'tip'
219 [255]
219 [255]
220 $ log '"date"'
220 $ log '"date"'
221 abort: unknown revision 'date'!
221 abort: unknown revision 'date'!
222 [255]
222 [255]
223 $ log 'date(2005) and 1::'
223 $ log 'date(2005) and 1::'
224 4
224 4
225
225
226 ancestor can accept 0 or more arguments
226 ancestor can accept 0 or more arguments
227
227
228 $ log 'ancestor()'
228 $ log 'ancestor()'
229 $ log 'ancestor(1)'
229 $ log 'ancestor(1)'
230 1
230 1
231 $ log 'ancestor(4,5)'
231 $ log 'ancestor(4,5)'
232 1
232 1
233 $ log 'ancestor(4,5) and 4'
233 $ log 'ancestor(4,5) and 4'
234 $ log 'ancestor(0,0,1,3)'
234 $ log 'ancestor(0,0,1,3)'
235 0
235 0
236 $ log 'ancestor(3,1,5,3,5,1)'
236 $ log 'ancestor(3,1,5,3,5,1)'
237 1
237 1
238 $ log 'ancestor(0,1,3,5)'
238 $ log 'ancestor(0,1,3,5)'
239 0
239 0
240 $ log 'ancestor(1,2,3,4,5)'
240 $ log 'ancestor(1,2,3,4,5)'
241 1
241 1
242 $ log 'ancestors(5)'
242 $ log 'ancestors(5)'
243 0
243 0
244 1
244 1
245 3
245 3
246 5
246 5
247 $ log 'ancestor(ancestors(5))'
247 $ log 'ancestor(ancestors(5))'
248 0
248 0
249 $ log 'author(bob)'
249 $ log 'author(bob)'
250 2
250 2
251 $ log 'author("re:bob|test")'
251 $ log 'author("re:bob|test")'
252 0
252 0
253 1
253 1
254 2
254 2
255 3
255 3
256 4
256 4
257 5
257 5
258 6
258 6
259 7
259 7
260 8
260 8
261 9
261 9
262 $ log 'branch(Γ©)'
262 $ log 'branch(Γ©)'
263 8
263 8
264 9
264 9
265 $ log 'branch(a)'
265 $ log 'branch(a)'
266 0
266 0
267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
268 0 a
268 0 a
269 2 a-b-c-
269 2 a-b-c-
270 3 +a+b+c+
270 3 +a+b+c+
271 4 -a-b-c-
271 4 -a-b-c-
272 5 !a/b/c/
272 5 !a/b/c/
273 6 _a_b_c_
273 6 _a_b_c_
274 7 .a.b.c.
274 7 .a.b.c.
275 $ log 'children(ancestor(4,5))'
275 $ log 'children(ancestor(4,5))'
276 2
276 2
277 3
277 3
278 $ log 'closed()'
278 $ log 'closed()'
279 $ log 'contains(a)'
279 $ log 'contains(a)'
280 0
280 0
281 1
281 1
282 3
282 3
283 5
283 5
284 $ log 'contains("../repo/a")'
284 $ log 'contains("../repo/a")'
285 0
285 0
286 1
286 1
287 3
287 3
288 5
288 5
289 $ log 'desc(B)'
289 $ log 'desc(B)'
290 5
290 5
291 $ log 'descendants(2 or 3)'
291 $ log 'descendants(2 or 3)'
292 2
292 2
293 3
293 3
294 4
294 4
295 5
295 5
296 6
296 6
297 7
297 7
298 8
298 8
299 9
299 9
300 $ log 'file("b*")'
300 $ log 'file("b*")'
301 1
301 1
302 4
302 4
303 $ log 'filelog("b")'
303 $ log 'filelog("b")'
304 1
304 1
305 4
305 4
306 $ log 'filelog("../repo/b")'
306 $ log 'filelog("../repo/b")'
307 1
307 1
308 4
308 4
309 $ log 'follow()'
309 $ log 'follow()'
310 0
310 0
311 1
311 1
312 2
312 2
313 4
313 4
314 8
314 8
315 9
315 9
316 $ log 'grep("issue\d+")'
316 $ log 'grep("issue\d+")'
317 6
317 6
318 $ try 'grep("(")' # invalid regular expression
318 $ try 'grep("(")' # invalid regular expression
319 (func
319 (func
320 ('symbol', 'grep')
320 ('symbol', 'grep')
321 ('string', '('))
321 ('string', '('))
322 hg: parse error: invalid match pattern: unbalanced parenthesis
322 hg: parse error: invalid match pattern: unbalanced parenthesis
323 [255]
323 [255]
324 $ try 'grep("\bissue\d+")'
324 $ try 'grep("\bissue\d+")'
325 (func
325 (func
326 ('symbol', 'grep')
326 ('symbol', 'grep')
327 ('string', '\x08issue\\d+'))
327 ('string', '\x08issue\\d+'))
328 $ try 'grep(r"\bissue\d+")'
328 $ try 'grep(r"\bissue\d+")'
329 (func
329 (func
330 ('symbol', 'grep')
330 ('symbol', 'grep')
331 ('string', '\\bissue\\d+'))
331 ('string', '\\bissue\\d+'))
332 6
332 6
333 $ try 'grep(r"\")'
333 $ try 'grep(r"\")'
334 hg: parse error at 7: unterminated string
334 hg: parse error at 7: unterminated string
335 [255]
335 [255]
336 $ log 'head()'
336 $ log 'head()'
337 0
337 0
338 1
338 1
339 2
339 2
340 3
340 3
341 4
341 4
342 5
342 5
343 6
343 6
344 7
344 7
345 9
345 9
346 $ log 'heads(6::)'
346 $ log 'heads(6::)'
347 7
347 7
348 $ log 'keyword(issue)'
348 $ log 'keyword(issue)'
349 6
349 6
350 $ log 'keyword("test a")'
350 $ log 'keyword("test a")'
351 $ log 'limit(head(), 1)'
351 $ log 'limit(head(), 1)'
352 0
352 0
353 $ log 'matching(6)'
353 $ log 'matching(6)'
354 6
354 6
355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
356 6
356 6
357 7
357 7
358 $ log 'max(contains(a))'
358 $ log 'max(contains(a))'
359 5
359 5
360 $ log 'min(contains(a))'
360 $ log 'min(contains(a))'
361 0
361 0
362 $ log 'merge()'
362 $ log 'merge()'
363 6
363 6
364 $ log 'branchpoint()'
364 $ log 'branchpoint()'
365 1
365 1
366 4
366 4
367 $ log 'modifies(b)'
367 $ log 'modifies(b)'
368 4
368 4
369 $ log 'modifies("path:b")'
369 $ log 'modifies("path:b")'
370 4
370 4
371 $ log 'modifies("*")'
371 $ log 'modifies("*")'
372 4
372 4
373 6
373 6
374 $ log 'modifies("set:modified()")'
374 $ log 'modifies("set:modified()")'
375 4
375 4
376 $ log 'id(5)'
376 $ log 'id(5)'
377 2
377 2
378 $ log 'only(9)'
378 $ log 'only(9)'
379 8
379 8
380 9
380 9
381 $ log 'only(8)'
381 $ log 'only(8)'
382 8
382 8
383 $ log 'only(9, 5)'
383 $ log 'only(9, 5)'
384 2
384 2
385 4
385 4
386 8
386 8
387 9
387 9
388 $ log 'only(7 + 9, 5 + 2)'
388 $ log 'only(7 + 9, 5 + 2)'
389 4
389 4
390 6
390 6
391 7
391 7
392 8
392 8
393 9
393 9
394 $ log 'outgoing()'
394 $ log 'outgoing()'
395 8
395 8
396 9
396 9
397 $ log 'outgoing("../remote1")'
397 $ log 'outgoing("../remote1")'
398 8
398 8
399 9
399 9
400 $ log 'outgoing("../remote2")'
400 $ log 'outgoing("../remote2")'
401 3
401 3
402 5
402 5
403 6
403 6
404 7
404 7
405 9
405 9
406 $ log 'p1(merge())'
406 $ log 'p1(merge())'
407 5
407 5
408 $ log 'p2(merge())'
408 $ log 'p2(merge())'
409 4
409 4
410 $ log 'parents(merge())'
410 $ log 'parents(merge())'
411 4
411 4
412 5
412 5
413 $ log 'p1(branchpoint())'
413 $ log 'p1(branchpoint())'
414 0
414 0
415 2
415 2
416 $ log 'p2(branchpoint())'
416 $ log 'p2(branchpoint())'
417 $ log 'parents(branchpoint())'
417 $ log 'parents(branchpoint())'
418 0
418 0
419 2
419 2
420 $ log 'removes(a)'
420 $ log 'removes(a)'
421 2
421 2
422 6
422 6
423 $ log 'roots(all())'
423 $ log 'roots(all())'
424 0
424 0
425 $ log 'reverse(2 or 3 or 4 or 5)'
425 $ log 'reverse(2 or 3 or 4 or 5)'
426 5
426 5
427 4
427 4
428 3
428 3
429 2
429 2
430 $ log 'reverse(all())'
430 $ log 'reverse(all())'
431 9
431 9
432 8
432 8
433 7
433 7
434 6
434 6
435 5
435 5
436 4
436 4
437 3
437 3
438 2
438 2
439 1
439 1
440 0
440 0
441 $ log '1:: and reverse(all())'
441 $ log '1:: and reverse(all())'
442 9
442 9
443 8
443 8
444 7
444 7
445 6
445 6
446 5
446 5
447 4
447 4
448 3
448 3
449 2
449 2
450 1
450 1
451 $ log 'rev(5)'
451 $ log 'rev(5)'
452 5
452 5
453 $ log 'sort(limit(reverse(all()), 3))'
453 $ log 'sort(limit(reverse(all()), 3))'
454 7
454 7
455 8
455 8
456 9
456 9
457 $ log 'sort(2 or 3 or 4 or 5, date)'
457 $ log 'sort(2 or 3 or 4 or 5, date)'
458 2
458 2
459 3
459 3
460 5
460 5
461 4
461 4
462 $ log 'tagged()'
462 $ log 'tagged()'
463 6
463 6
464 $ log 'tag()'
464 $ log 'tag()'
465 6
465 6
466 $ log 'tag(1.0)'
466 $ log 'tag(1.0)'
467 6
467 6
468 $ log 'tag(tip)'
468 $ log 'tag(tip)'
469 9
469 9
470
470
471 test sort revset
471 test sort revset
472 --------------------------------------------
472 --------------------------------------------
473
473
474 test when adding two unordered revsets
474 test when adding two unordered revsets
475
475
476 $ log 'sort(keyword(issue) or modifies(b))'
476 $ log 'sort(keyword(issue) or modifies(b))'
477 4
477 4
478 6
478 6
479
479
480 test when sorting a reversed collection in the same way it is
480 test when sorting a reversed collection in the same way it is
481
481
482 $ log 'sort(reverse(all()), -rev)'
482 $ log 'sort(reverse(all()), -rev)'
483 9
483 9
484 8
484 8
485 7
485 7
486 6
486 6
487 5
487 5
488 4
488 4
489 3
489 3
490 2
490 2
491 1
491 1
492 0
492 0
493
493
494 test when sorting a reversed collection
494 test when sorting a reversed collection
495
495
496 $ log 'sort(reverse(all()), rev)'
496 $ log 'sort(reverse(all()), rev)'
497 0
497 0
498 1
498 1
499 2
499 2
500 3
500 3
501 4
501 4
502 5
502 5
503 6
503 6
504 7
504 7
505 8
505 8
506 9
506 9
507
507
508
508
509 test sorting two sorted collections in different orders
509 test sorting two sorted collections in different orders
510
510
511 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
511 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
512 2
512 2
513 6
513 6
514 8
514 8
515 9
515 9
516
516
517 test sorting two sorted collections in different orders backwards
517 test sorting two sorted collections in different orders backwards
518
518
519 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
519 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
520 9
520 9
521 8
521 8
522 6
522 6
523 2
523 2
524
524
525 test substracting something from an addset
525 test substracting something from an addset
526
526
527 $ log '(outgoing() or removes(a)) - removes(a)'
527 $ log '(outgoing() or removes(a)) - removes(a)'
528 8
528 8
529 9
529 9
530
530
531 test intersecting something with an addset
531 test intersecting something with an addset
532
532
533 $ log 'parents(outgoing() or removes(a))'
533 $ log 'parents(outgoing() or removes(a))'
534 1
534 1
535 4
535 4
536 5
536 5
537 8
537 8
538
538
539 check that conversion to _missingancestors works
539 check that conversion to _missingancestors works
540 $ try --optimize '::3 - ::1'
540 $ try --optimize '::3 - ::1'
541 (minus
541 (minus
542 (dagrangepre
542 (dagrangepre
543 ('symbol', '3'))
543 ('symbol', '3'))
544 (dagrangepre
544 (dagrangepre
545 ('symbol', '1')))
545 ('symbol', '1')))
546 * optimized:
546 * optimized:
547 (func
547 (func
548 ('symbol', '_missingancestors')
548 ('symbol', '_missingancestors')
549 (list
549 (list
550 ('symbol', '3')
550 ('symbol', '3')
551 ('symbol', '1')))
551 ('symbol', '1')))
552 3
552 3
553 $ try --optimize 'ancestors(1) - ancestors(3)'
553 $ try --optimize 'ancestors(1) - ancestors(3)'
554 (minus
554 (minus
555 (func
555 (func
556 ('symbol', 'ancestors')
556 ('symbol', 'ancestors')
557 ('symbol', '1'))
557 ('symbol', '1'))
558 (func
558 (func
559 ('symbol', 'ancestors')
559 ('symbol', 'ancestors')
560 ('symbol', '3')))
560 ('symbol', '3')))
561 * optimized:
561 * optimized:
562 (func
562 (func
563 ('symbol', '_missingancestors')
563 ('symbol', '_missingancestors')
564 (list
564 (list
565 ('symbol', '1')
565 ('symbol', '1')
566 ('symbol', '3')))
566 ('symbol', '3')))
567 $ try --optimize 'not ::2 and ::6'
567 $ try --optimize 'not ::2 and ::6'
568 (and
568 (and
569 (not
569 (not
570 (dagrangepre
570 (dagrangepre
571 ('symbol', '2')))
571 ('symbol', '2')))
572 (dagrangepre
572 (dagrangepre
573 ('symbol', '6')))
573 ('symbol', '6')))
574 * optimized:
574 * optimized:
575 (func
575 (func
576 ('symbol', '_missingancestors')
576 ('symbol', '_missingancestors')
577 (list
577 (list
578 ('symbol', '6')
578 ('symbol', '6')
579 ('symbol', '2')))
579 ('symbol', '2')))
580 3
580 3
581 4
581 4
582 5
582 5
583 6
583 6
584 $ try --optimize 'ancestors(6) and not ancestors(4)'
584 $ try --optimize 'ancestors(6) and not ancestors(4)'
585 (and
585 (and
586 (func
586 (func
587 ('symbol', 'ancestors')
587 ('symbol', 'ancestors')
588 ('symbol', '6'))
588 ('symbol', '6'))
589 (not
589 (not
590 (func
590 (func
591 ('symbol', 'ancestors')
591 ('symbol', 'ancestors')
592 ('symbol', '4'))))
592 ('symbol', '4'))))
593 * optimized:
593 * optimized:
594 (func
594 (func
595 ('symbol', '_missingancestors')
595 ('symbol', '_missingancestors')
596 (list
596 (list
597 ('symbol', '6')
597 ('symbol', '6')
598 ('symbol', '4')))
598 ('symbol', '4')))
599 3
599 3
600 5
600 5
601 6
601 6
602
602
603 we can use patterns when searching for tags
603 we can use patterns when searching for tags
604
604
605 $ log 'tag("1..*")'
605 $ log 'tag("1..*")'
606 abort: tag '1..*' does not exist
606 abort: tag '1..*' does not exist
607 [255]
607 [255]
608 $ log 'tag("re:1..*")'
608 $ log 'tag("re:1..*")'
609 6
609 6
610 $ log 'tag("re:[0-9].[0-9]")'
610 $ log 'tag("re:[0-9].[0-9]")'
611 6
611 6
612 $ log 'tag("literal:1.0")'
612 $ log 'tag("literal:1.0")'
613 6
613 6
614 $ log 'tag("re:0..*")'
614 $ log 'tag("re:0..*")'
615
615
616 $ log 'tag(unknown)'
616 $ log 'tag(unknown)'
617 abort: tag 'unknown' does not exist
617 abort: tag 'unknown' does not exist
618 [255]
618 [255]
619 $ log 'branch(unknown)'
619 $ log 'branch(unknown)'
620 abort: unknown revision 'unknown'!
620 abort: unknown revision 'unknown'!
621 [255]
621 [255]
622 $ log 'user(bob)'
622 $ log 'user(bob)'
623 2
623 2
624
624
625 $ log '4::8'
625 $ log '4::8'
626 4
626 4
627 8
627 8
628 $ log '4:8'
628 $ log '4:8'
629 4
629 4
630 5
630 5
631 6
631 6
632 7
632 7
633 8
633 8
634
634
635 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
635 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
636 4
636 4
637 2
637 2
638 5
638 5
639
639
640 $ log 'not 0 and 0:2'
640 $ log 'not 0 and 0:2'
641 1
641 1
642 2
642 2
643 $ log 'not 1 and 0:2'
643 $ log 'not 1 and 0:2'
644 0
644 0
645 2
645 2
646 $ log 'not 2 and 0:2'
646 $ log 'not 2 and 0:2'
647 0
647 0
648 1
648 1
649 $ log '(1 and 2)::'
649 $ log '(1 and 2)::'
650 $ log '(1 and 2):'
650 $ log '(1 and 2):'
651 $ log '(1 and 2):3'
651 $ log '(1 and 2):3'
652 $ log 'sort(head(), -rev)'
652 $ log 'sort(head(), -rev)'
653 9
653 9
654 7
654 7
655 6
655 6
656 5
656 5
657 4
657 4
658 3
658 3
659 2
659 2
660 1
660 1
661 0
661 0
662 $ log '4::8 - 8'
662 $ log '4::8 - 8'
663 4
663 4
664 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
664 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
665 2
665 2
666 3
666 3
667 1
667 1
668
668
669 issue2437
669 issue2437
670
670
671 $ log '3 and p1(5)'
671 $ log '3 and p1(5)'
672 3
672 3
673 $ log '4 and p2(6)'
673 $ log '4 and p2(6)'
674 4
674 4
675 $ log '1 and parents(:2)'
675 $ log '1 and parents(:2)'
676 1
676 1
677 $ log '2 and children(1:)'
677 $ log '2 and children(1:)'
678 2
678 2
679 $ log 'roots(all()) or roots(all())'
679 $ log 'roots(all()) or roots(all())'
680 0
680 0
681 $ hg debugrevspec 'roots(all()) or roots(all())'
681 $ hg debugrevspec 'roots(all()) or roots(all())'
682 0
682 0
683 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
683 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
684 9
684 9
685 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
685 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
686 4
686 4
687
687
688 issue2654: report a parse error if the revset was not completely parsed
688 issue2654: report a parse error if the revset was not completely parsed
689
689
690 $ log '1 OR 2'
690 $ log '1 OR 2'
691 hg: parse error at 2: invalid token
691 hg: parse error at 2: invalid token
692 [255]
692 [255]
693
693
694 or operator should preserve ordering:
694 or operator should preserve ordering:
695 $ log 'reverse(2::4) or tip'
695 $ log 'reverse(2::4) or tip'
696 4
696 4
697 2
697 2
698 9
698 9
699
699
700 parentrevspec
700 parentrevspec
701
701
702 $ log 'merge()^0'
702 $ log 'merge()^0'
703 6
703 6
704 $ log 'merge()^'
704 $ log 'merge()^'
705 5
705 5
706 $ log 'merge()^1'
706 $ log 'merge()^1'
707 5
707 5
708 $ log 'merge()^2'
708 $ log 'merge()^2'
709 4
709 4
710 $ log 'merge()^^'
710 $ log 'merge()^^'
711 3
711 3
712 $ log 'merge()^1^'
712 $ log 'merge()^1^'
713 3
713 3
714 $ log 'merge()^^^'
714 $ log 'merge()^^^'
715 1
715 1
716
716
717 $ log 'merge()~0'
717 $ log 'merge()~0'
718 6
718 6
719 $ log 'merge()~1'
719 $ log 'merge()~1'
720 5
720 5
721 $ log 'merge()~2'
721 $ log 'merge()~2'
722 3
722 3
723 $ log 'merge()~2^1'
723 $ log 'merge()~2^1'
724 1
724 1
725 $ log 'merge()~3'
725 $ log 'merge()~3'
726 1
726 1
727
727
728 $ log '(-3:tip)^'
728 $ log '(-3:tip)^'
729 4
729 4
730 6
730 6
731 8
731 8
732
732
733 $ log 'tip^foo'
733 $ log 'tip^foo'
734 hg: parse error: ^ expects a number 0, 1, or 2
734 hg: parse error: ^ expects a number 0, 1, or 2
735 [255]
735 [255]
736
736
737 multiple revspecs
737 multiple revspecs
738
738
739 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
739 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
740 8
740 8
741 9
741 9
742 4
742 4
743 5
743 5
744 6
744 6
745 7
745 7
746
746
747 test usage in revpair (with "+")
748
749 (real pair)
750
751 $ hg diff -r 'tip^^' -r 'tip'
752 diff -r 2326846efdab -r 24286f4ae135 .hgtags
753 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
754 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
755 @@ -0,0 +1,1 @@
756 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
757 $ hg diff -r 'tip^^::tip'
758 diff -r 2326846efdab -r 24286f4ae135 .hgtags
759 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
760 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
761 @@ -0,0 +1,1 @@
762 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
763
764 (single rev)
765
766 $ hg diff -r 'tip^' -r 'tip^'
767 $ hg diff -r 'tip^::tip^ or tip^'
768
769 (single rev that does not looks like a range)
770
771 $ hg diff -r 'tip^ or tip^'
772 diff -r d5d0dcbdc4d9 .hgtags
773 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
774 +++ b/.hgtags * (glob)
775 @@ -0,0 +1,1 @@
776 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
777
778 (no rev)
779
780 $ hg diff -r 'author("babar") or author("celeste")'
781 abort: empty revision range
782 [255]
783
747 aliases:
784 aliases:
748
785
749 $ echo '[revsetalias]' >> .hg/hgrc
786 $ echo '[revsetalias]' >> .hg/hgrc
750 $ echo 'm = merge()' >> .hg/hgrc
787 $ echo 'm = merge()' >> .hg/hgrc
751 $ echo 'sincem = descendants(m)' >> .hg/hgrc
788 $ echo 'sincem = descendants(m)' >> .hg/hgrc
752 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
789 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
753 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
790 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
754 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
791 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
755
792
756 $ try m
793 $ try m
757 ('symbol', 'm')
794 ('symbol', 'm')
758 (func
795 (func
759 ('symbol', 'merge')
796 ('symbol', 'merge')
760 None)
797 None)
761 6
798 6
762
799
763 test alias recursion
800 test alias recursion
764
801
765 $ try sincem
802 $ try sincem
766 ('symbol', 'sincem')
803 ('symbol', 'sincem')
767 (func
804 (func
768 ('symbol', 'descendants')
805 ('symbol', 'descendants')
769 (func
806 (func
770 ('symbol', 'merge')
807 ('symbol', 'merge')
771 None))
808 None))
772 6
809 6
773 7
810 7
774
811
775 test infinite recursion
812 test infinite recursion
776
813
777 $ echo 'recurse1 = recurse2' >> .hg/hgrc
814 $ echo 'recurse1 = recurse2' >> .hg/hgrc
778 $ echo 'recurse2 = recurse1' >> .hg/hgrc
815 $ echo 'recurse2 = recurse1' >> .hg/hgrc
779 $ try recurse1
816 $ try recurse1
780 ('symbol', 'recurse1')
817 ('symbol', 'recurse1')
781 hg: parse error: infinite expansion of revset alias "recurse1" detected
818 hg: parse error: infinite expansion of revset alias "recurse1" detected
782 [255]
819 [255]
783
820
784 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
821 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
785 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
822 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
786 $ try "level2(level1(1, 2), 3)"
823 $ try "level2(level1(1, 2), 3)"
787 (func
824 (func
788 ('symbol', 'level2')
825 ('symbol', 'level2')
789 (list
826 (list
790 (func
827 (func
791 ('symbol', 'level1')
828 ('symbol', 'level1')
792 (list
829 (list
793 ('symbol', '1')
830 ('symbol', '1')
794 ('symbol', '2')))
831 ('symbol', '2')))
795 ('symbol', '3')))
832 ('symbol', '3')))
796 (or
833 (or
797 ('symbol', '3')
834 ('symbol', '3')
798 (or
835 (or
799 ('symbol', '1')
836 ('symbol', '1')
800 ('symbol', '2')))
837 ('symbol', '2')))
801 3
838 3
802 1
839 1
803 2
840 2
804
841
805 test nesting and variable passing
842 test nesting and variable passing
806
843
807 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
844 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
808 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
845 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
809 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
846 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
810 $ try 'nested(2:5)'
847 $ try 'nested(2:5)'
811 (func
848 (func
812 ('symbol', 'nested')
849 ('symbol', 'nested')
813 (range
850 (range
814 ('symbol', '2')
851 ('symbol', '2')
815 ('symbol', '5')))
852 ('symbol', '5')))
816 (func
853 (func
817 ('symbol', 'max')
854 ('symbol', 'max')
818 (range
855 (range
819 ('symbol', '2')
856 ('symbol', '2')
820 ('symbol', '5')))
857 ('symbol', '5')))
821 5
858 5
822
859
823 test variable isolation, variable placeholders are rewritten as string
860 test variable isolation, variable placeholders are rewritten as string
824 then parsed and matched again as string. Check they do not leak too
861 then parsed and matched again as string. Check they do not leak too
825 far away.
862 far away.
826
863
827 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
864 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
828 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
865 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
829 $ try 'callinjection(2:5)'
866 $ try 'callinjection(2:5)'
830 (func
867 (func
831 ('symbol', 'callinjection')
868 ('symbol', 'callinjection')
832 (range
869 (range
833 ('symbol', '2')
870 ('symbol', '2')
834 ('symbol', '5')))
871 ('symbol', '5')))
835 (func
872 (func
836 ('symbol', 'descendants')
873 ('symbol', 'descendants')
837 (func
874 (func
838 ('symbol', 'max')
875 ('symbol', 'max')
839 ('string', '$1')))
876 ('string', '$1')))
840 abort: unknown revision '$1'!
877 abort: unknown revision '$1'!
841 [255]
878 [255]
842
879
843 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
880 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
844 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
881 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
845 $ try 'callinjection2(2:5)'
882 $ try 'callinjection2(2:5)'
846 (func
883 (func
847 ('symbol', 'callinjection2')
884 ('symbol', 'callinjection2')
848 (range
885 (range
849 ('symbol', '2')
886 ('symbol', '2')
850 ('symbol', '5')))
887 ('symbol', '5')))
851 hg: parse error: not a function: _aliasarg
888 hg: parse error: not a function: _aliasarg
852 [255]
889 [255]
853 >>> data = file('.hg/hgrc', 'rb').read()
890 >>> data = file('.hg/hgrc', 'rb').read()
854 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
891 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
855
892
856 $ try 'd(2:5)'
893 $ try 'd(2:5)'
857 (func
894 (func
858 ('symbol', 'd')
895 ('symbol', 'd')
859 (range
896 (range
860 ('symbol', '2')
897 ('symbol', '2')
861 ('symbol', '5')))
898 ('symbol', '5')))
862 (func
899 (func
863 ('symbol', 'reverse')
900 ('symbol', 'reverse')
864 (func
901 (func
865 ('symbol', 'sort')
902 ('symbol', 'sort')
866 (list
903 (list
867 (range
904 (range
868 ('symbol', '2')
905 ('symbol', '2')
869 ('symbol', '5'))
906 ('symbol', '5'))
870 ('symbol', 'date'))))
907 ('symbol', 'date'))))
871 4
908 4
872 5
909 5
873 3
910 3
874 2
911 2
875 $ try 'rs(2 or 3, date)'
912 $ try 'rs(2 or 3, date)'
876 (func
913 (func
877 ('symbol', 'rs')
914 ('symbol', 'rs')
878 (list
915 (list
879 (or
916 (or
880 ('symbol', '2')
917 ('symbol', '2')
881 ('symbol', '3'))
918 ('symbol', '3'))
882 ('symbol', 'date')))
919 ('symbol', 'date')))
883 (func
920 (func
884 ('symbol', 'reverse')
921 ('symbol', 'reverse')
885 (func
922 (func
886 ('symbol', 'sort')
923 ('symbol', 'sort')
887 (list
924 (list
888 (or
925 (or
889 ('symbol', '2')
926 ('symbol', '2')
890 ('symbol', '3'))
927 ('symbol', '3'))
891 ('symbol', 'date'))))
928 ('symbol', 'date'))))
892 3
929 3
893 2
930 2
894 $ try 'rs()'
931 $ try 'rs()'
895 (func
932 (func
896 ('symbol', 'rs')
933 ('symbol', 'rs')
897 None)
934 None)
898 hg: parse error: invalid number of arguments: 0
935 hg: parse error: invalid number of arguments: 0
899 [255]
936 [255]
900 $ try 'rs(2)'
937 $ try 'rs(2)'
901 (func
938 (func
902 ('symbol', 'rs')
939 ('symbol', 'rs')
903 ('symbol', '2'))
940 ('symbol', '2'))
904 hg: parse error: invalid number of arguments: 1
941 hg: parse error: invalid number of arguments: 1
905 [255]
942 [255]
906 $ try 'rs(2, data, 7)'
943 $ try 'rs(2, data, 7)'
907 (func
944 (func
908 ('symbol', 'rs')
945 ('symbol', 'rs')
909 (list
946 (list
910 (list
947 (list
911 ('symbol', '2')
948 ('symbol', '2')
912 ('symbol', 'data'))
949 ('symbol', 'data'))
913 ('symbol', '7')))
950 ('symbol', '7')))
914 hg: parse error: invalid number of arguments: 3
951 hg: parse error: invalid number of arguments: 3
915 [255]
952 [255]
916 $ try 'rs4(2 or 3, x, x, date)'
953 $ try 'rs4(2 or 3, x, x, date)'
917 (func
954 (func
918 ('symbol', 'rs4')
955 ('symbol', 'rs4')
919 (list
956 (list
920 (list
957 (list
921 (list
958 (list
922 (or
959 (or
923 ('symbol', '2')
960 ('symbol', '2')
924 ('symbol', '3'))
961 ('symbol', '3'))
925 ('symbol', 'x'))
962 ('symbol', 'x'))
926 ('symbol', 'x'))
963 ('symbol', 'x'))
927 ('symbol', 'date')))
964 ('symbol', 'date')))
928 (func
965 (func
929 ('symbol', 'reverse')
966 ('symbol', 'reverse')
930 (func
967 (func
931 ('symbol', 'sort')
968 ('symbol', 'sort')
932 (list
969 (list
933 (or
970 (or
934 ('symbol', '2')
971 ('symbol', '2')
935 ('symbol', '3'))
972 ('symbol', '3'))
936 ('symbol', 'date'))))
973 ('symbol', 'date'))))
937 3
974 3
938 2
975 2
939
976
940 issue2549 - correct optimizations
977 issue2549 - correct optimizations
941
978
942 $ log 'limit(1 or 2 or 3, 2) and not 2'
979 $ log 'limit(1 or 2 or 3, 2) and not 2'
943 1
980 1
944 $ log 'max(1 or 2) and not 2'
981 $ log 'max(1 or 2) and not 2'
945 $ log 'min(1 or 2) and not 1'
982 $ log 'min(1 or 2) and not 1'
946 $ log 'last(1 or 2, 1) and not 2'
983 $ log 'last(1 or 2, 1) and not 2'
947
984
948 test revsets started with 40-chars hash (issue3669)
985 test revsets started with 40-chars hash (issue3669)
949
986
950 $ ISSUE3669_TIP=`hg tip --template '{node}'`
987 $ ISSUE3669_TIP=`hg tip --template '{node}'`
951 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
988 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
952 9
989 9
953 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
990 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
954 8
991 8
955
992
956 test or-ed indirect predicates (issue3775)
993 test or-ed indirect predicates (issue3775)
957
994
958 $ log '6 or 6^1' | sort
995 $ log '6 or 6^1' | sort
959 5
996 5
960 6
997 6
961 $ log '6^1 or 6' | sort
998 $ log '6^1 or 6' | sort
962 5
999 5
963 6
1000 6
964 $ log '4 or 4~1' | sort
1001 $ log '4 or 4~1' | sort
965 2
1002 2
966 4
1003 4
967 $ log '4~1 or 4' | sort
1004 $ log '4~1 or 4' | sort
968 2
1005 2
969 4
1006 4
970 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
1007 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
971 0
1008 0
972 1
1009 1
973 2
1010 2
974 3
1011 3
975 4
1012 4
976 5
1013 5
977 6
1014 6
978 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
1015 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
979 0
1016 0
980 1
1017 1
981 2
1018 2
982 3
1019 3
983 4
1020 4
984 5
1021 5
985 6
1022 6
986
1023
987 tests for 'remote()' predicate:
1024 tests for 'remote()' predicate:
988 #. (csets in remote) (id) (remote)
1025 #. (csets in remote) (id) (remote)
989 1. less than local current branch "default"
1026 1. less than local current branch "default"
990 2. same with local specified "default"
1027 2. same with local specified "default"
991 3. more than local specified specified
1028 3. more than local specified specified
992
1029
993 $ hg clone --quiet -U . ../remote3
1030 $ hg clone --quiet -U . ../remote3
994 $ cd ../remote3
1031 $ cd ../remote3
995 $ hg update -q 7
1032 $ hg update -q 7
996 $ echo r > r
1033 $ echo r > r
997 $ hg ci -Aqm 10
1034 $ hg ci -Aqm 10
998 $ log 'remote()'
1035 $ log 'remote()'
999 7
1036 7
1000 $ log 'remote("a-b-c-")'
1037 $ log 'remote("a-b-c-")'
1001 2
1038 2
1002 $ cd ../repo
1039 $ cd ../repo
1003 $ log 'remote(".a.b.c.", "../remote3")'
1040 $ log 'remote(".a.b.c.", "../remote3")'
1004
1041
1005 $ cd ..
1042 $ cd ..
1006
1043
1007 test author/desc/keyword in problematic encoding
1044 test author/desc/keyword in problematic encoding
1008 # unicode: cp932:
1045 # unicode: cp932:
1009 # u30A2 0x83 0x41(= 'A')
1046 # u30A2 0x83 0x41(= 'A')
1010 # u30C2 0x83 0x61(= 'a')
1047 # u30C2 0x83 0x61(= 'a')
1011
1048
1012 $ hg init problematicencoding
1049 $ hg init problematicencoding
1013 $ cd problematicencoding
1050 $ cd problematicencoding
1014
1051
1015 $ python > setup.sh <<EOF
1052 $ python > setup.sh <<EOF
1016 > print u'''
1053 > print u'''
1017 > echo a > text
1054 > echo a > text
1018 > hg add text
1055 > hg add text
1019 > hg --encoding utf-8 commit -u '\u30A2' -m none
1056 > hg --encoding utf-8 commit -u '\u30A2' -m none
1020 > echo b > text
1057 > echo b > text
1021 > hg --encoding utf-8 commit -u '\u30C2' -m none
1058 > hg --encoding utf-8 commit -u '\u30C2' -m none
1022 > echo c > text
1059 > echo c > text
1023 > hg --encoding utf-8 commit -u none -m '\u30A2'
1060 > hg --encoding utf-8 commit -u none -m '\u30A2'
1024 > echo d > text
1061 > echo d > text
1025 > hg --encoding utf-8 commit -u none -m '\u30C2'
1062 > hg --encoding utf-8 commit -u none -m '\u30C2'
1026 > '''.encode('utf-8')
1063 > '''.encode('utf-8')
1027 > EOF
1064 > EOF
1028 $ sh < setup.sh
1065 $ sh < setup.sh
1029
1066
1030 test in problematic encoding
1067 test in problematic encoding
1031 $ python > test.sh <<EOF
1068 $ python > test.sh <<EOF
1032 > print u'''
1069 > print u'''
1033 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1070 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1034 > echo ====
1071 > echo ====
1035 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1072 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1036 > echo ====
1073 > echo ====
1037 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1074 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1038 > echo ====
1075 > echo ====
1039 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1076 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1040 > echo ====
1077 > echo ====
1041 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1078 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1042 > echo ====
1079 > echo ====
1043 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1080 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1044 > '''.encode('cp932')
1081 > '''.encode('cp932')
1045 > EOF
1082 > EOF
1046 $ sh < test.sh
1083 $ sh < test.sh
1047 0
1084 0
1048 ====
1085 ====
1049 1
1086 1
1050 ====
1087 ====
1051 2
1088 2
1052 ====
1089 ====
1053 3
1090 3
1054 ====
1091 ====
1055 0
1092 0
1056 2
1093 2
1057 ====
1094 ====
1058 1
1095 1
1059 3
1096 3
1060
1097
1061 $ cd ..
1098 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now