-
-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] migration_pr_check: Remind migration guidelines
- Loading branch information
1 parent
5687d24
commit 2bc54d3
Showing
8 changed files
with
288 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Copyright 2022 Simone Rubino - TAKOBI | ||
# Distributed under the MIT License (http://opensource.org/licenses/MIT). | ||
import os.path | ||
import re | ||
|
||
from .. import config, github | ||
from ..config import switchable | ||
from ..manifest import addon_dirs_in | ||
from ..process import check_call | ||
from ..queue import getLogger, task | ||
from .migration_issue_bot import _find_branch_milestone, _find_issue | ||
|
||
_logger = getLogger(__name__) | ||
|
||
|
||
def _get_added_modules(org, repo, gh_pr): | ||
target_branch = gh_pr.base.ref | ||
with github.temporary_clone(org, repo, target_branch) as clonedir: | ||
# We need a list now because otherwise modules | ||
# are yielded when the directory is already changed | ||
existing_addons_paths = list(addon_dirs_in(clonedir, installable_only=True)) | ||
|
||
pr_branch = f"tmp-pr-{gh_pr.number}" | ||
check_call( | ||
["git", "fetch", "origin", f"refs/pull/{gh_pr.number}/head:{pr_branch}"], | ||
cwd=clonedir, | ||
) | ||
check_call(["git", "checkout", pr_branch], cwd=clonedir) | ||
pr_addons_paths = addon_dirs_in(clonedir, installable_only=True) | ||
|
||
new_addons_paths = { | ||
addon_path | ||
for addon_path in pr_addons_paths | ||
if addon_path not in existing_addons_paths | ||
} | ||
return new_addons_paths | ||
|
||
|
||
def is_migration_pr(org, repo, pr): | ||
""" | ||
Determine if the PR is a migration. | ||
""" | ||
with github.login() as gh: | ||
gh_repo = gh.repository(org, repo) | ||
gh_pr = gh_repo.pull_request(pr) | ||
target_branch = gh_pr.base.ref | ||
milestone = _find_branch_milestone(gh_repo, target_branch) | ||
gh_migration_issue = _find_issue(gh_repo, milestone, target_branch) | ||
|
||
# The PR is mentioned in the migration issue | ||
pr_regex = re.compile(rf"#({gh_pr.number})") | ||
found_pr = pr_regex.findall(gh_migration_issue.body) | ||
if found_pr: | ||
return True | ||
|
||
# The added module is mentioned in the migration issue | ||
new_addons_paths = _get_added_modules(org, repo, gh_pr) | ||
new_addons = map(os.path.basename, new_addons_paths) | ||
for addon in new_addons: | ||
module_regex = re.compile(rf"- \[[ x]] {addon}") | ||
found_module = module_regex.search(gh_migration_issue.body) | ||
if found_module: | ||
return True | ||
|
||
# The Title of the PR contains [MIG] | ||
pr_title = gh_pr.title | ||
if "[MIG]" in pr_title: | ||
return True | ||
return False | ||
|
||
|
||
@task() | ||
@switchable("comment_migration_guidelines") | ||
def comment_migration_guidelines(org, repo, pr, dry_run=False): | ||
migration_reminder = config.MIGRATION_GUIDELINES_REMINDER | ||
with github.login() as gh: | ||
gh_pr = gh.pull_request(org, repo, pr) | ||
return github.gh_call(gh_pr.create_comment, migration_reminder) | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2022 Simone Rubino - TAKOBI | ||
# Distributed under the MIT License (http://opensource.org/licenses/MIT). | ||
|
||
import logging | ||
|
||
from ..router import router | ||
from ..tasks.migration_pr_check import comment_migration_guidelines, is_migration_pr | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
@router.register("pull_request", action="opened") | ||
async def on_pr_open_migration_check(event, *args, **kwargs): | ||
""" | ||
Whenever a PR is opened, if it is a migration PR, | ||
remind the migration guidelines. | ||
""" | ||
org, repo = event.data["repository"]["full_name"].split("/") | ||
pr = event.data["pull_request"]["number"] | ||
if is_migration_pr(org, repo, pr): | ||
comment_migration_guidelines.delay(org, repo, pr) | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# Copyright 2022 Simone Rubino - TAKOBI | ||
# Distributed under the MIT License (http://opensource.org/licenses/MIT). | ||
|
||
import pytest | ||
|
||
from oca_github_bot.tasks.migration_pr_check import is_migration_pr | ||
|
||
MIGRATION_PR_PATH = "oca_github_bot.tasks.migration_pr_check" | ||
|
||
|
||
def _get_addons_gen_mock(pr_new_modules=None): | ||
""" | ||
Return a callable that returns a list of modules. | ||
The list contains `pr_new_modules` only after first call. | ||
""" | ||
if pr_new_modules is None: | ||
pr_new_modules = list() | ||
|
||
class AddonsGenMock: | ||
def __init__(self): | ||
self.addons_gen_calls = 0 | ||
|
||
def __call__(self, *args, **kwargs): | ||
# First time, only the existing addons are returned | ||
existing_addons = ["existing_addon"] | ||
if self.addons_gen_calls > 0: | ||
# After that, return also `pr_new_modules` | ||
if pr_new_modules: | ||
existing_addons.extend(pr_new_modules) | ||
self.addons_gen_calls += 1 | ||
return existing_addons | ||
|
||
return AddonsGenMock() | ||
|
||
|
||
@pytest.mark.vcr() | ||
def test_no_new_module(mocker): | ||
""" | ||
If a PR does not add a new module, then it is not a migration. | ||
""" | ||
mocker.patch("%s.github" % MIGRATION_PR_PATH) | ||
mocker.patch("%s.check_call" % MIGRATION_PR_PATH) | ||
|
||
migration_issue = mocker.patch("%s._find_issue" % MIGRATION_PR_PATH) | ||
migration_issue.return_value.body = "Migration Issue Body" | ||
|
||
addons_gen = mocker.patch("%s.addon_dirs_in" % MIGRATION_PR_PATH) | ||
addons_gen.side_effect = _get_addons_gen_mock() | ||
|
||
is_migration = is_migration_pr("org", "repo", "pr") | ||
assert not is_migration | ||
|
||
|
||
@pytest.mark.vcr() | ||
def test_new_module_no_migration(mocker): | ||
""" | ||
If a PR adds a new module but the module is not in the migration issue, | ||
then it is not a migration. | ||
""" | ||
mocker.patch("%s.github" % MIGRATION_PR_PATH) | ||
mocker.patch("%s.check_call" % MIGRATION_PR_PATH) | ||
|
||
migration_issue_body = """ | ||
Modules to migrate: | ||
- [ ] a_module | ||
""" | ||
migration_issue = mocker.patch("%s._find_issue" % MIGRATION_PR_PATH) | ||
migration_issue.return_value.body = migration_issue_body | ||
|
||
addons_gen = mocker.patch("%s.addon_dirs_in" % MIGRATION_PR_PATH) | ||
addons_gen.side_effect = _get_addons_gen_mock() | ||
|
||
is_migration = is_migration_pr("org", "repo", "pr") | ||
assert not is_migration | ||
|
||
|
||
@pytest.mark.vcr() | ||
def test_new_module_migration(mocker): | ||
""" | ||
If a PR adds a new module and the module is in the migration issue, | ||
then it is a migration. | ||
""" | ||
mocker.patch("%s.github" % MIGRATION_PR_PATH) | ||
mocker.patch("%s.check_call" % MIGRATION_PR_PATH) | ||
|
||
addon_name = "migrated_module" | ||
migration_issue_body = f""" | ||
Modules to migrate: | ||
- [ ] {addon_name} | ||
""" | ||
migration_issue = mocker.patch("%s._find_issue" % MIGRATION_PR_PATH) | ||
migration_issue.return_value.body = migration_issue_body | ||
|
||
addons_gen = mocker.patch("%s.addon_dirs_in" % MIGRATION_PR_PATH) | ||
addons_gen.side_effect = _get_addons_gen_mock([addon_name]) | ||
|
||
is_migration = is_migration_pr("org", "repo", "pr") | ||
assert is_migration | ||
|
||
|
||
@pytest.mark.vcr() | ||
def test_migration_comment(mocker): | ||
""" | ||
If a PR adds a new module and it is in the migration issue, | ||
then it is a migration. | ||
""" | ||
github_mock = mocker.patch("%s.github" % MIGRATION_PR_PATH) | ||
mocker.patch("%s.check_call" % MIGRATION_PR_PATH) | ||
|
||
addon_name = "migrated_module" | ||
migration_issue_body = f""" | ||
Modules to migrate: | ||
- [ ] {addon_name} | ||
""" | ||
migration_issue = mocker.patch("%s._find_issue" % MIGRATION_PR_PATH) | ||
migration_issue.return_value.body = migration_issue_body | ||
|
||
addons_gen = mocker.patch("%s.addon_dirs_in" % MIGRATION_PR_PATH) | ||
addons_gen.side_effect = _get_addons_gen_mock([addon_name]) | ||
|
||
gh_context = mocker.MagicMock() | ||
github_mock_login_cm = github_mock.login.return_value | ||
github_mock_login_cm.__enter__.return_value = gh_context | ||
|
||
is_migration = is_migration_pr("org", "repo", "pr") | ||
assert is_migration | ||
|
||
|
||
@pytest.mark.vcr() | ||
def test_pr_title(mocker): | ||
""" | ||
If a PR has [MIG] in its Title, | ||
then it is a migration. | ||
""" | ||
github_mock = mocker.patch("%s.github" % MIGRATION_PR_PATH) | ||
mocker.patch("%s.check_call" % MIGRATION_PR_PATH) | ||
|
||
migration_issue_body = """ | ||
Modules to migrate | ||
""" | ||
migration_issue = mocker.patch("%s._find_issue" % MIGRATION_PR_PATH) | ||
migration_issue.return_value.body = migration_issue_body | ||
|
||
addons_gen = mocker.patch("%s.addon_dirs_in" % MIGRATION_PR_PATH) | ||
addons_gen.side_effect = _get_addons_gen_mock(["another_module"]) | ||
|
||
pr_title = "[MIG] migrated_module" | ||
gh_pr = mocker.MagicMock() | ||
gh_pr.title = pr_title | ||
gh_repo = mocker.MagicMock() | ||
gh_repo.pull_request.return_value = gh_pr | ||
gh_context = mocker.MagicMock() | ||
gh_context.repository.return_value = gh_repo | ||
github_mock_login_cm = github_mock.login.return_value | ||
github_mock_login_cm.__enter__.return_value = gh_context | ||
|
||
is_migration = is_migration_pr("org", "repo", "pr") | ||
assert is_migration |