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