public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/python-b4] rawhide: Update to 0.15.2
@ 2026-09-23 7:37 Maxime Ripard
0 siblings, 0 replies; only message in thread
From: Maxime Ripard @ 2026-09-23 7:37 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/python-b4
Branch : rawhide
Commit : 06830e6a7bd7efed0e305ec885fc1658360ad055
Author : Maxime Ripard <mripard@redhat.com>
Date : 2026-09-23T07:37:26+00:00
Stats : +858/-7 in 7 file(s)
URL : https://src.fedoraproject.org/rpms/python-b4/c/06830e6a7bd7efed0e305ec885fc1658360ad055?branch=rawhide
Log:
Update to 0.15.2
b4 was recently released with a new version that, in part, includes a
new TUI that depends on textual, and a conversion from requirements to
pyproject.toml.
Adapt the spec to the new build system: switch %pyproject_buildrequires
from -r requirements.in to -p, adjust the attestation-stripping sed to
target pyproject.toml, and update the man page from section 5 to 1.
Backport two upstream patches so the package builds and tests pass
without the optional textual dependency: lazy-load the review_tui
package so import_all_modules.py no longer fails, and guard the
TUI-dependent tests with importorskip / skipif marks so %check skips
them cleanly. Add a third patch to skip TestSuspendToShellCwd, whose
fixture was moved out of the TUI package upstream by a refactoring too
large to backport.
Signed-off-by: Maxime Ripard <mripard@redhat.com>
---
diff --git a/0001-Make-TUI-packages-importable-without-the-textual-ext.patch b/0001-Make-TUI-packages-importable-without-the-textual-ext.patch
new file mode 100644
index 0000000..30e9451
--- /dev/null
+++ b/0001-Make-TUI-packages-importable-without-the-textual-ext.patch
@@ -0,0 +1,264 @@
+From f1f3c17d6d12837b20aa645f407c00c8862d6a89 Mon Sep 17 00:00:00 2001
+From: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Date: Thu, 28 May 2026 17:16:01 -0400
+Subject: [PATCH] Make TUI packages importable without the textual extra
+
+Downstream packagers run an importability check across every shipped
+module after install (e.g. Fedora's `import_all_modules.py`). Building
+the rpm without the optional `[tui]` extra broke at that check:
+
+ Check import: b4.review_tui
+ ...
+ ModuleNotFoundError: No module named 'textual'
+ Failed to import: b4.review_tui
+
+`b4/review_tui/__init__.py` and `b4/tui/__init__.py` eagerly pulled
+their submodules into the package namespace, and those submodules
+import `textual` at module load time. Just running `import
+b4.review_tui` therefore required the optional dependency, even
+though the actual TUI code is only ever reached through the guarded
+`cmd_tui` entry points.
+
+Convert both `__init__.py` files to PEP 562 lazy attribute access.
+`import b4.review_tui` and `import b4.tui` now succeed without
+`textual` present; the real submodule load only happens when a public
+symbol is accessed. `TYPE_CHECKING` imports keep the full public API
+visible to mypy, pyright, and ty, so callers and tests that use
+`from b4.review_tui import ...` still type-check correctly.
+
+The one consumer that obtained the package by name (`cmd_tui` in
+`b4/review/_review.py`) now imports `run_tracking_tui` directly from
+the submodule inside its existing `try/except ImportError` guard, so
+the user-facing "install b4[tui]" message still fires when the extra
+is missing -- the lazy `__init__.py` on its own would let the bare
+`import` succeed and crash later on attribute access.
+
+Add a regression test that mirrors the downstream check: spawn a
+subprocess, block `textual` via `sys.meta_path`, then walk every
+public `b4.*` submodule with `pkgutil.walk_packages` and import each
+one. The subprocess is required so the meta_path blocker is not
+bypassed by `textual` already being cached in `sys.modules` from
+earlier TUI tests in the same session.
+
+Reported-by: Maxime Ripard <mripard@redhat.com>
+Link: https://lore.kernel.org/tools/20260515-uber-pigeon-of-opportunity-c3bb74@houat/
+Assisted-by: claude-opus-4-7
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+---
+ src/b4/review/_review.py | 11 +++--
+ src/b4/review_tui/__init__.py | 88 ++++++++++++++++++++++++++++-------
+ src/tests/test_packaging.py | 68 +++++++++++++++++++++++++++
+ 3 files changed, 146 insertions(+), 21 deletions(-)
+ create mode 100644 src/tests/test_packaging.py
+
+diff --git a/src/b4/review/_review.py b/src/b4/review/_review.py
+index a0527a4420c3..4e1610826aa9 100644
+--- a/src/b4/review/_review.py
++++ b/src/b4/review/_review.py
+@@ -2020,11 +2020,11 @@ def update_series_tracking(
+ return result
+
+
+ def cmd_tui(cmdargs: argparse.Namespace) -> None:
+ try:
+- import b4.review_tui
++ from b4.review_tui._entry import run_tracking_tui
+ except ImportError:
+ logger.critical('The TUI requires the textual library.')
+ logger.critical('Install it with: pip install b4[tui]')
+ sys.exit(1)
+
+@@ -2045,13 +2045,16 @@ def cmd_tui(cmdargs: argparse.Namespace) -> None:
+ else:
+ logger.critical('Project not enrolled: %s', identifier)
+ logger.critical('Enroll with: b4 review enroll')
+ sys.exit(1)
+
+- b4.review_tui.run_tracking_tui(identifier, email_dryrun=cmdargs.email_dryrun,
+- no_sign=cmdargs.no_sign,
+- no_mouse=cmdargs.no_mouse)
++ run_tracking_tui(
++ identifier,
++ email_dryrun=cmdargs.email_dryrun,
++ no_sign=cmdargs.no_sign,
++ no_mouse=cmdargs.no_mouse,
++ )
+
+
+ def _prepare_review_session(cmdargs: argparse.Namespace) -> Dict[str, Any]:
+ """Common setup for review tui.
+
+diff --git a/src/b4/review_tui/__init__.py b/src/b4/review_tui/__init__.py
+index 47f3f93adec7..57f8227de6e2 100644
+--- a/src/b4/review_tui/__init__.py
++++ b/src/b4/review_tui/__init__.py
+@@ -1,21 +1,75 @@
+-from b4.review_tui._common import (
+- logger, PATCH_STATE_MARKERS,
+- resolve_styles, reviewer_colours,
+- gather_attestation_info,
+- _addrs_to_lines, _lines_to_header, _validate_addrs,
+-)
+-from b4.review_tui._review_app import ReviewApp
+-from b4.review_tui._tracking_app import TrackingApp
+-from b4.review_tui._pw_app import PwApp
+-from b4.review_tui._entry import (
+- run_branch_tui, run_pw_tui, run_tracking_tui,
+-)
++# SPDX-License-Identifier: GPL-2.0-or-later
++"""TUI components for ``b4 review``.
++
++The submodules import the optional ``textual`` dependency at module load
++time. Exposing the public API through :pep:`562` ``__getattr__`` keeps
++``import b4.review_tui`` working even when the ``[tui]`` extra is not
++installed -- needed so downstream packaging tools that probe importability
++of every shipped module (e.g. Fedora's ``import_all_modules.py``) don't
++fail when ``textual`` is absent.
++"""
++
++import importlib
++from typing import TYPE_CHECKING, Any
++
++if TYPE_CHECKING:
++ from b4.review_tui._common import (
++ PATCH_STATE_MARKERS,
++ _addrs_to_lines,
++ _lines_to_header,
++ _validate_addrs,
++ gather_attestation_info,
++ logger,
++ resolve_styles,
++ reviewer_colours,
++ )
++ from b4.review_tui._entry import (
++ run_branch_tui,
++ run_pw_tui,
++ run_tracking_tui,
++ )
++ from b4.review_tui._pw_app import PwApp
++ from b4.review_tui._review_app import ReviewApp
++ from b4.review_tui._tracking_app import TrackingApp
+
+ __all__ = [
+- 'logger', 'PATCH_STATE_MARKERS',
+- 'resolve_styles', 'reviewer_colours',
++ 'PATCH_STATE_MARKERS',
++ 'PwApp',
++ 'ReviewApp',
++ 'TrackingApp',
++ '_addrs_to_lines',
++ '_lines_to_header',
++ '_validate_addrs',
+ 'gather_attestation_info',
+- '_addrs_to_lines', '_lines_to_header', '_validate_addrs',
+- 'ReviewApp', 'TrackingApp', 'PwApp',
+- 'run_branch_tui', 'run_pw_tui', 'run_tracking_tui',
++ 'logger',
++ 'resolve_styles',
++ 'reviewer_colours',
++ 'run_branch_tui',
++ 'run_pw_tui',
++ 'run_tracking_tui',
+ ]
++
++_LAZY_ATTRS: dict[str, str] = {
++ 'PATCH_STATE_MARKERS': '_common',
++ '_addrs_to_lines': '_common',
++ '_lines_to_header': '_common',
++ '_validate_addrs': '_common',
++ 'gather_attestation_info': '_common',
++ 'logger': '_common',
++ 'resolve_styles': '_common',
++ 'reviewer_colours': '_common',
++ 'run_branch_tui': '_entry',
++ 'run_pw_tui': '_entry',
++ 'run_tracking_tui': '_entry',
++ 'PwApp': '_pw_app',
++ 'ReviewApp': '_review_app',
++ 'TrackingApp': '_tracking_app',
++}
++
++
++def __getattr__(name: str) -> Any:
++ submodule = _LAZY_ATTRS.get(name)
++ if submodule is None:
++ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
++ mod = importlib.import_module(f'b4.review_tui.{submodule}')
++ return getattr(mod, name)
+diff --git a/src/tests/test_packaging.py b/src/tests/test_packaging.py
+new file mode 100644
+index 000000000000..cedf8cf22d74
+--- /dev/null
++++ b/src/tests/test_packaging.py
+@@ -0,0 +1,68 @@
++#!/usr/bin/env python3
++# -*- coding: utf-8 -*-
++# SPDX-License-Identifier: GPL-2.0-or-later
++# Copyright (C) 2026 by the Linux Foundation
++#
++"""Regression tests for the packaged module surface.
++
++Downstream packagers (e.g. Fedora's ``import_all_modules.py`` script)
++probe importability of every public module after install. The
++``[tui]``-only modules must not break that check when the optional
++``textual`` dependency is absent.
++"""
++
++import subprocess
++import sys
++import textwrap
++
++_PROBE_SCRIPT = textwrap.dedent("""
++ import importlib
++ import pkgutil
++ import sys
++
++ # Simulate `textual` not being installed by blocking the import.
++ class _Blocker:
++ def find_spec(self, name, path, target=None):
++ if name == 'textual' or name.startswith('textual.'):
++ raise ModuleNotFoundError(f"No module named {name!r}")
++ return None
++
++ sys.meta_path.insert(0, _Blocker())
++
++ import b4
++
++ failures = []
++ for info in pkgutil.walk_packages(b4.__path__, prefix='b4.'):
++ name = info.name
++ # Mirror what Fedora's import-all check does: only public modules.
++ if any(part.startswith('_') for part in name.split('.')[1:]):
++ continue
++ try:
++ importlib.import_module(name)
++ except Exception as e:
++ failures.append(f'{name}: {type(e).__name__}: {e}')
++
++ if failures:
++ for line in failures:
++ print(line)
++ sys.exit(1)
++""")
++
++
++def test_public_modules_import_without_textual() -> None:
++ """Every public ``b4.*`` submodule must import without ``textual``.
++
++ Runs in a subprocess so that ``textual`` is not already cached in
++ ``sys.modules`` from earlier TUI tests in the same session.
++ """
++ result = subprocess.run(
++ [sys.executable, '-c', _PROBE_SCRIPT],
++ capture_output=True,
++ text=True,
++ check=False,
++ )
++ assert result.returncode == 0, (
++ 'Some public b4 modules failed to import without textual:\n'
++ f'stdout:\n{result.stdout}\n'
++ f'stderr:\n{result.stderr}'
++ )
+--
+2.55.0
+
diff --git a/0002-tests-guard-optional-dependency-imports-so-the-suite.patch b/0002-tests-guard-optional-dependency-imports-so-the-suite.patch
new file mode 100644
index 0000000..11be3c2
--- /dev/null
+++ b/0002-tests-guard-optional-dependency-imports-so-the-suite.patch
@@ -0,0 +1,234 @@
+From 5db3956f7164dd39dc768b2570373fb917870845 Mon Sep 17 00:00:00 2001
+From: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+Date: Wed, 22 Jul 2026 19:45:18 +0000
+Subject: [PATCH] tests: guard optional-dependency imports so the suite runs
+ without [tui]
+
+The test suite imported textual/rich (and PyNaCl) at module load, so a
+b4 install without the [tui] extra could not even collect the suite -- it
+errored out on the first missing import. That makes life hard for
+downstream packagers who ship a no-tui build (Debian, enterprise Linux)
+and want to run the tests to validate the package.
+
+Guard those imports:
+
+- Modules that are entirely about the TUI (or import a TUI submodule at
+ top level) call pytest.importorskip('textual') before the import, so the
+ whole module skips cleanly when textual is absent.
+- test_patatt.py guards on 'nacl' the same way.
+- test_review.py and test_review_checks.py collect fine without textual --
+ only some of their classes touch review_tui -- so those classes get a
+ @requires_textual skipif mark, preserving their substantial non-TUI
+ coverage on a no-tui install (656 vs 351 tests now run there).
+
+Also declare rich explicitly in the [tui] extra: b4 uses it directly (the
+review and bugs TUIs) rather than only through textual's transitive
+dependency. And allow E402 under src/tests/ in ruff, since importorskip
+guards must run before the imports they protect.
+
+With the suite self-skipping, misc/distro/_run.sh no longer needs its
+grep-based no-tui deselection or the test_patatt ignore; drop them and the
+now-unused WITH_TUI plumbing.
+
+Assisted-by: LLM [design, codegen, tests]
+Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
+[Maxime: Do a minimal backport to skip the new code introduced after 0.15.0]
+Signed-off-by: Maxime Ripard <mripard@kernel.org>
+---
+ src/tests/test_display_width.py | 4 ++++
+ src/tests/test_review.py | 18 ++++++++++++++++++
+ src/tests/test_review_tracking.py | 2 ++
+ src/tests/test_tui_modals.py | 2 ++
+ src/tests/test_tui_review.py | 2 ++
+ src/tests/test_tui_tracking.py | 2 ++
+ 6 files changed, 30 insertions(+)
+
+diff --git a/src/tests/test_display_width.py b/src/tests/test_display_width.py
+index 6b04b48d6f8f..eef748cefb8c 100644
+--- a/src/tests/test_display_width.py
++++ b/src/tests/test_display_width.py
+@@ -1,5 +1,9 @@
++import pytest
++
++pytest.importorskip('textual')
++
+ from b4.review_tui._common import display_width, pad_display
+
+
+ class TestDisplayWidth:
+ """Tests for display_width()."""
+diff --git a/src/tests/test_review.py b/src/tests/test_review.py
+index 4666ee7a0ba2..d76aa8d31336 100644
+--- a/src/tests/test_review.py
++++ b/src/tests/test_review.py
+@@ -1,15 +1,27 @@
+ import email.message
++import importlib
+ import json
+ from typing import Any, Dict, List, Optional, Union
+ from unittest import mock
+
+ import pytest
+
+ import b4
+ from b4 import review
+ from b4 import review_tui
++
++
++# The address-helper functions exposed via review_tui live in a module that
++# imports textual at load time, so the tests that exercise them need the [tui]
++# extra even though the functions themselves are pure. Skip them when textual
++# is absent (e.g. a deliberately no-tui install) rather than fail to collect.
++requires_textual = pytest.mark.skipif(
++ importlib.util.find_spec('textual') is None,
++ reason='requires the [tui] extra (textual)',
++)
++
+ from b4.review._review import REVIEW_MAGIC_MARKER, check_series_attestation
+
+
+ # -- Helper diffs used across tests ------------------------------------------
+
+@@ -665,10 +677,11 @@ index abc..def 100644
+ result = review._build_reply_from_comments(
+ diff, comments, [], commit_msg=commit_msg)
+ assert 'My general comment.' in result
+
+
++@requires_textual
+ class TestAddrsToLines:
+ """Tests for review_tui._addrs_to_lines()."""
+
+ def test_empty_string(self) -> None:
+ assert review_tui._addrs_to_lines('') == ''
+@@ -691,10 +704,11 @@ class TestAddrsToLines:
+ header = '"O\'Brien, Alice" <alice@example.com>'
+ result = review_tui._addrs_to_lines(header)
+ assert 'alice@example.com' in result
+
+
++@requires_textual
+ class TestLinesToHeader:
+ """Tests for review_tui._lines_to_header()."""
+
+ def test_empty_string(self) -> None:
+ assert review_tui._lines_to_header('') == ''
+@@ -724,10 +738,11 @@ class TestLinesToHeader:
+ result = review_tui._lines_to_header(text)
+ assert 'alice@example.com' in result
+ assert 'bob@example.com' in result
+
+
++@requires_textual
+ class TestValidateAddrs:
+ """Tests for review_tui._validate_addrs()."""
+
+ def test_empty_is_valid(self) -> None:
+ assert review_tui._validate_addrs('') is None
+@@ -763,10 +778,11 @@ class TestValidateAddrs:
+ def test_blank_lines_skipped(self) -> None:
+ text = 'alice@example.com\n\nbob@example.com'
+ assert review_tui._validate_addrs(text) is None
+
+
++@requires_textual
+ class TestAddrsRoundTrip:
+ """Round-trip: _addrs_to_lines → _lines_to_header preserves addresses."""
+
+ def test_single_named(self) -> None:
+ header = 'Alice <alice@example.com>'
+@@ -1553,10 +1569,11 @@ class TestCollectFollowups:
+ assert result == []
+
+
+ # -- Tests for _get_art_counts() ---------------------------------------------
+
++@requires_textual
+ class TestGetArtCounts:
+ """Tests for _get_art_counts() in _tracking_app."""
+
+ @staticmethod
+ def _make_tracking_json(followups: Optional[List[Dict[str, Any]]] = None, patches: Optional[List[Dict[str, Any]]] = None) -> str:
+@@ -2828,10 +2845,11 @@ class TestIntegrateFollowupInlineComments:
+ assert result is False
+ # Original comments untouched
+ assert patches[0]['reviews']['reviewer0@example.com']['comments'][0]['text'] == 'Already here.'
+
+
++@requires_textual
+ class TestFollowupItemPerMessage:
+ """Tests for per-message follow-up selection (msgid-based keying)."""
+
+ @staticmethod
+ def _make_session() -> Dict[str, Any]:
+diff --git a/src/tests/test_review_tracking.py b/src/tests/test_review_tracking.py
+index 61a14036d273..db8761643061 100644
+--- a/src/tests/test_review_tracking.py
++++ b/src/tests/test_review_tracking.py
+@@ -7,10 +7,12 @@ from email.message import EmailMessage
+ from typing import Any, Dict, List, Union
+ from unittest import mock
+
+ import pytest
+
++pytest.importorskip('textual')
++
+ import b4
+ import b4.review
+ from b4.review import tracking as review_tracking
+ from b4.review_tui._tracking_app import _format_snooze_until, _format_attestation
+ from b4.review_tui._modals import SnoozeScreen
+diff --git a/src/tests/test_tui_modals.py b/src/tests/test_tui_modals.py
+index d6b6c07a42fe..73e9c4cc26e9 100644
+--- a/src/tests/test_tui_modals.py
++++ b/src/tests/test_tui_modals.py
+@@ -9,10 +9,12 @@ Uses Textual's built-in ``App.run_test()`` / ``Pilot`` harness so the
+ tests run without a real terminal. Only lightweight, self-contained
+ modals are exercised here — no database, network, or git needed.
+ """
+ import pytest
+
++pytest.importorskip('textual')
++
+ from typing import Any, Dict, List, Optional, Tuple
+
+ from textual.app import App, ComposeResult
+ from textual.widgets import Input, Label, ListView
+
+diff --git a/src/tests/test_tui_review.py b/src/tests/test_tui_review.py
+index c3a2db4fd795..09f75c2868f4 100644
+--- a/src/tests/test_tui_review.py
++++ b/src/tests/test_tui_review.py
+@@ -8,10 +8,12 @@
+ Tests the shell-return reconciliation logic that detects and handles
+ cosmetic commit edits (e.g. reworded subjects via git rebase -i).
+ """
+ import pytest
+
++pytest.importorskip('textual')
++
+ from typing import Any, Dict, List, Tuple
+
+ import b4
+ import b4.review
+
+diff --git a/src/tests/test_tui_tracking.py b/src/tests/test_tui_tracking.py
+index 03495b40d99a..77d86aa4b76e 100644
+--- a/src/tests/test_tui_tracking.py
++++ b/src/tests/test_tui_tracking.py
+@@ -11,10 +11,12 @@ core user workflows: series listing, navigation, filtering,
+ status transitions, and modal interactions.
+ """
+ import pathlib
+ import pytest
+
++pytest.importorskip('textual')
++
+ from typing import Any, Dict, List, Optional
+ from unittest.mock import patch
+
+ import b4
+ import b4.review
+--
+2.55.0
+
diff --git a/0003-tests-three_way_merge-Skip-TestSuspendToShellCwd-wit.patch b/0003-tests-three_way_merge-Skip-TestSuspendToShellCwd-wit.patch
new file mode 100644
index 0000000..3796e15
--- /dev/null
+++ b/0003-tests-three_way_merge-Skip-TestSuspendToShellCwd-wit.patch
@@ -0,0 +1,64 @@
+From faf55ab96f02f368d56ea9fda75f15a8c891dc59 Mon Sep 17 00:00:00 2001
+From: Maxime Ripard <mripard@kernel.org>
+Date: Tue, 15 Sep 2026 15:42:44 +0000
+Subject: [PATCH] tests: three_way_merge: Skip TestSuspendToShellCwd without
+ textual
+
+TestSuspendToShellCwd tests _suspend_to_shell(), which lives in
+b4.review_tui._common -- a module that imports textual at load time.
+Without the [tui] extra the test cannot run.
+
+Upstream moved _suspend_to_shell into b4.__init__ (commit 51ddf692,
+"shazam: resolve --resolve conflicts inline via a subshell"), but that
+is a large refactoring across six files, not worth backporting for three
+test methods.
+
+Add a @requires_textual skipif mark to the class instead, matching the
+approach used in test_review.py for other TUI-dependent test classes.
+
+Signed-off-by: Maxime Ripard <mripard@kernel.org>
+---
+ src/tests/test_three_way_merge.py | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/tests/test_three_way_merge.py b/src/tests/test_three_way_merge.py
+index e862f1c2ea10..3493e269d923 100644
+--- a/src/tests/test_three_way_merge.py
++++ b/src/tests/test_three_way_merge.py
+@@ -1,15 +1,21 @@
+ import argparse
++import importlib
+ import json
+ import os
+ import pytest
+ import b4
+ import b4.mbox
+
+ from typing import Any, Dict, Optional, Tuple
+ from unittest.mock import patch
+
++requires_textual = pytest.mark.skipif(
++ importlib.util.find_spec('textual') is None,
++ reason='requires the [tui] extra (textual)',
++)
++
+
+ class TestAmConflictError:
+ """Tests for the AmConflictError exception class."""
+
+ def test_stores_worktree_path_and_output(self) -> None:
+@@ -252,10 +258,11 @@ class TestGitFetchAmIntoRepo:
+ assert common_dir is not None
+ gwt = os.path.join(common_dir, 'b4-shazam-worktree')
+ assert not os.path.exists(gwt)
+
+
++@requires_textual
+ class TestSuspendToShellCwd:
+ """Test that _suspend_to_shell passes cwd to subprocess.run."""
+
+ @patch('b4.review_tui._common.subprocess.run')
+ def test_cwd_passed_through(self, mock_run: Any,
+--
+2.55.0
+
diff --git a/0004-tests-test_patatt-Skip-when-patatt-is-not-installed.patch b/0004-tests-test_patatt-Skip-when-patatt-is-not-installed.patch
new file mode 100644
index 0000000..9f2c900
--- /dev/null
+++ b/0004-tests-test_patatt-Skip-when-patatt-is-not-installed.patch
@@ -0,0 +1,37 @@
+From efb45715060329f6f9aa09e04f0594138a75d877 Mon Sep 17 00:00:00 2001
+From: Maxime Ripard <mripard@kernel.org>
+Date: Tue, 15 Sep 2026 15:58:04 +0000
+Subject: [PATCH] tests: test_patatt: Skip when patatt is not installed
+
+The ELN build root does not include patatt: the spec strips it (along
+with dnspython and dkimpy) from the dependencies when building without
+the attest condition. Without a guard, test collection fails on `import
+patatt` before any test can run.
+
+Add a pytest.importorskip('patatt') call so the module skips cleanly.
+
+Signed-off-by: Maxime Ripard <mripard@kernel.org>
+---
+ src/tests/test_patatt.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/tests/test_patatt.py b/src/tests/test_patatt.py
+index 6a856995319a..b322245b8e24 100644
+--- a/src/tests/test_patatt.py
++++ b/src/tests/test_patatt.py
+@@ -9,10 +9,12 @@ import tempfile
+ from collections.abc import Generator
+ from typing import Tuple
+
+ import pytest
+
++pytest.importorskip('patatt')
++
+ import b4
+ import patatt
+
+ from nacl.signing import SigningKey
+
+--
+2.55.0
+
diff --git a/0005-b4-Drop-shebang-lines-from-library-modules.patch b/0005-b4-Drop-shebang-lines-from-library-modules.patch
new file mode 100644
index 0000000..cf19942
--- /dev/null
+++ b/0005-b4-Drop-shebang-lines-from-library-modules.patch
@@ -0,0 +1,247 @@
+From bd6ceb53928344ab1155c984e8c16ce52ce7d4a2 Mon Sep 17 00:00:00 2001
+From: Maxime Ripard <mripard@kernel.org>
+Date: Wed, 16 Sep 2026 08:55:24 +0200
+Subject: [PATCH] b4: Drop shebang lines from library modules
+
+rpmlint reports non-executable-script errors on several Python modules.
+These files are installed as regular library modules with 0644
+permissions, so the shebang line is unnecessary and triggers the
+diagnostic.
+
+Remove the shebang from all modules.
+
+Signed-off-by: Maxime Ripard <mripard@kernel.org>
+---
+ src/b4/command.py | 1 -
+ src/b4/diff.py | 1 -
+ src/b4/dig.py | 1 -
+ src/b4/ez.py | 1 -
+ src/b4/kr.py | 1 -
+ src/b4/mbox.py | 1 -
+ src/b4/pr.py | 1 -
+ src/b4/review/_review.py | 1 -
+ src/b4/review/checks.py | 1 -
+ src/b4/review/messages.py | 1 -
+ src/b4/review/tracking.py | 1 -
+ src/b4/review_tui/_common.py | 1 -
+ src/b4/review_tui/_entry.py | 1 -
+ src/b4/review_tui/_lite_app.py | 1 -
+ src/b4/review_tui/_modals.py | 1 -
+ src/b4/review_tui/_pw_app.py | 1 -
+ src/b4/review_tui/_review_app.py | 1 -
+ src/b4/review_tui/_tracking_app.py | 1 -
+ src/b4/ty.py | 1 -
+ 19 files changed, 19 deletions(-)
+
+diff --git a/src/b4/command.py b/src/b4/command.py
+index ca7f23875162..ac816e29bcbd 100644
+--- a/src/b4/command.py
++++ b/src/b4/command.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/diff.py b/src/b4/diff.py
+index 934b9ac2e8c7..aa4af4e770f6 100644
+--- a/src/b4/diff.py
++++ b/src/b4/diff.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/dig.py b/src/b4/dig.py
+index f13deac263f8..35816fc023d3 100644
+--- a/src/b4/dig.py
++++ b/src/b4/dig.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2025 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/ez.py b/src/b4/ez.py
+index b7afab52a14d..c34b2a4090bc 100644
+--- a/src/b4/ez.py
++++ b/src/b4/ez.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/kr.py b/src/b4/kr.py
+index 5ed9867793cb..ae48d06b934e 100644
+--- a/src/b4/kr.py
++++ b/src/b4/kr.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020-2021 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/mbox.py b/src/b4/mbox.py
+index 2164fcc4fd54..3a24935d5a55 100644
+--- a/src/b4/mbox.py
++++ b/src/b4/mbox.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/pr.py b/src/b4/pr.py
+index 5969a0d9b765..6c9ff0ce0517 100644
+--- a/src/b4/pr.py
++++ b/src/b4/pr.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review/_review.py b/src/b4/review/_review.py
+index 4e1610826aa9..973b4ecad026 100644
+--- a/src/b4/review/_review.py
++++ b/src/b4/review/_review.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review/checks.py b/src/b4/review/checks.py
+index 65ee0ca9948f..60d7179f4c58 100644
+--- a/src/b4/review/checks.py
++++ b/src/b4/review/checks.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review/messages.py b/src/b4/review/messages.py
+index 344d36baaeab..797b37b5477e 100644
+--- a/src/b4/review/messages.py
++++ b/src/b4/review/messages.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review/tracking.py b/src/b4/review/tracking.py
+index 63af6e6571b8..94f22e428538 100644
+--- a/src/b4/review/tracking.py
++++ b/src/b4/review/tracking.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_common.py b/src/b4/review_tui/_common.py
+index 3a370e145e6f..5b3b20909005 100644
+--- a/src/b4/review_tui/_common.py
++++ b/src/b4/review_tui/_common.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_entry.py b/src/b4/review_tui/_entry.py
+index e3560d132910..b746a41fbb47 100644
+--- a/src/b4/review_tui/_entry.py
++++ b/src/b4/review_tui/_entry.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_lite_app.py b/src/b4/review_tui/_lite_app.py
+index ededfd3c7077..b6b633efd4d4 100644
+--- a/src/b4/review_tui/_lite_app.py
++++ b/src/b4/review_tui/_lite_app.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_modals.py b/src/b4/review_tui/_modals.py
+index d592b9f98f90..589ac8d000d6 100644
+--- a/src/b4/review_tui/_modals.py
++++ b/src/b4/review_tui/_modals.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_pw_app.py b/src/b4/review_tui/_pw_app.py
+index cfc0b115cb1c..601a874839bd 100644
+--- a/src/b4/review_tui/_pw_app.py
++++ b/src/b4/review_tui/_pw_app.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_review_app.py b/src/b4/review_tui/_review_app.py
+index 3caa0126b31a..a12d499bf8de 100644
+--- a/src/b4/review_tui/_review_app.py
++++ b/src/b4/review_tui/_review_app.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/review_tui/_tracking_app.py b/src/b4/review_tui/_tracking_app.py
+index 9fcb787d85fd..878eda80155d 100644
+--- a/src/b4/review_tui/_tracking_app.py
++++ b/src/b4/review_tui/_tracking_app.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2024 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+diff --git a/src/b4/ty.py b/src/b4/ty.py
+index 58caebd92cad..037e26bfe1cb 100644
+--- a/src/b4/ty.py
++++ b/src/b4/ty.py
+@@ -1,6 +1,5 @@
+-#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # SPDX-License-Identifier: GPL-2.0-or-later
+ # Copyright (C) 2020 by the Linux Foundation
+ #
+ __author__ = 'Konstantin Ryabitsev <konstantin@linuxfoundation.org>'
+--
+2.55.0
+
diff --git a/python-b4.spec b/python-b4.spec
index ba4c659..e654e98 100644
--- a/python-b4.spec
+++ b/python-b4.spec
@@ -8,7 +8,7 @@
%endif
Name: python-%{srcname}
-Version: 0.14.3
+Version: 0.15.2
Release: %autorelease
Summary: A helper tool to work with public-inbox and patch series
License: GPL-2.0-or-later
@@ -17,6 +17,11 @@ Source0: https://mirrors.edge.kernel.org/pub/software/devel/%{srcname}/%{
Source1: https://mirrors.edge.kernel.org/pub/software/devel/%{srcname}/%{srcname}-%{version}.tar.sign
# https://git.kernel.org/pub/scm/utils/b4/b4.git/plain/.keys/openpgp/linuxfoundation.org/konstantin/default
Source2: gpgkey-DE0E66E32F1FDD0902666B96E63EDCA9329DD07E.asc
+Patch0: 0001-Make-TUI-packages-importable-without-the-textual-ext.patch
+Patch1: 0002-tests-guard-optional-dependency-imports-so-the-suite.patch
+Patch2: 0003-tests-three_way_merge-Skip-TestSuspendToShellCwd-wit.patch
+Patch3: 0004-tests-test_patatt-Skip-when-patatt-is-not-installed.patch
+Patch4: 0005-b4-Drop-shebang-lines-from-library-modules.patch
BuildArch: noarch
@@ -46,12 +51,12 @@ xz -dc '%{SOURCE0}' | %{gpgverify} --keyring='%{SOURCE2}' --signature='%{SOURCE1
# Disable attestation (only applicable to EPEL)
%if %{without attest}
-sed -Ei -e "/^ *'?(dnspython|dkimpy|patatt)/d" requirements.in
+sed -Ei -e "/^ *\"?(dnspython|dkimpy|patatt)/d" pyproject.toml
%endif
%generate_buildrequires
-%pyproject_buildrequires -r requirements.in
+%pyproject_buildrequires -p
%build
@@ -63,7 +68,7 @@ misc/tc-generate.sh zsh > b4-completion-zsh
%install
%pyproject_install
%pyproject_save_files %{srcname}
-install -m644 -Dt %{buildroot}%{_mandir}/man5/ src/b4/man/b4.5
+install -m644 -Dt %{buildroot}%{_mandir}/man1/ src/b4/man/b4.1
install -m644 -D b4-completion-bash %{buildroot}%{_datadir}/bash-completion/completions/b4
install -m644 -D b4-completion-zsh %{buildroot}%{_datadir}/zsh/site-functions/_b4
@@ -76,7 +81,7 @@ install -m644 -D b4-completion-zsh %{buildroot}%{_datadir}/zsh/site-functions/_b
%files -n %{srcname} -f %{pyproject_files}
%doc README.rst
%{_bindir}/%{srcname}
-%{_mandir}/man5/%{srcname}.5.*
+%{_mandir}/man1/%{srcname}.1.*
%{_datadir}/bash-completion/completions/b4
%{_datadir}/zsh/site-functions/_b4
diff --git a/sources b/sources
index 2d20a78..9bf2aaa 100644
--- a/sources
+++ b/sources
@@ -1,2 +1,2 @@
-SHA512 (b4-0.14.3.tar.xz) = e2189e5125ea77909014ae9df1b4bcea4aa6f2bbf4c0f9748cccfb4d8048327d0e6a4794114202a15ed5874b275c67db133be9acc287d259a885baa068eb59b8
-SHA512 (b4-0.14.3.tar.sign) = d6823cb0a4ddb17e4cf8f2586913ec8f4341607b442f98f0b3c0f621f91ccd00a8e27b4513760fd8f5c8594a1b36683dd70a6fbe8196127004eebe4ffac46e15
+SHA512 (b4-0.15.2.tar.sign) = d9c1f892c4cb52b3d133fa871211576f78de18dba70c3c91732dd6030b9a251fe2461e5309bb95a7bdd1d61fd5f64c3ee460f57675ce374a7c61f2ce6f2cd71e
+SHA512 (b4-0.15.2.tar.xz) = 8e84b05a15288d6325ed94542bc2408c33fb27e0593f5a4bcd4a42508ed078b8776e25d7e03fb62b37b4bc9818088aab1e18c854ccfa48ca798361934f74cddf
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-23 7:37 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 7:37 [rpms/python-b4] rawhide: Update to 0.15.2 Maxime Ripard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox