| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,6 +80,7 @@ | |||
| 80 | 80 | Sequence, | |
| 81 | 81 | Tuple, | |
| 82 | 82 | TYPE_CHECKING, | |
| 83 | + Type, | ||
| 83 | 84 | TypeVar, | |
| 84 | 85 | Union, | |
| 85 | 86 | cast, | |
@@ -108,6 +109,7 @@ | |||
| 108 | 109 | ||
| 109 | 110 | T_IterableObj = TypeVar("T_IterableObj", bound=Union["IterableObj", "Has_id_attribute"], covariant=True) | |
| 110 | 111 | # So IterableList[Head] is subtype of IterableList[IterableObj]. | |
| 112 | + T_Actor = TypeVar("T_Actor", bound="Actor") | ||
| 111 | 113 | ||
| 112 | 114 | _logger = logging.getLogger(__name__) | |
| 113 | 115 | ||
@@ -853,11 +855,27 @@ def update(self, *args: Any, **kwargs: Any) -> None: | |||
| 853 | 855 | self._callable(*args, **kwargs) | |
| 854 | 856 | ||
| 855 | 857 | ||
| 858 | + class _DeprecatedActorNameEmailRegex: | ||
| 859 | + _pattern = re.compile(r"(.*) <(.*?)>") | ||
| 860 | + | ||
| 861 | + def __get__(self, _instance: Any, _owner: Any) -> Pattern[str]: | ||
| 862 | + warnings.warn( | ||
| 863 | + "Actor.name_email_regex is deprecated and will be removed in GitPython 4.0.0 because searching long " | ||
| 864 | + "malformed strings with it can take quadratic time. Use Actor.from_string() to parse actor identities, " | ||
| 865 | + "or Actor(name, email) when the fields are already separate.", | ||
| 866 | + DeprecationWarning, | ||
| 867 | + stacklevel=2, | ||
| 868 | + ) | ||
| 869 | + return self._pattern | ||
| 870 | + | ||
| 871 | + | ||
| 856 | 872 | class Actor: | |
| 857 | 873 | """Actors hold information about a person acting on the repository. They can be | |
| 858 | 874 | committers and authors or anything with a name and an email as mentioned in the git | |
| 859 | 875 | log entries.""" | |
| 860 | 876 | ||
| 877 | + name_email_regex = _DeprecatedActorNameEmailRegex() | ||
| 878 | + | ||
| 861 | 879 | # ENVIRONMENT VARIABLES | |
| 862 | 880 | # These are read when creating new commits. | |
| 863 | 881 | env_author_name = "GIT_AUTHOR_NAME" | |
@@ -891,7 +909,7 @@ def __repr__(self) -> str: | |||
| 891 | 909 | return '<git.Actor "%s <%s>">' % (self.name, self.email) | |
| 892 | 910 | ||
| 893 | 911 | @classmethod | |
| 894 | - def _from_string(cls, string: str) -> "Actor": | ||
| 912 | + def from_string(cls: Type[T_Actor], string: str) -> T_Actor: | ||
| 895 | 913 | """Create an :class:`Actor` from a string. | |
| 896 | 914 | ||
| 897 | 915 | :param string: | |
@@ -906,10 +924,12 @@ def _from_string(cls, string: str) -> "Actor": | |||
| 906 | 924 | left_bracket = line.find("<") | |
| 907 | 925 | right_bracket = line.find(">", left_bracket + 1) | |
| 908 | 926 | if left_bracket >= 0 and right_bracket >= 0: | |
| 909 | - return Actor(line[:left_bracket].rstrip(), line[left_bracket + 1 : right_bracket]) | ||
| 927 | + return cls(line[:left_bracket].rstrip(), line[left_bracket + 1 : right_bracket]) | ||
| 910 | 928 | ||
| 911 | 929 | # Assume the best and use the whole string as name. | |
| 912 | - return Actor(string, None) | ||
| 930 | + return cls(string, None) | ||
| 931 | + | ||
| 932 | + _from_string = from_string | ||
| 913 | 933 | ||
| 914 | 934 | @classmethod | |
| 915 | 935 | def _main_actor( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,14 +3,15 @@ | |||
| 3 | 3 | # This module is part of GitPython and is released under the | |
| 4 | 4 | # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ | |
| 5 | 5 | ||
| 6 | - from git import Actor | ||
| 6 | + from unittest import mock | ||
| 7 | 7 | ||
| 8 | + from git import Actor | ||
| 8 | 9 | from test.lib import TestBase | |
| 9 | 10 | ||
| 10 | 11 | ||
| 11 | 12 | class TestActor(TestBase): | |
| 12 | 13 | def test_from_string_should_separate_name_and_email(self): | |
| 13 | - a = Actor._from_string("Michael Trier <mtrier@example.com>") | ||
| 14 | + a = Actor.from_string("Michael Trier <mtrier@example.com>") | ||
| 14 | 15 | self.assertEqual("Michael Trier", a.name) | |
| 15 | 16 | self.assertEqual("mtrier@example.com", a.email) | |
| 16 | 17 | ||
@@ -23,18 +24,34 @@ def test_from_string_should_separate_name_and_email(self): | |||
| 23 | 24 | assert len(m) == 1 | |
| 24 | 25 | ||
| 25 | 26 | def test_from_string_should_handle_just_name(self): | |
| 26 | - a = Actor._from_string("Michael Trier") | ||
| 27 | + a = Actor.from_string("Michael Trier") | ||
| 27 | 28 | self.assertEqual("Michael Trier", a.name) | |
| 28 | 29 | self.assertEqual(None, a.email) | |
| 29 | 30 | ||
| 31 | + def test_from_string_constructs_subclass(self): | ||
| 32 | + class DerivedActor(Actor): | ||
| 33 | + pass | ||
| 34 | + | ||
| 35 | + self.assertIsInstance(DerivedActor.from_string("name <email>"), DerivedActor) | ||
| 36 | + | ||
| 30 | 37 | def test_from_string_handles_unterminated_email_without_regex_backtracking(self): | |
| 31 | 38 | value = "A" * 20_000 + " <unterminated" | |
| 32 | - actor = Actor._from_string(value) | ||
| 33 | - self.assertNotIn("name_email_regex", vars(Actor)) | ||
| 39 | + with mock.patch.object(Actor, "name_email_regex", None): | ||
| 40 | + actor = Actor.from_string(value) | ||
| 34 | 41 | self.assertEqual(actor, Actor(value, None)) | |
| 35 | 42 | ||
| 43 | + def test_name_email_regex_is_available_but_deprecated(self): | ||
| 44 | + with self.assertWarns(DeprecationWarning) as context: | ||
| 45 | + match = Actor.name_email_regex.match("Michael Trier <mtrier@example.com>") | ||
| 46 | + | ||
| 47 | + message = str(context.warning) | ||
| 48 | + self.assertIn("Actor.from_string()", message) | ||
| 49 | + self.assertIn("Actor(name, email)", message) | ||
| 50 | + assert match is not None | ||
| 51 | + self.assertEqual(match.groups(), ("Michael Trier", "mtrier@example.com")) | ||
| 52 | + | ||
| 36 | 53 | def test_from_string_does_not_parse_across_lines(self): | |
| 37 | - self.assertEqual(Actor._from_string("x <a>\n y <b>"), Actor("x", "a")) | ||
| 54 | + self.assertEqual(Actor.from_string("x <a>\n y <b>"), Actor("x", "a")) | ||
| 38 | 55 | ||
| 39 | 56 | def test_from_string_uses_git_delimiters(self): | |
| 40 | 57 | for value, expected in ( | |
@@ -45,12 +62,12 @@ def test_from_string_uses_git_delimiters(self): | |||
| 45 | 62 | ("Name <email", Actor("Name <email", None)), | |
| 46 | 63 | ("Name email>", Actor("Name email>", None)), | |
| 47 | 64 | ): | |
| 48 | - self.assertEqual(Actor._from_string(value), expected) | ||
| 65 | + self.assertEqual(Actor.from_string(value), expected) | ||
| 49 | 66 | ||
| 50 | 67 | def test_should_display_representation(self): | |
| 51 | - a = Actor._from_string("Michael Trier <mtrier@example.com>") | ||
| 68 | + a = Actor.from_string("Michael Trier <mtrier@example.com>") | ||
| 52 | 69 | self.assertEqual('<git.Actor "Michael Trier <mtrier@example.com>">', repr(a)) | |
| 53 | 70 | ||
| 54 | 71 | def test_str_should_alias_name(self): | |
| 55 | - a = Actor._from_string("Michael Trier <mtrier@example.com>") | ||
| 72 | + a = Actor.from_string("Michael Trier <mtrier@example.com>") | ||
| 56 | 73 | self.assertEqual(a.name, str(a)) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments