diff --git a/git_py_stats/git_operations.py b/git_py_stats/git_operations.py index 80f0a4e..b70620d 100644 --- a/git_py_stats/git_operations.py +++ b/git_py_stats/git_operations.py @@ -20,8 +20,17 @@ def run_git_command(cmd: List[str]) -> Optional[str]: print("Error: Command list is empty!") return None try: + # We enforce utf-8 encoding and use surrogateescape to handle non-ascii characters + # that may be present in git history (e.g. in author names or commit messages) + # as recommended by PEP 383 for system interfaces. result = subprocess.run( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + encoding="utf-8", + errors="surrogateescape", ) return result.stdout.strip() except subprocess.CalledProcessError as e: diff --git a/git_py_stats/tests/test_git_operations.py b/git_py_stats/tests/test_git_operations.py index 56e3cbd..0d8c442 100644 --- a/git_py_stats/tests/test_git_operations.py +++ b/git_py_stats/tests/test_git_operations.py @@ -31,6 +31,8 @@ def test_run_git_command_success(self, mock_subprocess_run): stderr=subprocess.PIPE, text=True, check=True, + encoding="utf-8", + errors="surrogateescape", ) @patch("subprocess.run") @@ -54,6 +56,8 @@ def test_run_git_command_failure(self, mock_subprocess_run): stderr=subprocess.PIPE, text=True, check=True, + encoding="utf-8", + errors="surrogateescape", ) @patch("subprocess.run") @@ -72,7 +76,13 @@ def test_run_git_command_no_output(self, mock_subprocess_run): self.assertEqual(output, "") mock_subprocess_run.assert_called_once_with( - ["git", "status"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True + ["git", "status"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + encoding="utf-8", + errors="surrogateescape", ) @patch("subprocess.run") @@ -87,7 +97,13 @@ def test_run_git_command_exception(self, mock_subprocess_run): self.assertIsNone(output) mock_subprocess_run.assert_called_once_with( - ["git", "status"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True + ["git", "status"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + encoding="utf-8", + errors="surrogateescape", ) def test_run_git_command_empty_command(self):