##// END OF EJS Templates
scmutil: speed up revrange...
Bryan O'Sullivan -
r16390:4df76d55 default
parent child Browse files
Show More
@@ -1,866 +1,875 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 import util, error, osutil, revset, similar, encoding
9 import util, error, osutil, revset, similar, encoding
10 import match as matchmod
10 import match as matchmod
11 import os, errno, re, stat, sys, glob
11 import os, errno, re, stat, sys, glob
12
12
13 def nochangesfound(ui, secretlist=None):
13 def nochangesfound(ui, secretlist=None):
14 '''report no changes for push/pull'''
14 '''report no changes for push/pull'''
15 if secretlist:
15 if secretlist:
16 ui.status(_("no changes found (ignored %d secret changesets)\n")
16 ui.status(_("no changes found (ignored %d secret changesets)\n")
17 % len(secretlist))
17 % len(secretlist))
18 else:
18 else:
19 ui.status(_("no changes found\n"))
19 ui.status(_("no changes found\n"))
20
20
21 def checkfilename(f):
21 def checkfilename(f):
22 '''Check that the filename f is an acceptable filename for a tracked file'''
22 '''Check that the filename f is an acceptable filename for a tracked file'''
23 if '\r' in f or '\n' in f:
23 if '\r' in f or '\n' in f:
24 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
24 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
25
25
26 def checkportable(ui, f):
26 def checkportable(ui, f):
27 '''Check if filename f is portable and warn or abort depending on config'''
27 '''Check if filename f is portable and warn or abort depending on config'''
28 checkfilename(f)
28 checkfilename(f)
29 abort, warn = checkportabilityalert(ui)
29 abort, warn = checkportabilityalert(ui)
30 if abort or warn:
30 if abort or warn:
31 msg = util.checkwinfilename(f)
31 msg = util.checkwinfilename(f)
32 if msg:
32 if msg:
33 msg = "%s: %r" % (msg, f)
33 msg = "%s: %r" % (msg, f)
34 if abort:
34 if abort:
35 raise util.Abort(msg)
35 raise util.Abort(msg)
36 ui.warn(_("warning: %s\n") % msg)
36 ui.warn(_("warning: %s\n") % msg)
37
37
38 def checkportabilityalert(ui):
38 def checkportabilityalert(ui):
39 '''check if the user's config requests nothing, a warning, or abort for
39 '''check if the user's config requests nothing, a warning, or abort for
40 non-portable filenames'''
40 non-portable filenames'''
41 val = ui.config('ui', 'portablefilenames', 'warn')
41 val = ui.config('ui', 'portablefilenames', 'warn')
42 lval = val.lower()
42 lval = val.lower()
43 bval = util.parsebool(val)
43 bval = util.parsebool(val)
44 abort = os.name == 'nt' or lval == 'abort'
44 abort = os.name == 'nt' or lval == 'abort'
45 warn = bval or lval == 'warn'
45 warn = bval or lval == 'warn'
46 if bval is None and not (warn or abort or lval == 'ignore'):
46 if bval is None and not (warn or abort or lval == 'ignore'):
47 raise error.ConfigError(
47 raise error.ConfigError(
48 _("ui.portablefilenames value is invalid ('%s')") % val)
48 _("ui.portablefilenames value is invalid ('%s')") % val)
49 return abort, warn
49 return abort, warn
50
50
51 class casecollisionauditor(object):
51 class casecollisionauditor(object):
52 def __init__(self, ui, abort, existingiter):
52 def __init__(self, ui, abort, existingiter):
53 self._ui = ui
53 self._ui = ui
54 self._abort = abort
54 self._abort = abort
55 self._map = {}
55 self._map = {}
56 for f in existingiter:
56 for f in existingiter:
57 self._map[encoding.lower(f)] = f
57 self._map[encoding.lower(f)] = f
58
58
59 def __call__(self, f):
59 def __call__(self, f):
60 fl = encoding.lower(f)
60 fl = encoding.lower(f)
61 map = self._map
61 map = self._map
62 if fl in map and map[fl] != f:
62 if fl in map and map[fl] != f:
63 msg = _('possible case-folding collision for %s') % f
63 msg = _('possible case-folding collision for %s') % f
64 if self._abort:
64 if self._abort:
65 raise util.Abort(msg)
65 raise util.Abort(msg)
66 self._ui.warn(_("warning: %s\n") % msg)
66 self._ui.warn(_("warning: %s\n") % msg)
67 map[fl] = f
67 map[fl] = f
68
68
69 class pathauditor(object):
69 class pathauditor(object):
70 '''ensure that a filesystem path contains no banned components.
70 '''ensure that a filesystem path contains no banned components.
71 the following properties of a path are checked:
71 the following properties of a path are checked:
72
72
73 - ends with a directory separator
73 - ends with a directory separator
74 - under top-level .hg
74 - under top-level .hg
75 - starts at the root of a windows drive
75 - starts at the root of a windows drive
76 - contains ".."
76 - contains ".."
77 - traverses a symlink (e.g. a/symlink_here/b)
77 - traverses a symlink (e.g. a/symlink_here/b)
78 - inside a nested repository (a callback can be used to approve
78 - inside a nested repository (a callback can be used to approve
79 some nested repositories, e.g., subrepositories)
79 some nested repositories, e.g., subrepositories)
80 '''
80 '''
81
81
82 def __init__(self, root, callback=None):
82 def __init__(self, root, callback=None):
83 self.audited = set()
83 self.audited = set()
84 self.auditeddir = set()
84 self.auditeddir = set()
85 self.root = root
85 self.root = root
86 self.callback = callback
86 self.callback = callback
87 if os.path.lexists(root) and not util.checkcase(root):
87 if os.path.lexists(root) and not util.checkcase(root):
88 self.normcase = util.normcase
88 self.normcase = util.normcase
89 else:
89 else:
90 self.normcase = lambda x: x
90 self.normcase = lambda x: x
91
91
92 def __call__(self, path):
92 def __call__(self, path):
93 '''Check the relative path.
93 '''Check the relative path.
94 path may contain a pattern (e.g. foodir/**.txt)'''
94 path may contain a pattern (e.g. foodir/**.txt)'''
95
95
96 path = util.localpath(path)
96 path = util.localpath(path)
97 normpath = self.normcase(path)
97 normpath = self.normcase(path)
98 if normpath in self.audited:
98 if normpath in self.audited:
99 return
99 return
100 # AIX ignores "/" at end of path, others raise EISDIR.
100 # AIX ignores "/" at end of path, others raise EISDIR.
101 if util.endswithsep(path):
101 if util.endswithsep(path):
102 raise util.Abort(_("path ends in directory separator: %s") % path)
102 raise util.Abort(_("path ends in directory separator: %s") % path)
103 parts = util.splitpath(path)
103 parts = util.splitpath(path)
104 if (os.path.splitdrive(path)[0]
104 if (os.path.splitdrive(path)[0]
105 or parts[0].lower() in ('.hg', '.hg.', '')
105 or parts[0].lower() in ('.hg', '.hg.', '')
106 or os.pardir in parts):
106 or os.pardir in parts):
107 raise util.Abort(_("path contains illegal component: %s") % path)
107 raise util.Abort(_("path contains illegal component: %s") % path)
108 if '.hg' in path.lower():
108 if '.hg' in path.lower():
109 lparts = [p.lower() for p in parts]
109 lparts = [p.lower() for p in parts]
110 for p in '.hg', '.hg.':
110 for p in '.hg', '.hg.':
111 if p in lparts[1:]:
111 if p in lparts[1:]:
112 pos = lparts.index(p)
112 pos = lparts.index(p)
113 base = os.path.join(*parts[:pos])
113 base = os.path.join(*parts[:pos])
114 raise util.Abort(_("path '%s' is inside nested repo %r")
114 raise util.Abort(_("path '%s' is inside nested repo %r")
115 % (path, base))
115 % (path, base))
116
116
117 normparts = util.splitpath(normpath)
117 normparts = util.splitpath(normpath)
118 assert len(parts) == len(normparts)
118 assert len(parts) == len(normparts)
119
119
120 parts.pop()
120 parts.pop()
121 normparts.pop()
121 normparts.pop()
122 prefixes = []
122 prefixes = []
123 while parts:
123 while parts:
124 prefix = os.sep.join(parts)
124 prefix = os.sep.join(parts)
125 normprefix = os.sep.join(normparts)
125 normprefix = os.sep.join(normparts)
126 if normprefix in self.auditeddir:
126 if normprefix in self.auditeddir:
127 break
127 break
128 curpath = os.path.join(self.root, prefix)
128 curpath = os.path.join(self.root, prefix)
129 try:
129 try:
130 st = os.lstat(curpath)
130 st = os.lstat(curpath)
131 except OSError, err:
131 except OSError, err:
132 # EINVAL can be raised as invalid path syntax under win32.
132 # EINVAL can be raised as invalid path syntax under win32.
133 # They must be ignored for patterns can be checked too.
133 # They must be ignored for patterns can be checked too.
134 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
134 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
135 raise
135 raise
136 else:
136 else:
137 if stat.S_ISLNK(st.st_mode):
137 if stat.S_ISLNK(st.st_mode):
138 raise util.Abort(
138 raise util.Abort(
139 _('path %r traverses symbolic link %r')
139 _('path %r traverses symbolic link %r')
140 % (path, prefix))
140 % (path, prefix))
141 elif (stat.S_ISDIR(st.st_mode) and
141 elif (stat.S_ISDIR(st.st_mode) and
142 os.path.isdir(os.path.join(curpath, '.hg'))):
142 os.path.isdir(os.path.join(curpath, '.hg'))):
143 if not self.callback or not self.callback(curpath):
143 if not self.callback or not self.callback(curpath):
144 raise util.Abort(_("path '%s' is inside nested repo %r") %
144 raise util.Abort(_("path '%s' is inside nested repo %r") %
145 (path, prefix))
145 (path, prefix))
146 prefixes.append(normprefix)
146 prefixes.append(normprefix)
147 parts.pop()
147 parts.pop()
148 normparts.pop()
148 normparts.pop()
149
149
150 self.audited.add(normpath)
150 self.audited.add(normpath)
151 # only add prefixes to the cache after checking everything: we don't
151 # only add prefixes to the cache after checking everything: we don't
152 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
152 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
153 self.auditeddir.update(prefixes)
153 self.auditeddir.update(prefixes)
154
154
155 class abstractopener(object):
155 class abstractopener(object):
156 """Abstract base class; cannot be instantiated"""
156 """Abstract base class; cannot be instantiated"""
157
157
158 def __init__(self, *args, **kwargs):
158 def __init__(self, *args, **kwargs):
159 '''Prevent instantiation; don't call this from subclasses.'''
159 '''Prevent instantiation; don't call this from subclasses.'''
160 raise NotImplementedError('attempted instantiating ' + str(type(self)))
160 raise NotImplementedError('attempted instantiating ' + str(type(self)))
161
161
162 def read(self, path):
162 def read(self, path):
163 fp = self(path, 'rb')
163 fp = self(path, 'rb')
164 try:
164 try:
165 return fp.read()
165 return fp.read()
166 finally:
166 finally:
167 fp.close()
167 fp.close()
168
168
169 def write(self, path, data):
169 def write(self, path, data):
170 fp = self(path, 'wb')
170 fp = self(path, 'wb')
171 try:
171 try:
172 return fp.write(data)
172 return fp.write(data)
173 finally:
173 finally:
174 fp.close()
174 fp.close()
175
175
176 def append(self, path, data):
176 def append(self, path, data):
177 fp = self(path, 'ab')
177 fp = self(path, 'ab')
178 try:
178 try:
179 return fp.write(data)
179 return fp.write(data)
180 finally:
180 finally:
181 fp.close()
181 fp.close()
182
182
183 class opener(abstractopener):
183 class opener(abstractopener):
184 '''Open files relative to a base directory
184 '''Open files relative to a base directory
185
185
186 This class is used to hide the details of COW semantics and
186 This class is used to hide the details of COW semantics and
187 remote file access from higher level code.
187 remote file access from higher level code.
188 '''
188 '''
189 def __init__(self, base, audit=True):
189 def __init__(self, base, audit=True):
190 self.base = base
190 self.base = base
191 self._audit = audit
191 self._audit = audit
192 if audit:
192 if audit:
193 self.auditor = pathauditor(base)
193 self.auditor = pathauditor(base)
194 else:
194 else:
195 self.auditor = util.always
195 self.auditor = util.always
196 self.createmode = None
196 self.createmode = None
197 self._trustnlink = None
197 self._trustnlink = None
198
198
199 @util.propertycache
199 @util.propertycache
200 def _cansymlink(self):
200 def _cansymlink(self):
201 return util.checklink(self.base)
201 return util.checklink(self.base)
202
202
203 def _fixfilemode(self, name):
203 def _fixfilemode(self, name):
204 if self.createmode is None:
204 if self.createmode is None:
205 return
205 return
206 os.chmod(name, self.createmode & 0666)
206 os.chmod(name, self.createmode & 0666)
207
207
208 def __call__(self, path, mode="r", text=False, atomictemp=False):
208 def __call__(self, path, mode="r", text=False, atomictemp=False):
209 if self._audit:
209 if self._audit:
210 r = util.checkosfilename(path)
210 r = util.checkosfilename(path)
211 if r:
211 if r:
212 raise util.Abort("%s: %r" % (r, path))
212 raise util.Abort("%s: %r" % (r, path))
213 self.auditor(path)
213 self.auditor(path)
214 f = self.join(path)
214 f = self.join(path)
215
215
216 if not text and "b" not in mode:
216 if not text and "b" not in mode:
217 mode += "b" # for that other OS
217 mode += "b" # for that other OS
218
218
219 nlink = -1
219 nlink = -1
220 dirname, basename = os.path.split(f)
220 dirname, basename = os.path.split(f)
221 # If basename is empty, then the path is malformed because it points
221 # If basename is empty, then the path is malformed because it points
222 # to a directory. Let the posixfile() call below raise IOError.
222 # to a directory. Let the posixfile() call below raise IOError.
223 if basename and mode not in ('r', 'rb'):
223 if basename and mode not in ('r', 'rb'):
224 if atomictemp:
224 if atomictemp:
225 if not os.path.isdir(dirname):
225 if not os.path.isdir(dirname):
226 util.makedirs(dirname, self.createmode)
226 util.makedirs(dirname, self.createmode)
227 return util.atomictempfile(f, mode, self.createmode)
227 return util.atomictempfile(f, mode, self.createmode)
228 try:
228 try:
229 if 'w' in mode:
229 if 'w' in mode:
230 util.unlink(f)
230 util.unlink(f)
231 nlink = 0
231 nlink = 0
232 else:
232 else:
233 # nlinks() may behave differently for files on Windows
233 # nlinks() may behave differently for files on Windows
234 # shares if the file is open.
234 # shares if the file is open.
235 fd = util.posixfile(f)
235 fd = util.posixfile(f)
236 nlink = util.nlinks(f)
236 nlink = util.nlinks(f)
237 if nlink < 1:
237 if nlink < 1:
238 nlink = 2 # force mktempcopy (issue1922)
238 nlink = 2 # force mktempcopy (issue1922)
239 fd.close()
239 fd.close()
240 except (OSError, IOError), e:
240 except (OSError, IOError), e:
241 if e.errno != errno.ENOENT:
241 if e.errno != errno.ENOENT:
242 raise
242 raise
243 nlink = 0
243 nlink = 0
244 if not os.path.isdir(dirname):
244 if not os.path.isdir(dirname):
245 util.makedirs(dirname, self.createmode)
245 util.makedirs(dirname, self.createmode)
246 if nlink > 0:
246 if nlink > 0:
247 if self._trustnlink is None:
247 if self._trustnlink is None:
248 self._trustnlink = nlink > 1 or util.checknlink(f)
248 self._trustnlink = nlink > 1 or util.checknlink(f)
249 if nlink > 1 or not self._trustnlink:
249 if nlink > 1 or not self._trustnlink:
250 util.rename(util.mktempcopy(f), f)
250 util.rename(util.mktempcopy(f), f)
251 fp = util.posixfile(f, mode)
251 fp = util.posixfile(f, mode)
252 if nlink == 0:
252 if nlink == 0:
253 self._fixfilemode(f)
253 self._fixfilemode(f)
254 return fp
254 return fp
255
255
256 def symlink(self, src, dst):
256 def symlink(self, src, dst):
257 self.auditor(dst)
257 self.auditor(dst)
258 linkname = self.join(dst)
258 linkname = self.join(dst)
259 try:
259 try:
260 os.unlink(linkname)
260 os.unlink(linkname)
261 except OSError:
261 except OSError:
262 pass
262 pass
263
263
264 dirname = os.path.dirname(linkname)
264 dirname = os.path.dirname(linkname)
265 if not os.path.exists(dirname):
265 if not os.path.exists(dirname):
266 util.makedirs(dirname, self.createmode)
266 util.makedirs(dirname, self.createmode)
267
267
268 if self._cansymlink:
268 if self._cansymlink:
269 try:
269 try:
270 os.symlink(src, linkname)
270 os.symlink(src, linkname)
271 except OSError, err:
271 except OSError, err:
272 raise OSError(err.errno, _('could not symlink to %r: %s') %
272 raise OSError(err.errno, _('could not symlink to %r: %s') %
273 (src, err.strerror), linkname)
273 (src, err.strerror), linkname)
274 else:
274 else:
275 f = self(dst, "w")
275 f = self(dst, "w")
276 f.write(src)
276 f.write(src)
277 f.close()
277 f.close()
278 self._fixfilemode(dst)
278 self._fixfilemode(dst)
279
279
280 def audit(self, path):
280 def audit(self, path):
281 self.auditor(path)
281 self.auditor(path)
282
282
283 def join(self, path):
283 def join(self, path):
284 return os.path.join(self.base, path)
284 return os.path.join(self.base, path)
285
285
286 class filteropener(abstractopener):
286 class filteropener(abstractopener):
287 '''Wrapper opener for filtering filenames with a function.'''
287 '''Wrapper opener for filtering filenames with a function.'''
288
288
289 def __init__(self, opener, filter):
289 def __init__(self, opener, filter):
290 self._filter = filter
290 self._filter = filter
291 self._orig = opener
291 self._orig = opener
292
292
293 def __call__(self, path, *args, **kwargs):
293 def __call__(self, path, *args, **kwargs):
294 return self._orig(self._filter(path), *args, **kwargs)
294 return self._orig(self._filter(path), *args, **kwargs)
295
295
296 def canonpath(root, cwd, myname, auditor=None):
296 def canonpath(root, cwd, myname, auditor=None):
297 '''return the canonical path of myname, given cwd and root'''
297 '''return the canonical path of myname, given cwd and root'''
298 if util.endswithsep(root):
298 if util.endswithsep(root):
299 rootsep = root
299 rootsep = root
300 else:
300 else:
301 rootsep = root + os.sep
301 rootsep = root + os.sep
302 name = myname
302 name = myname
303 if not os.path.isabs(name):
303 if not os.path.isabs(name):
304 name = os.path.join(root, cwd, name)
304 name = os.path.join(root, cwd, name)
305 name = os.path.normpath(name)
305 name = os.path.normpath(name)
306 if auditor is None:
306 if auditor is None:
307 auditor = pathauditor(root)
307 auditor = pathauditor(root)
308 if name != rootsep and name.startswith(rootsep):
308 if name != rootsep and name.startswith(rootsep):
309 name = name[len(rootsep):]
309 name = name[len(rootsep):]
310 auditor(name)
310 auditor(name)
311 return util.pconvert(name)
311 return util.pconvert(name)
312 elif name == root:
312 elif name == root:
313 return ''
313 return ''
314 else:
314 else:
315 # Determine whether `name' is in the hierarchy at or beneath `root',
315 # Determine whether `name' is in the hierarchy at or beneath `root',
316 # by iterating name=dirname(name) until that causes no change (can't
316 # by iterating name=dirname(name) until that causes no change (can't
317 # check name == '/', because that doesn't work on windows). For each
317 # check name == '/', because that doesn't work on windows). For each
318 # `name', compare dev/inode numbers. If they match, the list `rel'
318 # `name', compare dev/inode numbers. If they match, the list `rel'
319 # holds the reversed list of components making up the relative file
319 # holds the reversed list of components making up the relative file
320 # name we want.
320 # name we want.
321 root_st = os.stat(root)
321 root_st = os.stat(root)
322 rel = []
322 rel = []
323 while True:
323 while True:
324 try:
324 try:
325 name_st = os.stat(name)
325 name_st = os.stat(name)
326 except OSError:
326 except OSError:
327 name_st = None
327 name_st = None
328 if name_st and util.samestat(name_st, root_st):
328 if name_st and util.samestat(name_st, root_st):
329 if not rel:
329 if not rel:
330 # name was actually the same as root (maybe a symlink)
330 # name was actually the same as root (maybe a symlink)
331 return ''
331 return ''
332 rel.reverse()
332 rel.reverse()
333 name = os.path.join(*rel)
333 name = os.path.join(*rel)
334 auditor(name)
334 auditor(name)
335 return util.pconvert(name)
335 return util.pconvert(name)
336 dirname, basename = os.path.split(name)
336 dirname, basename = os.path.split(name)
337 rel.append(basename)
337 rel.append(basename)
338 if dirname == name:
338 if dirname == name:
339 break
339 break
340 name = dirname
340 name = dirname
341
341
342 raise util.Abort('%s not under root' % myname)
342 raise util.Abort('%s not under root' % myname)
343
343
344 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
344 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
345 '''yield every hg repository under path, recursively.'''
345 '''yield every hg repository under path, recursively.'''
346 def errhandler(err):
346 def errhandler(err):
347 if err.filename == path:
347 if err.filename == path:
348 raise err
348 raise err
349 samestat = getattr(os.path, 'samestat', None)
349 samestat = getattr(os.path, 'samestat', None)
350 if followsym and samestat is not None:
350 if followsym and samestat is not None:
351 def adddir(dirlst, dirname):
351 def adddir(dirlst, dirname):
352 match = False
352 match = False
353 dirstat = os.stat(dirname)
353 dirstat = os.stat(dirname)
354 for lstdirstat in dirlst:
354 for lstdirstat in dirlst:
355 if samestat(dirstat, lstdirstat):
355 if samestat(dirstat, lstdirstat):
356 match = True
356 match = True
357 break
357 break
358 if not match:
358 if not match:
359 dirlst.append(dirstat)
359 dirlst.append(dirstat)
360 return not match
360 return not match
361 else:
361 else:
362 followsym = False
362 followsym = False
363
363
364 if (seen_dirs is None) and followsym:
364 if (seen_dirs is None) and followsym:
365 seen_dirs = []
365 seen_dirs = []
366 adddir(seen_dirs, path)
366 adddir(seen_dirs, path)
367 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
367 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
368 dirs.sort()
368 dirs.sort()
369 if '.hg' in dirs:
369 if '.hg' in dirs:
370 yield root # found a repository
370 yield root # found a repository
371 qroot = os.path.join(root, '.hg', 'patches')
371 qroot = os.path.join(root, '.hg', 'patches')
372 if os.path.isdir(os.path.join(qroot, '.hg')):
372 if os.path.isdir(os.path.join(qroot, '.hg')):
373 yield qroot # we have a patch queue repo here
373 yield qroot # we have a patch queue repo here
374 if recurse:
374 if recurse:
375 # avoid recursing inside the .hg directory
375 # avoid recursing inside the .hg directory
376 dirs.remove('.hg')
376 dirs.remove('.hg')
377 else:
377 else:
378 dirs[:] = [] # don't descend further
378 dirs[:] = [] # don't descend further
379 elif followsym:
379 elif followsym:
380 newdirs = []
380 newdirs = []
381 for d in dirs:
381 for d in dirs:
382 fname = os.path.join(root, d)
382 fname = os.path.join(root, d)
383 if adddir(seen_dirs, fname):
383 if adddir(seen_dirs, fname):
384 if os.path.islink(fname):
384 if os.path.islink(fname):
385 for hgname in walkrepos(fname, True, seen_dirs):
385 for hgname in walkrepos(fname, True, seen_dirs):
386 yield hgname
386 yield hgname
387 else:
387 else:
388 newdirs.append(d)
388 newdirs.append(d)
389 dirs[:] = newdirs
389 dirs[:] = newdirs
390
390
391 def osrcpath():
391 def osrcpath():
392 '''return default os-specific hgrc search path'''
392 '''return default os-specific hgrc search path'''
393 path = systemrcpath()
393 path = systemrcpath()
394 path.extend(userrcpath())
394 path.extend(userrcpath())
395 path = [os.path.normpath(f) for f in path]
395 path = [os.path.normpath(f) for f in path]
396 return path
396 return path
397
397
398 _rcpath = None
398 _rcpath = None
399
399
400 def rcpath():
400 def rcpath():
401 '''return hgrc search path. if env var HGRCPATH is set, use it.
401 '''return hgrc search path. if env var HGRCPATH is set, use it.
402 for each item in path, if directory, use files ending in .rc,
402 for each item in path, if directory, use files ending in .rc,
403 else use item.
403 else use item.
404 make HGRCPATH empty to only look in .hg/hgrc of current repo.
404 make HGRCPATH empty to only look in .hg/hgrc of current repo.
405 if no HGRCPATH, use default os-specific path.'''
405 if no HGRCPATH, use default os-specific path.'''
406 global _rcpath
406 global _rcpath
407 if _rcpath is None:
407 if _rcpath is None:
408 if 'HGRCPATH' in os.environ:
408 if 'HGRCPATH' in os.environ:
409 _rcpath = []
409 _rcpath = []
410 for p in os.environ['HGRCPATH'].split(os.pathsep):
410 for p in os.environ['HGRCPATH'].split(os.pathsep):
411 if not p:
411 if not p:
412 continue
412 continue
413 p = util.expandpath(p)
413 p = util.expandpath(p)
414 if os.path.isdir(p):
414 if os.path.isdir(p):
415 for f, kind in osutil.listdir(p):
415 for f, kind in osutil.listdir(p):
416 if f.endswith('.rc'):
416 if f.endswith('.rc'):
417 _rcpath.append(os.path.join(p, f))
417 _rcpath.append(os.path.join(p, f))
418 else:
418 else:
419 _rcpath.append(p)
419 _rcpath.append(p)
420 else:
420 else:
421 _rcpath = osrcpath()
421 _rcpath = osrcpath()
422 return _rcpath
422 return _rcpath
423
423
424 if os.name != 'nt':
424 if os.name != 'nt':
425
425
426 def rcfiles(path):
426 def rcfiles(path):
427 rcs = [os.path.join(path, 'hgrc')]
427 rcs = [os.path.join(path, 'hgrc')]
428 rcdir = os.path.join(path, 'hgrc.d')
428 rcdir = os.path.join(path, 'hgrc.d')
429 try:
429 try:
430 rcs.extend([os.path.join(rcdir, f)
430 rcs.extend([os.path.join(rcdir, f)
431 for f, kind in osutil.listdir(rcdir)
431 for f, kind in osutil.listdir(rcdir)
432 if f.endswith(".rc")])
432 if f.endswith(".rc")])
433 except OSError:
433 except OSError:
434 pass
434 pass
435 return rcs
435 return rcs
436
436
437 def systemrcpath():
437 def systemrcpath():
438 path = []
438 path = []
439 if sys.platform == 'plan9':
439 if sys.platform == 'plan9':
440 root = '/lib/mercurial'
440 root = '/lib/mercurial'
441 else:
441 else:
442 root = '/etc/mercurial'
442 root = '/etc/mercurial'
443 # old mod_python does not set sys.argv
443 # old mod_python does not set sys.argv
444 if len(getattr(sys, 'argv', [])) > 0:
444 if len(getattr(sys, 'argv', [])) > 0:
445 p = os.path.dirname(os.path.dirname(sys.argv[0]))
445 p = os.path.dirname(os.path.dirname(sys.argv[0]))
446 path.extend(rcfiles(os.path.join(p, root)))
446 path.extend(rcfiles(os.path.join(p, root)))
447 path.extend(rcfiles(root))
447 path.extend(rcfiles(root))
448 return path
448 return path
449
449
450 def userrcpath():
450 def userrcpath():
451 if sys.platform == 'plan9':
451 if sys.platform == 'plan9':
452 return [os.environ['home'] + '/lib/hgrc']
452 return [os.environ['home'] + '/lib/hgrc']
453 else:
453 else:
454 return [os.path.expanduser('~/.hgrc')]
454 return [os.path.expanduser('~/.hgrc')]
455
455
456 else:
456 else:
457
457
458 _HKEY_LOCAL_MACHINE = 0x80000002L
458 _HKEY_LOCAL_MACHINE = 0x80000002L
459
459
460 def systemrcpath():
460 def systemrcpath():
461 '''return default os-specific hgrc search path'''
461 '''return default os-specific hgrc search path'''
462 rcpath = []
462 rcpath = []
463 filename = util.executablepath()
463 filename = util.executablepath()
464 # Use mercurial.ini found in directory with hg.exe
464 # Use mercurial.ini found in directory with hg.exe
465 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
465 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
466 if os.path.isfile(progrc):
466 if os.path.isfile(progrc):
467 rcpath.append(progrc)
467 rcpath.append(progrc)
468 return rcpath
468 return rcpath
469 # Use hgrc.d found in directory with hg.exe
469 # Use hgrc.d found in directory with hg.exe
470 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
470 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
471 if os.path.isdir(progrcd):
471 if os.path.isdir(progrcd):
472 for f, kind in osutil.listdir(progrcd):
472 for f, kind in osutil.listdir(progrcd):
473 if f.endswith('.rc'):
473 if f.endswith('.rc'):
474 rcpath.append(os.path.join(progrcd, f))
474 rcpath.append(os.path.join(progrcd, f))
475 return rcpath
475 return rcpath
476 # else look for a system rcpath in the registry
476 # else look for a system rcpath in the registry
477 value = util.lookupreg('SOFTWARE\\Mercurial', None,
477 value = util.lookupreg('SOFTWARE\\Mercurial', None,
478 _HKEY_LOCAL_MACHINE)
478 _HKEY_LOCAL_MACHINE)
479 if not isinstance(value, str) or not value:
479 if not isinstance(value, str) or not value:
480 return rcpath
480 return rcpath
481 value = util.localpath(value)
481 value = util.localpath(value)
482 for p in value.split(os.pathsep):
482 for p in value.split(os.pathsep):
483 if p.lower().endswith('mercurial.ini'):
483 if p.lower().endswith('mercurial.ini'):
484 rcpath.append(p)
484 rcpath.append(p)
485 elif os.path.isdir(p):
485 elif os.path.isdir(p):
486 for f, kind in osutil.listdir(p):
486 for f, kind in osutil.listdir(p):
487 if f.endswith('.rc'):
487 if f.endswith('.rc'):
488 rcpath.append(os.path.join(p, f))
488 rcpath.append(os.path.join(p, f))
489 return rcpath
489 return rcpath
490
490
491 def userrcpath():
491 def userrcpath():
492 '''return os-specific hgrc search path to the user dir'''
492 '''return os-specific hgrc search path to the user dir'''
493 home = os.path.expanduser('~')
493 home = os.path.expanduser('~')
494 path = [os.path.join(home, 'mercurial.ini'),
494 path = [os.path.join(home, 'mercurial.ini'),
495 os.path.join(home, '.hgrc')]
495 os.path.join(home, '.hgrc')]
496 userprofile = os.environ.get('USERPROFILE')
496 userprofile = os.environ.get('USERPROFILE')
497 if userprofile:
497 if userprofile:
498 path.append(os.path.join(userprofile, 'mercurial.ini'))
498 path.append(os.path.join(userprofile, 'mercurial.ini'))
499 path.append(os.path.join(userprofile, '.hgrc'))
499 path.append(os.path.join(userprofile, '.hgrc'))
500 return path
500 return path
501
501
502 def revsingle(repo, revspec, default='.'):
502 def revsingle(repo, revspec, default='.'):
503 if not revspec:
503 if not revspec:
504 return repo[default]
504 return repo[default]
505
505
506 l = revrange(repo, [revspec])
506 l = revrange(repo, [revspec])
507 if len(l) < 1:
507 if len(l) < 1:
508 raise util.Abort(_('empty revision set'))
508 raise util.Abort(_('empty revision set'))
509 return repo[l[-1]]
509 return repo[l[-1]]
510
510
511 def revpair(repo, revs):
511 def revpair(repo, revs):
512 if not revs:
512 if not revs:
513 return repo.dirstate.p1(), None
513 return repo.dirstate.p1(), None
514
514
515 l = revrange(repo, revs)
515 l = revrange(repo, revs)
516
516
517 if len(l) == 0:
517 if len(l) == 0:
518 return repo.dirstate.p1(), None
518 return repo.dirstate.p1(), None
519
519
520 if len(l) == 1:
520 if len(l) == 1:
521 return repo.lookup(l[0]), None
521 return repo.lookup(l[0]), None
522
522
523 return repo.lookup(l[0]), repo.lookup(l[-1])
523 return repo.lookup(l[0]), repo.lookup(l[-1])
524
524
525 _revrangesep = ':'
525 _revrangesep = ':'
526
526
527 def revrange(repo, revs):
527 def revrange(repo, revs):
528 """Yield revision as strings from a list of revision specifications."""
528 """Yield revision as strings from a list of revision specifications."""
529
529
530 def revfix(repo, val, defval):
530 def revfix(repo, val, defval):
531 if not val and val != 0 and defval is not None:
531 if not val and val != 0 and defval is not None:
532 return defval
532 return defval
533 return repo[val].rev()
533 return repo[val].rev()
534
534
535 seen, l = set(), []
535 seen, l = set(), []
536 for spec in revs:
536 for spec in revs:
537 if l and not seen:
538 seen = set(l)
537 # attempt to parse old-style ranges first to deal with
539 # attempt to parse old-style ranges first to deal with
538 # things like old-tag which contain query metacharacters
540 # things like old-tag which contain query metacharacters
539 try:
541 try:
540 if isinstance(spec, int):
542 if isinstance(spec, int):
541 seen.add(spec)
543 seen.add(spec)
542 l.append(spec)
544 l.append(spec)
543 continue
545 continue
544
546
545 if _revrangesep in spec:
547 if _revrangesep in spec:
546 start, end = spec.split(_revrangesep, 1)
548 start, end = spec.split(_revrangesep, 1)
547 start = revfix(repo, start, 0)
549 start = revfix(repo, start, 0)
548 end = revfix(repo, end, len(repo) - 1)
550 end = revfix(repo, end, len(repo) - 1)
549 step = start > end and -1 or 1
551 step = start > end and -1 or 1
550 for rev in xrange(start, end + step, step):
552 if not seen and not l:
551 if rev in seen:
553 # by far the most common case: revs = ["-1:0"]
552 continue
554 l = range(start, end + step, step)
553 seen.add(rev)
555 # defer syncing seen until next iteration
554 l.append(rev)
556 continue
557 newrevs = set(xrange(start, end + step, step))
558 if seen:
559 newrevs.difference_update(seen)
560 seen.union(newrevs)
561 else:
562 seen = newrevs
563 l.extend(sorted(newrevs, reverse=start > end))
555 continue
564 continue
556 elif spec and spec in repo: # single unquoted rev
565 elif spec and spec in repo: # single unquoted rev
557 rev = revfix(repo, spec, None)
566 rev = revfix(repo, spec, None)
558 if rev in seen:
567 if rev in seen:
559 continue
568 continue
560 seen.add(rev)
569 seen.add(rev)
561 l.append(rev)
570 l.append(rev)
562 continue
571 continue
563 except error.RepoLookupError:
572 except error.RepoLookupError:
564 pass
573 pass
565
574
566 # fall through to new-style queries if old-style fails
575 # fall through to new-style queries if old-style fails
567 m = revset.match(repo.ui, spec)
576 m = revset.match(repo.ui, spec)
568 for r in m(repo, range(len(repo))):
577 for r in m(repo, range(len(repo))):
569 if r not in seen:
578 if r not in seen:
570 l.append(r)
579 l.append(r)
571 seen.update(l)
580 seen.update(l)
572
581
573 return l
582 return l
574
583
575 def expandpats(pats):
584 def expandpats(pats):
576 if not util.expandglobs:
585 if not util.expandglobs:
577 return list(pats)
586 return list(pats)
578 ret = []
587 ret = []
579 for p in pats:
588 for p in pats:
580 kind, name = matchmod._patsplit(p, None)
589 kind, name = matchmod._patsplit(p, None)
581 if kind is None:
590 if kind is None:
582 try:
591 try:
583 globbed = glob.glob(name)
592 globbed = glob.glob(name)
584 except re.error:
593 except re.error:
585 globbed = [name]
594 globbed = [name]
586 if globbed:
595 if globbed:
587 ret.extend(globbed)
596 ret.extend(globbed)
588 continue
597 continue
589 ret.append(p)
598 ret.append(p)
590 return ret
599 return ret
591
600
592 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
601 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
593 if pats == ("",):
602 if pats == ("",):
594 pats = []
603 pats = []
595 if not globbed and default == 'relpath':
604 if not globbed and default == 'relpath':
596 pats = expandpats(pats or [])
605 pats = expandpats(pats or [])
597
606
598 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
607 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
599 default)
608 default)
600 def badfn(f, msg):
609 def badfn(f, msg):
601 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
610 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
602 m.bad = badfn
611 m.bad = badfn
603 return m, pats
612 return m, pats
604
613
605 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
614 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
606 return matchandpats(ctx, pats, opts, globbed, default)[0]
615 return matchandpats(ctx, pats, opts, globbed, default)[0]
607
616
608 def matchall(repo):
617 def matchall(repo):
609 return matchmod.always(repo.root, repo.getcwd())
618 return matchmod.always(repo.root, repo.getcwd())
610
619
611 def matchfiles(repo, files):
620 def matchfiles(repo, files):
612 return matchmod.exact(repo.root, repo.getcwd(), files)
621 return matchmod.exact(repo.root, repo.getcwd(), files)
613
622
614 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
623 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
615 if dry_run is None:
624 if dry_run is None:
616 dry_run = opts.get('dry_run')
625 dry_run = opts.get('dry_run')
617 if similarity is None:
626 if similarity is None:
618 similarity = float(opts.get('similarity') or 0)
627 similarity = float(opts.get('similarity') or 0)
619 # we'd use status here, except handling of symlinks and ignore is tricky
628 # we'd use status here, except handling of symlinks and ignore is tricky
620 added, unknown, deleted, removed = [], [], [], []
629 added, unknown, deleted, removed = [], [], [], []
621 audit_path = pathauditor(repo.root)
630 audit_path = pathauditor(repo.root)
622 m = match(repo[None], pats, opts)
631 m = match(repo[None], pats, opts)
623 rejected = []
632 rejected = []
624 m.bad = lambda x, y: rejected.append(x)
633 m.bad = lambda x, y: rejected.append(x)
625
634
626 for abs in repo.walk(m):
635 for abs in repo.walk(m):
627 target = repo.wjoin(abs)
636 target = repo.wjoin(abs)
628 good = True
637 good = True
629 try:
638 try:
630 audit_path(abs)
639 audit_path(abs)
631 except (OSError, util.Abort):
640 except (OSError, util.Abort):
632 good = False
641 good = False
633 rel = m.rel(abs)
642 rel = m.rel(abs)
634 exact = m.exact(abs)
643 exact = m.exact(abs)
635 if good and abs not in repo.dirstate:
644 if good and abs not in repo.dirstate:
636 unknown.append(abs)
645 unknown.append(abs)
637 if repo.ui.verbose or not exact:
646 if repo.ui.verbose or not exact:
638 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
647 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
639 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
648 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
640 or (os.path.isdir(target) and not os.path.islink(target))):
649 or (os.path.isdir(target) and not os.path.islink(target))):
641 deleted.append(abs)
650 deleted.append(abs)
642 if repo.ui.verbose or not exact:
651 if repo.ui.verbose or not exact:
643 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
652 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
644 # for finding renames
653 # for finding renames
645 elif repo.dirstate[abs] == 'r':
654 elif repo.dirstate[abs] == 'r':
646 removed.append(abs)
655 removed.append(abs)
647 elif repo.dirstate[abs] == 'a':
656 elif repo.dirstate[abs] == 'a':
648 added.append(abs)
657 added.append(abs)
649 copies = {}
658 copies = {}
650 if similarity > 0:
659 if similarity > 0:
651 for old, new, score in similar.findrenames(repo,
660 for old, new, score in similar.findrenames(repo,
652 added + unknown, removed + deleted, similarity):
661 added + unknown, removed + deleted, similarity):
653 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
662 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
654 repo.ui.status(_('recording removal of %s as rename to %s '
663 repo.ui.status(_('recording removal of %s as rename to %s '
655 '(%d%% similar)\n') %
664 '(%d%% similar)\n') %
656 (m.rel(old), m.rel(new), score * 100))
665 (m.rel(old), m.rel(new), score * 100))
657 copies[new] = old
666 copies[new] = old
658
667
659 if not dry_run:
668 if not dry_run:
660 wctx = repo[None]
669 wctx = repo[None]
661 wlock = repo.wlock()
670 wlock = repo.wlock()
662 try:
671 try:
663 wctx.forget(deleted)
672 wctx.forget(deleted)
664 wctx.add(unknown)
673 wctx.add(unknown)
665 for new, old in copies.iteritems():
674 for new, old in copies.iteritems():
666 wctx.copy(old, new)
675 wctx.copy(old, new)
667 finally:
676 finally:
668 wlock.release()
677 wlock.release()
669
678
670 for f in rejected:
679 for f in rejected:
671 if f in m.files():
680 if f in m.files():
672 return 1
681 return 1
673 return 0
682 return 0
674
683
675 def updatedir(ui, repo, patches, similarity=0):
684 def updatedir(ui, repo, patches, similarity=0):
676 '''Update dirstate after patch application according to metadata'''
685 '''Update dirstate after patch application according to metadata'''
677 if not patches:
686 if not patches:
678 return []
687 return []
679 copies = []
688 copies = []
680 removes = set()
689 removes = set()
681 cfiles = patches.keys()
690 cfiles = patches.keys()
682 cwd = repo.getcwd()
691 cwd = repo.getcwd()
683 if cwd:
692 if cwd:
684 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
693 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
685 for f in patches:
694 for f in patches:
686 gp = patches[f]
695 gp = patches[f]
687 if not gp:
696 if not gp:
688 continue
697 continue
689 if gp.op == 'RENAME':
698 if gp.op == 'RENAME':
690 copies.append((gp.oldpath, gp.path))
699 copies.append((gp.oldpath, gp.path))
691 removes.add(gp.oldpath)
700 removes.add(gp.oldpath)
692 elif gp.op == 'COPY':
701 elif gp.op == 'COPY':
693 copies.append((gp.oldpath, gp.path))
702 copies.append((gp.oldpath, gp.path))
694 elif gp.op == 'DELETE':
703 elif gp.op == 'DELETE':
695 removes.add(gp.path)
704 removes.add(gp.path)
696
705
697 wctx = repo[None]
706 wctx = repo[None]
698 for src, dst in copies:
707 for src, dst in copies:
699 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
708 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
700 if (not similarity) and removes:
709 if (not similarity) and removes:
701 wctx.remove(sorted(removes), True)
710 wctx.remove(sorted(removes), True)
702
711
703 for f in patches:
712 for f in patches:
704 gp = patches[f]
713 gp = patches[f]
705 if gp and gp.mode:
714 if gp and gp.mode:
706 islink, isexec = gp.mode
715 islink, isexec = gp.mode
707 dst = repo.wjoin(gp.path)
716 dst = repo.wjoin(gp.path)
708 # patch won't create empty files
717 # patch won't create empty files
709 if gp.op == 'ADD' and not os.path.lexists(dst):
718 if gp.op == 'ADD' and not os.path.lexists(dst):
710 flags = (isexec and 'x' or '') + (islink and 'l' or '')
719 flags = (isexec and 'x' or '') + (islink and 'l' or '')
711 repo.wwrite(gp.path, '', flags)
720 repo.wwrite(gp.path, '', flags)
712 util.setflags(dst, islink, isexec)
721 util.setflags(dst, islink, isexec)
713 addremove(repo, cfiles, similarity=similarity)
722 addremove(repo, cfiles, similarity=similarity)
714 files = patches.keys()
723 files = patches.keys()
715 files.extend([r for r in removes if r not in files])
724 files.extend([r for r in removes if r not in files])
716 return sorted(files)
725 return sorted(files)
717
726
718 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
727 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
719 """Update the dirstate to reflect the intent of copying src to dst. For
728 """Update the dirstate to reflect the intent of copying src to dst. For
720 different reasons it might not end with dst being marked as copied from src.
729 different reasons it might not end with dst being marked as copied from src.
721 """
730 """
722 origsrc = repo.dirstate.copied(src) or src
731 origsrc = repo.dirstate.copied(src) or src
723 if dst == origsrc: # copying back a copy?
732 if dst == origsrc: # copying back a copy?
724 if repo.dirstate[dst] not in 'mn' and not dryrun:
733 if repo.dirstate[dst] not in 'mn' and not dryrun:
725 repo.dirstate.normallookup(dst)
734 repo.dirstate.normallookup(dst)
726 else:
735 else:
727 if repo.dirstate[origsrc] == 'a' and origsrc == src:
736 if repo.dirstate[origsrc] == 'a' and origsrc == src:
728 if not ui.quiet:
737 if not ui.quiet:
729 ui.warn(_("%s has not been committed yet, so no copy "
738 ui.warn(_("%s has not been committed yet, so no copy "
730 "data will be stored for %s.\n")
739 "data will be stored for %s.\n")
731 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
740 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
732 if repo.dirstate[dst] in '?r' and not dryrun:
741 if repo.dirstate[dst] in '?r' and not dryrun:
733 wctx.add([dst])
742 wctx.add([dst])
734 elif not dryrun:
743 elif not dryrun:
735 wctx.copy(origsrc, dst)
744 wctx.copy(origsrc, dst)
736
745
737 def readrequires(opener, supported):
746 def readrequires(opener, supported):
738 '''Reads and parses .hg/requires and checks if all entries found
747 '''Reads and parses .hg/requires and checks if all entries found
739 are in the list of supported features.'''
748 are in the list of supported features.'''
740 requirements = set(opener.read("requires").splitlines())
749 requirements = set(opener.read("requires").splitlines())
741 missings = []
750 missings = []
742 for r in requirements:
751 for r in requirements:
743 if r not in supported:
752 if r not in supported:
744 if not r or not r[0].isalnum():
753 if not r or not r[0].isalnum():
745 raise error.RequirementError(_(".hg/requires file is corrupt"))
754 raise error.RequirementError(_(".hg/requires file is corrupt"))
746 missings.append(r)
755 missings.append(r)
747 missings.sort()
756 missings.sort()
748 if missings:
757 if missings:
749 raise error.RequirementError(_("unknown repository format: "
758 raise error.RequirementError(_("unknown repository format: "
750 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
759 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
751 return requirements
760 return requirements
752
761
753 class filecacheentry(object):
762 class filecacheentry(object):
754 def __init__(self, path):
763 def __init__(self, path):
755 self.path = path
764 self.path = path
756 self.cachestat = filecacheentry.stat(self.path)
765 self.cachestat = filecacheentry.stat(self.path)
757
766
758 if self.cachestat:
767 if self.cachestat:
759 self._cacheable = self.cachestat.cacheable()
768 self._cacheable = self.cachestat.cacheable()
760 else:
769 else:
761 # None means we don't know yet
770 # None means we don't know yet
762 self._cacheable = None
771 self._cacheable = None
763
772
764 def refresh(self):
773 def refresh(self):
765 if self.cacheable():
774 if self.cacheable():
766 self.cachestat = filecacheentry.stat(self.path)
775 self.cachestat = filecacheentry.stat(self.path)
767
776
768 def cacheable(self):
777 def cacheable(self):
769 if self._cacheable is not None:
778 if self._cacheable is not None:
770 return self._cacheable
779 return self._cacheable
771
780
772 # we don't know yet, assume it is for now
781 # we don't know yet, assume it is for now
773 return True
782 return True
774
783
775 def changed(self):
784 def changed(self):
776 # no point in going further if we can't cache it
785 # no point in going further if we can't cache it
777 if not self.cacheable():
786 if not self.cacheable():
778 return True
787 return True
779
788
780 newstat = filecacheentry.stat(self.path)
789 newstat = filecacheentry.stat(self.path)
781
790
782 # we may not know if it's cacheable yet, check again now
791 # we may not know if it's cacheable yet, check again now
783 if newstat and self._cacheable is None:
792 if newstat and self._cacheable is None:
784 self._cacheable = newstat.cacheable()
793 self._cacheable = newstat.cacheable()
785
794
786 # check again
795 # check again
787 if not self._cacheable:
796 if not self._cacheable:
788 return True
797 return True
789
798
790 if self.cachestat != newstat:
799 if self.cachestat != newstat:
791 self.cachestat = newstat
800 self.cachestat = newstat
792 return True
801 return True
793 else:
802 else:
794 return False
803 return False
795
804
796 @staticmethod
805 @staticmethod
797 def stat(path):
806 def stat(path):
798 try:
807 try:
799 return util.cachestat(path)
808 return util.cachestat(path)
800 except OSError, e:
809 except OSError, e:
801 if e.errno != errno.ENOENT:
810 if e.errno != errno.ENOENT:
802 raise
811 raise
803
812
804 class filecache(object):
813 class filecache(object):
805 '''A property like decorator that tracks a file under .hg/ for updates.
814 '''A property like decorator that tracks a file under .hg/ for updates.
806
815
807 Records stat info when called in _filecache.
816 Records stat info when called in _filecache.
808
817
809 On subsequent calls, compares old stat info with new info, and recreates
818 On subsequent calls, compares old stat info with new info, and recreates
810 the object when needed, updating the new stat info in _filecache.
819 the object when needed, updating the new stat info in _filecache.
811
820
812 Mercurial either atomic renames or appends for files under .hg,
821 Mercurial either atomic renames or appends for files under .hg,
813 so to ensure the cache is reliable we need the filesystem to be able
822 so to ensure the cache is reliable we need the filesystem to be able
814 to tell us if a file has been replaced. If it can't, we fallback to
823 to tell us if a file has been replaced. If it can't, we fallback to
815 recreating the object on every call (essentially the same behaviour as
824 recreating the object on every call (essentially the same behaviour as
816 propertycache).'''
825 propertycache).'''
817 def __init__(self, path):
826 def __init__(self, path):
818 self.path = path
827 self.path = path
819
828
820 def join(self, obj, fname):
829 def join(self, obj, fname):
821 """Used to compute the runtime path of the cached file.
830 """Used to compute the runtime path of the cached file.
822
831
823 Users should subclass filecache and provide their own version of this
832 Users should subclass filecache and provide their own version of this
824 function to call the appropriate join function on 'obj' (an instance
833 function to call the appropriate join function on 'obj' (an instance
825 of the class that its member function was decorated).
834 of the class that its member function was decorated).
826 """
835 """
827 return obj.join(fname)
836 return obj.join(fname)
828
837
829 def __call__(self, func):
838 def __call__(self, func):
830 self.func = func
839 self.func = func
831 self.name = func.__name__
840 self.name = func.__name__
832 return self
841 return self
833
842
834 def __get__(self, obj, type=None):
843 def __get__(self, obj, type=None):
835 # do we need to check if the file changed?
844 # do we need to check if the file changed?
836 if self.name in obj.__dict__:
845 if self.name in obj.__dict__:
837 return obj.__dict__[self.name]
846 return obj.__dict__[self.name]
838
847
839 entry = obj._filecache.get(self.name)
848 entry = obj._filecache.get(self.name)
840
849
841 if entry:
850 if entry:
842 if entry.changed():
851 if entry.changed():
843 entry.obj = self.func(obj)
852 entry.obj = self.func(obj)
844 else:
853 else:
845 path = self.join(obj, self.path)
854 path = self.join(obj, self.path)
846
855
847 # We stat -before- creating the object so our cache doesn't lie if
856 # We stat -before- creating the object so our cache doesn't lie if
848 # a writer modified between the time we read and stat
857 # a writer modified between the time we read and stat
849 entry = filecacheentry(path)
858 entry = filecacheentry(path)
850 entry.obj = self.func(obj)
859 entry.obj = self.func(obj)
851
860
852 obj._filecache[self.name] = entry
861 obj._filecache[self.name] = entry
853
862
854 obj.__dict__[self.name] = entry.obj
863 obj.__dict__[self.name] = entry.obj
855 return entry.obj
864 return entry.obj
856
865
857 def __set__(self, obj, value):
866 def __set__(self, obj, value):
858 if self.name in obj._filecache:
867 if self.name in obj._filecache:
859 obj._filecache[self.name].obj = value # update cached copy
868 obj._filecache[self.name].obj = value # update cached copy
860 obj.__dict__[self.name] = value # update copy returned by obj.x
869 obj.__dict__[self.name] = value # update copy returned by obj.x
861
870
862 def __delete__(self, obj):
871 def __delete__(self, obj):
863 try:
872 try:
864 del obj.__dict__[self.name]
873 del obj.__dict__[self.name]
865 except KeyError:
874 except KeyError:
866 raise AttributeError, self.name
875 raise AttributeError, self.name
General Comments 0
You need to be logged in to leave comments. Login now