From 146cbdaffdd1b551e6689f162e26226d5a351d6e Mon Sep 17 00:00:00 2001 From: Twist Date: Mon, 22 Aug 2022 18:00:37 +0100 Subject: [PATCH 1/6] Add co_authors property to the Commit object, which parses the commit message for designated co-authors, include a simple test. --- AUTHORS | 1 + git/objects/commit.py | 22 ++++++++++++++++++++++ test/test_commit.py | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/AUTHORS b/AUTHORS index ef414e316..97e147892 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,4 +48,5 @@ Contributors are: -Hiroki Tokunaga -Julien Mauroy -Patrick Gerard +-Luke Twist Portions derived from other open source works and are clearly marked. diff --git a/git/objects/commit.py b/git/objects/commit.py index 66cb91918..65c94f238 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -4,6 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php import datetime +import re from subprocess import Popen, PIPE from gitdb import IStream from git.util import hex_to_bin, Actor, Stats, finalize_process @@ -738,3 +739,24 @@ def _deserialize(self, stream: BytesIO) -> "Commit": return self # } END serializable implementation + + @property + def co_authors(self) -> List[Actor]: + """ + Search the commit message for any co-authors of this commit. + Details on co-authors: https://github.blog/2018-01-29-commit-together-with-co-authors/ + + :return: List of co-authors for this commit (as Actor objects). + """ + co_authors = [] + + if self.message: + results = re.findall( + r"^Co-authored-by: ((?:\w|\-| ){0,38}) <(\S*)>$", + self.message, + re.MULTILINE, + ) + for author in results: + co_authors.append(Actor(*author)) + + return co_authors diff --git a/test/test_commit.py b/test/test_commit.py index 821269878..cf8e1db63 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -509,3 +509,14 @@ def test_trailers(self): assert KEY_1 not in commit.trailers.keys() assert KEY_2 in commit.trailers.keys() assert commit.trailers[KEY_2] == VALUE_2 + + def test_commit_co_authors(self): + commit = copy.copy(self.rorepo.commit("4251bd5")) + commit.message = """Commit message + +Co-authored-by: Test User 1 <602352+test@users.noreply.github.com> +Co-authored-by: test_user_2 """ + assert commit.co_authors == [ + Actor("Test User 1", "602352+test@users.noreply.github.com"), + Actor("test_user_2", "another_user-email@.github.com"), + ] From 3cb7ecf4e03c599d9e6f0b2416082025d3fa849a Mon Sep 17 00:00:00 2001 From: Twist Date: Tue, 23 Aug 2022 19:50:00 +0100 Subject: [PATCH 2/6] Add malformed co-authors to the test, to check they aren't detected with the regex. --- test/test_commit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_commit.py b/test/test_commit.py index cf8e1db63..a08ac39b4 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -515,8 +515,12 @@ def test_commit_co_authors(self): commit.message = """Commit message Co-authored-by: Test User 1 <602352+test@users.noreply.github.com> -Co-authored-by: test_user_2 """ +Co-authored-by: test_user_2 +Co_authored_by: test_user_x +Co-authored-by: test_user_y +Co-authored-by: test_user_3 """ assert commit.co_authors == [ Actor("Test User 1", "602352+test@users.noreply.github.com"), - Actor("test_user_2", "another_user-email@.github.com"), + Actor("test_user_2", "another_user-email@github.com"), + Actor("test_user_3", "test_user_3@github.com"), ] From c2fd97e374f9c1187f165b18651928c011e6f041 Mon Sep 17 00:00:00 2001 From: Twist Date: Tue, 23 Aug 2022 19:52:11 +0100 Subject: [PATCH 3/6] Update regex to extract the author string, and create the Actor using the _from_string classmethod. --- git/objects/commit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 65c94f238..58f0bde72 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -752,11 +752,11 @@ def co_authors(self) -> List[Actor]: if self.message: results = re.findall( - r"^Co-authored-by: ((?:\w|\-| ){0,38}) <(\S*)>$", + r"^Co-authored-by: ((?:\w|\-| ){0,38} <\S*>)$", self.message, re.MULTILINE, ) - for author in results: - co_authors.append(Actor(*author)) + for author_string in results: + co_authors.append(Actor._from_string(author_string)) return co_authors From 09f8a1b7b674d6138b872643b13ffc5444fab24f Mon Sep 17 00:00:00 2001 From: Twist Date: Wed, 24 Aug 2022 19:05:45 +0100 Subject: [PATCH 4/6] Use the same regex as the Actor class when determining co-authors. --- git/objects/commit.py | 6 +++--- test/test_commit.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 58f0bde72..cf7d9aaa2 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -752,11 +752,11 @@ def co_authors(self) -> List[Actor]: if self.message: results = re.findall( - r"^Co-authored-by: ((?:\w|\-| ){0,38} <\S*>)$", + r"^Co-authored-by: (.*) <(.*?)>$", self.message, re.MULTILINE, ) - for author_string in results: - co_authors.append(Actor._from_string(author_string)) + for author in results: + co_authors.append(Actor(*author)) return co_authors diff --git a/test/test_commit.py b/test/test_commit.py index a08ac39b4..c5a43c94a 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -517,7 +517,7 @@ def test_commit_co_authors(self): Co-authored-by: Test User 1 <602352+test@users.noreply.github.com> Co-authored-by: test_user_2 Co_authored_by: test_user_x -Co-authored-by: test_user_y +Co-authored-by: test_user_y text Co-authored-by: test_user_3 """ assert commit.co_authors == [ Actor("Test User 1", "602352+test@users.noreply.github.com"), From 4c460a3909a5df54fdae66fef6bb8ccbbe0c51d4 Mon Sep 17 00:00:00 2001 From: Twist Date: Wed, 24 Aug 2022 19:06:01 +0100 Subject: [PATCH 5/6] Ignore flake8 error. --- git/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/config.py b/git/config.py index 5f07cb002..71d7ea689 100644 --- a/git/config.py +++ b/git/config.py @@ -84,7 +84,7 @@ CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"") -class MetaParserBuilder(abc.ABCMeta): +class MetaParserBuilder(abc.ABCMeta): # noqa: B024 """Utility class wrapping base-class methods into decorators that assure read-only properties""" def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> "MetaParserBuilder": From 72cf71cb3e9d0458dc27158ecb67d8dd4f26af04 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 25 Aug 2022 09:28:54 +0800 Subject: [PATCH 6/6] Allow failure of that one test on cygwin --- test/test_util.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_util.py b/test/test_util.py index eb0161898..90dd89a91 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -6,11 +6,13 @@ import os import pickle +import sys import tempfile import time from unittest import mock, skipIf from datetime import datetime +import pytest import ddt from git.cmd import dashify @@ -154,6 +156,11 @@ def test_lock_file(self): lock_file._obtain_lock_or_raise() lock_file._release_lock() + @pytest.mark.xfail( + sys.platform == "cygwin", + reason="Cygwin fails here for some reason, always", + raises=AssertionError + ) def test_blocking_lock_file(self): my_file = tempfile.mktemp() lock_file = BlockingLockFile(my_file)