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])
Esempio n. 2
0
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]
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
 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
Esempio n. 6
0
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
Esempio n. 7
0
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)
Esempio n. 8
0
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)
Esempio n. 9
0
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)
Esempio n. 10
0
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,
    ]
Esempio n. 11
0
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
Esempio n. 12
0
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"
    )
Esempio n. 13
0
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
Esempio n. 14
0
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
Esempio n. 15
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)
Esempio n. 16
0
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
Esempio n. 17
0
    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)