Issue #9993: When the source and destination are on different filesystems,

and the source is a symlink, shutil.move() now recreates a symlink on the
destination instead of copying the file contents.
Patch by Jonathan Niehof and Hynek Schlawack.
This commit is contained in:
Antoine Pitrou 2012-01-06 20:16:19 +01:00
parent deec7566ae
commit 0a08d7a095
5 changed files with 64 additions and 3 deletions

View File

@ -196,7 +196,12 @@ Directory and files operations
If the destination is on the current filesystem, then :func:`os.rename` is
used. Otherwise, *src* is copied (using :func:`copy2`) to *dst* and then
removed.
removed. In case of symlinks, a new symlink pointing to the target of *src*
will be created in or as *dst* and *src* will be removed.
.. versionchanged:: 3.3
Added explicit symlink handling for foreign filesystems, thus adapting
it to the behavior of GNU's :program:`mv`.
.. function:: disk_usage(path)

View File

@ -356,7 +356,10 @@ def move(src, dst):
overwritten depending on os.rename() semantics.
If the destination is on our current filesystem, then rename() is used.
Otherwise, src is copied to the destination and then removed.
Otherwise, src is copied to the destination and then removed. Symlinks are
recreated under the new name if os.rename() fails because of cross
filesystem renames.
A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over.
@ -375,7 +378,11 @@ def move(src, dst):
try:
os.rename(src, real_dst)
except OSError:
if os.path.isdir(src):
if os.path.islink(src):
linkto = os.readlink(src)
os.symlink(linkto, real_dst)
os.unlink(src)
elif os.path.isdir(src):
if _destinsrc(src, dst):
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
copytree(src, real_dst, symlinks=True)

View File

@ -1104,6 +1104,49 @@ class TestMove(unittest.TestCase):
finally:
shutil.rmtree(TESTFN, ignore_errors=True)
@support.skip_unless_symlink
@mock_rename
def test_move_file_symlink(self):
dst = os.path.join(self.src_dir, 'bar')
os.symlink(self.src_file, dst)
shutil.move(dst, self.dst_file)
self.assertTrue(os.path.islink(self.dst_file))
self.assertTrue(os.path.samefile(self.src_file, self.dst_file))
@support.skip_unless_symlink
@mock_rename
def test_move_file_symlink_to_dir(self):
filename = "bar"
dst = os.path.join(self.src_dir, filename)
os.symlink(self.src_file, dst)
shutil.move(dst, self.dst_dir)
final_link = os.path.join(self.dst_dir, filename)
self.assertTrue(os.path.islink(final_link))
self.assertTrue(os.path.samefile(self.src_file, final_link))
@support.skip_unless_symlink
@mock_rename
def test_move_dangling_symlink(self):
src = os.path.join(self.src_dir, 'baz')
dst = os.path.join(self.src_dir, 'bar')
os.symlink(src, dst)
dst_link = os.path.join(self.dst_dir, 'quux')
shutil.move(dst, dst_link)
self.assertTrue(os.path.islink(dst_link))
self.assertEqual(os.path.realpath(src), os.path.realpath(dst_link))
@support.skip_unless_symlink
@mock_rename
def test_move_dir_symlink(self):
src = os.path.join(self.src_dir, 'baz')
dst = os.path.join(self.src_dir, 'bar')
os.mkdir(src)
os.symlink(src, dst)
dst_link = os.path.join(self.dst_dir, 'quux')
shutil.move(dst, dst_link)
self.assertTrue(os.path.islink(dst_link))
self.assertTrue(os.path.samefile(src, dst_link))
class TestCopyFile(unittest.TestCase):

View File

@ -707,6 +707,7 @@ Max Neunhöffer
George Neville-Neil
Johannes Nicolai
Samuel Nicolary
Jonathan Niehof
Gustavo Niemeyer
Oscar Nierstrasz
Hrvoje Niksic

View File

@ -422,6 +422,11 @@ Core and Builtins
Library
-------
- Issue #9993: When the source and destination are on different filesystems,
and the source is a symlink, shutil.move() now recreates a symlink on the
destination instead of copying the file contents. Patch by Jonathan Niehof
and Hynek Schlawack.
- Issue #12926: Fix a bug in tarfile's link extraction.
- Issue #13696: Fix the 302 Relative URL Redirection problem.