codecov-cli 11.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codecov_cli/__init__.py +3 -0
- codecov_cli/commands/__init__.py +0 -0
- codecov_cli/commands/base_picking.py +75 -0
- codecov_cli/commands/commit.py +72 -0
- codecov_cli/commands/create_report_result.py +41 -0
- codecov_cli/commands/empty_upload.py +80 -0
- codecov_cli/commands/get_report_results.py +50 -0
- codecov_cli/commands/labelanalysis.py +269 -0
- codecov_cli/commands/process_test_results.py +273 -0
- codecov_cli/commands/report.py +65 -0
- codecov_cli/commands/send_notifications.py +46 -0
- codecov_cli/commands/staticanalysis.py +62 -0
- codecov_cli/commands/upload.py +316 -0
- codecov_cli/commands/upload_coverage.py +186 -0
- codecov_cli/commands/upload_process.py +133 -0
- codecov_cli/fallbacks.py +41 -0
- codecov_cli/helpers/__init__.py +0 -0
- codecov_cli/helpers/args.py +31 -0
- codecov_cli/helpers/ci_adapters/__init__.py +63 -0
- codecov_cli/helpers/ci_adapters/appveyor_ci.py +54 -0
- codecov_cli/helpers/ci_adapters/azure_pipelines.py +44 -0
- codecov_cli/helpers/ci_adapters/base.py +102 -0
- codecov_cli/helpers/ci_adapters/bitbucket_ci.py +42 -0
- codecov_cli/helpers/ci_adapters/bitrise_ci.py +37 -0
- codecov_cli/helpers/ci_adapters/buildkite.py +45 -0
- codecov_cli/helpers/ci_adapters/circleci.py +47 -0
- codecov_cli/helpers/ci_adapters/cirrus_ci.py +36 -0
- codecov_cli/helpers/ci_adapters/cloudbuild.py +70 -0
- codecov_cli/helpers/ci_adapters/codebuild.py +49 -0
- codecov_cli/helpers/ci_adapters/droneci.py +36 -0
- codecov_cli/helpers/ci_adapters/github_actions.py +90 -0
- codecov_cli/helpers/ci_adapters/gitlab_ci.py +56 -0
- codecov_cli/helpers/ci_adapters/heroku.py +36 -0
- codecov_cli/helpers/ci_adapters/jenkins.py +38 -0
- codecov_cli/helpers/ci_adapters/local.py +39 -0
- codecov_cli/helpers/ci_adapters/teamcity.py +37 -0
- codecov_cli/helpers/ci_adapters/travis_ci.py +44 -0
- codecov_cli/helpers/ci_adapters/woodpeckerci.py +36 -0
- codecov_cli/helpers/config.py +66 -0
- codecov_cli/helpers/encoder.py +49 -0
- codecov_cli/helpers/folder_searcher.py +114 -0
- codecov_cli/helpers/git.py +97 -0
- codecov_cli/helpers/git_services/__init__.py +14 -0
- codecov_cli/helpers/git_services/github.py +40 -0
- codecov_cli/helpers/glob.py +146 -0
- codecov_cli/helpers/logging_utils.py +77 -0
- codecov_cli/helpers/options.py +51 -0
- codecov_cli/helpers/request.py +198 -0
- codecov_cli/helpers/upload_type.py +15 -0
- codecov_cli/helpers/validators.py +13 -0
- codecov_cli/helpers/versioning_systems.py +201 -0
- codecov_cli/main.py +99 -0
- codecov_cli/opentelemetry.py +26 -0
- codecov_cli/plugins/__init__.py +92 -0
- codecov_cli/plugins/compress_pycoverage_contexts.py +141 -0
- codecov_cli/plugins/gcov.py +69 -0
- codecov_cli/plugins/pycoverage.py +134 -0
- codecov_cli/plugins/types.py +8 -0
- codecov_cli/plugins/xcode.py +117 -0
- codecov_cli/runners/__init__.py +80 -0
- codecov_cli/runners/dan_runner.py +64 -0
- codecov_cli/runners/pytest_standard_runner.py +184 -0
- codecov_cli/runners/types.py +33 -0
- codecov_cli/services/__init__.py +0 -0
- codecov_cli/services/commit/__init__.py +86 -0
- codecov_cli/services/commit/base_picking.py +24 -0
- codecov_cli/services/empty_upload/__init__.py +42 -0
- codecov_cli/services/report/__init__.py +169 -0
- codecov_cli/services/upload/__init__.py +169 -0
- codecov_cli/services/upload/file_finder.py +320 -0
- codecov_cli/services/upload/legacy_upload_sender.py +132 -0
- codecov_cli/services/upload/network_finder.py +49 -0
- codecov_cli/services/upload/upload_collector.py +198 -0
- codecov_cli/services/upload/upload_sender.py +232 -0
- codecov_cli/services/upload_completion/__init__.py +38 -0
- codecov_cli/services/upload_coverage/__init__.py +93 -0
- codecov_cli/types.py +88 -0
- codecov_cli-11.0.0.dist-info/METADATA +298 -0
- codecov_cli-11.0.0.dist-info/RECORD +83 -0
- codecov_cli-11.0.0.dist-info/WHEEL +5 -0
- codecov_cli-11.0.0.dist-info/entry_points.txt +3 -0
- codecov_cli-11.0.0.dist-info/licenses/LICENSE +201 -0
- codecov_cli-11.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GithubActionsCIAdapter(CIAdapterBase):
|
|
9
|
+
# https://docs.github.com/en/actions/learn-github-actions/environment-variables
|
|
10
|
+
def detect(self) -> bool:
|
|
11
|
+
return bool(os.getenv("GITHUB_ACTIONS"))
|
|
12
|
+
|
|
13
|
+
def _get_commit_sha(self):
|
|
14
|
+
pr = self._get_pull_request_number()
|
|
15
|
+
commit = os.getenv("GITHUB_SHA")
|
|
16
|
+
|
|
17
|
+
if not pr:
|
|
18
|
+
return commit
|
|
19
|
+
|
|
20
|
+
# actions/checkout should be run with fetch-depth > 1 or set to 0 for this to work
|
|
21
|
+
completed_subprocess = subprocess.run(
|
|
22
|
+
["git", "rev-parse", "HEAD^@"], capture_output=True
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parents_hash = completed_subprocess.stdout.decode().strip().splitlines()
|
|
26
|
+
if len(parents_hash) == 2:
|
|
27
|
+
return parents_hash[1]
|
|
28
|
+
|
|
29
|
+
return commit
|
|
30
|
+
|
|
31
|
+
def _get_build_url(self):
|
|
32
|
+
server_url = os.getenv("GITHUB_SERVER_URL")
|
|
33
|
+
slug = self._get_slug()
|
|
34
|
+
build_code = self._get_build_code()
|
|
35
|
+
|
|
36
|
+
if server_url and slug and build_code:
|
|
37
|
+
return f"{server_url}/{slug}/actions/runs/{build_code}"
|
|
38
|
+
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def _get_build_code(self):
|
|
42
|
+
return os.getenv("GITHUB_RUN_ID")
|
|
43
|
+
|
|
44
|
+
def _get_job_code(self):
|
|
45
|
+
return os.getenv("GITHUB_WORKFLOW")
|
|
46
|
+
|
|
47
|
+
def _get_pull_request_number(self):
|
|
48
|
+
if not os.getenv("GITHUB_HEAD_REF"):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
pr_ref = os.getenv("GITHUB_REF")
|
|
52
|
+
|
|
53
|
+
if not pr_ref:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
match = re.search(r"refs/pull/(\d+)/merge", pr_ref)
|
|
57
|
+
|
|
58
|
+
if match is None:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
pr = match.group(1)
|
|
62
|
+
|
|
63
|
+
return pr or None
|
|
64
|
+
|
|
65
|
+
def _get_slug(self):
|
|
66
|
+
return os.getenv("GITHUB_REPOSITORY")
|
|
67
|
+
|
|
68
|
+
def _get_branch(self):
|
|
69
|
+
branch = os.getenv("GITHUB_HEAD_REF")
|
|
70
|
+
if branch:
|
|
71
|
+
return branch
|
|
72
|
+
|
|
73
|
+
branch_ref = os.getenv("GITHUB_REF")
|
|
74
|
+
|
|
75
|
+
if not branch_ref:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
match = re.search(r"refs/heads/(.*)", branch_ref)
|
|
79
|
+
|
|
80
|
+
if match is None:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
branch = match.group(1)
|
|
84
|
+
return branch or None
|
|
85
|
+
|
|
86
|
+
def _get_service(self):
|
|
87
|
+
return "github-actions"
|
|
88
|
+
|
|
89
|
+
def get_service_name(self):
|
|
90
|
+
return "GithubActions"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
from codecov_cli.helpers.git import parse_slug
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GitlabCIAdapter(CIAdapterBase):
|
|
8
|
+
# https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
|
|
9
|
+
def detect(self) -> bool:
|
|
10
|
+
return bool(os.getenv("GITLAB_CI"))
|
|
11
|
+
|
|
12
|
+
def _get_commit_sha(self):
|
|
13
|
+
return (
|
|
14
|
+
os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_SHA")
|
|
15
|
+
or os.getenv("CI_BUILD_REF")
|
|
16
|
+
or os.getenv("CI_COMMIT_SHA")
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def _get_build_url(self):
|
|
20
|
+
return os.getenv("CI_JOB_URL")
|
|
21
|
+
|
|
22
|
+
def _get_build_code(self):
|
|
23
|
+
return os.getenv("CI_BUILD_ID") or os.getenv("CI_JOB_ID")
|
|
24
|
+
|
|
25
|
+
def _get_job_code(self):
|
|
26
|
+
return os.getenv("CI_JOB_ID")
|
|
27
|
+
|
|
28
|
+
def _get_pull_request_number(self):
|
|
29
|
+
return os.getenv("CI_MERGE_REQUEST_IID")
|
|
30
|
+
|
|
31
|
+
def _get_slug(self):
|
|
32
|
+
slug = os.getenv("CI_PROJECT_PATH")
|
|
33
|
+
if slug:
|
|
34
|
+
return slug
|
|
35
|
+
|
|
36
|
+
owner = os.getenv("CI_PROJECT_NAMESPACE")
|
|
37
|
+
project_name = os.getenv("CI_PROJECT_NAME")
|
|
38
|
+
|
|
39
|
+
if owner and project_name:
|
|
40
|
+
return f"{owner}/{project_name}"
|
|
41
|
+
|
|
42
|
+
remote_address = os.getenv("CI_BUILD_REPO") or os.getenv("CI_REPOSITORY_URL")
|
|
43
|
+
|
|
44
|
+
if remote_address:
|
|
45
|
+
return parse_slug(remote_address)
|
|
46
|
+
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
def _get_branch(self):
|
|
50
|
+
return os.getenv("CI_BUILD_REF_NAME") or os.getenv("CI_COMMIT_REF_NAME")
|
|
51
|
+
|
|
52
|
+
def _get_service(self):
|
|
53
|
+
return "gitlab"
|
|
54
|
+
|
|
55
|
+
def get_service_name(self):
|
|
56
|
+
return "GitlabCI"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HerokuCIAdapter(CIAdapterBase):
|
|
7
|
+
# https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables
|
|
8
|
+
def detect(self) -> bool:
|
|
9
|
+
return bool(os.getenv("CI")) and bool(os.getenv("HEROKU_TEST_RUN_BRANCH"))
|
|
10
|
+
|
|
11
|
+
def _get_branch(self):
|
|
12
|
+
return os.getenv("HEROKU_TEST_RUN_BRANCH")
|
|
13
|
+
|
|
14
|
+
def _get_commit_sha(self):
|
|
15
|
+
return os.getenv("HEROKU_TEST_RUN_COMMIT_VERSION")
|
|
16
|
+
|
|
17
|
+
def _get_slug(self):
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def _get_service(self):
|
|
21
|
+
return "heroku"
|
|
22
|
+
|
|
23
|
+
def _get_build_url(self):
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def _get_build_code(self):
|
|
27
|
+
return os.getenv("HEROKU_TEST_RUN_ID")
|
|
28
|
+
|
|
29
|
+
def _get_job_code(self):
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def _get_pull_request_number(self):
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
def get_service_name(self):
|
|
36
|
+
return "Heroku"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JenkinsAdapter(CIAdapterBase):
|
|
7
|
+
# https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
|
8
|
+
# https://www.jenkins.io/doc/book/pipeline/multibranch/
|
|
9
|
+
|
|
10
|
+
def detect(self) -> bool:
|
|
11
|
+
return bool(os.getenv("JENKINS_URL"))
|
|
12
|
+
|
|
13
|
+
def _get_commit_sha(self):
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
def _get_build_url(self):
|
|
17
|
+
return os.getenv("BUILD_URL")
|
|
18
|
+
|
|
19
|
+
def _get_build_code(self):
|
|
20
|
+
return os.getenv("BUILD_NUMBER")
|
|
21
|
+
|
|
22
|
+
def _get_job_code(self):
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
def _get_pull_request_number(self):
|
|
26
|
+
return os.getenv("CHANGE_ID")
|
|
27
|
+
|
|
28
|
+
def _get_slug(self):
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def _get_branch(self):
|
|
32
|
+
return os.getenv("BRANCH_NAME")
|
|
33
|
+
|
|
34
|
+
def _get_service(self):
|
|
35
|
+
return "jenkins"
|
|
36
|
+
|
|
37
|
+
def get_service_name(self):
|
|
38
|
+
return "Jenkins"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
5
|
+
|
|
6
|
+
SUCCESS = 0
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LocalAdapter(CIAdapterBase):
|
|
10
|
+
def detect(self) -> bool:
|
|
11
|
+
s = subprocess.run(["git", "help"], capture_output=True)
|
|
12
|
+
return s.returncode == SUCCESS
|
|
13
|
+
|
|
14
|
+
def _get_branch(self):
|
|
15
|
+
return os.getenv("GIT_BRANCH") or os.getenv("BRANCH_NAME")
|
|
16
|
+
|
|
17
|
+
def _get_build_code(self):
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def _get_build_url(self):
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def _get_commit_sha(self):
|
|
24
|
+
return os.getenv("GIT_COMMIT")
|
|
25
|
+
|
|
26
|
+
def _get_slug(self):
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def _get_service(self):
|
|
30
|
+
return "local"
|
|
31
|
+
|
|
32
|
+
def _get_pull_request_number(self):
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
def _get_job_code(self):
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def get_service_name(self):
|
|
39
|
+
return "Local"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TeamcityAdapter(CIAdapterBase):
|
|
7
|
+
# https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
|
|
8
|
+
|
|
9
|
+
def detect(self) -> bool:
|
|
10
|
+
return bool(os.getenv("TEAMCITY_VERSION"))
|
|
11
|
+
|
|
12
|
+
def _get_branch(self):
|
|
13
|
+
return os.getenv("BRANCH_NAME")
|
|
14
|
+
|
|
15
|
+
def _get_build_code(self):
|
|
16
|
+
return os.getenv("BUILD_NUMBER")
|
|
17
|
+
|
|
18
|
+
def _get_build_url(self):
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
def _get_commit_sha(self):
|
|
22
|
+
return os.getenv("BUILD_VCS_NUMBER")
|
|
23
|
+
|
|
24
|
+
def _get_slug(self):
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def _get_service(self):
|
|
28
|
+
return "teamcity"
|
|
29
|
+
|
|
30
|
+
def _get_pull_request_number(self):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def _get_job_code(self):
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
def get_service_name(self):
|
|
37
|
+
return "Teamcity"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TravisCIAdapter(CIAdapterBase):
|
|
7
|
+
# https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
|
|
8
|
+
def detect(self) -> bool:
|
|
9
|
+
return (
|
|
10
|
+
bool(os.getenv("CI"))
|
|
11
|
+
and bool(os.getenv("TRAVIS"))
|
|
12
|
+
and not bool(os.getenv("SHIPPABLE"))
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def _get_commit_sha(self):
|
|
16
|
+
return os.getenv("TRAVIS_PULL_REQUEST_SHA") or os.getenv("TRAVIS_COMMIT")
|
|
17
|
+
|
|
18
|
+
def _get_build_url(self):
|
|
19
|
+
return os.getenv("TRAVIS_BUILD_WEB_URL")
|
|
20
|
+
|
|
21
|
+
def _get_build_code(self):
|
|
22
|
+
return os.getenv("TRAVIS_JOB_NUMBER")
|
|
23
|
+
|
|
24
|
+
def _get_job_code(self):
|
|
25
|
+
return os.getenv("TRAVIS_JOB_ID")
|
|
26
|
+
|
|
27
|
+
def _get_pull_request_number(self):
|
|
28
|
+
# The pull request number if the current job is a pull request, “false” if it’s not a pull request.
|
|
29
|
+
pr_num = os.getenv("TRAVIS_PULL_REQUEST")
|
|
30
|
+
return pr_num if pr_num != "false" else None
|
|
31
|
+
|
|
32
|
+
def _get_slug(self):
|
|
33
|
+
return os.getenv("TRAVIS_REPO_SLUG")
|
|
34
|
+
|
|
35
|
+
def _get_branch(self):
|
|
36
|
+
if os.getenv("TRAVIS_BRANCH") != os.getenv("TRAVIS_TAG"):
|
|
37
|
+
return os.getenv("TRAVIS_PULL_REQUEST_BRANCH") or os.getenv("TRAVIS_BRANCH")
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def _get_service(self):
|
|
41
|
+
return "travis"
|
|
42
|
+
|
|
43
|
+
def get_service_name(self):
|
|
44
|
+
return "Travis"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WoodpeckerCIAdapter(CIAdapterBase):
|
|
7
|
+
# https://woodpecker-ci.org/docs/usage/environment
|
|
8
|
+
def detect(self) -> bool:
|
|
9
|
+
return os.getenv("CI") == "woodpecker"
|
|
10
|
+
|
|
11
|
+
def _get_branch(self):
|
|
12
|
+
return os.getenv("CI_COMMIT_SOURCE_BRANCH") or os.getenv("CI_COMMIT_BRANCH")
|
|
13
|
+
|
|
14
|
+
def _get_build_code(self):
|
|
15
|
+
return os.getenv("CI_BUILD_NUMBER")
|
|
16
|
+
|
|
17
|
+
def _get_build_url(self):
|
|
18
|
+
return os.getenv("CI_BUILD_LINK")
|
|
19
|
+
|
|
20
|
+
def _get_commit_sha(self):
|
|
21
|
+
return os.getenv("CI_COMMIT_SHA")
|
|
22
|
+
|
|
23
|
+
def _get_slug(self):
|
|
24
|
+
return os.getenv("CI_REPO")
|
|
25
|
+
|
|
26
|
+
def _get_service(self):
|
|
27
|
+
return "woodpecker"
|
|
28
|
+
|
|
29
|
+
def _get_pull_request_number(self):
|
|
30
|
+
return os.getenv("CI_COMMIT_PULL_REQUEST")
|
|
31
|
+
|
|
32
|
+
def _get_job_code(self):
|
|
33
|
+
return os.getenv("CI_JOB_NUMBER")
|
|
34
|
+
|
|
35
|
+
def get_service_name(self):
|
|
36
|
+
return "Woodpecker"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import pathlib
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from codecov_cli.helpers.versioning_systems import get_versioning_system
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("codecovcli")
|
|
10
|
+
|
|
11
|
+
CODECOV_API_URL = "https://api.codecov.io"
|
|
12
|
+
CODECOV_INGEST_URL = "https://ingest.codecov.io"
|
|
13
|
+
LEGACY_CODECOV_API_URL = "https://codecov.io"
|
|
14
|
+
|
|
15
|
+
# Relative to the project root
|
|
16
|
+
CODECOV_YAML_RECOGNIZED_DIRECTORIES = [
|
|
17
|
+
"",
|
|
18
|
+
".github/",
|
|
19
|
+
"dev/",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
CODECOV_YAML_RECOGNIZED_FILENAMES = [
|
|
23
|
+
"codecov.yml",
|
|
24
|
+
"codecov.yaml",
|
|
25
|
+
".codecov.yml",
|
|
26
|
+
".codecov.yaml",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _find_codecov_yamls():
|
|
31
|
+
vcs = get_versioning_system()
|
|
32
|
+
vcs_root = vcs.get_network_root() if vcs else None
|
|
33
|
+
project_root = vcs_root if vcs_root else pathlib.Path.cwd()
|
|
34
|
+
|
|
35
|
+
yamls = []
|
|
36
|
+
for directory in CODECOV_YAML_RECOGNIZED_DIRECTORIES:
|
|
37
|
+
dir_candidate = project_root / directory
|
|
38
|
+
if not dir_candidate.exists() or not dir_candidate.is_dir():
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
for filename in CODECOV_YAML_RECOGNIZED_FILENAMES:
|
|
42
|
+
file_candidate = dir_candidate / filename
|
|
43
|
+
if file_candidate.exists() and file_candidate.is_file():
|
|
44
|
+
yamls.append(file_candidate)
|
|
45
|
+
|
|
46
|
+
return yamls
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_cli_config(codecov_yml_path: t.Optional[pathlib.Path]) -> t.Optional[dict]:
|
|
50
|
+
if not codecov_yml_path:
|
|
51
|
+
yamls = _find_codecov_yamls()
|
|
52
|
+
codecov_yml_path = yamls[0] if yamls else None
|
|
53
|
+
|
|
54
|
+
if not codecov_yml_path:
|
|
55
|
+
logger.warning("No config file could be found. Ignoring config.")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
if not codecov_yml_path.exists() or not codecov_yml_path.is_file():
|
|
59
|
+
logger.warning(
|
|
60
|
+
f"Config file {codecov_yml_path} not found, or is not a file. Ignoring config."
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
logger.debug(f"Loading config from {codecov_yml_path}")
|
|
65
|
+
with open(codecov_yml_path, "r") as file_stream:
|
|
66
|
+
return yaml.safe_load(file_stream.read())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
slug_without_subgroups_regex = re.compile(r"[^/\s]+\/[^/\s]+$")
|
|
4
|
+
slug_with_subgroups_regex = re.compile(r"[^/\s]+(\/[^/\s]+)+$")
|
|
5
|
+
encoded_slug_regex = re.compile(r"[^:\s]+(:::[^:\s]+)*(::::[^:\s]+){1}$")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def encode_slug(slug: str):
|
|
9
|
+
if slug_with_subgroups_is_invalid(slug):
|
|
10
|
+
raise ValueError("The provided slug is invalid")
|
|
11
|
+
owner, repo = slug.rsplit("/", 1)
|
|
12
|
+
encoded_owner = ":::".join(owner.split("/"))
|
|
13
|
+
encoded_slug = "::::".join([encoded_owner, repo])
|
|
14
|
+
return encoded_slug
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def decode_slug(slug: str):
|
|
18
|
+
if slug_encoded_incorrectly(slug):
|
|
19
|
+
raise ValueError("The slug is not encoded correctly")
|
|
20
|
+
|
|
21
|
+
owner, repo = slug.split("::::", 1)
|
|
22
|
+
decoded_owner = "/".join(owner.split(":::"))
|
|
23
|
+
decoded_slug = "/".join([decoded_owner, repo])
|
|
24
|
+
return decoded_slug
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def slug_without_subgroups_is_invalid(slug: str):
|
|
28
|
+
"""
|
|
29
|
+
Checks if slug is in the form of owner/repo
|
|
30
|
+
Returns True if it's invalid, otherwise return False
|
|
31
|
+
"""
|
|
32
|
+
return not slug or not slug_without_subgroups_regex.match(slug)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def slug_with_subgroups_is_invalid(slug: str):
|
|
36
|
+
"""
|
|
37
|
+
Checks if slug is in the form of owner/repo or owner/subgroup/repo
|
|
38
|
+
Returns True if it's invalid, otherwise return False
|
|
39
|
+
"""
|
|
40
|
+
return not slug or not slug_with_subgroups_regex.match(slug)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def slug_encoded_incorrectly(slug: str):
|
|
44
|
+
"""
|
|
45
|
+
Checks if slug is encoded incorrectly based on the encoding mechanism we use.
|
|
46
|
+
Checks if slug is in the form of owner:::subowner::::repo or owner::::repo
|
|
47
|
+
Returns True if invalid, otherwise returns False
|
|
48
|
+
"""
|
|
49
|
+
return not slug or not encoded_slug_regex.match(slug)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import re
|
|
6
|
+
from typing import Generator, List, Optional, Pattern
|
|
7
|
+
|
|
8
|
+
from codecov_cli.helpers.glob import translate
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("codecovcli")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _is_included(
|
|
14
|
+
filename_include_regex: Pattern,
|
|
15
|
+
multipart_include_regex: Optional[Pattern],
|
|
16
|
+
path: pathlib.Path,
|
|
17
|
+
):
|
|
18
|
+
return filename_include_regex.match(path.name) and (
|
|
19
|
+
multipart_include_regex is None
|
|
20
|
+
or multipart_include_regex.match(path.resolve().as_posix())
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_excluded(
|
|
25
|
+
filename_exclude_regex: Optional[Pattern],
|
|
26
|
+
multipart_exclude_regex: Optional[Pattern],
|
|
27
|
+
path: pathlib.Path,
|
|
28
|
+
):
|
|
29
|
+
return (
|
|
30
|
+
filename_exclude_regex is not None and filename_exclude_regex.match(path.name)
|
|
31
|
+
) or (
|
|
32
|
+
multipart_exclude_regex is not None
|
|
33
|
+
and multipart_exclude_regex.match(path.as_posix())
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def search_files(
|
|
38
|
+
folder_to_search: pathlib.Path,
|
|
39
|
+
folders_to_ignore: List[str],
|
|
40
|
+
*,
|
|
41
|
+
filename_include_regex: Pattern,
|
|
42
|
+
filename_exclude_regex: Optional[Pattern] = None,
|
|
43
|
+
multipart_include_regex: Optional[Pattern] = None,
|
|
44
|
+
multipart_exclude_regex: Optional[Pattern] = None,
|
|
45
|
+
search_for_directories: bool = False,
|
|
46
|
+
) -> Generator[pathlib.Path, None, None]:
|
|
47
|
+
""" "
|
|
48
|
+
Searches for files or directories in a given folder
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
folder_to_search (pathlib.Path): in which folder you want the search to be
|
|
52
|
+
folders_to_ignore (list of str): what folders inside the folder_to_search to ignore and not search inside
|
|
53
|
+
filename_include_regex (regex): Regex for filenames only, this does not include the full path of the file
|
|
54
|
+
filename_exclude_regex (regex): Regex for filenames only, this does not include the full path of the file
|
|
55
|
+
multipart_include_regex (regex): Regex for full path of the files you want to include
|
|
56
|
+
multipart_exclude_regex (regex): Regex for full path of the files you want to exclude
|
|
57
|
+
search_for_directories (bool)
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
this_is_included = functools.partial(
|
|
61
|
+
_is_included, filename_include_regex, multipart_include_regex
|
|
62
|
+
)
|
|
63
|
+
this_is_excluded = functools.partial(
|
|
64
|
+
_is_excluded, filename_exclude_regex, multipart_exclude_regex
|
|
65
|
+
)
|
|
66
|
+
for dirpath, dirnames, filenames in os.walk(folder_to_search):
|
|
67
|
+
dirs_to_remove = set(d for d in dirnames if d in folders_to_ignore)
|
|
68
|
+
|
|
69
|
+
if multipart_exclude_regex is not None:
|
|
70
|
+
dirs_to_remove.union(
|
|
71
|
+
directory
|
|
72
|
+
for directory in dirnames
|
|
73
|
+
if multipart_exclude_regex.match(
|
|
74
|
+
(pathlib.Path(dirpath) / directory).as_posix()
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for directory in dirs_to_remove:
|
|
79
|
+
# Removing to ensure we don't even try to search those
|
|
80
|
+
# This is the documented way of doing this on python docs
|
|
81
|
+
dirnames.remove(directory)
|
|
82
|
+
|
|
83
|
+
if search_for_directories:
|
|
84
|
+
for directory in dirnames:
|
|
85
|
+
dir_path = pathlib.Path(dirpath) / directory
|
|
86
|
+
if not this_is_excluded(dir_path) and this_is_included(dir_path):
|
|
87
|
+
yield dir_path
|
|
88
|
+
else:
|
|
89
|
+
for single_filename in filenames:
|
|
90
|
+
file_path = pathlib.Path(dirpath) / single_filename
|
|
91
|
+
if not this_is_excluded(file_path) and this_is_included(file_path):
|
|
92
|
+
yield file_path
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def globs_to_regex(patterns: List[str]) -> Optional[Pattern]:
|
|
96
|
+
"""
|
|
97
|
+
Converts a list of glob patterns to a combined ORed regex
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
patterns (List[str]): a list of globs, possibly empty
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
(Pattern): a combined ORed regex, or None if patterns is an empty list
|
|
104
|
+
"""
|
|
105
|
+
# if patterns is an empty list, avoid returning re.compile("") since it matches everything
|
|
106
|
+
if not patterns:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
regex_patterns = []
|
|
110
|
+
for pattern in patterns:
|
|
111
|
+
regex_pattern = translate(pattern, recursive=True, include_hidden=True)
|
|
112
|
+
logger.debug(f"Translating `{pattern}` into `{regex_pattern}`")
|
|
113
|
+
regex_patterns.append(regex_pattern)
|
|
114
|
+
return re.compile("|".join(regex_patterns))
|