From bd58716966ffb231f96aeada76c5159d5b4f9beb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:46:53 +0000 Subject: [PATCH 1/6] Use consistent interpret-trailers encoding Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/1a855cb6-0111-4f52-b48d-46417aec5bde Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- git/objects/commit.py | 31 ++++++++++++++----------------- test/test_commit.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 6ea252395..081ccf402 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -450,14 +450,7 @@ def trailers_list(self) -> List[Tuple[str, str]]: :return: List containing key-value tuples of whitespace stripped trailer information. """ - cmd = ["git", "interpret-trailers", "--parse"] - proc: Git.AutoInterrupt = self.repo.git.execute( # type: ignore[call-overload] - cmd, - as_process=True, - istream=PIPE, - ) - trailer: str = proc.communicate(str(self.message).encode())[0].decode("utf8") - trailer = trailer.strip() + trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], self.encoding).strip() if not trailer: return [] @@ -469,6 +462,18 @@ def trailers_list(self) -> List[Tuple[str, str]]: return trailer_list + @staticmethod + def _interpret_trailers(repo: "Repo", message: str, trailer_args: Sequence[str], encoding: str) -> str: + cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] + proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] + cmd, + as_process=True, + istream=PIPE, + ) + stdout_bytes, _ = proc.communicate(message.encode(encoding, errors="strict")) + finalize_process(proc) + return stdout_bytes.decode(encoding, errors="strict") + @property def trailers_dict(self) -> Dict[str, List[str]]: """Get the trailers of the message as a dictionary. @@ -699,15 +704,7 @@ def create_from_tree( trailer_args.append("--trailer") trailer_args.append(f"{key}: {val}") - cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers"] + trailer_args - proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] - cmd, - as_process=True, - istream=PIPE, - ) - stdout_bytes, _ = proc.communicate(str(message).encode()) - finalize_process(proc) - message = stdout_bytes.decode("utf8") + message = cls._interpret_trailers(repo, str(message), trailer_args, conf_encoding) # END apply trailers # CREATE NEW COMMIT diff --git a/test/test_commit.py b/test/test_commit.py index 11308cbdb..5ea6642c0 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -622,6 +622,31 @@ def test_create_from_tree_with_trailers_list(self, rw_dir): "Issue": ["456"], } + @with_rw_directory + def test_create_from_tree_with_non_utf8_trailers(self, rw_dir): + """Test that trailer creation and parsing respect the configured commit encoding.""" + rw_repo = Repo.init(osp.join(rw_dir, "test_trailers_non_utf8")) + with rw_repo.config_writer() as writer: + writer.set_value("i18n", "commitencoding", "ISO-8859-1") + + path = osp.join(str(rw_repo.working_tree_dir), "hello.txt") + touch(path) + rw_repo.index.add([path]) + tree = rw_repo.index.write_tree() + + commit = Commit.create_from_tree( + rw_repo, + tree, + "Résumé", + head=True, + trailers={"Reviewed-by": "André "}, + ) + + assert commit.encoding == "ISO-8859-1" + assert "Résumé" in commit.message + assert "Reviewed-by: André " in commit.message + assert commit.trailers_list == [("Reviewed-by", "André ")] + @with_rw_directory def test_index_commit_with_trailers(self, rw_dir): """Test that IndexFile.commit() supports adding trailers.""" From 7cdf9c7fb5d27dfee1e22fa81fc28d9e538d58a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:47:59 +0000 Subject: [PATCH 2/6] Normalize interpret-trailers subprocess IO Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/1a855cb6-0111-4f52-b48d-46417aec5bde Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- git/objects/commit.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 081ccf402..a8bb5e852 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -450,7 +450,7 @@ def trailers_list(self) -> List[Tuple[str, str]]: :return: List containing key-value tuples of whitespace stripped trailer information. """ - trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], self.encoding).strip() + trailer = self._interpret_trailers(self.repo, self.message, ["--parse"]).strip() if not trailer: return [] @@ -462,17 +462,17 @@ def trailers_list(self) -> List[Tuple[str, str]]: return trailer_list - @staticmethod - def _interpret_trailers(repo: "Repo", message: str, trailer_args: Sequence[str], encoding: str) -> str: + @classmethod + def _interpret_trailers(cls, repo: "Repo", message: str, trailer_args: Sequence[str]) -> str: cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] cmd, as_process=True, istream=PIPE, ) - stdout_bytes, _ = proc.communicate(message.encode(encoding, errors="strict")) + stdout_bytes, _ = proc.communicate(message.encode(cls.default_encoding, errors="strict")) finalize_process(proc) - return stdout_bytes.decode(encoding, errors="strict") + return stdout_bytes.decode(cls.default_encoding, errors="strict") @property def trailers_dict(self) -> Dict[str, List[str]]: @@ -704,7 +704,7 @@ def create_from_tree( trailer_args.append("--trailer") trailer_args.append(f"{key}: {val}") - message = cls._interpret_trailers(repo, str(message), trailer_args, conf_encoding) + message = cls._interpret_trailers(repo, str(message), trailer_args) # END apply trailers # CREATE NEW COMMIT From 1e2a895ef55911b500b28360ee97c37e6678c014 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 23:43:59 +0000 Subject: [PATCH 3/6] Handle mypy CI typing failure Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/3d3e7ffc-d3af-478e-9c6c-128731cdd102 Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- git/objects/commit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index a8bb5e852..6e5550e37 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -463,14 +463,15 @@ def trailers_list(self) -> List[Tuple[str, str]]: return trailer_list @classmethod - def _interpret_trailers(cls, repo: "Repo", message: str, trailer_args: Sequence[str]) -> str: + def _interpret_trailers(cls, repo: "Repo", message: Union[str, bytes], trailer_args: Sequence[str]) -> str: cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] cmd, as_process=True, istream=PIPE, ) - stdout_bytes, _ = proc.communicate(message.encode(cls.default_encoding, errors="strict")) + message_bytes = message if isinstance(message, bytes) else message.encode(cls.default_encoding, errors="strict") + stdout_bytes, _ = proc.communicate(message_bytes) finalize_process(proc) return stdout_bytes.decode(cls.default_encoding, errors="strict") From 34ec40dc70ab897127184ba88792596fce78d44b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:37:51 +0000 Subject: [PATCH 4/6] Use commit encoding for trailer parsing Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/519084d5-d5e2-4486-a9cc-5c258e596e13 Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- git/objects/commit.py | 14 ++++++++++---- test/test_commit.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 6e5550e37..206b6fcc3 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -450,7 +450,7 @@ def trailers_list(self) -> List[Tuple[str, str]]: :return: List containing key-value tuples of whitespace stripped trailer information. """ - trailer = self._interpret_trailers(self.repo, self.message, ["--parse"]).strip() + trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], encoding=self.encoding).strip() if not trailer: return [] @@ -463,17 +463,23 @@ def trailers_list(self) -> List[Tuple[str, str]]: return trailer_list @classmethod - def _interpret_trailers(cls, repo: "Repo", message: Union[str, bytes], trailer_args: Sequence[str]) -> str: + def _interpret_trailers( + cls, + repo: "Repo", + message: Union[str, bytes], + trailer_args: Sequence[str], + encoding: str = default_encoding, + ) -> str: cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] cmd, as_process=True, istream=PIPE, ) - message_bytes = message if isinstance(message, bytes) else message.encode(cls.default_encoding, errors="strict") + message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict") stdout_bytes, _ = proc.communicate(message_bytes) finalize_process(proc) - return stdout_bytes.decode(cls.default_encoding, errors="strict") + return stdout_bytes.decode(encoding, errors="strict") @property def trailers_dict(self) -> Dict[str, List[str]]: diff --git a/test/test_commit.py b/test/test_commit.py index 5ea6642c0..b3b5f03ec 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -647,6 +647,30 @@ def test_create_from_tree_with_non_utf8_trailers(self, rw_dir): assert "Reviewed-by: André " in commit.message assert commit.trailers_list == [("Reviewed-by", "André ")] + @with_rw_directory + def test_trailers_list_with_non_utf8_message_bytes(self, rw_dir): + """Test that trailer parsing handles non-UTF-8 commit message bytes.""" + rw_repo = Repo.init(osp.join(rw_dir, "test_trailers_non_utf8_bytes")) + with rw_repo.config_writer() as writer: + writer.set_value("i18n", "commitencoding", "ISO-8859-1") + + path = osp.join(str(rw_repo.working_tree_dir), "hello.txt") + touch(path) + rw_repo.index.add([path]) + tree = rw_repo.index.write_tree() + + commit = Commit.create_from_tree( + rw_repo, + tree, + "Résumé", + head=True, + trailers={"Reviewed-by": "André "}, + ) + + commit.message = commit.message.encode(commit.encoding) + + assert commit.trailers_list == [("Reviewed-by", "André ")] + @with_rw_directory def test_index_commit_with_trailers(self, rw_dir): """Test that IndexFile.commit() supports adding trailers.""" From 4aa8157cbb4d70f18f599c35cb358b500a364b59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:39:22 +0000 Subject: [PATCH 5/6] Refine trailer encoding review follow-up Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/519084d5-d5e2-4486-a9cc-5c258e596e13 Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- test/test_commit.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/test_commit.py b/test/test_commit.py index b3b5f03ec..8982db1e3 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -667,9 +667,14 @@ def test_trailers_list_with_non_utf8_message_bytes(self, rw_dir): trailers={"Reviewed-by": "André "}, ) - commit.message = commit.message.encode(commit.encoding) + bytes_commit = Commit( + rw_repo, + commit.binsha, + message=commit.message.encode(commit.encoding), + encoding=commit.encoding, + ) - assert commit.trailers_list == [("Reviewed-by", "André ")] + assert bytes_commit.trailers_list == [("Reviewed-by", "André ")] @with_rw_directory def test_index_commit_with_trailers(self, rw_dir): From 633abdbcd2e897656c998289e48369080a05f600 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 04:23:04 +0000 Subject: [PATCH 6/6] Fix trailer subprocess lifetime Agent-Logs-Url: https://github.com/gitpython-developers/GitPython/sessions/3cc0bd6d-d54d-4299-9a18-1576c2a91c12 Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- git/objects/commit.py | 10 ++++++---- test/test_commit.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 206b6fcc3..da7677ee0 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -470,16 +470,18 @@ def _interpret_trailers( trailer_args: Sequence[str], encoding: str = default_encoding, ) -> str: + message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict") cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args] proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload] cmd, as_process=True, istream=PIPE, ) - message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict") - stdout_bytes, _ = proc.communicate(message_bytes) - finalize_process(proc) - return stdout_bytes.decode(encoding, errors="strict") + try: + stdout_bytes, _ = proc.communicate(message_bytes) + return stdout_bytes.decode(encoding, errors="strict") + finally: + finalize_process(proc) @property def trailers_dict(self) -> Dict[str, List[str]]: diff --git a/test/test_commit.py b/test/test_commit.py index 8982db1e3..b56ad3a18 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -676,6 +676,17 @@ def test_trailers_list_with_non_utf8_message_bytes(self, rw_dir): assert bytes_commit.trailers_list == [("Reviewed-by", "André ")] + def test_interpret_trailers_encodes_before_launching_process(self): + """Test that encoding failures happen before spawning interpret-trailers.""" + repo = Mock() + repo.git = Mock() + repo.git.GIT_PYTHON_GIT_EXECUTABLE = "git" + + with self.assertRaises(UnicodeEncodeError): + Commit._interpret_trailers(repo, "Euro: €", ["--parse"], encoding="ISO-8859-1") + + repo.git.execute.assert_not_called() + @with_rw_directory def test_index_commit_with_trailers(self, rw_dir): """Test that IndexFile.commit() supports adding trailers."""