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 Mon May 21 23:00:26 2012 -0500 @@ -978,8 +978,8 @@ 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 - copied to *dst*, and directories under *src* are recursively copied to *dst*. + :func:`mkpath`. 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 @@ -992,6 +992,10 @@ destination of the symlink will be copied. *update* and *verbose* are the same as for :func:`copy_file`. + 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 `_. + .. function:: remove_tree(directory[, verbose=0, dry_run=0]) 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 Mon May 21 23:00:26 2012 -0500 @@ -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 Mon May 21 23:00:26 2012 -0500 @@ -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') diff -r c00ac2b25048 Lib/distutils/tests/test_sdist.py --- a/Lib/distutils/tests/test_sdist.py Tue Mar 13 11:31:36 2012 -0700 +++ b/Lib/distutils/tests/test_sdist.py Mon May 21 23:00:26 2012 -0500 @@ -83,9 +83,9 @@ @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') def test_prune_file_list(self): - # this test creates a package with some vcs dirs in it - # and launch sdist to make sure they get pruned - # on all systems + # this test creates a package with some vcs dirs, and a NFS 'silly + # rename' file in it. It then launches sdist to make sure they get + # pruned on all systems # creating VCS directories with some files in them os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) @@ -99,6 +99,9 @@ self.write_file((self.tmp_dir, 'somecode', '.git', 'ok'), 'xxx') + # creating an NFS 'silly rename' file. + self.write_file((self.tmp_dir, 'somecode', '.nfs0001'), 'xxx') + # now building a sdist dist, cmd = self.get_cmd()