From e4528f195e0da8825dbb81ac829f3c4a07a7ece0 Mon Sep 17 00:00:00 2001 From: random-access7 Date: Tue, 17 Apr 2018 15:29:53 +0530 Subject: [PATCH] labhub.py: add migrate_issue plugin Introduce migrate_issue plugin that adds ability to migrate an issue from a source repo to target repo, both owned by the org. Issue title, issue description and all comments are copied with a few additional details appended to the the description and comments. Source issue is referenced in the target issue and is closed after the migration is complete. Closes https://github.com/coala/corobo/issues/518 --- plugins/labhub.py | 85 ++++++++++++++++++++++++++++++++++ tests/labhub_test.py | 106 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/plugins/labhub.py b/plugins/labhub.py index 7616ba06..9cf32070 100644 --- a/plugins/labhub.py +++ b/plugins/labhub.py @@ -381,3 +381,88 @@ def pr_stats(self, msg, match): state=type(self).community_state(pr_count) ) yield reply + + @re_botcmd(pattern=r'^migrate\s+https://(github|gitlab)\.com/([^/]+)/([^/]+)/+issues/(\d+)\s+https://(github|gitlab)\.com/([^/]+)/([^/]+)/*$', # Ignore LineLengthBear, PyCodeStyleBear + # Ignore LineLengthBear, PyCodeStyleBear + re_cmd_name_help='migrate ', + flags=re.IGNORECASE) + def migrate_issue(self, msg, match): + """ + Migrate an issue from source repo + to target repo owned by the org + """ + orig_host = match.group(1) + org = match.group(2) + repo_name_orig = match.group(3) + issue_number = match.group(4) + user = msg.frm.nick + final_host = match.group(5) + org2 = match.group(6) + repo_name_final = match.group(7) + + if org != self.GH_ORG_NAME and org != self.GL_ORG_NAME: + return 'Source repository not owned by our org.' + + if org2 != self.GH_ORG_NAME and org2 != self.GL_ORG_NAME: + return 'Target repository not owned by our org.' + + if repo_name_orig not in self.REPOS: + return 'Source repository does not exist.' + + if repo_name_final not in self.REPOS: + return 'Target repository does not exist.' + + if not self.TEAMS[self.GH_ORG_NAME + ' maintainers'].is_member(user): + return tenv().get_template( + 'labhub/errors/not-maintainer.jinja2.md' + ).render( + action='migrate issues', + target=user, + ) + + try: + old_issue = self.REPOS[repo_name_orig].get_issue(int(issue_number)) + old_labels = old_issue.labels + + except RuntimeError as err: + sterr, errno = err.args + if errno == 404: + return 'Issue does not exist!' + else: + raise RuntimeError(sterr, errno) + + if str(old_issue.state) != 'open': + return 'Issue must be open in order to be migrated!' + + url1 = 'https://{}.com/{}/{}/issues/{}'.format( + orig_host, org, repo_name_orig, issue_number) + new_issue_title = old_issue.title + issue_author = old_issue.author.username + + # Ignore LineLengthBear, PyCodeStyleBear + ext_msg = '\n\nThis is a migrated issue originally opened by @{} as {} and was migrated by @{}' + new_issue_descrip = old_issue.description.rstrip() + ext_msg.format( + issue_author, url1, str(user)) + new_issue = self.REPOS[repo_name_final].create_issue( + new_issue_title, new_issue_descrip) + new_issue.labels = old_labels + + # Ignore LineLengthBear, PyCodeStyleBear + comment_ext = '\n\nOriginally commented by @{} on {} UTC and can be seen [here]({})' + + for comment in old_issue.comments: + comm_text = comment.body.rstrip() + comm_url = url1 + '#issuecomment-' + str(comment.number) + new_body = comm_text + comment_ext.format( + comment.author.username, str(comment.updated), comm_url) + new_issue.add_comment(new_body) + + url2 = 'https://{}.com/{}/{}/issues/{}'.format( + final_host, org, repo_name_final, new_issue.number) + migrate_comm = 'Issue has been migrated to this [repository]({}) by @{}' + old_issue.add_comment(migrate_comm.format(url2, str(user))) + old_labels.add('Invalid') + old_issue.labels = old_labels + old_issue.close() + + return 'New issue created: {}'.format(url2) diff --git a/tests/labhub_test.py b/tests/labhub_test.py index 86de6d34..b4421605 100644 --- a/tests/labhub_test.py +++ b/tests/labhub_test.py @@ -343,3 +343,109 @@ def test_invite_me(self): 'Command \"hey\" / \"hey there\" not found.') with self.assertRaises(queue.Empty): testbot.pop_message() + + def test_migrate_issue(self): + plugins.labhub.GitHub = create_autospec(IGitt.GitHub.GitHub.GitHub) + plugins.labhub.GitLab = create_autospec(IGitt.GitLab.GitLab.GitLab) + labhub, testbot = plugin_testbot(plugins.labhub.LabHub, logging.ERROR) + labhub.activate() + + labhub.REPOS = { + 'a': self.mock_repo, + 'b': self.mock_repo + } + + mock_maint_team = create_autospec(github3.orgs.Team) + mock_maint_team.is_member.return_value = False + + labhub.TEAMS = { + 'coala maintainers': mock_maint_team, + 'coala developers': self.mock_team, + 'coala newcomers': self.mock_team + } + cmd = '!migrate https://github.com/{}/{}/issues/{} https://github.com/{}/{}/' + + # Not a maintainer + testbot.assertCommand(cmd.format('coala', 'a', '21', 'coala', 'b'), + 'you are not a maintainer!') + # Unknown first org + testbot.assertCommand(cmd.format('coa', 'a', '23', 'coala', 'b'), + 'Source repository not owned by our org') + # Unknown second org + testbot.assertCommand(cmd.format('coala', 'a', '23', 'coa', 'b'), + 'Target repository not owned by our org') + # Repo does not exist + testbot.assertCommand(cmd.format('coala', 'c', '23', 'coala', 'b'), + 'Source repository does not exist') + # Repo does not exist + testbot.assertCommand(cmd.format('coala', 'a', '23', 'coala', 'e'), + 'Target repository does not exist') + # No issue exists + mock_maint_team.is_member.return_value = True + self.mock_repo.get_issue = Mock(side_effect=RuntimeError('Error message', 404)) + testbot.assertCommand(cmd.format('coala', 'a', '21', 'coala', 'b'), + 'Issue does not exist!') + # Runtime error + mock_maint_team.is_member.return_value = True + self.mock_repo.get_issue = Mock(side_effect=RuntimeError('Error message', 403)) + testbot.assertCommand(cmd.format('coala', 'a', '21', 'coala', 'b'), + 'Computer says') + # Issue closed + mock_maint_team.is_member.return_value = True + mock_issue = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + self.mock_repo.get_issue = Mock(return_value=mock_issue) + mock_issue.labels = PropertyMock() + mock_issue.state = PropertyMock() + mock_issue.state = 'closed' + testbot.assertCommand(cmd.format('coala', 'a', '21', 'coala', 'b'), + 'Issue must be open') + # Migrate issue + mock_maint_team.is_member.return_value = True + mock_issue = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + mock_issue2 = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + + self.mock_repo.get_issue = Mock(return_value=mock_issue) + label_prop = PropertyMock(return_value=set()) + type(mock_issue).labels = label_prop + mock_issue.title = PropertyMock() + mock_issue.title = 'Issue title' + mock_issue.description = PropertyMock() + mock_issue.description = 'Issue description' + mock_issue.state = PropertyMock() + mock_issue.state = 'open' + mock_issue.author.username = PropertyMock() + mock_issue.author.username = 'random-access7' + + self.mock_repo.create_issue = Mock(return_value=mock_issue2) + mock_issue2.labels = PropertyMock() + mock_issue2.number = PropertyMock() + mock_issue2.number = 45 + + mock_comment = create_autospec(IGitt.GitHub.GitHub.GitHubComment) + mock_comment2 = create_autospec(IGitt.GitHub.GitHub.GitHubComment) + + mock_issue.comments = PropertyMock() + mock_issue.comments = list() + mock_issue.comments.append(mock_comment) + mock_comment.author.username = PropertyMock() + mock_comment.author.username = 'random-access7' + mock_comment.body = PropertyMock() + mock_comment.body = 'Comment body' + mock_comment.number = PropertyMock() + mock_comment.number = 172 + mock_comment.updated = PropertyMock() + mock_comment.updated = '07/04/2018' + + testbot.assertCommand(cmd.format('coala', 'a', '21', 'coala', 'b'), + 'issue created:') + + self.mock_repo.get_issue.assert_called_with(21) + self.mock_repo.create_issue.assert_called_with('Issue title', + 'Issue description\n\nThis is a migrated issue originally opened by @random-access7 ' + \ + 'as https://github.com/coala/a/issues/21 and was migrated by @None') + mock_issue2.add_comment.assert_called_with( + 'Comment body\n\nOriginally commented by @random-access7 on 07/04/2018 UTC and ' + \ + 'can be seen [here](https://github.com/coala/a/issues/21#issuecomment-172)') + mock_issue.add_comment.assert_called_with( + 'Issue has been migrated to this [repository](https://github.com/coala/b/issues/45) by @None') + mock_issue.close.assert_called_with()