Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions pr_agent/git_providers/gitea_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,11 +605,11 @@ def get_pr_labels(self,update=False) -> List[str]:

return [label.name for label in labels]

def get_repo_settings(self) -> str:
def get_repo_settings(self) -> bytes:
"""Get repository settings"""
if not self.repo_settings:
self.logger.error("Repository settings not found")
return ""
return b""

response = self.repo_api.get_file_content(
owner=self.owner,
Expand All @@ -619,9 +619,13 @@ def get_repo_settings(self) -> str:
)
if not response:
self.logger.error("Failed to get repository settings")
return ""
return b""

return response
# utils.apply_repo_settings() writes this via os.write() and later
# calls .decode() on it, so it must be bytes to match the GitHub/
# GitLab/Bitbucket contract. get_file_content() decodes the raw bytes
# to str, so re-encode here (see issue #2347).
return response.encode('utf-8')
Comment on lines +624 to +628

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. get_repo_settings() assumes str content πŸ“Ž Requirement gap ☼ Reliability

get_repo_settings() unconditionally calls response.encode('utf-8'), which will raise
AttributeError if get_file_content() (or a mock/SDK change) returns bytes. This violates the
requirement to accept both str and bytes repo settings content for Gitea without type errors.
Agent Prompt
## Issue description
`GiteaProvider.get_repo_settings()` must accept repository settings content returned as either `str` or `bytes`. The current implementation always does `response.encode('utf-8')`, which will crash if `response` is already `bytes`.

## Issue Context
Compliance requires Gitea repo settings loading to be robust to `str`/`bytes` variations to avoid `os.write()`/`.decode()` type errors.

## Fix Focus Areas
- pr_agent/git_providers/gitea_provider.py[624-628]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


def get_user_id(self) -> str:
"""Get the ID of the authenticated user"""
Expand Down
45 changes: 45 additions & 0 deletions tests/unittest/test_gitea_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,48 @@ def call_api_side_effect(path, method, **kwargs):
args, kwargs = mock_api_client.call_api.call_args
assert args[0] == '/repos/owner/repo/pulls/123/commits'
assert kwargs.get('auth_settings') == ['AuthorizationHeaderToken']

def test_get_repo_settings_returns_bytes(self):
"""Regression for #2347: get_repo_settings must return bytes so that
utils.apply_repo_settings can os.write() it and later .decode() it. The
Gitea raw-file API yields str (unlike GitHub/GitLab/Bitbucket, which hand
back bytes), so the provider must encode before returning."""
from pr_agent.git_providers.gitea_provider import GiteaProvider

toml = '[pr_reviewer]\nnum_code_suggestions = 4\n'
provider = GiteaProvider.__new__(GiteaProvider)
provider.logger = MagicMock()
provider.owner = 'owner'
provider.repo = 'repo'
provider.sha = 'sha1'
provider.repo_settings = '.pr_agent.toml'
provider.repo_api = MagicMock()
provider.repo_api.get_file_content.return_value = toml # API decodes to str

result = provider.get_repo_settings()

assert isinstance(result, bytes)
assert result == toml.encode('utf-8')
# The bytes must survive the exact operations utils.py performs on them.
assert result.decode() == toml

def test_get_repo_settings_empty_bytes_when_unset_or_missing(self):
"""No settings path configured, or empty/absent file: return empty
bytes, so every code path honours the -> bytes contract (not just the
success path) and a caller can never receive a str."""
from pr_agent.git_providers.gitea_provider import GiteaProvider

unset = GiteaProvider.__new__(GiteaProvider)
unset.logger = MagicMock()
unset.repo_settings = None
assert unset.get_repo_settings() == b""

empty = GiteaProvider.__new__(GiteaProvider)
empty.logger = MagicMock()
empty.owner = 'owner'
empty.repo = 'repo'
empty.sha = 'sha1'
empty.repo_settings = '.pr_agent.toml'
empty.repo_api = MagicMock()
empty.repo_api.get_file_content.return_value = ''
assert empty.get_repo_settings() == b""
Loading