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
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)
......
......@@ -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)
......
......@@ -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):
......
......@@ -707,6 +707,7 @@ Max Neunhöffer
George Neville-Neil
Johannes Nicolai
Samuel Nicolary
Jonathan Niehof
Gustavo Niemeyer
Oscar Nierstrasz
Hrvoje Niksic
......
......@@ -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.
......
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