Skip to content

Commit

Permalink
bpo-26791: Update shutil.move() to provide the same symlink move beha…
Browse files Browse the repository at this point in the history
…vior as the mv shell when moving a symlink into a directory that is the target of the symlink (pythonGH-21759)
  • Loading branch information
websurfer5 committed Dec 27, 2023
1 parent 1b19d73 commit c66b577
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,7 @@ def move(src, dst, copy_function=copy2):
sys.audit("shutil.move", src, dst)
real_dst = dst
if os.path.isdir(dst):
if _samefile(src, dst):
if _samefile(src, dst) and not os.path.islink(src):
# We might be on a case insensitive filesystem,
# perform the rename anyway.
os.rename(src, dst)
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,35 @@ def test_move_dir_caseinsensitive(self):
finally:
os.rmdir(dst_dir)

# bpo-26791: Check that a symlink to a directory can
# be moved into that directory.
@mock_rename
def _test_move_symlink_to_dir_into_dir(self, dst):
src = os.path.join(self.src_dir, 'linktodir')
dst_link = os.path.join(self.dst_dir, 'linktodir')
os.symlink(self.dst_dir, src, target_is_directory=True)
shutil.move(src, dst)
self.assertTrue(os.path.islink(dst_link))
self.assertTrue(os.path.samefile(self.dst_dir, dst_link))
self.assertFalse(os.path.exists(src))

# Repeat the move operation with the destination
# symlink already in place (should raise shutil.Error).
os.symlink(self.dst_dir, src, target_is_directory=True)
with self.assertRaises(shutil.Error):
shutil.move(src, dst)
self.assertTrue(os.path.samefile(self.dst_dir, dst_link))
self.assertTrue(os.path.exists(src))

@os_helper.skip_unless_symlink
def test_move_symlink_to_dir_into_dir(self):
self._test_move_symlink_to_dir_into_dir(self.dst_dir)

@os_helper.skip_unless_symlink
def test_move_symlink_to_dir_into_symlink_to_dir(self):
dst = os.path.join(self.src_dir, 'otherlinktodir')
os.symlink(self.dst_dir, dst, target_is_directory=True)
self._test_move_symlink_to_dir_into_dir(dst)

@os_helper.skip_unless_dac_override
@unittest.skipUnless(hasattr(os, 'lchflags')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:func:`shutil.move` now moves a symlink into a directory when that
directory is the target of the symlink. This provides the same behavior as
the mv shell command. The previous behavior raised an exception. Patch by
Jeffrey Kintscher.

0 comments on commit c66b577

Please sign in to comment.