##// END OF EJS Templates
scmutil: improve path calculation for install-relative RC files (issue2841)...
Matt Mackall -
r14527:5867bd6e default
parent child Browse files
Show More
@@ -1,705 +1,705 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):
259 def audit(self, path):
260 self.auditor(path)
260 self.auditor(path)
261
261
262 class filteropener(abstractopener):
262 class filteropener(abstractopener):
263 '''Wrapper opener for filtering filenames with a function.'''
263 '''Wrapper opener for filtering filenames with a function.'''
264
264
265 def __init__(self, opener, filter):
265 def __init__(self, opener, filter):
266 self._filter = filter
266 self._filter = filter
267 self._orig = opener
267 self._orig = opener
268
268
269 def __call__(self, path, *args, **kwargs):
269 def __call__(self, path, *args, **kwargs):
270 return self._orig(self._filter(path), *args, **kwargs)
270 return self._orig(self._filter(path), *args, **kwargs)
271
271
272 def canonpath(root, cwd, myname, auditor=None):
272 def canonpath(root, cwd, myname, auditor=None):
273 '''return the canonical path of myname, given cwd and root'''
273 '''return the canonical path of myname, given cwd and root'''
274 if util.endswithsep(root):
274 if util.endswithsep(root):
275 rootsep = root
275 rootsep = root
276 else:
276 else:
277 rootsep = root + os.sep
277 rootsep = root + os.sep
278 name = myname
278 name = myname
279 if not os.path.isabs(name):
279 if not os.path.isabs(name):
280 name = os.path.join(root, cwd, name)
280 name = os.path.join(root, cwd, name)
281 name = os.path.normpath(name)
281 name = os.path.normpath(name)
282 if auditor is None:
282 if auditor is None:
283 auditor = pathauditor(root)
283 auditor = pathauditor(root)
284 if name != rootsep and name.startswith(rootsep):
284 if name != rootsep and name.startswith(rootsep):
285 name = name[len(rootsep):]
285 name = name[len(rootsep):]
286 auditor(name)
286 auditor(name)
287 return util.pconvert(name)
287 return util.pconvert(name)
288 elif name == root:
288 elif name == root:
289 return ''
289 return ''
290 else:
290 else:
291 # Determine whether `name' is in the hierarchy at or beneath `root',
291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 # 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
293 # check name == '/', because that doesn't work on windows). For each
293 # check name == '/', because that doesn't work on windows). For each
294 # `name', compare dev/inode numbers. If they match, the list `rel'
294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 # holds the reversed list of components making up the relative file
295 # holds the reversed list of components making up the relative file
296 # name we want.
296 # name we want.
297 root_st = os.stat(root)
297 root_st = os.stat(root)
298 rel = []
298 rel = []
299 while True:
299 while True:
300 try:
300 try:
301 name_st = os.stat(name)
301 name_st = os.stat(name)
302 except OSError:
302 except OSError:
303 break
303 break
304 if util.samestat(name_st, root_st):
304 if util.samestat(name_st, root_st):
305 if not rel:
305 if not rel:
306 # name was actually the same as root (maybe a symlink)
306 # name was actually the same as root (maybe a symlink)
307 return ''
307 return ''
308 rel.reverse()
308 rel.reverse()
309 name = os.path.join(*rel)
309 name = os.path.join(*rel)
310 auditor(name)
310 auditor(name)
311 return util.pconvert(name)
311 return util.pconvert(name)
312 dirname, basename = os.path.split(name)
312 dirname, basename = os.path.split(name)
313 rel.append(basename)
313 rel.append(basename)
314 if dirname == name:
314 if dirname == name:
315 break
315 break
316 name = dirname
316 name = dirname
317
317
318 raise util.Abort('%s not under root' % myname)
318 raise util.Abort('%s not under root' % myname)
319
319
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 '''yield every hg repository under path, recursively.'''
321 '''yield every hg repository under path, recursively.'''
322 def errhandler(err):
322 def errhandler(err):
323 if err.filename == path:
323 if err.filename == path:
324 raise err
324 raise err
325 if followsym and hasattr(os.path, 'samestat'):
325 if followsym and hasattr(os.path, 'samestat'):
326 def adddir(dirlst, dirname):
326 def adddir(dirlst, dirname):
327 match = False
327 match = False
328 samestat = os.path.samestat
328 samestat = os.path.samestat
329 dirstat = os.stat(dirname)
329 dirstat = os.stat(dirname)
330 for lstdirstat in dirlst:
330 for lstdirstat in dirlst:
331 if samestat(dirstat, lstdirstat):
331 if samestat(dirstat, lstdirstat):
332 match = True
332 match = True
333 break
333 break
334 if not match:
334 if not match:
335 dirlst.append(dirstat)
335 dirlst.append(dirstat)
336 return not match
336 return not match
337 else:
337 else:
338 followsym = False
338 followsym = False
339
339
340 if (seen_dirs is None) and followsym:
340 if (seen_dirs is None) and followsym:
341 seen_dirs = []
341 seen_dirs = []
342 adddir(seen_dirs, path)
342 adddir(seen_dirs, path)
343 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):
344 dirs.sort()
344 dirs.sort()
345 if '.hg' in dirs:
345 if '.hg' in dirs:
346 yield root # found a repository
346 yield root # found a repository
347 qroot = os.path.join(root, '.hg', 'patches')
347 qroot = os.path.join(root, '.hg', 'patches')
348 if os.path.isdir(os.path.join(qroot, '.hg')):
348 if os.path.isdir(os.path.join(qroot, '.hg')):
349 yield qroot # we have a patch queue repo here
349 yield qroot # we have a patch queue repo here
350 if recurse:
350 if recurse:
351 # avoid recursing inside the .hg directory
351 # avoid recursing inside the .hg directory
352 dirs.remove('.hg')
352 dirs.remove('.hg')
353 else:
353 else:
354 dirs[:] = [] # don't descend further
354 dirs[:] = [] # don't descend further
355 elif followsym:
355 elif followsym:
356 newdirs = []
356 newdirs = []
357 for d in dirs:
357 for d in dirs:
358 fname = os.path.join(root, d)
358 fname = os.path.join(root, d)
359 if adddir(seen_dirs, fname):
359 if adddir(seen_dirs, fname):
360 if os.path.islink(fname):
360 if os.path.islink(fname):
361 for hgname in walkrepos(fname, True, seen_dirs):
361 for hgname in walkrepos(fname, True, seen_dirs):
362 yield hgname
362 yield hgname
363 else:
363 else:
364 newdirs.append(d)
364 newdirs.append(d)
365 dirs[:] = newdirs
365 dirs[:] = newdirs
366
366
367 def osrcpath():
367 def osrcpath():
368 '''return default os-specific hgrc search path'''
368 '''return default os-specific hgrc search path'''
369 path = systemrcpath()
369 path = systemrcpath()
370 path.extend(userrcpath())
370 path.extend(userrcpath())
371 path = [os.path.normpath(f) for f in path]
371 path = [os.path.normpath(f) for f in path]
372 return path
372 return path
373
373
374 _rcpath = None
374 _rcpath = None
375
375
376 def rcpath():
376 def rcpath():
377 '''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.
378 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,
379 else use item.
379 else use item.
380 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.
381 if no HGRCPATH, use default os-specific path.'''
381 if no HGRCPATH, use default os-specific path.'''
382 global _rcpath
382 global _rcpath
383 if _rcpath is None:
383 if _rcpath is None:
384 if 'HGRCPATH' in os.environ:
384 if 'HGRCPATH' in os.environ:
385 _rcpath = []
385 _rcpath = []
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 if not p:
387 if not p:
388 continue
388 continue
389 p = util.expandpath(p)
389 p = util.expandpath(p)
390 if os.path.isdir(p):
390 if os.path.isdir(p):
391 for f, kind in osutil.listdir(p):
391 for f, kind in osutil.listdir(p):
392 if f.endswith('.rc'):
392 if f.endswith('.rc'):
393 _rcpath.append(os.path.join(p, f))
393 _rcpath.append(os.path.join(p, f))
394 else:
394 else:
395 _rcpath.append(p)
395 _rcpath.append(p)
396 else:
396 else:
397 _rcpath = osrcpath()
397 _rcpath = osrcpath()
398 return _rcpath
398 return _rcpath
399
399
400 if os.name != 'nt':
400 if os.name != 'nt':
401
401
402 def rcfiles(path):
402 def rcfiles(path):
403 rcs = [os.path.join(path, 'hgrc')]
403 rcs = [os.path.join(path, 'hgrc')]
404 rcdir = os.path.join(path, 'hgrc.d')
404 rcdir = os.path.join(path, 'hgrc.d')
405 try:
405 try:
406 rcs.extend([os.path.join(rcdir, f)
406 rcs.extend([os.path.join(rcdir, f)
407 for f, kind in osutil.listdir(rcdir)
407 for f, kind in osutil.listdir(rcdir)
408 if f.endswith(".rc")])
408 if f.endswith(".rc")])
409 except OSError:
409 except OSError:
410 pass
410 pass
411 return rcs
411 return rcs
412
412
413 def systemrcpath():
413 def systemrcpath():
414 path = []
414 path = []
415 # old mod_python does not set sys.argv
415 # old mod_python does not set sys.argv
416 if len(getattr(sys, 'argv', [])) > 0:
416 if len(getattr(sys, 'argv', [])) > 0:
417 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 '/../etc/mercurial'))
418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 path.extend(rcfiles('/etc/mercurial'))
419 path.extend(rcfiles('/etc/mercurial'))
420 return path
420 return path
421
421
422 def userrcpath():
422 def userrcpath():
423 return [os.path.expanduser('~/.hgrc')]
423 return [os.path.expanduser('~/.hgrc')]
424
424
425 else:
425 else:
426
426
427 _HKEY_LOCAL_MACHINE = 0x80000002L
427 _HKEY_LOCAL_MACHINE = 0x80000002L
428
428
429 def systemrcpath():
429 def systemrcpath():
430 '''return default os-specific hgrc search path'''
430 '''return default os-specific hgrc search path'''
431 rcpath = []
431 rcpath = []
432 filename = util.executablepath()
432 filename = util.executablepath()
433 # Use mercurial.ini found in directory with hg.exe
433 # Use mercurial.ini found in directory with hg.exe
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 if os.path.isfile(progrc):
435 if os.path.isfile(progrc):
436 rcpath.append(progrc)
436 rcpath.append(progrc)
437 return rcpath
437 return rcpath
438 # Use hgrc.d found in directory with hg.exe
438 # Use hgrc.d found in directory with hg.exe
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 if os.path.isdir(progrcd):
440 if os.path.isdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
442 if f.endswith('.rc'):
442 if f.endswith('.rc'):
443 rcpath.append(os.path.join(progrcd, f))
443 rcpath.append(os.path.join(progrcd, f))
444 return rcpath
444 return rcpath
445 # else look for a system rcpath in the registry
445 # else look for a system rcpath in the registry
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 _HKEY_LOCAL_MACHINE)
447 _HKEY_LOCAL_MACHINE)
448 if not isinstance(value, str) or not value:
448 if not isinstance(value, str) or not value:
449 return rcpath
449 return rcpath
450 value = value.replace('/', os.sep)
450 value = value.replace('/', os.sep)
451 for p in value.split(os.pathsep):
451 for p in value.split(os.pathsep):
452 if p.lower().endswith('mercurial.ini'):
452 if p.lower().endswith('mercurial.ini'):
453 rcpath.append(p)
453 rcpath.append(p)
454 elif os.path.isdir(p):
454 elif os.path.isdir(p):
455 for f, kind in osutil.listdir(p):
455 for f, kind in osutil.listdir(p):
456 if f.endswith('.rc'):
456 if f.endswith('.rc'):
457 rcpath.append(os.path.join(p, f))
457 rcpath.append(os.path.join(p, f))
458 return rcpath
458 return rcpath
459
459
460 def userrcpath():
460 def userrcpath():
461 '''return os-specific hgrc search path to the user dir'''
461 '''return os-specific hgrc search path to the user dir'''
462 home = os.path.expanduser('~')
462 home = os.path.expanduser('~')
463 path = [os.path.join(home, 'mercurial.ini'),
463 path = [os.path.join(home, 'mercurial.ini'),
464 os.path.join(home, '.hgrc')]
464 os.path.join(home, '.hgrc')]
465 userprofile = os.environ.get('USERPROFILE')
465 userprofile = os.environ.get('USERPROFILE')
466 if userprofile:
466 if userprofile:
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 path.append(os.path.join(userprofile, '.hgrc'))
468 path.append(os.path.join(userprofile, '.hgrc'))
469 return path
469 return path
470
470
471 def revsingle(repo, revspec, default='.'):
471 def revsingle(repo, revspec, default='.'):
472 if not revspec:
472 if not revspec:
473 return repo[default]
473 return repo[default]
474
474
475 l = revrange(repo, [revspec])
475 l = revrange(repo, [revspec])
476 if len(l) < 1:
476 if len(l) < 1:
477 raise util.Abort(_('empty revision set'))
477 raise util.Abort(_('empty revision set'))
478 return repo[l[-1]]
478 return repo[l[-1]]
479
479
480 def revpair(repo, revs):
480 def revpair(repo, revs):
481 if not revs:
481 if not revs:
482 return repo.dirstate.p1(), None
482 return repo.dirstate.p1(), None
483
483
484 l = revrange(repo, revs)
484 l = revrange(repo, revs)
485
485
486 if len(l) == 0:
486 if len(l) == 0:
487 return repo.dirstate.p1(), None
487 return repo.dirstate.p1(), None
488
488
489 if len(l) == 1:
489 if len(l) == 1:
490 return repo.lookup(l[0]), None
490 return repo.lookup(l[0]), None
491
491
492 return repo.lookup(l[0]), repo.lookup(l[-1])
492 return repo.lookup(l[0]), repo.lookup(l[-1])
493
493
494 _revrangesep = ':'
494 _revrangesep = ':'
495
495
496 def revrange(repo, revs):
496 def revrange(repo, revs):
497 """Yield revision as strings from a list of revision specifications."""
497 """Yield revision as strings from a list of revision specifications."""
498
498
499 def revfix(repo, val, defval):
499 def revfix(repo, val, defval):
500 if not val and val != 0 and defval is not None:
500 if not val and val != 0 and defval is not None:
501 return defval
501 return defval
502 return repo.changelog.rev(repo.lookup(val))
502 return repo.changelog.rev(repo.lookup(val))
503
503
504 seen, l = set(), []
504 seen, l = set(), []
505 for spec in revs:
505 for spec in revs:
506 # attempt to parse old-style ranges first to deal with
506 # attempt to parse old-style ranges first to deal with
507 # things like old-tag which contain query metacharacters
507 # things like old-tag which contain query metacharacters
508 try:
508 try:
509 if isinstance(spec, int):
509 if isinstance(spec, int):
510 seen.add(spec)
510 seen.add(spec)
511 l.append(spec)
511 l.append(spec)
512 continue
512 continue
513
513
514 if _revrangesep in spec:
514 if _revrangesep in spec:
515 start, end = spec.split(_revrangesep, 1)
515 start, end = spec.split(_revrangesep, 1)
516 start = revfix(repo, start, 0)
516 start = revfix(repo, start, 0)
517 end = revfix(repo, end, len(repo) - 1)
517 end = revfix(repo, end, len(repo) - 1)
518 step = start > end and -1 or 1
518 step = start > end and -1 or 1
519 for rev in xrange(start, end + step, step):
519 for rev in xrange(start, end + step, step):
520 if rev in seen:
520 if rev in seen:
521 continue
521 continue
522 seen.add(rev)
522 seen.add(rev)
523 l.append(rev)
523 l.append(rev)
524 continue
524 continue
525 elif spec and spec in repo: # single unquoted rev
525 elif spec and spec in repo: # single unquoted rev
526 rev = revfix(repo, spec, None)
526 rev = revfix(repo, spec, None)
527 if rev in seen:
527 if rev in seen:
528 continue
528 continue
529 seen.add(rev)
529 seen.add(rev)
530 l.append(rev)
530 l.append(rev)
531 continue
531 continue
532 except error.RepoLookupError:
532 except error.RepoLookupError:
533 pass
533 pass
534
534
535 # fall through to new-style queries if old-style fails
535 # fall through to new-style queries if old-style fails
536 m = revset.match(repo.ui, spec)
536 m = revset.match(repo.ui, spec)
537 for r in m(repo, range(len(repo))):
537 for r in m(repo, range(len(repo))):
538 if r not in seen:
538 if r not in seen:
539 l.append(r)
539 l.append(r)
540 seen.update(l)
540 seen.update(l)
541
541
542 return l
542 return l
543
543
544 def expandpats(pats):
544 def expandpats(pats):
545 if not util.expandglobs:
545 if not util.expandglobs:
546 return list(pats)
546 return list(pats)
547 ret = []
547 ret = []
548 for p in pats:
548 for p in pats:
549 kind, name = matchmod._patsplit(p, None)
549 kind, name = matchmod._patsplit(p, None)
550 if kind is None:
550 if kind is None:
551 try:
551 try:
552 globbed = glob.glob(name)
552 globbed = glob.glob(name)
553 except re.error:
553 except re.error:
554 globbed = [name]
554 globbed = [name]
555 if globbed:
555 if globbed:
556 ret.extend(globbed)
556 ret.extend(globbed)
557 continue
557 continue
558 ret.append(p)
558 ret.append(p)
559 return ret
559 return ret
560
560
561 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
561 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
562 if pats == ("",):
562 if pats == ("",):
563 pats = []
563 pats = []
564 if not globbed and default == 'relpath':
564 if not globbed and default == 'relpath':
565 pats = expandpats(pats or [])
565 pats = expandpats(pats or [])
566 m = matchmod.match(repo.root, repo.getcwd(), pats,
566 m = matchmod.match(repo.root, repo.getcwd(), pats,
567 opts.get('include'), opts.get('exclude'), default,
567 opts.get('include'), opts.get('exclude'), default,
568 auditor=repo.auditor)
568 auditor=repo.auditor)
569 def badfn(f, msg):
569 def badfn(f, msg):
570 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
570 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 m.bad = badfn
571 m.bad = badfn
572 return m
572 return m
573
573
574 def matchall(repo):
574 def matchall(repo):
575 return matchmod.always(repo.root, repo.getcwd())
575 return matchmod.always(repo.root, repo.getcwd())
576
576
577 def matchfiles(repo, files):
577 def matchfiles(repo, files):
578 return matchmod.exact(repo.root, repo.getcwd(), files)
578 return matchmod.exact(repo.root, repo.getcwd(), files)
579
579
580 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
580 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
581 if dry_run is None:
581 if dry_run is None:
582 dry_run = opts.get('dry_run')
582 dry_run = opts.get('dry_run')
583 if similarity is None:
583 if similarity is None:
584 similarity = float(opts.get('similarity') or 0)
584 similarity = float(opts.get('similarity') or 0)
585 # 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
586 added, unknown, deleted, removed = [], [], [], []
586 added, unknown, deleted, removed = [], [], [], []
587 audit_path = pathauditor(repo.root)
587 audit_path = pathauditor(repo.root)
588 m = match(repo, pats, opts)
588 m = match(repo, pats, opts)
589 for abs in repo.walk(m):
589 for abs in repo.walk(m):
590 target = repo.wjoin(abs)
590 target = repo.wjoin(abs)
591 good = True
591 good = True
592 try:
592 try:
593 audit_path(abs)
593 audit_path(abs)
594 except (OSError, util.Abort):
594 except (OSError, util.Abort):
595 good = False
595 good = False
596 rel = m.rel(abs)
596 rel = m.rel(abs)
597 exact = m.exact(abs)
597 exact = m.exact(abs)
598 if good and abs not in repo.dirstate:
598 if good and abs not in repo.dirstate:
599 unknown.append(abs)
599 unknown.append(abs)
600 if repo.ui.verbose or not exact:
600 if repo.ui.verbose or not exact:
601 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
601 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
602 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)
603 or (os.path.isdir(target) and not os.path.islink(target))):
603 or (os.path.isdir(target) and not os.path.islink(target))):
604 deleted.append(abs)
604 deleted.append(abs)
605 if repo.ui.verbose or not exact:
605 if repo.ui.verbose or not exact:
606 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
606 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
607 # for finding renames
607 # for finding renames
608 elif repo.dirstate[abs] == 'r':
608 elif repo.dirstate[abs] == 'r':
609 removed.append(abs)
609 removed.append(abs)
610 elif repo.dirstate[abs] == 'a':
610 elif repo.dirstate[abs] == 'a':
611 added.append(abs)
611 added.append(abs)
612 copies = {}
612 copies = {}
613 if similarity > 0:
613 if similarity > 0:
614 for old, new, score in similar.findrenames(repo,
614 for old, new, score in similar.findrenames(repo,
615 added + unknown, removed + deleted, similarity):
615 added + unknown, removed + deleted, similarity):
616 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):
617 repo.ui.status(_('recording removal of %s as rename to %s '
617 repo.ui.status(_('recording removal of %s as rename to %s '
618 '(%d%% similar)\n') %
618 '(%d%% similar)\n') %
619 (m.rel(old), m.rel(new), score * 100))
619 (m.rel(old), m.rel(new), score * 100))
620 copies[new] = old
620 copies[new] = old
621
621
622 if not dry_run:
622 if not dry_run:
623 wctx = repo[None]
623 wctx = repo[None]
624 wlock = repo.wlock()
624 wlock = repo.wlock()
625 try:
625 try:
626 wctx.forget(deleted)
626 wctx.forget(deleted)
627 wctx.add(unknown)
627 wctx.add(unknown)
628 for new, old in copies.iteritems():
628 for new, old in copies.iteritems():
629 wctx.copy(old, new)
629 wctx.copy(old, new)
630 finally:
630 finally:
631 wlock.release()
631 wlock.release()
632
632
633 def updatedir(ui, repo, patches, similarity=0):
633 def updatedir(ui, repo, patches, similarity=0):
634 '''Update dirstate after patch application according to metadata'''
634 '''Update dirstate after patch application according to metadata'''
635 if not patches:
635 if not patches:
636 return []
636 return []
637 copies = []
637 copies = []
638 removes = set()
638 removes = set()
639 cfiles = patches.keys()
639 cfiles = patches.keys()
640 cwd = repo.getcwd()
640 cwd = repo.getcwd()
641 if cwd:
641 if cwd:
642 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()]
643 for f in patches:
643 for f in patches:
644 gp = patches[f]
644 gp = patches[f]
645 if not gp:
645 if not gp:
646 continue
646 continue
647 if gp.op == 'RENAME':
647 if gp.op == 'RENAME':
648 copies.append((gp.oldpath, gp.path))
648 copies.append((gp.oldpath, gp.path))
649 removes.add(gp.oldpath)
649 removes.add(gp.oldpath)
650 elif gp.op == 'COPY':
650 elif gp.op == 'COPY':
651 copies.append((gp.oldpath, gp.path))
651 copies.append((gp.oldpath, gp.path))
652 elif gp.op == 'DELETE':
652 elif gp.op == 'DELETE':
653 removes.add(gp.path)
653 removes.add(gp.path)
654
654
655 wctx = repo[None]
655 wctx = repo[None]
656 for src, dst in copies:
656 for src, dst in copies:
657 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
657 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
658 if (not similarity) and removes:
658 if (not similarity) and removes:
659 wctx.remove(sorted(removes), True)
659 wctx.remove(sorted(removes), True)
660
660
661 for f in patches:
661 for f in patches:
662 gp = patches[f]
662 gp = patches[f]
663 if gp and gp.mode:
663 if gp and gp.mode:
664 islink, isexec = gp.mode
664 islink, isexec = gp.mode
665 dst = repo.wjoin(gp.path)
665 dst = repo.wjoin(gp.path)
666 # patch won't create empty files
666 # patch won't create empty files
667 if gp.op == 'ADD' and not os.path.lexists(dst):
667 if gp.op == 'ADD' and not os.path.lexists(dst):
668 flags = (isexec and 'x' or '') + (islink and 'l' or '')
668 flags = (isexec and 'x' or '') + (islink and 'l' or '')
669 repo.wwrite(gp.path, '', flags)
669 repo.wwrite(gp.path, '', flags)
670 util.setflags(dst, islink, isexec)
670 util.setflags(dst, islink, isexec)
671 addremove(repo, cfiles, similarity=similarity)
671 addremove(repo, cfiles, similarity=similarity)
672 files = patches.keys()
672 files = patches.keys()
673 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])
674 return sorted(files)
674 return sorted(files)
675
675
676 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
676 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
677 """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
678 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.
679 """
679 """
680 origsrc = repo.dirstate.copied(src) or src
680 origsrc = repo.dirstate.copied(src) or src
681 if dst == origsrc: # copying back a copy?
681 if dst == origsrc: # copying back a copy?
682 if repo.dirstate[dst] not in 'mn' and not dryrun:
682 if repo.dirstate[dst] not in 'mn' and not dryrun:
683 repo.dirstate.normallookup(dst)
683 repo.dirstate.normallookup(dst)
684 else:
684 else:
685 if repo.dirstate[origsrc] == 'a' and origsrc == src:
685 if repo.dirstate[origsrc] == 'a' and origsrc == src:
686 if not ui.quiet:
686 if not ui.quiet:
687 ui.warn(_("%s has not been committed yet, so no copy "
687 ui.warn(_("%s has not been committed yet, so no copy "
688 "data will be stored for %s.\n")
688 "data will be stored for %s.\n")
689 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
689 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
690 if repo.dirstate[dst] in '?r' and not dryrun:
690 if repo.dirstate[dst] in '?r' and not dryrun:
691 wctx.add([dst])
691 wctx.add([dst])
692 elif not dryrun:
692 elif not dryrun:
693 wctx.copy(origsrc, dst)
693 wctx.copy(origsrc, dst)
694
694
695 def readrequires(opener, supported):
695 def readrequires(opener, supported):
696 '''Reads and parses .hg/requires and checks if all entries found
696 '''Reads and parses .hg/requires and checks if all entries found
697 are in the list of supported features.'''
697 are in the list of supported features.'''
698 requirements = set(opener.read("requires").splitlines())
698 requirements = set(opener.read("requires").splitlines())
699 for r in requirements:
699 for r in requirements:
700 if r not in supported:
700 if r not in supported:
701 if not r or not r[0].isalnum():
701 if not r or not r[0].isalnum():
702 raise error.RequirementError(_(".hg/requires file is corrupt"))
702 raise error.RequirementError(_(".hg/requires file is corrupt"))
703 raise error.RequirementError(_("unknown repository format: "
703 raise error.RequirementError(_("unknown repository format: "
704 "requires feature '%s' (upgrade Mercurial)") % r)
704 "requires feature '%s' (upgrade Mercurial)") % r)
705 return requirements
705 return requirements
General Comments 0
You need to be logged in to leave comments. Login now