Skip to content

Commit 31ecd7a

Browse files
committed
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 #518
1 parent 583785c commit 31ecd7a

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

plugins/labhub.py

+97
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,100 @@ def pr_stats(self, msg, match):
381381
state=type(self).community_state(pr_count)
382382
)
383383
yield reply
384+
385+
@re_botcmd(pattern=r'^migrate\s+https://github.com/([^/]+)/([^/]+)/+issues/(\d+)\s+https://github.com/([^/]+)/([^/]+)/*$', # Ignore LineLengthBear, PyCodeStyleBear
386+
# Ignore LineLengthBear, PyCodeStyleBear
387+
re_cmd_name_help='migrate <complete-issue-URL-to-be-copied-from> <complete-repo-URL-to-be-copied-into>',
388+
flags=re.IGNORECASE)
389+
def migrate_issue(self, msg, match):
390+
"""
391+
Migrate an issue from one repo
392+
to another repo of coala
393+
"""
394+
org = match.group(1)
395+
repo_name_orig = match.group(2)
396+
issue_number = match.group(3)
397+
user = msg.frm.nick
398+
org2 = match.group(4)
399+
repo_name_final = match.group(5)
400+
401+
try:
402+
assert org == self.GH_ORG_NAME or org == self.GL_ORG_NAME
403+
except AssertionError:
404+
yield 'First repository not owned by our org.'
405+
return
406+
407+
try:
408+
assert org2 == self.GH_ORG_NAME or org2 == self.GL_ORG_NAME
409+
except AssertionError:
410+
yield 'Second repository not owned by our org.'
411+
return
412+
413+
if (repo_name_orig in self.REPOS) and (repo_name_final in self.REPOS):
414+
pass
415+
else:
416+
yield 'Repository does not exist!'
417+
return
418+
419+
if self.TEAMS[self.GH_ORG_NAME + ' maintainers'].is_member(user):
420+
pass
421+
else:
422+
yield tenv().get_template(
423+
'labhub/errors/not-maintainer.jinja2.md'
424+
).render(
425+
action='migrate issues',
426+
target=user,
427+
)
428+
return
429+
430+
try:
431+
old_issue = self.REPOS[repo_name_orig].get_issue(int(issue_number))
432+
old_labels = old_issue.labels
433+
434+
except RuntimeError:
435+
yield 'Issue does not exist!'
436+
return
437+
438+
if str(old_issue.state) == 'closed':
439+
yield 'Issue cannot be migrated as it has been closed already.'
440+
return
441+
else:
442+
pass
443+
444+
url1 = 'https://github.com/{}/{}/issues/{}'
445+
new_issue_title = old_issue.title
446+
new_issue_description = old_issue.description.rstrip()
447+
extra_msg = '\n\nMigrated issue from '+url1.format(
448+
org, repo_name_orig, issue_number) + \
449+
' by @' + str(user)
450+
451+
new_issue = self.REPOS[repo_name_final].create_issue(
452+
new_issue_title, new_issue_description+extra_msg)
453+
new_issue.labels = old_labels
454+
455+
old_comm = old_issue.comments
456+
457+
for i in range(len(old_comm)):
458+
author = old_comm[i].author
459+
comm_text = old_comm[i].body.rstrip()
460+
updated = old_comm[i].updated
461+
comm_url = url1.format(org, repo_name_orig, issue_number) + \
462+
'#issuecomment-' + str(old_comm[i].number)
463+
new_body = comm_text + '\n\nOriginally written by @' + \
464+
author + ' on ' + \
465+
str(updated) + ' UTC' + \
466+
' and you can view it [here!](' + comm_url + ')'
467+
new_issue.add_comment(new_body)
468+
469+
url2 = 'https://github.com/{}/{}/issues/{}'.format(
470+
org, repo_name_final, new_issue.number)
471+
472+
migrated_comm = 'Issue has been migrated to another [repository](' + \
473+
url2 + ') by @' + str(user)
474+
old_issue.add_comment(migrated_comm)
475+
old_labels.add('Invalid')
476+
old_issue.labels = old_labels
477+
old_issue.close()
478+
479+
yield 'New issue created: {}'.format(url2)
480+
return

tests/labhub_test.py

+88
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def setUp(self):
2525

2626
self.mock_org = create_autospec(github3.orgs.Organization)
2727
self.mock_gh = create_autospec(github3.GitHub)
28+
2829
self.mock_team = create_autospec(github3.orgs.Team)
2930
self.mock_team.name = PropertyMock()
3031
self.mock_team.name = 'mocked team'
@@ -343,3 +344,90 @@ def test_invite_me(self):
343344
'Command \"hey\" / \"hey there\" not found.')
344345
with self.assertRaises(queue.Empty):
345346
testbot.pop_message()
347+
348+
def test_migrate_issue(self):
349+
plugins.labhub.GitHub = create_autospec(IGitt.GitHub.GitHub.GitHub)
350+
plugins.labhub.GitLab = create_autospec(IGitt.GitLab.GitLab.GitLab)
351+
labhub, testbot = plugin_testbot(plugins.labhub.LabHub, logging.ERROR)
352+
labhub.activate()
353+
354+
labhub.REPOS = {
355+
'a': self.mock_repo,
356+
'b': self.mock_repo
357+
}
358+
359+
mock_maint_team = create_autospec(github3.orgs.Team)
360+
mock_maint_team.is_member.return_value = False
361+
362+
labhub.TEAMS = {
363+
'coala maintainers': mock_maint_team,
364+
'coala developers': self.mock_team,
365+
'coala newcomers': self.mock_team
366+
}
367+
cmd = '!migrate https://github.com/{}/{}/issues/{} https://github.com/{}/{}/'
368+
369+
# Not a maintainer
370+
testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'),
371+
'you are not a maintainer!')
372+
# Unknown first org
373+
testbot.assertCommand(cmd.format('coa', 'a', '23','coala','b'),
374+
'First repository not owned by our org')
375+
# Unknown second org
376+
testbot.assertCommand(cmd.format('coala', 'a', '23','coa','b'),
377+
'Second repository not owned by our org')
378+
# Repo does not exist
379+
testbot.assertCommand(cmd.format('coala', 'c', '23','coala','b'),
380+
'Repository does not exist')
381+
# No issue exists
382+
mock_maint_team.is_member.return_value = True
383+
self.mock_repo.get_issue = Mock(side_effect=RuntimeError)
384+
testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'),
385+
'Issue does not exist!')
386+
# Issue closed
387+
mock_maint_team.is_member.return_value = True
388+
mock_iss = create_autospec(IGitt.GitHub.GitHub.GitHubIssue)
389+
self.mock_repo.get_issue = Mock(return_value=mock_iss)
390+
mock_iss.labels = PropertyMock()
391+
mock_iss.state = PropertyMock()
392+
mock_iss.state = 'closed'
393+
testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'),
394+
'has been closed already')
395+
# Migrate issue
396+
mock_maint_team.is_member.return_value = True
397+
mock_iss = create_autospec(IGitt.GitHub.GitHub.GitHubIssue)
398+
issue2 = create_autospec(IGitt.GitHub.GitHub.GitHubIssue)
399+
400+
self.mock_repo.get_issue = Mock(return_value=mock_iss)
401+
label_prop = PropertyMock(return_value={'enhancement','bug'})
402+
type(mock_iss).labels = label_prop
403+
mock_iss.title = PropertyMock()
404+
mock_iss.labels = 'Issue title'
405+
mock_iss.description = PropertyMock()
406+
mock_iss.description = 'Issue description'
407+
mock_iss.state = PropertyMock()
408+
mock_iss.state = 'open'
409+
410+
self.mock_repo.create_issue = Mock(return_value=issue2)
411+
issue2.labels = PropertyMock()
412+
413+
mock_comment = create_autospec(IGitt.GitHub.GitHub.GitHubComment)
414+
mock_comment2 = create_autospec(IGitt.GitHub.GitHub.GitHubComment)
415+
416+
mock_iss.comments = PropertyMock()
417+
mock_iss.comments = list()
418+
mock_iss.comments.append(mock_comment)
419+
mock_comment.author = PropertyMock()
420+
mock_comment.author = 'random-access7'
421+
mock_comment.body = PropertyMock()
422+
mock_comment.body = 'Comment bobdy'
423+
mock_comment.number = PropertyMock()
424+
mock_comment.number = 1743
425+
mock_comment.updated = PropertyMock()
426+
mock_comment.updated = '07/04/2018'
427+
428+
issue2.add_comment = Mock(return_value=mock_comment2)
429+
mock_iss.add_comment = Mock(return_value=mock_comment2)
430+
mock_iss.close = Mock(return_value=True)
431+
432+
testbot.assertCommand(cmd.format('coala', 'a', '21','coala','b'),
433+
'issue created:')

0 commit comments

Comments
 (0)