##// END OF EJS Templates
scmutil: refactor ui.portablefilenames processing...
Kevin Gessner -
r14067:e88a4958 default
parent child Browse files
Show More
@@ -1,401 +1,419 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, error, osutil
10 10 import os, errno, stat, sys
11 11
12 12 def checkfilename(f):
13 13 '''Check that the filename f is an acceptable filename for a tracked file'''
14 14 if '\r' in f or '\n' in f:
15 15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16 16
17 17 def checkportable(ui, f):
18 18 '''Check if filename f is portable and warn or abort depending on config'''
19 19 checkfilename(f)
20 val = ui.config('ui', 'portablefilenames', 'warn')
21 lval = val.lower()
22 abort = os.name == 'nt' or lval == 'abort'
23 bval = util.parsebool(val)
24 if abort or lval == 'warn' or bval:
20 if showportabilityalert(ui):
25 21 msg = util.checkwinfilename(f)
26 22 if msg:
27 if abort:
28 raise util.Abort("%s: %r" % (msg, f))
29 ui.warn(_("warning: %s: %r\n") % (msg, f))
30 elif bval is None and lval != 'ignore':
23 portabilityalert(ui, "%s: %r" % (msg, f))
24
25 def checkportabilityalert(ui):
26 '''check if the user's config requests nothing, a warning, or abort for
27 non-portable filenames'''
28 val = ui.config('ui', 'portablefilenames', 'warn')
29 lval = val.lower()
30 bval = util.parsebool(val)
31 abort = os.name == 'nt' or lval == 'abort'
32 warn = bval or lval == 'warn'
33 if bval is None and not (warn or abort or lval == 'ignore'):
31 34 raise error.ConfigError(
32 35 _("ui.portablefilenames value is invalid ('%s')") % val)
36 return abort, warn
37
38 def showportabilityalert(ui):
39 '''check if the user wants any notification of portability problems'''
40 abort, warn = checkportabilityalert(ui)
41 return abort or warn
42
43 def portabilityalert(ui, msg):
44 if not msg:
45 return
46 abort, warn = checkportabilityalert(ui)
47 if abort:
48 raise util.Abort("%s" % msg)
49 elif warn:
50 ui.warn(_("warning: %s\n") % msg)
33 51
34 52 class path_auditor(object):
35 53 '''ensure that a filesystem path contains no banned components.
36 54 the following properties of a path are checked:
37 55
38 56 - ends with a directory separator
39 57 - under top-level .hg
40 58 - starts at the root of a windows drive
41 59 - contains ".."
42 60 - traverses a symlink (e.g. a/symlink_here/b)
43 61 - inside a nested repository (a callback can be used to approve
44 62 some nested repositories, e.g., subrepositories)
45 63 '''
46 64
47 65 def __init__(self, root, callback=None):
48 66 self.audited = set()
49 67 self.auditeddir = set()
50 68 self.root = root
51 69 self.callback = callback
52 70
53 71 def __call__(self, path):
54 72 '''Check the relative path.
55 73 path may contain a pattern (e.g. foodir/**.txt)'''
56 74
57 75 if path in self.audited:
58 76 return
59 77 # AIX ignores "/" at end of path, others raise EISDIR.
60 78 if util.endswithsep(path):
61 79 raise util.Abort(_("path ends in directory separator: %s") % path)
62 80 normpath = os.path.normcase(path)
63 81 parts = util.splitpath(normpath)
64 82 if (os.path.splitdrive(path)[0]
65 83 or parts[0].lower() in ('.hg', '.hg.', '')
66 84 or os.pardir in parts):
67 85 raise util.Abort(_("path contains illegal component: %s") % path)
68 86 if '.hg' in path.lower():
69 87 lparts = [p.lower() for p in parts]
70 88 for p in '.hg', '.hg.':
71 89 if p in lparts[1:]:
72 90 pos = lparts.index(p)
73 91 base = os.path.join(*parts[:pos])
74 92 raise util.Abort(_('path %r is inside nested repo %r')
75 93 % (path, base))
76 94
77 95 parts.pop()
78 96 prefixes = []
79 97 while parts:
80 98 prefix = os.sep.join(parts)
81 99 if prefix in self.auditeddir:
82 100 break
83 101 curpath = os.path.join(self.root, prefix)
84 102 try:
85 103 st = os.lstat(curpath)
86 104 except OSError, err:
87 105 # EINVAL can be raised as invalid path syntax under win32.
88 106 # They must be ignored for patterns can be checked too.
89 107 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
90 108 raise
91 109 else:
92 110 if stat.S_ISLNK(st.st_mode):
93 111 raise util.Abort(
94 112 _('path %r traverses symbolic link %r')
95 113 % (path, prefix))
96 114 elif (stat.S_ISDIR(st.st_mode) and
97 115 os.path.isdir(os.path.join(curpath, '.hg'))):
98 116 if not self.callback or not self.callback(curpath):
99 117 raise util.Abort(_('path %r is inside nested repo %r') %
100 118 (path, prefix))
101 119 prefixes.append(prefix)
102 120 parts.pop()
103 121
104 122 self.audited.add(path)
105 123 # only add prefixes to the cache after checking everything: we don't
106 124 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
107 125 self.auditeddir.update(prefixes)
108 126
109 127 class opener(object):
110 128 '''Open files relative to a base directory
111 129
112 130 This class is used to hide the details of COW semantics and
113 131 remote file access from higher level code.
114 132 '''
115 133 def __init__(self, base, audit=True):
116 134 self.base = base
117 135 if audit:
118 136 self.auditor = path_auditor(base)
119 137 else:
120 138 self.auditor = util.always
121 139 self.createmode = None
122 140 self._trustnlink = None
123 141
124 142 @util.propertycache
125 143 def _can_symlink(self):
126 144 return util.checklink(self.base)
127 145
128 146 def _fixfilemode(self, name):
129 147 if self.createmode is None:
130 148 return
131 149 os.chmod(name, self.createmode & 0666)
132 150
133 151 def __call__(self, path, mode="r", text=False, atomictemp=False):
134 152 r = util.checkosfilename(path)
135 153 if r:
136 154 raise util.Abort("%s: %r" % (r, path))
137 155 self.auditor(path)
138 156 f = os.path.join(self.base, path)
139 157
140 158 if not text and "b" not in mode:
141 159 mode += "b" # for that other OS
142 160
143 161 nlink = -1
144 162 dirname, basename = os.path.split(f)
145 163 # If basename is empty, then the path is malformed because it points
146 164 # to a directory. Let the posixfile() call below raise IOError.
147 165 if basename and mode not in ('r', 'rb'):
148 166 if atomictemp:
149 167 if not os.path.isdir(dirname):
150 168 util.makedirs(dirname, self.createmode)
151 169 return util.atomictempfile(f, mode, self.createmode)
152 170 try:
153 171 if 'w' in mode:
154 172 util.unlink(f)
155 173 nlink = 0
156 174 else:
157 175 # nlinks() may behave differently for files on Windows
158 176 # shares if the file is open.
159 177 fd = util.posixfile(f)
160 178 nlink = util.nlinks(f)
161 179 if nlink < 1:
162 180 nlink = 2 # force mktempcopy (issue1922)
163 181 fd.close()
164 182 except (OSError, IOError), e:
165 183 if e.errno != errno.ENOENT:
166 184 raise
167 185 nlink = 0
168 186 if not os.path.isdir(dirname):
169 187 util.makedirs(dirname, self.createmode)
170 188 if nlink > 0:
171 189 if self._trustnlink is None:
172 190 self._trustnlink = nlink > 1 or util.checknlink(f)
173 191 if nlink > 1 or not self._trustnlink:
174 192 util.rename(util.mktempcopy(f), f)
175 193 fp = util.posixfile(f, mode)
176 194 if nlink == 0:
177 195 self._fixfilemode(f)
178 196 return fp
179 197
180 198 def symlink(self, src, dst):
181 199 self.auditor(dst)
182 200 linkname = os.path.join(self.base, dst)
183 201 try:
184 202 os.unlink(linkname)
185 203 except OSError:
186 204 pass
187 205
188 206 dirname = os.path.dirname(linkname)
189 207 if not os.path.exists(dirname):
190 208 util.makedirs(dirname, self.createmode)
191 209
192 210 if self._can_symlink:
193 211 try:
194 212 os.symlink(src, linkname)
195 213 except OSError, err:
196 214 raise OSError(err.errno, _('could not symlink to %r: %s') %
197 215 (src, err.strerror), linkname)
198 216 else:
199 217 f = self(dst, "w")
200 218 f.write(src)
201 219 f.close()
202 220 self._fixfilemode(dst)
203 221
204 222 def canonpath(root, cwd, myname, auditor=None):
205 223 '''return the canonical path of myname, given cwd and root'''
206 224 if util.endswithsep(root):
207 225 rootsep = root
208 226 else:
209 227 rootsep = root + os.sep
210 228 name = myname
211 229 if not os.path.isabs(name):
212 230 name = os.path.join(root, cwd, name)
213 231 name = os.path.normpath(name)
214 232 if auditor is None:
215 233 auditor = path_auditor(root)
216 234 if name != rootsep and name.startswith(rootsep):
217 235 name = name[len(rootsep):]
218 236 auditor(name)
219 237 return util.pconvert(name)
220 238 elif name == root:
221 239 return ''
222 240 else:
223 241 # Determine whether `name' is in the hierarchy at or beneath `root',
224 242 # by iterating name=dirname(name) until that causes no change (can't
225 243 # check name == '/', because that doesn't work on windows). For each
226 244 # `name', compare dev/inode numbers. If they match, the list `rel'
227 245 # holds the reversed list of components making up the relative file
228 246 # name we want.
229 247 root_st = os.stat(root)
230 248 rel = []
231 249 while True:
232 250 try:
233 251 name_st = os.stat(name)
234 252 except OSError:
235 253 break
236 254 if util.samestat(name_st, root_st):
237 255 if not rel:
238 256 # name was actually the same as root (maybe a symlink)
239 257 return ''
240 258 rel.reverse()
241 259 name = os.path.join(*rel)
242 260 auditor(name)
243 261 return util.pconvert(name)
244 262 dirname, basename = os.path.split(name)
245 263 rel.append(basename)
246 264 if dirname == name:
247 265 break
248 266 name = dirname
249 267
250 268 raise util.Abort('%s not under root' % myname)
251 269
252 270 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
253 271 '''yield every hg repository under path, recursively.'''
254 272 def errhandler(err):
255 273 if err.filename == path:
256 274 raise err
257 275 if followsym and hasattr(os.path, 'samestat'):
258 276 def _add_dir_if_not_there(dirlst, dirname):
259 277 match = False
260 278 samestat = os.path.samestat
261 279 dirstat = os.stat(dirname)
262 280 for lstdirstat in dirlst:
263 281 if samestat(dirstat, lstdirstat):
264 282 match = True
265 283 break
266 284 if not match:
267 285 dirlst.append(dirstat)
268 286 return not match
269 287 else:
270 288 followsym = False
271 289
272 290 if (seen_dirs is None) and followsym:
273 291 seen_dirs = []
274 292 _add_dir_if_not_there(seen_dirs, path)
275 293 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
276 294 dirs.sort()
277 295 if '.hg' in dirs:
278 296 yield root # found a repository
279 297 qroot = os.path.join(root, '.hg', 'patches')
280 298 if os.path.isdir(os.path.join(qroot, '.hg')):
281 299 yield qroot # we have a patch queue repo here
282 300 if recurse:
283 301 # avoid recursing inside the .hg directory
284 302 dirs.remove('.hg')
285 303 else:
286 304 dirs[:] = [] # don't descend further
287 305 elif followsym:
288 306 newdirs = []
289 307 for d in dirs:
290 308 fname = os.path.join(root, d)
291 309 if _add_dir_if_not_there(seen_dirs, fname):
292 310 if os.path.islink(fname):
293 311 for hgname in walkrepos(fname, True, seen_dirs):
294 312 yield hgname
295 313 else:
296 314 newdirs.append(d)
297 315 dirs[:] = newdirs
298 316
299 317 def os_rcpath():
300 318 '''return default os-specific hgrc search path'''
301 319 path = system_rcpath()
302 320 path.extend(user_rcpath())
303 321 path = [os.path.normpath(f) for f in path]
304 322 return path
305 323
306 324 _rcpath = None
307 325
308 326 def rcpath():
309 327 '''return hgrc search path. if env var HGRCPATH is set, use it.
310 328 for each item in path, if directory, use files ending in .rc,
311 329 else use item.
312 330 make HGRCPATH empty to only look in .hg/hgrc of current repo.
313 331 if no HGRCPATH, use default os-specific path.'''
314 332 global _rcpath
315 333 if _rcpath is None:
316 334 if 'HGRCPATH' in os.environ:
317 335 _rcpath = []
318 336 for p in os.environ['HGRCPATH'].split(os.pathsep):
319 337 if not p:
320 338 continue
321 339 p = util.expandpath(p)
322 340 if os.path.isdir(p):
323 341 for f, kind in osutil.listdir(p):
324 342 if f.endswith('.rc'):
325 343 _rcpath.append(os.path.join(p, f))
326 344 else:
327 345 _rcpath.append(p)
328 346 else:
329 347 _rcpath = os_rcpath()
330 348 return _rcpath
331 349
332 350 if os.name != 'nt':
333 351
334 352 def rcfiles(path):
335 353 rcs = [os.path.join(path, 'hgrc')]
336 354 rcdir = os.path.join(path, 'hgrc.d')
337 355 try:
338 356 rcs.extend([os.path.join(rcdir, f)
339 357 for f, kind in osutil.listdir(rcdir)
340 358 if f.endswith(".rc")])
341 359 except OSError:
342 360 pass
343 361 return rcs
344 362
345 363 def system_rcpath():
346 364 path = []
347 365 # old mod_python does not set sys.argv
348 366 if len(getattr(sys, 'argv', [])) > 0:
349 367 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
350 368 '/../etc/mercurial'))
351 369 path.extend(rcfiles('/etc/mercurial'))
352 370 return path
353 371
354 372 def user_rcpath():
355 373 return [os.path.expanduser('~/.hgrc')]
356 374
357 375 else:
358 376
359 377 _HKEY_LOCAL_MACHINE = 0x80000002L
360 378
361 379 def system_rcpath():
362 380 '''return default os-specific hgrc search path'''
363 381 rcpath = []
364 382 filename = util.executable_path()
365 383 # Use mercurial.ini found in directory with hg.exe
366 384 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
367 385 if os.path.isfile(progrc):
368 386 rcpath.append(progrc)
369 387 return rcpath
370 388 # Use hgrc.d found in directory with hg.exe
371 389 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
372 390 if os.path.isdir(progrcd):
373 391 for f, kind in osutil.listdir(progrcd):
374 392 if f.endswith('.rc'):
375 393 rcpath.append(os.path.join(progrcd, f))
376 394 return rcpath
377 395 # else look for a system rcpath in the registry
378 396 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
379 397 _HKEY_LOCAL_MACHINE)
380 398 if not isinstance(value, str) or not value:
381 399 return rcpath
382 400 value = value.replace('/', os.sep)
383 401 for p in value.split(os.pathsep):
384 402 if p.lower().endswith('mercurial.ini'):
385 403 rcpath.append(p)
386 404 elif os.path.isdir(p):
387 405 for f, kind in osutil.listdir(p):
388 406 if f.endswith('.rc'):
389 407 rcpath.append(os.path.join(p, f))
390 408 return rcpath
391 409
392 410 def user_rcpath():
393 411 '''return os-specific hgrc search path to the user dir'''
394 412 home = os.path.expanduser('~')
395 413 path = [os.path.join(home, 'mercurial.ini'),
396 414 os.path.join(home, '.hgrc')]
397 415 userprofile = os.environ.get('USERPROFILE')
398 416 if userprofile:
399 417 path.append(os.path.join(userprofile, 'mercurial.ini'))
400 418 path.append(os.path.join(userprofile, '.hgrc'))
401 419 return path
General Comments 0
You need to be logged in to leave comments. Login now