Skip to content
4 changes: 4 additions & 0 deletions docs/gl_objects/merge_requests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ List the changes of a MR::

changes = mr.changes()

List issues related to this merge request::

related_issues = mr.related_issues()

List issues that will close on merge::

mr.closes_issues()
Expand Down
29 changes: 29 additions & 0 deletions gitlab/v4/objects/merge_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,35 @@ def cancel_merge_when_pipeline_succeeds(self, **kwargs: Any) -> Dict[str, str]:
assert isinstance(server_data, dict)
return server_data

@cli.register_custom_action(cls_names="ProjectMergeRequest")
@exc.on_http_error(exc.GitlabListError)
def related_issues(self, **kwargs: Any) -> RESTObjectList:
"""List issues related to this merge request."

Args:
all: If True, return all the items, without pagination
per_page: Number of items to retrieve per request
page: ID of the page to return (starts with page 1)
**kwargs: Extra options to send to the server (e.g. sudo)

Raises:
GitlabAuthenticationError: If authentication is not correct
GitlabListError: If the list could not be retrieved

Returns:
List of issues
"""

path = f"{self.manager.path}/{self.encoded_id}/related_issues"
data_list = self.manager.gitlab.http_list(path, iterator=True, **kwargs)

if TYPE_CHECKING:
assert isinstance(data_list, gitlab.GitlabList)

manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent)

return RESTObjectList(manager, ProjectIssue, data_list)

@cli.register_custom_action(cls_names="ProjectMergeRequest")
@exc.on_http_error(exc.GitlabListError)
def closes_issues(self, **kwargs: Any) -> RESTObjectList:
Expand Down
104 changes: 104 additions & 0 deletions tests/unit/objects/test_merge_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import pytest
import responses

from gitlab.base import RESTObjectList
from gitlab.v4.objects import (
ProjectDeploymentMergeRequest,
ProjectIssue,
ProjectMergeRequest,
ProjectMergeRequestReviewerDetail,
)
Expand Down Expand Up @@ -57,6 +59,78 @@
}
]

related_issues = [
{
"id": 1,
"iid": 1,
"project_id": 1,
"title": "Fake Title for Merge Requests via API",
"description": "Something here",
"state": "closed",
"created_at": "2024-05-14T04:01:40.042Z",
"updated_at": "2024-06-13T05:29:13.661Z",
"closed_at": "2024-06-13T05:29:13.602Z",
"closed_by": {
"id": 2,
"name": "Sam Bauch",
"username": "kenyatta_oconnell",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon",
"web_url": "http://gitlab.example.com/kenyatta_oconnell",
},
"labels": [
"FakeCategory",
"fake:ml",
],
"assignees": [
{
"id": 2,
"name": "Sam Bauch",
"username": "kenyatta_oconnell",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon",
"web_url": "http://gitlab.example.com/kenyatta_oconnell",
}
],
"author": {
"id": 2,
"name": "Sam Bauch",
"username": "kenyatta_oconnell",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon",
"web_url": "http://gitlab.example.com//kenyatta_oconnell",
},
"type": "ISSUE",
"assignee": {
"id": 4459593,
"username": "fakeuser",
"name": "Fake User",
"state": "active",
"locked": False,
"avatar_url": "https://example.com/uploads/-/system/user/avatar/4459593/avatar.png",
"web_url": "https://example.com/fakeuser",
},
"user_notes_count": 9,
"merge_requests_count": 0,
"upvotes": 1,
"downvotes": 0,
"due_date": None,
"confidential": False,
"discussion_locked": None,
"issue_type": "issue",
"web_url": "https://example.com/fakeorg/fakeproject/-/issues/461536",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": None,
"human_total_time_spent": None,
},
"task_completion_status": {"count": 0, "completed_count": 0},
"weight": None,
"blocking_issues_count": 0,
}
]


@pytest.fixture
def resp_list_merge_requests():
Expand Down Expand Up @@ -93,6 +167,26 @@ def resp_get_merge_request_reviewers():
yield rsps


@pytest.fixture
def resp_list_merge_requests_related_issues():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/projects/1/merge_requests/1",
json=mr_content,
content_type="application/json",
status=200,
)
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/projects/1/merge_requests/1/related_issues",
json=related_issues,
content_type="application/json",
status=200,
)
yield rsps


def test_list_project_merge_requests(project, resp_list_merge_requests):
mrs = project.mergerequests.list()
assert isinstance(mrs[0], ProjectMergeRequest)
Expand All @@ -115,3 +209,13 @@ def test_get_merge_request_reviewers(project, resp_get_merge_request_reviewers):
assert mr.reviewers[0]["name"] == reviewers_details[0].user["name"]
assert reviewers_details[0].state == "unreviewed"
assert reviewers_details[0].created_at == "2022-07-27T17:03:27.684Z"


def test_list_related_issues(project, resp_list_merge_requests_related_issues):
mr = project.mergerequests.get(1)
this_mr_related_issues = mr.related_issues()
the_issue = next(iter(this_mr_related_issues))
assert isinstance(mr, ProjectMergeRequest)
assert isinstance(this_mr_related_issues, RESTObjectList)
assert isinstance(the_issue, ProjectIssue)
assert the_issue.title == related_issues[0]["title"]