def test_get_commits_to_cherry_pick_rebase(_): g = mock.Mock() g_pull = mock.Mock() g_pull.merged = True g_pull.number = 6 c1 = mock.Mock() c1.sha = "c1f" c1.parents = [] c2 = mock.Mock() c2.sha = "c2" c2.parents = [c1] def _get_commits(): return [c1, c2] g_pull.get_commits = _get_commits pull = mergify_pull.MergifyPull(g=g, g_pull=g_pull, installation_id=config.INSTALLATION_ID) base_branch = mock.Mock() base_branch.sha = "base_branch" base_branch.parents = [] rebased_c1 = mock.Mock() rebased_c1.sha = "rebased_c1" rebased_c1.parents = [base_branch] rebased_c2 = mock.Mock() rebased_c2.sha = "rebased_c2" rebased_c2.parents = [rebased_c1] assert (backports._get_commits_to_cherrypick( pull, rebased_c2) == [rebased_c1, rebased_c2])
def test_get_commits_to_cherry_pick_merge(): g = mock.Mock() g_pull = mock.Mock() g_pull.merged = True c1 = mock.Mock() c1.sha = "c1f" c1.parents = [] c2 = mock.Mock() c2.sha = "c2" c2.parents = [c1] def _get_commits(): return [c1, c2] g_pull.get_commits = _get_commits pull = mergify_pull.MergifyPull( g=g, g_pull=g_pull, installation_id=config.INSTALLATION_ID ) base_branch = mock.Mock() base_branch.sha = "base_branch" base_branch.parents = [] merge_commit = mock.Mock() merge_commit.sha = "merge_commit" merge_commit.parents = [base_branch, c2] assert duplicate_pull._get_commits_to_cherrypick(pull, merge_commit) == [c1, c2]
def test_pull_behind(commits_tree_generator): expected, commits = commits_tree_generator with mock.patch( "mergify_engine.mergify_pull.MergifyPull.commits", new_callable=mock.PropertyMock(return_value=commits), ): with mock.patch( "mergify_engine.mergify_pull.MergifyPull.g_pull", return_value=mock.PropertyMock, ) as g_pull: g_pull.base.repo.get_branch.return_value = mock.Mock( commit=mock.Mock(sha="base")) pull = mergify_pull.MergifyPull( mock.Mock(), data={ "mergeable_state": "clean", "state": "open", "merged": False, "merged_at": None, "merged_by": None, "base": { "ref": "#foo" }, }, ) assert expected == pull.is_behind
def report(url): path = url.replace("https://github.com/", "") owner, repo, _, pull_number = path.split("/") integration = github.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY) install_id = utils.get_installation_id(integration, owner) installation_token = integration.get_access_token(install_id).token g = github.Github(installation_token) r = g.get_repo(owner + "/" + repo) p = r.get_pull(int(pull_number)) print("* CONFIGURATION:") print(r.get_contents(".mergify.yml").decoded_content.decode()) mp = mergify_pull.MergifyPull(p, installation_token) print("* PULL REQUEST:") pprint.pprint(mp.to_dict(), width=160) print("is_behind: %s" % mp.is_behind()) print("* MERGIFY STATUSES:") commit = p.base.repo.get_commit(p.head.sha) for s in commit.get_combined_status().statuses: if s.context.startswith("mergify"): print("[%s]: %s" % (s.context, s.state)) print("* MERGIFY CHECKS:") checks = list(check_api.get_checks(p)) for c in checks: if c.name.startswith("Mergify"): print("[%s]: %s | %s" % (c.name, c.conclusion, c.output.get("title"))) print("> " + "\n> ".join(c.output.get("summary").split("\n"))) return g, p
def _load_from_cache_and_complete(self, data, branch_rule, collaborators): data = json.loads(data) pull = mergify_pull.MergifyPull(github.PullRequest.PullRequest( self.repository._requester, {}, data, completed=True)) changed = pull.complete(data, branch_rule, collaborators) if changed: self._cache_save_pull(pull) return pull
def test_pull_behind(commits_tree_generator): expected, commits = commits_tree_generator g_pull = mock.Mock() g_pull.base.repo.get_branch.return_value = mock.Mock(commit=mock.Mock( sha="base")) g_pull.get_commits.return_value = commits pull = mergify_pull.MergifyPull(g_pull=g_pull, installation_id=config.INSTALLATION_ID) behind = pull.is_behind() assert expected == behind
def run_command_async(installation_id, pull_request_raw, sources, comment, user, rerun=False): owner = pull_request_raw["base"]["user"]["login"] repo = pull_request_raw["base"]["repo"]["name"] client = github.get_client(owner, repo, installation_id) pull = mergify_pull.MergifyPull(client, pull_request_raw) return run_command(pull, sources, comment, user, rerun)
def _get_next_pull_request(queue, queue_log): _, installation_id, owner, repo, branch = queue.split("~") pull_numbers = _get_pulls(queue) queue_log.debug("%d pulls queued", len(pull_numbers), queue=list(pull_numbers)) if pull_numbers: pull_number = int(pull_numbers[0]) # TODO(sileht): We should maybe cleanup the queue on error here, instead of # retrying forever, but since it's not clear if mergify have been uninstalled or # if it's a temporary Github issue. client = github.get_client(owner, repo, installation_id) data = client.item(f"pulls/{pull_number}") return mergify_pull.MergifyPull(client, data)
def PullRequestUrl(v): _, owner, repo, _, pull_number = urlsplit(v).path.split("/") pull_number = int(pull_number) try: client = github.get_client(owner, repo) except httpx.HTTPNotFound: raise PullRequestUrlInvalid( message="Mergify not installed on repository '%s'" % owner) try: data = client.item(f"pulls/{pull_number}") except httpx.HTTPNotFound: raise PullRequestUrlInvalid(message=("Pull request '%s' not found" % v)) return mergify_pull.MergifyPull(client, data)
def test_get_commits_to_cherry_pick_rebase(commits, g_pull, _): c1 = {"sha": "c1f", "parents": [], "commit": {"message": "foobar"}} c2 = {"sha": "c2", "parents": [c1], "commit": {"message": "foobar"}} commits.return_value = [c1, c2] client = mock.Mock() client.auth.get_access_token.return_value = "<token>" pull = mergify_pull.MergifyPull( client, { "number": 6, "merged": True, "state": "closed", "html_url": "<html_url>", "base": { "ref": "ref", "repo": { "name": "name", "private": False } }, "user": { "login": "******" }, "merged_by": None, "merged_at": None, "mergeable_state": "clean", }, ) base_branch = {"sha": "base_branch", "parents": []} rebased_c1 = {"sha": "rebased_c1", "parents": [base_branch]} rebased_c2 = {"sha": "rebased_c2", "parents": [rebased_c1]} assert duplicate_pull._get_commits_to_cherrypick(pull, rebased_c2) == [ rebased_c1, rebased_c2, ]
def report(url): redis = utils.get_redis_for_cache() path = url.replace("https://github.com/", "") owner, repo, _, pull_number = path.split("/") integration = github.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY) install_id = utils.get_installation_id(integration, owner) print("* INSTALLATION ID: %s" % install_id) cached_sub = sub_utils.get_subscription(redis, install_id) db_sub = sub_utils._retrieve_subscription_from_db(install_id) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) print("* SUB DETAIL: %s" % db_sub["subscription_reason"]) print("* NUMBER OF CACHED TOKENS: %d" % len(cached_sub["tokens"])) try: for login, token in cached_sub["tokens"].items(): try: repos = get_repositories_setuped(token, install_id) except github.BadCredentialsException: print("** token for %s invalid" % login) except github.GithubException as e: if e.status != 401: raise print("** token for %s invalid" % login) else: if any((r["full_name"] == owner + "/" + repo) for r in repos): print("* MERGIFY INSTALLED AND ENABLED ON THIS REPOSITORY") else: print("* MERGIFY INSTALLED BUT DISABLED " "ON THIS REPOSITORY") break else: print("* MERGIFY DOESN'T HAVE ANY VALID OAUTH TOKENS") except github.UnknownObjectException: print("* MERGIFY SEEMS NOT INSTALLED") installation_token = integration.get_access_token(install_id).token g = github.Github(installation_token, base_url="https://api.%s" % config.GITHUB_DOMAIN) r = g.get_repo(owner + "/" + repo) print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC") p = r.get_pull(int(pull_number)) print("* CONFIGURATION:") print(r.get_contents(".mergify.yml").decoded_content.decode()) mp = mergify_pull.MergifyPull(g, p, install_id) print("* PULL REQUEST:") pprint.pprint(mp.to_dict(), width=160) try: print("is_behind: %s" % mp.is_behind()) except github.GithubException as e: print("Unable to know if pull request branch is behind: %s" % e) print("mergeable_state: %s" % mp.g_pull.mergeable_state) print("* MERGIFY STATUSES:") commit = p.base.repo.get_commit(p.head.sha) for s in commit.get_combined_status().statuses: if s.context.startswith("mergify"): print("[%s]: %s" % (s.context, s.state)) print("* MERGIFY CHECKS:") checks = list(check_api.get_checks(p)) for c in checks: if c._rawData['app']['id'] == config.INTEGRATION_ID: print("[%s]: %s | %s" % (c.name, c.conclusion, c.output.get("title"))) print("> " + "\n> ".join(c.output.get("summary").split("\n"))) return g, p
def test_get_pull_request_rule(): g_pull = mock.Mock() g_pull.assignees = [] g_pull.labels = [] g_pull.get_review_requests.return_value = ([], []) g_pull.author = "jd" g_pull.base.ref = "master" g_pull.head.ref = "myfeature" g_pull.base.repo.get_collaborator_permission.return_value = "write" g_pull._rawData = {'locked': False} g_pull.title = "My awesome job" g_pull.body = "I rock" file1 = mock.Mock() file1.filename = "README.rst" file2 = mock.Mock() file2.filename = "setup.py" g_pull.get_files.return_value = [file1, file2] review = mock.Mock() review.user.login = "******" review.state = "APPROVED" review._rawData = {"author_association": "MEMBER"} g_pull.get_reviews.return_value = [review] pull_request = mergify_pull.MergifyPull(g_pull=g_pull, installation_id=123) fake_ci = mock.Mock() fake_ci.context = "continuous-integration/fake-ci" fake_ci.state = "success" pull_request._get_checks = mock.Mock() pull_request._get_checks.return_value = [fake_ci] # Empty conditions pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": [], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello"] assert [r['name'] for r, _ in match.matching_rules] == ["hello"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "base:master", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["hello", "backport"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "#files=3", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["backport"] for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "#files=2", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["hello", "backport"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} # No match pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=xyz", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["merge"] assert [r['name'] for r, _ in match.matching_rules] == [] pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=master", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["merge"] assert [r['name'] for r, _ in match.matching_rules] == ["merge"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=master", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=2", ], "actions": {} }, { "name": "fast merge", "conditions": [ "base=master", "label=fast-track", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }, { "name": "fast merge with alternate ci", "conditions": [ "base=master", "label=fast-track", "status-success=continuous-integration/fake-ci-bis", "#approved-reviews-by>=1", ], "actions": {} }, { "name": "fast merge from a bot", "conditions": [ "base=master", "author=mybot", "status-success=continuous-integration/fake-ci", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == [ "merge", "fast merge", "fast merge with alternate ci", "fast merge from a bot"] assert [r['name'] for r, _ in match.matching_rules] == [ "merge", "fast merge", "fast merge with alternate ci"] for rule in match.rules: assert rule['actions'] == {} assert match.matching_rules[0][0]['name'] == "merge" assert len(match.matching_rules[0][1]) == 1 assert str(match.matching_rules[0][1][0]) == "#approved-reviews-by>=2" assert match.matching_rules[1][0]['name'] == "fast merge" assert len(match.matching_rules[1][1]) == 1 assert str(match.matching_rules[1][1][0]) == "label=fast-track" assert match.matching_rules[2][0]['name'] == "fast merge with alternate ci" assert len(match.matching_rules[2][1]) == 2 assert str(match.matching_rules[2][1][0]) == "label=fast-track" assert ( str(match.matching_rules[2][1][1]) == "status-success=continuous-integration/fake-ci-bis" )
def report(url): redis = utils.get_redis_for_cache() path = url.replace("https://github.com/", "") try: owner, repo, _, pull_number = path.split("/") except ValueError: print(f"Wrong URL: {url}") return slug = owner + "/" + repo integration = github.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY) install_id = utils.get_installation_id(integration, owner, repo=repo) print("* INSTALLATION ID: %s" % install_id) cached_sub = sub_utils.get_subscription(redis, install_id) db_sub = sub_utils._retrieve_subscription_from_db(install_id) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) report_sub(install_id, slug, cached_sub, "ENGINE-CACHE") report_sub(install_id, slug, db_sub, "DASHBOARD") installation_token = integration.get_access_token(install_id).token g = github.Github(installation_token, base_url="https://api.%s" % config.GITHUB_DOMAIN) r = g.get_repo(owner + "/" + repo) print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC") print("* CONFIGURATION:") try: mergify_config_content = rules.get_mergify_config_content(r) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") else: print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema(mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: pull_request_rules_raw = mergify_config["pull_request_rules"].as_dict() pull_request_rules_raw["rules"].extend( actions_runner.MERGIFY_RULE["rules"]) pull_request_rules = rules.PullRequestRules(**pull_request_rules_raw) try: p = r.get_pull(int(pull_number)) except github.UnknownObjectException: print("Wrong pull request number") return g, None mp = mergify_pull.MergifyPull(g, p, install_id) print("* PULL REQUEST:") pprint.pprint(mp.to_dict(), width=160) try: print("is_behind: %s" % mp.is_behind()) except github.GithubException as e: print("Unable to know if pull request branch is behind: %s" % e) print("mergeable_state: %s" % mp.g_pull.mergeable_state) print("* MERGIFY LAST CHECKS:") checks = list(check_api.get_checks(p)) for c in checks: if c._rawData["app"]["id"] == config.INTEGRATION_ID: print("[%s]: %s | %s" % (c.name, c.conclusion, c.output.get("title"))) print("> " + "\n> ".join(c.output.get("summary").split("\n"))) print("* MERGIFY LIVE MATCHES:") match = pull_request_rules.get_pull_request_rule(mp) summary_title, summary = actions_runner.gen_summary( "refresh", {}, mp, match) print("> %s" % summary_title) print(summary) return g, p
def test_get_pull_request_rule(): g = mock.Mock() team = mock.Mock() team.slug = "my-reviewers" team.get_members.return_value = [ mock.Mock(login="******"), mock.Mock(login="******") ] org = mock.Mock() org.get_teams.return_value = [team] g.get_organization.return_value = org g_pull = mock.Mock() g_pull.assignees = [] g_pull.labels = [] g_pull.get_review_requests.return_value = ([], []) g_pull.author = "jd" g_pull.base.ref = "master" g_pull.head.ref = "myfeature" g_pull.base.repo.get_collaborator_permission.return_value = "write" g_pull._rawData = {'locked': False} g_pull.title = "My awesome job" g_pull.body = "I rock" g_pull.user.login = "******" file1 = mock.Mock() file1.filename = "README.rst" file2 = mock.Mock() file2.filename = "setup.py" g_pull.get_files.return_value = [file1, file2] review = mock.Mock() review.user.login = "******" review.state = "APPROVED" review._rawData = {"author_association": "MEMBER"} g_pull.get_reviews.return_value = [review] pull_request = mergify_pull.MergifyPull(g=g, g_pull=g_pull, installation_id=123) # Don't catch data in these tests pull_request.to_dict = pull_request._get_consolidated_data fake_ci = mock.Mock() fake_ci.context = "continuous-integration/fake-ci" fake_ci.state = "success" pull_request._get_checks = mock.Mock() pull_request._get_checks.return_value = [fake_ci] # Empty conditions pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": [], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello"] assert [r['name'] for r, _ in match.matching_rules] == ["hello"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "base:master", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["hello", "backport"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "#files=3", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["backport"] for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "hello", "conditions": [ "#files=2", ], "actions": {} }, { "name": "backport", "conditions": [ "base:master", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["hello", "backport"] assert [r['name'] for r, _ in match.matching_rules] == ["hello", "backport"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} # No match pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=xyz", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["merge"] assert [r['name'] for r, _ in match.matching_rules] == [] pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=master", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["merge"] assert [r['name'] for r, _ in match.matching_rules] == ["merge"] assert [(r, []) for r in match.rules] == match.matching_rules for rule in match.rules: assert rule['actions'] == {} pull_request_rules = rules.PullRequestRules([{ "name": "merge", "conditions": [ "base=master", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=2", ], "actions": {} }, { "name": "fast merge", "conditions": [ "base=master", "label=fast-track", "status-success=continuous-integration/fake-ci", "#approved-reviews-by>=1", ], "actions": {} }, { "name": "fast merge with alternate ci", "conditions": [ "base=master", "label=fast-track", "status-success=continuous-integration/fake-ci-bis", "#approved-reviews-by>=1", ], "actions": {} }, { "name": "fast merge from a bot", "conditions": [ "base=master", "author=mybot", "status-success=continuous-integration/fake-ci", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == [ "merge", "fast merge", "fast merge with alternate ci", "fast merge from a bot" ] assert [r['name'] for r, _ in match.matching_rules ] == ["merge", "fast merge", "fast merge with alternate ci"] for rule in match.rules: assert rule['actions'] == {} assert match.matching_rules[0][0]['name'] == "merge" assert len(match.matching_rules[0][1]) == 1 assert str(match.matching_rules[0][1][0]) == "#approved-reviews-by>=2" assert match.matching_rules[1][0]['name'] == "fast merge" assert len(match.matching_rules[1][1]) == 1 assert str(match.matching_rules[1][1][0]) == "label=fast-track" assert match.matching_rules[2][0]['name'] == "fast merge with alternate ci" assert len(match.matching_rules[2][1]) == 2 assert str(match.matching_rules[2][1][0]) == "label=fast-track" assert (str(match.matching_rules[2][1][1]) == "status-success=continuous-integration/fake-ci-bis") # Team conditions with one review missing pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": [ "approved-reviews-by=@orgs/my-reviewers", "#approved-reviews-by>=2", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 1 assert str(match.matching_rules[0][1][0]) == "#approved-reviews-by>=2" review2 = mock.Mock() review2.user.login = "******" review2.state = "APPROVED" review2._rawData = {"author_association": "MEMBER"} g_pull.get_reviews.return_value = [review, review2] # Team conditions with no review missing pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": [ "approved-reviews-by=@orgs/my-reviewers", "#approved-reviews-by>=2", ], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 0 # Forbidden labels, when no label set pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": ["-label~=^(status/wip|status/blocked|review/need2)$"], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 0 # Forbidden labels, when forbiden label set label = mock.Mock() label.name = "status/wip" pull_request.g_pull.labels = [label] match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 1 assert str(match.matching_rules[0][1][0]) == ( "-label~=^(status/wip|status/blocked|review/need2)$") # Forbidden labels, when other label set label = mock.Mock() label.name = "allowed" pull_request.g_pull.labels = [label] match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 0 # Test team expander pull_request_rules = rules.PullRequestRules([{ "name": "default", "conditions": ["author~=^(user1|user2|another-jd)$"], "actions": {} }]) match = pull_request_rules.get_pull_request_rule(pull_request) assert [r['name'] for r in match.rules] == ["default"] assert [r['name'] for r, _ in match.matching_rules] == ["default"] assert match.matching_rules[0][0]['name'] == "default" assert len(match.matching_rules[0][1]) == 0
def run(event_type, data): """Everything starts here.""" installation_id = data["installation"]["id"] owner = data["repository"]["owner"]["login"] repo = data["repository"]["name"] client = github.get_client(owner, repo, installation_id) raw_pull = get_github_pull_from_event(client, event_type, data) if not raw_pull: # pragma: no cover LOG.info( "No pull request found in the event %s, ignoring", event_type, gh_owner=owner, gh_repo=repo, ) return pull = mergify_pull.MergifyPull(client, raw_pull) # Override pull_request with the updated one data["pull_request"] = pull.data pull.log.info("Pull request found in the event %s", event_type) if ("base" not in pull.data or "repo" not in pull.data["base"] or len(list(pull.data["base"]["repo"].keys())) < 70): pull.log.warning( "the pull request payload looks suspicious", event_type=event_type, data=data, ) if (event_type == "status" and pull.data["head"]["sha"] != data["sha"]): # pragma: no cover pull.log.info( "No need to proceed queue (got status of an old commit)", ) return elif (event_type in ["status", "check_suite", "check_run"] and pull.data["merged"]): # pragma: no cover pull.log.info( "No need to proceed queue (got status of a merged pull request)", ) return elif (event_type in ["check_suite", "check_run"] and pull.data["head"]["sha"] != data[event_type]["head_sha"]): # pragma: no cover pull.log.info( "No need to proceed queue (got %s of an old " "commit)", event_type, ) return if check_configuration_changes(pull.g_pull): pull.log.info("Configuration changed, ignoring", ) return # BRANCH CONFIGURATION CHECKING try: mergify_config = rules.get_mergify_config(pull.g_pull.base.repo) except rules.NoRules: # pragma: no cover pull.log.info("No need to proceed queue (.mergify.yml is missing)", ) return except rules.InvalidRules as e: # pragma: no cover # Not configured, post status check with the error message if event_type == "pull_request" and data["action"] in [ "opened", "synchronize" ]: check_api.set_check_run( pull.g_pull, "Summary", "completed", "failure", output={ "title": "The Mergify configuration is invalid", "summary": str(e), }, ) return # Add global and mandatory rules mergify_config["pull_request_rules"].rules.extend( rules.load_pull_request_rules_schema(MERGIFY_RULE["rules"])) subscription = sub_utils.get_subscription(utils.get_redis_for_cache(), installation_id) if pull.data["base"]["repo"][ "private"] and not subscription["subscription_active"]: check_api.set_check_run( pull.g_pull, "Summary", "completed", "failure", output={ "title": "Mergify is disabled", "summary": subscription["subscription_reason"], }, ) return # CheckRun are attached to head sha, so when user add commits or force push # we can't directly get the previous Mergify Summary. So we copy it here, then # anything that looks at it in next celery tasks will find it. if event_type == "pull_request" and data["action"] == "synchronize": copy_summary_from_previous_head_sha(pull.g_pull, data["before"]) sources = [{"event_type": event_type, "data": data}] commands_runner.spawn_pending_commands_tasks(pull, sources) if event_type == "issue_comment": commands_runner.run_command(pull, sources, data["comment"]["body"], data["comment"]["user"]) else: actions_runner.handle(mergify_config["pull_request_rules"], pull, sources)
def report(url): redis = utils.get_redis_for_cache() path = url.replace("https://github.com/", "") owner, repo, _, pull_number = path.split("/") integration = github.GithubIntegration(config.INTEGRATION_ID, config.PRIVATE_KEY) install_id = utils.get_installation_id(integration, owner, repo=repo) print("* INSTALLATION ID: %s" % install_id) cached_sub = sub_utils.get_subscription(redis, install_id) db_sub = sub_utils._retrieve_subscription_from_db(install_id) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) print("* SUB DETAIL: %s" % db_sub["subscription_reason"]) print("* NUMBER OF CACHED TOKENS: %d" % len(cached_sub["tokens"])) try: for login, token in cached_sub["tokens"].items(): try: repos = get_repositories_setuped(token, install_id) except github.BadCredentialsException: print("** token for %s invalid" % login) except github.GithubException as e: if e.status != 401: raise print("** token for %s invalid" % login) else: if any((r["full_name"] == owner + "/" + repo) for r in repos): print("* MERGIFY INSTALLED AND ENABLED ON THIS REPOSITORY") else: print("* MERGIFY INSTALLED BUT DISABLED " "ON THIS REPOSITORY") break else: print("* MERGIFY DOESN'T HAVE ANY VALID OAUTH TOKENS") except github.UnknownObjectException: print("* MERGIFY SEEMS NOT INSTALLED") installation_token = integration.get_access_token(install_id).token g = github.Github(installation_token, base_url="https://api.%s" % config.GITHUB_DOMAIN) r = g.get_repo(owner + "/" + repo) print("* REPOSITORY IS %s" % "PRIVATE" if r.private else "PUBLIC") print("* CONFIGURATION:") try: mergify_config_content = rules.get_mergify_config_content(r) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema(mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: pull_request_rules_raw = mergify_config["pull_request_rules"].as_dict() pull_request_rules_raw["rules"].extend( actions_runner.MERGIFY_RULE["rules"]) pull_request_rules = rules.PullRequestRules(**pull_request_rules_raw) try: p = r.get_pull(int(pull_number)) except github.UnknownObjectException: print("Wrong pull request number") return g, None mp = mergify_pull.MergifyPull(g, p, install_id) print("* PULL REQUEST:") pprint.pprint(mp.to_dict(), width=160) try: print("is_behind: %s" % mp.is_behind()) except github.GithubException as e: print("Unable to know if pull request branch is behind: %s" % e) print("mergeable_state: %s" % mp.g_pull.mergeable_state) print("* MERGIFY LAST CHECKS:") checks = list(check_api.get_checks(p)) for c in checks: if c._rawData["app"]["id"] == config.INTEGRATION_ID: print("[%s]: %s | %s" % (c.name, c.conclusion, c.output.get("title"))) print("> " + "\n> ".join(c.output.get("summary").split("\n"))) print("* MERGIFY LIVE MATCHES:") match = pull_request_rules.get_pull_request_rule(mp) summary_title, summary = actions_runner.gen_summary( "refresh", {}, mp, match) print("> %s" % summary_title) print(summary) return g, p
def handle(self, event_type, data): # Everything start here event_pull = self.get_github_pull_from_event(event_type, data) if not event_pull: # pragma: no cover self.log_formated_event(event_type, None, data) LOG.info("No pull request found in the event %s, " "ignoring" % event_type) return incoming_pull = mergify_pull.MergifyPull(event_pull) incoming_branch = incoming_pull.g_pull.base.ref incoming_sha = incoming_pull.g_pull.head.sha incoming_state = incoming_pull.g_pull.state self.log_formated_event(event_type, incoming_pull, data) if (event_type == "status" and incoming_sha != data["sha"]): # pragma: no cover LOG.info("No need to proceed queue (got status of an old commit)") return elif event_type == "status" and incoming_pull.g_pull.merged: LOG.info("No need to proceed queue (got status of a merged " "pull request)") return # CHECK IF THE CONFIGURATION IS GOING TO CHANGE if (event_type == "pull_request" and data["action"] in ["opened", "synchronize"] and self.repository.default_branch == incoming_branch): ref = None for f in incoming_pull.g_pull.get_files(): if f.filename == ".mergify.yml": ref = f.contents_url.split("?ref=")[1] if ref is not None: try: rules.get_branch_rule( self.repository, incoming_branch, ref ) except rules.InvalidRules as e: # pragma: no cover # Not configured, post status check with the error message # FIXME()!!!!!!!!!!! incoming_pull.post_check_status( self._redis, self.installation_id, "failure", str(e), "future-config-checker") else: incoming_pull.post_check_status( self._redis, self.installation_id, "success", "The new configuration is valid", "future-config-checker") # BRANCH CONFIGURATION CHECKING branch_rule = None try: branch_rule = rules.get_branch_rule(self.repository, incoming_branch) except rules.NoRules as e: LOG.info("No need to proceed queue (.mergify.yml is missing)") return except rules.InvalidRules as e: # pragma: no cover # Not configured, post status check with the error message if (event_type == "pull_request" and data["action"] in ["opened", "synchronize"]): incoming_pull.post_check_status( self._redis, self.installation_id, "failure", str(e)) return try: branch_protection.configure_protection_if_needed( self.repository, incoming_branch, branch_rule) except github.GithubException as e: # pragma: no cover if e.status == 404 and e.data["message"] == "Branch not found": LOG.info("head branch no longer exists", pull_request=incoming_pull) return raise if not branch_rule: LOG.info("Mergify disabled on branch", branch=incoming_branch) return # PULL REQUEST UPDATER collaborators = [u.id for u in self.repository.get_collaborators()] if incoming_state == "closed": self._cache_remove_pull(incoming_pull) LOG.info("Just update cache (pull request closed)") if (event_type == "pull_request" and data["action"] in ["closed", "labeled"] and incoming_pull.g_pull.merged): backports.backports( self.repository, incoming_pull, branch_rule["automated_backport_labels"], self._installation_token) if event_type == "pull_request" and data["action"] == "closed": self.get_processor().proceed_queue( incoming_branch, branch_rule, collaborators) if not incoming_pull.g_pull.merged: incoming_pull.post_check_status( self._redis, self.installation_id, "success", "Pull request closed unmerged") head_branch = incoming_pull.g_pull.head.ref if head_branch.startswith("mergify/bp/%s" % incoming_branch): try: self.repository.get_git_ref( "heads/%s" % head_branch ).delete() LOG.info("branch deleted", pull_request=incoming_pull, branch=head_branch) except github.GithubException as e: # pragma: no cover if e.status != 404: raise return # First, remove informations we don't want to get from cache, so their # will be recomputed by MergifyPull.complete() if event_type == "refresh": cache = {} old_status = None else: cache = self.get_cache_for_pull_number(incoming_branch, incoming_pull.g_pull.number) cache = dict((k, v) for k, v in cache.items() if k.startswith("mergify_engine_")) old_status = cache.pop("mergify_engine_status", None) if event_type == "status": cache.pop("mergify_engine_required_statuses", None) elif event_type == "pull_request_review": cache.pop("mergify_engine_reviews_ok", None) cache.pop("mergify_engine_reviews_ko", None) elif (event_type == "pull_request" and data["action"] == "synchronize"): cache.pop("mergify_engine_required_statuses", None) changed = incoming_pull.complete(cache, branch_rule, collaborators) if changed: self._cache_save_pull(incoming_pull) if (event_type == "pull_request_review" and data["review"]["user"]["id"] not in collaborators): LOG.info("Just update cache (pull_request_review non-collab)") return # NOTE(sileht): PullRequest updated or comment posted, maybe we need to # update github # Get and refresh the queues if old_status != incoming_pull.status: incoming_pull.post_check_status( self._redis, self.installation_id, incoming_pull.github_state, incoming_pull.github_description, ) self.get_processor().proceed_queue( incoming_branch, branch_rule, collaborators)