mirror of https://github.com/python/cpython
merge heads
This commit is contained in:
commit
9b9c708de1
|
@ -45,7 +45,7 @@ Directory and files operations
|
||||||
be copied.
|
be copied.
|
||||||
|
|
||||||
|
|
||||||
.. function:: copyfile(src, dst)
|
.. function:: copyfile(src, dst[, symlinks=False])
|
||||||
|
|
||||||
Copy the contents (no metadata) of the file named *src* to a file named *dst*.
|
Copy the contents (no metadata) of the file named *src* to a file named *dst*.
|
||||||
*dst* must be the complete target file name; look at :func:`copy` for a copy that
|
*dst* must be the complete target file name; look at :func:`copy` for a copy that
|
||||||
|
@ -56,37 +56,56 @@ Directory and files operations
|
||||||
such as character or block devices and pipes cannot be copied with this
|
such as character or block devices and pipes cannot be copied with this
|
||||||
function. *src* and *dst* are path names given as strings.
|
function. *src* and *dst* are path names given as strings.
|
||||||
|
|
||||||
|
If *symlinks* is true and *src* is a symbolic link, a new symbolic link will
|
||||||
|
be created instead of copying the file *src* points to.
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
:exc:`IOError` used to be raised instead of :exc:`OSError`.
|
:exc:`IOError` used to be raised instead of :exc:`OSError`.
|
||||||
|
Added *symlinks* argument.
|
||||||
|
|
||||||
|
|
||||||
.. function:: copymode(src, dst)
|
.. function:: copymode(src, dst[, symlinks=False])
|
||||||
|
|
||||||
Copy the permission bits from *src* to *dst*. The file contents, owner, and
|
Copy the permission bits from *src* to *dst*. The file contents, owner, and
|
||||||
group are unaffected. *src* and *dst* are path names given as strings.
|
group are unaffected. *src* and *dst* are path names given as strings. If
|
||||||
|
*symlinks* is true, *src* a symbolic link and the operating system supports
|
||||||
|
modes for symbolic links (for example BSD-based ones), the mode of the link
|
||||||
|
will be copied.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Added *symlinks* argument.
|
||||||
|
|
||||||
.. function:: copystat(src, dst)
|
.. function:: copystat(src, dst[, symlinks=False])
|
||||||
|
|
||||||
Copy the permission bits, last access time, last modification time, and flags
|
Copy the permission bits, last access time, last modification time, and flags
|
||||||
from *src* to *dst*. The file contents, owner, and group are unaffected. *src*
|
from *src* to *dst*. The file contents, owner, and group are unaffected. *src*
|
||||||
and *dst* are path names given as strings.
|
and *dst* are path names given as strings. If *src* and *dst* are both
|
||||||
|
symbolic links and *symlinks* true, the stats of the link will be copied as
|
||||||
|
far as the platform allows.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Added *symlinks* argument.
|
||||||
|
|
||||||
.. function:: copy(src, dst)
|
.. function:: copy(src, dst[, symlinks=False]))
|
||||||
|
|
||||||
Copy the file *src* to the file or directory *dst*. If *dst* is a directory, a
|
Copy the file *src* to the file or directory *dst*. If *dst* is a directory, a
|
||||||
file with the same basename as *src* is created (or overwritten) in the
|
file with the same basename as *src* is created (or overwritten) in the
|
||||||
directory specified. Permission bits are copied. *src* and *dst* are path
|
directory specified. Permission bits are copied. *src* and *dst* are path
|
||||||
names given as strings.
|
names given as strings. If *symlinks* is true, symbolic links won't be
|
||||||
|
followed but recreated instead -- this resembles GNU's :program:`cp -P`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Added *symlinks* argument.
|
||||||
|
|
||||||
.. function:: copy2(src, dst)
|
.. function:: copy2(src, dst[, symlinks=False])
|
||||||
|
|
||||||
Similar to :func:`copy`, but metadata is copied as well -- in fact, this is just
|
Similar to :func:`copy`, but metadata is copied as well -- in fact, this is just
|
||||||
:func:`copy` followed by :func:`copystat`. This is similar to the
|
:func:`copy` followed by :func:`copystat`. This is similar to the
|
||||||
Unix command :program:`cp -p`.
|
Unix command :program:`cp -p`. If *symlinks* is true, symbolic links won't
|
||||||
|
be followed but recreated instead -- this resembles GNU's :program:`cp -P`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Added *symlinks* argument.
|
||||||
|
|
||||||
.. function:: ignore_patterns(\*patterns)
|
.. function:: ignore_patterns(\*patterns)
|
||||||
|
|
||||||
|
@ -104,9 +123,9 @@ Directory and files operations
|
||||||
:func:`copy2`.
|
:func:`copy2`.
|
||||||
|
|
||||||
If *symlinks* is true, symbolic links in the source tree are represented as
|
If *symlinks* is true, symbolic links in the source tree are represented as
|
||||||
symbolic links in the new tree, but the metadata of the original links is NOT
|
symbolic links in the new tree and the metadata of the original links will
|
||||||
copied; if false or omitted, the contents and metadata of the linked files
|
be copied as far as the platform allows; if false or omitted, the contents
|
||||||
are copied to the new tree.
|
and metadata of the linked files are copied to the new tree.
|
||||||
|
|
||||||
When *symlinks* is false, if the file pointed by the symlink doesn't
|
When *symlinks* is false, if the file pointed by the symlink doesn't
|
||||||
exist, a exception will be added in the list of errors raised in
|
exist, a exception will be added in the list of errors raised in
|
||||||
|
@ -140,6 +159,9 @@ Directory and files operations
|
||||||
Added the *ignore_dangling_symlinks* argument to silent dangling symlinks
|
Added the *ignore_dangling_symlinks* argument to silent dangling symlinks
|
||||||
errors when *symlinks* is false.
|
errors when *symlinks* is false.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
Copy metadata when *symlinks* is false.
|
||||||
|
|
||||||
|
|
||||||
.. function:: rmtree(path, ignore_errors=False, onerror=None)
|
.. function:: rmtree(path, ignore_errors=False, onerror=None)
|
||||||
|
|
||||||
|
|
101
Lib/shutil.py
101
Lib/shutil.py
|
@ -82,8 +82,13 @@ def _samefile(src, dst):
|
||||||
return (os.path.normcase(os.path.abspath(src)) ==
|
return (os.path.normcase(os.path.abspath(src)) ==
|
||||||
os.path.normcase(os.path.abspath(dst)))
|
os.path.normcase(os.path.abspath(dst)))
|
||||||
|
|
||||||
def copyfile(src, dst):
|
def copyfile(src, dst, symlinks=False):
|
||||||
"""Copy data from src to dst"""
|
"""Copy data from src to dst.
|
||||||
|
|
||||||
|
If optional flag `symlinks` is set and `src` is a symbolic link, a new
|
||||||
|
symlink will be created instead of copying the file it points to.
|
||||||
|
|
||||||
|
"""
|
||||||
if _samefile(src, dst):
|
if _samefile(src, dst):
|
||||||
raise Error("`%s` and `%s` are the same file" % (src, dst))
|
raise Error("`%s` and `%s` are the same file" % (src, dst))
|
||||||
|
|
||||||
|
@ -98,54 +103,94 @@ def copyfile(src, dst):
|
||||||
if stat.S_ISFIFO(st.st_mode):
|
if stat.S_ISFIFO(st.st_mode):
|
||||||
raise SpecialFileError("`%s` is a named pipe" % fn)
|
raise SpecialFileError("`%s` is a named pipe" % fn)
|
||||||
|
|
||||||
with open(src, 'rb') as fsrc:
|
if symlinks and os.path.islink(src):
|
||||||
with open(dst, 'wb') as fdst:
|
os.symlink(os.readlink(src), dst)
|
||||||
copyfileobj(fsrc, fdst)
|
else:
|
||||||
|
with open(src, 'rb') as fsrc:
|
||||||
|
with open(dst, 'wb') as fdst:
|
||||||
|
copyfileobj(fsrc, fdst)
|
||||||
|
|
||||||
def copymode(src, dst):
|
def copymode(src, dst, symlinks=False):
|
||||||
"""Copy mode bits from src to dst"""
|
"""Copy mode bits from src to dst.
|
||||||
if hasattr(os, 'chmod'):
|
|
||||||
st = os.stat(src)
|
|
||||||
mode = stat.S_IMODE(st.st_mode)
|
|
||||||
os.chmod(dst, mode)
|
|
||||||
|
|
||||||
def copystat(src, dst):
|
If the optional flag `symlinks` is set, symlinks aren't followed if and
|
||||||
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
|
only if both `src` and `dst` are symlinks. If `lchmod` isn't available (eg.
|
||||||
st = os.stat(src)
|
Linux), in these cases, this method does nothing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if symlinks and os.path.islink(src) and os.path.islink(dst):
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
stat_func, chmod_func = os.lstat, os.lchmod
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
elif hasattr(os, 'chmod'):
|
||||||
|
stat_func, chmod_func = os.stat, os.chmod
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
st = stat_func(src)
|
||||||
|
chmod_func(dst, stat.S_IMODE(st.st_mode))
|
||||||
|
|
||||||
|
def copystat(src, dst, symlinks=False):
|
||||||
|
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst.
|
||||||
|
|
||||||
|
If the optional flag `symlinks` is set, symlinks aren't followed if and
|
||||||
|
only if both `src` and `dst` are symlinks.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def _nop(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if symlinks and os.path.islink(src) and os.path.islink(dst):
|
||||||
|
stat_func = os.lstat
|
||||||
|
utime_func = os.lutimes if hasattr(os, 'lutimes') else _nop
|
||||||
|
chmod_func = os.lchmod if hasattr(os, 'lchmod') else _nop
|
||||||
|
chflags_func = os.lchflags if hasattr(os, 'lchflags') else _nop
|
||||||
|
else:
|
||||||
|
stat_func = os.stat
|
||||||
|
utime_func = os.utime if hasattr(os, 'utime') else _nop
|
||||||
|
chmod_func = os.chmod if hasattr(os, 'chmod') else _nop
|
||||||
|
chflags_func = os.chflags if hasattr(os, 'chflags') else _nop
|
||||||
|
|
||||||
|
st = stat_func(src)
|
||||||
mode = stat.S_IMODE(st.st_mode)
|
mode = stat.S_IMODE(st.st_mode)
|
||||||
if hasattr(os, 'utime'):
|
utime_func(dst, (st.st_atime, st.st_mtime))
|
||||||
os.utime(dst, (st.st_atime, st.st_mtime))
|
chmod_func(dst, mode)
|
||||||
if hasattr(os, 'chmod'):
|
if hasattr(st, 'st_flags'):
|
||||||
os.chmod(dst, mode)
|
|
||||||
if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
|
|
||||||
try:
|
try:
|
||||||
os.chflags(dst, st.st_flags)
|
chflags_func(dst, st.st_flags)
|
||||||
except OSError as why:
|
except OSError as why:
|
||||||
if (not hasattr(errno, 'EOPNOTSUPP') or
|
if (not hasattr(errno, 'EOPNOTSUPP') or
|
||||||
why.errno != errno.EOPNOTSUPP):
|
why.errno != errno.EOPNOTSUPP):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def copy(src, dst):
|
def copy(src, dst, symlinks=False):
|
||||||
"""Copy data and mode bits ("cp src dst").
|
"""Copy data and mode bits ("cp src dst").
|
||||||
|
|
||||||
The destination may be a directory.
|
The destination may be a directory.
|
||||||
|
|
||||||
|
If the optional flag `symlinks` is set, symlinks won't be followed. This
|
||||||
|
resembles GNU's "cp -P src dst".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(dst):
|
if os.path.isdir(dst):
|
||||||
dst = os.path.join(dst, os.path.basename(src))
|
dst = os.path.join(dst, os.path.basename(src))
|
||||||
copyfile(src, dst)
|
copyfile(src, dst, symlinks=symlinks)
|
||||||
copymode(src, dst)
|
copymode(src, dst, symlinks=symlinks)
|
||||||
|
|
||||||
def copy2(src, dst):
|
def copy2(src, dst, symlinks=False):
|
||||||
"""Copy data and all stat info ("cp -p src dst").
|
"""Copy data and all stat info ("cp -p src dst").
|
||||||
|
|
||||||
The destination may be a directory.
|
The destination may be a directory.
|
||||||
|
|
||||||
|
If the optional flag `symlinks` is set, symlinks won't be followed. This
|
||||||
|
resembles GNU's "cp -P src dst".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(dst):
|
if os.path.isdir(dst):
|
||||||
dst = os.path.join(dst, os.path.basename(src))
|
dst = os.path.join(dst, os.path.basename(src))
|
||||||
copyfile(src, dst)
|
copyfile(src, dst, symlinks=symlinks)
|
||||||
copystat(src, dst)
|
copystat(src, dst, symlinks=symlinks)
|
||||||
|
|
||||||
def ignore_patterns(*patterns):
|
def ignore_patterns(*patterns):
|
||||||
"""Function that can be used as copytree() ignore parameter.
|
"""Function that can be used as copytree() ignore parameter.
|
||||||
|
@ -212,7 +257,11 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
||||||
if os.path.islink(srcname):
|
if os.path.islink(srcname):
|
||||||
linkto = os.readlink(srcname)
|
linkto = os.readlink(srcname)
|
||||||
if symlinks:
|
if symlinks:
|
||||||
|
# We can't just leave it to `copy_function` because legacy
|
||||||
|
# code with a custom `copy_function` may rely on copytree
|
||||||
|
# doing the right thing.
|
||||||
os.symlink(linkto, dstname)
|
os.symlink(linkto, dstname)
|
||||||
|
copystat(srcname, dstname, symlinks=symlinks)
|
||||||
else:
|
else:
|
||||||
# ignore dangling symlink if the flag is on
|
# ignore dangling symlink if the flag is on
|
||||||
if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
||||||
|
|
|
@ -164,6 +164,197 @@ class TestShutil(unittest.TestCase):
|
||||||
self.assertTrue(issubclass(exc[0], OSError))
|
self.assertTrue(issubclass(exc[0], OSError))
|
||||||
self.errorState = 2
|
self.errorState = 2
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod')
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copymode_follow_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
write_file(dst, 'foo')
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
os.symlink(dst, dst_link)
|
||||||
|
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
||||||
|
# file to file
|
||||||
|
os.chmod(dst, stat.S_IRWXO)
|
||||||
|
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
shutil.copymode(src, dst)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
# follow src link
|
||||||
|
os.chmod(dst, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src_link, dst)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
# follow dst link
|
||||||
|
os.chmod(dst, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src, dst_link)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
# follow both links
|
||||||
|
os.chmod(dst, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src_link, dst)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod')
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copymode_symlink_to_symlink(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
write_file(dst, 'foo')
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
os.symlink(dst, dst_link)
|
||||||
|
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
||||||
|
os.chmod(dst, stat.S_IRWXU)
|
||||||
|
os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)
|
||||||
|
# link to link
|
||||||
|
os.lchmod(dst_link, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src_link, dst_link, symlinks=True)
|
||||||
|
self.assertEqual(os.lstat(src_link).st_mode,
|
||||||
|
os.lstat(dst_link).st_mode)
|
||||||
|
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
# src link - use chmod
|
||||||
|
os.lchmod(dst_link, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src_link, dst, symlinks=True)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
# dst link - use chmod
|
||||||
|
os.lchmod(dst_link, stat.S_IRWXO)
|
||||||
|
shutil.copymode(src, dst_link, symlinks=True)
|
||||||
|
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
|
||||||
|
|
||||||
|
@unittest.skipIf(hasattr(os, 'lchmod'), 'requires os.lchmod to be missing')
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copymode_symlink_to_symlink_wo_lchmod(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
write_file(dst, 'foo')
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
os.symlink(dst, dst_link)
|
||||||
|
shutil.copymode(src_link, dst_link, symlinks=True) # silent fail
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copystat_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
dst_link = os.path.join(tmp_dir, 'qux')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
src_stat = os.stat(src)
|
||||||
|
os.utime(src, (src_stat.st_atime,
|
||||||
|
src_stat.st_mtime - 42.0)) # ensure different mtimes
|
||||||
|
write_file(dst, 'bar')
|
||||||
|
self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime)
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
os.symlink(dst, dst_link)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
os.lchmod(src_link, stat.S_IRWXO)
|
||||||
|
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
|
||||||
|
os.lchflags(src_link, stat.UF_NODUMP)
|
||||||
|
src_link_stat = os.lstat(src_link)
|
||||||
|
# follow
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
shutil.copystat(src_link, dst_link, symlinks=False)
|
||||||
|
self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode)
|
||||||
|
# don't follow
|
||||||
|
shutil.copystat(src_link, dst_link, symlinks=True)
|
||||||
|
dst_link_stat = os.lstat(dst_link)
|
||||||
|
if hasattr(os, 'lutimes'):
|
||||||
|
for attr in 'st_atime', 'st_mtime':
|
||||||
|
# The modification times may be truncated in the new file.
|
||||||
|
self.assertLessEqual(getattr(src_link_stat, attr),
|
||||||
|
getattr(dst_link_stat, attr) + 1)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
self.assertEqual(src_link_stat.st_mode, dst_link_stat.st_mode)
|
||||||
|
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):
|
||||||
|
self.assertEqual(src_link_stat.st_flags, dst_link_stat.st_flags)
|
||||||
|
# tell to follow but dst is not a link
|
||||||
|
shutil.copystat(src_link, dst, symlinks=True)
|
||||||
|
self.assertTrue(abs(os.stat(src).st_mtime - os.stat(dst).st_mtime) <
|
||||||
|
00000.1)
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copy_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
||||||
|
# don't follow
|
||||||
|
shutil.copy(src_link, dst, symlinks=False)
|
||||||
|
self.assertFalse(os.path.islink(dst))
|
||||||
|
self.assertEqual(read_file(src), read_file(dst))
|
||||||
|
os.remove(dst)
|
||||||
|
# follow
|
||||||
|
shutil.copy(src_link, dst, symlinks=True)
|
||||||
|
self.assertTrue(os.path.islink(dst))
|
||||||
|
self.assertEqual(os.readlink(dst), os.readlink(src_link))
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
self.assertEqual(os.lstat(src_link).st_mode,
|
||||||
|
os.lstat(dst).st_mode)
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copy2_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
os.symlink(src, src_link)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
||||||
|
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
|
||||||
|
os.lchflags(src_link, stat.UF_NODUMP)
|
||||||
|
src_stat = os.stat(src)
|
||||||
|
src_link_stat = os.lstat(src_link)
|
||||||
|
# follow
|
||||||
|
shutil.copy2(src_link, dst, symlinks=False)
|
||||||
|
self.assertFalse(os.path.islink(dst))
|
||||||
|
self.assertEqual(read_file(src), read_file(dst))
|
||||||
|
os.remove(dst)
|
||||||
|
# don't follow
|
||||||
|
shutil.copy2(src_link, dst, symlinks=True)
|
||||||
|
self.assertTrue(os.path.islink(dst))
|
||||||
|
self.assertEqual(os.readlink(dst), os.readlink(src_link))
|
||||||
|
dst_stat = os.lstat(dst)
|
||||||
|
if hasattr(os, 'lutimes'):
|
||||||
|
for attr in 'st_atime', 'st_mtime':
|
||||||
|
# The modification times may be truncated in the new file.
|
||||||
|
self.assertLessEqual(getattr(src_link_stat, attr),
|
||||||
|
getattr(dst_stat, attr) + 1)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
self.assertEqual(src_link_stat.st_mode, dst_stat.st_mode)
|
||||||
|
self.assertNotEqual(src_stat.st_mode, dst_stat.st_mode)
|
||||||
|
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):
|
||||||
|
self.assertEqual(src_link_stat.st_flags, dst_stat.st_flags)
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copyfile_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src = os.path.join(tmp_dir, 'src')
|
||||||
|
dst = os.path.join(tmp_dir, 'dst')
|
||||||
|
dst_link = os.path.join(tmp_dir, 'dst_link')
|
||||||
|
link = os.path.join(tmp_dir, 'link')
|
||||||
|
write_file(src, 'foo')
|
||||||
|
os.symlink(src, link)
|
||||||
|
# don't follow
|
||||||
|
shutil.copyfile(link, dst_link, symlinks=True)
|
||||||
|
self.assertTrue(os.path.islink(dst_link))
|
||||||
|
self.assertEqual(os.readlink(link), os.readlink(dst_link))
|
||||||
|
# follow
|
||||||
|
shutil.copyfile(link, dst)
|
||||||
|
self.assertFalse(os.path.islink(dst))
|
||||||
|
|
||||||
def test_rmtree_dont_delete_file(self):
|
def test_rmtree_dont_delete_file(self):
|
||||||
# When called on a file instead of a directory, don't delete it.
|
# When called on a file instead of a directory, don't delete it.
|
||||||
handle, path = tempfile.mkstemp()
|
handle, path = tempfile.mkstemp()
|
||||||
|
@ -190,6 +381,34 @@ class TestShutil(unittest.TestCase):
|
||||||
actual = read_file((dst_dir, 'test_dir', 'test.txt'))
|
actual = read_file((dst_dir, 'test_dir', 'test.txt'))
|
||||||
self.assertEqual(actual, '456')
|
self.assertEqual(actual, '456')
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_copytree_symlinks(self):
|
||||||
|
tmp_dir = self.mkdtemp()
|
||||||
|
src_dir = os.path.join(tmp_dir, 'src')
|
||||||
|
dst_dir = os.path.join(tmp_dir, 'dst')
|
||||||
|
sub_dir = os.path.join(src_dir, 'sub')
|
||||||
|
os.mkdir(src_dir)
|
||||||
|
os.mkdir(sub_dir)
|
||||||
|
write_file((src_dir, 'file.txt'), 'foo')
|
||||||
|
src_link = os.path.join(sub_dir, 'link')
|
||||||
|
dst_link = os.path.join(dst_dir, 'sub/link')
|
||||||
|
os.symlink(os.path.join(src_dir, 'file.txt'),
|
||||||
|
src_link)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
||||||
|
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
|
||||||
|
os.lchflags(src_link, stat.UF_NODUMP)
|
||||||
|
src_stat = os.lstat(src_link)
|
||||||
|
shutil.copytree(src_dir, dst_dir, symlinks=True)
|
||||||
|
self.assertTrue(os.path.islink(os.path.join(dst_dir, 'sub', 'link')))
|
||||||
|
self.assertEqual(os.readlink(os.path.join(dst_dir, 'sub', 'link')),
|
||||||
|
os.path.join(src_dir, 'file.txt'))
|
||||||
|
dst_stat = os.lstat(dst_link)
|
||||||
|
if hasattr(os, 'lchmod'):
|
||||||
|
self.assertEqual(dst_stat.st_mode, src_stat.st_mode)
|
||||||
|
if hasattr(os, 'lchflags'):
|
||||||
|
self.assertEqual(dst_stat.st_flags, src_stat.st_flags)
|
||||||
|
|
||||||
def test_copytree_with_exclude(self):
|
def test_copytree_with_exclude(self):
|
||||||
# creating data
|
# creating data
|
||||||
join = os.path.join
|
join = os.path.join
|
||||||
|
|
|
@ -422,6 +422,11 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #12715: Add an optional symlinks argument to shutil functions
|
||||||
|
(copyfile, copymode, copystat, copy, copy2). When that parameter is
|
||||||
|
true, symlinks aren't dereferenced and the operation instead acts on the
|
||||||
|
symlink itself (or creates one, if relevant). Patch by Hynek Schlawack.
|
||||||
|
|
||||||
- Add a flags parameter to select.epoll.
|
- Add a flags parameter to select.epoll.
|
||||||
|
|
||||||
- Issue #12798: Updated the mimetypes documentation.
|
- Issue #12798: Updated the mimetypes documentation.
|
||||||
|
|
Loading…
Reference in New Issue