Skip to content

Commit 4cfbcff

Browse files
serhiy-storchaka6t8k
andauthoredAug 16, 2023
[3.11] gh-99203: shutil.make_archive(): restore select CPython <= 3.10.5 behavior (GH-99802) (GH-107999)
Restore following CPython <= 3.10.5 behavior of shutil.make_archive() that went away as part of gh-93160: Do not create an empty archive if root_dir is not a directory, and, in that case, raise FileNotFoundError or NotADirectoryError regardless of format choice. Beyond the brought-back behavior, the function may now also raise these exceptions in dry_run mode. (cherry picked from commit a86df29) Co-authored-by: 6t8k <58048945+6t8k@users.noreply.github.com>
1 parent 3f7dfb6 commit 4cfbcff

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed
 

‎Lib/shutil.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1114,10 +1114,14 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
11141114
if base_dir is None:
11151115
base_dir = os.curdir
11161116

1117-
support_root_dir = format_info[3]
1117+
supports_root_dir = format_info[3]
11181118
save_cwd = None
11191119
if root_dir is not None:
1120-
if support_root_dir:
1120+
stmd = os.stat(root_dir).st_mode
1121+
if not stat.S_ISDIR(stmd):
1122+
raise NotADirectoryError(errno.ENOTDIR, 'Not a directory', root_dir)
1123+
1124+
if supports_root_dir:
11211125
# Support path-like base_name here for backwards-compatibility.
11221126
base_name = os.fspath(base_name)
11231127
kwargs['root_dir'] = root_dir

‎Lib/test/test_shutil.py

+43
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,49 @@ def test_register_archive_format(self):
16291629
formats = [name for name, params in get_archive_formats()]
16301630
self.assertNotIn('xxx', formats)
16311631

1632+
def test_make_tarfile_rootdir_nodir(self):
1633+
# GH-99203
1634+
self.addCleanup(os_helper.unlink, f'{TESTFN}.tar')
1635+
for dry_run in (False, True):
1636+
with self.subTest(dry_run=dry_run):
1637+
tmp_dir = self.mkdtemp()
1638+
nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
1639+
with self.assertRaises(FileNotFoundError) as cm:
1640+
make_archive(TESTFN, 'tar', nonexisting_file, dry_run=dry_run)
1641+
self.assertEqual(cm.exception.errno, errno.ENOENT)
1642+
self.assertEqual(cm.exception.filename, nonexisting_file)
1643+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1644+
1645+
tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
1646+
os.close(tmp_fd)
1647+
with self.assertRaises(NotADirectoryError) as cm:
1648+
make_archive(TESTFN, 'tar', tmp_file, dry_run=dry_run)
1649+
self.assertEqual(cm.exception.errno, errno.ENOTDIR)
1650+
self.assertEqual(cm.exception.filename, tmp_file)
1651+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1652+
1653+
@support.requires_zlib()
1654+
def test_make_zipfile_rootdir_nodir(self):
1655+
# GH-99203
1656+
self.addCleanup(os_helper.unlink, f'{TESTFN}.zip')
1657+
for dry_run in (False, True):
1658+
with self.subTest(dry_run=dry_run):
1659+
tmp_dir = self.mkdtemp()
1660+
nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
1661+
with self.assertRaises(FileNotFoundError) as cm:
1662+
make_archive(TESTFN, 'zip', nonexisting_file, dry_run=dry_run)
1663+
self.assertEqual(cm.exception.errno, errno.ENOENT)
1664+
self.assertEqual(cm.exception.filename, nonexisting_file)
1665+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1666+
1667+
tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
1668+
os.close(tmp_fd)
1669+
with self.assertRaises(NotADirectoryError) as cm:
1670+
make_archive(TESTFN, 'zip', tmp_file, dry_run=dry_run)
1671+
self.assertEqual(cm.exception.errno, errno.ENOTDIR)
1672+
self.assertEqual(cm.exception.filename, tmp_file)
1673+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1674+
16321675
### shutil.unpack_archive
16331676

16341677
def check_unpack_archive(self, format, **kwargs):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Restore following CPython <= 3.10.5 behavior of :func:`shutil.make_archive`:
2+
do not create an empty archive if ``root_dir`` is not a directory, and, in that
3+
case, raise :class:`FileNotFoundError` or :class:`NotADirectoryError`
4+
regardless of ``format`` choice. Beyond the brought-back behavior, the function
5+
may now also raise these exceptions in ``dry_run`` mode.

0 commit comments

Comments
 (0)
Please sign in to comment.