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