from openhands.core.logger import openhands_logger as logger from openhands.integrations.gitlab.service.base import GitLabMixinBase from openhands.integrations.service_types import RequestMethod class GitLabPRsMixin(GitLabMixinBase): """ Methods for interacting with GitLab merge requests (PRs) """ async def create_mr( self, id: int | str, source_branch: str, target_branch: str, title: str, description: str | None = None, labels: list[str] | None = None, ) -> str: """Creates a merge request in GitLab Args: id: The ID or URL-encoded path of the project source_branch: The name of the branch where your changes are implemented target_branch: The name of the branch you want the changes merged into title: The title of the merge request (optional, defaults to a generic title) description: The description of the merge request (optional) labels: A list of labels to apply to the merge request (optional) Returns: - MR URL when successful - Error message when unsuccessful """ # Convert string ID to URL-encoded path if needed project_id = str(id).replace('/', '%2F') if isinstance(id, str) else id url = f'{self.BASE_URL}/projects/{project_id}/merge_requests' # Set default description if none provided if not description: description = f'Merging changes from {source_branch} into {target_branch}' # Prepare the request payload payload = { 'source_branch': source_branch, 'target_branch': target_branch, 'title': title, 'description': description, } # Add labels if provided if labels and len(labels) > 0: payload['labels'] = ','.join(labels) # Make the POST request to create the MR response, _ = await self._make_request( url=url, params=payload, method=RequestMethod.POST ) return response['web_url'] async def get_pr_details(self, repository: str, pr_number: int) -> dict: """Get detailed information about a specific merge request Args: repository: Repository name in format 'owner/repo' pr_number: The merge request number (iid) Returns: Raw GitLab API response for the merge request """ project_id = self._extract_project_id(repository) url = f'{self.BASE_URL}/projects/{project_id}/merge_requests/{pr_number}' mr_data, _ = await self._make_request(url) return mr_data async def is_pr_open(self, repository: str, pr_number: int) -> bool: """Check if a GitLab merge request is still active (not closed/merged). Args: repository: Repository name in format 'owner/repo' pr_number: The merge request number (iid) Returns: True if MR is active (opened), False if closed/merged """ try: mr_details = await self.get_pr_details(repository, pr_number) # GitLab API response structure # https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr if 'state' in mr_details: return mr_details['state'] == 'opened' elif 'merged_at' in mr_details and 'closed_at' in mr_details: # Check if MR is merged or closed return not (mr_details['merged_at'] or mr_details['closed_at']) # If we can't determine the state, assume it's active (safer default) logger.warning( f'Could not determine GitLab MR status for {repository}#{pr_number}. ' f'Response keys: {list(mr_details.keys())}. Assuming MR is active.' ) return True except Exception as e: logger.warning( f'Could not determine GitLab MR status for {repository}#{pr_number}: {e}. ' f'Including conversation to be safe.' ) # If we can't determine the MR status, include the conversation to be safe return True