diff -r d0f775432705 Doc/library/shutil.rst --- a/Doc/library/shutil.rst Thu Jun 28 01:45:48 2012 +0200 +++ b/Doc/library/shutil.rst Thu Jun 28 11:03:18 2012 +0300 @@ -47,7 +47,7 @@ be copied. -.. function:: copyfile(src, dst, symlinks=False) +.. function:: copyfile(src, dst, follow_symlinks=True) Copy the contents (no metadata) of the file named *src* to a file named *dst* and return *dst*. *dst* must be the complete target file name; look at @@ -59,63 +59,63 @@ such as character or block devices and pipes cannot be copied with this 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 + If *follow_symlinks* is false and *src* is a symbolic link, a new symbolic link will be created instead of copying the file *src* points to. .. versionchanged:: 3.3 :exc:`IOError` used to be raised instead of :exc:`OSError`. - Added *symlinks* argument. + Added *follow_symlinks* argument. .. versionchanged:: 3.3 Added return of the *dst*. -.. function:: copymode(src, dst, symlinks=False) +.. function:: copymode(src, dst, follow_symlinks=True) 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. If - *symlinks* is true, *src* a symbolic link and the operating system supports + *follow_symlinks* is false, *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. + Added *follow_symlinks* argument. -.. function:: copystat(src, dst, symlinks=False) +.. function:: copystat(src, dst, follow_symlinks=True) 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* 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 + symbolic links and *follow_symlinks* false, the stats of the link will be copied as far as the platform allows. .. versionchanged:: 3.3 - Added *symlinks* argument. + Added *follow_symlinks* argument. -.. function:: copy(src, dst, symlinks=False) +.. function:: copy(src, dst, follow_symlinks=True) Copy the file *src* to the file or directory *dst* and return the file's destination. If *dst* is a directory, a file with the same basename as *src* is created (or overwritten) in the directory specified. Permission bits are copied. *src* and *dst* are path - names given as strings. If *symlinks* is true, symbolic links won't be + names given as strings. If *follow_symlinks* is false, symbolic links won't be followed but recreated instead -- this resembles GNU's :program:`cp -P`. .. versionchanged:: 3.3 - Added *symlinks* argument. + Added *follow_symlinks* argument. .. versionchanged:: 3.3 Added return of the *dst*. -.. function:: copy2(src, dst, symlinks=False) +.. function:: copy2(src, dst, follow_symlinks=False) Similar to :func:`shutil.copy`, including that the destination is returned, but metadata is copied as well. This is - similar to the Unix command :program:`cp -p`. If *symlinks* is true, + similar to the Unix command :program:`cp -p`. If *follow_symlinks* is false, symbolic links won't be followed but recreated instead -- this resembles GNU's :program:`cp -P`. .. versionchanged:: 3.3 - Added *symlinks* argument, try to copy extended file system attributes + Added *follow_symlinks* argument, try to copy extended file system attributes too (currently Linux only). .. versionchanged:: 3.3 @@ -128,7 +128,9 @@ match one of the glob-style *patterns* provided. See the example below. -.. function:: copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False) +.. function:: copytree(src, dst, symlinks=False, ignore=None, + copy_function=copy2, ignore_dangling_symlinks=False, *, + follow_symlinks=True) Recursively copy an entire directory tree rooted at *src*, returning the destination directory. The destination @@ -137,15 +139,17 @@ are copied with :func:`copystat`, individual files are copied using :func:`shutil.copy2`. - If *symlinks* is true, symbolic links in the source tree are represented as + If *follow_symlinks* is false or *symlinks* is true, + symbolic links in the source tree are represented as symbolic links in the new tree and the metadata of the original links will be copied as far as the platform allows; if false or omitted, the contents 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 *follow_symlinks* is true or *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 a :exc:`Error` exception at the end of the copy process. - You can set the optional *ignore_dangling_symlinks* flag to true if you + You can set the optional *ignore_dangling_symlinks* flag to ``True`` if you want to silence this exception. Notice that this option has no effect on platforms that don't support :func:`os.symlink`. @@ -172,10 +176,15 @@ .. versionchanged:: 3.2 Added the *ignore_dangling_symlinks* argument to silent dangling symlinks - errors when *symlinks* is false. + errors when *follow_symlinks* is true or *symlinks* is false. .. versionchanged:: 3.3 - Copy metadata when *symlinks* is false. + Copy metadata when *follow_symlinks* is true or *symlinks* is false. + + .. versionchanged:: 3.3 + Added *follow_symlinks* argument with opposite meaning to *symlinks*. + *symlinks* should not be used in new code. A :exc:`TypeError` is raised if + both *symlinks* and *follow_symlinks* are specified. .. versionchanged:: 3.3 Added return of the *dst*. @@ -312,7 +321,7 @@ above, with the docstring omitted. It demonstrates many of the other functions provided by this module. :: - def copytree(src, dst, symlinks=False): + def copytree(src, dst, *, follow_symlinks=True): names = os.listdir(src) os.makedirs(dst) errors = [] @@ -320,11 +329,11 @@ srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: - if symlinks and os.path.islink(srcname): + if not follow_symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): - copytree(srcname, dstname, symlinks) + copytree(srcname, dstname, follow_symlinks = follow_symlinks) else: copy2(srcname, dstname) # XXX What about devices, sockets etc.? diff -r d0f775432705 Lib/os.py --- a/Lib/os.py Thu Jun 28 01:45:48 2012 +0200 +++ b/Lib/os.py Thu Jun 28 11:03:18 2012 +0300 @@ -388,6 +388,15 @@ dirs.remove('CVS') # don't visit CVS directories """ + try: + fwalk + except NameError: + pass + else: + for x in fwalk(top, topdown=topdown, onerror=onerror, followlinks=followlinks): + yield x[:-1] + return + islink, join, isdir = path.islink, path.join, path.isdir # We may not have read permission for top, in which case we can't diff -r d0f775432705 Lib/shutil.py --- a/Lib/shutil.py Thu Jun 28 01:45:48 2012 +0200 +++ b/Lib/shutil.py Thu Jun 28 11:03:18 2012 +0300 @@ -82,10 +82,10 @@ return (os.path.normcase(os.path.abspath(src)) == os.path.normcase(os.path.abspath(dst))) -def copyfile(src, dst, symlinks=False): +def copyfile(src, dst, follow_symlinks=True): """Copy data from src to dst. - If optional flag `symlinks` is set and `src` is a symbolic link, a new + If optional flag `follow_symlinks` is not set and `src` is a symbolic link, a new symlink will be created instead of copying the file it points to. """ @@ -103,7 +103,7 @@ if stat.S_ISFIFO(st.st_mode): raise SpecialFileError("`%s` is a named pipe" % fn) - if symlinks and os.path.islink(src): + if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: with open(src, 'rb') as fsrc: @@ -111,15 +111,15 @@ copyfileobj(fsrc, fdst) return dst -def copymode(src, dst, symlinks=False): +def copymode(src, dst, follow_symlinks=True): """Copy mode bits from src to dst. - If the optional flag `symlinks` is set, symlinks aren't followed if and + If the optional flag `follow_symlinks` is not set, symlinks aren't followed if and only if both `src` and `dst` are symlinks. If `lchmod` isn't available (eg. Linux), in these cases, this method does nothing. """ - if symlinks and os.path.islink(src) and os.path.islink(dst): + if not follow_symlinks and os.path.islink(src) and os.path.islink(dst): if hasattr(os, 'lchmod'): stat_func, chmod_func = os.lstat, os.lchmod else: @@ -132,10 +132,10 @@ st = stat_func(src) chmod_func(dst, stat.S_IMODE(st.st_mode)) -def copystat(src, dst, symlinks=False): +def copystat(src, dst, follow_symlinks=True): """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 + If the optional flag `follow_symlinks` is not set, symlinks aren't followed if and only if both `src` and `dst` are symlinks. """ @@ -143,7 +143,7 @@ pass # follow symlinks (aka don't not follow symlinks) - follow = not (symlinks and os.path.islink(src) and os.path.islink(dst)) + follow = follow_symlinks or not (os.path.islink(src) and os.path.islink(dst)) if follow: # use the real function if it exists def lookup(name): @@ -186,19 +186,19 @@ raise if hasattr(os, 'listxattr'): - def _copyxattr(src, dst, symlinks=False): + def _copyxattr(src, dst, follow_symlinks=True): """Copy extended filesystem attributes from `src` to `dst`. Overwrite existing attributes. - If the optional flag `symlinks` is set, symlinks won't be followed. + If the optional flag `follow_symlinks` is not set, symlinks won't be followed. """ - for name in os.listxattr(src, follow_symlinks=symlinks): + for name in os.listxattr(src, follow_symlinks=follow_symlinks): try: - value = os.getxattr(src, name, follow_symlinks=symlinks) - os.setxattr(dst, name, value, follow_symlinks=symlinks) + value = os.getxattr(src, name, follow_symlinks=follow_symlinks) + os.setxattr(dst, name, value, follow_symlinks=follow_symlinks) except OSError as e: if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA): raise @@ -206,36 +206,36 @@ def _copyxattr(*args, **kwargs): pass -def copy(src, dst, symlinks=False): +def copy(src, dst, follow_symlinks=True): """Copy data and mode bits ("cp src dst"). Return the file's destination. The destination may be a directory. - If the optional flag `symlinks` is set, symlinks won't be followed. This + If the optional flag `follow_symlinks` is not set, symlinks won't be followed. This resembles GNU's "cp -P src dst". """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) - copyfile(src, dst, symlinks=symlinks) - copymode(src, dst, symlinks=symlinks) + copyfile(src, dst, follow_symlinks=follow_symlinks) + copymode(src, dst, follow_symlinks=follow_symlinks) return dst -def copy2(src, dst, symlinks=False): +def copy2(src, dst, follow_symlinks=True): """Copy data and all stat info ("cp -p src dst"). Return the file's destination." The destination may be a directory. - If the optional flag `symlinks` is set, symlinks won't be followed. This + If the optional flag `follow_symlinks` is set, symlinks won't be followed. This resembles GNU's "cp -P src dst". """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) - copyfile(src, dst, symlinks=symlinks) - copystat(src, dst, symlinks=symlinks) - _copyxattr(src, dst, symlinks=symlinks) + copyfile(src, dst, follow_symlinks=follow_symlinks) + copystat(src, dst, follow_symlinks=follow_symlinks) + _copyxattr(src, dst, follow_symlinks=follow_symlinks) return dst def ignore_patterns(*patterns): @@ -250,8 +250,10 @@ return set(ignored_names) return _ignore_patterns -def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, - ignore_dangling_symlinks=False): +_sentry = object() + +def copytree(src, dst, symlinks=_sentry, ignore=None, copy_function=copy2, + ignore_dangling_symlinks=False, *, follow_symlinks=_sentry): """Recursively copy a directory tree. The destination directory must not already exist. @@ -264,7 +266,7 @@ exist, an exception will be added in the list of errors raised in an Error exception at the end of the copy process. - You can set the optional ignore_dangling_symlinks flag to true if you + You can set the optional ignore_dangling_symlinks flag to True if you want to silence this exception. Notice that this has no effect on platforms that don't support os.symlink. @@ -286,6 +288,12 @@ function that supports the same signature (like copy()) can be used. """ + if symlinks is _sentry: + symlinks = follow_symlinks is not _sentry and not follow_symlinks + elif follow_symlinks is not _sentry: + raise TypeError("copytree() got values for both 'symlinks' and" + " 'follow_symlinks' arguments") + names = os.listdir(src) if ignore is not None: ignored_names = ignore(src, names) @@ -307,7 +315,7 @@ # code with a custom `copy_function` may rely on copytree # doing the right thing. os.symlink(linkto, dstname) - copystat(srcname, dstname, symlinks=symlinks) + copystat(srcname, dstname, follow_symlinks=not symlinks) else: # ignore dangling symlink if the flag is on if not os.path.exists(linkto) and ignore_dangling_symlinks: diff -r d0f775432705 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Thu Jun 28 01:45:48 2012 +0200 +++ b/Lib/test/test_shutil.py Thu Jun 28 11:03:18 2012 +0300 @@ -245,17 +245,17 @@ 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) + shutil.copymode(src_link, dst_link, follow_symlinks=False) 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) + shutil.copymode(src_link, dst, follow_symlinks=False) 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) + shutil.copymode(src, dst_link, follow_symlinks=False) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) @unittest.skipIf(hasattr(os, 'lchmod'), 'requires os.lchmod to be missing') @@ -270,7 +270,7 @@ write_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) - shutil.copymode(src_link, dst_link, symlinks=True) # silent fail + shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent fail @support.skip_unless_symlink def test_copystat_symlinks(self): @@ -294,10 +294,10 @@ src_link_stat = os.lstat(src_link) # follow if hasattr(os, 'lchmod'): - shutil.copystat(src_link, dst_link, symlinks=False) + shutil.copystat(src_link, dst_link, follow_symlinks=True) self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode) # don't follow - shutil.copystat(src_link, dst_link, symlinks=True) + shutil.copystat(src_link, dst_link, follow_symlinks=False) dst_link_stat = os.lstat(dst_link) if os.utime in os.supports_follow_symlinks: for attr in 'st_atime', 'st_mtime': @@ -309,7 +309,7 @@ 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) + shutil.copystat(src_link, dst, follow_symlinks=False) self.assertTrue(abs(os.stat(src).st_mtime - os.stat(dst).st_mtime) < 00000.1) @@ -397,10 +397,10 @@ dst_link = os.path.join(tmp_dir, 'qux') write_file(dst, 'bar') os.symlink(dst, dst_link) - shutil._copyxattr(src_link, dst_link, symlinks=True) + shutil._copyxattr(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43') self.assertRaises(OSError, os.getxattr, dst, 'trusted.foo') - shutil._copyxattr(src_link, dst, symlinks=True) + shutil._copyxattr(src_link, dst, follow_symlinks=False) self.assertEqual(os.getxattr(dst, 'trusted.foo'), b'43') @support.skip_unless_symlink @@ -414,12 +414,12 @@ if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO) # don't follow - shutil.copy(src_link, dst, symlinks=False) + shutil.copy(src_link, dst, follow_symlinks=True) 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) + shutil.copy(src_link, dst, follow_symlinks=False) self.assertTrue(os.path.islink(dst)) self.assertEqual(os.readlink(dst), os.readlink(src_link)) if hasattr(os, 'lchmod'): @@ -441,12 +441,12 @@ src_stat = os.stat(src) src_link_stat = os.lstat(src_link) # follow - shutil.copy2(src_link, dst, symlinks=False) + shutil.copy2(src_link, dst, follow_symlinks=True) 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) + shutil.copy2(src_link, dst, follow_symlinks=False) self.assertTrue(os.path.islink(dst)) self.assertEqual(os.readlink(dst), os.readlink(src_link)) dst_stat = os.lstat(dst) @@ -484,7 +484,7 @@ write_file(src, 'foo') os.symlink(src, link) # don't follow - shutil.copyfile(link, dst_link, symlinks=True) + shutil.copyfile(link, dst_link, follow_symlinks=False) self.assertTrue(os.path.islink(dst_link)) self.assertEqual(os.readlink(link), os.readlink(dst_link)) # follow @@ -555,6 +555,33 @@ 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, follow_symlinks=False) + 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_symlinks_old(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')), @@ -565,6 +592,15 @@ if hasattr(os, 'lchflags'): self.assertEqual(dst_stat.st_flags, src_stat.st_flags) + def test_copytree_incompatible_args(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) + self.assertRaises(TypeError, shutil.copytree, src_dir, dst_dir, symlinks=True, follow_symlinks=False) + def test_copytree_with_exclude(self): # creating data join = os.path.join @@ -748,9 +784,9 @@ shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True) self.assertNotIn('test.txt', os.listdir(dst_dir)) - # a dangling symlink is copied if symlinks=True + # a dangling symlink is copied if follow_symlinks=False dst_dir = os.path.join(self.mkdtemp(), 'destination3') - shutil.copytree(src_dir, dst_dir, symlinks=True) + shutil.copytree(src_dir, dst_dir, follow_symlinks=False) self.assertIn('test.txt', os.listdir(dst_dir)) def _copy_file(self, method):