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