github2gerrit 1.2.2__tar.gz → 1.2.4__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.
Files changed (134) hide show
  1. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.pre-commit-config.yaml +4 -4
  2. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/PKG-INFO +4 -4
  3. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/README.md +2 -2
  4. github2gerrit-1.2.4/SECURITY.md +77 -0
  5. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/action.yaml +43 -30
  6. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/pyproject.toml +22 -2
  7. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/core.py +6 -2
  8. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/external_api.py +17 -3
  9. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gerrit_pr_closer.py +71 -2
  10. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gerrit_query.py +15 -0
  11. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gerrit_rest.py +111 -4
  12. github2gerrit-1.2.4/src/github2gerrit/gerrit_ssh.py +284 -0
  13. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/orchestrator/reconciliation.py +26 -2
  14. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/pr_content_filter.py +8 -1
  15. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/utils.py +45 -0
  16. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_action_environment_mapping.py +8 -7
  17. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_action_step_validation.py +8 -3
  18. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_dependency_supersession.py +19 -0
  19. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_external_api_framework.py +47 -0
  20. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_rest_client.py +137 -0
  21. github2gerrit-1.2.4/tests/test_gerrit_ssh_abandon.py +258 -0
  22. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_urls_more.py +1 -2
  23. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_netrc.py +8 -7
  24. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_orphan_rest_side_effects.py +3 -0
  25. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_reconciliation_plan_and_orphans.py +6 -0
  26. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_discovery.py +9 -3
  27. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_utils.py +81 -0
  28. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/uv.lock +86 -79
  29. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.editorconfig +0 -0
  30. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.gitignore +0 -0
  31. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.gitlint +0 -0
  32. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.markdownlint.yaml +0 -0
  33. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.readthedocs.yml +0 -0
  34. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/.yamllint +0 -0
  35. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/LICENSE +0 -0
  36. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/LICENSES/Apache-2.0.txt +0 -0
  37. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/REUSE.toml +0 -0
  38. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/docs/COMMIT_RULES.md +0 -0
  39. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
  40. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
  41. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/docs/RELEASE-v0.2.0.md +0 -0
  42. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/docs/github2gerrit_token_permissions_classic.png +0 -0
  43. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/sitecustomize.py +0 -0
  44. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/__init__.py +0 -0
  45. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/cli.py +0 -0
  46. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/commit_normalization.py +0 -0
  47. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/commit_rules.py +0 -0
  48. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/config.py +0 -0
  49. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/constants.py +0 -0
  50. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/duplicate_detection.py +0 -0
  51. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/error_codes.py +0 -0
  52. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gerrit_urls.py +0 -0
  53. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/github_api.py +0 -0
  54. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gitreview.py +0 -0
  55. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/gitutils.py +0 -0
  56. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/mapping_comment.py +0 -0
  57. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/models.py +0 -0
  58. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/netrc.py +0 -0
  59. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/orchestrator/__init__.py +0 -0
  60. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/pr_commands.py +0 -0
  61. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/reconcile_matcher.py +0 -0
  62. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/rich_display.py +0 -0
  63. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/rich_logging.py +0 -0
  64. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/similarity.py +0 -0
  65. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/ssh_agent_setup.py +0 -0
  66. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/ssh_common.py +0 -0
  67. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/ssh_config_parser.py +0 -0
  68. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/ssh_discovery.py +0 -0
  69. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/src/github2gerrit/trailers.py +0 -0
  70. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/conftest.py +0 -0
  71. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/fixtures/__init__.py +0 -0
  72. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/fixtures/make_repo.py +0 -0
  73. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/fixtures/ssh_config_samples.py +0 -0
  74. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_action_outputs.py +0 -0
  75. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_action_pr_number_handling.py +0 -0
  76. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_automation_only.py +0 -0
  77. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_change_id_deduplication.py +0 -0
  78. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_clean_squash_title.py +0 -0
  79. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_cli.py +0 -0
  80. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_cli_helpers.py +0 -0
  81. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_cli_netrc_options.py +0 -0
  82. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_cli_outputs_file.py +0 -0
  83. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_cli_url_and_dryrun.py +0 -0
  84. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_commit_normalization.py +0 -0
  85. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_commit_rules.py +0 -0
  86. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_composite_action_coverage.py +0 -0
  87. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_config_and_reviewers.py +0 -0
  88. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_config_helpers.py +0 -0
  89. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_close_pr_policy.py +0 -0
  90. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_config_and_errors.py +0 -0
  91. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_gerrit_backref_comment.py +0 -0
  92. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_gerrit_push_errors.py +0 -0
  93. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_gerrit_rest_results.py +0 -0
  94. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_integration_fixture_repo.py +0 -0
  95. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_prepare_commits.py +0 -0
  96. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_shallow_clone.py +0 -0
  97. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_ssh_setup.py +0 -0
  98. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_core_ssrf_protection.py +0 -0
  99. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_dns_validation_and_no_gerrit.py +0 -0
  100. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_duplicate_detection.py +0 -0
  101. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_email_case_normalization.py +0 -0
  102. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_error_codes.py +0 -0
  103. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_force_flag_cli.py +0 -0
  104. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_change_id_footer.py +0 -0
  105. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_change_status_checks.py +0 -0
  106. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_pr_closer.py +0 -0
  107. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gerrit_urls.py +0 -0
  108. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ghe_and_gitreview_args.py +0 -0
  109. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_github_api_error_handling.py +0 -0
  110. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_github_api_helpers.py +0 -0
  111. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_github_api_retry_and_helpers.py +0 -0
  112. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gitreview.py +0 -0
  113. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_gitutils_helpers.py +0 -0
  114. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_issue_157_regressions.py +0 -0
  115. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_mapping_comment_additional.py +0 -0
  116. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_mapping_comment_digest_and_backref.py +0 -0
  117. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_metadata_and_reconciliation.py +0 -0
  118. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_metadata_trailer_separation_bug.py +0 -0
  119. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_misc_small_coverage.py +0 -0
  120. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_pr_commands.py +0 -0
  121. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_pr_content_filter.py +0 -0
  122. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_pr_content_filter_integration.py +0 -0
  123. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_pr_update_detection.py +0 -0
  124. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_reconciliation_extracted_module.py +0 -0
  125. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_reconciliation_scenarios.py +0 -0
  126. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_agent.py +0 -0
  127. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_agent_ownership.py +0 -0
  128. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_artifact_prevention.py +0 -0
  129. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_common.py +0 -0
  130. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_ssh_discovery_dry_run.py +0 -0
  131. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_trailers_additional.py +0 -0
  132. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/test_url_parser.py +0 -0
  133. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/tests/unit/test_config_integration.py +0 -0
  134. {github2gerrit-1.2.2 → github2gerrit-1.2.4}/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: 0c7b6c989466a93942def1f84baf36ddfcd60c83 # frozen: v0.15.14
62
+ rev: 3b3f7c3f57fe9925356faf5fe6230835138be230 # frozen: v0.15.17
63
63
  hooks:
64
64
  - id: ruff
65
65
  files: ^(src|scripts|tests)/.+\.py$
@@ -115,18 +115,18 @@ 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: 78e6efd50b63647fecb7e65fc7032745d861e2c5 # frozen: 1.39.6
118
+ rev: 5f6f2cb9fa8aec15105f77fd21e8dfe29838b16d # frozen: 1.39.8
119
119
  hooks:
120
120
  - id: basedpyright
121
121
  files: ^src/.+\.py$
122
122
 
123
123
  - repo: https://github.com/lfreleng-actions/gha-workflow-linter
124
- rev: 2c315e461ec3379bf9c1682360fdc1a3899f88c9 # frozen: v1.0.4
124
+ rev: 1a307fd9eed98268b11300ab86b1a107e1d3d1ae # frozen: v1.1.1
125
125
  hooks:
126
126
  - id: gha-workflow-linter
127
127
 
128
128
  - repo: https://github.com/python-jsonschema/check-jsonschema
129
- rev: 943377262562a12b57292fc98fabd7dbf81451fe # frozen: 0.37.2
129
+ rev: 8ef330cbb7204d388aa7a620f9549bcea8009663 # frozen: 0.37.3
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.2
3
+ Version: 1.2.4
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
@@ -23,7 +23,7 @@ Classifier: Topic :: Software Development :: Version Control
23
23
  Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.11
25
25
  Requires-Dist: click>=8.1.7
26
- Requires-Dist: cryptography>=46.0.5
26
+ Requires-Dist: cryptography>=48.0.1
27
27
  Requires-Dist: git-review>=2.5.0
28
28
  Requires-Dist: pygerrit2>=2.0.15
29
29
  Requires-Dist: pygithub>=2.8.1
@@ -963,7 +963,7 @@ name: github2gerrit
963
963
 
964
964
  on:
965
965
  pull_request_target:
966
- types: [opened, reopened, edited, synchronize]
966
+ types: [opened, reopened, edited, synchronize, closed]
967
967
  workflow_dispatch:
968
968
 
969
969
  permissions:
@@ -1192,7 +1192,7 @@ name: github2gerrit (advanced)
1192
1192
 
1193
1193
  on:
1194
1194
  pull_request_target:
1195
- types: [opened, reopened, edited, synchronize]
1195
+ types: [opened, reopened, edited, synchronize, closed]
1196
1196
  workflow_dispatch:
1197
1197
 
1198
1198
  permissions:
@@ -917,7 +917,7 @@ name: github2gerrit
917
917
 
918
918
  on:
919
919
  pull_request_target:
920
- types: [opened, reopened, edited, synchronize]
920
+ types: [opened, reopened, edited, synchronize, closed]
921
921
  workflow_dispatch:
922
922
 
923
923
  permissions:
@@ -1146,7 +1146,7 @@ name: github2gerrit (advanced)
1146
1146
 
1147
1147
  on:
1148
1148
  pull_request_target:
1149
- types: [opened, reopened, edited, synchronize]
1149
+ types: [opened, reopened, edited, synchronize, closed]
1150
1150
  workflow_dispatch:
1151
1151
 
1152
1152
  permissions:
@@ -0,0 +1,77 @@
1
+ <!--
2
+ SPDX-License-Identifier: Apache-2.0
3
+ SPDX-FileCopyrightText: 2026 The Linux Foundation
4
+ -->
5
+
6
+ # Security Policy
7
+
8
+ This document describes the security policy for this repository, including
9
+ which versions receive security updates and how to report vulnerabilities.
10
+
11
+ ## Supported Versions
12
+
13
+ Maintainers develop and merge security fixes on the default branch
14
+ (`main`) and publish those fixes in the latest tagged release. Older
15
+ releases and tags do not receive security updates. Users should track
16
+ the latest tagged release for security patches.
17
+
18
+ | Version | Supported |
19
+ | --------------------- | ------------------ |
20
+ | Latest tagged release | :white_check_mark: |
21
+ | Older releases/tags | :x: |
22
+
23
+ ## Reporting a Vulnerability
24
+
25
+ If you discover a security vulnerability in this project, please report it
26
+ **privately** so that maintainers can investigate and release a fix before
27
+ the issue becomes publicly known.
28
+
29
+ ### Preferred: GitHub Private Vulnerability Reporting
30
+
31
+ Use GitHub's private vulnerability reporting feature:
32
+
33
+ 1. Navigate to the **Security** tab of this repository.
34
+ 2. Click **Report a vulnerability**.
35
+ 3. Provide as much detail as possible (see below).
36
+
37
+ This creates a private advisory visible to maintainers.
38
+
39
+ ### Alternative: Email
40
+
41
+ If you cannot use GitHub's private reporting, send an email to the Linux
42
+ Foundation Release Engineering team at:
43
+
44
+ - **<releng@linuxfoundation.org>**
45
+
46
+ Please do **not** report security vulnerabilities through public GitHub
47
+ issues, discussions, or pull requests.
48
+
49
+ ### What to Include
50
+
51
+ To help maintainers triage and resolve the report, please include:
52
+
53
+ - A clear description of the vulnerability and its potential impact.
54
+ - Steps to reproduce the issue (proof-of-concept code or commands).
55
+ - The affected version(s), commit SHA, or release tag.
56
+ - Any known mitigations or workarounds.
57
+ - Your name and contact details for follow-up (optional).
58
+
59
+ ## Response Process
60
+
61
+ Maintainers will acknowledge receipt of vulnerability reports within
62
+ **5 business days**. We aim to:
63
+
64
+ 1. Confirm the vulnerability and determine its severity.
65
+ 2. Develop and test a fix in a private branch or advisory.
66
+ 3. Coordinate a disclosure timeline with the reporter.
67
+ 4. Release a patched version and publish a security advisory.
68
+
69
+ We follow a responsible disclosure process and credit reporters in the
70
+ published advisory unless they request to remain anonymous.
71
+
72
+ ## Scope
73
+
74
+ This policy covers the source code, configuration, and documentation
75
+ in this repository. Please report vulnerabilities in upstream
76
+ dependencies to their respective maintainers; this project will update
77
+ affected dependencies once fixes become available.
@@ -179,11 +179,13 @@ runs:
179
179
  - name: "Check if GitHub2Gerrit is disabled"
180
180
  id: disabled-check
181
181
  shell: bash
182
+ env:
183
+ INPUT_G2G_DISABLED: ${{ inputs.G2G_DISABLED }}
182
184
  run: |
183
185
  # Check if GitHub2Gerrit is disabled
184
186
  set -euo pipefail
185
187
  DISABLED_ENV="${G2G_DISABLED:-}"
186
- DISABLED_INPUT="${{ inputs.G2G_DISABLED }}"
188
+ DISABLED_INPUT="${INPUT_G2G_DISABLED:-}"
187
189
  # Normalize: accept the same truthy set as env_bool()
188
190
  # (1/true/yes/on, case-insensitive, trimmed)
189
191
  _normalize() {
@@ -205,11 +207,15 @@ runs:
205
207
  - name: "Checkout repository"
206
208
  if: steps.disabled-check.outputs.disabled != 'true'
207
209
  # yamllint disable-line rule:line-length
208
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
210
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
209
211
  with:
210
212
  fetch-depth: ${{ inputs.FETCH_DEPTH }}
211
213
  # Ensure we are on the PR's head SHA when triggered by PR events
212
214
  ref: ${{ github.event.pull_request.head.sha || github.sha }}
215
+ # The action authenticates to Gerrit over SSH and to GitHub via an
216
+ # explicit GITHUB_TOKEN env var, so the checkout token does not need
217
+ # to be persisted into the local git config.
218
+ persist-credentials: false
213
219
 
214
220
  - name: "Setup Python"
215
221
  if: steps.disabled-check.outputs.disabled != 'true'
@@ -221,7 +227,7 @@ runs:
221
227
  - name: "Setup uv"
222
228
  if: steps.disabled-check.outputs.disabled != 'true'
223
229
  # yamllint disable-line rule:line-length
224
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
230
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
225
231
  with:
226
232
  enable-cache: false
227
233
 
@@ -233,15 +239,16 @@ runs:
233
239
  # when git metadata is unavailable
234
240
  # GitHub Actions shallow checkout doesn't include .git history
235
241
  SETUPTOOLS_SCM_PRETEND_VERSION: "0.0.0+dev"
242
+ USE_LOCAL_ACTION: ${{ inputs.USE_LOCAL_ACTION }}
236
243
  run: |
237
244
  # Setup github2gerrit
238
245
  set -euo pipefail
239
246
  uv --version
240
247
  # Install locally for self-testing, use uvx for external repos
241
- if [[ "${{ inputs.USE_LOCAL_ACTION }}" == "true" ]] || \
242
- [[ "${{ github.repository }}" =~ lfreleng-actions/github2gerrit-action ]]; then
243
- echo "Installing with: uv pip install --system ${{ github.action_path }}"
244
- uv pip install --system ${{ github.action_path }}
248
+ if [[ "${USE_LOCAL_ACTION}" == "true" ]] || \
249
+ [[ "${GITHUB_REPOSITORY}" =~ lfreleng-actions/github2gerrit-action ]]; then
250
+ echo "Installing with: uv pip install --system ${GITHUB_ACTION_PATH}"
251
+ uv pip install --system "${GITHUB_ACTION_PATH}"
245
252
  else
246
253
  echo "uvx will install GitHub2Gerrit from PyPI"
247
254
  fi
@@ -258,11 +265,14 @@ runs:
258
265
 
259
266
  - name: "Normalize PR_NUMBER"
260
267
  if: ${{ steps.disabled-check.outputs.disabled != 'true' && github.event_name == 'workflow_dispatch' }}
268
+ id: normalize
261
269
  shell: bash
270
+ env:
271
+ INPUT_PR_NUMBER: ${{ inputs.PR_NUMBER }}
262
272
  run: |
263
273
  # Normalize PR_NUMBER
264
274
  set -euo pipefail
265
- pr_in="${{ inputs.PR_NUMBER }}"
275
+ pr_in="${INPUT_PR_NUMBER:-}"
266
276
  if [[ -z "${pr_in}" || "${pr_in}" == "null" ]]; then
267
277
  pr_in="0"
268
278
  fi
@@ -271,51 +281,53 @@ runs:
271
281
  exit 2
272
282
  fi
273
283
  if [[ "${pr_in}" == "0" ]]; then
274
- echo "SYNC_ALL_OPEN_PRS=true" >> "$GITHUB_ENV"
284
+ echo "sync_all=true" >> "$GITHUB_OUTPUT"
275
285
  else
276
- echo "PR_NUMBER=${pr_in}" >> "$GITHUB_ENV"
286
+ echo "pr_number=${pr_in}" >> "$GITHUB_OUTPUT"
277
287
  fi
278
288
 
279
289
  - name: "Extract PR number, validate context"
280
290
  if: steps.disabled-check.outputs.disabled != 'true'
291
+ id: extract
281
292
  shell: bash
293
+ env:
294
+ EVENT_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || '' }}
295
+ DISPATCH_PR_NUMBER: ${{ steps.normalize.outputs.pr_number }}
296
+ DISPATCH_SYNC_ALL: ${{ steps.normalize.outputs.sync_all }}
282
297
  run: |
283
298
  # Extract PR number, validate context
284
299
  set -euo pipefail
285
300
 
286
301
  # Push events don't need PR_NUMBER (used for closing merged PRs)
287
302
  # The CLI handles push events specially via _process_close_merged_prs()
288
- if [[ "${{ github.event_name }}" == "push" ]]; then
303
+ if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
289
304
  echo "Push event detected - will process merged commits for PR closure"
290
- # Set PR_NUMBER to empty to indicate this is intentional for push events
291
- echo "PR_NUMBER=" >> "$GITHUB_ENV"
305
+ # Emit an empty PR number to signal intentional push handling
306
+ echo "pr_number=" >> "$GITHUB_OUTPUT"
292
307
  exit 0
293
308
  fi
294
309
 
295
- # Honor PR_NUMBER or SYNC_ALL_OPEN_PRS set by workflow_dispatch
296
- if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
297
- if [[ -n "${SYNC_ALL_OPEN_PRS:-}" ]]; then
310
+ # Honor PR_NUMBER or sync-all from workflow_dispatch normalization
311
+ if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
312
+ if [[ -n "${DISPATCH_SYNC_ALL:-}" ]]; then
298
313
  echo "Processing all open pull requests via workflow_dispatch."
314
+ echo "sync_all=true" >> "$GITHUB_OUTPUT"
299
315
  else
300
- if [[ -z "${PR_NUMBER:-}" || "${PR_NUMBER}" == "null" ]]; then
316
+ if [[ -z "${DISPATCH_PR_NUMBER:-}" || "${DISPATCH_PR_NUMBER}" == "null" ]]; then
301
317
  echo "Error: provide PR_NUMBER or set 0 to process all PRs."
302
318
  exit 2
303
319
  fi
304
- echo "PR_NUMBER=${PR_NUMBER}" >> "$GITHUB_ENV"
320
+ echo "pr_number=${DISPATCH_PR_NUMBER}" >> "$GITHUB_OUTPUT"
305
321
  fi
306
322
  else
307
- PR_NUMBER_EVT="${{ github.event.pull_request.number || github.event.issue.number || '' }}"
308
- # Do not override PR_NUMBER if previously set
309
- if [[ -z "${PR_NUMBER:-}" ]]; then
310
- PR_NUMBER="${PR_NUMBER_EVT}"
311
- echo "PR_NUMBER=${PR_NUMBER}" >> "$GITHUB_ENV"
312
- fi
313
- if [[ -z "${PR_NUMBER}" || "${PR_NUMBER}" == "null" ]]; then
323
+ pr_number="${EVENT_PR_NUMBER}"
324
+ if [[ -z "${pr_number}" || "${pr_number}" == "null" ]]; then
314
325
  echo "Error: PR_NUMBER is empty."
315
326
  echo "This action requires a valid pull request context."
316
- echo "Current event: ${{ github.event_name }}"
327
+ echo "Current event: ${GITHUB_EVENT_NAME}"
317
328
  exit 2
318
329
  fi
330
+ echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT"
319
331
  fi
320
332
 
321
333
  - name: "Run github2gerrit Python CLI"
@@ -375,16 +387,17 @@ runs:
375
387
  GITHUB_BASE_REF: ${{ github.base_ref }}
376
388
  GITHUB_HEAD_REF: ${{ github.head_ref }}
377
389
  GITHUB_ACTOR: ${{ github.actor }}
378
- SYNC_ALL_OPEN_PRS: ${{ env.SYNC_ALL_OPEN_PRS }}
379
- PR_NUMBER: ${{ env.PR_NUMBER }}
390
+ SYNC_ALL_OPEN_PRS: ${{ steps.extract.outputs.sync_all }}
391
+ PR_NUMBER: ${{ steps.extract.outputs.pr_number }}
380
392
  G2G_TEST_MODE: "false"
381
393
  G2G_NO_GERRIT: ${{ inputs.G2G_NO_GERRIT }}
394
+ USE_LOCAL_ACTION: ${{ inputs.USE_LOCAL_ACTION }}
382
395
  run: |
383
396
  # Run github2gerrit Python CLI
384
397
  set -euo pipefail
385
398
  # Use different invocation methods based on repository or USE_LOCAL_ACTION flag
386
- if [[ "${{ inputs.USE_LOCAL_ACTION }}" == "true" ]] || \
387
- [[ "${{ github.repository }}" =~ lfreleng-actions/github2gerrit-action ]]; then
399
+ if [[ "${USE_LOCAL_ACTION}" == "true" ]] || \
400
+ [[ "${GITHUB_REPOSITORY}" =~ lfreleng-actions/github2gerrit-action ]]; then
388
401
  echo "Running:python -m github2gerrit.cli"
389
402
  python -m github2gerrit.cli
390
403
  else
@@ -58,8 +58,13 @@ dependencies = [
58
58
  # Security: Fix CVE-2026-21441 (decompression-bomb vulnerability)
59
59
  "urllib3>=2.6.3",
60
60
 
61
- # Security: Fix CVE-2026-26007 (SECT curve subgroup attack)
62
- "cryptography>=46.0.5",
61
+ # Security: cryptography bundles OpenSSL in its wheels, so OpenSSL
62
+ # security fixes ship as new cryptography releases. Floor at the
63
+ # patched release: fixes CVE-2026-26007 (SECT curve subgroup attack,
64
+ # 46.0.5) and GHSA-537c-gmf6-5ccf (vulnerable OpenSSL in wheels, no
65
+ # CVE assigned, 48.0.1). No upper bound, so future security releases
66
+ # are not held back.
67
+ "cryptography>=48.0.1",
63
68
  ]
64
69
 
65
70
  [project.urls]
@@ -268,6 +273,21 @@ reportUnsupportedDunderAll = "error"
268
273
 
269
274
 
270
275
  [tool.uv]
276
+ # Supply-chain cooldown: do not resolve anything published in the last
277
+ # 7 days (rolling window; uv records it as a relative span in uv.lock).
278
+ # Requires uv >= 0.9.17 for the relative-duration ("7 days") form.
279
+ exclude-newer = "7 days"
280
+ # cryptography bundles OpenSSL directly in its wheels, so OpenSSL security
281
+ # fixes are delivered as new cryptography releases. Exempt it from the
282
+ # supply-chain cooldown above so those patches are not held back by the
283
+ # rolling window.
284
+ #
285
+ # Added 2026-06-15 to unblock GHSA-537c-gmf6-5ccf ("Vulnerable OpenSSL
286
+ # included in cryptography wheels"; no CVE assigned), fixed in 48.0.1,
287
+ # which the 7-day cooldown was excluding. References:
288
+ # https://github.com/advisories/GHSA-537c-gmf6-5ccf
289
+ # https://openssl-library.org/news/secadv/20260609.txt
290
+ exclude-newer-package = { cryptography = "0 days" }
271
291
  # uv will manage installation based on this pyproject and lockfile.
272
292
  # No extra settings are required here; this stanza reserves the
273
293
  # namespace for future use if needed.
@@ -410,9 +410,13 @@ class Orchestrator:
410
410
 
411
411
  # Check if client has authentication
412
412
  if not client.is_authenticated:
413
+ from .gerrit_rest import warn_gerrit_credentials_unavailable
414
+
415
+ warn_gerrit_credentials_unavailable()
413
416
  log.debug(
414
- "Cannot update Gerrit change metadata: "
415
- "No credentials found (check .netrc or environment)"
417
+ "Cannot update Gerrit change metadata for %s: "
418
+ "no Gerrit REST credentials available",
419
+ change_id,
416
420
  )
417
421
  return False
418
422
 
@@ -380,12 +380,26 @@ def external_api_call(
380
380
  reason = (
381
381
  "final attempt" if is_final_attempt else "non-retryable"
382
382
  )
383
- log_exception_conditionally(
384
- log,
383
+ failure_msg = (
385
384
  f"[{api_type.value}] {operation} failed ({reason}) "
386
385
  f"after {attempt} attempt(s) in {duration:.2f}s: "
387
- f"{target}",
386
+ f"{target}"
388
387
  )
388
+ # Authentication/authorization failures (Gerrit REST
389
+ # 401/403) are surfaced once, closer to the request, as a
390
+ # concise warning. Avoid emitting a duplicate error/
391
+ # traceback for them here. Scope this strictly to Gerrit
392
+ # REST: other API types (e.g. GitHub) may also expose a
393
+ # ``.status`` attribute, and their auth/permission
394
+ # failures must retain error-level visibility.
395
+ is_gerrit_auth_failure = (
396
+ api_type == ApiType.GERRIT_REST
397
+ and getattr(exc, "status", None) in (401, 403)
398
+ )
399
+ if is_gerrit_auth_failure:
400
+ log.debug(failure_msg)
401
+ else:
402
+ log_exception_conditionally(log, failure_msg)
389
403
  _update_metrics(api_type, context, success=False, exc=exc)
390
404
  raise
391
405
  else:
@@ -11,6 +11,7 @@ merged and close the corresponding GitHub pull request that originated it.
11
11
  from __future__ import annotations
12
12
 
13
13
  import logging
14
+ import os
14
15
  import re
15
16
  from typing import Any
16
17
  from typing import Literal
@@ -1679,11 +1680,57 @@ def _build_gerrit_abandon_message(pr_obj: Any, pr_url: str) -> str:
1679
1680
  )
1680
1681
 
1681
1682
 
1683
+ def _abandon_change_via_ssh_if_possible(
1684
+ client: Any, change_number: str, message: str
1685
+ ) -> bool:
1686
+ """Attempt to abandon a change over SSH using ambient credentials.
1687
+
1688
+ Reads the Gerrit SSH connection details from the environment
1689
+ (populated by the action) and the host from the REST *client*.
1690
+
1691
+ Returns ``True`` only if the change was abandoned via SSH; ``False``
1692
+ when SSH is not configured or the SSH abandon did not succeed (in
1693
+ which case the caller should fall back to REST).
1694
+ """
1695
+ ssh_privkey = os.getenv("GERRIT_SSH_PRIVKEY_G2G", "").strip()
1696
+ ssh_user = os.getenv("GERRIT_SSH_USER_G2G", "").strip()
1697
+ if not ssh_privkey or not ssh_user:
1698
+ return False
1699
+
1700
+ host = os.getenv("GERRIT_SERVER", "").strip()
1701
+ if not host:
1702
+ host = getattr(client, "host", "") or ""
1703
+ if not host:
1704
+ return False
1705
+
1706
+ try:
1707
+ port = int(os.getenv("GERRIT_SERVER_PORT", "29418") or "29418")
1708
+ except ValueError:
1709
+ port = 29418
1710
+
1711
+ from .gerrit_ssh import abandon_change_via_ssh
1712
+
1713
+ return abandon_change_via_ssh(
1714
+ host=host,
1715
+ change_number=str(change_number),
1716
+ message=message,
1717
+ user=ssh_user,
1718
+ ssh_privkey=ssh_privkey,
1719
+ known_hosts=os.getenv("GERRIT_KNOWN_HOSTS", ""),
1720
+ port=port,
1721
+ )
1722
+
1723
+
1682
1724
  def _abandon_gerrit_change(
1683
1725
  client: Any, change_number: str, message: str
1684
1726
  ) -> None:
1685
1727
  """
1686
- Abandon a Gerrit change via REST API.
1728
+ Abandon a Gerrit change.
1729
+
1730
+ Prefers SSH (``gerrit review --abandon``) because mutating REST calls
1731
+ are rejected with HTTP 403 on Gerrit servers that do not allow
1732
+ unauthenticated REST writes and where only SSH credentials are
1733
+ configured. Falls back to the REST API when SSH is unavailable.
1687
1734
 
1688
1735
  Args:
1689
1736
  client: Gerrit REST client
@@ -1691,13 +1738,35 @@ def _abandon_gerrit_change(
1691
1738
  message: Abandon message
1692
1739
 
1693
1740
  Raises:
1694
- Exception: If abandon operation fails
1741
+ Exception: If the abandon operation fails
1695
1742
  """
1743
+ if _abandon_change_via_ssh_if_possible(client, change_number, message):
1744
+ log.debug(
1745
+ "Successfully abandoned Gerrit change %s via SSH", change_number
1746
+ )
1747
+ return
1748
+
1696
1749
  try:
1750
+ log.debug(
1751
+ "Falling back to REST abandon for Gerrit change %s", change_number
1752
+ )
1697
1753
  abandon_path = f"/changes/{change_number}/abandon"
1698
1754
  abandon_data = {"message": message}
1699
1755
  client.post(abandon_path, data=abandon_data)
1700
1756
  log.debug("Successfully abandoned Gerrit change %s", change_number)
1757
+ except GerritRestError as exc:
1758
+ if exc.is_auth_error:
1759
+ # Expected when no Gerrit REST credentials are available; the
1760
+ # REST layer already surfaced this once. Avoid a duplicate
1761
+ # error-level traceback for an authentication failure.
1762
+ log.debug(
1763
+ "REST abandon for Gerrit change %s failed (HTTP %s)",
1764
+ change_number,
1765
+ exc.status,
1766
+ )
1767
+ else:
1768
+ log.exception("Failed to abandon Gerrit change %s", change_number)
1769
+ raise
1701
1770
  except Exception:
1702
1771
  log.exception("Failed to abandon Gerrit change %s", change_number)
1703
1772
  raise
@@ -13,6 +13,7 @@ from typing import Any
13
13
  from urllib.parse import quote
14
14
 
15
15
  from .gerrit_rest import GerritRestClient
16
+ from .gerrit_rest import warn_gerrit_credentials_unavailable
16
17
 
17
18
 
18
19
  log = logging.getLogger(__name__)
@@ -148,6 +149,20 @@ def query_open_changes_by_project(
148
149
  query = f'project:"{_gerrit_quote(project)}" status:open owner:self'
149
150
  if branch:
150
151
  query += f' branch:"{_gerrit_quote(branch)}"'
152
+
153
+ # The ``owner:self`` predicate requires an authenticated Gerrit
154
+ # session; an anonymous request is rejected with HTTP 403. Skip the
155
+ # query (warning once per run) instead of issuing a request that is
156
+ # guaranteed to fail and would otherwise emit error-level noise.
157
+ if not client.is_authenticated:
158
+ warn_gerrit_credentials_unavailable()
159
+ log.debug(
160
+ "Skipping owner:self query for project '%s': "
161
+ "no Gerrit REST credentials available",
162
+ project,
163
+ )
164
+ return []
165
+
151
166
  log.debug("Querying Gerrit for open changes: %s", query)
152
167
 
153
168
  try: