github2gerrit 1.0.8__tar.gz → 1.0.9__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 (128) hide show
  1. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.pre-commit-config.yaml +3 -3
  2. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/PKG-INFO +8 -3
  3. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/README.md +7 -1
  4. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/action.yaml +10 -1
  5. github2gerrit-1.0.9/docs/COMMIT_RULES.md +255 -0
  6. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/pyproject.toml +0 -2
  7. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/cli.py +21 -4
  8. github2gerrit-1.0.9/src/github2gerrit/commit_rules.py +518 -0
  9. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/config.py +44 -3
  10. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/core.py +208 -172
  11. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/duplicate_detection.py +26 -60
  12. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/error_codes.py +15 -11
  13. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/gerrit_pr_closer.py +13 -10
  14. github2gerrit-1.0.9/src/github2gerrit/gitreview.py +592 -0
  15. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/models.py +1 -0
  16. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/orchestrator/reconciliation.py +6 -12
  17. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/rich_display.py +8 -9
  18. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/rich_logging.py +2 -7
  19. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/ssh_discovery.py +12 -12
  20. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/conftest.py +4 -2
  21. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_change_id_deduplication.py +1 -0
  22. github2gerrit-1.0.9/tests/test_commit_rules.py +1029 -0
  23. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_config_and_errors.py +1 -0
  24. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_integration_fixture_repo.py +2 -0
  25. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_prepare_commits.py +1 -0
  26. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_ssh_setup.py +5 -0
  27. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_force_flag_cli.py +1 -0
  28. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_change_id_footer.py +1 -0
  29. github2gerrit-1.0.9/tests/test_gitreview.py +1011 -0
  30. github2gerrit-1.0.9/tests/test_issue_157_regressions.py +512 -0
  31. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_metadata_trailer_separation_bug.py +1 -0
  32. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_pr_commands.py +3 -0
  33. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_pr_content_filter_integration.py +1 -0
  34. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_pr_update_detection.py +2 -0
  35. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/uv.lock +24 -35
  36. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.editorconfig +0 -0
  37. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.gitignore +0 -0
  38. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.gitlint +0 -0
  39. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.markdownlint.yaml +0 -0
  40. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.readthedocs.yml +0 -0
  41. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/.yamllint +0 -0
  42. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/LICENSE +0 -0
  43. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/LICENSES/Apache-2.0.txt +0 -0
  44. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/REUSE.toml +0 -0
  45. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
  46. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
  47. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/docs/RELEASE-v0.2.0.md +0 -0
  48. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/docs/github2gerrit_token_permissions_classic.png +0 -0
  49. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/sitecustomize.py +0 -0
  50. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/__init__.py +0 -0
  51. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/commit_normalization.py +0 -0
  52. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/constants.py +0 -0
  53. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/external_api.py +0 -0
  54. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/gerrit_query.py +0 -0
  55. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/gerrit_rest.py +0 -0
  56. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/gerrit_urls.py +0 -0
  57. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/github_api.py +0 -0
  58. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/gitutils.py +0 -0
  59. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/mapping_comment.py +0 -0
  60. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/netrc.py +0 -0
  61. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/orchestrator/__init__.py +0 -0
  62. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/pr_commands.py +0 -0
  63. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/pr_content_filter.py +0 -0
  64. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/reconcile_matcher.py +0 -0
  65. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/similarity.py +0 -0
  66. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/ssh_agent_setup.py +0 -0
  67. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/ssh_common.py +0 -0
  68. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/ssh_config_parser.py +0 -0
  69. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/trailers.py +0 -0
  70. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/src/github2gerrit/utils.py +0 -0
  71. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/fixtures/__init__.py +0 -0
  72. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/fixtures/make_repo.py +0 -0
  73. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/fixtures/ssh_config_samples.py +0 -0
  74. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_action_environment_mapping.py +0 -0
  75. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_action_outputs.py +0 -0
  76. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_action_pr_number_handling.py +0 -0
  77. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_action_step_validation.py +0 -0
  78. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_automation_only.py +0 -0
  79. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_cli.py +0 -0
  80. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_cli_helpers.py +0 -0
  81. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_cli_netrc_options.py +0 -0
  82. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_cli_outputs_file.py +0 -0
  83. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_cli_url_and_dryrun.py +0 -0
  84. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_commit_normalization.py +0 -0
  85. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_composite_action_coverage.py +0 -0
  86. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_config_and_reviewers.py +0 -0
  87. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_config_helpers.py +0 -0
  88. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_close_pr_policy.py +0 -0
  89. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_gerrit_backref_comment.py +0 -0
  90. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_gerrit_push_errors.py +0 -0
  91. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_gerrit_rest_results.py +0 -0
  92. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_shallow_clone.py +0 -0
  93. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_core_ssrf_protection.py +0 -0
  94. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_duplicate_detection.py +0 -0
  95. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_email_case_normalization.py +0 -0
  96. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_error_codes.py +0 -0
  97. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_external_api_framework.py +0 -0
  98. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_change_status_checks.py +0 -0
  99. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_pr_closer.py +0 -0
  100. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_rest_client.py +0 -0
  101. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_urls.py +0 -0
  102. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gerrit_urls_more.py +0 -0
  103. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ghe_and_gitreview_args.py +0 -0
  104. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_github_api_error_handling.py +0 -0
  105. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_github_api_helpers.py +0 -0
  106. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_github_api_retry_and_helpers.py +0 -0
  107. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_gitutils_helpers.py +0 -0
  108. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_mapping_comment_additional.py +0 -0
  109. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_mapping_comment_digest_and_backref.py +0 -0
  110. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_metadata_and_reconciliation.py +0 -0
  111. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_misc_small_coverage.py +0 -0
  112. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_netrc.py +0 -0
  113. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_orphan_rest_side_effects.py +0 -0
  114. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_pr_content_filter.py +0 -0
  115. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_reconciliation_extracted_module.py +0 -0
  116. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_reconciliation_plan_and_orphans.py +0 -0
  117. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_reconciliation_scenarios.py +0 -0
  118. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_agent.py +0 -0
  119. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_agent_ownership.py +0 -0
  120. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_artifact_prevention.py +0 -0
  121. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_common.py +0 -0
  122. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_discovery.py +0 -0
  123. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_ssh_discovery_dry_run.py +0 -0
  124. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_trailers_additional.py +0 -0
  125. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_url_parser.py +0 -0
  126. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/test_utils.py +0 -0
  127. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/tests/unit/test_config_integration.py +0 -0
  128. {github2gerrit-1.0.8 → github2gerrit-1.0.9}/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: a27a2e47c7751b639d2b5badf0ef6ff11fee893f # frozen: v0.15.4
62
+ rev: b969e2851312ca2b24bbec879ba4954341d1bd12 # frozen: v0.15.5
63
63
  hooks:
64
64
  - id: ruff
65
65
  files: ^(src|scripts|tests)/.+\.py$
@@ -96,7 +96,7 @@ repos:
96
96
  - id: shellcheck
97
97
 
98
98
  - repo: https://github.com/igorshubovych/markdownlint-cli
99
- rev: 76b3d32d3f4b965e1d6425253c59407420ae2c43 # frozen: v0.47.0
99
+ rev: e72a3ca1632f0b11a07d171449fe447a7ff6795e # frozen: v0.48.0
100
100
  hooks:
101
101
  - id: markdownlint
102
102
  args: ["--fix", "--config", ".markdownlint.yaml"]
@@ -116,7 +116,7 @@ repos:
116
116
 
117
117
  # Check for misspellings in documentation files
118
118
  - repo: https://github.com/codespell-project/codespell
119
- rev: 63c8f8312b7559622c0d82815639671ae42132ac # frozen: v2.4.1
119
+ rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
120
120
  hooks:
121
121
  - id: codespell
122
122
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github2gerrit
3
- Version: 1.0.8
3
+ Version: 1.0.9
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
@@ -39,7 +39,6 @@ Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
39
39
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
40
40
  Requires-Dist: responses>=0.25.8; extra == 'dev'
41
41
  Requires-Dist: ruff>=0.6.3; extra == 'dev'
42
- Requires-Dist: types-click>=7.1.8; extra == 'dev'
43
42
  Requires-Dist: types-requests>=2.31.0; extra == 'dev'
44
43
  Requires-Dist: types-urllib3>=1.26.25.14; extra == 'dev'
45
44
  Description-Content-Type: text/markdown
@@ -1637,7 +1636,9 @@ requiring manual configuration per PR or user.
1637
1636
  - `src/github2gerrit/gitutils.py` (subprocess and git helpers)
1638
1637
  - Linting and type checking
1639
1638
  - Ruff and MyPy use settings in `pyproject.toml`.
1640
- - Run from pre‑commit hooks and CI.
1639
+ - Run from [prek](https://github.com/j178/prek) hooks and CI.
1640
+ - prek is a faster, Rust-based drop-in replacement for pre-commit
1641
+ that reads the existing `.pre-commit-config.yaml` unchanged.
1641
1642
  - Tests
1642
1643
  - Pytest with coverage targets around 80%.
1643
1644
  - Add unit and integration tests for each feature.
@@ -1647,6 +1648,10 @@ requiring manual configuration per PR or user.
1647
1648
  - Install `uv` and run:
1648
1649
  - `uv pip install --system .`
1649
1650
  - `uv run github2gerrit --help`
1651
+ - Install prek hooks:
1652
+ - `uv tool install prek && prek install -f`
1653
+ - Run all checks (including tests) manually:
1654
+ - `prek run --all-files`
1650
1655
  - Run tests:
1651
1656
  - `uv run pytest -q`
1652
1657
  - Lint and type check:
@@ -1591,7 +1591,9 @@ requiring manual configuration per PR or user.
1591
1591
  - `src/github2gerrit/gitutils.py` (subprocess and git helpers)
1592
1592
  - Linting and type checking
1593
1593
  - Ruff and MyPy use settings in `pyproject.toml`.
1594
- - Run from pre‑commit hooks and CI.
1594
+ - Run from [prek](https://github.com/j178/prek) hooks and CI.
1595
+ - prek is a faster, Rust-based drop-in replacement for pre-commit
1596
+ that reads the existing `.pre-commit-config.yaml` unchanged.
1595
1597
  - Tests
1596
1598
  - Pytest with coverage targets around 80%.
1597
1599
  - Add unit and integration tests for each feature.
@@ -1601,6 +1603,10 @@ requiring manual configuration per PR or user.
1601
1603
  - Install `uv` and run:
1602
1604
  - `uv pip install --system .`
1603
1605
  - `uv run github2gerrit --help`
1606
+ - Install prek hooks:
1607
+ - `uv tool install prek && prek install -f`
1608
+ - Run all checks (including tests) manually:
1609
+ - `prek run --all-files`
1604
1610
  - Run tests:
1605
1611
  - `uv run pytest -q`
1606
1612
  - Lint and type check:
@@ -129,6 +129,14 @@ inputs:
129
129
  (format: [{"key": "username", "value": "ISSUE-ID"}])
130
130
  required: false
131
131
  default: "[]"
132
+ COMMIT_RULES_JSON:
133
+ description: >-
134
+ JSON object defining commit message rules with per-project and
135
+ per-actor overrides. Supports arbitrary label-value pairs placed
136
+ in the commit body or trailer block.
137
+ (see: docs/COMMIT_RULES.md)
138
+ required: false
139
+ default: ""
132
140
  AUTOMATION_ONLY:
133
141
  description: "Only accept pull requests from known automation tools"
134
142
  required: false
@@ -168,7 +176,7 @@ runs:
168
176
 
169
177
  - name: "Setup uv"
170
178
  # yamllint disable-line rule:line-length
171
- uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
179
+ uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
172
180
  with:
173
181
  enable-cache: false
174
182
 
@@ -294,6 +302,7 @@ runs:
294
302
  ALLOW_DUPLICATES: ${{ inputs.ALLOW_DUPLICATES }}
295
303
  ISSUE_ID: ${{ inputs.ISSUE_ID }}
296
304
  ISSUE_ID_LOOKUP_JSON: ${{ inputs.ISSUE_ID_LOOKUP_JSON }}
305
+ COMMIT_RULES_JSON: ${{ inputs.COMMIT_RULES_JSON }}
297
306
  CI_TESTING: ${{ inputs.CI_TESTING }}
298
307
  CLOSE_MERGED_PRS: ${{ inputs.CLOSE_MERGED_PRS }}
299
308
  FORCE: ${{ inputs.FORCE }}
@@ -0,0 +1,255 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2025 The Linux Foundation -->
3
+
4
+ # Commit Rules (`COMMIT_RULES_JSON`)
5
+
6
+ The **commit rules** feature provides a flexible, JSON-driven mechanism for
7
+ injecting arbitrary lines into commit messages submitted to Gerrit. It
8
+ generalises the existing `ISSUE_ID` / `ISSUE_ID_LOOKUP_JSON` support to handle
9
+ per-project requirements such as FD.io VPP's mandatory `Type:` field.
10
+
11
+ ## Quick Start
12
+
13
+ Set the `COMMIT_RULES_JSON` GitHub Actions variable (organisation or
14
+ repository level) to a JSON object describing the rules:
15
+
16
+ ```yaml
17
+ # .github/workflows/g2g.yml
18
+ jobs:
19
+ submit:
20
+ steps:
21
+ - uses: lfreleng-actions/github2gerrit-action@main
22
+ with:
23
+ COMMIT_RULES_JSON: ${{ vars.COMMIT_RULES_JSON }}
24
+ # ... other inputs ...
25
+ ```
26
+
27
+ ## JSON Schema
28
+
29
+ The top-level object has three optional sections:
30
+
31
+ | Section | Type | Description |
32
+ |------------|-------------------------------|------------------------------------------------|
33
+ | `defaults` | `array` of rule objects | Baseline rules applied to every commit. |
34
+ | `projects` | `object` (project → rules[]) | Per-Gerrit-project overrides. |
35
+ | `actors` | `object` (actor → rules[]) | Per-GitHub-actor overrides (e.g. bots). |
36
+
37
+ ### Rule Object
38
+
39
+ Each rule object describes a single line to insert into the commit message:
40
+
41
+ | Field | Type | Required | Default | Description |
42
+ |-------------|----------|----------|----------------|----------------------------------------------------------------------------|
43
+ | `key` | `string` | Yes | — | The label name (e.g. `Type`, `Issue-ID`, `Ticket`). |
44
+ | `value` | `string` | Yes | — | The value to insert. |
45
+ | `location` | `string` | No | `"trailer"` | Where to place the line — `"trailer"` or `"body"`. |
46
+ | `separator` | `string` | No | `"blank_line"` | Separation style when `location` is `"body"` — `"blank_line"` or `"none"`. |
47
+
48
+ ### Locations
49
+
50
+ - **`trailer`** — places the line in the Git trailer block at the end of
51
+ the commit message, alongside `Change-Id`, `Signed-off-by`, etc.
52
+ - **`body`** — places the line in the commit body, before the trailer
53
+ block. Fields like VPP's `Type:` need this location because Gerrit
54
+ server-side hooks expect them in the body rather than the trailer section.
55
+
56
+ ### Separators (body location only)
57
+
58
+ - **`blank_line`** (default) — inserts a blank line before the new
59
+ content, matching the conventional VPP commit style.
60
+ - **`none`** — appends the line directly after the existing body text
61
+ without extra blank lines.
62
+
63
+ ## Resolution Precedence
64
+
65
+ The engine resolves rules in this order when building the commit message
66
+ (last writer wins for a given `key`):
67
+
68
+ 1. **`defaults`** — baseline rules for all projects and actors.
69
+ 2. **`projects[<gerrit_project>]`** — overrides defaults for the matching
70
+ Gerrit project (from `.gitreview` or `GERRIT_PROJECT`).
71
+ 3. **`actors[<github_actor>]`** — overrides everything for the matching
72
+ GitHub actor (from `GITHUB_ACTOR`).
73
+
74
+ The existing `ISSUE_ID` input always takes priority over any `Issue-ID`
75
+ rule from commit rules. Both mechanisms can coexist safely.
76
+
77
+ ## Examples
78
+
79
+ ### FD.io (VPP + CSIT on the same Gerrit server)
80
+
81
+ VPP requires a `Type:` field in the commit body; CSIT does not.
82
+ Both projects need `Issue-ID` in the trailer block.
83
+
84
+ ```json
85
+ {
86
+ "defaults": [
87
+ {
88
+ "key": "Issue-ID",
89
+ "value": "CIMAN-33",
90
+ "location": "trailer"
91
+ }
92
+ ],
93
+ "projects": {
94
+ "vpp": [
95
+ {
96
+ "key": "Type",
97
+ "value": "ci",
98
+ "location": "body",
99
+ "separator": "blank_line"
100
+ },
101
+ {
102
+ "key": "Issue-ID",
103
+ "value": "CIMAN-33",
104
+ "location": "trailer"
105
+ }
106
+ ],
107
+ "hicn": [
108
+ {
109
+ "key": "Type",
110
+ "value": "ci",
111
+ "location": "body",
112
+ "separator": "blank_line"
113
+ }
114
+ ]
115
+ },
116
+ "actors": {
117
+ "dependabot[bot]": [
118
+ {
119
+ "key": "Type",
120
+ "value": "ci",
121
+ "location": "body"
122
+ },
123
+ {
124
+ "key": "Issue-ID",
125
+ "value": "CIMAN-33",
126
+ "location": "trailer"
127
+ }
128
+ ],
129
+ "renovate[bot]": [
130
+ {
131
+ "key": "Issue-ID",
132
+ "value": "CIMAN-44",
133
+ "location": "trailer"
134
+ }
135
+ ]
136
+ }
137
+ }
138
+ ```
139
+
140
+ **Result for VPP + dependabot:**
141
+
142
+ ```text
143
+ gha: update actions/checkout from v3 to v4
144
+
145
+ Type: ci
146
+
147
+ Issue-ID: CIMAN-33
148
+ Change-Id: I1234567890abcdef...
149
+ Signed-off-by: dependabot[bot] <support@github.com>
150
+ ```
151
+
152
+ **Result for CSIT + human user:**
153
+
154
+ ```text
155
+ fix: correct test assertion
156
+
157
+ Issue-ID: CIMAN-33
158
+ Change-Id: I1234567890abcdef...
159
+ Signed-off-by: Jane Doe <jane@example.com>
160
+ ```
161
+
162
+ ### ONAP (Issue-ID only)
163
+
164
+ ONAP projects only need `Issue-ID` in the trailer:
165
+
166
+ ```json
167
+ {
168
+ "actors": {
169
+ "dependabot[bot]": [
170
+ {
171
+ "key": "Issue-ID",
172
+ "value": "CIMAN-33"
173
+ }
174
+ ]
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Extra body fields
180
+
181
+ Some projects need more than one body field:
182
+
183
+ ```json
184
+ {
185
+ "projects": {
186
+ "vpp": [
187
+ {
188
+ "key": "Type",
189
+ "value": "ci",
190
+ "location": "body",
191
+ "separator": "blank_line"
192
+ },
193
+ {
194
+ "key": "Ticket",
195
+ "value": "VPP-2088",
196
+ "location": "body",
197
+ "separator": "none"
198
+ }
199
+ ]
200
+ }
201
+ }
202
+ ```
203
+
204
+ **Result:**
205
+
206
+ ```text
207
+ gha: update dependency versions
208
+
209
+ Type: ci
210
+ Ticket: VPP-2088
211
+
212
+ Change-Id: I1234567890abcdef...
213
+ Signed-off-by: bot <bot@example.com>
214
+ ```
215
+
216
+ ## CLI Usage
217
+
218
+ You can also pass the commit rules JSON via the command line:
219
+
220
+ ```bash
221
+ github2gerrit --commit-rules '{"defaults": [...]}' \
222
+ https://github.com/org/repo/pull/123
223
+ ```
224
+
225
+ Or via the environment variable:
226
+
227
+ ```bash
228
+ export COMMIT_RULES_JSON='{"defaults": [...]}'
229
+ github2gerrit https://github.com/org/repo/pull/123
230
+ ```
231
+
232
+ ## Interaction with ISSUE_ID / ISSUE_ID_LOOKUP_JSON
233
+
234
+ The existing `ISSUE_ID` and `ISSUE_ID_LOOKUP_JSON` inputs continue to
235
+ work unchanged. When both mechanisms specify an `Issue-ID`:
236
+
237
+ 1. An explicit `ISSUE_ID` input (or a value resolved from
238
+ `ISSUE_ID_LOOKUP_JSON`) **always wins**.
239
+ 2. If `ISSUE_ID` is empty, the engine applies the `Issue-ID` rule from
240
+ `COMMIT_RULES_JSON` instead.
241
+
242
+ This means you can safely enable `COMMIT_RULES_JSON` for an organisation
243
+ without breaking workflows that already set `ISSUE_ID` directly.
244
+
245
+ ## Validation and Error Handling
246
+
247
+ - Invalid JSON produces a warning but does **not** fail the workflow
248
+ (matching the existing `ISSUE_ID_LOOKUP_JSON` convention).
249
+ - Individual rule entries with missing or invalid `key`/`value` fields
250
+ produce a warning and the engine skips them; valid entries in the same
251
+ document still apply.
252
+ - Unknown `location` values default to `"trailer"` with a warning.
253
+ - Unknown `separator` values default to `"blank_line"` with a warning.
254
+ - Duplicate lines are automatically detected and skipped (both in body
255
+ and trailer locations).
@@ -102,7 +102,6 @@ dev = [
102
102
  # Type checking helpers
103
103
  "pytest-mock>=3.15.1",
104
104
  "types-requests>=2.31.0",
105
- "types-click>=7.1.8",
106
105
  "types-urllib3>=1.26.25.14",
107
106
  ]
108
107
 
@@ -230,7 +229,6 @@ directory = "coverage_html_report"
230
229
  minversion = "8.0"
231
230
  addopts = "-ra -q --cov=github2gerrit --cov-report=term-missing --cov-report=html"
232
231
  testpaths = ["tests"]
233
- asyncio_default_fixture_loop_scope = "function"
234
232
  markers = [
235
233
  "integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
236
234
  ]
@@ -22,6 +22,7 @@ from typing import cast
22
22
  from urllib.parse import urlparse
23
23
 
24
24
  import click
25
+ import click.core
25
26
  import typer
26
27
 
27
28
  from . import models
@@ -674,6 +675,16 @@ def main(
674
675
  "for automatic lookup."
675
676
  ),
676
677
  ),
678
+ commit_rules_json: str = typer.Option(
679
+ "",
680
+ "--commit-rules",
681
+ envvar="COMMIT_RULES_JSON",
682
+ help=(
683
+ "JSON object defining commit message rules with per-project "
684
+ "and per-actor overrides. Supports arbitrary key-value lines "
685
+ "placed in the commit body or trailer block."
686
+ ),
687
+ ),
677
688
  log_reconcile_json: bool = typer.Option(
678
689
  True,
679
690
  "--log-reconcile-json/--no-log-reconcile-json",
@@ -845,8 +856,8 @@ def main(
845
856
  param_name: str, env_var: str, current: bool
846
857
  ) -> bool:
847
858
  """Return *current* if the CLI flag was explicit, else parse env."""
848
- source = ctx.get_parameter_source(param_name)
849
- if source == click.core.ParameterSource.COMMANDLINE:
859
+ source = ctx.get_parameter_source(param_name) # pyright: ignore[reportAttributeAccessIssue]
860
+ if source == click.core.ParameterSource.COMMANDLINE: # pyright: ignore[reportAttributeAccessIssue]
850
861
  return current
851
862
  env_val = os.getenv(env_var)
852
863
  if env_val is not None:
@@ -995,6 +1006,9 @@ def main(
995
1006
 
996
1007
  if resolved_issue_id:
997
1008
  os.environ["ISSUE_ID"] = resolved_issue_id
1009
+ # Always set COMMIT_RULES_JSON so passing an empty string via CLI
1010
+ # clears any pre-existing env var rather than leaving stale rules.
1011
+ os.environ["COMMIT_RULES_JSON"] = commit_rules_json or ""
998
1012
  os.environ["ALLOW_DUPLICATES"] = "true" if allow_duplicates else "false"
999
1013
  os.environ["CI_TESTING"] = "true" if ci_testing else "false"
1000
1014
  os.environ["CLOSE_MERGED_PRS"] = "true" if close_merged_prs else "false"
@@ -1173,6 +1187,7 @@ def _build_inputs_from_env() -> Inputs:
1173
1187
  gerrit_project=env_str("GERRIT_PROJECT"),
1174
1188
  issue_id=env_str("ISSUE_ID", ""),
1175
1189
  issue_id_lookup_json=env_str("ISSUE_ID_LOOKUP_JSON", ""),
1190
+ commit_rules_json=env_str("COMMIT_RULES_JSON", ""),
1176
1191
  allow_duplicates=env_bool("ALLOW_DUPLICATES", True),
1177
1192
  ci_testing=env_bool("CI_TESTING", False),
1178
1193
  duplicates_filter=env_str("DUPLICATE_TYPES", "open"),
@@ -1620,8 +1635,9 @@ def _process_single(
1620
1635
  progress_tracker=progress_tracker,
1621
1636
  )
1622
1637
  safe_console_print(
1623
- " To create a new change, trigger the 'opened' "
1624
- "workflow action.",
1638
+ " To create a new change, set CREATE_MISSING=true "
1639
+ "or add a '@github2gerrit create missing change' "
1640
+ "comment on the PR.",
1625
1641
  style="yellow",
1626
1642
  progress_tracker=progress_tracker,
1627
1643
  )
@@ -1800,6 +1816,7 @@ def _load_effective_inputs() -> Inputs:
1800
1816
  gerrit_project=data.gerrit_project,
1801
1817
  issue_id=data.issue_id,
1802
1818
  issue_id_lookup_json=data.issue_id_lookup_json,
1819
+ commit_rules_json=data.commit_rules_json,
1803
1820
  allow_duplicates=data.allow_duplicates,
1804
1821
  ci_testing=data.ci_testing,
1805
1822
  duplicates_filter=data.duplicates_filter,