github2gerrit 2.4.2__tar.gz → 2.4.3__tar.gz
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.
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.pre-commit-config.yaml +5 -5
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/PKG-INFO +1 -1
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/action.yaml +1 -1
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gitreview.py +69 -17
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gitreview.py +205 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/uv.lock +237 -173
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.aislop/config.yml +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.editorconfig +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.gitignore +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.gitlint +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.markdownlint.yaml +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/.yamllint +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/AGENTS.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/LICENSE +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/LICENSES/Apache-2.0.txt +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/README.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/REUSE.toml +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/SECURITY.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/docs/COMMIT_RULES.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/docs/cli.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/docs/development.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/docs/features.md +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/docs/github2gerrit_token_permissions_classic.png +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/pyproject.toml +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/sitecustomize.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/__init__.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/approvers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/cli.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/commit_normalization.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/commit_rules.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/config.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/config_ini.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/constants.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/core.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/duplicate_detection.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/error_codes.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/external_api.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gerrit_pr_closer.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gerrit_query.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gerrit_rest.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gerrit_ssh.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gerrit_urls.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/github_api.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/gitutils.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/mapping_comment.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/models.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/netrc.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/orchestrator/__init__.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/pr_approval.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/pr_commands.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/pr_content_filter.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/pr_directives.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/project_names.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/reconcile_matcher.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/rich_display.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/rich_logging.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/similarity.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/ssh_agent_setup.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/ssh_common.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/ssh_config_parser.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/ssh_discovery.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/trailers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/trust.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/src/github2gerrit/utils.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/conftest.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/fixtures/__init__.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/fixtures/make_repo.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/fixtures/ssh_config_samples.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_action_environment_mapping.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_action_outputs.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_action_pr_number_handling.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_action_step_validation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_approver_sources.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_automation_only.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_bulk_fan_out.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cache_isolation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_change_id_deduplication.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_clean_squash_title.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cli.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cli_helpers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cli_netrc_options.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cli_outputs_file.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_cli_url_and_dryrun.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_commit_normalization.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_commit_rules.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_composite_action_coverage.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_config_and_reviewers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_config_helpers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_close_pr_policy.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_config_and_errors.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_gerrit_backref_comment.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_gerrit_push_errors.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_gerrit_rest_results.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_integration_fixture_repo.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_prepare_commits.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_shallow_clone.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_ssh_setup.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_core_ssrf_protection.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_dependency_supersession.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_dns_validation_and_no_gerrit.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_duplicate_detection.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_email_case_normalization.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_error_codes.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_external_api_framework.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_final_state_reconciliation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_force_flag_cli.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_fork_approval_gate.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_fork_pr_hardening.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_fork_transfer_record.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_change_id_footer.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_change_status_checks.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_pr_closer.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_rest_client.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_ssh_abandon.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_urls.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gerrit_urls_more.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ghe_and_gitreview_args.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_github_api_error_handling.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_github_api_helpers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_github_api_retry_and_helpers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_gitutils_helpers.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_issue_157_regressions.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_mapping_comment_additional.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_mapping_comment_digest_and_backref.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_metadata_and_reconciliation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_metadata_trailer_separation_bug.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_misc_small_coverage.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_netrc.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_orphan_rest_side_effects.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_closed_handling.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_command_authorisation.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_commands.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_content_filter.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_content_filter_integration.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_pr_update_detection.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_precommit_mypy_pins.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_project_names.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_reconciliation_extracted_module.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_reconciliation_plan_and_orphans.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_reconciliation_scenarios.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_scheduled_sweep.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_agent.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_agent_ownership.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_artifact_prevention.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_common.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_discovery.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_ssh_discovery_dry_run.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_sync_mypy_pins.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_trailers_additional.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_url_parser.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/test_utils.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/unit/test_config_integration.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/unit/test_ssh_config_parser.py +0 -0
- {github2gerrit-2.4.2 → github2gerrit-2.4.3}/tests/workflow_harness.py +0 -0
|
@@ -86,7 +86,7 @@ repos:
|
|
|
86
86
|
types: [yaml]
|
|
87
87
|
|
|
88
88
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
89
|
-
rev:
|
|
89
|
+
rev: f12be1ebaa5351c1fc76472de98db2c3446c8253 # frozen: v0.16.10
|
|
90
90
|
hooks:
|
|
91
91
|
- id: ruff
|
|
92
92
|
files: ^(src|scripts|tests)/.+\.py$|^sitecustomize\.py$
|
|
@@ -104,12 +104,12 @@ repos:
|
|
|
104
104
|
pass_filenames: false
|
|
105
105
|
|
|
106
106
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
107
|
-
rev:
|
|
107
|
+
rev: 2834ec6639549dd6796205c8f011dedcd587288b # frozen: v2.4.0
|
|
108
108
|
hooks:
|
|
109
109
|
- id: mypy
|
|
110
110
|
files: ^(src|scripts|tests)/.+\.py$|^sitecustomize\.py$
|
|
111
111
|
additional_dependencies:
|
|
112
|
-
- mypy==2.
|
|
112
|
+
- mypy==2.4.0
|
|
113
113
|
- click==8.5.0
|
|
114
114
|
- pytest==9.1.1
|
|
115
115
|
- responses==0.26.3
|
|
@@ -153,13 +153,13 @@ repos:
|
|
|
153
153
|
- id: codespell
|
|
154
154
|
|
|
155
155
|
- repo: https://github.com/DetachHead/basedpyright-prek-mirror
|
|
156
|
-
rev:
|
|
156
|
+
rev: a11caebebdbf797e1e96611123db279b334a7675 # frozen: 1.40.2
|
|
157
157
|
hooks:
|
|
158
158
|
- id: basedpyright
|
|
159
159
|
files: ^(src|scripts|tests)/.+\.py$|^sitecustomize\.py$
|
|
160
160
|
|
|
161
161
|
- repo: https://github.com/lfreleng-actions/gha-workflow-linter
|
|
162
|
-
rev:
|
|
162
|
+
rev: b23b60e5884e985320a030e6114032152e2373cd # frozen: v1.7.1
|
|
163
163
|
hooks:
|
|
164
164
|
- id: gha-workflow-linter
|
|
165
165
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: github2gerrit
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.3
|
|
4
4
|
Summary: Submit a GitHub pull request to a Gerrit repository.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit-action
|
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit-action
|
|
@@ -266,7 +266,7 @@ runs:
|
|
|
266
266
|
- name: "Setup uv"
|
|
267
267
|
if: steps.disabled-check.outputs.disabled != 'true'
|
|
268
268
|
# yamllint disable-line rule:line-length
|
|
269
|
-
uses: astral-sh/setup-uv@
|
|
269
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
270
270
|
with:
|
|
271
271
|
enable-cache: false
|
|
272
272
|
|
|
@@ -19,15 +19,17 @@ Design goals
|
|
|
19
19
|
``config.py`` (host-only), ``core.py`` (host+port+project), and
|
|
20
20
|
``duplicate_detection.py`` (host+project) is supported, plus the
|
|
21
21
|
``base_path`` field used by the sister ``dependamerge`` project.
|
|
22
|
-
* **Consistent
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
* **Consistent parsing** — a file with a ``[gerrit]`` section is read
|
|
23
|
+
with ``configparser``, as git-review reads it. Any other file falls
|
|
24
|
+
back to a single set of precompiled patterns that tolerates optional
|
|
25
|
+
whitespace around ``=`` and is case-insensitive on keys.
|
|
25
26
|
* **Bug-free fetching** — URL-encodes branch names, deduplicates the
|
|
26
27
|
branch list, and validates raw URLs before opening them.
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
from __future__ import annotations
|
|
30
31
|
|
|
32
|
+
import configparser
|
|
31
33
|
import logging
|
|
32
34
|
import os
|
|
33
35
|
import re
|
|
@@ -45,9 +47,54 @@ log = logging.getLogger(__name__)
|
|
|
45
47
|
DEFAULT_GERRIT_PORT: int = 29418
|
|
46
48
|
"""Default Gerrit SSH port when the ``port=`` line is absent."""
|
|
47
49
|
|
|
50
|
+
# Fallback scan for files configparser cannot read (see
|
|
51
|
+
# _read_gerrit_section): keys at the start of a line, anywhere in the file.
|
|
48
52
|
_HOST_RE = re.compile(r"(?mi)^host[ \t]*=[ \t]*(.+?)[ \t\r]*$")
|
|
49
53
|
_PORT_RE = re.compile(r"(?mi)^port[ \t]*=[ \t]*(\d+)[ \t\r]*$")
|
|
50
54
|
_PROJECT_RE = re.compile(r"(?mi)^project[ \t]*=[ \t]*(.+?)[ \t\r]*$")
|
|
55
|
+
_PORT_VALUE_RE = re.compile(r"\d+")
|
|
56
|
+
|
|
57
|
+
_Fields = tuple[str | None, str | None, str | None]
|
|
58
|
+
"""Raw ``host``, ``port`` and ``project`` values; ``None`` when absent."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_gerrit_section(text: str) -> _Fields | None:
|
|
62
|
+
"""Read the fields as git-review does, through ``configparser``.
|
|
63
|
+
|
|
64
|
+
That settles indentation, continuation lines, other sections and
|
|
65
|
+
``[DEFAULT]`` inheritance (an empty ``[gerrit]`` value included)
|
|
66
|
+
exactly as git-review sees them. Returns ``None`` when the text has
|
|
67
|
+
no ``[gerrit]`` section or ``configparser`` rejects it; git-review
|
|
68
|
+
cannot read such a file either, so the caller falls back to the
|
|
69
|
+
lenient scan.
|
|
70
|
+
"""
|
|
71
|
+
parser = configparser.ConfigParser(interpolation=None)
|
|
72
|
+
try:
|
|
73
|
+
parser.read_string(text)
|
|
74
|
+
except configparser.Error as exc:
|
|
75
|
+
log.debug(".gitreview: not readable as INI (%s); scanning lines", exc)
|
|
76
|
+
return None
|
|
77
|
+
if not parser.has_section("gerrit"):
|
|
78
|
+
return None
|
|
79
|
+
section = parser["gerrit"]
|
|
80
|
+
|
|
81
|
+
def first_line(key: str) -> str | None:
|
|
82
|
+
# A continuation makes a multi-line value; a field is one line.
|
|
83
|
+
value = section.get(key)
|
|
84
|
+
return None if value is None else value.strip().split("\n", 1)[0]
|
|
85
|
+
|
|
86
|
+
return first_line("host"), first_line("port"), first_line("project")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _scan_fields(text: str) -> _Fields:
|
|
90
|
+
"""Find the fields by line pattern anywhere in *text*."""
|
|
91
|
+
matches = (
|
|
92
|
+
_HOST_RE.search(text),
|
|
93
|
+
_PORT_RE.search(text),
|
|
94
|
+
_PROJECT_RE.search(text),
|
|
95
|
+
)
|
|
96
|
+
host, port, project = (m.group(1) if m else None for m in matches)
|
|
97
|
+
return host, port, project
|
|
51
98
|
|
|
52
99
|
|
|
53
100
|
@dataclass(frozen=True)
|
|
@@ -144,10 +191,14 @@ def parse_gitreview(text: str) -> GitReviewInfo | None:
|
|
|
144
191
|
|
|
145
192
|
This parser is intentionally lenient:
|
|
146
193
|
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
194
|
+
* A file with a ``[gerrit]`` section is read with ``configparser``,
|
|
195
|
+
as git-review reads it: keys are case-insensitive and may be
|
|
196
|
+
indented, continuation lines and other sections are ignored, and
|
|
197
|
+
``[DEFAULT]`` supplies any field ``[gerrit]`` does not set.
|
|
198
|
+
* The ``[gerrit]`` header itself is **not** required. A file without
|
|
199
|
+
one, or one ``configparser`` rejects, is scanned for ``host=``,
|
|
200
|
+
``port=`` and ``project=`` lines anywhere, with optional
|
|
201
|
+
whitespace around ``=``.
|
|
151
202
|
|
|
152
203
|
Args:
|
|
153
204
|
text: Raw text content of a ``.gitreview`` file.
|
|
@@ -156,23 +207,24 @@ def parse_gitreview(text: str) -> GitReviewInfo | None:
|
|
|
156
207
|
A :class:`GitReviewInfo` if at least ``host`` is present and
|
|
157
208
|
non-empty, otherwise ``None``.
|
|
158
209
|
"""
|
|
159
|
-
|
|
160
|
-
|
|
210
|
+
fields = _read_gerrit_section(text)
|
|
211
|
+
host, port_text, project_text = (
|
|
212
|
+
fields if fields is not None else _scan_fields(text)
|
|
213
|
+
)
|
|
214
|
+
if host is None:
|
|
161
215
|
log.debug(".gitreview: no host= line found")
|
|
162
216
|
return None
|
|
163
217
|
|
|
164
|
-
host =
|
|
218
|
+
host = host.strip()
|
|
165
219
|
if not host:
|
|
166
220
|
log.debug(".gitreview: host= line is empty")
|
|
167
221
|
return None
|
|
168
222
|
|
|
169
|
-
|
|
170
|
-
|
|
223
|
+
port_text = (port_text or "").strip()
|
|
224
|
+
port_given = _PORT_VALUE_RE.fullmatch(port_text) is not None
|
|
225
|
+
port = int(port_text) if port_given else DEFAULT_GERRIT_PORT
|
|
171
226
|
|
|
172
|
-
project = ""
|
|
173
|
-
project_match = _PROJECT_RE.search(text)
|
|
174
|
-
if project_match:
|
|
175
|
-
project = project_match.group(1).strip().removesuffix(".git")
|
|
227
|
+
project = (project_text or "").strip().removesuffix(".git")
|
|
176
228
|
|
|
177
229
|
base_path = derive_base_path(host)
|
|
178
230
|
|
|
@@ -181,7 +233,7 @@ def parse_gitreview(text: str) -> GitReviewInfo | None:
|
|
|
181
233
|
port=port,
|
|
182
234
|
project=project,
|
|
183
235
|
base_path=base_path,
|
|
184
|
-
port_given=
|
|
236
|
+
port_given=port_given,
|
|
185
237
|
)
|
|
186
238
|
log.debug(
|
|
187
239
|
"Parsed .gitreview: host=%s, port=%d, project=%s, base_path=%s",
|
|
@@ -75,6 +75,19 @@ NON_DEFAULT_PORT = (
|
|
|
75
75
|
"[gerrit]\nhost=gerrit.acme.org\nport=29419\nproject=acme/widgets.git\n"
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
# The form LF automation wrote into ONAP repositories in 2020, as found in
|
|
79
|
+
# onap/cps: a leading blank line and every line indented by eight spaces.
|
|
80
|
+
# git-review reads it, since configparser ignores leading whitespace.
|
|
81
|
+
INDENTED_GITREVIEW = (
|
|
82
|
+
"\n"
|
|
83
|
+
" [gerrit]\n"
|
|
84
|
+
" host=gerrit.onap.org\n"
|
|
85
|
+
" port=29418\n"
|
|
86
|
+
" project=cps\n"
|
|
87
|
+
" defaultbranch=master\n"
|
|
88
|
+
" "
|
|
89
|
+
)
|
|
90
|
+
|
|
78
91
|
|
|
79
92
|
# -----------------------------------------------------------------------
|
|
80
93
|
# GitReviewInfo data model
|
|
@@ -223,6 +236,15 @@ class TestParseGitreview:
|
|
|
223
236
|
assert info.port == 29419
|
|
224
237
|
assert info.port_given is True
|
|
225
238
|
|
|
239
|
+
def test_unicode_decimal_port(self) -> None:
|
|
240
|
+
# int() accepts any Unicode decimal digits, as the \d in the port
|
|
241
|
+
# pattern does; Arabic-Indic "٢٩٤١٩" is 29419.
|
|
242
|
+
text = "[gerrit]\nhost=h.example.org\nport=\u0662\u0669\u0664\u0661\u0669\n"
|
|
243
|
+
info = parse_gitreview(text)
|
|
244
|
+
assert info is not None
|
|
245
|
+
assert info.port == 29419
|
|
246
|
+
assert info.port_given is True
|
|
247
|
+
|
|
226
248
|
def test_empty_string(self) -> None:
|
|
227
249
|
assert parse_gitreview("") is None
|
|
228
250
|
|
|
@@ -253,6 +275,181 @@ class TestParseGitreview:
|
|
|
253
275
|
assert info is not None
|
|
254
276
|
assert info.host == "gerrit.example.org"
|
|
255
277
|
|
|
278
|
+
def test_space_indented_keys(self) -> None:
|
|
279
|
+
info = parse_gitreview(INDENTED_GITREVIEW)
|
|
280
|
+
assert info is not None
|
|
281
|
+
assert info.host == "gerrit.onap.org"
|
|
282
|
+
assert info.port == 29418
|
|
283
|
+
assert info.port_given is True
|
|
284
|
+
assert info.project == "cps"
|
|
285
|
+
|
|
286
|
+
def test_tab_indented_keys(self) -> None:
|
|
287
|
+
text = (
|
|
288
|
+
"[gerrit]\n\thost=gerrit.example.org\n\tport=29419\n\tproject=a/b\n"
|
|
289
|
+
)
|
|
290
|
+
info = parse_gitreview(text)
|
|
291
|
+
assert info is not None
|
|
292
|
+
assert info.host == "gerrit.example.org"
|
|
293
|
+
assert info.port == 29419
|
|
294
|
+
assert info.project == "a/b"
|
|
295
|
+
|
|
296
|
+
def test_key_inside_a_line_is_not_matched(self) -> None:
|
|
297
|
+
text = "[gerrit]\nnote = set host=wrong.example.org\nxhost=wrong\n"
|
|
298
|
+
assert parse_gitreview(text) is None
|
|
299
|
+
|
|
300
|
+
def test_headerless_file_is_searched_whole(self) -> None:
|
|
301
|
+
info = parse_gitreview("host=h.example.org\nport=29419\nproject=p\n")
|
|
302
|
+
assert info is not None
|
|
303
|
+
assert (info.host, info.port, info.project) == (
|
|
304
|
+
"h.example.org",
|
|
305
|
+
29419,
|
|
306
|
+
"p",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def test_file_configparser_rejects_is_searched_whole(self) -> None:
|
|
310
|
+
# A duplicate section makes configparser raise, so git-review
|
|
311
|
+
# cannot read this file at all; the lenient scan still serves it.
|
|
312
|
+
text = "[gerrit]\nhost=a.example.org\n[gerrit]\nhost=b.example.org\n"
|
|
313
|
+
info = parse_gitreview(text)
|
|
314
|
+
assert info is not None
|
|
315
|
+
assert info.host == "a.example.org"
|
|
316
|
+
|
|
317
|
+
@pytest.mark.parametrize("key", ["host", "port", "project"])
|
|
318
|
+
def test_continuation_line_is_not_a_key(self, key: str) -> None:
|
|
319
|
+
# configparser folds a line indented deeper than the option
|
|
320
|
+
# above it into that option's value; git-review never sees it
|
|
321
|
+
# as a key, so neither may this parser.
|
|
322
|
+
value = {"host": "wrong.example.org", "port": "1", "project": "wrong"}
|
|
323
|
+
text = (
|
|
324
|
+
"[gerrit]\n"
|
|
325
|
+
"host=right.example.org\n"
|
|
326
|
+
"port=29419\n"
|
|
327
|
+
"project=right\n"
|
|
328
|
+
"note = explanation\n"
|
|
329
|
+
f" {key}={value[key]}\n"
|
|
330
|
+
)
|
|
331
|
+
info = parse_gitreview(text)
|
|
332
|
+
assert info is not None
|
|
333
|
+
assert (info.host, info.port, info.project) == (
|
|
334
|
+
"right.example.org",
|
|
335
|
+
29419,
|
|
336
|
+
"right",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@pytest.mark.parametrize(
|
|
340
|
+
"gap",
|
|
341
|
+
["\n", "# comment\n", "; comment\n"],
|
|
342
|
+
ids=["blank", "hash", "semi"],
|
|
343
|
+
)
|
|
344
|
+
def test_continuation_survives_blank_and_comment_lines(
|
|
345
|
+
self, gap: str
|
|
346
|
+
) -> None:
|
|
347
|
+
text = f"[gerrit]\nnote = x\n{gap} host=wrong.example.org\n"
|
|
348
|
+
assert parse_gitreview(text) is None
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# Layouts on which parse_gitreview must agree with configparser, the
|
|
352
|
+
# reader git-review uses. Each is a valid .gitreview a repository could
|
|
353
|
+
# hold; the parser is lenient beyond this, but never contrary to it.
|
|
354
|
+
CONFIGPARSER_LAYOUTS = [
|
|
355
|
+
pytest.param(TYPICAL_GITREVIEW, id="flat"),
|
|
356
|
+
pytest.param(INDENTED_GITREVIEW, id="all-indented"),
|
|
357
|
+
pytest.param(
|
|
358
|
+
"[gerrit]\n\thost=h.example.org\n\tport=29419\n\tproject=p\n",
|
|
359
|
+
id="tab-indented",
|
|
360
|
+
),
|
|
361
|
+
pytest.param(
|
|
362
|
+
"[gerrit]\n host=h.example.org\n",
|
|
363
|
+
id="indented-under-flat-header",
|
|
364
|
+
),
|
|
365
|
+
pytest.param(
|
|
366
|
+
"[gerrit]\n host=h.example.org\nport=29419\n",
|
|
367
|
+
id="dedent-after-indent",
|
|
368
|
+
),
|
|
369
|
+
pytest.param(
|
|
370
|
+
"[gerrit]\nnote = x\n host=wrong.example.org\n", id="continuation"
|
|
371
|
+
),
|
|
372
|
+
pytest.param(
|
|
373
|
+
"[gerrit]\nnote = x\n host=wrong.example.org\nhost=h.example.org\n",
|
|
374
|
+
id="continuation-then-key",
|
|
375
|
+
),
|
|
376
|
+
pytest.param(
|
|
377
|
+
"[gerrit]\nhost=h.example.org\n port=1\n", id="continuation-of-host"
|
|
378
|
+
),
|
|
379
|
+
pytest.param(
|
|
380
|
+
"[gerrit]\n note = x\n port=1\n host=h.example.org\n",
|
|
381
|
+
id="deeper-option-ends-run",
|
|
382
|
+
),
|
|
383
|
+
pytest.param(
|
|
384
|
+
"[other]\n host=wrong.example.org\n port=1\n project=wrong\n"
|
|
385
|
+
"[gerrit]\nhost=right.example.org\nport=29419\nproject=right\n",
|
|
386
|
+
id="indented-keys-in-other-section",
|
|
387
|
+
),
|
|
388
|
+
pytest.param(
|
|
389
|
+
"[other]\nhost=wrong.example.org\nport=1\nproject=wrong\n"
|
|
390
|
+
"[gerrit]\nhost=right.example.org\nport=29419\nproject=right\n",
|
|
391
|
+
id="flat-keys-in-earlier-section",
|
|
392
|
+
),
|
|
393
|
+
pytest.param(
|
|
394
|
+
"[gerrit]\nhost=right.example.org\n[other]\nport=1\nproject=wrong\n",
|
|
395
|
+
id="flat-keys-in-later-section",
|
|
396
|
+
),
|
|
397
|
+
pytest.param(
|
|
398
|
+
"[DEFAULT]\nhost=default.example.org\nport=29419\n[gerrit]\nproject=p\n",
|
|
399
|
+
id="default-section-fills-gerrit",
|
|
400
|
+
),
|
|
401
|
+
pytest.param(
|
|
402
|
+
"[DEFAULT]\nhost=default.example.org\nport=1\n"
|
|
403
|
+
"[gerrit]\nhost=right.example.org\nport=29419\n",
|
|
404
|
+
id="gerrit-overrides-earlier-default",
|
|
405
|
+
),
|
|
406
|
+
pytest.param(
|
|
407
|
+
"[gerrit]\nhost=right.example.org\n[DEFAULT]\nhost=default.example.org\n",
|
|
408
|
+
id="gerrit-overrides-later-default",
|
|
409
|
+
),
|
|
410
|
+
pytest.param(
|
|
411
|
+
"[DEFAULT]\nhost=default.example.org\n[gerrit]\nhost=\n",
|
|
412
|
+
id="empty-host-overrides-default",
|
|
413
|
+
),
|
|
414
|
+
pytest.param(
|
|
415
|
+
"[DEFAULT]\nport=1\n[gerrit]\nhost=h.example.org\nport=\n",
|
|
416
|
+
id="empty-port-overrides-default",
|
|
417
|
+
),
|
|
418
|
+
pytest.param(
|
|
419
|
+
"[DEFAULT]\nproject=wrong\n[gerrit]\nhost=h.example.org\nproject=\n",
|
|
420
|
+
id="empty-project-overrides-default",
|
|
421
|
+
),
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _first_line(value: str) -> str:
|
|
426
|
+
"""Return the first line of a configparser value, stripped.
|
|
427
|
+
|
|
428
|
+
configparser joins continuation lines into the value; a .gitreview
|
|
429
|
+
field is single-line, so only the first line is comparable.
|
|
430
|
+
"""
|
|
431
|
+
return value.strip().split("\n", 1)[0].strip()
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@pytest.mark.parametrize("text", CONFIGPARSER_LAYOUTS)
|
|
435
|
+
def test_agrees_with_configparser(text: str) -> None:
|
|
436
|
+
import configparser
|
|
437
|
+
|
|
438
|
+
parser = configparser.ConfigParser(interpolation=None)
|
|
439
|
+
parser.read_string(text)
|
|
440
|
+
section = parser["gerrit"]
|
|
441
|
+
info = parse_gitreview(text)
|
|
442
|
+
expected_host = _first_line(section.get("host", ""))
|
|
443
|
+
if not expected_host:
|
|
444
|
+
assert info is None
|
|
445
|
+
return
|
|
446
|
+
assert info is not None
|
|
447
|
+
assert info.host == expected_host
|
|
448
|
+
port = _first_line(section.get("port", "")) or str(DEFAULT_GERRIT_PORT)
|
|
449
|
+
assert info.port == int(port)
|
|
450
|
+
project = _first_line(section.get("project", "")).removesuffix(".git")
|
|
451
|
+
assert info.project == project
|
|
452
|
+
|
|
256
453
|
|
|
257
454
|
# -----------------------------------------------------------------------
|
|
258
455
|
# read_local_gitreview — local file reader
|
|
@@ -275,6 +472,14 @@ class TestReadLocalGitreview:
|
|
|
275
472
|
p.write_text("garbage\n", encoding="utf-8")
|
|
276
473
|
assert read_local_gitreview(p) is None
|
|
277
474
|
|
|
475
|
+
def test_reads_indented_file(self, tmp_path: Path) -> None:
|
|
476
|
+
p = tmp_path / ".gitreview"
|
|
477
|
+
p.write_text(INDENTED_GITREVIEW, encoding="utf-8")
|
|
478
|
+
info = read_local_gitreview(p)
|
|
479
|
+
assert info is not None
|
|
480
|
+
assert info.host == "gerrit.onap.org"
|
|
481
|
+
assert info.project == "cps"
|
|
482
|
+
|
|
278
483
|
def test_returns_none_for_unreadable(self, tmp_path: Path) -> None:
|
|
279
484
|
p = tmp_path / ".gitreview"
|
|
280
485
|
p.write_text(TYPICAL_GITREVIEW, encoding="utf-8")
|