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