mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
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:
parent
deec7566ae
commit
0a08d7a095
5 changed files with 64 additions and 3 deletions
|
@ -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):
|
||||||
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):
|
||||||
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):
|
||||||
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
|
||||||
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
|
||||||
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.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue