Repository: goerz/zip_files

Issue:
Permissions are not preserved
File permissions, like the "executable" flag are not preserved in the zip file.

Discussion:

Patch:
diff --git a/src/zip_files/backend.py b/src/zip_files/backend.py
index 36c0d44..b1c4999 100644
--- a/src/zip_files/backend.py
+++ b/src/zip_files/backend.py
@@ -9,7 +9,7 @@ import random
 import re
 from pathlib import Path
 from string import ascii_letters
-from zipfile import ZipFile
+from zipfile import ZipFile, ZipInfo
 
 import click
 
@@ -257,12 +257,19 @@ def zip_files(
                 exclude,
                 exclude_dotfiles,
                 relative_to=file.parent,
+                compression=compression,
             )
     logger.debug("Done")
 
 
 def _add_to_zip(
-    zipfile, file, root_folder, exclude, exclude_dotfiles, relative_to
+    zipfile,
+    file,
+    root_folder,
+    exclude,
+    exclude_dotfiles,
+    relative_to,
+    compression,
 ):
     """Recursively add the `file` to the (open) `zipfile`."""
     logger = logging.getLogger(__name__)
@@ -293,7 +300,9 @@ def _add_to_zip(
             else:
                 raise TypeError("Invalid type for pattern %r" % pattern)
         logger.debug("Adding %s to zip as %s", file, filename)
-        zipfile.writestr(str(filename), data)
+        zinfo = ZipInfo.from_file(file, arcname=str(filename))
+        zinfo.compress_type = compression
+        zipfile.writestr(zinfo, data)
     elif file.is_dir():
         directory = file
         for file_in_dir in directory.iterdir():
@@ -304,4 +313,5 @@ def _add_to_zip(
                 exclude,
                 exclude_dotfiles,
                 relative_to,
+                compression,
             )


Test patch:
diff --git a/tests/test_zip_files.py b/tests/test_zip_files.py
index 82e9744..f204848 100644
--- a/tests/test_zip_files.py
+++ b/tests/test_zip_files.py
@@ -1,9 +1,14 @@
 """Tests for `zip-files` executable."""
 
 import io
+import os
+import stat
+import sys
+import time
 from pathlib import Path
 from zipfile import ZipFile
 
+import pytest
 from click.testing import CliRunner
 from pkg_resources import parse_version
 
@@ -273,3 +278,31 @@ def test_zip_files_default_include_dotfiles(tmp_path):
         zipfile.debug = 3
         assert zipfile.testzip() is None
         assert set(zipfile.namelist()) == set(expected_files)
+
+
+@pytest.mark.skipif(
+    sys.platform == 'win32',
+    reason="Windows does not have Unix file permissions",
+)
+def test_zip_files_preserve_executable(tmp_path):
+    """Test that an executable file permission is preserved."""
+    runner = CliRunner()
+    outfile = tmp_path / 'archive.zip'
+    executable = tmp_path / 'executable.sh'
+    with open(executable, "w") as fh:
+        fh.write("#!/usr/bin/bash\n")
+        fh.write('echo "Hello World"\n')
+    os.chmod(executable, stat.S_IXUSR | stat.S_IRUSR)
+    result = runner.invoke(
+        zip_files, ['--debug', '-o', str(outfile), str(executable)]
+    )
+    _check_exit_code(result)
+    with ZipFile(outfile) as zipfile:
+        zipfile.debug = 3
+        assert zipfile.testzip() is None
+        assert set(zipfile.namelist()) == set(["executable.sh"])
+        zip_info = zipfile.getinfo("executable.sh")
+        today = time.localtime()
+        today_ymd = (today.tm_year, today.tm_mon, today.tm_mday)
+        assert zip_info.date_time >= today_ymd
+        assert stat.filemode(zip_info.external_attr >> 16) == '-r-x------'