From: Simon Glass <simon.glass@canonical.com> Add function to update a merge request's description via the GitLab API. This is used to update the MR with the agent's conversation log after processing review comments. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/pickman/ftest.py | 42 +++++++++++++++++++++++++++++++++++++ tools/pickman/gitlab_api.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index ae4ceaceaaa..ec9e7e91d65 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -1345,6 +1345,48 @@ class TestCheckAvailable(unittest.TestCase): self.assertTrue(result) +class TestUpdateMrDescription(unittest.TestCase): + """Tests for update_mr_description function.""" + + @mock.patch.object(gitlab_api, 'get_remote_url') + @mock.patch.object(gitlab_api, 'get_token') + @mock.patch.object(gitlab_api, 'AVAILABLE', True) + def test_update_mr_description_success(self, mock_token, mock_url): + """Test successful MR description update.""" + mock_token.return_value = 'test-token' + mock_url.return_value = 'git@gitlab.com:group/project.git' + + mock_mr = mock.MagicMock() + mock_project = mock.MagicMock() + mock_project.mergerequests.get.return_value = mock_mr + + with mock.patch('gitlab.Gitlab') as mock_gitlab: + mock_gitlab.return_value.projects.get.return_value = mock_project + + result = gitlab_api.update_mr_description('origin', 123, + 'New description') + + self.assertTrue(result) + self.assertEqual(mock_mr.description, 'New description') + mock_mr.save.assert_called_once() + + @mock.patch.object(gitlab_api, 'AVAILABLE', False) + def test_update_mr_description_not_available(self): + """Test update_mr_description when gitlab not available.""" + with terminal.capture(): + result = gitlab_api.update_mr_description('origin', 123, 'desc') + self.assertFalse(result) + + @mock.patch.object(gitlab_api, 'get_token') + @mock.patch.object(gitlab_api, 'AVAILABLE', True) + def test_update_mr_description_no_token(self, mock_token): + """Test update_mr_description when no token set.""" + mock_token.return_value = None + with terminal.capture(): + result = gitlab_api.update_mr_description('origin', 123, 'desc') + self.assertFalse(result) + + class TestParseApplyWithPush(unittest.TestCase): """Tests for apply command with push options.""" diff --git a/tools/pickman/gitlab_api.py b/tools/pickman/gitlab_api.py index d0128e13f80..508168aa75c 100644 --- a/tools/pickman/gitlab_api.py +++ b/tools/pickman/gitlab_api.py @@ -320,6 +320,43 @@ def reply_to_mr(remote, mr_iid, message): return False +def update_mr_description(remote, mr_iid, desc): + """Update a merge request's description + + Args: + remote (str): Remote name + mr_iid (int): Merge request IID + desc (str): New description + + Returns: + bool: True on success + """ + if not check_available(): + return False + + token = get_token() + if not token: + tout.error('GITLAB_TOKEN environment variable not set') + return False + + remote_url = get_remote_url(remote) + host, proj_path = parse_url(remote_url) + + if not host or not proj_path: + return False + + try: + glab = gitlab.Gitlab(f'https://{host}', private_token=token) + project = glab.projects.get(proj_path) + merge_req = project.mergerequests.get(mr_iid) + merge_req.description = desc + merge_req.save() + return True + except gitlab.exceptions.GitlabError as exc: + tout.error(f'GitLab API error: {exc}') + return False + + def push_and_create_mr(remote, branch, target, title, desc=''): """Push a branch and create a merge request -- 2.43.0