diff --git a/docs/gl_objects/merge_trains.rst b/docs/gl_objects/merge_trains.rst index 6d98e04d8..205053045 100644 --- a/docs/gl_objects/merge_trains.rst +++ b/docs/gl_objects/merge_trains.rst @@ -9,6 +9,8 @@ Reference + :class:`gitlab.v4.objects.ProjectMergeTrain` + :class:`gitlab.v4.objects.ProjectMergeTrainManager` + + :class:`gilab.v4.objects.ProjectMergeTrainMergeRequest` + + :class:`gilab.v4.objects.ProjectMergeTrainMergeRequestManager` + :attr:`gitlab.v4.objects.Project.merge_trains` * GitLab API: https://docs.gitlab.com/api/merge_trains @@ -27,3 +29,13 @@ List active merge trains for a project:: List completed (have been merged) merge trains for a project:: merge_trains = project.merge_trains.list(scope="complete") + +Get Merge Request Status for a Merge Train:: + + merge_train_mr = project.merge_trains.get(1, lazy=True).merge_requests.get(1) + merge_train_mr_status = merge_train_mr.pipeline.get("status") + +Add Merge Request to a Merge Train:: + + merge_train_to_update = project.merge_trains.get(1, lazy=True) + merge_requests_update = merge_train_to_update.merge_requests.update(5, new_data={"sha": "cd22awr721ssds"}) diff --git a/gitlab/v4/objects/merge_trains.py b/gitlab/v4/objects/merge_trains.py index a1c5a447d..2b483e157 100644 --- a/gitlab/v4/objects/merge_trains.py +++ b/gitlab/v4/objects/merge_trains.py @@ -1,14 +1,43 @@ -from gitlab.base import RESTObject -from gitlab.mixins import ListMixin +from gitlab.base import RESTManager, RESTObject +from gitlab.mixins import GetMixin, ListMixin, UpdateMethod, UpdateMixin +from gitlab.types import RequiredOptional -__all__ = ["ProjectMergeTrain", "ProjectMergeTrainManager"] +__all__ = [ + "ProjectMergeTrain", + "ProjectMergeTrainManager", + "ProjectMergeTrainMergeRequest", + "ProjectMergeTrainMergeRequestManager", +] -class ProjectMergeTrain(RESTObject): +class ProjectMergeTrainMergeRequest(RESTObject): pass -class ProjectMergeTrainManager(ListMixin[ProjectMergeTrain]): +class ProjectMergeTrainMergeRequestManager( + GetMixin[ProjectMergeTrainMergeRequest], + UpdateMixin[ProjectMergeTrainMergeRequest], + RESTManager[ProjectMergeTrainMergeRequest], +): + _path = "/projects/{project_id}/merge_trains/merge_requests" + _obj_cls = ProjectMergeTrainMergeRequest + _from_parent_attrs = {"project_id": "project_id"} + _update_method: UpdateMethod = UpdateMethod.POST + + _update_attrs = RequiredOptional( + optional=("sha", "squash", "when_pipeline_succeeds") + ) + + +class ProjectMergeTrain(RESTObject): + merge_requests: ProjectMergeTrainMergeRequestManager + + +class ProjectMergeTrainManager( + GetMixin[ProjectMergeTrain], + ListMixin[ProjectMergeTrain], + RESTManager[ProjectMergeTrain], +): _path = "/projects/{project_id}/merge_trains" _obj_cls = ProjectMergeTrain _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index b415a8b98..f74fd64b1 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -64,7 +64,7 @@ ProjectApprovalRuleManager, ) from .merge_requests import ProjectMergeRequestManager # noqa: F401 -from .merge_trains import ProjectMergeTrainManager # noqa: F401 +from .merge_trains import ProjectMergeTrainManager from .milestones import ProjectMilestoneManager # noqa: F401 from .notes import ProjectNoteManager # noqa: F401 from .notification_settings import ProjectNotificationSettingsManager # noqa: F401 diff --git a/tests/unit/objects/test_merge_trains.py b/tests/unit/objects/test_merge_trains.py index f58d04422..fa5fe6221 100644 --- a/tests/unit/objects/test_merge_trains.py +++ b/tests/unit/objects/test_merge_trains.py @@ -6,12 +6,12 @@ import pytest import responses -from gitlab.v4.objects import ProjectMergeTrain +from gitlab.v4.objects import ProjectMergeTrain, ProjectMergeTrainMergeRequest mr_content = { "id": 110, "merge_request": { - "id": 1, + "id": 273, "iid": 1, "project_id": 3, "title": "Test merge train", @@ -46,6 +46,10 @@ "duration": 70, } +merge_train_update = mr_content.copy() +merge_train_update["iid"] = 4 +merge_train_update["pipeline"]["sha"] = "ef33a3zxc3" + @pytest.fixture def resp_list_merge_trains(): @@ -60,7 +64,61 @@ def resp_list_merge_trains(): yield rsps +@pytest.fixture +def resp_merge_trains_merge_request_get(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/merge_trains/merge_requests/1", + json=mr_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_merge_trains_merge_request_post(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/projects/1/merge_trains/merge_requests/4", + json=[merge_train_update], + content_type="application/json", + status=200, + ) + yield rsps + + def test_list_project_merge_requests(project, resp_list_merge_trains): merge_trains = project.merge_trains.list() assert isinstance(merge_trains[0], ProjectMergeTrain) assert merge_trains[0].id == mr_content["id"] + + +def test_merge_trains_status_merge_request( + project, resp_merge_trains_merge_request_get +): + merge_train_mr: ProjectMergeTrainMergeRequest = project.merge_trains.get( + 1, lazy=True + ).merge_requests.get(1) + assert isinstance(merge_train_mr, ProjectMergeTrainMergeRequest) + assert merge_train_mr.get_id() == 110 + assert merge_train_mr.merge_request["iid"] == mr_content["merge_request"]["iid"] + assert merge_train_mr.pipeline.get("status") == mr_content["pipeline"]["status"] + + +def test_merge_train_add_merge_request(project, resp_merge_trains_merge_request_post): + merge_train: ProjectMergeTrain = project.merge_trains.get(1, lazy=True) + merge_requests_update = merge_train.merge_requests.update( + 4, new_data={"sha": "ef33a3zxc3"} + ) + assert isinstance(merge_train, ProjectMergeTrain) + assert ( + merge_requests_update[0]["pipeline"]["sha"] + == merge_train_update["pipeline"]["sha"] + ) + assert ( + merge_requests_update[0]["merge_request"]["iid"] + == merge_train_update["merge_request"]["iid"] + )