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]