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