diff --git a/docs/docs/usage-guide/additional_configurations.md b/docs/docs/usage-guide/additional_configurations.md index c345e0c6..eb46bbaa 100644 --- a/docs/docs/usage-guide/additional_configurations.md +++ b/docs/docs/usage-guide/additional_configurations.md @@ -164,6 +164,7 @@ Qodo Merge allows you to automatically ignore certain PRs based on various crite - PRs with specific titles (using regex matching) - PRs between specific branches (using regex matching) +- PRs from specific repositories (using regex matching) - PRs not from specific folders - PRs containing specific labels - PRs opened by specific users @@ -172,7 +173,7 @@ Qodo Merge allows you to automatically ignore certain PRs based on various crite To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file: -``` +```toml [config] ignore_pr_title = ["\\[Bump\\]"] ``` @@ -183,7 +184,7 @@ Where the `ignore_pr_title` is a list of regex patterns to match the PR title yo To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file: -``` +```toml [config] ignore_pr_source_branches = ['develop', 'main', 'master', 'stage'] ignore_pr_target_branches = ["qa"] @@ -192,6 +193,18 @@ ignore_pr_target_branches = ["qa"] Where the `ignore_pr_source_branches` and `ignore_pr_target_branches` are lists of regex patterns to match the source and target branches you want to ignore. They are not mutually exclusive, you can use them together or separately. +### Ignoring PRs from specific repositories + +To ignore PRs from specific repositories, you can add the following to your `configuration.toml` file: + +```toml +[config] +ignore_repositories = ["my-org/my-repo1", "my-org/my-repo2"] +``` + +Where the `ignore_repositories` is a list of regex patterns to match the repositories you want to ignore. This is useful when you have multiple repositories and want to exclude certain ones from analysis. + + ### Ignoring PRs not from specific folders To allow only specific folders (often needed in large monorepos), set: diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py index a641adeb..0d75d143 100644 --- a/pr_agent/servers/bitbucket_app.py +++ b/pr_agent/servers/bitbucket_app.py @@ -127,6 +127,14 @@ def should_process_pr_logic(data) -> bool: source_branch = pr_data.get("source", {}).get("branch", {}).get("name", "") target_branch = pr_data.get("destination", {}).get("branch", {}).get("name", "") sender = _get_username(data) + repo_full_name = pr_data.get("destination", {}).get("repository", {}).get("full_name", "") + + # logic to ignore PRs from specific repositories + ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", []) + if repo_full_name and ignore_repos: + if any(re.search(regex, repo_full_name) for regex in ignore_repos): + get_logger().info(f"Ignoring PR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting") + return False # logic to ignore PRs from specific users ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", []) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 4576bd9d..36db3c69 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -258,6 +258,14 @@ def should_process_pr_logic(body) -> bool: source_branch = pull_request.get("head", {}).get("ref", "") target_branch = pull_request.get("base", {}).get("ref", "") sender = body.get("sender", {}).get("login") + repo_full_name = body.get("repository", {}).get("full_name", "") + + # logic to ignore PRs from specific repositories + ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", []) + if ignore_repos and repo_full_name: + if any(re.search(regex, repo_full_name) for regex in ignore_repos): + get_logger().info(f"Ignoring PR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting") + return False # logic to ignore PRs from specific users ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", []) diff --git a/pr_agent/servers/gitlab_webhook.py b/pr_agent/servers/gitlab_webhook.py index 7a753c9e..af777a9a 100644 --- a/pr_agent/servers/gitlab_webhook.py +++ b/pr_agent/servers/gitlab_webhook.py @@ -113,6 +113,14 @@ def should_process_pr_logic(data) -> bool: return False title = data['object_attributes'].get('title') sender = data.get("user", {}).get("username", "") + repo_full_name = data.get('project', {}).get('path_with_namespace', "") + + # logic to ignore PRs from specific repositories + ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", []) + if ignore_repos and repo_full_name: + if any(re.search(regex, repo_full_name) for regex in ignore_repos): + get_logger().info(f"Ignoring MR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting") + return False # logic to ignore PRs from specific users ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", []) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index e63b7ea8..ce097c95 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -55,6 +55,7 @@ ignore_pr_target_branches = [] # a list of regular expressions of target branche ignore_pr_source_branches = [] # a list of regular expressions of source branches to ignore from PR agent when an PR is created ignore_pr_labels = [] # labels to ignore from PR agent when an PR is created ignore_pr_authors = [] # authors to ignore from PR agent when an PR is created +ignore_repositories = [] # a list of regular expressions of repository full names (e.g. "org/repo") to ignore from PR agent processing # is_auto_command = false # will be auto-set to true if the command is triggered by an automation enable_ai_metadata = false # will enable adding ai metadata diff --git a/tests/unittest/test_ignore_repositories.py b/tests/unittest/test_ignore_repositories.py new file mode 100644 index 00000000..e05447d5 --- /dev/null +++ b/tests/unittest/test_ignore_repositories.py @@ -0,0 +1,79 @@ +import pytest +from pr_agent.servers.github_app import should_process_pr_logic as github_should_process_pr_logic +from pr_agent.servers.bitbucket_app import should_process_pr_logic as bitbucket_should_process_pr_logic +from pr_agent.servers.gitlab_webhook import should_process_pr_logic as gitlab_should_process_pr_logic +from pr_agent.config_loader import get_settings + +def make_bitbucket_payload(full_name): + return { + "data": { + "pullrequest": { + "title": "Test PR", + "source": {"branch": {"name": "feature/test"}}, + "destination": { + "branch": {"name": "main"}, + "repository": {"full_name": full_name} + } + }, + "actor": {"username": "user", "type": "user"} + } + } + +def make_github_body(full_name): + return { + "pull_request": {}, + "repository": {"full_name": full_name}, + "sender": {"login": "user"} + } + +def make_gitlab_body(full_name): + return { + "object_attributes": {"title": "Test MR"}, + "project": {"path_with_namespace": full_name} + } + +PROVIDERS = [ + ("github", github_should_process_pr_logic, make_github_body), + ("bitbucket", bitbucket_should_process_pr_logic, make_bitbucket_payload), + ("gitlab", gitlab_should_process_pr_logic, make_gitlab_body), +] + +class TestIgnoreRepositories: + def setup_method(self): + get_settings().set("CONFIG.IGNORE_REPOSITORIES", []) + + @pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS) + def test_should_ignore_matching_repository(self, provider_name, provider_func, body_func): + get_settings().set("CONFIG.IGNORE_REPOSITORIES", ["org/repo-to-ignore"]) + body = { + "pull_request": {}, + "repository": {"full_name": "org/repo-to-ignore"}, + "sender": {"login": "user"} + } + result = provider_func(body_func(body["repository"]["full_name"])) + # print(f"DEBUG: Provider={provider_name}, test_should_ignore_matching_repository, result={result}") + assert result is False, f"{provider_name}: PR from ignored repository should be ignored (return False)" + + @pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS) + def test_should_not_ignore_non_matching_repository(self, provider_name, provider_func, body_func): + get_settings().set("CONFIG.IGNORE_REPOSITORIES", ["org/repo-to-ignore"]) + body = { + "pull_request": {}, + "repository": {"full_name": "org/other-repo"}, + "sender": {"login": "user"} + } + result = provider_func(body_func(body["repository"]["full_name"])) + # print(f"DEBUG: Provider={provider_name}, test_should_not_ignore_non_matching_repository, result={result}") + assert result is True, f"{provider_name}: PR from non-ignored repository should not be ignored (return True)" + + @pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS) + def test_should_not_ignore_when_config_empty(self, provider_name, provider_func, body_func): + get_settings().set("CONFIG.IGNORE_REPOSITORIES", []) + body = { + "pull_request": {}, + "repository": {"full_name": "org/repo-to-ignore"}, + "sender": {"login": "user"} + } + result = provider_func(body_func(body["repository"]["full_name"])) + # print(f"DEBUG: Provider={provider_name}, test_should_not_ignore_when_config_empty, result={result}") + assert result is True, f"{provider_name}: PR should not be ignored if ignore_repositories config is empty" \ No newline at end of file