mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
[Bug, GitLab]: fix missing context in cloud resolver (#10509)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
83b9262379
commit
e47bcf31e4
@ -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]:
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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]:
|
||||
|
||||
@ -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
|
||||
@ -1 +0,0 @@
|
||||
{{ issue_comment }}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1 +0,0 @@
|
||||
Please fix issue number #{{ issue_number }} in your repository.
|
||||
@ -0,0 +1,5 @@
|
||||
{% if issue_comment %}
|
||||
{{ issue_comment }}
|
||||
{% else %}
|
||||
Please fix issue number #{{ issue_number }}.
|
||||
{% endif %}
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user