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