Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-acefile for openSUSE:Factory 
checked in at 2026-01-22 17:59:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acefile (Old)
 and      /work/SRC/openSUSE:Factory/.python-acefile.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acefile"

Thu Jan 22 17:59:03 2026 rev:7 rq:1328692 version:0.6.14

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acefile/python-acefile.changes    
2025-06-17 18:21:39.420896388 +0200
+++ /work/SRC/openSUSE:Factory/.python-acefile.new.1928/python-acefile.changes  
2026-01-22 17:59:12.939157357 +0100
@@ -1,0 +2,10 @@
+Thu Jan 22 13:51:11 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.6.14:
+  * Experimental support for free-threaded Python.
+  * Fix memory leak in acebitstream module.
+  * Add pytest smoke tests and configure GitHub Actions CI for
+    testing.
+  * Remove deprecated setup.py features.
+
+-------------------------------------------------------------------

Old:
----
  acefile-0.6.13.tar.gz

New:
----
  acefile-0.6.14.tar.gz

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

Other differences:
------------------
++++++ python-acefile.spec ++++++
--- /var/tmp/diff_new_pack.g1Ow0g/_old  2026-01-22 17:59:13.555183055 +0100
+++ /var/tmp/diff_new_pack.g1Ow0g/_new  2026-01-22 17:59:13.559183222 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-acefile
 #
-# 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
@@ -19,7 +19,7 @@
 %define skip_python2 1
 %bcond_without libalternatives
 Name:           python-acefile
-Version:        0.6.13
+Version:        0.6.14
 Release:        0
 Summary:        ACE 1.0 and 2.0 archive reader/extractor in pure Python
 License:        BSD-2-Clause

++++++ acefile-0.6.13.tar.gz -> acefile-0.6.14.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/.github/workflows/test.yml 
new/acefile-0.6.14/.github/workflows/test.yml
--- old/acefile-0.6.13/.github/workflows/test.yml       1970-01-01 
01:00:00.000000000 +0100
+++ new/acefile-0.6.14/.github/workflows/test.yml       2026-01-21 
00:19:33.000000000 +0100
@@ -0,0 +1,108 @@
+name: Test
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+jobs:
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ["3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", 
"3.14t"]
+
+    steps:
+
+    - uses: actions/checkout@v6
+      with:
+        path: acefile
+
+    - uses: actions/checkout@v6
+      with:
+        repository: droe/acefile-testdata
+        path: testdata
+
+    - name: "Set up Python ${{ matrix.python-version }}"
+      uses: actions/setup-python@v5
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: "Install dependencies"
+      shell: bash
+      run: |
+        python -m pip install --upgrade pip
+        if [ -f acefile/requirements.txt ]; then pip install -r 
acefile/requirements.txt; fi
+        if [ -f acefile/requirements-dev.txt ]; then pip install -r 
acefile/requirements-dev.txt; fi
+
+    - name: "Display Python version"
+      shell: bash
+      run: |
+        python -c "import sys; print(sys.version)"
+
+    - name: "Build acebitstream C module"
+      shell: bash
+      run: |
+        cd acefile && python setup.py build_ext --inplace
+
+
+    # doctest variants
+
+    - name: "Run doctest, with acebitstream, free-threaded"
+      if: ${{ endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python -X gil=0 acefile.py 
--doctest -v
+
+    - name: "Run doctest, with acebitstream, with GIL"
+      if: ${{ endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python -X gil=1 acefile.py 
--doctest -v
+
+    - name: "Run doctest, with acebitstream"
+      if: ${{ ! endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 python acefile.py --doctest 
-v
+
+    - name: "Run doctest, without acebitstream, free-threaded"
+      if: ${{ endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python -X gil=0 acefile.py 
--doctest -v
+
+    - name: "Run doctest, without acebitstream, with GIL"
+      if: ${{ endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python -X gil=1 acefile.py 
--doctest -v
+
+    - name: "Run doctest, without acebitstream"
+      if: ${{ ! endsWith(matrix.python-version, 't') }}
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 python acefile.py --doctest 
-v
+
+
+    # pytest variants without GIL variants for now
+
+    - name: "Run pytest, with acebitstream"
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=1 pytest -v
+
+    - name: "Run pytest, without acebitstream"
+      shell: bash
+      run: |
+        cd acefile && ACEFILE_FORCE_ACEBITSTREAM=0 pytest -v
+
+
+    # build documentation
+
+    - name: "Build apidocs"
+      shell: bash
+      run: |
+        cd acefile/apidoc && sphinx-build -W . _build
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/GNUmakefile 
new/acefile-0.6.14/GNUmakefile
--- old/acefile-0.6.13/GNUmakefile      2019-02-26 21:22:06.000000000 +0100
+++ new/acefile-0.6.14/GNUmakefile      2026-01-19 12:59:18.000000000 +0100
@@ -30,9 +30,10 @@
 
 doctest:
        $(PYTHON) acefile.py --doctest
+       $(PYTHON) tests/test_smoke.py --doctest
 
-test:
-       $(PYTHON) setup.py test
+test: doctest
+       pytest -v
 
 build:
        $(PYTHON) setup.py build_ext --inplace
@@ -47,5 +48,5 @@
 todo:
        egrep -r 'XXX|TODO|FIXME' *.py
 
-.PHONY: all apidoc dist sign upload doctest test build clean todo
+.PHONY: all apidoc dist sign upload doctest build clean todo
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/LICENSE.md 
new/acefile-0.6.14/LICENSE.md
--- old/acefile-0.6.13/LICENSE.md       2024-11-09 01:16:39.000000000 +0100
+++ new/acefile-0.6.14/LICENSE.md       2026-01-18 02:00:10.000000000 +0100
@@ -2,7 +2,7 @@
 
 ## Copyright
 
-Copyright (c) 2017-2024, Daniel Roethlisberger and contributors.
+Copyright (c) 2017-2026, Daniel Roethlisberger and contributors.
 All rights reserved.
 Licensed under the 2-clause BSD license contained herein.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/NEWS.md new/acefile-0.6.14/NEWS.md
--- old/acefile-0.6.13/NEWS.md  2024-11-09 01:15:52.000000000 +0100
+++ new/acefile-0.6.14/NEWS.md  2026-01-21 00:30:30.000000000 +0100
@@ -1,3 +1,11 @@
+### acefile 0.6.14 2026-01-21
+
+-   Experimental support for free-threaded Python.
+-   Fix memory leak in acebitstream module.
+-   Add pytest smoke tests and configure GitHub Actions CI for testing.
+-   Remove deprecated setup.py features.
+
+
 ### acefile 0.6.13 2024-11-09
 
 -   Rephrase setup failure notices for clarity.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/PKG-INFO new/acefile-0.6.14/PKG-INFO
--- old/acefile-0.6.13/PKG-INFO 2024-11-09 01:26:55.703423700 +0100
+++ new/acefile-0.6.14/PKG-INFO 2026-01-21 00:33:21.405675200 +0100
@@ -1,15 +1,14 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: acefile
-Version: 0.6.13
+Version: 0.6.14
 Summary: Read/test/extract ACE 1.0 and 2.0 archives in pure python.
 Home-page: https://www.roe.ch/acefile
 Author: Daniel Roethlisberger
 Author-email: [email protected]
-License: BSD
+License: BSD-2-Clause
 Keywords: ace,unace,compression,decompression,archive
 Platform: all
 Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
@@ -22,8 +21,19 @@
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
 Classifier: Topic :: System :: Archiving :: Compression
 License-File: LICENSE.md
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: platform
+Dynamic: summary
 
 This single-file, pure python 3, no-dependencies implementation is intended
 to be used as a library, but also provides a stand-alone unace utility.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/README.md new/acefile-0.6.14/README.md
--- old/acefile-0.6.13/README.md        2024-11-09 01:18:11.000000000 +0100
+++ new/acefile-0.6.14/README.md        2026-01-21 00:04:28.000000000 +0100
@@ -1,5 +1,5 @@
 # acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python
-Copyright (C) 2017-2024, [Daniel Roethlisberger](//daniel.roe.ch/).  
+Copyright (C) 2017-2026, [Daniel Roethlisberger](//daniel.roe.ch/).  
 https://www.roe.ch/acefile  
 
 
@@ -116,6 +116,24 @@
     acefile-unace -h
 
 
+## Testing
+
+This project uses docstrings for unit testing:
+
+    ./acefile.py --doctest
+
+This project uses pytest for integration testing, recursively looking for
+corpora of ACE archives in `../acefile-testdata/` and
+`../acefile-testdata-private/`.  The former is intended for a checkout of
+[droe/acefile-testdata](https://github.com/droe/acefile-testdata).  The latter
+is intended for a developer-local test corpus that might be impractically large
+or contain data that cannot be redistributed.  Both are optional, the
+integration tests are currently not useful without any test archives though.
+
+    git clone https://github.com/droe/acefile-testdata.git ../acefile-testdata
+    pytest -v
+
+
 ## Credits
 
 Marcel Lemke for designing the ACE archive format and ACE compression and
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/acefile.egg-info/PKG-INFO 
new/acefile-0.6.14/acefile.egg-info/PKG-INFO
--- old/acefile-0.6.13/acefile.egg-info/PKG-INFO        2024-11-09 
01:26:55.000000000 +0100
+++ new/acefile-0.6.14/acefile.egg-info/PKG-INFO        2026-01-21 
00:33:21.000000000 +0100
@@ -1,15 +1,14 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: acefile
-Version: 0.6.13
+Version: 0.6.14
 Summary: Read/test/extract ACE 1.0 and 2.0 archives in pure python.
 Home-page: https://www.roe.ch/acefile
 Author: Daniel Roethlisberger
 Author-email: [email protected]
-License: BSD
+License: BSD-2-Clause
 Keywords: ace,unace,compression,decompression,archive
 Platform: all
 Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
@@ -22,8 +21,19 @@
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
 Classifier: Topic :: System :: Archiving :: Compression
 License-File: LICENSE.md
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: platform
+Dynamic: summary
 
 This single-file, pure python 3, no-dependencies implementation is intended
 to be used as a library, but also provides a stand-alone unace utility.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/acefile.egg-info/SOURCES.txt 
new/acefile-0.6.14/acefile.egg-info/SOURCES.txt
--- old/acefile-0.6.13/acefile.egg-info/SOURCES.txt     2024-11-09 
01:26:55.000000000 +0100
+++ new/acefile-0.6.14/acefile.egg-info/SOURCES.txt     2026-01-21 
00:33:21.000000000 +0100
@@ -5,8 +5,10 @@
 NEWS.md
 README.md
 acefile.py
+requirements-dev.txt
 requirements.txt
 setup.py
+.github/workflows/test.yml
 acefile.egg-info/PKG-INFO
 acefile.egg-info/SOURCES.txt
 acefile.egg-info/dependency_links.txt
@@ -15,6 +17,9 @@
 apidoc/GNUmakefile
 apidoc/conf.py
 apidoc/index.rst
+apidoc/_static/.gitkeep
 c/acebitstream.c
 c/acebitstream.h
-c/acebitstream_mod.c
\ No newline at end of file
+c/acebitstream_mod.c
+tests/conftest.py
+tests/test_smoke.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/acefile.py 
new/acefile-0.6.14/acefile.py
--- old/acefile-0.6.13/acefile.py       2024-11-09 01:20:20.000000000 +0100
+++ new/acefile-0.6.14/acefile.py       2026-01-21 00:29:30.000000000 +0100
@@ -1,8 +1,7 @@
 #!/usr/bin/env python3
-# vim: set list et ts=8 sts=4 sw=4 ft=python:
 
 # acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python
-# Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]>
+# Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]>
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -55,12 +54,12 @@
 - https://infosec.exchange/@droe
 """
 
-__version__     = '0.6.13'
+__version__     = '0.6.14'
 __author__      = 'Daniel Roethlisberger'
 __email__       = '[email protected]'
-__copyright__   = 'Copyright 2017-2024, Daniel Roethlisberger'
+__copyright__   = 'Copyright 2017-2026, Daniel Roethlisberger'
 __credits__     = ['Marcel Lemke']
-__license__     = 'BSD'
+__license__     = 'BSD-2-Clause'
 __url__         = 'https://www.roe.ch/acefile'
 
 
@@ -80,10 +79,18 @@
 import sys
 import zlib
 
-try:
+
+
+_ACEFILE_FORCE_ACEBITSTREAM = os.environ.get('ACEFILE_FORCE_ACEBITSTREAM', 
None)
+if _ACEFILE_FORCE_ACEBITSTREAM == '1':
     import acebitstream
-except:
+elif _ACEFILE_FORCE_ACEBITSTREAM == '0':
     acebitstream = None
+else:
+    try:
+        import acebitstream
+    except:
+        acebitstream = None
 
 
 
@@ -2812,29 +2819,29 @@
         want to ignore the filenames in the archive headers and instead
         generate your own filename for each archive member.
 
-        >>> AceMember._sanitize_filename(b'a.exe\\0b.txt')
+        >>> AceMember._sanitize_filename(b'a.exe\\0b.txt').replace('\\\\', '/')
         'a.exe'
-        >>> AceMember._sanitize_filename(b'\\\\etc\\\\foo/bar\\\\baz.txt')
+        >>> 
AceMember._sanitize_filename(b'\\\\etc\\\\foo/bar\\\\baz.txt').replace('\\\\', 
'/')
         'etc/foo/bar/baz.txt'
-        >>> 
AceMember._sanitize_filename(b'a/b/../b/.//.././c/.//../d/file.txt')
+        >>> 
AceMember._sanitize_filename(b'a/b/../b/.//.././c/.//../d/file.txt').replace('\\\\',
 '/')
         'a/d/file.txt'
-        >>> AceMember._sanitize_filename(b'/etc/passwd')
+        >>> AceMember._sanitize_filename(b'/etc/passwd').replace('\\\\', '/')
         'etc/passwd'
-        >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/passwd')
+        >>> 
AceMember._sanitize_filename(b'.././.././.././.././../etc/passwd').replace('\\\\',
 '/')
         'etc/passwd'
-        >>> AceMember._sanitize_filename(b'C:\\\\Windows\\\\foo.exe')
+        >>> 
AceMember._sanitize_filename(b'C:\\\\Windows\\\\foo.exe').replace('\\\\', '/')
         'C/Windows/foo.exe'
-        >>> AceMember._sanitize_filename(b'\\\\\\\\server\\\\share\\\\file')
+        >>> 
AceMember._sanitize_filename(b'\\\\\\\\server\\\\share\\\\file').replace('\\\\',
 '/')
         'server/share/file'
-        >>> AceMember._sanitize_filename(b'\\\\\\\\.\\\\CdRom0')
+        >>> 
AceMember._sanitize_filename(b'\\\\\\\\.\\\\CdRom0').replace('\\\\', '/')
         'CdRom0'
-        >>> AceMember._sanitize_filename(b'\\\\\\\\?\\\\raw\\\\path')
+        >>> 
AceMember._sanitize_filename(b'\\\\\\\\?\\\\raw\\\\path').replace('\\\\', '/')
         'raw/path'
-        >>> AceMember._sanitize_filename(b'hello\x05world')
+        >>> AceMember._sanitize_filename(b'hello\x05world').replace('\\\\', 
'/')
         'helloworld'
-        >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/../')
+        >>> 
AceMember._sanitize_filename(b'.././.././.././.././../etc/../').replace('\\\\', 
'/')
         ''
-        >>> 
AceMember._sanitize_filename(b'c:\\\\c:\\\\CVE-2018-20250\\\\p.lnk')
+        >>> 
AceMember._sanitize_filename(b'c:\\\\c:\\\\CVE-2018-20250\\\\p.lnk').replace('\\\\',
 '/')
         'c/c/CVE-2018-20250/p.lnk'
         """
         filename = filename.decode('utf-8', errors='replace')
@@ -2847,15 +2854,17 @@
             filename = filename.replace('/', os.sep)
         if os.sep != '\\':
             filename = filename.replace('\\', os.sep)
-        # eliminate characters illegal on some platforms, including some
-        # characters that are relevant for path traversal attacks (i.e. colon)
+        # Eliminate characters illegal on some platforms, including some
+        # characters that are relevant for path traversal attacks (i.e. colon).
         filename = filename.translate(AceMember.TRANSLATION_TAB)
-        # first eliminate all /./, foo/../ and similar sequences, then remove
-        # all remaining .. labels in order to avoid path traversal attacks but
-        # still allow a safe subset of dot syntax in the filename
+        # First eliminate all /./, foo/../ and similar sequences, then remove
+        # all remaining . and .. labels in order to avoid path traversal
+        # attacks but still allow a safe subset of dot syntax in the filename.
+        # The exact behaviour of normpath differs between POSIX and Windows, so
+        # the regex also serves as a second line of defense.
         filename = os.path.normpath(filename)
         escsep = re.escape(os.sep)
-        pattern = r'(^|%s)(?:\.\.(?:%s|$))+' % (escsep, escsep)
+        pattern = r'(^|%s)(?:\.\.?(?:%s|$))+' % (escsep, escsep)
         filename = re.sub(pattern, r'\1', filename)
         # remove leading path separators to ensure a relative path
         filename = filename.lstrip(os.sep)
@@ -4353,6 +4362,8 @@
     import doctest
     return doctest.DocTestSuite(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
 
+
+
 def test():
     import doctest
     fails, tests = doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/apidoc/GNUmakefile 
new/acefile-0.6.14/apidoc/GNUmakefile
--- old/acefile-0.6.13/apidoc/GNUmakefile       2017-07-11 22:09:12.000000000 
+0200
+++ new/acefile-0.6.14/apidoc/GNUmakefile       2026-01-21 00:17:03.000000000 
+0100
@@ -1,15 +1,11 @@
 SPHINXOPTS    =
 SPHINXBUILD   = sphinx-build
-SPHINXPROJ    = acefile
 SOURCEDIR     = .
 BUILDDIR      = _build
 
-help:
-       @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
 all: html
 
-%: GNUmakefile
-       @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+html:
+       @$(SPHINXBUILD) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
 
-.PHONY: help all GNUmakefile
+.PHONY: all html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/apidoc/conf.py 
new/acefile-0.6.14/apidoc/conf.py
--- old/acefile-0.6.13/apidoc/conf.py   2017-07-16 21:03:49.000000000 +0200
+++ new/acefile-0.6.14/apidoc/conf.py   2026-01-18 16:12:15.000000000 +0100
@@ -1,5 +1,4 @@
 #!/usr/bin/env python3
-# vim: set list et ts=8 sts=4 sw=4 ft=python:
 
 import os
 import sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/apidoc/index.rst 
new/acefile-0.6.14/apidoc/index.rst
--- old/acefile-0.6.13/apidoc/index.rst 2018-01-02 23:47:09.000000000 +0100
+++ new/acefile-0.6.14/apidoc/index.rst 2026-01-20 01:18:22.000000000 +0100
@@ -22,6 +22,9 @@
 See :class:`acefile.AceArchive` and :class:`acefile.AceMember` for the
 complete descriptions of the methods supported by these two classes.
 
+On free-threaded Python, :mod:`acefile` is only thread-safe as long as only a
+single thread operates on any given :class:`acefile.AceArchive` at a time.
+
 Functions
 ~~~~~~~~~
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream.c 
new/acefile-0.6.14/c/acebitstream.c
--- old/acefile-0.6.13/c/acebitstream.c 2024-11-09 01:18:42.000000000 +0100
+++ new/acefile-0.6.14/c/acebitstream.c 2026-01-20 00:42:37.000000000 +0100
@@ -1,6 +1,6 @@
 /*
  * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python
- * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]>
+ * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -62,8 +62,9 @@
        assert(ctx->bitcount <= 32);
        if (ctx->bufptr == ctx->bufend) {
                acebitstream_refill_buf(ctx);
-               if (ctx->bufptr == ctx->bufend)
+               if (ctx->bufptr == ctx->bufend) {
                        return;
+               }
        }
        ctx->bits |= ((uint64_t)*ctx->bufptr) << (32 - ctx->bitcount);
        ctx->bitcount += 32;
@@ -76,8 +77,9 @@
        acebitstream_ctx_t *ctx;
 
        ctx = malloc(sizeof(acebitstream_ctx_t));
-       if (!ctx)
+       if (!ctx) {
                return NULL;
+       }
        memset(ctx, 0, sizeof(acebitstream_ctx_t));
 
        ctx->read = read;
@@ -115,8 +117,9 @@
        assert(n > 0 && n < 32);
        if (ctx->bitcount < n) {
                acebitstream_refill_bits(ctx);
-               if (ctx->bitcount < n)
+               if (ctx->bitcount < n) {
                        return ACEBITSTREAM_EOF;
+               }
        }
        ctx->bits <<= n;
        ctx->bitcount -= n;
@@ -130,8 +133,9 @@
 
        value = acebitstream_peek_bits(ctx, n);
        rv = acebitstream_skip_bits(ctx, n);
-       if (rv == ACEBITSTREAM_EOF)
+       if (rv == ACEBITSTREAM_EOF) {
                return rv;
+       }
        return value;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream.h 
new/acefile-0.6.14/c/acebitstream.h
--- old/acefile-0.6.13/c/acebitstream.h 2024-11-09 01:18:50.000000000 +0100
+++ new/acefile-0.6.14/c/acebitstream.h 2026-01-18 02:01:40.000000000 +0100
@@ -1,6 +1,6 @@
 /*
  * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python
- * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]>
+ * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/c/acebitstream_mod.c 
new/acefile-0.6.14/c/acebitstream_mod.c
--- old/acefile-0.6.13/c/acebitstream_mod.c     2024-11-09 01:19:00.000000000 
+0100
+++ new/acefile-0.6.14/c/acebitstream_mod.c     2026-01-20 00:47:08.000000000 
+0100
@@ -1,6 +1,6 @@
 /*
  * acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python
- * Copyright (C) 2017-2024, Daniel Roethlisberger <[email protected]>
+ * Copyright (C) 2017-2026, Daniel Roethlisberger <[email protected]>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -49,17 +49,22 @@
        PyObject *f = ctx;
 
        method = PyObject_GetAttrString(f, (char*)"read");
-       if (method == NULL)
+       if (method == NULL) {
                return 0;
+       }
        arglist = Py_BuildValue("(k)", n);
-       if (arglist == NULL)
+       if (arglist == NULL) {
+               Py_DECREF(method);
                return 0;
+       }
        buffer = PyObject_CallObject(method, arglist);
+       Py_DECREF(method);
        Py_DECREF(arglist);
-       if (buffer == NULL)
+       if (buffer == NULL) {
                return 0;
+       }
        ret = PyBytes_Size(buffer);
-       if (ret % 4) {
+       if (ret % 4 != 0) {
                Py_DECREF(buffer);
                PyErr_SetString(PyExc_ValueError,
                                "Truncated 32-bit word from file-like object");
@@ -107,8 +112,9 @@
        BitStream *self;
 
        self = (BitStream*)type->tp_alloc(type, 0);
-       if (self == NULL)
+       if (self == NULL) {
                return NULL;
+       }
        self->ctx = NULL;
        return (PyObject*)self;
 }
@@ -120,12 +126,16 @@
        PyObject *f;
        size_t bufsz = 131072;
 
-       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|I", kwlist, &f, &bufsz))
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|I", kwlist, &f, 
&bufsz)) {
                return -1;
+       }
+
        Py_INCREF(f);
        self->ctx = acebitstream_new(filelike_read, f, bufsz);
-       if (PyErr_Occurred())
+       if (PyErr_Occurred()) {
+               /* filelike_read raised an exception */
                return -1;
+       }
        return 0;
 }
 
@@ -135,8 +145,9 @@
        size_t ret;
        unsigned int n = 0;
 
-       if (!PyArg_ParseTuple(args, "I", &n))
+       if (!PyArg_ParseTuple(args, "I", &n)) {
                return NULL;
+       }
        if (n > 31) {
                PyErr_SetString(PyExc_ValueError,
                                "Cannot peek more than 31 bits");
@@ -144,8 +155,11 @@
        }
 
        ret = acebitstream_peek_bits(self->ctx, n);
-       if (PyErr_Occurred())
+       if (PyErr_Occurred()) {
+               /* filelike_read raised an exception */
                return NULL;
+       }
+       /* peek_bits never returns ACEBITSTREAM_EOF */
        return PyLong_FromLong(ret);
 }
 
@@ -155,8 +169,9 @@
        size_t ret;
        unsigned int n = 0;
 
-       if (!PyArg_ParseTuple(args, "I", &n))
+       if (!PyArg_ParseTuple(args, "I", &n)) {
                return NULL;
+       }
        if (n > 31) {
                PyErr_SetString(PyExc_ValueError,
                                "Cannot skip more than 31 bits");
@@ -164,8 +179,10 @@
        }
 
        ret = acebitstream_skip_bits(self->ctx, n);
-       if (PyErr_Occurred())
+       if (PyErr_Occurred()) {
+               /* filelike_read raised an exception */
                return NULL;
+       }
        if (ret == ACEBITSTREAM_EOF) {
                PyErr_SetString(PyExc_EOFError,
                                "Cannot skip bits beyond EOF");
@@ -181,8 +198,9 @@
        size_t ret;
        unsigned int n = 0;
 
-       if (!PyArg_ParseTuple(args, "I", &n))
+       if (!PyArg_ParseTuple(args, "I", &n)) {
                return NULL;
+       }
        if (n > 31) {
                PyErr_SetString(PyExc_ValueError,
                                "Cannot read more than 31 bits");
@@ -190,8 +208,10 @@
        }
 
        ret = acebitstream_read_bits(self->ctx, n);
-       if (PyErr_Occurred())
+       if (PyErr_Occurred()) {
+               /* filelike_read raised an exception */
                return NULL;
+       }
        if (ret == ACEBITSTREAM_EOF) {
                PyErr_SetString(PyExc_EOFError,
                                "Cannot read bits beyond EOF");
@@ -261,17 +281,28 @@
 {
        PyObject *module;
 
-       BitStreamType.tp_new = PyType_GenericNew;
-       if (PyType_Ready(&BitStreamType) < 0)
+       if (PyType_Ready(&BitStreamType) < 0) {
                return NULL;
+       }
 
        module = PyModule_Create(&acebitstream_module);
-       if (module == NULL)
+       if (module == NULL) {
                return NULL;
+       }
+
+#ifdef Py_GIL_DISABLED
+       /*
+        * There are no critical sections, because the premise is that
+        * decompression from the same file-like object is inherently not
+        * thread-safe.  Decompressing archive members from the same non-solid
+        * archive in parallel would need to be made thread-safe at a higher
+        * layer than reading bits from a file-like object.
+        */
+       PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
+#endif
 
        Py_INCREF(&BitStreamType);
        PyModule_AddObject(module, "BitStream", (PyObject*)&BitStreamType);
-
        return module;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/requirements-dev.txt 
new/acefile-0.6.14/requirements-dev.txt
--- old/acefile-0.6.13/requirements-dev.txt     1970-01-01 01:00:00.000000000 
+0100
+++ new/acefile-0.6.14/requirements-dev.txt     2026-01-20 01:31:38.000000000 
+0100
@@ -0,0 +1,2 @@
+pytest
+sphinx
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/requirements.txt 
new/acefile-0.6.14/requirements.txt
--- old/acefile-0.6.13/requirements.txt 2018-07-15 20:18:42.000000000 +0200
+++ new/acefile-0.6.14/requirements.txt 2026-01-18 21:05:22.000000000 +0100
@@ -0,0 +1 @@
+setuptools
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/setup.py new/acefile-0.6.14/setup.py
--- old/acefile-0.6.13/setup.py 2024-08-04 14:02:35.000000000 +0200
+++ new/acefile-0.6.14/setup.py 2026-01-21 00:04:45.000000000 +0100
@@ -1,5 +1,4 @@
 #!/usr/bin/env python3
-# vim: set list et ts=8 sts=4 sw=4 ft=python:
 
 from setuptools import setup, find_packages
 from setuptools.extension import Extension
@@ -33,9 +32,7 @@
         license=acefile.__license__,
         platforms=['all'],
         classifiers=[
-            # https://pypi.python.org/pypi?%3Aaction=list_classifiers
             'Development Status :: 5 - Production/Stable',
-            'License :: OSI Approved :: BSD License',
             'Operating System :: OS Independent',
             'Programming Language :: Python :: 3',
             'Programming Language :: Python :: 3.3',
@@ -48,17 +45,17 @@
             'Programming Language :: Python :: 3.10',
             'Programming Language :: Python :: 3.11',
             'Programming Language :: Python :: 3.12',
+            'Programming Language :: Python :: 3.13',
             'Topic :: System :: Archiving :: Compression',
         ],
         keywords=['ace', 'unace', 'compression', 'decompression', 'archive'],
         py_modules=['acefile'],
         ext_modules=ext_modules,
-        entry_points = {
+        entry_points={
             'console_scripts': [
                 'acefile-unace=acefile:unace',
             ],
         },
-        test_suite = 'acefile.testsuite',
     )
 
 try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/tests/conftest.py 
new/acefile-0.6.14/tests/conftest.py
--- old/acefile-0.6.13/tests/conftest.py        1970-01-01 01:00:00.000000000 
+0100
+++ new/acefile-0.6.14/tests/conftest.py        2026-01-20 00:11:44.000000000 
+0100
@@ -0,0 +1,3 @@
+def pytest_addoption(parser):
+    parser.addoption("--fast", action="store_true",
+                     help="Skip private testdata")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acefile-0.6.13/tests/test_smoke.py 
new/acefile-0.6.14/tests/test_smoke.py
--- old/acefile-0.6.13/tests/test_smoke.py      1970-01-01 01:00:00.000000000 
+0100
+++ new/acefile-0.6.14/tests/test_smoke.py      2026-01-20 00:17:27.000000000 
+0100
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+import glob
+import os
+import re
+import sys
+
+import pytest
+
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/..')
+import acefile
+
+
+
+def _metadict_from_path(path):
+    """
+    >>> _metadict_from_path("winappdbg-winappdbg_v1.6.ace")
+    {}
+    >>> 
_metadict_from_path("winappdbg-winappdbg_v1.6_password_pw=infected.ace")
+    {'pw': 'infected'}
+    >>> _metadict_from_path("foo_foo=oof-bar=baz+qux=quux.ace")
+    {'foo': 'oof', 'bar': 'baz', 'qux': 'quux'}
+    """
+    metadict = {}
+    for m in re.finditer(r'([a-zA-Z0-9]+)=([a-zA-Z0-9]+)', path):
+        metadict[m.group(1)] = m.group(2)
+    return metadict
+
+
+
+def pytest_generate_tests(metafunc):
+    archives = []
+    here = os.path.dirname(os.path.abspath(__file__))
+    subdirs = ['acefile-testdata']
+    if not metafunc.config.getoption("fast"):
+        subdirs.append('acefile-testdata-private')
+    for subdir in subdirs:
+        path = os.path.realpath(f'{here}/../../{subdir}')
+        if os.path.exists(path):
+            archives += glob.glob(f'{path}/**/*.ace')
+            archives += glob.glob(f'{path}/**/*.exe')
+    metafunc.parametrize("archive_path", archives)
+
+
+
+def test_archive_test(archive_path):
+    metadict = _metadict_from_path(archive_path)
+    pwd = metadict.get('pw', None)
+    with acefile.open(archive_path) as f:
+        for member in f:
+            if member.is_dir():
+                continue
+            assert f.test(member, pwd=pwd)
+
+
+
+def doctst():
+    import doctest
+    fails, tests = doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)
+    sys.exit(min(1, fails))
+
+
+
+if __name__ == '__main__':
+    if '--doctest' in sys.argv:
+        doctst()
+    raise NotImplementedError("main")

Reply via email to