diff -r c00ac2b25048 Doc/distutils/apiref.rst --- a/Doc/distutils/apiref.rst Tue Mar 13 11:31:36 2012 -0700 +++ b/Doc/distutils/apiref.rst Tue Mar 13 14:32:03 2012 -0700 @@ -978,13 +978,17 @@ Copy an entire directory tree *src* to a new location *dst*. Both *src* and *dst* must be directory names. If *src* is not a directory, raise :exc:`DistutilsFileError`. If *dst* does not exist, it is created with - :func:`mkpath`. The end result of the copy is that every file in *src* is + :func:`mkpath`. Files in *src* that begin with '.nfs' are skipped. More + information on these NFS 'silly rename' files are available in answer 'D2' + on the `NFS FAQ page `_. + The end result of the copy is that every file in *src* is copied to *dst*, and directories under *src* are recursively copied to *dst*. Return the list of files that were copied or might have been copied, using their output name. The return value is unaffected by *update* or *dry_run*: it is simply the list of all files under *src*, with the names changed to be under *dst*. + *preserve_mode* and *preserve_times* are the same as for :func:`copy_file` in :mod:`distutils.file_util`; note that they only apply to regular files, not to directories. If *preserve_symlinks* is true, symlinks will be copied as diff -r c00ac2b25048 Lib/distutils/dir_util.py --- a/Lib/distutils/dir_util.py Tue Mar 13 11:31:36 2012 -0700 +++ b/Lib/distutils/dir_util.py Tue Mar 13 14:32:03 2012 -0700 @@ -141,7 +141,9 @@ src_name = os.path.join(src, n) dst_name = os.path.join(dst, n) - if preserve_symlinks and os.path.islink(src_name): + if n.startswith('.nfs'): + pass + elif preserve_symlinks and os.path.islink(src_name): link_dest = os.readlink(src_name) if verbose >= 1: log.info("linking %s -> %s", dst_name, link_dest) diff -r c00ac2b25048 Lib/distutils/tests/test_dir_util.py --- a/Lib/distutils/tests/test_dir_util.py Tue Mar 13 11:31:36 2012 -0700 +++ b/Lib/distutils/tests/test_dir_util.py Tue Mar 13 14:32:03 2012 -0700 @@ -101,6 +101,30 @@ remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) + def test_copy_tree_skips_nfs_temp_files(self): + # this test creates an NFS 'silly rename' file + # then runs copy_tree to make sure the file is + # skipped. More info on NFS 'silly rename' files + # are available in answer 'D2' on the NFS FAQ here: + # http://nfs.sourceforge.net/#section_d + + mkpath(self.target, verbose=0) + + a_file = os.path.join(self.target, 'ok.txt') + nfs_file = os.path.join(self.target, '.nfs123abc') + for f in a_file, nfs_file: + fh = open(f, 'w') + try: + fh.write('some content') + finally: + fh.close() + + copy_tree(self.target, self.target2) + self.assertEqual(len(os.listdir(self.target2)), 1, '.nfs123abc file was *not* skipped!') + + remove_tree(self.root_target, verbose=0) + remove_tree(self.target2, verbose=0) + def test_ensure_relative(self): if os.sep == '/': self.assertEqual(ensure_relative('/home/foo'), 'home/foo')