Kaydet (Commit) 0a08d7a0 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

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.
üst deec7566
...@@ -196,7 +196,12 @@ Directory and files operations ...@@ -196,7 +196,12 @@ Directory and files operations
If the destination is on the current filesystem, then :func:`os.rename` is 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 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) .. function:: disk_usage(path)
......
...@@ -356,7 +356,10 @@ def move(src, dst): ...@@ -356,7 +356,10 @@ def move(src, dst):
overwritten depending on os.rename() semantics. overwritten depending on os.rename() semantics.
If the destination is on our current filesystem, then rename() is used. 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 A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over. the issues this implementation glosses over.
...@@ -375,7 +378,11 @@ def move(src, dst): ...@@ -375,7 +378,11 @@ def move(src, dst):
try: try:
os.rename(src, real_dst) os.rename(src, real_dst)
except OSError: 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): if _destinsrc(src, dst):
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst)) raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
copytree(src, real_dst, symlinks=True) copytree(src, real_dst, symlinks=True)
......
...@@ -1104,6 +1104,49 @@ class TestMove(unittest.TestCase): ...@@ -1104,6 +1104,49 @@ class TestMove(unittest.TestCase):
finally: finally:
shutil.rmtree(TESTFN, ignore_errors=True) 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): class TestCopyFile(unittest.TestCase):
......
...@@ -707,6 +707,7 @@ Max Neunhöffer ...@@ -707,6 +707,7 @@ Max Neunhöffer
George Neville-Neil George Neville-Neil
Johannes Nicolai Johannes Nicolai
Samuel Nicolary Samuel Nicolary
Jonathan Niehof
Gustavo Niemeyer Gustavo Niemeyer
Oscar Nierstrasz Oscar Nierstrasz
Hrvoje Niksic Hrvoje Niksic
......
...@@ -422,6 +422,11 @@ Core and Builtins ...@@ -422,6 +422,11 @@ Core and Builtins
Library 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 #12926: Fix a bug in tarfile's link extraction.
- Issue #13696: Fix the 302 Relative URL Redirection problem. - Issue #13696: Fix the 302 Relative URL Redirection problem.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment