github2gerrit 1.2.0__tar.gz → 1.2.1__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-1.2.0 → github2gerrit-1.2.1}/.pre-commit-config.yaml +5 -5
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/PKG-INFO +1 -1
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/action.yaml +1 -1
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/cli.py +25 -12
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/commit_normalization.py +3 -2
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/core.py +243 -26
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gerrit_pr_closer.py +231 -28
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gerrit_query.py +67 -2
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/netrc.py +4 -6
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/pr_content_filter.py +1 -1
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/similarity.py +21 -6
- github2gerrit-1.2.1/tests/test_clean_squash_title.py +298 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_commit_normalization.py +1 -1
- github2gerrit-1.2.1/tests/test_dependency_supersession.py +776 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/uv.lock +201 -180
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.editorconfig +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.gitignore +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.gitlint +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.markdownlint.yaml +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.readthedocs.yml +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/.yamllint +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/LICENSE +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/LICENSES/Apache-2.0.txt +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/README.md +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/REUSE.toml +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/docs/COMMIT_RULES.md +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/docs/RELEASE-v0.2.0.md +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/docs/github2gerrit_token_permissions_classic.png +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/pyproject.toml +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/sitecustomize.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/__init__.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/commit_rules.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/config.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/constants.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/duplicate_detection.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/error_codes.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/external_api.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gerrit_rest.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gerrit_urls.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/github_api.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gitreview.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/gitutils.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/mapping_comment.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/models.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/orchestrator/__init__.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/pr_commands.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/reconcile_matcher.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/rich_display.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/rich_logging.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/ssh_agent_setup.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/ssh_common.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/ssh_config_parser.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/ssh_discovery.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/trailers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/src/github2gerrit/utils.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/conftest.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/fixtures/__init__.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/fixtures/make_repo.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/fixtures/ssh_config_samples.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_action_environment_mapping.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_action_outputs.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_action_pr_number_handling.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_action_step_validation.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_automation_only.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_change_id_deduplication.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_cli.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_cli_helpers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_cli_netrc_options.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_cli_outputs_file.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_cli_url_and_dryrun.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_commit_rules.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_composite_action_coverage.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_config_and_reviewers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_config_helpers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_close_pr_policy.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_config_and_errors.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_gerrit_backref_comment.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_gerrit_push_errors.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_gerrit_rest_results.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_integration_fixture_repo.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_prepare_commits.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_shallow_clone.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_ssh_setup.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_core_ssrf_protection.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_dns_validation_and_no_gerrit.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_duplicate_detection.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_email_case_normalization.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_error_codes.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_external_api_framework.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_force_flag_cli.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_change_id_footer.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_change_status_checks.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_pr_closer.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_rest_client.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_urls.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gerrit_urls_more.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ghe_and_gitreview_args.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_github_api_error_handling.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_github_api_helpers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_github_api_retry_and_helpers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gitreview.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_gitutils_helpers.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_issue_157_regressions.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_mapping_comment_additional.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_mapping_comment_digest_and_backref.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_metadata_and_reconciliation.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_metadata_trailer_separation_bug.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_misc_small_coverage.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_netrc.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_orphan_rest_side_effects.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_pr_commands.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_pr_content_filter.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_pr_content_filter_integration.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_pr_update_detection.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_reconciliation_extracted_module.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_reconciliation_plan_and_orphans.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_reconciliation_scenarios.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_agent.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_agent_ownership.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_artifact_prevention.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_common.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_discovery.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_ssh_discovery_dry_run.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_trailers_additional.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_url_parser.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/test_utils.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/unit/test_config_integration.py +0 -0
- {github2gerrit-1.2.0 → github2gerrit-1.2.1}/tests/unit/test_ssh_config_parser.py +0 -0
|
@@ -59,7 +59,7 @@ repos:
|
|
|
59
59
|
types: [yaml]
|
|
60
60
|
|
|
61
61
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
62
|
-
rev:
|
|
62
|
+
rev: b831c3dc5d27d9da294ae4e915773b99aa24a7c5 # frozen: v0.15.10
|
|
63
63
|
hooks:
|
|
64
64
|
- id: ruff
|
|
65
65
|
files: ^(src|scripts|tests)/.+\.py$
|
|
@@ -68,7 +68,7 @@ repos:
|
|
|
68
68
|
files: ^(src|scripts|tests)/.+\.py$
|
|
69
69
|
|
|
70
70
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
71
|
-
rev:
|
|
71
|
+
rev: 0f369d245750787ce34997d464ed9605391a5283 # frozen: v1.20.1
|
|
72
72
|
hooks:
|
|
73
73
|
- id: mypy
|
|
74
74
|
files: ^src/.+\.py$
|
|
@@ -103,7 +103,7 @@ repos:
|
|
|
103
103
|
# Replaces: https://github.com/rhysd/actionlint
|
|
104
104
|
# Permits actionlint to run both locally and with precommit.ci/GitHub
|
|
105
105
|
- repo: https://github.com/Mateusz-Grzelinski/actionlint-py
|
|
106
|
-
rev:
|
|
106
|
+
rev: c04ed26e40637cab1aa9879c693832a9c120fb20 # frozen: v1.7.12.24
|
|
107
107
|
hooks:
|
|
108
108
|
- id: actionlint
|
|
109
109
|
|
|
@@ -115,7 +115,7 @@ repos:
|
|
|
115
115
|
|
|
116
116
|
# Requires a mirror, primary repo lacks .pre-commit-hooks.yaml
|
|
117
117
|
- repo: https://github.com/DetachHead/basedpyright-prek-mirror
|
|
118
|
-
rev:
|
|
118
|
+
rev: 7664ed7e31234c8369d85ee9a13a1ca3361c0aa1 # frozen: 1.39.0
|
|
119
119
|
hooks:
|
|
120
120
|
- id: basedpyright
|
|
121
121
|
files: ^src/.+\.py$
|
|
@@ -126,7 +126,7 @@ repos:
|
|
|
126
126
|
- id: gha-workflow-linter
|
|
127
127
|
|
|
128
128
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
129
|
-
rev:
|
|
129
|
+
rev: ed81924a8b1cecdaa570b072528fa80c9c4d6ccd # frozen: 0.37.1
|
|
130
130
|
hooks:
|
|
131
131
|
- id: check-github-actions
|
|
132
132
|
- id: check-github-workflows
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github2gerrit
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
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
|
|
@@ -221,7 +221,7 @@ runs:
|
|
|
221
221
|
- name: "Setup uv"
|
|
222
222
|
if: steps.disabled-check.outputs.disabled != 'true'
|
|
223
223
|
# yamllint disable-line rule:line-length
|
|
224
|
-
uses: astral-sh/setup-uv@
|
|
224
|
+
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
|
225
225
|
with:
|
|
226
226
|
enable-cache: false
|
|
227
227
|
|
|
@@ -2226,7 +2226,7 @@ def _process() -> None:
|
|
|
2226
2226
|
):
|
|
2227
2227
|
try:
|
|
2228
2228
|
log.debug(
|
|
2229
|
-
"
|
|
2229
|
+
"Checking for Gerrit change to abandon for PR #%s",
|
|
2230
2230
|
gh.pr_number,
|
|
2231
2231
|
)
|
|
2232
2232
|
change_number = abandon_gerrit_change_for_closed_pr(
|
|
@@ -2238,16 +2238,29 @@ def _process() -> None:
|
|
|
2238
2238
|
progress_tracker=None,
|
|
2239
2239
|
)
|
|
2240
2240
|
if change_number:
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2241
|
+
try:
|
|
2242
|
+
from .gerrit_urls import create_gerrit_url_builder
|
|
2243
|
+
|
|
2244
|
+
_url_builder = create_gerrit_url_builder(
|
|
2245
|
+
data.gerrit_server
|
|
2246
|
+
)
|
|
2247
|
+
gerrit_change_url = _url_builder.change_url(
|
|
2248
|
+
data.gerrit_project,
|
|
2249
|
+
int(change_number),
|
|
2250
|
+
)
|
|
2251
|
+
log.debug(
|
|
2252
|
+
"Successfully abandoned Gerrit "
|
|
2253
|
+
"change %s for pull request #%s",
|
|
2254
|
+
gerrit_change_url,
|
|
2255
|
+
gh.pr_number,
|
|
2256
|
+
)
|
|
2257
|
+
except Exception:
|
|
2258
|
+
log.debug(
|
|
2259
|
+
"Successfully abandoned Gerrit "
|
|
2260
|
+
"change %s for pull request #%s",
|
|
2261
|
+
change_number,
|
|
2262
|
+
gh.pr_number,
|
|
2263
|
+
)
|
|
2251
2264
|
# Console output already done by
|
|
2252
2265
|
# abandon_gerrit_change_for_closed_pr
|
|
2253
2266
|
else:
|
|
@@ -2295,7 +2308,7 @@ def _process() -> None:
|
|
|
2295
2308
|
log.warning("Gerrit cleanup failed: %s", exc)
|
|
2296
2309
|
|
|
2297
2310
|
log.debug(
|
|
2298
|
-
"
|
|
2311
|
+
"Cleanup operations completed for closed PR #%s",
|
|
2299
2312
|
gh.pr_number or "unknown",
|
|
2300
2313
|
)
|
|
2301
2314
|
return
|
|
@@ -401,8 +401,9 @@ class CommitNormalizer:
|
|
|
401
401
|
# Remove trailing ellipsis
|
|
402
402
|
title = re.sub(r"\s*[.]{3,}.*$", "", title)
|
|
403
403
|
|
|
404
|
-
# Remove markdown formatting
|
|
405
|
-
|
|
404
|
+
# Remove markdown bold/code formatting but preserve underscores
|
|
405
|
+
# (which appear in package names and filesystem paths).
|
|
406
|
+
title = re.sub(r"[*`]", "", title)
|
|
406
407
|
|
|
407
408
|
# For dependabot titles, extract the essential information
|
|
408
409
|
for pattern in DEPENDABOT_PATTERNS:
|
|
@@ -154,6 +154,93 @@ def _clean_ellipses_from_message(message: str) -> str:
|
|
|
154
154
|
return "\n".join(cleaned_lines)
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def _clean_squash_title_line(title_line: str | None) -> str:
|
|
158
|
+
"""Clean and truncate a squashed commit title line.
|
|
159
|
+
|
|
160
|
+
Handles markdown removal, separator splitting, and length
|
|
161
|
+
truncation while preserving conventional commit prefixes
|
|
162
|
+
and underscores in package/path names.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
title_line: Raw title line from git log output.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Cleaned title line, safe for use as a commit subject.
|
|
169
|
+
"""
|
|
170
|
+
from .similarity import CC_PREFIX_RE
|
|
171
|
+
|
|
172
|
+
if not title_line:
|
|
173
|
+
return ""
|
|
174
|
+
|
|
175
|
+
# Remove markdown links
|
|
176
|
+
title_line = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", title_line)
|
|
177
|
+
# Remove trailing ellipsis/truncation
|
|
178
|
+
title_line = re.sub(r"\s*[.]{3,}.*$", "", title_line)
|
|
179
|
+
# Split on common separators to avoid leaking body content
|
|
180
|
+
for separator in [". Bumps ", " Bumps ", ". - ", " - "]:
|
|
181
|
+
if separator in title_line:
|
|
182
|
+
title_line = title_line.split(separator)[0].strip()
|
|
183
|
+
break
|
|
184
|
+
# Remove markdown bold/code formatting but preserve underscores
|
|
185
|
+
# (which appear in package names and filesystem paths).
|
|
186
|
+
title_line = re.sub(r"[*`]", "", title_line).strip()
|
|
187
|
+
|
|
188
|
+
if len(title_line) > 100:
|
|
189
|
+
# Detect conventional commit prefix length so that the
|
|
190
|
+
# ": " break-point does not split on the prefix separator
|
|
191
|
+
# (e.g. "Build(deps): " should not be treated as a sentence
|
|
192
|
+
# break).
|
|
193
|
+
cc_match = CC_PREFIX_RE.match(title_line)
|
|
194
|
+
cc_prefix_len = cc_match.end() if cc_match else 0
|
|
195
|
+
|
|
196
|
+
break_points = [". ", "! ", "? ", " - ", ": "]
|
|
197
|
+
max_bp_len = max(len(bp) for bp in break_points)
|
|
198
|
+
truncated = False
|
|
199
|
+
for bp in break_points:
|
|
200
|
+
# For the ": " break-point, start searching after the
|
|
201
|
+
# conventional commit prefix to avoid splitting there.
|
|
202
|
+
search_start = cc_prefix_len if bp == ": " else 0
|
|
203
|
+
# Extend the slice by (max_bp_len - 1) so that a
|
|
204
|
+
# break-point starting just before position 100 is
|
|
205
|
+
# still detected even if it spans across the boundary.
|
|
206
|
+
candidate_end = min(len(title_line), 100 + max_bp_len - 1)
|
|
207
|
+
candidate = title_line[search_start:candidate_end]
|
|
208
|
+
bp_offset = candidate.find(bp)
|
|
209
|
+
if bp_offset != -1:
|
|
210
|
+
bp_idx = search_start + bp_offset
|
|
211
|
+
# Only use this break-point if it starts within
|
|
212
|
+
# the 100-char limit.
|
|
213
|
+
if bp_idx >= 100:
|
|
214
|
+
continue
|
|
215
|
+
# Punctuation break-points (". ", ": ") — include
|
|
216
|
+
# the punctuation mark. Separator break-points
|
|
217
|
+
# (" - ") — truncate before the separator.
|
|
218
|
+
if bp[0].isspace():
|
|
219
|
+
title_line = title_line[:bp_idx].rstrip()
|
|
220
|
+
else:
|
|
221
|
+
title_line = title_line[
|
|
222
|
+
: bp_idx + len(bp.rstrip())
|
|
223
|
+
].rstrip()
|
|
224
|
+
truncated = True
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
if not truncated and cc_prefix_len == 0:
|
|
228
|
+
# Non-CC title with no break-point found: fall back
|
|
229
|
+
# to word-boundary truncation at 100 characters.
|
|
230
|
+
words = title_line[:100].split()
|
|
231
|
+
title_line = (
|
|
232
|
+
" ".join(words[:-1])
|
|
233
|
+
if len(words) > 1
|
|
234
|
+
else title_line[:100].rstrip()
|
|
235
|
+
)
|
|
236
|
+
# For CC titles with no break-point: pass through the
|
|
237
|
+
# full title. The length is inherent to the structured
|
|
238
|
+
# subject (e.g. long dependency paths), not body-content
|
|
239
|
+
# leakage.
|
|
240
|
+
|
|
241
|
+
return title_line
|
|
242
|
+
|
|
243
|
+
|
|
157
244
|
# ---------------------
|
|
158
245
|
# Utility functions
|
|
159
246
|
# ---------------------
|
|
@@ -678,6 +765,9 @@ class Orchestrator:
|
|
|
678
765
|
2. GitHub-Hash trailer matching
|
|
679
766
|
3. GitHub-PR trailer URL matching
|
|
680
767
|
4. Mapping comment parsing from PR comments
|
|
768
|
+
5. Dependency package match — find an open change that
|
|
769
|
+
bumps the same dependency (for Dependabot / Renovate
|
|
770
|
+
supersession).
|
|
681
771
|
|
|
682
772
|
Args:
|
|
683
773
|
gh: GitHub context containing PR information
|
|
@@ -793,6 +883,10 @@ class Orchestrator:
|
|
|
793
883
|
except Exception as exc:
|
|
794
884
|
log.debug("GitHub-Hash trailer query failed: %s", exc)
|
|
795
885
|
|
|
886
|
+
# Cache the PR title for reuse across strategies 4 and 5
|
|
887
|
+
# so we don't duplicate GitHub API requests.
|
|
888
|
+
cached_pr_title: str = ""
|
|
889
|
+
|
|
796
890
|
# Strategy 4: Parse mapping comments from PR
|
|
797
891
|
try:
|
|
798
892
|
from .mapping_comment import parse_mapping_comments
|
|
@@ -800,6 +894,7 @@ class Orchestrator:
|
|
|
800
894
|
client_gh = build_client()
|
|
801
895
|
repo = get_repo_from_env(client_gh)
|
|
802
896
|
pr_obj = get_pull(repo, int(gh.pr_number))
|
|
897
|
+
cached_pr_title = getattr(pr_obj, "title", "") or ""
|
|
803
898
|
|
|
804
899
|
issue = pr_obj.as_issue()
|
|
805
900
|
comments = list(issue.get_comments())
|
|
@@ -830,6 +925,110 @@ class Orchestrator:
|
|
|
830
925
|
except Exception as exc:
|
|
831
926
|
log.debug("Mapping comment parsing failed: %s", exc)
|
|
832
927
|
|
|
928
|
+
# Strategy 5: Dependency package match (supersession)
|
|
929
|
+
# When a new Dependabot/Renovate PR bumps the same dependency
|
|
930
|
+
# as an existing open Gerrit change, reuse that Change-Id so
|
|
931
|
+
# the push creates a new patchset instead of a duplicate change.
|
|
932
|
+
try:
|
|
933
|
+
from .gerrit_query import GerritChange
|
|
934
|
+
from .gerrit_query import query_open_changes_by_project
|
|
935
|
+
from .gerrit_rest import build_client_for_host
|
|
936
|
+
from .similarity import extract_dependency_package_from_subject
|
|
937
|
+
from .trailers import GITHUB_PR_TRAILER
|
|
938
|
+
from .trailers import parse_trailers
|
|
939
|
+
|
|
940
|
+
# Reuse PR title cached by Strategy 4 to avoid a
|
|
941
|
+
# duplicate GitHub API request.
|
|
942
|
+
pr_title = cached_pr_title
|
|
943
|
+
if not pr_title:
|
|
944
|
+
log.debug(
|
|
945
|
+
"Strategy 5: PR title cache miss, fetching from GitHub API",
|
|
946
|
+
)
|
|
947
|
+
try:
|
|
948
|
+
gh_client = build_client()
|
|
949
|
+
gh_repo = get_repo_from_env(gh_client)
|
|
950
|
+
pr_obj = get_pull(gh_repo, int(gh.pr_number))
|
|
951
|
+
pr_title = getattr(pr_obj, "title", "") or ""
|
|
952
|
+
except Exception:
|
|
953
|
+
pr_title = ""
|
|
954
|
+
|
|
955
|
+
current_pkg = extract_dependency_package_from_subject(pr_title)
|
|
956
|
+
if current_pkg:
|
|
957
|
+
log.debug(
|
|
958
|
+
"Strategy 5: searching for open changes that "
|
|
959
|
+
"bump dependency '%s'",
|
|
960
|
+
current_pkg,
|
|
961
|
+
)
|
|
962
|
+
dep_client = build_client_for_host(gerrit.host)
|
|
963
|
+
open_changes = query_open_changes_by_project(
|
|
964
|
+
dep_client,
|
|
965
|
+
gerrit.project,
|
|
966
|
+
branch=gh.base_ref,
|
|
967
|
+
max_results=200,
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
# Collect all matching changes, then select the
|
|
971
|
+
# oldest one (lowest change number) to avoid
|
|
972
|
+
# "downgrading" a newer change by uploading an
|
|
973
|
+
# older patchset to it.
|
|
974
|
+
candidates: list[tuple[int, GerritChange]] = []
|
|
975
|
+
for change in open_changes:
|
|
976
|
+
candidate_pkg = extract_dependency_package_from_subject(
|
|
977
|
+
change.subject
|
|
978
|
+
)
|
|
979
|
+
if candidate_pkg and candidate_pkg == current_pkg:
|
|
980
|
+
# Verify this is a GitHub2Gerrit change
|
|
981
|
+
commit_msg = change.commit_message or ""
|
|
982
|
+
trailers = parse_trailers(commit_msg)
|
|
983
|
+
if GITHUB_PR_TRAILER not in trailers:
|
|
984
|
+
log.debug(
|
|
985
|
+
"Strategy 5: skipping change %s "
|
|
986
|
+
"(no GitHub2Gerrit metadata)",
|
|
987
|
+
change.number,
|
|
988
|
+
)
|
|
989
|
+
continue
|
|
990
|
+
try:
|
|
991
|
+
change_num = int(change.number)
|
|
992
|
+
except (TypeError, ValueError):
|
|
993
|
+
log.debug(
|
|
994
|
+
"Strategy 5: skipping change with "
|
|
995
|
+
"invalid number %r for subject %r",
|
|
996
|
+
change.number,
|
|
997
|
+
change.subject,
|
|
998
|
+
)
|
|
999
|
+
continue
|
|
1000
|
+
candidates.append((change_num, change))
|
|
1001
|
+
|
|
1002
|
+
if candidates:
|
|
1003
|
+
# Prefer the oldest open change so the newest
|
|
1004
|
+
# PR always updates the original change and
|
|
1005
|
+
# the post-push sweep abandons the rest.
|
|
1006
|
+
candidates.sort(key=lambda t: t[0])
|
|
1007
|
+
_, oldest = candidates[0]
|
|
1008
|
+
change_ids = [oldest.change_id]
|
|
1009
|
+
log.info(
|
|
1010
|
+
"Found superseding target by dependency "
|
|
1011
|
+
"package '%s': change %s (%s) "
|
|
1012
|
+
"(oldest of %d candidate(s))",
|
|
1013
|
+
current_pkg,
|
|
1014
|
+
oldest.number,
|
|
1015
|
+
oldest.subject,
|
|
1016
|
+
len(candidates),
|
|
1017
|
+
)
|
|
1018
|
+
return change_ids
|
|
1019
|
+
|
|
1020
|
+
log.debug(
|
|
1021
|
+
"No open changes found for dependency '%s'",
|
|
1022
|
+
current_pkg,
|
|
1023
|
+
)
|
|
1024
|
+
else:
|
|
1025
|
+
log.debug(
|
|
1026
|
+
"Strategy 5 skipped: could not extract dependency "
|
|
1027
|
+
"package from PR title"
|
|
1028
|
+
)
|
|
1029
|
+
except Exception as exc:
|
|
1030
|
+
log.debug("Dependency package strategy failed: %s", exc)
|
|
1031
|
+
|
|
833
1032
|
log.warning(
|
|
834
1033
|
"⚠️ No existing Gerrit changes found for PR #%s",
|
|
835
1034
|
gh.pr_number,
|
|
@@ -1936,6 +2135,49 @@ class Orchestrator:
|
|
|
1936
2135
|
# Validate that no unexpected files were committed
|
|
1937
2136
|
self._validate_committed_files(gh, result)
|
|
1938
2137
|
|
|
2138
|
+
# Post-push supersession sweep (Option A fallback).
|
|
2139
|
+
# After a successful push, check whether other open Gerrit
|
|
2140
|
+
# changes in the same project bump the same dependency
|
|
2141
|
+
# package. If Strategy 5 already reused the old Change-Id
|
|
2142
|
+
# (update-in-place), no duplicates should exist. If that
|
|
2143
|
+
# path was skipped (e.g. non-dependency PR, or the query
|
|
2144
|
+
# failed), this sweep catches and abandons stale changes.
|
|
2145
|
+
if not inputs.dry_run and gerrit and prep.change_ids:
|
|
2146
|
+
try:
|
|
2147
|
+
from .gerrit_pr_closer import (
|
|
2148
|
+
abandon_superseded_dependency_changes,
|
|
2149
|
+
)
|
|
2150
|
+
|
|
2151
|
+
# Derive the subject from the pushed commit,
|
|
2152
|
+
# regardless of whether change URL lookup
|
|
2153
|
+
# succeeded.
|
|
2154
|
+
push_subject = ""
|
|
2155
|
+
try:
|
|
2156
|
+
push_subject = run_cmd(
|
|
2157
|
+
[
|
|
2158
|
+
"git",
|
|
2159
|
+
"show",
|
|
2160
|
+
"-s",
|
|
2161
|
+
"--pretty=format:%s",
|
|
2162
|
+
"HEAD",
|
|
2163
|
+
],
|
|
2164
|
+
cwd=self.workspace,
|
|
2165
|
+
).stdout.strip()
|
|
2166
|
+
except Exception:
|
|
2167
|
+
push_subject = ""
|
|
2168
|
+
|
|
2169
|
+
if push_subject:
|
|
2170
|
+
abandon_superseded_dependency_changes(
|
|
2171
|
+
gerrit_server=gerrit.host,
|
|
2172
|
+
gerrit_project=gerrit.project,
|
|
2173
|
+
current_subject=push_subject,
|
|
2174
|
+
exclude_change_ids=prep.change_ids,
|
|
2175
|
+
dry_run=False,
|
|
2176
|
+
target_branch=self._resolve_target_branch(),
|
|
2177
|
+
)
|
|
2178
|
+
except Exception as exc:
|
|
2179
|
+
log.debug("Post-push supersession sweep skipped: %s", exc)
|
|
2180
|
+
|
|
1939
2181
|
self._close_pull_request_if_required(gh)
|
|
1940
2182
|
|
|
1941
2183
|
log.debug("Pipeline complete: %s", result)
|
|
@@ -3564,32 +3806,7 @@ class Orchestrator:
|
|
|
3564
3806
|
return message_lines, signed_off, change_ids
|
|
3565
3807
|
|
|
3566
3808
|
def _clean_title_line(title_line: str) -> str:
|
|
3567
|
-
|
|
3568
|
-
title_line = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", title_line)
|
|
3569
|
-
# Remove trailing ellipsis/truncation
|
|
3570
|
-
title_line = re.sub(r"\s*[.]{3,}.*$", "", title_line)
|
|
3571
|
-
# Split on common separators to avoid leaking body content
|
|
3572
|
-
for separator in [". Bumps ", " Bumps ", ". - ", " - "]:
|
|
3573
|
-
if separator in title_line:
|
|
3574
|
-
title_line = title_line.split(separator)[0].strip()
|
|
3575
|
-
break
|
|
3576
|
-
# Remove simple markdown/formatting artifacts
|
|
3577
|
-
title_line = re.sub(r"[*_`]", "", title_line).strip()
|
|
3578
|
-
if len(title_line) > 100:
|
|
3579
|
-
break_points = [". ", "! ", "? ", " - ", ": "]
|
|
3580
|
-
for bp in break_points:
|
|
3581
|
-
if bp in title_line[:100]:
|
|
3582
|
-
title_line = title_line[
|
|
3583
|
-
: title_line.index(bp) + len(bp.strip())
|
|
3584
|
-
]
|
|
3585
|
-
break
|
|
3586
|
-
else:
|
|
3587
|
-
words = title_line[:100].split()
|
|
3588
|
-
title_line = (
|
|
3589
|
-
" ".join(words[:-1])
|
|
3590
|
-
if len(words) > 1
|
|
3591
|
-
else title_line[:100].rstrip()
|
|
3592
|
-
)
|
|
3809
|
+
title_line = _clean_squash_title_line(title_line)
|
|
3593
3810
|
|
|
3594
3811
|
# Apply conventional commit normalization if enabled
|
|
3595
3812
|
if inputs.normalise_commit and gh.pr_number:
|