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