github2gerrit 2.1.1__tar.gz → 2.2.0__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 (148) hide show
  1. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.pre-commit-config.yaml +41 -9
  2. github2gerrit-2.2.0/PKG-INFO +463 -0
  3. github2gerrit-2.2.0/README.md +416 -0
  4. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/action.yaml +27 -2
  5. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/docs/cli.md +29 -1
  6. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/docs/features.md +124 -10
  7. github2gerrit-2.2.0/src/github2gerrit/approvers.py +296 -0
  8. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/cli.py +330 -12
  9. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/config.py +29 -10
  10. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/core.py +13 -11
  11. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gitutils.py +1 -1
  12. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/models.py +40 -7
  13. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/pr_approval.py +35 -6
  14. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/pr_commands.py +113 -5
  15. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/utils.py +10 -4
  16. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_action_environment_mapping.py +3 -0
  17. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_action_step_validation.py +650 -0
  18. github2gerrit-2.2.0/tests/test_approver_sources.py +433 -0
  19. github2gerrit-2.2.0/tests/test_cache_isolation.py +159 -0
  20. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_composite_action_coverage.py +28 -2
  21. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_config_helpers.py +55 -0
  22. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_duplicate_detection.py +3 -2
  23. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_fork_approval_gate.py +352 -8
  24. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_fork_pr_hardening.py +95 -1
  25. github2gerrit-2.2.0/tests/test_precommit_mypy_pins.py +338 -0
  26. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_utils.py +27 -1
  27. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/uv.lock +81 -84
  28. github2gerrit-2.1.1/PKG-INFO +0 -395
  29. github2gerrit-2.1.1/README.md +0 -348
  30. github2gerrit-2.1.1/tests/test_cache_isolation.py +0 -210
  31. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.aislop/config.yml +0 -0
  32. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.editorconfig +0 -0
  33. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.gitignore +0 -0
  34. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.gitlint +0 -0
  35. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.markdownlint.yaml +0 -0
  36. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/.yamllint +0 -0
  37. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/LICENSE +0 -0
  38. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/LICENSES/Apache-2.0.txt +0 -0
  39. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/REUSE.toml +0 -0
  40. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/SECURITY.md +0 -0
  41. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/docs/COMMIT_RULES.md +0 -0
  42. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/docs/development.md +0 -0
  43. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/docs/github2gerrit_token_permissions_classic.png +0 -0
  44. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/pyproject.toml +0 -0
  45. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/sitecustomize.py +0 -0
  46. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/__init__.py +0 -0
  47. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/commit_normalization.py +0 -0
  48. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/commit_rules.py +0 -0
  49. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/constants.py +0 -0
  50. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/duplicate_detection.py +0 -0
  51. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/error_codes.py +0 -0
  52. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/external_api.py +0 -0
  53. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gerrit_pr_closer.py +0 -0
  54. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gerrit_query.py +0 -0
  55. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gerrit_rest.py +0 -0
  56. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gerrit_ssh.py +0 -0
  57. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gerrit_urls.py +0 -0
  58. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/github_api.py +0 -0
  59. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/gitreview.py +0 -0
  60. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/mapping_comment.py +0 -0
  61. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/netrc.py +0 -0
  62. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/orchestrator/__init__.py +0 -0
  63. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
  64. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/pr_content_filter.py +0 -0
  65. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/pr_directives.py +0 -0
  66. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/reconcile_matcher.py +0 -0
  67. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/rich_display.py +0 -0
  68. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/rich_logging.py +0 -0
  69. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/similarity.py +0 -0
  70. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/ssh_agent_setup.py +0 -0
  71. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/ssh_common.py +0 -0
  72. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/ssh_config_parser.py +0 -0
  73. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/ssh_discovery.py +0 -0
  74. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/trailers.py +0 -0
  75. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/src/github2gerrit/trust.py +0 -0
  76. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/conftest.py +0 -0
  77. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/fixtures/__init__.py +0 -0
  78. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/fixtures/make_repo.py +0 -0
  79. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/fixtures/ssh_config_samples.py +0 -0
  80. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_action_outputs.py +0 -0
  81. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_action_pr_number_handling.py +0 -0
  82. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_automation_only.py +0 -0
  83. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_change_id_deduplication.py +0 -0
  84. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_clean_squash_title.py +0 -0
  85. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_cli.py +0 -0
  86. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_cli_helpers.py +0 -0
  87. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_cli_netrc_options.py +0 -0
  88. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_cli_outputs_file.py +0 -0
  89. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_cli_url_and_dryrun.py +0 -0
  90. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_commit_normalization.py +0 -0
  91. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_commit_rules.py +0 -0
  92. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_config_and_reviewers.py +0 -0
  93. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_close_pr_policy.py +0 -0
  94. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_config_and_errors.py +0 -0
  95. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_gerrit_backref_comment.py +0 -0
  96. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_gerrit_push_errors.py +0 -0
  97. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_gerrit_rest_results.py +0 -0
  98. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_integration_fixture_repo.py +0 -0
  99. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_prepare_commits.py +0 -0
  100. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_shallow_clone.py +0 -0
  101. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_ssh_setup.py +0 -0
  102. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_core_ssrf_protection.py +0 -0
  103. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_dependency_supersession.py +0 -0
  104. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_dns_validation_and_no_gerrit.py +0 -0
  105. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_email_case_normalization.py +0 -0
  106. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_error_codes.py +0 -0
  107. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_external_api_framework.py +0 -0
  108. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_final_state_reconciliation.py +0 -0
  109. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_force_flag_cli.py +0 -0
  110. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_change_id_footer.py +0 -0
  111. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_change_status_checks.py +0 -0
  112. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_pr_closer.py +0 -0
  113. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_rest_client.py +0 -0
  114. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_ssh_abandon.py +0 -0
  115. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_urls.py +0 -0
  116. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gerrit_urls_more.py +0 -0
  117. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ghe_and_gitreview_args.py +0 -0
  118. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_github_api_error_handling.py +0 -0
  119. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_github_api_helpers.py +0 -0
  120. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_github_api_retry_and_helpers.py +0 -0
  121. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gitreview.py +0 -0
  122. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_gitutils_helpers.py +0 -0
  123. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_issue_157_regressions.py +0 -0
  124. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_mapping_comment_additional.py +0 -0
  125. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_mapping_comment_digest_and_backref.py +0 -0
  126. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_metadata_and_reconciliation.py +0 -0
  127. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_metadata_trailer_separation_bug.py +0 -0
  128. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_misc_small_coverage.py +0 -0
  129. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_netrc.py +0 -0
  130. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_orphan_rest_side_effects.py +0 -0
  131. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_pr_command_authorisation.py +0 -0
  132. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_pr_commands.py +0 -0
  133. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_pr_content_filter.py +0 -0
  134. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_pr_content_filter_integration.py +0 -0
  135. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_pr_update_detection.py +0 -0
  136. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_reconciliation_extracted_module.py +0 -0
  137. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_reconciliation_plan_and_orphans.py +0 -0
  138. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_reconciliation_scenarios.py +0 -0
  139. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_agent.py +0 -0
  140. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_agent_ownership.py +0 -0
  141. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_artifact_prevention.py +0 -0
  142. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_common.py +0 -0
  143. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_discovery.py +0 -0
  144. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_ssh_discovery_dry_run.py +0 -0
  145. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_trailers_additional.py +0 -0
  146. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/test_url_parser.py +0 -0
  147. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/unit/test_config_integration.py +0 -0
  148. {github2gerrit-2.1.1 → github2gerrit-2.2.0}/tests/unit/test_ssh_config_parser.py +0 -0
@@ -64,7 +64,7 @@ repos:
64
64
  types: [yaml]
65
65
 
66
66
  - repo: https://github.com/astral-sh/ruff-pre-commit
67
- rev: 1f1e8bf348ff38fc88619a38d3ca4d9c56abea49 # frozen: v0.16.5
67
+ rev: e8f7d69b957a30acb2c875078d5fd012de24b64c # frozen: v0.16.7
68
68
  hooks:
69
69
  - id: ruff
70
70
  files: ^(src|scripts|tests)/.+\.py$|^sitecustomize\.py$
@@ -91,13 +91,45 @@ repos:
91
91
  # pygerrit2 is deliberately absent: it is an optional runtime
92
92
  # dependency and pyproject.toml already gives it
93
93
  # ignore_missing_imports.
94
+ #
95
+ # The pins below are exact (==) rather than floors. This
96
+ # environment is resolved from PyPI at install time, not from
97
+ # uv.lock, so a >= constraint would still pick up whatever is
98
+ # newest whenever pre-commit.ci happens to rebuild the
99
+ # environment, and the hook would then report on a dependency set
100
+ # nobody runs locally. That drift is not theoretical: CI once
101
+ # resolved typer 0.27.2 against a lockfile holding 0.27.1, and the
102
+ # two disagreed about which module typer.Exit lives in.
103
+ # Every version here must equal the one uv.lock records for that
104
+ # package, so update both together (after `uv lock --upgrade`, for
105
+ # instance). tests/test_precommit_mypy_pins.py compares the two
106
+ # files and fails if they diverge in either direction.
107
+ #
108
+ # These pins do not freeze the whole resolved closure: pip still
109
+ # picks transitive versions at build time, and listing every
110
+ # package uv.lock records would duplicate the lockfile here. They
111
+ # do not need to. mypy's verdict can only turn on a package whose
112
+ # types it reads, which means one the checked files import, so the
113
+ # list covers exactly that set -- rich is here because src/ imports
114
+ # it, not because typer happens to pull it in. The same test asserts
115
+ # the list stays complete against those imports.
116
+ #
117
+ # mypy itself is pinned here as well as coming from the mirror's
118
+ # rev above. That is not redundant: the rev decides which mypy
119
+ # runs, and nothing otherwise ties it to the locked one, so an
120
+ # autoupdate of the mirror or a lockfile-only mypy upgrade would
121
+ # leave the hook running a different checker in silence. With the
122
+ # pin present, the test compares it against uv.lock, and a mirror
123
+ # whose rev disagrees fails to install rather than quietly winning.
94
124
  additional_dependencies:
95
- - click>=8.2
96
- - pytest
97
- - responses
98
- - typer
99
- - types-PyYAML
100
- - types-requests
125
+ - mypy==2.3.1
126
+ - click==8.5.0
127
+ - pytest==9.1.1
128
+ - responses==0.26.3
129
+ - rich==15.0.0
130
+ - typer==0.27.2
131
+ - types-PyYAML==6.0.12.20260906
132
+ - types-requests==2.33.0.20260906
101
133
 
102
134
  - repo: https://github.com/btford/write-good
103
135
  rev: ab66ce10136dfad5146e69e70f82a3efac8842c1 # frozen: v1.0.8
@@ -138,7 +170,7 @@ repos:
138
170
 
139
171
  # Requires a mirror, primary repo lacks .pre-commit-hooks.yaml
140
172
  - repo: https://github.com/DetachHead/basedpyright-prek-mirror
141
- rev: 3c94d276aa3005fa1f5a977946c9dc106135c083 # frozen: 1.39.10
173
+ rev: 706e6acebe9e88c2c28d1527d3085a14b11794c4 # frozen: 1.40.1
142
174
  hooks:
143
175
  - id: basedpyright
144
176
  # Every Python path in the repository. Keep in sync with the
@@ -146,7 +178,7 @@ repos:
146
178
  files: ^(src|scripts|tests)/.+\.py$|^sitecustomize\.py$
147
179
 
148
180
  - repo: https://github.com/lfreleng-actions/gha-workflow-linter
149
- rev: cae470a1e8988c3cacbe9cc40a0d6b8e6881bde2 # frozen: v1.5.5
181
+ rev: 445a55d2a15b12217828eecf8d4bf7741e0e1360 # frozen: v1.5.6
150
182
  hooks:
151
183
  - id: gha-workflow-linter
152
184
 
@@ -0,0 +1,463 @@
1
+ Metadata-Version: 2.5
2
+ Name: github2gerrit
3
+ Version: 2.2.0
4
+ Summary: Submit a GitHub pull request to a Gerrit repository.
5
+ Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit-action
6
+ Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit-action
7
+ Project-URL: Issues, https://github.com/lfreleng-actions/github2gerrit-action/issues
8
+ Author-email: Matthew Watkins <mwatkins@linuxfoundation.org>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: actions,ci,cli,gerrit,github,typer
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: Software Development :: Version Control
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: cryptography>=48.0.1
26
+ Requires-Dist: git-review>=2.5.0
27
+ Requires-Dist: pygerrit2>=2.0.15
28
+ Requires-Dist: pygithub>=2.8.1
29
+ Requires-Dist: pynacl>=1.6.2
30
+ Requires-Dist: pyyaml>=6.0.3
31
+ Requires-Dist: rich>=14.2.0
32
+ Requires-Dist: typer>=0.20.1
33
+ Requires-Dist: urllib3>=2.6.3
34
+ Provides-Extra: dev
35
+ Requires-Dist: click>=8.2; extra == 'dev'
36
+ Requires-Dist: coverage[toml]>=7.10.6; extra == 'dev'
37
+ Requires-Dist: mypy>=1.17.1; extra == 'dev'
38
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
39
+ Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
40
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
41
+ Requires-Dist: responses>=0.25.8; extra == 'dev'
42
+ Requires-Dist: ruff>=0.6.3; extra == 'dev'
43
+ Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
44
+ Requires-Dist: types-requests>=2.31.0; extra == 'dev'
45
+ Requires-Dist: types-urllib3>=1.26.25.14; extra == 'dev'
46
+ Description-Content-Type: text/markdown
47
+
48
+ <!--
49
+ SPDX-License-Identifier: Apache-2.0
50
+ SPDX-FileCopyrightText: 2025 The Linux Foundation
51
+ -->
52
+
53
+ # github2gerrit
54
+
55
+ Mirror GitHub pull requests into Gerrit changes.
56
+
57
+ github2gerrit serves projects where Gerrit is the source of truth and
58
+ GitHub hosts a read-only mirror. Automation tools such as Dependabot
59
+ raise pull requests against the GitHub mirror; this action translates
60
+ those pull requests into Gerrit changes, keeping the two systems in
61
+ sync across the entire lifecycle: creation, updates/rebases, metadata
62
+ edits, closure, and cleanup.
63
+
64
+ The implementation is a Python CLI tool (`github2gerrit`, published to
65
+ PyPI) wrapped in a GitHub composite action, plus a reusable workflow
66
+ for straightforward deployment in consuming repositories.
67
+
68
+ ## Goals and purpose
69
+
70
+ - Let Gerrit-based projects receive dependency updates (Dependabot)
71
+ and other automated changes raised against a GitHub mirror.
72
+ - Keep GitHub PRs and Gerrit changes synchronized in both directions:
73
+ PR updates become new patchsets, merged/abandoned changes close
74
+ their source PRs, and closed PRs abandon their Gerrit changes.
75
+ - Avoid duplicate Gerrit changes through Change-Id reuse and
76
+ reconciliation when automation rebases or re-raises PRs.
77
+
78
+ ## How it works
79
+
80
+ ```mermaid
81
+ flowchart TD
82
+ A[PR event on GitHub mirror] --> B[github2gerrit action]
83
+ B --> C{Operation mode}
84
+ C -->|opened| D[Create Gerrit change]
85
+ C -->|synchronize| E[New patchset on existing change]
86
+ C -->|edited| F[Sync PR metadata to Gerrit]
87
+ C -->|closed| G[Abandon Gerrit change]
88
+ D --> H[Comment Gerrit URL on PR]
89
+ E --> H
90
+ ```
91
+
92
+ In more detail, a run:
93
+
94
+ 1. Reads PR context and inputs; detects the operation mode
95
+ (CREATE, UPDATE, EDIT, CLOSE) from the triggering event.
96
+ 2. Reads `.gitreview` for the Gerrit host, port, and project
97
+ (or uses explicit `GERRIT_SERVER` / `GERRIT_PROJECT` inputs).
98
+ 3. Sets up git and SSH for Gerrit, derives missing credentials from
99
+ the organization name where possible.
100
+ 4. Prepares commits: squashed single commit (default), one-by-one
101
+ cherry-picks (`SUBMIT_SINGLE_COMMITS`), or PR title/body as the
102
+ commit message (`USE_PR_AS_COMMIT`). Reuses existing Change-Id
103
+ trailers on updates so pushes create new patchsets, not new
104
+ changes.
105
+ 5. Pushes to `refs/for/<branch>` with a Gerrit topic derived from the
106
+ project and PR number (prefix configurable via `G2G_TOPIC_PREFIX`,
107
+ default `GH`), queries Gerrit for the resulting URL/number/SHA, and
108
+ cross-links: a back-reference comment in Gerrit and the change
109
+ URL(s) on the PR.
110
+
111
+ See [docs/features.md](docs/features.md) for detailed feature
112
+ documentation: PR update handling, comment commands, duplicate
113
+ detection, reconciliation, cleanup, commit normalization,
114
+ configuration precedence, and credential derivation.
115
+
116
+ ## Quick start
117
+
118
+ ### Prerequisites
119
+
120
+ - A Gerrit account for the automation user, with SSH access and
121
+ permission to push to `refs/for/*` on the target project.
122
+ - The SSH private key stored as a repository or organization secret:
123
+ `GERRIT_SSH_PRIVKEY_G2G`.
124
+ - A `.gitreview` file in the repository (recommended). Without it,
125
+ pass `GERRIT_SERVER`, `GERRIT_SERVER_PORT`, and `GERRIT_PROJECT`
126
+ explicitly.
127
+ - Optional repository/organization variables: `GERRIT_KNOWN_HOSTS`,
128
+ `GERRIT_SSH_USER_G2G`, `GERRIT_SSH_USER_G2G_EMAIL`. The tool
129
+ derives missing values from the organization name and populates
130
+ known hosts automatically on first run.
131
+
132
+ ### Option A: reusable workflow (recommended)
133
+
134
+ Add a thin caller workflow to the consuming repository:
135
+
136
+ ```yaml
137
+ # .github/workflows/github2gerrit.yaml
138
+ name: github2gerrit
139
+
140
+ on:
141
+ pull_request_target:
142
+ types: [opened, reopened, edited, synchronize, closed]
143
+ # Lets anyone post '@github2gerrit check' to transfer an approved
144
+ # fork PR. A review cannot do it: GitHub withholds secrets from
145
+ # pull_request_review runs on fork PRs.
146
+ issue_comment:
147
+ types: [created]
148
+ push:
149
+ branches: [main, master]
150
+ workflow_dispatch:
151
+ inputs:
152
+ PR_NUMBER:
153
+ description: "PR number to process; 0 processes all open"
154
+ required: false
155
+ default: "0"
156
+ type: string
157
+
158
+ permissions: {}
159
+
160
+ jobs:
161
+ github2gerrit:
162
+ permissions:
163
+ contents: read
164
+ pull-requests: write
165
+ issues: write
166
+ # yamllint disable-line rule:line-length
167
+ uses: lfreleng-actions/github2gerrit-action/.github/workflows/github2gerrit.yaml@main
168
+ with:
169
+ GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}
170
+ GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}
171
+ GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}
172
+ PR_NUMBER: ${{ inputs.PR_NUMBER || '0' }}
173
+ # Required for human-authored fork PRs: the default closes them
174
+ # before the approval gate ever sees them
175
+ AUTOMATION_ONLY: false
176
+ secrets:
177
+ GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
178
+ ```
179
+
180
+ The `push` trigger enables closing PRs whose Gerrit changes have
181
+ merged; `workflow_dispatch` enables manual processing. The
182
+ `issue_comment` trigger exists for pull requests raised from forks,
183
+ which need a maintainer's approval and a *privileged* run to act on
184
+ it — see
185
+ [fork pull requests](docs/features.md#which-triggers-can-lift-the-gate).
186
+
187
+ Setting `AUTOMATION_ONLY: false` goes with it. It defaults to `true`,
188
+ which closes a human-authored pull request before the approval gate
189
+ sees it, so leaving it set would make the fork path unreachable. Drop
190
+ both if the repository only ever receives automation PRs.
191
+
192
+ Repositories using the Gerrit-side dispatch integration should also
193
+ declare `GERRIT_CHANGE_URL`, `GERRIT_EVENT_TYPE`, and `GERRIT_BRANCH`
194
+ as dispatch inputs and forward them the same way.
195
+
196
+ Pin `@main` to a release tag or commit SHA for production use.
197
+
198
+ A bulk `workflow_dispatch` (`PR_NUMBER` of `0`) processes every open
199
+ pull request in one run, which does not serialise against the
200
+ per-pull-request runs that events start. Avoid triggering one while
201
+ pull request activity is in flight; see
202
+ [#422](https://github.com/lfreleng-actions/github2gerrit-action/issues/422).
203
+
204
+ ### Option B: composite action
205
+
206
+ Call the action directly for full control over all inputs:
207
+
208
+ ```yaml
209
+ name: github2gerrit
210
+
211
+ on:
212
+ pull_request_target:
213
+ types: [opened, reopened, edited, synchronize, closed]
214
+ # Lets anyone post '@github2gerrit check' to transfer an approved
215
+ # fork PR. A review cannot do it: GitHub withholds secrets from
216
+ # pull_request_review runs on fork PRs.
217
+ issue_comment:
218
+ types: [created]
219
+ workflow_dispatch:
220
+
221
+ permissions:
222
+ contents: read
223
+ pull-requests: write
224
+ issues: write
225
+
226
+ jobs:
227
+ submit-to-gerrit:
228
+ runs-on: ubuntu-latest
229
+ # Ordinary comments must not start a run. Besides the wasted job,
230
+ # a comment occupies this pull request's concurrency slot and can
231
+ # evict a pending re-check. A closed pull request has no gate left
232
+ # to lift, so a run there could only fail. The guard checks the
233
+ # mention and the command apart, since the parser accepts any
234
+ # whitespace between them.
235
+ # yamllint disable-line rule:line-length
236
+ if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && github.event.issue.state == 'open' && contains(github.event.comment.body, '@github2gerrit') && (contains(github.event.comment.body, 'check') || contains(github.event.comment.body, 'recheck') || contains(github.event.comment.body, 'retry'))) }}
237
+ # Serialise every route to one pull request. Two runs for the same
238
+ # approved commit could otherwise both find no Gerrit change and
239
+ # create one each; ALLOW_DUPLICATES defaults to true, so nothing
240
+ # downstream would stop them.
241
+ concurrency:
242
+ # yamllint disable-line rule:line-length
243
+ group: g2g-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number || github.event_name }}
244
+ cancel-in-progress: false
245
+ steps:
246
+ - name: Submit PR to Gerrit
247
+ id: g2g
248
+ uses: lfreleng-actions/github2gerrit-action@main
249
+ with:
250
+ GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}
251
+ GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
252
+ GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}
253
+ GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}
254
+ # Goes with the fork trigger above: the default closes a
255
+ # human-authored PR before the approval gate sees it
256
+ AUTOMATION_ONLY: false
257
+ ```
258
+
259
+ ### Option C: command-line tool
260
+
261
+ The underlying Python CLI supports local and ad-hoc use (for example,
262
+ processing a single PR URL or bulk-processing a repository). This is
263
+ a secondary use case; see [docs/cli.md](docs/cli.md).
264
+
265
+ ```bash
266
+ uvx github2gerrit https://github.com/onap/portal-ng-bff/pull/33
267
+ ```
268
+
269
+ ## Action inputs
270
+
271
+ <!-- markdownlint-disable MD013 -->
272
+
273
+ | Input | Required | Default | Description |
274
+ | ------------------------------ | -------- | ---------------- | -------------------------------------------------------------- |
275
+ | `GERRIT_SSH_PRIVKEY_G2G` | Yes | — | SSH private key content used to authenticate to Gerrit |
276
+ | `GERRIT_KNOWN_HOSTS` | No | — | Known hosts entries for Gerrit SSH (auto-populated when empty) |
277
+ | `GERRIT_SSH_USER_G2G` | No | `""` | Gerrit SSH username; derived when not supplied |
278
+ | `GERRIT_SSH_USER_G2G_EMAIL` | No | `""` | Gerrit user email address; derived when not supplied |
279
+ | `GERRIT_SERVER` | No | `""` | Gerrit server hostname; `.gitreview` preferred |
280
+ | `GERRIT_SERVER_PORT` | No | `"29418"` | Gerrit SSH port |
281
+ | `GERRIT_PROJECT` | No | `""` | Gerrit project name; `.gitreview` preferred |
282
+ | `GERRIT_HTTP_BASE_PATH` | No | `""` | HTTP base path for Gerrit REST API (e.g. `/r`) |
283
+ | `GERRIT_HTTP_USER` | No | `""` | Gerrit HTTP user for REST queries |
284
+ | `GERRIT_HTTP_PASSWORD` | No | `""` | Gerrit HTTP password/token for REST queries |
285
+ | `ORGANIZATION` | No | repository owner | GitHub organization/owner used for credential derivation |
286
+ | `PR_NUMBER` | No | `"0"` | PR number to process; `0` processes all open PRs (dispatch) |
287
+ | `FETCH_DEPTH` | No | `"10"` | Git history depth: PR fetch, push checkout, reconciliation |
288
+ | `SUBMIT_SINGLE_COMMITS` | No | `"false"` | Submit one commit at a time to Gerrit |
289
+ | `USE_PR_AS_COMMIT` | No | `"false"` | Use PR title and body as the commit message |
290
+ | `PRESERVE_GITHUB_PRS` | No | `"true"` | Do not close GitHub PRs after pushing to Gerrit |
291
+ | `CLOSE_MERGED_PRS` | No | `"true"` | Close GitHub PRs when their Gerrit change merges |
292
+ | `CLEANUP_ABANDONED` | No | `"true"` | Close GitHub PRs for abandoned Gerrit changes |
293
+ | `CLEANUP_GERRIT` | No | `"true"` | Abandon Gerrit changes when their GitHub PR closes |
294
+ | `CREATE_MISSING` | No | `"false"` | Create a new change when UPDATE finds no existing change |
295
+ | `ALLOW_DUPLICATES` | No | `"true"` | Allow submitting duplicate changes without error |
296
+ | `DUPLICATE_TYPES` | No | `"open"` | Comma-separated Gerrit states checked for duplicates |
297
+ | `AUTOMATION_ONLY` | No | `"true"` | Accept PRs from known automation tools only |
298
+ | `NORMALISE_COMMIT` | No | `"false"` | Normalize commit messages to conventional commit format |
299
+ | `COMMIT_RULES_JSON` | No | `""` | JSON commit message validation rules (see docs) |
300
+ | `ISSUE_ID` | No | `""` | Issue ID trailer to include (e.g. `ABC-123`) |
301
+ | `ISSUE_ID_LOOKUP_JSON` | No | `"[]"` | JSON array mapping GitHub actors to Issue IDs |
302
+ | `REVIEWERS_EMAIL` | No | `""` | Comma-separated reviewer emails |
303
+ | `DRY_RUN` | No | `"false"` | Check settings and PR metadata; do not write to Gerrit |
304
+ | `FORCE` | No | `"false"` | Force PR closure regardless of Gerrit change status |
305
+ | `G2G_USE_SSH_AGENT` | No | `"true"` | Use SSH agent instead of file-based keys |
306
+ | `G2G_APPROVER_LOGINS` | No | `""` | Logins whose approvals clear the fork gate, comma-separated |
307
+ | `G2G_APPROVERS_FROM_INFO_YAML` | No | `"false"` | Read fork-gate approvers from the base repo's `INFO.yaml` |
308
+ | `G2G_INFO_YAML_MATCH_LFID` | No | `"false"` | Also match `INFO.yaml` `id` as a GitHub login (see note below) |
309
+ | `G2G_NO_GERRIT` | No | `"false"` | Run the pipeline without contacting Gerrit (forces dry-run) |
310
+ | `G2G_DISABLED` | No | `""` | Kill switch: skip all processing when `true` |
311
+ | `ALLOW_GHE_URLS` | No | `"false"` | Allow GitHub Enterprise URLs in direct URL mode |
312
+ | `VERBOSE` | No | `"false"` | Verbose output (sets log level to DEBUG) |
313
+ | `CI_TESTING` | No | `"false"` | CI testing mode; overrides `.gitreview` |
314
+ | `USE_LOCAL_ACTION` | No | `"false"` | Use local repository code instead of the PyPI package |
315
+
316
+ <!-- markdownlint-enable MD013 -->
317
+
318
+ Every input maps to an environment variable of the same name
319
+ (`VERBOSE` maps to `G2G_VERBOSE`), and most have matching CLI
320
+ flags. Reconciliation tuning (`SIMILARITY_SUBJECT`,
321
+ `SIMILARITY_UPDATE_FACTOR`, `SIMILARITY_FILES`, `REUSE_STRATEGY`) is
322
+ available through environment variables and CLI flags only; set these
323
+ via `env:` on the action step when needed. That works for the
324
+ composite action but **not** through the reusable workflow, where
325
+ environment variables do not cross the `workflow_call` boundary. See
326
+ [docs/cli.md](docs/cli.md) for the full option reference and
327
+ [docs/features.md](docs/features.md) for feature-specific settings.
328
+
329
+ Nine `G2G_`-prefixed settings can come from repository or organisation
330
+ **variables**, which the reusable workflow reads and passes to the
331
+ action — among them `G2G_TRUSTED_ASSOCIATIONS`, `G2G_TOPIC_PREFIX`
332
+ and `G2G_ENABLE_DERIVATION`, plus the `G2G_NO_GERRIT` and
333
+ `G2G_DISABLED` kill switches. An organisation can set one once for
334
+ every repository beneath it. A workflow calling the composite action
335
+ directly does not inherit those variables; map them in with `env:`
336
+ yourself. See [docs/cli.md](docs/cli.md) for the full list.
337
+
338
+ The three `G2G_APPROVER*` inputs widen who may clear the fork approval
339
+ gate, and each defaults to off. `G2G_INFO_YAML_MATCH_LFID` carries a
340
+ risk worth understanding before enabling: `INFO.yaml`'s `id` field
341
+ holds an LFID rather than a GitHub login, so matching on it lets
342
+ whoever registers that username on GitHub inherit committer authority.
343
+ Matching on `github_id` has no such exposure. See
344
+ [fork pull requests](docs/features.md#naming-approvers-directly).
345
+
346
+ ## Action outputs
347
+
348
+ <!-- markdownlint-disable MD013 -->
349
+
350
+ | Output | Description |
351
+ | --------------------------- | ------------------------------------------ |
352
+ | `gerrit_change_request_url` | Gerrit change URL(s), newline-separated |
353
+ | `gerrit_change_request_num` | Gerrit change number(s), newline-separated |
354
+ | `gerrit_commit_sha` | Patch set commit SHA(s), newline-separated |
355
+
356
+ <!-- markdownlint-enable MD013 -->
357
+
358
+ Access outputs in later steps with
359
+ `${{ steps.<step-id>.outputs.<output-name> }}`. The reusable workflow
360
+ re-exports all three outputs to callers.
361
+
362
+ ## Reusable workflow interface
363
+
364
+ The reusable workflow (`.github/workflows/github2gerrit.yaml`) wraps
365
+ the composite action for `workflow_call`, supporting caller triggers
366
+ `pull_request_target`, `issue_comment` (re-check an approved fork PR),
367
+ `push` (close PRs for merged Gerrit changes), and `workflow_dispatch`
368
+ (manual runs and Gerrit-event dispatches).
369
+ Input defaults match the composite action defaults.
370
+
371
+ <!-- markdownlint-disable MD013 -->
372
+
373
+ | Input | Type | Default | Description |
374
+ | ------------------------------ | ------- | ---------------- | ------------------------------------------------ |
375
+ | `GERRIT_KNOWN_HOSTS` | string | `""` | Known hosts entries for Gerrit SSH |
376
+ | `GERRIT_SSH_USER_G2G` | string | `""` | Gerrit SSH username |
377
+ | `GERRIT_SSH_USER_G2G_EMAIL` | string | `""` | Gerrit user email address |
378
+ | `GERRIT_SERVER` | string | `""` | Gerrit server hostname |
379
+ | `GERRIT_SERVER_PORT` | string | `"29418"` | Gerrit SSH port |
380
+ | `GERRIT_PROJECT` | string | `""` | Gerrit project name |
381
+ | `GERRIT_HTTP_BASE_PATH` | string | `""` | HTTP base path for Gerrit REST |
382
+ | `GERRIT_HTTP_USER` | string | `""` | Gerrit HTTP user for REST queries |
383
+ | `GERRIT_HTTP_PASSWORD` | string | `""` | Gerrit HTTP password/token for REST queries |
384
+ | `G2G_USE_SSH_AGENT` | boolean | `true` | Use SSH agent instead of file-based keys |
385
+ | `G2G_APPROVER_LOGINS` | string | `""` | Logins whose approvals clear the fork gate |
386
+ | `G2G_APPROVERS_FROM_INFO_YAML` | boolean | `false` | Read approvers from the base repo's `INFO.yaml` |
387
+ | `G2G_INFO_YAML_MATCH_LFID` | boolean | `false` | Also match `INFO.yaml` `id` as a GitHub login |
388
+ | `ORGANIZATION` | string | repository owner | GitHub organization/owner |
389
+ | `PR_NUMBER` | string | `"0"` | PR to process on dispatch; `0` processes all |
390
+ | `FETCH_DEPTH` | string | `"10"` | Git depth: PR, push, reconciliation |
391
+ | `SUBMIT_SINGLE_COMMITS` | boolean | `false` | Submit one commit at a time |
392
+ | `USE_PR_AS_COMMIT` | boolean | `false` | Use PR title and body as the commit message |
393
+ | `PRESERVE_GITHUB_PRS` | boolean | `true` | Do not close GitHub PRs after pushing |
394
+ | `CLOSE_MERGED_PRS` | boolean | `true` | Close GitHub PRs when their Gerrit change merges |
395
+ | `CLEANUP_ABANDONED` | boolean | `true` | Close GitHub PRs for abandoned Gerrit changes |
396
+ | `CLEANUP_GERRIT` | boolean | `true` | Abandon Gerrit changes when their PR closes |
397
+ | `CREATE_MISSING` | boolean | `false` | Create a change when UPDATE finds none |
398
+ | `AUTOMATION_ONLY` | boolean | `true` | Accept PRs from known automation tools only |
399
+ | `NORMALISE_COMMIT` | boolean | `false` | Normalize commit messages |
400
+ | `COMMIT_RULES_JSON` | string | `""` | JSON commit message validation rules |
401
+ | `ALLOW_DUPLICATES` | boolean | `true` | Allow submitting duplicate changes |
402
+ | `DUPLICATE_TYPES` | string | `"open"` | Gerrit states checked for duplicates |
403
+ | `FORCE` | boolean | `false` | Force PR closure regardless of change status |
404
+ | `VERBOSE` | boolean | `false` | Verbose output (DEBUG log level) |
405
+ | `ALLOW_GHE_URLS` | boolean | `false` | Allow GitHub Enterprise URLs |
406
+ | `DRY_RUN` | boolean | `false` | Check only; do not write to Gerrit |
407
+ | `ISSUE_ID` | string | `""` | Issue ID trailer to include |
408
+ | `ISSUE_ID_LOOKUP_JSON` | string | `"[]"` | JSON array mapping GitHub actors to Issue IDs |
409
+ | `REVIEWERS_EMAIL` | string | `""` | Comma-separated reviewer emails |
410
+ | `GERRIT_CHANGE_URL` | string | `""` | Gerrit change URL from a Gerrit event dispatch¹ |
411
+ | `GERRIT_EVENT_TYPE` | string | `""` | Gerrit event type (e.g. `change-merged`)¹ |
412
+ | `GERRIT_BRANCH` | string | `""` | Target branch override (Gerrit event dispatch)¹ |
413
+
414
+ | Secret | Required | Description |
415
+ | ------------------------ | -------- | ---------------------------------------------- |
416
+ | `GERRIT_SSH_PRIVKEY_G2G` | Yes | SSH private key for the Gerrit automation user |
417
+
418
+ | Output | Description |
419
+ | --------------------------- | ------------------------------------------ |
420
+ | `gerrit_change_request_url` | Gerrit change URL(s), newline-separated |
421
+ | `gerrit_change_request_num` | Gerrit change number(s), newline-separated |
422
+ | `gerrit_commit_sha` | Patch set commit SHA(s), newline-separated |
423
+
424
+ <!-- markdownlint-enable MD013 -->
425
+
426
+ ¹ Gerrit → GitHub reverse flow: when Gerrit-side automation
427
+ dispatches the caller workflow to report a merged or abandoned
428
+ change, forward these dispatch inputs and the tool closes the source
429
+ GitHub PR instead of processing pull requests.
430
+
431
+ Repository variables provide operational kill switches: set
432
+ `G2G_NO_GERRIT` to `true` to skip Gerrit interaction, or
433
+ `G2G_DISABLED` to `true` to exit immediately without processing.
434
+ Test-only settings (`CI_TESTING`, `USE_LOCAL_ACTION`) remain
435
+ composite-action-only by design.
436
+
437
+ ## Documentation
438
+
439
+ <!-- markdownlint-disable MD013 -->
440
+
441
+ | Document | Contents |
442
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
443
+ | [docs/features.md](docs/features.md) | Feature reference: PR updates, comment commands, cleanup, duplicate detection, reconciliation, normalization, configuration |
444
+ | [docs/cli.md](docs/cli.md) | CLI installation, options, environment variables, exit codes, debugging |
445
+ | [docs/COMMIT_RULES.md](docs/COMMIT_RULES.md) | Commit message validation rules and `COMMIT_RULES_JSON` format |
446
+ | [docs/development.md](docs/development.md) | Contributor guide: local setup, testing, composite action test suite |
447
+
448
+ <!-- markdownlint-enable MD013 -->
449
+
450
+ ## Security notes
451
+
452
+ - Do not hardcode secrets or keys. Provide the private key through
453
+ workflow secrets and known hosts through repository or organization
454
+ variables.
455
+ - SSH handling is non-invasive: the tool creates temporary SSH files
456
+ in the workspace without modifying user SSH configuration or keys,
457
+ and cleans them up after execution.
458
+ - SSH connections use `IdentitiesOnly=yes` to avoid unintended key
459
+ usage (e.g. signing keys requiring biometric authentication).
460
+
461
+ ## License
462
+
463
+ Apache License 2.0. See [LICENSE](LICENSE).