diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 22d6dba9e1a9c6..5bcd6839133ca6 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -226,8 +226,8 @@ Directory and files operations .. function:: copytree(src, dst, symlinks=False, ignore=None, \ - copy_function=copy2, ignore_dangling_symlinks=False, \ - dirs_exist_ok=False) + copy_stat=True, copy_function=copy2, \ + ignore_dangling_symlinks=False, dirs_exist_ok=False) Recursively copy an entire directory tree rooted at *src* to a directory named *dst* and return the destination directory. *dirs_exist_ok* dictates @@ -235,7 +235,10 @@ Directory and files operations already exists. Permissions and times of directories are copied with :func:`copystat`, - individual files are copied using :func:`~shutil.copy2`. + unless *copy_stat* is `False`. Individual files are copied using + :func:`~shutil.copy2` by default. Using `copy_stat=False` along with + `copy_function=shutil.copyfile` is useful when you wish to not use the + source permissions at all. If *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 diff --git a/Lib/shutil.py b/Lib/shutil.py index 949e024853c1d2..503241e2bfea75 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -450,7 +450,7 @@ def _ignore_patterns(path, names): return _ignore_patterns def _copytree(entries, src, dst, symlinks, ignore, copy_function, - ignore_dangling_symlinks, dirs_exist_ok=False): + copy_stat, ignore_dangling_symlinks, dirs_exist_ok=False): if ignore is not None: ignored_names = ignore(os.fspath(src), [x.name for x in entries]) else: @@ -481,7 +481,8 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function, # code with a custom `copy_function` may rely on copytree # doing the right thing. os.symlink(linkto, dstname) - copystat(srcobj, dstname, follow_symlinks=not symlinks) + if copy_stat: + copystat(srcobj, dstname, follow_symlinks=not symlinks) else: # ignore dangling symlink if the flag is on if not os.path.exists(linkto) and ignore_dangling_symlinks: @@ -489,12 +490,13 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function, # otherwise let the copy occur. copy2 will raise an error if srcentry.is_dir(): copytree(srcobj, dstname, symlinks, ignore, - copy_function, dirs_exist_ok=dirs_exist_ok) + copy_function, copy_stat, + dirs_exist_ok=dirs_exist_ok) else: copy_function(srcobj, dstname) elif srcentry.is_dir(): copytree(srcobj, dstname, symlinks, ignore, copy_function, - dirs_exist_ok=dirs_exist_ok) + copy_stat, dirs_exist_ok=dirs_exist_ok) else: # Will raise a SpecialFileError for unsupported file types copy_function(srcobj, dstname) @@ -504,18 +506,20 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function, errors.extend(err.args[0]) except OSError as why: errors.append((srcname, dstname, str(why))) - try: - copystat(src, dst) - except OSError as why: - # Copying file access times may fail on Windows - if getattr(why, 'winerror', None) is None: - errors.append((src, dst, str(why))) + if copy_stat: + try: + copystat(src, dst) + except OSError as why: + # Copying file access times may fail on Windows + if getattr(why, 'winerror', None) is None: + errors.append((src, dst, str(why))) if errors: raise Error(errors) return dst def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, - ignore_dangling_symlinks=False, dirs_exist_ok=False): + copy_stat=True, ignore_dangling_symlinks=False, + dirs_exist_ok=False): """Recursively copy a directory tree and return the destination directory. dirs_exist_ok dictates whether to raise an exception in case dst or any @@ -551,12 +555,17 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, destination path as arguments. By default, copy2() is used, but any function that supports the same signature (like copy()) can be used. + The optional copy_stat argument controls whether permissions will be + copied from the source to the destination. This can be used along with + copy_function=copyfile when you wish to not use the source permissions + at all. """ sys.audit("shutil.copytree", src, dst) with os.scandir(src) as itr: entries = list(itr) return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks, ignore=ignore, copy_function=copy_function, + copy_stat=copy_stat, ignore_dangling_symlinks=ignore_dangling_symlinks, dirs_exist_ok=dirs_exist_ok) diff --git a/Misc/NEWS.d/next/Library/2021-10-23-07-16-33.bpo-45558.jGmQIV.rst b/Misc/NEWS.d/next/Library/2021-10-23-07-16-33.bpo-45558.jGmQIV.rst new file mode 100644 index 00000000000000..14ed3138660cb7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-10-23-07-16-33.bpo-45558.jGmQIV.rst @@ -0,0 +1 @@ +:func:`shutil.copytree`: Add a ``copy_stat`` boolean option that controls whether permissions will be copied as well from the source directory.