Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-libarchive-c for 
openSUSE:Factory checked in at 2026-03-29 20:01:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-libarchive-c (Old)
 and      /work/SRC/openSUSE:Factory/.python-libarchive-c.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-libarchive-c"

Sun Mar 29 20:01:01 2026 rev:11 rq:1343421 version:5.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-libarchive-c/python-libarchive-c.changes  
2025-05-07 19:19:13.990076980 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-libarchive-c.new.8177/python-libarchive-c.changes
        2026-03-29 20:01:30.419445944 +0200
@@ -1,0 +2,9 @@
+Sun Mar 29 12:02:23 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.3:
+  * The `ArchiveWrite.add_files()` method has a new
+    `symlink_mode` argument.
+  * The `ArchiveEntry` class has a new `stored_digests` property
+    and new methods `get_stored_digest` and `set_stored_digest`.
+
+-------------------------------------------------------------------

Old:
----
  libarchive_c-5.2.tar.gz

New:
----
  libarchive_c-5.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-libarchive-c.spec ++++++
--- /var/tmp/diff_new_pack.BwiIOf/_old  2026-03-29 20:01:30.879464897 +0200
+++ /var/tmp/diff_new_pack.BwiIOf/_new  2026-03-29 20:01:30.879464897 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-libarchive-c
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,8 +18,9 @@
 
 %define requires_file() %( readlink -f '%*' | LC_ALL=C xargs -r rpm -q --qf 
'Requires: %%{name} >= %%{epoch}:%%{version}\\n' -f | sed -e 's/ (none):/ /' -e 
's/ 0:/ /' | grep -v "is not")
 
+%global skip_python311 1
 Name:           python-libarchive-c
-Version:        5.2
+Version:        5.3
 Release:        0
 Summary:        Python interface to libarchive
 License:        CC0-1.0

++++++ libarchive_c-5.2.tar.gz -> libarchive_c-5.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/.github/workflows/main.yml 
new/libarchive_c-5.3/.github/workflows/main.yml
--- old/libarchive_c-5.2/.github/workflows/main.yml     2023-06-29 
10:33:58.000000000 +0200
+++ new/libarchive_c-5.3/.github/workflows/main.yml     2025-04-13 
11:31:03.000000000 +0200
@@ -14,22 +14,14 @@
       - uses: actions/checkout@v2
       - name: Install libarchive
         run: sudo apt-get install -y libarchive13
-      - name: Install Python 3.11
+      - name: Install Python 3.13
         uses: actions/setup-python@v2
         with:
-          python-version: '3.11'
-      - name: Install Python 3.10
+          python-version: '3.13'
+      - name: Install Python 3.12
         uses: actions/setup-python@v2
         with:
-          python-version: '3.10'
-      - name: Install Python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: '3.9'
-      - name: Install Python 3.8
-        uses: actions/setup-python@v2
-        with:
-          python-version: '3.8'
+          python-version: '3.12'
       - name: Install tox
         run: pip install tox
       - name: Run the tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/PKG-INFO 
new/libarchive_c-5.3/PKG-INFO
--- old/libarchive_c-5.2/PKG-INFO       2025-03-14 11:33:20.043938000 +0100
+++ new/libarchive_c-5.3/PKG-INFO       2025-05-22 10:04:38.631754000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
 Name: libarchive-c
-Version: 5.2
+Version: 5.3
 Summary: Python interface to libarchive
 Home-page: https://github.com/Changaco/python-libarchive-c
 Author: Changaco
@@ -16,6 +16,7 @@
 Dynamic: home-page
 Dynamic: keywords
 Dynamic: license
+Dynamic: license-file
 Dynamic: summary
 
 A Python interface to libarchive. It uses the standard ctypes_ module to
@@ -34,7 +35,7 @@
 python
 ------
 
-python-libarchive-c is currently tested with python 3.8, 3.9, 3.10 and 3.11.
+python-libarchive-c is currently tested with python 3.12 and 3.13.
 
 If you find an incompatibility with older versions you can send us a small 
patch,
 but we won't accept big changes.
@@ -134,6 +135,14 @@
 libarchive). The acceptable values are listed in 
``libarchive.ffi.WRITE_FORMATS``
 and ``libarchive.ffi.WRITE_FILTERS``.
 
+Symbolic links
+~~~~~~~~~~~~~~
+
+By default, libarchive preserves symbolic links. If you want it to resolve the
+links and archive the files they point to instead, pass 
``symlink_mode='logical'``
+when calling the ``add_files`` method. If you do that, an ``ArchiveError``
+exception will be raised when a symbolic link points to a nonexistent file.
+
 File metadata codecs
 --------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/README.rst 
new/libarchive_c-5.3/README.rst
--- old/libarchive_c-5.2/README.rst     2024-09-27 10:03:31.000000000 +0200
+++ new/libarchive_c-5.3/README.rst     2025-05-19 10:36:29.000000000 +0200
@@ -14,7 +14,7 @@
 python
 ------
 
-python-libarchive-c is currently tested with python 3.8, 3.9, 3.10 and 3.11.
+python-libarchive-c is currently tested with python 3.12 and 3.13.
 
 If you find an incompatibility with older versions you can send us a small 
patch,
 but we won't accept big changes.
@@ -114,6 +114,14 @@
 libarchive). The acceptable values are listed in 
``libarchive.ffi.WRITE_FORMATS``
 and ``libarchive.ffi.WRITE_FILTERS``.
 
+Symbolic links
+~~~~~~~~~~~~~~
+
+By default, libarchive preserves symbolic links. If you want it to resolve the
+links and archive the files they point to instead, pass 
``symlink_mode='logical'``
+when calling the ``add_files`` method. If you do that, an ``ArchiveError``
+exception will be raised when a symbolic link points to a nonexistent file.
+
 File metadata codecs
 --------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/libarchive/entry.py 
new/libarchive_c-5.3/libarchive/entry.py
--- old/libarchive_c-5.2/libarchive/entry.py    2024-09-27 10:03:31.000000000 
+0200
+++ new/libarchive_c-5.3/libarchive/entry.py    2025-05-22 09:33:39.000000000 
+0200
@@ -1,9 +1,10 @@
 from contextlib import contextmanager
-from ctypes import create_string_buffer
+from ctypes import create_string_buffer, string_at
 from enum import IntEnum
 import math
 
 from . import ffi
+from .exception import ArchiveError
 
 
 class FileType(IntEnum):
@@ -86,6 +87,7 @@
             rdev (int | Tuple[int, int]): device number, if the file is a 
device
             rdevmajor (int): major part of the device number
             rdevminor (int): minor part of the device number
+            stored_digests (dict[str, bytes]): hashes of the file's contents
         """
         if header_codec:
             self.header_codec = header_codec
@@ -433,6 +435,64 @@
     def rdevminor(self, value):
         ffi.entry_set_rdevminor(self._entry_p, value)
 
+    @property
+    def stored_digests(self):
+        """The file's hashes stored in the archive.
+
+        libarchive only supports reading and writing digests from and to 
'mtree'
+        files. Setting the digests requires at least version 3.8.0 of 
libarchive
+        (released in May 2025). It also requires including the names of the
+        digest algorithms in the string of options passed to the archive writer
+        (e.g. `file_writer(archive_path, 'mtree', 
options='md5,rmd160,sha256')`).
+        """
+        return {name: self.get_stored_digest(name) for name in 
ffi.DIGEST_ALGORITHMS}
+
+    @stored_digests.setter
+    def stored_digests(self, values):
+        for name, value in values.items():
+            self.set_stored_digest(name, value)
+
+    def get_stored_digest(self, algorithm_name):
+        algorithm = ffi.DIGEST_ALGORITHMS[algorithm_name]
+        try:
+            ptr = ffi.entry_digest(self._entry_p, algorithm.libarchive_id)
+        except AttributeError:
+            raise NotImplementedError(
+                f"the libarchive being used (version {ffi.version_number()}, 
path "
+                f"{ffi.libarchive_path}) doesn't support reading entry digests"
+            ) from None
+        except ArchiveError:
+            raise NotImplementedError(
+                f"the libarchive being used (version {ffi.version_number()}, 
path "
+                f"{ffi.libarchive_path}) doesn't support {algorithm_name} 
digests"
+            ) from None
+        return string_at(ptr, algorithm.bytes_length)
+
+    def set_stored_digest(self, algorithm_name, value):
+        algorithm = ffi.DIGEST_ALGORITHMS[algorithm_name]
+        expected_length = algorithm.bytes_length
+        if len(value) != expected_length:
+            raise ValueError(
+                f"invalid input digest: expected {expected_length} bytes, "
+                f"got {len(value)}"
+            )
+        try:
+            retcode = ffi.entry_set_digest(
+                self._entry_p,
+                algorithm.libarchive_id,
+                (expected_length * ffi.c_ubyte).from_buffer_copy(value)
+            )
+        except AttributeError:
+            raise NotImplementedError(
+                f"the libarchive being used (version {ffi.version_number()}, 
path "
+                f"{ffi.libarchive_path}) doesn't support writing entry digests"
+            ) from None
+        if retcode < 0:
+            raise NotImplementedError(
+                f"the libarchive being used (version {ffi.version_number()}, 
path "
+                f"{ffi.libarchive_path}) doesn't support {algorithm_name} 
digests"
+            ) from None
+
 
 class ConsumedArchiveEntry(ArchiveEntry):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/libarchive/ffi.py 
new/libarchive_c-5.3/libarchive/ffi.py
--- old/libarchive_c-5.2/libarchive/ffi.py      2024-09-27 10:03:31.000000000 
+0200
+++ new/libarchive_c-5.3/libarchive/ffi.py      2025-05-22 09:33:39.000000000 
+0200
@@ -1,6 +1,6 @@
 from ctypes import (
     c_char_p, c_int, c_uint, c_long, c_longlong, c_size_t, c_int64,
-    c_void_p, c_wchar_p, CFUNCTYPE, POINTER,
+    c_ubyte, c_void_p, c_wchar_p, CFUNCTYPE, POINTER,
 )
 
 try:
@@ -286,6 +286,9 @@
 ffi('read_disk_open', [c_archive_p, c_char_p], c_int, check_int)
 ffi('read_disk_open_w', [c_archive_p, c_wchar_p], c_int, check_int)
 ffi('read_disk_descend', [c_archive_p], c_int, check_int)
+ffi('read_disk_set_symlink_hybrid', [c_archive_p], c_int, check_int)
+ffi('read_disk_set_symlink_logical', [c_archive_p], c_int, check_int)
+ffi('read_disk_set_symlink_physical', [c_archive_p], c_int, check_int)
 
 # archive_read_data
 
@@ -362,3 +365,42 @@
         f"the libarchive being used (version {version_number()}, "
         f"path {libarchive_path}) doesn't support encryption"
     )
+
+
+# archive entry digests (a.k.a. hashes)
+
+class DigestAlgorithm:
+    __slots__ = ('name', 'libarchive_id', 'bytes_length')
+
+    def __init__(self, name, libarchive_id, bytes_length):
+        self.name = name
+        self.libarchive_id = libarchive_id
+        self.bytes_length = bytes_length
+
+
+DIGEST_ALGORITHMS = {
+    'md5': DigestAlgorithm('md5', libarchive_id=1, bytes_length=16),
+    'rmd160': DigestAlgorithm('rmd160', libarchive_id=2, bytes_length=20),
+    'sha1': DigestAlgorithm('sha1', libarchive_id=3, bytes_length=20),
+    'sha256': DigestAlgorithm('sha256', libarchive_id=4, bytes_length=32),
+    'sha384': DigestAlgorithm('sha384', libarchive_id=5, bytes_length=48),
+    'sha512': DigestAlgorithm('sha512', libarchive_id=6, bytes_length=64),
+}
+
+try:
+    ffi('entry_digest', [c_archive_entry_p, c_int], POINTER(c_ubyte), 
check_null)
+except AttributeError:
+    logger.info(
+        f"the libarchive being used (version {version_number()}, "
+        f"path {libarchive_path}) doesn't support reading entry digests"
+    )
+
+try:
+    ffi('entry_set_digest',
+        [c_archive_entry_p, c_int, POINTER(c_ubyte)],
+        c_int, check_int)
+except AttributeError:
+    logger.info(
+        f"the libarchive being used (version {version_number()}, "
+        f"path {libarchive_path}) doesn't support modifying entry digests"
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/libarchive/write.py 
new/libarchive_c-5.3/libarchive/write.py
--- old/libarchive_c-5.2/libarchive/write.py    2025-03-14 11:26:27.000000000 
+0100
+++ new/libarchive_c-5.3/libarchive/write.py    2025-05-21 23:39:21.000000000 
+0200
@@ -46,7 +46,7 @@
 
     def add_files(
         self, *paths, flags=0, lookup=False, pathname=None, recursive=True,
-        **attributes
+        symlink_mode=None, **attributes
     ):
         """Read files through the OS and add them to the archive.
 
@@ -63,6 +63,9 @@
             recursive (bool):
                 when False, if a path in `paths` is a directory,
                 only the directory itself is added.
+            symlink_mode (Literal['hybrid', 'logical', 'physical'] | None):
+                how symbolic links should be handled; see `man 
archive_read_disk`
+                for meanings
             attributes (dict): passed to `ArchiveEntry.modify()`
 
         Raises:
@@ -75,10 +78,23 @@
         if block_size <= 0:
             block_size = 10240  # pragma: no cover
 
+        set_symlink_mode = None
+        if symlink_mode:
+            try:
+                set_symlink_mode = getattr(
+                    ffi, f'read_disk_set_symlink_{symlink_mode}'
+                )
+            except AttributeError:
+                raise ValueError(
+                    f"symlink_mode value {symlink_mode!r} is invalid"
+                ) from None
+
         entry = ArchiveEntry(header_codec=self.header_codec)
         entry_p = entry._entry_p
         for path in paths:
             with new_archive_read_disk(path, flags, lookup) as read_p:
+                if set_symlink_mode:
+                    set_symlink_mode(read_p)
                 while 1:
                     r = read_next_header2(read_p, entry_p)
                     if r == ARCHIVE_EOF:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/libarchive_c.egg-info/PKG-INFO 
new/libarchive_c-5.3/libarchive_c.egg-info/PKG-INFO
--- old/libarchive_c-5.2/libarchive_c.egg-info/PKG-INFO 2025-03-14 
11:33:20.000000000 +0100
+++ new/libarchive_c-5.3/libarchive_c.egg-info/PKG-INFO 2025-05-22 
10:04:38.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
 Name: libarchive-c
-Version: 5.2
+Version: 5.3
 Summary: Python interface to libarchive
 Home-page: https://github.com/Changaco/python-libarchive-c
 Author: Changaco
@@ -16,6 +16,7 @@
 Dynamic: home-page
 Dynamic: keywords
 Dynamic: license
+Dynamic: license-file
 Dynamic: summary
 
 A Python interface to libarchive. It uses the standard ctypes_ module to
@@ -34,7 +35,7 @@
 python
 ------
 
-python-libarchive-c is currently tested with python 3.8, 3.9, 3.10 and 3.11.
+python-libarchive-c is currently tested with python 3.12 and 3.13.
 
 If you find an incompatibility with older versions you can send us a small 
patch,
 but we won't accept big changes.
@@ -134,6 +135,14 @@
 libarchive). The acceptable values are listed in 
``libarchive.ffi.WRITE_FORMATS``
 and ``libarchive.ffi.WRITE_FILTERS``.
 
+Symbolic links
+~~~~~~~~~~~~~~
+
+By default, libarchive preserves symbolic links. If you want it to resolve the
+links and archive the files they point to instead, pass 
``symlink_mode='logical'``
+when calling the ``add_files`` method. If you do that, an ``ArchiveError``
+exception will be raised when a symbolic link points to a nonexistent file.
+
 File metadata codecs
 --------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/tests/test_entry.py 
new/libarchive_c-5.3/tests/test_entry.py
--- old/libarchive_c-5.2/tests/test_entry.py    2024-09-29 13:43:47.000000000 
+0200
+++ new/libarchive_c-5.3/tests/test_entry.py    2025-05-22 09:33:39.000000000 
+0200
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-
-from codecs import open
 import json
 import locale
 from os import environ, stat
@@ -9,14 +6,12 @@
 
 import pytest
 
-from libarchive import ArchiveError, memory_reader, memory_writer
+from libarchive import ArchiveError, ffi, file_writer, memory_reader, 
memory_writer
 from libarchive.entry import ArchiveEntry, ConsumedArchiveEntry, 
PassedArchiveEntry
 
 from . import data_dir, get_entries, get_tarinfos
 
 
-text_type = unicode if str is bytes else str  # noqa: F821
-
 locale.setlocale(locale.LC_ALL, '')
 
 # needed for sane time stamp comparison
@@ -106,7 +101,7 @@
         # Normalize all unicode (can vary depending on the system)
         for d in (e1, e2):
             for key in d:
-                if isinstance(d[key], text_type):
+                if isinstance(d[key], str):
                     d[key] = unicodedata.normalize('NFC', d[key])
         assert e1 == e2
 
@@ -155,3 +150,40 @@
     with memory_reader(buf, header_codec='cp037') as archive:
         entry = next(iter(archive))
         assert entry.pathname == file_name
+
+
+fake_hashes = dict(
+    md5=b'!' * 16,
+    rmd160=b'!' * 20,
+    sha1=b'!' * 20,
+    sha256=b'!' * 32,
+    sha384=b'!' * 48,
+    sha512=b'!' * 64,
+)
+mtree = (
+    '#mtree\n'
+    './empty.txt nlink=0 time=0.0 mode=664 gid=0 uid=0 type=file size=42 '
+    f'md5digest={'21'*16} rmd160digest={'21'*20} sha1digest={'21'*20} '
+    f'sha256digest={'21'*32} sha384digest={'21'*48} sha512digest={'21'*64}\n'
+)
+
+
+def test_reading_entry_digests(tmpdir):
+    with memory_reader(mtree.encode('ascii')) as archive:
+        entry = next(iter(archive))
+        assert entry.stored_digests == fake_hashes
+
+
[email protected](
+    condition=ffi.version_number() < 3008000,
+    reason="libarchive < 3.8",
+)
+def test_writing_entry_digests(tmpdir):
+    archive_path = str(tmpdir / 'mtree')
+    options = ','.join(fake_hashes.keys())
+    with file_writer(archive_path, 'mtree', options=options) as archive:
+        # Add an empty file, with fake hashes.
+        archive.add_file_from_memory('empty.txt', 42, (), 
stored_digests=fake_hashes)
+    with open(archive_path) as f:
+        libarchive_mtree = f.read()
+        assert libarchive_mtree == mtree
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/tests/test_rwx.py 
new/libarchive_c-5.3/tests/test_rwx.py
--- old/libarchive_c-5.2/tests/test_rwx.py      2024-03-05 14:13:34.000000000 
+0100
+++ new/libarchive_c-5.3/tests/test_rwx.py      2025-05-19 10:36:30.000000000 
+0200
@@ -2,6 +2,7 @@
 
 import io
 import json
+import os
 
 import libarchive
 from libarchive.entry import format_time
@@ -181,3 +182,32 @@
                 )
             assert archive_entry.uid == 1000
             assert archive_entry.gid == 1000
+
+
+def test_symlinks(tmpdir):
+    os.chdir(tmpdir)
+    with open('empty', 'w'):
+        pass
+    with open('unreadable', 'w') as f:
+        f.write('secret')
+    os.chmod('unreadable', 0)
+
+    os.symlink('empty', 'symlink-to-empty')
+    os.symlink('unreadable', 'symlink-to-unreadable')
+
+    with libarchive.file_writer('archive.tar', 'gnutar') as archive:
+        archive.add_files('symlink-to-empty', symlink_mode='hybrid')
+        with pytest.raises(libarchive.ArchiveError):
+            archive.add_files('symlink-to-unreadable', symlink_mode='logical')
+        archive.add_files('symlink-to-unreadable', symlink_mode='physical')
+
+    with libarchive.file_reader('archive.tar') as archive:
+        entries = iter(archive)
+        e1 = next(entries)
+        assert e1.pathname == 'symlink-to-empty'
+        assert e1.isreg
+        assert e1.size == 0
+        e2 = next(entries)
+        assert e2.pathname == 'symlink-to-unreadable'
+        assert e2.issym
+        assert e2.linkpath == 'unreadable'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libarchive_c-5.2/tox.ini new/libarchive_c-5.3/tox.ini
--- old/libarchive_c-5.2/tox.ini        2024-09-27 10:03:31.000000000 +0200
+++ new/libarchive_c-5.3/tox.ini        2025-04-13 11:31:03.000000000 +0200
@@ -1,5 +1,5 @@
 [tox]
-envlist=py38,py39,py310,py311
+envlist=py312,py313
 skipsdist=True
 
 [testenv]

Reply via email to