##// END OF EJS Templates
vfs: add option to not create parent directories implicitly...
Yuya Nishihara -
r40827:03bca908 default
parent child Browse files
Show More
@@ -1,660 +1,664
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
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 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import threading
14 import threading
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 pathutil,
20 pathutil,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 def _avoidambig(path, oldstat):
25 def _avoidambig(path, oldstat):
26 """Avoid file stat ambiguity forcibly
26 """Avoid file stat ambiguity forcibly
27
27
28 This function causes copying ``path`` file, if it is owned by
28 This function causes copying ``path`` file, if it is owned by
29 another (see issue5418 and issue5584 for detail).
29 another (see issue5418 and issue5584 for detail).
30 """
30 """
31 def checkandavoid():
31 def checkandavoid():
32 newstat = util.filestat.frompath(path)
32 newstat = util.filestat.frompath(path)
33 # return whether file stat ambiguity is (already) avoided
33 # return whether file stat ambiguity is (already) avoided
34 return (not newstat.isambig(oldstat) or
34 return (not newstat.isambig(oldstat) or
35 newstat.avoidambig(path, oldstat))
35 newstat.avoidambig(path, oldstat))
36 if not checkandavoid():
36 if not checkandavoid():
37 # simply copy to change owner of path to get privilege to
37 # simply copy to change owner of path to get privilege to
38 # advance mtime (see issue5418)
38 # advance mtime (see issue5418)
39 util.rename(util.mktempcopy(path), path)
39 util.rename(util.mktempcopy(path), path)
40 checkandavoid()
40 checkandavoid()
41
41
42 class abstractvfs(object):
42 class abstractvfs(object):
43 """Abstract base class; cannot be instantiated"""
43 """Abstract base class; cannot be instantiated"""
44
44
45 def __init__(self, *args, **kwargs):
45 def __init__(self, *args, **kwargs):
46 '''Prevent instantiation; don't call this from subclasses.'''
46 '''Prevent instantiation; don't call this from subclasses.'''
47 raise NotImplementedError('attempted instantiating ' + str(type(self)))
47 raise NotImplementedError('attempted instantiating ' + str(type(self)))
48
48
49 def tryread(self, path):
49 def tryread(self, path):
50 '''gracefully return an empty string for missing files'''
50 '''gracefully return an empty string for missing files'''
51 try:
51 try:
52 return self.read(path)
52 return self.read(path)
53 except IOError as inst:
53 except IOError as inst:
54 if inst.errno != errno.ENOENT:
54 if inst.errno != errno.ENOENT:
55 raise
55 raise
56 return ""
56 return ""
57
57
58 def tryreadlines(self, path, mode='rb'):
58 def tryreadlines(self, path, mode='rb'):
59 '''gracefully return an empty array for missing files'''
59 '''gracefully return an empty array for missing files'''
60 try:
60 try:
61 return self.readlines(path, mode=mode)
61 return self.readlines(path, mode=mode)
62 except IOError as inst:
62 except IOError as inst:
63 if inst.errno != errno.ENOENT:
63 if inst.errno != errno.ENOENT:
64 raise
64 raise
65 return []
65 return []
66
66
67 @util.propertycache
67 @util.propertycache
68 def open(self):
68 def open(self):
69 '''Open ``path`` file, which is relative to vfs root.
69 '''Open ``path`` file, which is relative to vfs root.
70
70
71 Newly created directories are marked as "not to be indexed by
71 Newly created directories are marked as "not to be indexed by
72 the content indexing service", if ``notindexed`` is specified
72 the content indexing service", if ``notindexed`` is specified
73 for "write" mode access.
73 for "write" mode access.
74 '''
74 '''
75 return self.__call__
75 return self.__call__
76
76
77 def read(self, path):
77 def read(self, path):
78 with self(path, 'rb') as fp:
78 with self(path, 'rb') as fp:
79 return fp.read()
79 return fp.read()
80
80
81 def readlines(self, path, mode='rb'):
81 def readlines(self, path, mode='rb'):
82 with self(path, mode=mode) as fp:
82 with self(path, mode=mode) as fp:
83 return fp.readlines()
83 return fp.readlines()
84
84
85 def write(self, path, data, backgroundclose=False, **kwargs):
85 def write(self, path, data, backgroundclose=False, **kwargs):
86 with self(path, 'wb', backgroundclose=backgroundclose, **kwargs) as fp:
86 with self(path, 'wb', backgroundclose=backgroundclose, **kwargs) as fp:
87 return fp.write(data)
87 return fp.write(data)
88
88
89 def writelines(self, path, data, mode='wb', notindexed=False):
89 def writelines(self, path, data, mode='wb', notindexed=False):
90 with self(path, mode=mode, notindexed=notindexed) as fp:
90 with self(path, mode=mode, notindexed=notindexed) as fp:
91 return fp.writelines(data)
91 return fp.writelines(data)
92
92
93 def append(self, path, data):
93 def append(self, path, data):
94 with self(path, 'ab') as fp:
94 with self(path, 'ab') as fp:
95 return fp.write(data)
95 return fp.write(data)
96
96
97 def basename(self, path):
97 def basename(self, path):
98 """return base element of a path (as os.path.basename would do)
98 """return base element of a path (as os.path.basename would do)
99
99
100 This exists to allow handling of strange encoding if needed."""
100 This exists to allow handling of strange encoding if needed."""
101 return os.path.basename(path)
101 return os.path.basename(path)
102
102
103 def chmod(self, path, mode):
103 def chmod(self, path, mode):
104 return os.chmod(self.join(path), mode)
104 return os.chmod(self.join(path), mode)
105
105
106 def dirname(self, path):
106 def dirname(self, path):
107 """return dirname element of a path (as os.path.dirname would do)
107 """return dirname element of a path (as os.path.dirname would do)
108
108
109 This exists to allow handling of strange encoding if needed."""
109 This exists to allow handling of strange encoding if needed."""
110 return os.path.dirname(path)
110 return os.path.dirname(path)
111
111
112 def exists(self, path=None):
112 def exists(self, path=None):
113 return os.path.exists(self.join(path))
113 return os.path.exists(self.join(path))
114
114
115 def fstat(self, fp):
115 def fstat(self, fp):
116 return util.fstat(fp)
116 return util.fstat(fp)
117
117
118 def isdir(self, path=None):
118 def isdir(self, path=None):
119 return os.path.isdir(self.join(path))
119 return os.path.isdir(self.join(path))
120
120
121 def isfile(self, path=None):
121 def isfile(self, path=None):
122 return os.path.isfile(self.join(path))
122 return os.path.isfile(self.join(path))
123
123
124 def islink(self, path=None):
124 def islink(self, path=None):
125 return os.path.islink(self.join(path))
125 return os.path.islink(self.join(path))
126
126
127 def isfileorlink(self, path=None):
127 def isfileorlink(self, path=None):
128 '''return whether path is a regular file or a symlink
128 '''return whether path is a regular file or a symlink
129
129
130 Unlike isfile, this doesn't follow symlinks.'''
130 Unlike isfile, this doesn't follow symlinks.'''
131 try:
131 try:
132 st = self.lstat(path)
132 st = self.lstat(path)
133 except OSError:
133 except OSError:
134 return False
134 return False
135 mode = st.st_mode
135 mode = st.st_mode
136 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
136 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
137
137
138 def reljoin(self, *paths):
138 def reljoin(self, *paths):
139 """join various elements of a path together (as os.path.join would do)
139 """join various elements of a path together (as os.path.join would do)
140
140
141 The vfs base is not injected so that path stay relative. This exists
141 The vfs base is not injected so that path stay relative. This exists
142 to allow handling of strange encoding if needed."""
142 to allow handling of strange encoding if needed."""
143 return os.path.join(*paths)
143 return os.path.join(*paths)
144
144
145 def split(self, path):
145 def split(self, path):
146 """split top-most element of a path (as os.path.split would do)
146 """split top-most element of a path (as os.path.split would do)
147
147
148 This exists to allow handling of strange encoding if needed."""
148 This exists to allow handling of strange encoding if needed."""
149 return os.path.split(path)
149 return os.path.split(path)
150
150
151 def lexists(self, path=None):
151 def lexists(self, path=None):
152 return os.path.lexists(self.join(path))
152 return os.path.lexists(self.join(path))
153
153
154 def lstat(self, path=None):
154 def lstat(self, path=None):
155 return os.lstat(self.join(path))
155 return os.lstat(self.join(path))
156
156
157 def listdir(self, path=None):
157 def listdir(self, path=None):
158 return os.listdir(self.join(path))
158 return os.listdir(self.join(path))
159
159
160 def makedir(self, path=None, notindexed=True):
160 def makedir(self, path=None, notindexed=True):
161 return util.makedir(self.join(path), notindexed)
161 return util.makedir(self.join(path), notindexed)
162
162
163 def makedirs(self, path=None, mode=None):
163 def makedirs(self, path=None, mode=None):
164 return util.makedirs(self.join(path), mode)
164 return util.makedirs(self.join(path), mode)
165
165
166 def makelock(self, info, path):
166 def makelock(self, info, path):
167 return util.makelock(info, self.join(path))
167 return util.makelock(info, self.join(path))
168
168
169 def mkdir(self, path=None):
169 def mkdir(self, path=None):
170 return os.mkdir(self.join(path))
170 return os.mkdir(self.join(path))
171
171
172 def mkstemp(self, suffix='', prefix='tmp', dir=None):
172 def mkstemp(self, suffix='', prefix='tmp', dir=None):
173 fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix,
173 fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix,
174 dir=self.join(dir))
174 dir=self.join(dir))
175 dname, fname = util.split(name)
175 dname, fname = util.split(name)
176 if dir:
176 if dir:
177 return fd, os.path.join(dir, fname)
177 return fd, os.path.join(dir, fname)
178 else:
178 else:
179 return fd, fname
179 return fd, fname
180
180
181 def readdir(self, path=None, stat=None, skip=None):
181 def readdir(self, path=None, stat=None, skip=None):
182 return util.listdir(self.join(path), stat, skip)
182 return util.listdir(self.join(path), stat, skip)
183
183
184 def readlock(self, path):
184 def readlock(self, path):
185 return util.readlock(self.join(path))
185 return util.readlock(self.join(path))
186
186
187 def rename(self, src, dst, checkambig=False):
187 def rename(self, src, dst, checkambig=False):
188 """Rename from src to dst
188 """Rename from src to dst
189
189
190 checkambig argument is used with util.filestat, and is useful
190 checkambig argument is used with util.filestat, and is useful
191 only if destination file is guarded by any lock
191 only if destination file is guarded by any lock
192 (e.g. repo.lock or repo.wlock).
192 (e.g. repo.lock or repo.wlock).
193
193
194 To avoid file stat ambiguity forcibly, checkambig=True involves
194 To avoid file stat ambiguity forcibly, checkambig=True involves
195 copying ``src`` file, if it is owned by another. Therefore, use
195 copying ``src`` file, if it is owned by another. Therefore, use
196 checkambig=True only in limited cases (see also issue5418 and
196 checkambig=True only in limited cases (see also issue5418 and
197 issue5584 for detail).
197 issue5584 for detail).
198 """
198 """
199 srcpath = self.join(src)
199 srcpath = self.join(src)
200 dstpath = self.join(dst)
200 dstpath = self.join(dst)
201 oldstat = checkambig and util.filestat.frompath(dstpath)
201 oldstat = checkambig and util.filestat.frompath(dstpath)
202 if oldstat and oldstat.stat:
202 if oldstat and oldstat.stat:
203 ret = util.rename(srcpath, dstpath)
203 ret = util.rename(srcpath, dstpath)
204 _avoidambig(dstpath, oldstat)
204 _avoidambig(dstpath, oldstat)
205 return ret
205 return ret
206 return util.rename(srcpath, dstpath)
206 return util.rename(srcpath, dstpath)
207
207
208 def readlink(self, path):
208 def readlink(self, path):
209 return util.readlink(self.join(path))
209 return util.readlink(self.join(path))
210
210
211 def removedirs(self, path=None):
211 def removedirs(self, path=None):
212 """Remove a leaf directory and all empty intermediate ones
212 """Remove a leaf directory and all empty intermediate ones
213 """
213 """
214 return util.removedirs(self.join(path))
214 return util.removedirs(self.join(path))
215
215
216 def rmdir(self, path=None):
216 def rmdir(self, path=None):
217 """Remove an empty directory."""
217 """Remove an empty directory."""
218 return os.rmdir(self.join(path))
218 return os.rmdir(self.join(path))
219
219
220 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
220 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
221 """Remove a directory tree recursively
221 """Remove a directory tree recursively
222
222
223 If ``forcibly``, this tries to remove READ-ONLY files, too.
223 If ``forcibly``, this tries to remove READ-ONLY files, too.
224 """
224 """
225 if forcibly:
225 if forcibly:
226 def onerror(function, path, excinfo):
226 def onerror(function, path, excinfo):
227 if function is not os.remove:
227 if function is not os.remove:
228 raise
228 raise
229 # read-only files cannot be unlinked under Windows
229 # read-only files cannot be unlinked under Windows
230 s = os.stat(path)
230 s = os.stat(path)
231 if (s.st_mode & stat.S_IWRITE) != 0:
231 if (s.st_mode & stat.S_IWRITE) != 0:
232 raise
232 raise
233 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
233 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
234 os.remove(path)
234 os.remove(path)
235 else:
235 else:
236 onerror = None
236 onerror = None
237 return shutil.rmtree(self.join(path),
237 return shutil.rmtree(self.join(path),
238 ignore_errors=ignore_errors, onerror=onerror)
238 ignore_errors=ignore_errors, onerror=onerror)
239
239
240 def setflags(self, path, l, x):
240 def setflags(self, path, l, x):
241 return util.setflags(self.join(path), l, x)
241 return util.setflags(self.join(path), l, x)
242
242
243 def stat(self, path=None):
243 def stat(self, path=None):
244 return os.stat(self.join(path))
244 return os.stat(self.join(path))
245
245
246 def unlink(self, path=None):
246 def unlink(self, path=None):
247 return util.unlink(self.join(path))
247 return util.unlink(self.join(path))
248
248
249 def tryunlink(self, path=None):
249 def tryunlink(self, path=None):
250 """Attempt to remove a file, ignoring missing file errors."""
250 """Attempt to remove a file, ignoring missing file errors."""
251 util.tryunlink(self.join(path))
251 util.tryunlink(self.join(path))
252
252
253 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
253 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
254 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
254 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
255 rmdir=rmdir)
255 rmdir=rmdir)
256
256
257 def utime(self, path=None, t=None):
257 def utime(self, path=None, t=None):
258 return os.utime(self.join(path), t)
258 return os.utime(self.join(path), t)
259
259
260 def walk(self, path=None, onerror=None):
260 def walk(self, path=None, onerror=None):
261 """Yield (dirpath, dirs, files) tuple for each directories under path
261 """Yield (dirpath, dirs, files) tuple for each directories under path
262
262
263 ``dirpath`` is relative one from the root of this vfs. This
263 ``dirpath`` is relative one from the root of this vfs. This
264 uses ``os.sep`` as path separator, even you specify POSIX
264 uses ``os.sep`` as path separator, even you specify POSIX
265 style ``path``.
265 style ``path``.
266
266
267 "The root of this vfs" is represented as empty ``dirpath``.
267 "The root of this vfs" is represented as empty ``dirpath``.
268 """
268 """
269 root = os.path.normpath(self.join(None))
269 root = os.path.normpath(self.join(None))
270 # when dirpath == root, dirpath[prefixlen:] becomes empty
270 # when dirpath == root, dirpath[prefixlen:] becomes empty
271 # because len(dirpath) < prefixlen.
271 # because len(dirpath) < prefixlen.
272 prefixlen = len(pathutil.normasprefix(root))
272 prefixlen = len(pathutil.normasprefix(root))
273 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
273 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
274 yield (dirpath[prefixlen:], dirs, files)
274 yield (dirpath[prefixlen:], dirs, files)
275
275
276 @contextlib.contextmanager
276 @contextlib.contextmanager
277 def backgroundclosing(self, ui, expectedcount=-1):
277 def backgroundclosing(self, ui, expectedcount=-1):
278 """Allow files to be closed asynchronously.
278 """Allow files to be closed asynchronously.
279
279
280 When this context manager is active, ``backgroundclose`` can be passed
280 When this context manager is active, ``backgroundclose`` can be passed
281 to ``__call__``/``open`` to result in the file possibly being closed
281 to ``__call__``/``open`` to result in the file possibly being closed
282 asynchronously, on a background thread.
282 asynchronously, on a background thread.
283 """
283 """
284 # Sharing backgroundfilecloser between threads is complex and using
284 # Sharing backgroundfilecloser between threads is complex and using
285 # multiple instances puts us at risk of running out of file descriptors
285 # multiple instances puts us at risk of running out of file descriptors
286 # only allow to use backgroundfilecloser when in main thread.
286 # only allow to use backgroundfilecloser when in main thread.
287 if not isinstance(threading.currentThread(), threading._MainThread):
287 if not isinstance(threading.currentThread(), threading._MainThread):
288 yield
288 yield
289 return
289 return
290 vfs = getattr(self, 'vfs', self)
290 vfs = getattr(self, 'vfs', self)
291 if getattr(vfs, '_backgroundfilecloser', None):
291 if getattr(vfs, '_backgroundfilecloser', None):
292 raise error.Abort(
292 raise error.Abort(
293 _('can only have 1 active background file closer'))
293 _('can only have 1 active background file closer'))
294
294
295 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
295 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
296 try:
296 try:
297 vfs._backgroundfilecloser = bfc
297 vfs._backgroundfilecloser = bfc
298 yield bfc
298 yield bfc
299 finally:
299 finally:
300 vfs._backgroundfilecloser = None
300 vfs._backgroundfilecloser = None
301
301
302 class vfs(abstractvfs):
302 class vfs(abstractvfs):
303 '''Operate files relative to a base directory
303 '''Operate files relative to a base directory
304
304
305 This class is used to hide the details of COW semantics and
305 This class is used to hide the details of COW semantics and
306 remote file access from higher level code.
306 remote file access from higher level code.
307
307
308 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
308 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
309 (b) the base directory is managed by hg and considered sort-of append-only.
309 (b) the base directory is managed by hg and considered sort-of append-only.
310 See pathutil.pathauditor() for details.
310 See pathutil.pathauditor() for details.
311 '''
311 '''
312 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
312 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
313 realpath=False):
313 realpath=False):
314 if expandpath:
314 if expandpath:
315 base = util.expandpath(base)
315 base = util.expandpath(base)
316 if realpath:
316 if realpath:
317 base = os.path.realpath(base)
317 base = os.path.realpath(base)
318 self.base = base
318 self.base = base
319 self._audit = audit
319 self._audit = audit
320 if audit:
320 if audit:
321 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
321 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
322 else:
322 else:
323 self.audit = (lambda path, mode=None: True)
323 self.audit = (lambda path, mode=None: True)
324 self.createmode = None
324 self.createmode = None
325 self._trustnlink = None
325 self._trustnlink = None
326
326
327 @util.propertycache
327 @util.propertycache
328 def _cansymlink(self):
328 def _cansymlink(self):
329 return util.checklink(self.base)
329 return util.checklink(self.base)
330
330
331 @util.propertycache
331 @util.propertycache
332 def _chmod(self):
332 def _chmod(self):
333 return util.checkexec(self.base)
333 return util.checkexec(self.base)
334
334
335 def _fixfilemode(self, name):
335 def _fixfilemode(self, name):
336 if self.createmode is None or not self._chmod:
336 if self.createmode is None or not self._chmod:
337 return
337 return
338 os.chmod(name, self.createmode & 0o666)
338 os.chmod(name, self.createmode & 0o666)
339
339
340 def _auditpath(self, path, mode):
340 def _auditpath(self, path, mode):
341 if self._audit:
341 if self._audit:
342 r = util.checkosfilename(path)
342 r = util.checkosfilename(path)
343 if r:
343 if r:
344 raise error.Abort("%s: %r" % (r, path))
344 raise error.Abort("%s: %r" % (r, path))
345 self.audit(path, mode=mode)
345 self.audit(path, mode=mode)
346
346
347 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
347 def __call__(self, path, mode="r", atomictemp=False, notindexed=False,
348 backgroundclose=False, checkambig=False, auditpath=True):
348 backgroundclose=False, checkambig=False, auditpath=True,
349 makeparentdirs=True):
349 '''Open ``path`` file, which is relative to vfs root.
350 '''Open ``path`` file, which is relative to vfs root.
350
351
351 Newly created directories are marked as "not to be indexed by
352 By default, parent directories are created as needed. Newly created
352 the content indexing service", if ``notindexed`` is specified
353 directories are marked as "not to be indexed by the content indexing
353 for "write" mode access.
354 service", if ``notindexed`` is specified for "write" mode access.
355 Set ``makeparentdirs=False`` to not create directories implicitly.
354
356
355 If ``backgroundclose`` is passed, the file may be closed asynchronously.
357 If ``backgroundclose`` is passed, the file may be closed asynchronously.
356 It can only be used if the ``self.backgroundclosing()`` context manager
358 It can only be used if the ``self.backgroundclosing()`` context manager
357 is active. This should only be specified if the following criteria hold:
359 is active. This should only be specified if the following criteria hold:
358
360
359 1. There is a potential for writing thousands of files. Unless you
361 1. There is a potential for writing thousands of files. Unless you
360 are writing thousands of files, the performance benefits of
362 are writing thousands of files, the performance benefits of
361 asynchronously closing files is not realized.
363 asynchronously closing files is not realized.
362 2. Files are opened exactly once for the ``backgroundclosing``
364 2. Files are opened exactly once for the ``backgroundclosing``
363 active duration and are therefore free of race conditions between
365 active duration and are therefore free of race conditions between
364 closing a file on a background thread and reopening it. (If the
366 closing a file on a background thread and reopening it. (If the
365 file were opened multiple times, there could be unflushed data
367 file were opened multiple times, there could be unflushed data
366 because the original file handle hasn't been flushed/closed yet.)
368 because the original file handle hasn't been flushed/closed yet.)
367
369
368 ``checkambig`` argument is passed to atomictemplfile (valid
370 ``checkambig`` argument is passed to atomictemplfile (valid
369 only for writing), and is useful only if target file is
371 only for writing), and is useful only if target file is
370 guarded by any lock (e.g. repo.lock or repo.wlock).
372 guarded by any lock (e.g. repo.lock or repo.wlock).
371
373
372 To avoid file stat ambiguity forcibly, checkambig=True involves
374 To avoid file stat ambiguity forcibly, checkambig=True involves
373 copying ``path`` file opened in "append" mode (e.g. for
375 copying ``path`` file opened in "append" mode (e.g. for
374 truncation), if it is owned by another. Therefore, use
376 truncation), if it is owned by another. Therefore, use
375 combination of append mode and checkambig=True only in limited
377 combination of append mode and checkambig=True only in limited
376 cases (see also issue5418 and issue5584 for detail).
378 cases (see also issue5418 and issue5584 for detail).
377 '''
379 '''
378 if auditpath:
380 if auditpath:
379 self._auditpath(path, mode)
381 self._auditpath(path, mode)
380 f = self.join(path)
382 f = self.join(path)
381
383
382 if "b" not in mode:
384 if "b" not in mode:
383 mode += "b" # for that other OS
385 mode += "b" # for that other OS
384
386
385 nlink = -1
387 nlink = -1
386 if mode not in ('r', 'rb'):
388 if mode not in ('r', 'rb'):
387 dirname, basename = util.split(f)
389 dirname, basename = util.split(f)
388 # If basename is empty, then the path is malformed because it points
390 # If basename is empty, then the path is malformed because it points
389 # to a directory. Let the posixfile() call below raise IOError.
391 # to a directory. Let the posixfile() call below raise IOError.
390 if basename:
392 if basename:
391 if atomictemp:
393 if atomictemp:
392 util.makedirs(dirname, self.createmode, notindexed)
394 if makeparentdirs:
395 util.makedirs(dirname, self.createmode, notindexed)
393 return util.atomictempfile(f, mode, self.createmode,
396 return util.atomictempfile(f, mode, self.createmode,
394 checkambig=checkambig)
397 checkambig=checkambig)
395 try:
398 try:
396 if 'w' in mode:
399 if 'w' in mode:
397 util.unlink(f)
400 util.unlink(f)
398 nlink = 0
401 nlink = 0
399 else:
402 else:
400 # nlinks() may behave differently for files on Windows
403 # nlinks() may behave differently for files on Windows
401 # shares if the file is open.
404 # shares if the file is open.
402 with util.posixfile(f):
405 with util.posixfile(f):
403 nlink = util.nlinks(f)
406 nlink = util.nlinks(f)
404 if nlink < 1:
407 if nlink < 1:
405 nlink = 2 # force mktempcopy (issue1922)
408 nlink = 2 # force mktempcopy (issue1922)
406 except (OSError, IOError) as e:
409 except (OSError, IOError) as e:
407 if e.errno != errno.ENOENT:
410 if e.errno != errno.ENOENT:
408 raise
411 raise
409 nlink = 0
412 nlink = 0
410 util.makedirs(dirname, self.createmode, notindexed)
413 if makeparentdirs:
414 util.makedirs(dirname, self.createmode, notindexed)
411 if nlink > 0:
415 if nlink > 0:
412 if self._trustnlink is None:
416 if self._trustnlink is None:
413 self._trustnlink = nlink > 1 or util.checknlink(f)
417 self._trustnlink = nlink > 1 or util.checknlink(f)
414 if nlink > 1 or not self._trustnlink:
418 if nlink > 1 or not self._trustnlink:
415 util.rename(util.mktempcopy(f), f)
419 util.rename(util.mktempcopy(f), f)
416 fp = util.posixfile(f, mode)
420 fp = util.posixfile(f, mode)
417 if nlink == 0:
421 if nlink == 0:
418 self._fixfilemode(f)
422 self._fixfilemode(f)
419
423
420 if checkambig:
424 if checkambig:
421 if mode in ('r', 'rb'):
425 if mode in ('r', 'rb'):
422 raise error.Abort(_('implementation error: mode %s is not'
426 raise error.Abort(_('implementation error: mode %s is not'
423 ' valid for checkambig=True') % mode)
427 ' valid for checkambig=True') % mode)
424 fp = checkambigatclosing(fp)
428 fp = checkambigatclosing(fp)
425
429
426 if (backgroundclose and
430 if (backgroundclose and
427 isinstance(threading.currentThread(), threading._MainThread)):
431 isinstance(threading.currentThread(), threading._MainThread)):
428 if not self._backgroundfilecloser:
432 if not self._backgroundfilecloser:
429 raise error.Abort(_('backgroundclose can only be used when a '
433 raise error.Abort(_('backgroundclose can only be used when a '
430 'backgroundclosing context manager is active')
434 'backgroundclosing context manager is active')
431 )
435 )
432
436
433 fp = delayclosedfile(fp, self._backgroundfilecloser)
437 fp = delayclosedfile(fp, self._backgroundfilecloser)
434
438
435 return fp
439 return fp
436
440
437 def symlink(self, src, dst):
441 def symlink(self, src, dst):
438 self.audit(dst)
442 self.audit(dst)
439 linkname = self.join(dst)
443 linkname = self.join(dst)
440 util.tryunlink(linkname)
444 util.tryunlink(linkname)
441
445
442 util.makedirs(os.path.dirname(linkname), self.createmode)
446 util.makedirs(os.path.dirname(linkname), self.createmode)
443
447
444 if self._cansymlink:
448 if self._cansymlink:
445 try:
449 try:
446 os.symlink(src, linkname)
450 os.symlink(src, linkname)
447 except OSError as err:
451 except OSError as err:
448 raise OSError(err.errno, _('could not symlink to %r: %s') %
452 raise OSError(err.errno, _('could not symlink to %r: %s') %
449 (src, encoding.strtolocal(err.strerror)),
453 (src, encoding.strtolocal(err.strerror)),
450 linkname)
454 linkname)
451 else:
455 else:
452 self.write(dst, src)
456 self.write(dst, src)
453
457
454 def join(self, path, *insidef):
458 def join(self, path, *insidef):
455 if path:
459 if path:
456 return os.path.join(self.base, path, *insidef)
460 return os.path.join(self.base, path, *insidef)
457 else:
461 else:
458 return self.base
462 return self.base
459
463
460 opener = vfs
464 opener = vfs
461
465
462 class proxyvfs(object):
466 class proxyvfs(object):
463 def __init__(self, vfs):
467 def __init__(self, vfs):
464 self.vfs = vfs
468 self.vfs = vfs
465
469
466 @property
470 @property
467 def options(self):
471 def options(self):
468 return self.vfs.options
472 return self.vfs.options
469
473
470 @options.setter
474 @options.setter
471 def options(self, value):
475 def options(self, value):
472 self.vfs.options = value
476 self.vfs.options = value
473
477
474 class filtervfs(abstractvfs, proxyvfs):
478 class filtervfs(abstractvfs, proxyvfs):
475 '''Wrapper vfs for filtering filenames with a function.'''
479 '''Wrapper vfs for filtering filenames with a function.'''
476
480
477 def __init__(self, vfs, filter):
481 def __init__(self, vfs, filter):
478 proxyvfs.__init__(self, vfs)
482 proxyvfs.__init__(self, vfs)
479 self._filter = filter
483 self._filter = filter
480
484
481 def __call__(self, path, *args, **kwargs):
485 def __call__(self, path, *args, **kwargs):
482 return self.vfs(self._filter(path), *args, **kwargs)
486 return self.vfs(self._filter(path), *args, **kwargs)
483
487
484 def join(self, path, *insidef):
488 def join(self, path, *insidef):
485 if path:
489 if path:
486 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
490 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
487 else:
491 else:
488 return self.vfs.join(path)
492 return self.vfs.join(path)
489
493
490 filteropener = filtervfs
494 filteropener = filtervfs
491
495
492 class readonlyvfs(abstractvfs, proxyvfs):
496 class readonlyvfs(abstractvfs, proxyvfs):
493 '''Wrapper vfs preventing any writing.'''
497 '''Wrapper vfs preventing any writing.'''
494
498
495 def __init__(self, vfs):
499 def __init__(self, vfs):
496 proxyvfs.__init__(self, vfs)
500 proxyvfs.__init__(self, vfs)
497
501
498 def __call__(self, path, mode='r', *args, **kw):
502 def __call__(self, path, mode='r', *args, **kw):
499 if mode not in ('r', 'rb'):
503 if mode not in ('r', 'rb'):
500 raise error.Abort(_('this vfs is read only'))
504 raise error.Abort(_('this vfs is read only'))
501 return self.vfs(path, mode, *args, **kw)
505 return self.vfs(path, mode, *args, **kw)
502
506
503 def join(self, path, *insidef):
507 def join(self, path, *insidef):
504 return self.vfs.join(path, *insidef)
508 return self.vfs.join(path, *insidef)
505
509
506 class closewrapbase(object):
510 class closewrapbase(object):
507 """Base class of wrapper, which hooks closing
511 """Base class of wrapper, which hooks closing
508
512
509 Do not instantiate outside of the vfs layer.
513 Do not instantiate outside of the vfs layer.
510 """
514 """
511 def __init__(self, fh):
515 def __init__(self, fh):
512 object.__setattr__(self, r'_origfh', fh)
516 object.__setattr__(self, r'_origfh', fh)
513
517
514 def __getattr__(self, attr):
518 def __getattr__(self, attr):
515 return getattr(self._origfh, attr)
519 return getattr(self._origfh, attr)
516
520
517 def __setattr__(self, attr, value):
521 def __setattr__(self, attr, value):
518 return setattr(self._origfh, attr, value)
522 return setattr(self._origfh, attr, value)
519
523
520 def __delattr__(self, attr):
524 def __delattr__(self, attr):
521 return delattr(self._origfh, attr)
525 return delattr(self._origfh, attr)
522
526
523 def __enter__(self):
527 def __enter__(self):
524 return self._origfh.__enter__()
528 return self._origfh.__enter__()
525
529
526 def __exit__(self, exc_type, exc_value, exc_tb):
530 def __exit__(self, exc_type, exc_value, exc_tb):
527 raise NotImplementedError('attempted instantiating ' + str(type(self)))
531 raise NotImplementedError('attempted instantiating ' + str(type(self)))
528
532
529 def close(self):
533 def close(self):
530 raise NotImplementedError('attempted instantiating ' + str(type(self)))
534 raise NotImplementedError('attempted instantiating ' + str(type(self)))
531
535
532 class delayclosedfile(closewrapbase):
536 class delayclosedfile(closewrapbase):
533 """Proxy for a file object whose close is delayed.
537 """Proxy for a file object whose close is delayed.
534
538
535 Do not instantiate outside of the vfs layer.
539 Do not instantiate outside of the vfs layer.
536 """
540 """
537 def __init__(self, fh, closer):
541 def __init__(self, fh, closer):
538 super(delayclosedfile, self).__init__(fh)
542 super(delayclosedfile, self).__init__(fh)
539 object.__setattr__(self, r'_closer', closer)
543 object.__setattr__(self, r'_closer', closer)
540
544
541 def __exit__(self, exc_type, exc_value, exc_tb):
545 def __exit__(self, exc_type, exc_value, exc_tb):
542 self._closer.close(self._origfh)
546 self._closer.close(self._origfh)
543
547
544 def close(self):
548 def close(self):
545 self._closer.close(self._origfh)
549 self._closer.close(self._origfh)
546
550
547 class backgroundfilecloser(object):
551 class backgroundfilecloser(object):
548 """Coordinates background closing of file handles on multiple threads."""
552 """Coordinates background closing of file handles on multiple threads."""
549 def __init__(self, ui, expectedcount=-1):
553 def __init__(self, ui, expectedcount=-1):
550 self._running = False
554 self._running = False
551 self._entered = False
555 self._entered = False
552 self._threads = []
556 self._threads = []
553 self._threadexception = None
557 self._threadexception = None
554
558
555 # Only Windows/NTFS has slow file closing. So only enable by default
559 # Only Windows/NTFS has slow file closing. So only enable by default
556 # on that platform. But allow to be enabled elsewhere for testing.
560 # on that platform. But allow to be enabled elsewhere for testing.
557 defaultenabled = pycompat.iswindows
561 defaultenabled = pycompat.iswindows
558 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
562 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
559
563
560 if not enabled:
564 if not enabled:
561 return
565 return
562
566
563 # There is overhead to starting and stopping the background threads.
567 # There is overhead to starting and stopping the background threads.
564 # Don't do background processing unless the file count is large enough
568 # Don't do background processing unless the file count is large enough
565 # to justify it.
569 # to justify it.
566 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
570 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
567 # FUTURE dynamically start background threads after minfilecount closes.
571 # FUTURE dynamically start background threads after minfilecount closes.
568 # (We don't currently have any callers that don't know their file count)
572 # (We don't currently have any callers that don't know their file count)
569 if expectedcount > 0 and expectedcount < minfilecount:
573 if expectedcount > 0 and expectedcount < minfilecount:
570 return
574 return
571
575
572 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
576 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
573 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
577 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
574
578
575 ui.debug('starting %d threads for background file closing\n' %
579 ui.debug('starting %d threads for background file closing\n' %
576 threadcount)
580 threadcount)
577
581
578 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
582 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
579 self._running = True
583 self._running = True
580
584
581 for i in range(threadcount):
585 for i in range(threadcount):
582 t = threading.Thread(target=self._worker, name='backgroundcloser')
586 t = threading.Thread(target=self._worker, name='backgroundcloser')
583 self._threads.append(t)
587 self._threads.append(t)
584 t.start()
588 t.start()
585
589
586 def __enter__(self):
590 def __enter__(self):
587 self._entered = True
591 self._entered = True
588 return self
592 return self
589
593
590 def __exit__(self, exc_type, exc_value, exc_tb):
594 def __exit__(self, exc_type, exc_value, exc_tb):
591 self._running = False
595 self._running = False
592
596
593 # Wait for threads to finish closing so open files don't linger for
597 # Wait for threads to finish closing so open files don't linger for
594 # longer than lifetime of context manager.
598 # longer than lifetime of context manager.
595 for t in self._threads:
599 for t in self._threads:
596 t.join()
600 t.join()
597
601
598 def _worker(self):
602 def _worker(self):
599 """Main routine for worker thread."""
603 """Main routine for worker thread."""
600 while True:
604 while True:
601 try:
605 try:
602 fh = self._queue.get(block=True, timeout=0.100)
606 fh = self._queue.get(block=True, timeout=0.100)
603 # Need to catch or the thread will terminate and
607 # Need to catch or the thread will terminate and
604 # we could orphan file descriptors.
608 # we could orphan file descriptors.
605 try:
609 try:
606 fh.close()
610 fh.close()
607 except Exception as e:
611 except Exception as e:
608 # Stash so can re-raise from main thread later.
612 # Stash so can re-raise from main thread later.
609 self._threadexception = e
613 self._threadexception = e
610 except pycompat.queue.Empty:
614 except pycompat.queue.Empty:
611 if not self._running:
615 if not self._running:
612 break
616 break
613
617
614 def close(self, fh):
618 def close(self, fh):
615 """Schedule a file for closing."""
619 """Schedule a file for closing."""
616 if not self._entered:
620 if not self._entered:
617 raise error.Abort(_('can only call close() when context manager '
621 raise error.Abort(_('can only call close() when context manager '
618 'active'))
622 'active'))
619
623
620 # If a background thread encountered an exception, raise now so we fail
624 # If a background thread encountered an exception, raise now so we fail
621 # fast. Otherwise we may potentially go on for minutes until the error
625 # fast. Otherwise we may potentially go on for minutes until the error
622 # is acted on.
626 # is acted on.
623 if self._threadexception:
627 if self._threadexception:
624 e = self._threadexception
628 e = self._threadexception
625 self._threadexception = None
629 self._threadexception = None
626 raise e
630 raise e
627
631
628 # If we're not actively running, close synchronously.
632 # If we're not actively running, close synchronously.
629 if not self._running:
633 if not self._running:
630 fh.close()
634 fh.close()
631 return
635 return
632
636
633 self._queue.put(fh, block=True, timeout=None)
637 self._queue.put(fh, block=True, timeout=None)
634
638
635 class checkambigatclosing(closewrapbase):
639 class checkambigatclosing(closewrapbase):
636 """Proxy for a file object, to avoid ambiguity of file stat
640 """Proxy for a file object, to avoid ambiguity of file stat
637
641
638 See also util.filestat for detail about "ambiguity of file stat".
642 See also util.filestat for detail about "ambiguity of file stat".
639
643
640 This proxy is useful only if the target file is guarded by any
644 This proxy is useful only if the target file is guarded by any
641 lock (e.g. repo.lock or repo.wlock)
645 lock (e.g. repo.lock or repo.wlock)
642
646
643 Do not instantiate outside of the vfs layer.
647 Do not instantiate outside of the vfs layer.
644 """
648 """
645 def __init__(self, fh):
649 def __init__(self, fh):
646 super(checkambigatclosing, self).__init__(fh)
650 super(checkambigatclosing, self).__init__(fh)
647 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
651 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
648
652
649 def _checkambig(self):
653 def _checkambig(self):
650 oldstat = self._oldstat
654 oldstat = self._oldstat
651 if oldstat.stat:
655 if oldstat.stat:
652 _avoidambig(self._origfh.name, oldstat)
656 _avoidambig(self._origfh.name, oldstat)
653
657
654 def __exit__(self, exc_type, exc_value, exc_tb):
658 def __exit__(self, exc_type, exc_value, exc_tb):
655 self._origfh.__exit__(exc_type, exc_value, exc_tb)
659 self._origfh.__exit__(exc_type, exc_value, exc_tb)
656 self._checkambig()
660 self._checkambig()
657
661
658 def close(self):
662 def close(self):
659 self._origfh.close()
663 self._origfh.close()
660 self._checkambig()
664 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now