##// END OF EJS Templates
scmutil: use context instead of lookup
Matt Mackall -
r16379:5cbfbb83 default
parent child Browse files
Show More
@@ -1,859 +1,859 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 # old mod_python does not set sys.argv
439 # old mod_python does not set sys.argv
440 if len(getattr(sys, 'argv', [])) > 0:
440 if len(getattr(sys, 'argv', [])) > 0:
441 p = os.path.dirname(os.path.dirname(sys.argv[0]))
441 p = os.path.dirname(os.path.dirname(sys.argv[0]))
442 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
442 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
443 path.extend(rcfiles('/etc/mercurial'))
443 path.extend(rcfiles('/etc/mercurial'))
444 return path
444 return path
445
445
446 def userrcpath():
446 def userrcpath():
447 return [os.path.expanduser('~/.hgrc')]
447 return [os.path.expanduser('~/.hgrc')]
448
448
449 else:
449 else:
450
450
451 _HKEY_LOCAL_MACHINE = 0x80000002L
451 _HKEY_LOCAL_MACHINE = 0x80000002L
452
452
453 def systemrcpath():
453 def systemrcpath():
454 '''return default os-specific hgrc search path'''
454 '''return default os-specific hgrc search path'''
455 rcpath = []
455 rcpath = []
456 filename = util.executablepath()
456 filename = util.executablepath()
457 # Use mercurial.ini found in directory with hg.exe
457 # Use mercurial.ini found in directory with hg.exe
458 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
458 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
459 if os.path.isfile(progrc):
459 if os.path.isfile(progrc):
460 rcpath.append(progrc)
460 rcpath.append(progrc)
461 return rcpath
461 return rcpath
462 # Use hgrc.d found in directory with hg.exe
462 # Use hgrc.d found in directory with hg.exe
463 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
463 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
464 if os.path.isdir(progrcd):
464 if os.path.isdir(progrcd):
465 for f, kind in osutil.listdir(progrcd):
465 for f, kind in osutil.listdir(progrcd):
466 if f.endswith('.rc'):
466 if f.endswith('.rc'):
467 rcpath.append(os.path.join(progrcd, f))
467 rcpath.append(os.path.join(progrcd, f))
468 return rcpath
468 return rcpath
469 # else look for a system rcpath in the registry
469 # else look for a system rcpath in the registry
470 value = util.lookupreg('SOFTWARE\\Mercurial', None,
470 value = util.lookupreg('SOFTWARE\\Mercurial', None,
471 _HKEY_LOCAL_MACHINE)
471 _HKEY_LOCAL_MACHINE)
472 if not isinstance(value, str) or not value:
472 if not isinstance(value, str) or not value:
473 return rcpath
473 return rcpath
474 value = util.localpath(value)
474 value = util.localpath(value)
475 for p in value.split(os.pathsep):
475 for p in value.split(os.pathsep):
476 if p.lower().endswith('mercurial.ini'):
476 if p.lower().endswith('mercurial.ini'):
477 rcpath.append(p)
477 rcpath.append(p)
478 elif os.path.isdir(p):
478 elif os.path.isdir(p):
479 for f, kind in osutil.listdir(p):
479 for f, kind in osutil.listdir(p):
480 if f.endswith('.rc'):
480 if f.endswith('.rc'):
481 rcpath.append(os.path.join(p, f))
481 rcpath.append(os.path.join(p, f))
482 return rcpath
482 return rcpath
483
483
484 def userrcpath():
484 def userrcpath():
485 '''return os-specific hgrc search path to the user dir'''
485 '''return os-specific hgrc search path to the user dir'''
486 home = os.path.expanduser('~')
486 home = os.path.expanduser('~')
487 path = [os.path.join(home, 'mercurial.ini'),
487 path = [os.path.join(home, 'mercurial.ini'),
488 os.path.join(home, '.hgrc')]
488 os.path.join(home, '.hgrc')]
489 userprofile = os.environ.get('USERPROFILE')
489 userprofile = os.environ.get('USERPROFILE')
490 if userprofile:
490 if userprofile:
491 path.append(os.path.join(userprofile, 'mercurial.ini'))
491 path.append(os.path.join(userprofile, 'mercurial.ini'))
492 path.append(os.path.join(userprofile, '.hgrc'))
492 path.append(os.path.join(userprofile, '.hgrc'))
493 return path
493 return path
494
494
495 def revsingle(repo, revspec, default='.'):
495 def revsingle(repo, revspec, default='.'):
496 if not revspec:
496 if not revspec:
497 return repo[default]
497 return repo[default]
498
498
499 l = revrange(repo, [revspec])
499 l = revrange(repo, [revspec])
500 if len(l) < 1:
500 if len(l) < 1:
501 raise util.Abort(_('empty revision set'))
501 raise util.Abort(_('empty revision set'))
502 return repo[l[-1]]
502 return repo[l[-1]]
503
503
504 def revpair(repo, revs):
504 def revpair(repo, revs):
505 if not revs:
505 if not revs:
506 return repo.dirstate.p1(), None
506 return repo.dirstate.p1(), None
507
507
508 l = revrange(repo, revs)
508 l = revrange(repo, revs)
509
509
510 if len(l) == 0:
510 if len(l) == 0:
511 return repo.dirstate.p1(), None
511 return repo.dirstate.p1(), None
512
512
513 if len(l) == 1:
513 if len(l) == 1:
514 return repo.lookup(l[0]), None
514 return repo.lookup(l[0]), None
515
515
516 return repo.lookup(l[0]), repo.lookup(l[-1])
516 return repo.lookup(l[0]), repo.lookup(l[-1])
517
517
518 _revrangesep = ':'
518 _revrangesep = ':'
519
519
520 def revrange(repo, revs):
520 def revrange(repo, revs):
521 """Yield revision as strings from a list of revision specifications."""
521 """Yield revision as strings from a list of revision specifications."""
522
522
523 def revfix(repo, val, defval):
523 def revfix(repo, val, defval):
524 if not val and val != 0 and defval is not None:
524 if not val and val != 0 and defval is not None:
525 return defval
525 return defval
526 return repo.changelog.rev(repo.lookup(val))
526 return repo[val].rev()
527
527
528 seen, l = set(), []
528 seen, l = set(), []
529 for spec in revs:
529 for spec in revs:
530 # attempt to parse old-style ranges first to deal with
530 # attempt to parse old-style ranges first to deal with
531 # things like old-tag which contain query metacharacters
531 # things like old-tag which contain query metacharacters
532 try:
532 try:
533 if isinstance(spec, int):
533 if isinstance(spec, int):
534 seen.add(spec)
534 seen.add(spec)
535 l.append(spec)
535 l.append(spec)
536 continue
536 continue
537
537
538 if _revrangesep in spec:
538 if _revrangesep in spec:
539 start, end = spec.split(_revrangesep, 1)
539 start, end = spec.split(_revrangesep, 1)
540 start = revfix(repo, start, 0)
540 start = revfix(repo, start, 0)
541 end = revfix(repo, end, len(repo) - 1)
541 end = revfix(repo, end, len(repo) - 1)
542 step = start > end and -1 or 1
542 step = start > end and -1 or 1
543 for rev in xrange(start, end + step, step):
543 for rev in xrange(start, end + step, step):
544 if rev in seen:
544 if rev in seen:
545 continue
545 continue
546 seen.add(rev)
546 seen.add(rev)
547 l.append(rev)
547 l.append(rev)
548 continue
548 continue
549 elif spec and spec in repo: # single unquoted rev
549 elif spec and spec in repo: # single unquoted rev
550 rev = revfix(repo, spec, None)
550 rev = revfix(repo, spec, None)
551 if rev in seen:
551 if rev in seen:
552 continue
552 continue
553 seen.add(rev)
553 seen.add(rev)
554 l.append(rev)
554 l.append(rev)
555 continue
555 continue
556 except error.RepoLookupError:
556 except error.RepoLookupError:
557 pass
557 pass
558
558
559 # fall through to new-style queries if old-style fails
559 # fall through to new-style queries if old-style fails
560 m = revset.match(repo.ui, spec)
560 m = revset.match(repo.ui, spec)
561 for r in m(repo, range(len(repo))):
561 for r in m(repo, range(len(repo))):
562 if r not in seen:
562 if r not in seen:
563 l.append(r)
563 l.append(r)
564 seen.update(l)
564 seen.update(l)
565
565
566 return l
566 return l
567
567
568 def expandpats(pats):
568 def expandpats(pats):
569 if not util.expandglobs:
569 if not util.expandglobs:
570 return list(pats)
570 return list(pats)
571 ret = []
571 ret = []
572 for p in pats:
572 for p in pats:
573 kind, name = matchmod._patsplit(p, None)
573 kind, name = matchmod._patsplit(p, None)
574 if kind is None:
574 if kind is None:
575 try:
575 try:
576 globbed = glob.glob(name)
576 globbed = glob.glob(name)
577 except re.error:
577 except re.error:
578 globbed = [name]
578 globbed = [name]
579 if globbed:
579 if globbed:
580 ret.extend(globbed)
580 ret.extend(globbed)
581 continue
581 continue
582 ret.append(p)
582 ret.append(p)
583 return ret
583 return ret
584
584
585 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
585 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
586 if pats == ("",):
586 if pats == ("",):
587 pats = []
587 pats = []
588 if not globbed and default == 'relpath':
588 if not globbed and default == 'relpath':
589 pats = expandpats(pats or [])
589 pats = expandpats(pats or [])
590
590
591 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
591 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
592 default)
592 default)
593 def badfn(f, msg):
593 def badfn(f, msg):
594 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
594 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
595 m.bad = badfn
595 m.bad = badfn
596 return m, pats
596 return m, pats
597
597
598 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
598 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
599 return matchandpats(ctx, pats, opts, globbed, default)[0]
599 return matchandpats(ctx, pats, opts, globbed, default)[0]
600
600
601 def matchall(repo):
601 def matchall(repo):
602 return matchmod.always(repo.root, repo.getcwd())
602 return matchmod.always(repo.root, repo.getcwd())
603
603
604 def matchfiles(repo, files):
604 def matchfiles(repo, files):
605 return matchmod.exact(repo.root, repo.getcwd(), files)
605 return matchmod.exact(repo.root, repo.getcwd(), files)
606
606
607 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
607 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
608 if dry_run is None:
608 if dry_run is None:
609 dry_run = opts.get('dry_run')
609 dry_run = opts.get('dry_run')
610 if similarity is None:
610 if similarity is None:
611 similarity = float(opts.get('similarity') or 0)
611 similarity = float(opts.get('similarity') or 0)
612 # we'd use status here, except handling of symlinks and ignore is tricky
612 # we'd use status here, except handling of symlinks and ignore is tricky
613 added, unknown, deleted, removed = [], [], [], []
613 added, unknown, deleted, removed = [], [], [], []
614 audit_path = pathauditor(repo.root)
614 audit_path = pathauditor(repo.root)
615 m = match(repo[None], pats, opts)
615 m = match(repo[None], pats, opts)
616 rejected = []
616 rejected = []
617 m.bad = lambda x, y: rejected.append(x)
617 m.bad = lambda x, y: rejected.append(x)
618
618
619 for abs in repo.walk(m):
619 for abs in repo.walk(m):
620 target = repo.wjoin(abs)
620 target = repo.wjoin(abs)
621 good = True
621 good = True
622 try:
622 try:
623 audit_path(abs)
623 audit_path(abs)
624 except (OSError, util.Abort):
624 except (OSError, util.Abort):
625 good = False
625 good = False
626 rel = m.rel(abs)
626 rel = m.rel(abs)
627 exact = m.exact(abs)
627 exact = m.exact(abs)
628 if good and abs not in repo.dirstate:
628 if good and abs not in repo.dirstate:
629 unknown.append(abs)
629 unknown.append(abs)
630 if repo.ui.verbose or not exact:
630 if repo.ui.verbose or not exact:
631 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
631 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
632 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
632 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
633 or (os.path.isdir(target) and not os.path.islink(target))):
633 or (os.path.isdir(target) and not os.path.islink(target))):
634 deleted.append(abs)
634 deleted.append(abs)
635 if repo.ui.verbose or not exact:
635 if repo.ui.verbose or not exact:
636 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
636 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
637 # for finding renames
637 # for finding renames
638 elif repo.dirstate[abs] == 'r':
638 elif repo.dirstate[abs] == 'r':
639 removed.append(abs)
639 removed.append(abs)
640 elif repo.dirstate[abs] == 'a':
640 elif repo.dirstate[abs] == 'a':
641 added.append(abs)
641 added.append(abs)
642 copies = {}
642 copies = {}
643 if similarity > 0:
643 if similarity > 0:
644 for old, new, score in similar.findrenames(repo,
644 for old, new, score in similar.findrenames(repo,
645 added + unknown, removed + deleted, similarity):
645 added + unknown, removed + deleted, similarity):
646 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
646 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
647 repo.ui.status(_('recording removal of %s as rename to %s '
647 repo.ui.status(_('recording removal of %s as rename to %s '
648 '(%d%% similar)\n') %
648 '(%d%% similar)\n') %
649 (m.rel(old), m.rel(new), score * 100))
649 (m.rel(old), m.rel(new), score * 100))
650 copies[new] = old
650 copies[new] = old
651
651
652 if not dry_run:
652 if not dry_run:
653 wctx = repo[None]
653 wctx = repo[None]
654 wlock = repo.wlock()
654 wlock = repo.wlock()
655 try:
655 try:
656 wctx.forget(deleted)
656 wctx.forget(deleted)
657 wctx.add(unknown)
657 wctx.add(unknown)
658 for new, old in copies.iteritems():
658 for new, old in copies.iteritems():
659 wctx.copy(old, new)
659 wctx.copy(old, new)
660 finally:
660 finally:
661 wlock.release()
661 wlock.release()
662
662
663 for f in rejected:
663 for f in rejected:
664 if f in m.files():
664 if f in m.files():
665 return 1
665 return 1
666 return 0
666 return 0
667
667
668 def updatedir(ui, repo, patches, similarity=0):
668 def updatedir(ui, repo, patches, similarity=0):
669 '''Update dirstate after patch application according to metadata'''
669 '''Update dirstate after patch application according to metadata'''
670 if not patches:
670 if not patches:
671 return []
671 return []
672 copies = []
672 copies = []
673 removes = set()
673 removes = set()
674 cfiles = patches.keys()
674 cfiles = patches.keys()
675 cwd = repo.getcwd()
675 cwd = repo.getcwd()
676 if cwd:
676 if cwd:
677 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
677 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
678 for f in patches:
678 for f in patches:
679 gp = patches[f]
679 gp = patches[f]
680 if not gp:
680 if not gp:
681 continue
681 continue
682 if gp.op == 'RENAME':
682 if gp.op == 'RENAME':
683 copies.append((gp.oldpath, gp.path))
683 copies.append((gp.oldpath, gp.path))
684 removes.add(gp.oldpath)
684 removes.add(gp.oldpath)
685 elif gp.op == 'COPY':
685 elif gp.op == 'COPY':
686 copies.append((gp.oldpath, gp.path))
686 copies.append((gp.oldpath, gp.path))
687 elif gp.op == 'DELETE':
687 elif gp.op == 'DELETE':
688 removes.add(gp.path)
688 removes.add(gp.path)
689
689
690 wctx = repo[None]
690 wctx = repo[None]
691 for src, dst in copies:
691 for src, dst in copies:
692 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
692 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
693 if (not similarity) and removes:
693 if (not similarity) and removes:
694 wctx.remove(sorted(removes), True)
694 wctx.remove(sorted(removes), True)
695
695
696 for f in patches:
696 for f in patches:
697 gp = patches[f]
697 gp = patches[f]
698 if gp and gp.mode:
698 if gp and gp.mode:
699 islink, isexec = gp.mode
699 islink, isexec = gp.mode
700 dst = repo.wjoin(gp.path)
700 dst = repo.wjoin(gp.path)
701 # patch won't create empty files
701 # patch won't create empty files
702 if gp.op == 'ADD' and not os.path.lexists(dst):
702 if gp.op == 'ADD' and not os.path.lexists(dst):
703 flags = (isexec and 'x' or '') + (islink and 'l' or '')
703 flags = (isexec and 'x' or '') + (islink and 'l' or '')
704 repo.wwrite(gp.path, '', flags)
704 repo.wwrite(gp.path, '', flags)
705 util.setflags(dst, islink, isexec)
705 util.setflags(dst, islink, isexec)
706 addremove(repo, cfiles, similarity=similarity)
706 addremove(repo, cfiles, similarity=similarity)
707 files = patches.keys()
707 files = patches.keys()
708 files.extend([r for r in removes if r not in files])
708 files.extend([r for r in removes if r not in files])
709 return sorted(files)
709 return sorted(files)
710
710
711 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
711 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
712 """Update the dirstate to reflect the intent of copying src to dst. For
712 """Update the dirstate to reflect the intent of copying src to dst. For
713 different reasons it might not end with dst being marked as copied from src.
713 different reasons it might not end with dst being marked as copied from src.
714 """
714 """
715 origsrc = repo.dirstate.copied(src) or src
715 origsrc = repo.dirstate.copied(src) or src
716 if dst == origsrc: # copying back a copy?
716 if dst == origsrc: # copying back a copy?
717 if repo.dirstate[dst] not in 'mn' and not dryrun:
717 if repo.dirstate[dst] not in 'mn' and not dryrun:
718 repo.dirstate.normallookup(dst)
718 repo.dirstate.normallookup(dst)
719 else:
719 else:
720 if repo.dirstate[origsrc] == 'a' and origsrc == src:
720 if repo.dirstate[origsrc] == 'a' and origsrc == src:
721 if not ui.quiet:
721 if not ui.quiet:
722 ui.warn(_("%s has not been committed yet, so no copy "
722 ui.warn(_("%s has not been committed yet, so no copy "
723 "data will be stored for %s.\n")
723 "data will be stored for %s.\n")
724 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
724 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
725 if repo.dirstate[dst] in '?r' and not dryrun:
725 if repo.dirstate[dst] in '?r' and not dryrun:
726 wctx.add([dst])
726 wctx.add([dst])
727 elif not dryrun:
727 elif not dryrun:
728 wctx.copy(origsrc, dst)
728 wctx.copy(origsrc, dst)
729
729
730 def readrequires(opener, supported):
730 def readrequires(opener, supported):
731 '''Reads and parses .hg/requires and checks if all entries found
731 '''Reads and parses .hg/requires and checks if all entries found
732 are in the list of supported features.'''
732 are in the list of supported features.'''
733 requirements = set(opener.read("requires").splitlines())
733 requirements = set(opener.read("requires").splitlines())
734 missings = []
734 missings = []
735 for r in requirements:
735 for r in requirements:
736 if r not in supported:
736 if r not in supported:
737 if not r or not r[0].isalnum():
737 if not r or not r[0].isalnum():
738 raise error.RequirementError(_(".hg/requires file is corrupt"))
738 raise error.RequirementError(_(".hg/requires file is corrupt"))
739 missings.append(r)
739 missings.append(r)
740 missings.sort()
740 missings.sort()
741 if missings:
741 if missings:
742 raise error.RequirementError(_("unknown repository format: "
742 raise error.RequirementError(_("unknown repository format: "
743 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
743 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
744 return requirements
744 return requirements
745
745
746 class filecacheentry(object):
746 class filecacheentry(object):
747 def __init__(self, path):
747 def __init__(self, path):
748 self.path = path
748 self.path = path
749 self.cachestat = filecacheentry.stat(self.path)
749 self.cachestat = filecacheentry.stat(self.path)
750
750
751 if self.cachestat:
751 if self.cachestat:
752 self._cacheable = self.cachestat.cacheable()
752 self._cacheable = self.cachestat.cacheable()
753 else:
753 else:
754 # None means we don't know yet
754 # None means we don't know yet
755 self._cacheable = None
755 self._cacheable = None
756
756
757 def refresh(self):
757 def refresh(self):
758 if self.cacheable():
758 if self.cacheable():
759 self.cachestat = filecacheentry.stat(self.path)
759 self.cachestat = filecacheentry.stat(self.path)
760
760
761 def cacheable(self):
761 def cacheable(self):
762 if self._cacheable is not None:
762 if self._cacheable is not None:
763 return self._cacheable
763 return self._cacheable
764
764
765 # we don't know yet, assume it is for now
765 # we don't know yet, assume it is for now
766 return True
766 return True
767
767
768 def changed(self):
768 def changed(self):
769 # no point in going further if we can't cache it
769 # no point in going further if we can't cache it
770 if not self.cacheable():
770 if not self.cacheable():
771 return True
771 return True
772
772
773 newstat = filecacheentry.stat(self.path)
773 newstat = filecacheentry.stat(self.path)
774
774
775 # we may not know if it's cacheable yet, check again now
775 # we may not know if it's cacheable yet, check again now
776 if newstat and self._cacheable is None:
776 if newstat and self._cacheable is None:
777 self._cacheable = newstat.cacheable()
777 self._cacheable = newstat.cacheable()
778
778
779 # check again
779 # check again
780 if not self._cacheable:
780 if not self._cacheable:
781 return True
781 return True
782
782
783 if self.cachestat != newstat:
783 if self.cachestat != newstat:
784 self.cachestat = newstat
784 self.cachestat = newstat
785 return True
785 return True
786 else:
786 else:
787 return False
787 return False
788
788
789 @staticmethod
789 @staticmethod
790 def stat(path):
790 def stat(path):
791 try:
791 try:
792 return util.cachestat(path)
792 return util.cachestat(path)
793 except OSError, e:
793 except OSError, e:
794 if e.errno != errno.ENOENT:
794 if e.errno != errno.ENOENT:
795 raise
795 raise
796
796
797 class filecache(object):
797 class filecache(object):
798 '''A property like decorator that tracks a file under .hg/ for updates.
798 '''A property like decorator that tracks a file under .hg/ for updates.
799
799
800 Records stat info when called in _filecache.
800 Records stat info when called in _filecache.
801
801
802 On subsequent calls, compares old stat info with new info, and recreates
802 On subsequent calls, compares old stat info with new info, and recreates
803 the object when needed, updating the new stat info in _filecache.
803 the object when needed, updating the new stat info in _filecache.
804
804
805 Mercurial either atomic renames or appends for files under .hg,
805 Mercurial either atomic renames or appends for files under .hg,
806 so to ensure the cache is reliable we need the filesystem to be able
806 so to ensure the cache is reliable we need the filesystem to be able
807 to tell us if a file has been replaced. If it can't, we fallback to
807 to tell us if a file has been replaced. If it can't, we fallback to
808 recreating the object on every call (essentially the same behaviour as
808 recreating the object on every call (essentially the same behaviour as
809 propertycache).'''
809 propertycache).'''
810 def __init__(self, path):
810 def __init__(self, path):
811 self.path = path
811 self.path = path
812
812
813 def join(self, obj, fname):
813 def join(self, obj, fname):
814 """Used to compute the runtime path of the cached file.
814 """Used to compute the runtime path of the cached file.
815
815
816 Users should subclass filecache and provide their own version of this
816 Users should subclass filecache and provide their own version of this
817 function to call the appropriate join function on 'obj' (an instance
817 function to call the appropriate join function on 'obj' (an instance
818 of the class that its member function was decorated).
818 of the class that its member function was decorated).
819 """
819 """
820 return obj.join(fname)
820 return obj.join(fname)
821
821
822 def __call__(self, func):
822 def __call__(self, func):
823 self.func = func
823 self.func = func
824 self.name = func.__name__
824 self.name = func.__name__
825 return self
825 return self
826
826
827 def __get__(self, obj, type=None):
827 def __get__(self, obj, type=None):
828 # do we need to check if the file changed?
828 # do we need to check if the file changed?
829 if self.name in obj.__dict__:
829 if self.name in obj.__dict__:
830 return obj.__dict__[self.name]
830 return obj.__dict__[self.name]
831
831
832 entry = obj._filecache.get(self.name)
832 entry = obj._filecache.get(self.name)
833
833
834 if entry:
834 if entry:
835 if entry.changed():
835 if entry.changed():
836 entry.obj = self.func(obj)
836 entry.obj = self.func(obj)
837 else:
837 else:
838 path = self.join(obj, self.path)
838 path = self.join(obj, self.path)
839
839
840 # We stat -before- creating the object so our cache doesn't lie if
840 # We stat -before- creating the object so our cache doesn't lie if
841 # a writer modified between the time we read and stat
841 # a writer modified between the time we read and stat
842 entry = filecacheentry(path)
842 entry = filecacheentry(path)
843 entry.obj = self.func(obj)
843 entry.obj = self.func(obj)
844
844
845 obj._filecache[self.name] = entry
845 obj._filecache[self.name] = entry
846
846
847 obj.__dict__[self.name] = entry.obj
847 obj.__dict__[self.name] = entry.obj
848 return entry.obj
848 return entry.obj
849
849
850 def __set__(self, obj, value):
850 def __set__(self, obj, value):
851 if self.name in obj._filecache:
851 if self.name in obj._filecache:
852 obj._filecache[self.name].obj = value # update cached copy
852 obj._filecache[self.name].obj = value # update cached copy
853 obj.__dict__[self.name] = value # update copy returned by obj.x
853 obj.__dict__[self.name] = value # update copy returned by obj.x
854
854
855 def __delete__(self, obj):
855 def __delete__(self, obj):
856 try:
856 try:
857 del obj.__dict__[self.name]
857 del obj.__dict__[self.name]
858 except KeyError:
858 except KeyError:
859 raise AttributeError, self.name
859 raise AttributeError, self.name
General Comments 0
You need to be logged in to leave comments. Login now