Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions Doc/library/shutil.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ Archiving operations
High-level utilities to create and read compressed and archived files are also
provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.

.. function:: make_archive(base_name, format, [root_dir, [base_dir, [verbose, [dry_run, [owner, [group, [logger]]]]]]])
.. function:: make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, dry_run=False, owner=None, group=None, logger=None)

Create an archive file (such as zip or tar) and return its name.

Expand All @@ -601,18 +601,20 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
:mod:`zlib` module is available), "bztar" (if the :mod:`bz2` module is
available), or "xztar" (if the :mod:`lzma` module is available).

*root_dir* is a directory that will be the root directory of the
archive, all paths in the archive will be relative to it; for example,
we typically chdir into *root_dir* before creating the archive.
If specified, *root_dir* is a directory that will be the root directory of the
archive, all paths in the archive will be relative to it; it is equivalent
to chdir into *root_dir* before creating the archive.
By default the current directory is used.

*base_dir* is the directory where we start archiving from;
If specified, *base_dir* is the directory where we start archiving from;
i.e. *base_dir* will be the common prefix of all files and
directories in the archive. *base_dir* must be given relative
to *root_dir*. See :ref:`shutil-archiving-example-with-basedir` for how to
to *root_dir*.
By default all entries in the *root_dir* directory or the current directory
are added without prefix.
See :ref:`shutil-archiving-example-with-basedir` for how to
use *base_dir* and *root_dir* together.

*root_dir* and *base_dir* both default to the current directory.

If *dry_run* is true, no archive is created, but the operations that would be
executed are logged to *logger*.

Expand Down Expand Up @@ -642,6 +644,13 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
This function is now made thread-safe during creation of standard
``.zip`` and tar archives.

.. versionchanged:: 3.14
The ``'.'`` entry no longer added to tar arhives and the ``'./'`` prefix
no longer added to tar entries unless ``base_dir='.'`` is
explicitly specified.
The ``'.'`` entry is now added to zip arhives if ``base_dir='.'``
is explicitly specified.

.. function:: get_archive_formats()

Return a list of supported formats for archiving.
Expand Down
36 changes: 20 additions & 16 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,14 +1030,19 @@ def _set_uid_gid(tarinfo):
return tarinfo

if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression)
arcname = base_dir
if root_dir is not None:
base_dir = os.path.join(root_dir, base_dir)
try:
tar.add(base_dir, arcname, filter=_set_uid_gid)
finally:
tar.close()
with tarfile.open(archive_name, 'w|%s' % tar_compression) as tar:
if base_dir is not None:
arcname = base_dir
if root_dir is not None:
base_dir = os.path.join(root_dir, base_dir)
tar.add(base_dir, arcname, filter=_set_uid_gid)
elif root_dir is not None:
for name in os.listdir(root_dir):
path = os.path.join(root_dir, name)
tar.add(path, name, filter=_set_uid_gid)
else:
for name in os.listdir():
tar.add(name, name, filter=_set_uid_gid)

if root_dir is not None:
archive_name = os.path.abspath(archive_name)
Expand Down Expand Up @@ -1068,11 +1073,13 @@ def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0,
if not dry_run:
with zipfile.ZipFile(zip_filename, "w",
compression=zipfile.ZIP_DEFLATED) as zf:
arcname = os.path.normpath(base_dir)
if root_dir is not None:
base_dir = os.path.join(root_dir, base_dir)
base_dir = os.path.normpath(base_dir)
if arcname != os.curdir:
if base_dir is None:
base_dir = root_dir if root_dir is not None else os.curdir
else:
arcname = os.path.normpath(base_dir)
if root_dir is not None:
base_dir = os.path.join(root_dir, base_dir)
base_dir = os.path.normpath(base_dir)
zf.write(base_dir, arcname)
if logger is not None:
logger.info("adding '%s'", base_dir)
Expand Down Expand Up @@ -1190,9 +1197,6 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
for arg, val in format_info[1]:
kwargs[arg] = val

if base_dir is None:
base_dir = os.curdir

supports_root_dir = getattr(func, 'supports_root_dir', False)
save_cwd = None
if root_dir is not None:
Expand Down
14 changes: 7 additions & 7 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1648,9 +1648,9 @@ def test_make_tarfile(self):
self.assertTrue(tarfile.is_tarfile(archive))
with tarfile.open(archive, 'r') as tf:
self.assertCountEqual(tf.getnames(),
['.', './dist', './dist/sub', './dist/sub2',
'./dist/file1', './dist/file2', './dist/sub/file3',
'./outer'])
['dist', 'dist/sub', 'dist/sub2',
'dist/file1', 'dist/file2', 'dist/sub/file3',
'outer'])

# Test with base_dir.
with os_helper.temp_cwd(), no_chdir:
Expand Down Expand Up @@ -1690,9 +1690,9 @@ def test_make_tarfile_without_rootdir(self):
self.assertTrue(tarfile.is_tarfile(archive))
with tarfile.open(archive, 'r:gz') as tf:
self.assertCountEqual(tf.getnames(),
['.', './dist', './dist/sub', './dist/sub2',
'./dist/file1', './dist/file2', './dist/sub/file3',
'./outer'])
['dist', 'dist/sub', 'dist/sub2',
'dist/file1', 'dist/file2', 'dist/sub/file3',
'outer'])

# Test with base_dir.
with os_helper.change_cwd(root_dir), no_chdir:
Expand Down Expand Up @@ -1850,7 +1850,7 @@ def test_make_zipfile_with_explicit_curdir(self):
self.assertTrue(zipfile.is_zipfile(archive))
with zipfile.ZipFile(archive) as zf:
self.assertCountEqual(zf.namelist(),
['dist/', 'dist/sub/', 'dist/sub2/',
['./', 'dist/', 'dist/sub/', 'dist/sub2/',
'dist/file1', 'dist/file2', 'dist/sub/file3',
'outer'])

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:func:`shutil.make_archive` no longer adds the the ``'.'`` entry to tar
arhives and the ``'./'`` prefix to tar entries unless ``base_dir='.'`` is
explicitly specified. It now adds the ``'.'`` entry to zip arhives if
``base_dir='.'`` is explicitly specified.