From db1a2cfe2d9b6dd88fe34195badaffd89e24610b Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Thu, 27 Aug 2026 15:35:34 +0200 Subject: [PATCH 1/3] fix: wait for git daemon to listen before connecting Properly wait instead of hardcoding a short sleep. Fixes #1676 --- test/lib/helper.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/lib/helper.py b/test/lib/helper.py index 58923eaef..4135fe5dd 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -32,6 +32,7 @@ import os import os.path as osp from stat import S_ISLNK, ST_MODE +import socket import subprocess import sys import tempfile @@ -218,8 +219,15 @@ def git_daemon_launched(base_path, ip, port): base_path=base_path, as_process=True, ) - # Yes, I know... fortunately, this is always going to work if sleep time is just large enough. - time.sleep(1.0 if sys.platform == "win32" else 0.5) + + # Wait until git daemon listens for connections. + for _attempt in range(1, 30): + try: + socket.create_connection((ip, port), timeout=30).close() + break + except ConnectionRefusedError: + time.sleep(0.5) + except Exception as ex: msg = textwrap.dedent( """ From bc63b027e2fc737e2ec0222bfa764e1aa76a427f Mon Sep 17 00:00:00 2001 From: Byron Date: Fri, 28 Aug 2026 08:37:50 +0200 Subject: [PATCH 2/3] fix: restore Actor.name_email_regex (#2220) GitPython 3.1.60 removed the Actor.name_email_regex class attribute while replacing internal actor parsing, which broke downstream consumers such as python-semantic-release. Bring back the historical compiled pattern for downstream compatibility, but emit DeprecationWarning whenever the class member is accessed. The warning explains its quadratic behavior on long malformed strings and points callers to the public Actor(name, email) constructor when fields are separate, or direct string parsing when they are not. Assisted-by: GPT 5.6 Co-authored-by: GPT 5.6 # 252a84ac Add public Actor.from_string constructor (#2220) Add public Actor.from_string constructor (#2220) Promote the existing actor identity parser to Actor.from_string while retaining _from_string as a compatibility alias. Point the name_email_regex deprecation warning to the new public constructor or Actor(name, email). Validation: - actor-related test/test_actor.py and test/test_util.py cases: 12 passed - ruff check and format checks passed - basedpyright git/util.py: no errors Co-authored-by: Codex GPT-5 # dd2181d8 Address review feedback about Actor subclasses Address review feedback about Actor subclasses Review feedback: Actor.from_string() should preserve the class on which the public classmethod is called instead of always returning a base Actor. Construct through cls on both parsing paths and cover subclass construction directly. Validation: - actor-related test/test_actor.py and test/test_util.py cases: 13 passed - ruff check and format checks passed - basedpyright git/util.py: no errors # fcf1ecb1 Address review feedback about constructor typing Address review feedback about constructor typing Review feedback: Actor.from_string() constructs subclasses at runtime but its Actor return annotation prevents type checkers from preserving subclass-specific members. Use the project existing Python 3.7-compatible bound TypeVar pattern so the declared return type follows cls. Validation: - actor-related test/test_actor.py and test/test_util.py cases: 13 passed - ruff check and format checks passed - basedpyright git/util.py: no errors Co-authored-by: Codex GPT-5 --- git/util.py | 26 +++++++++++++++++++++++--- test/test_actor.py | 35 ++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/git/util.py b/git/util.py index b0593feea..a80e667c7 100644 --- a/git/util.py +++ b/git/util.py @@ -80,6 +80,7 @@ Sequence, Tuple, TYPE_CHECKING, + Type, TypeVar, Union, cast, @@ -108,6 +109,7 @@ T_IterableObj = TypeVar("T_IterableObj", bound=Union["IterableObj", "Has_id_attribute"], covariant=True) # So IterableList[Head] is subtype of IterableList[IterableObj]. +T_Actor = TypeVar("T_Actor", bound="Actor") _logger = logging.getLogger(__name__) @@ -853,11 +855,27 @@ def update(self, *args: Any, **kwargs: Any) -> None: self._callable(*args, **kwargs) +class _DeprecatedActorNameEmailRegex: + _pattern = re.compile(r"(.*) <(.*?)>") + + def __get__(self, _instance: Any, _owner: Any) -> Pattern[str]: + warnings.warn( + "Actor.name_email_regex is deprecated and will be removed in GitPython 4.0.0 because searching long " + "malformed strings with it can take quadratic time. Use Actor.from_string() to parse actor identities, " + "or Actor(name, email) when the fields are already separate.", + DeprecationWarning, + stacklevel=2, + ) + return self._pattern + + class Actor: """Actors hold information about a person acting on the repository. They can be committers and authors or anything with a name and an email as mentioned in the git log entries.""" + name_email_regex = _DeprecatedActorNameEmailRegex() + # ENVIRONMENT VARIABLES # These are read when creating new commits. env_author_name = "GIT_AUTHOR_NAME" @@ -891,7 +909,7 @@ def __repr__(self) -> str: return '">' % (self.name, self.email) @classmethod - def _from_string(cls, string: str) -> "Actor": + def from_string(cls: Type[T_Actor], string: str) -> T_Actor: """Create an :class:`Actor` from a string. :param string: @@ -906,10 +924,12 @@ def _from_string(cls, string: str) -> "Actor": left_bracket = line.find("<") right_bracket = line.find(">", left_bracket + 1) if left_bracket >= 0 and right_bracket >= 0: - return Actor(line[:left_bracket].rstrip(), line[left_bracket + 1 : right_bracket]) + return cls(line[:left_bracket].rstrip(), line[left_bracket + 1 : right_bracket]) # Assume the best and use the whole string as name. - return Actor(string, None) + return cls(string, None) + + _from_string = from_string @classmethod def _main_actor( diff --git a/test/test_actor.py b/test/test_actor.py index baf6545f1..68afb80d3 100644 --- a/test/test_actor.py +++ b/test/test_actor.py @@ -3,14 +3,15 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -from git import Actor +from unittest import mock +from git import Actor from test.lib import TestBase class TestActor(TestBase): def test_from_string_should_separate_name_and_email(self): - a = Actor._from_string("Michael Trier ") + a = Actor.from_string("Michael Trier ") self.assertEqual("Michael Trier", a.name) self.assertEqual("mtrier@example.com", a.email) @@ -23,18 +24,34 @@ def test_from_string_should_separate_name_and_email(self): assert len(m) == 1 def test_from_string_should_handle_just_name(self): - a = Actor._from_string("Michael Trier") + a = Actor.from_string("Michael Trier") self.assertEqual("Michael Trier", a.name) self.assertEqual(None, a.email) + def test_from_string_constructs_subclass(self): + class DerivedActor(Actor): + pass + + self.assertIsInstance(DerivedActor.from_string("name "), DerivedActor) + def test_from_string_handles_unterminated_email_without_regex_backtracking(self): value = "A" * 20_000 + " ") + + message = str(context.warning) + self.assertIn("Actor.from_string()", message) + self.assertIn("Actor(name, email)", message) + assert match is not None + self.assertEqual(match.groups(), ("Michael Trier", "mtrier@example.com")) + def test_from_string_does_not_parse_across_lines(self): - self.assertEqual(Actor._from_string("x \n y "), Actor("x", "a")) + self.assertEqual(Actor.from_string("x \n y "), Actor("x", "a")) def test_from_string_uses_git_delimiters(self): for value, expected in ( @@ -45,12 +62,12 @@ def test_from_string_uses_git_delimiters(self): ("Name ", Actor("Name email>", None)), ): - self.assertEqual(Actor._from_string(value), expected) + self.assertEqual(Actor.from_string(value), expected) def test_should_display_representation(self): - a = Actor._from_string("Michael Trier ") + a = Actor.from_string("Michael Trier ") self.assertEqual('">', repr(a)) def test_str_should_alias_name(self): - a = Actor._from_string("Michael Trier ") + a = Actor.from_string("Michael Trier ") self.assertEqual(a.name, str(a)) From 5346d1b587a790bac4dc4f100a28fae9db0fd7c8 Mon Sep 17 00:00:00 2001 From: Byron Date: Fri, 28 Aug 2026 13:00:28 +0200 Subject: [PATCH 3/3] prepare next release --- VERSION | 2 +- doc/source/changes.rst | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 17f8e2fbb..c29b32b56 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.60 +3.1.61 diff --git a/doc/source/changes.rst b/doc/source/changes.rst index dcdc6d0c5..6b06dd5bf 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,18 @@ Changelog ========= +3.1.61 +====== + +A fixup release to avoid accidental removal of public class regex on Actor. +It's now deprecated instead. + +If you can, also try and provide feedback on the upcoming v4 branch +https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome. + +See the following for all changes. +https://github.com/gitpython-developers/GitPython/releases/tag/3.1.61 + 3.1.60 ======