[Bug, GitLab]: fix missing context in cloud resolver (#10509)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Rohit Malhotra 2025-08-29 02:38:03 -04:00 committed by GitHub
parent 83b9262379
commit e47bcf31e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 169 additions and 99 deletions

View File

@ -1028,14 +1028,6 @@ class GitHubService(BaseGitService, GitService, InstallationsService):
return self._process_raw_comments(all_thread_comments)
def _truncate_comment(
self, comment_body: str, max_comment_length: int = 500
) -> str:
"""Truncate comment body to a maximum length."""
if len(comment_body) > max_comment_length:
return comment_body[:max_comment_length] + '...'
return comment_body
def _process_raw_comments(
self, comments_data: list, max_comments: int = 10
) -> list[Comment]:

View File

@ -756,79 +756,129 @@ class GitLabService(BaseGitService, GitService):
# Parse the content to extract triggers from frontmatter
return self._parse_microagent_content(response, file_path)
async def get_issue_comments(
self, project_id: str, issue_iid: int, limit: int = 100
async def get_review_thread_comments(
self, project_id: str, issue_iid: int, discussion_id: str
) -> list[Comment]:
"""Get the last n comments for a specific issue.
url = (
f'{self.BASE_URL}/projects/{project_id}'
f'/merge_requests/{issue_iid}/discussions/{discussion_id}'
)
# Single discussion fetch; notes are returned inline.
response, _ = await self._make_request(url)
notes = response.get('notes') or []
return self._process_raw_comments(notes)
async def get_issue_or_mr_title_and_body(
self, project_id: str, issue_number: int, is_mr: bool = False
) -> tuple[str, str]:
"""Get the title and body of an issue or merge request.
Args:
project_id: The GitLab project ID (can be numeric ID or URL-encoded path)
issue_iid: The issue internal ID (iid) in GitLab
limit: Maximum number of comments to retrieve (default: 100)
repository: Repository name in format 'owner/repo' or 'domain/owner/repo'
issue_number: The issue/MR IID within the project
is_mr: If True, treat as merge request; if False, treat as issue;
if None, try issue first then merge request (default behavior)
Returns:
List of Comment objects, ordered by creation date (newest first)
Raises:
UnknownException: If the request fails or the issue is not found
A tuple of (title, body)
"""
# URL-encode the project_id if it contains special characters
if '/' in str(project_id):
encoded_project_id = str(project_id).replace('/', '%2F')
else:
encoded_project_id = str(project_id)
if is_mr:
url = f'{self.BASE_URL}/projects/{project_id}/merge_requests/{issue_number}'
response, _ = await self._make_request(url)
title = response.get('title') or ''
body = response.get('description') or ''
return title, body
url = f'{self.BASE_URL}/projects/{encoded_project_id}/issues/{issue_iid}/notes'
url = f'{self.BASE_URL}/projects/{project_id}/issues/{issue_number}'
response, _ = await self._make_request(url)
title = response.get('title') or ''
body = response.get('description') or ''
return title, body
async def get_issue_or_mr_comments(
self,
project_id: str,
issue_number: int,
max_comments: int = 10,
is_mr: bool = False,
) -> list[Comment]:
"""Get comments for an issue or merge request.
Args:
repository: Repository name in format 'owner/repo' or 'domain/owner/repo'
issue_number: The issue/MR IID within the project
max_comments: Maximum number of comments to retrieve
is_pr: If True, treat as merge request; if False, treat as issue;
if None, try issue first then merge request (default behavior)
Returns:
List of Comment objects ordered by creation date
"""
all_comments: list[Comment] = []
page = 1
per_page = min(limit, 100) # GitLab API max per_page is 100
per_page = min(max_comments, 10)
while len(all_comments) < limit:
# Get comments with pagination, ordered by creation date descending
url = (
f'{self.BASE_URL}/projects/{project_id}/merge_requests/{issue_number}/discussions'
if is_mr
else f'{self.BASE_URL}/projects/{project_id}/issues/{issue_number}/notes'
)
while len(all_comments) < max_comments:
params = {
'per_page': per_page,
'page': page,
'order_by': 'created_at',
'sort': 'desc', # Get newest comments first
'sort': 'asc',
}
response, headers = await self._make_request(url, params)
if not response: # No more comments
if not response:
break
# Filter out system comments and convert to Comment objects
for comment_data in response:
if len(all_comments) >= limit:
break
if is_mr:
for discussions in response:
# Keep root level comments
all_comments.append(discussions['notes'][0])
else:
all_comments.extend(response)
# Skip system-generated comments unless explicitly requested
if comment_data.get('system', False):
continue
comment = Comment(
id=str(comment_data['id']),
body=comment_data['body'],
author=comment_data.get('author', {}).get('username', 'unknown'),
created_at=datetime.fromisoformat(
comment_data['created_at'].replace('Z', '+00:00')
),
updated_at=datetime.fromisoformat(
comment_data['updated_at'].replace('Z', '+00:00')
),
system=comment_data.get('system', False),
)
all_comments.append(comment)
# Check if we have more pages
link_header = headers.get('Link', '')
if 'rel="next"' not in link_header or len(all_comments) >= limit:
if 'rel="next"' not in link_header:
break
page += 1
return all_comments
return self._process_raw_comments(all_comments)
def _process_raw_comments(
self, comments: list, max_comments: int = 10
) -> list[Comment]:
"""Helper method to fetch comments from a given URL with pagination."""
all_comments: list[Comment] = []
for comment_data in comments:
comment = Comment(
id=str(comment_data.get('id', 'unknown')),
body=self._truncate_comment(comment_data.get('body', '')),
author=comment_data.get('author', {}).get('username', 'unknown'),
created_at=datetime.fromisoformat(
comment_data.get('created_at', '').replace('Z', '+00:00')
)
if comment_data.get('created_at')
else datetime.fromtimestamp(0),
updated_at=datetime.fromisoformat(
comment_data.get('updated_at', '').replace('Z', '+00:00')
)
if comment_data.get('updated_at')
else datetime.fromtimestamp(0),
system=comment_data.get('system', False),
)
all_comments.append(comment)
# Sort comments by creation date and return the most recent ones
all_comments.sort(key=lambda c: c.created_at)
return all_comments[-max_comments:]
async def is_pr_open(self, repository: str, pr_number: int) -> bool:
"""Check if a GitLab merge request is still active (not closed/merged).

View File

@ -454,6 +454,14 @@ class BaseGitService(ABC):
return microagents
def _truncate_comment(
self, comment_body: str, max_comment_length: int = 500
) -> str:
"""Truncate comment body to a maximum length."""
if len(comment_body) > max_comment_length:
return comment_body[:max_comment_length] + '...'
return comment_body
class InstallationsService(Protocol):
async def get_installations(self) -> list[str]:

View File

@ -1,22 +0,0 @@
You are requested to fix issue number #{{ issue_number }} in a repository.
A comment on the issue has been addressed to you.
# Steps to Handle the Comment
1. Address the comment. Use the $GITLAB_TOKEN and GitLab API to read issue title, body, and comments if you need more context
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Re-read the issue title, body, and comments and make sure that you have successfully implemented all requirements.
2. Create a new branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
3. Commit your changes with a clear commit message
4. Push the branch to GitLab
5. Use the `create_mr` tool to open a new MR
6. The MR description should:
- Follow the repository's MR template (check `.gitlab/merge_request_templates/` or `.github/pull_request_template.md` if it exists)
- Mention that it "fixes" or "closes" the issue number
- Include all required sections from the template

View File

@ -1 +0,0 @@
{{ issue_comment }}

View File

@ -0,0 +1,41 @@
{% if issue_number %}
You are requested to fix issue #{{ issue_number }}: "{{ issue_title }}" in a repository.
A comment on the issue has been addressed to you.
{% else %}
Your task is to fix the issue: "{{ issue_title }}".
{% endif %}
# Issue Body
{{ issue_body }}
{% if comments %}
# Previous Comments
For reference, here are the previous comments on the issue:
{% for comment in comments %}
- @{{ comment.author }} said:
{{ comment.body }}
{% if not loop.last %}\n\n{% endif %}
{% endfor %}
{% endif %}
# Guidelines
1. Review the task carefully.
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
# Final Checklist
Re-read the issue title, body, and comments and make sure that you have successfully implemented all requirements.
Use the $GITLAB_TOKEN and GitLab APIs to
1. Create a new branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
2. Commit your changes with a clear commit message
3. Push the branch to GitLab
4. Use the `create_mr` tool to open a new MR
5. The MR description should:
- Follow the repository's MR template (check `.gitlab/merge_request_templates/` or `.github/pull_request_template.md` if it exists)
- Mention that it "fixes" or "closes" the issue number
- Include all required sections from the template

View File

@ -1,17 +0,0 @@
Your tasking is to fix an issue in your repository. Do the following
1. Read the issue body and comments using the $GITLAB_TOKEN and GitLab API
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Create a new branch with a descriptive name (e.g., `openhands/fix-issue-123`)
2. Commit your changes with a clear commit message
3. Push the branch to GitLab
4. Use the `create_mr` tool to open a new MR
5. The MR description should:
- Follow the repository's MR template (check `.gitlab/merge_request_templates/` or `.github/pull_request_template.md` if it exists)
- Mention that it "fixes" or "closes" the issue number
- Include all required sections from the template

View File

@ -1 +0,0 @@
Please fix issue number #{{ issue_number }} in your repository.

View File

@ -0,0 +1,5 @@
{% if issue_comment %}
{{ issue_comment }}
{% else %}
Please fix issue number #{{ issue_number }}.
{% endif %}

View File

@ -1,7 +1,22 @@
You are checked out to branch {{ branch_name }}, which has an open MR #{{ mr_number }}.
A comment on the MR has been addressed to you. Do NOT respond to this comment via the GitLab API.
You are checked out to branch {{ branch_name }}, which has an open MR #{{ mr_number }}: "{{ mr_title }}".
A comment on the MR has been addressed to you.
{% if file_location %} The comment is in the file `{{ file_location }}` on line #{{ line_number }}{% endif %}.
# MR Description
{{ mr_body }}
{% if comments %}
You may find these other comments relevant:
{% for comment in comments %}
- @{{ comment.author }} said at {{ comment.created_at }}:
{{ comment.body }}
{% if not loop.last %}\n\n{% endif %}
{% endfor %}
{% endif %}
{% if file_location %}
# Comment location
The comment is in the file `{{ file_location }}` on line #{{ line_number }}
{% endif %}.
# Steps to Handle the Comment