From aff0b9e47e6c98488feb2b03cfbb879854d1c8f5 Mon Sep 17 00:00:00 2001 From: random-access7 Date: Tue, 3 Apr 2018 01:05:55 +0530 Subject: [PATCH] labhub.py: add migrate_issue plugin Includes a migrate issue plugin that migrates an issue from one repo to another subject to conditions on the command like only maintainers can perform migration, issue must exist and issue must not be closed already. The plugin copies the issue title, issue description but appends the URL of the old issue and handle of the user that migrated the issue, to the description of the new issue. All comments are copied and written along with other details like author, date/time and URL of the old comment. Also includes tests to check functionality. Closes https://github.com/coala/corobo/issues/518 --- plugins/labhub.py | 94 ++++++++++++++++++++++++++++++++++++++++++++ tests/labhub_test.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/plugins/labhub.py b/plugins/labhub.py index 7616ba06..e75a5adc 100644 --- a/plugins/labhub.py +++ b/plugins/labhub.py @@ -381,3 +381,97 @@ 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 one repo + to another repo of coala + """ + 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) + + try: + assert org == self.GH_ORG_NAME or org == self.GL_ORG_NAME + except AssertionError: + yield 'First repository not owned by our org.' + return + + try: + assert org2 == self.GH_ORG_NAME or org2 == self.GL_ORG_NAME + except AssertionError: + yield 'Second repository not owned by our org.' + return + + if (repo_name_orig not in self.REPOS) or (repo_name_final not in self.REPOS): + yield 'Repository does not exist!' + return + + if (self.TEAMS[self.GH_ORG_NAME + ' maintainers'].is_member(user) == False): + yield tenv().get_template( + 'labhub/errors/not-maintainer.jinja2.md' + ).render( + action='migrate issues', + target=user, + ) + return + + 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: + yield 'Issue does not exist!' + return + else: + raise RuntimeError(sterr,errno) + + if str(old_issue.state) == 'closed': + yield 'Issue cannot be migrated as it has been closed already.' + return + + url1 = 'https://{}.com/{}/{}/issues/{}' + new_issue_title = old_issue.title + new_issue_description = old_issue.description.rstrip() + issue_author = old_issue.author.username + extra_msg = '\n\nMigrated issue originally opened by @' + issue_author + \ + ' from ' + url1.format( + orig_host, org, repo_name_orig, issue_number) + \ + ' Migration done by @' + str(user) + new_issue_description += extra_msg + new_issue = self.REPOS[repo_name_final].create_issue( + new_issue_title, new_issue_description) + new_issue.labels = old_labels + + for comment in old_issue.comments: + comm_text = comment.body.rstrip() + comm_url = url1.format(orig_host, org, repo_name_orig, issue_number) + \ + '#issuecomment-' + str(comment.number) + new_body = comm_text + '\n\nOriginally written by @' + \ + comment.author.username + ' on ' + str(comment.updated) + ' UTC' + \ + ' and you can view it [here!](' + comm_url + ')' + new_issue.add_comment(new_body) + + url2 = 'https://{}.com/{}/{}/issues/{}'.format( + final_host, org, repo_name_final, new_issue.number) + + migrated_comment = 'Issue has been migrated to another [repository](' + \ + url2 + ') by @' + str(user) + old_issue.add_comment(migrated_comment) + old_labels.add('Invalid') + old_issue.labels = old_labels + old_issue.close() + + yield 'New issue created: {}'.format(url2) + return diff --git a/tests/labhub_test.py b/tests/labhub_test.py index 86de6d34..21c881f9 100644 --- a/tests/labhub_test.py +++ b/tests/labhub_test.py @@ -25,6 +25,7 @@ def setUp(self): self.mock_org = create_autospec(github3.orgs.Organization) self.mock_gh = create_autospec(github3.GitHub) + self.mock_team = create_autospec(github3.orgs.Team) self.mock_team.name = PropertyMock() self.mock_team.name = 'mocked team' @@ -343,3 +344,92 @@ 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'), + 'First repository not owned by our org') + # Unknown second org + testbot.assertCommand(cmd.format('coala', 'a', '23','coa','b'), + 'Second repository not owned by our org') + # Repo does not exist + testbot.assertCommand(cmd.format('coala', 'c', '23','coala','b'), + '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!') + # Issue closed + mock_maint_team.is_member.return_value = True + mock_iss = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + self.mock_repo.get_issue = Mock(return_value=mock_iss) + mock_iss.labels = PropertyMock() + mock_iss.state = PropertyMock() + mock_iss.state = 'closed' + testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'), + 'has been closed already') + # Migrate issue + mock_maint_team.is_member.return_value = True + mock_iss = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + issue2 = create_autospec(IGitt.GitHub.GitHub.GitHubIssue) + + self.mock_repo.get_issue = Mock(return_value=mock_iss) + label_prop = PropertyMock(return_value={'enhancement','bug'}) + type(mock_iss).labels = label_prop + mock_iss.title = PropertyMock() + mock_iss.labels = 'Issue title' + mock_iss.description = PropertyMock() + mock_iss.description = 'Issue description' + mock_iss.state = PropertyMock() + mock_iss.state = 'open' + mock_iss.author.username = PropertyMock() + mock_iss.author.username = 'random-access7' + + self.mock_repo.create_issue = Mock(return_value=issue2) + issue2.labels = PropertyMock() + + mock_comment = create_autospec(IGitt.GitHub.GitHub.GitHubComment) + mock_comment2 = create_autospec(IGitt.GitHub.GitHub.GitHubComment) + + mock_iss.comments = PropertyMock() + mock_iss.comments = list() + mock_iss.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 = 1743 + mock_comment.updated = PropertyMock() + mock_comment.updated = '07/04/2018' + + issue2.add_comment = Mock(return_value=mock_comment2) + mock_iss.add_comment = Mock(return_value=mock_comment2) + mock_iss.close = Mock(return_value=True) + + testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'), + 'issue created:')