github2gerrit 1.0.7__tar.gz → 1.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.pre-commit-config.yaml +2 -2
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/PKG-INFO +112 -1
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/README.md +111 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/action.yaml +6 -1
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/cli.py +80 -50
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/core.py +160 -17
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/models.py +3 -0
- github2gerrit-1.0.8/src/github2gerrit/pr_commands.py +355 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/rich_display.py +4 -4
- github2gerrit-1.0.8/tests/test_pr_commands.py +956 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/uv.lock +19 -19
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.editorconfig +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.gitignore +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.gitlint +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.markdownlint.yaml +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.readthedocs.yml +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/.yamllint +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/LICENSE +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/LICENSES/Apache-2.0.txt +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/REUSE.toml +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/docs/RELEASE-v0.2.0.md +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/docs/github2gerrit_token_permissions_classic.png +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/pyproject.toml +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/sitecustomize.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/__init__.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/commit_normalization.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/config.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/constants.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/duplicate_detection.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/error_codes.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/external_api.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/gerrit_pr_closer.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/gerrit_query.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/gerrit_rest.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/gerrit_urls.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/github_api.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/gitutils.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/mapping_comment.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/netrc.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/orchestrator/__init__.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/pr_content_filter.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/reconcile_matcher.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/rich_logging.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/similarity.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/ssh_agent_setup.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/ssh_common.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/ssh_config_parser.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/ssh_discovery.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/trailers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/src/github2gerrit/utils.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/conftest.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/fixtures/__init__.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/fixtures/make_repo.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/fixtures/ssh_config_samples.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_action_environment_mapping.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_action_outputs.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_action_pr_number_handling.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_action_step_validation.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_automation_only.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_change_id_deduplication.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_cli.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_cli_helpers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_cli_netrc_options.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_cli_outputs_file.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_cli_url_and_dryrun.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_commit_normalization.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_composite_action_coverage.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_config_and_reviewers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_config_helpers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_close_pr_policy.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_config_and_errors.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_gerrit_backref_comment.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_gerrit_push_errors.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_gerrit_rest_results.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_integration_fixture_repo.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_prepare_commits.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_shallow_clone.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_ssh_setup.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_core_ssrf_protection.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_duplicate_detection.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_email_case_normalization.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_error_codes.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_external_api_framework.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_force_flag_cli.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_change_id_footer.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_change_status_checks.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_pr_closer.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_rest_client.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_urls.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gerrit_urls_more.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ghe_and_gitreview_args.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_github_api_error_handling.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_github_api_helpers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_github_api_retry_and_helpers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_gitutils_helpers.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_mapping_comment_additional.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_mapping_comment_digest_and_backref.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_metadata_and_reconciliation.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_metadata_trailer_separation_bug.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_misc_small_coverage.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_netrc.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_orphan_rest_side_effects.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_pr_content_filter.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_pr_content_filter_integration.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_pr_update_detection.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_reconciliation_extracted_module.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_reconciliation_plan_and_orphans.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_reconciliation_scenarios.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_agent.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_agent_ownership.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_artifact_prevention.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_common.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_discovery.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_ssh_discovery_dry_run.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_trailers_additional.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_url_parser.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/test_utils.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/unit/test_config_integration.py +0 -0
- {github2gerrit-1.0.7 → github2gerrit-1.0.8}/tests/unit/test_ssh_config_parser.py +0 -0
|
@@ -59,7 +59,7 @@ repos:
|
|
|
59
59
|
types: [yaml]
|
|
60
60
|
|
|
61
61
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
62
|
-
rev:
|
|
62
|
+
rev: a27a2e47c7751b639d2b5badf0ef6ff11fee893f # frozen: v0.15.4
|
|
63
63
|
hooks:
|
|
64
64
|
- id: ruff
|
|
65
65
|
files: ^(src|scripts|tests)/.+\.py$
|
|
@@ -121,7 +121,7 @@ repos:
|
|
|
121
121
|
- id: codespell
|
|
122
122
|
|
|
123
123
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
124
|
-
rev:
|
|
124
|
+
rev: 8db279a37c552206d2df62269ff6f9d31125815a # frozen: 0.37.0
|
|
125
125
|
hooks:
|
|
126
126
|
- id: check-github-actions
|
|
127
127
|
- id: check-github-workflows
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github2gerrit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
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
|
|
@@ -147,6 +147,117 @@ If UPDATE fails to find existing change:
|
|
|
147
147
|
To create a new change, trigger the 'opened' workflow action.
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## PR Comment Commands
|
|
151
|
+
|
|
152
|
+
GitHub2Gerrit supports an extensible set of directives issued through
|
|
153
|
+
pull request comments. Add a comment containing `@github2gerrit`
|
|
154
|
+
followed by a command phrase and the tool will act on it during
|
|
155
|
+
the next workflow run.
|
|
156
|
+
|
|
157
|
+
### Command Format
|
|
158
|
+
|
|
159
|
+
<!-- markdownlint-disable MD013 -->
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
@github2gerrit <command>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
<!-- markdownlint-enable MD013 -->
|
|
166
|
+
|
|
167
|
+
- Commands are **case-insensitive** — `@github2gerrit Create Missing Change`
|
|
168
|
+
works the same as `@github2gerrit create missing change`.
|
|
169
|
+
- Only the **latest** occurrence of each command takes effect when the same
|
|
170
|
+
command appears in more than one comment.
|
|
171
|
+
- The tool logs unrecognised directives at debug level and ignores them.
|
|
172
|
+
|
|
173
|
+
### Available Commands
|
|
174
|
+
|
|
175
|
+
<!-- markdownlint-disable MD013 MD060 -->
|
|
176
|
+
|
|
177
|
+
| Command | Aliases | Description |
|
|
178
|
+
| --- | --- | --- |
|
|
179
|
+
| `create missing change` | `create-missing`, `create missing` | Create a Gerrit change when an UPDATE operation cannot find an existing one |
|
|
180
|
+
|
|
181
|
+
<!-- markdownlint-enable MD013 MD060 -->
|
|
182
|
+
|
|
183
|
+
### Create Missing Change
|
|
184
|
+
|
|
185
|
+
When a PR `synchronize` event fires, GitHub2Gerrit treats it as an
|
|
186
|
+
**UPDATE** operation and expects a Gerrit change to exist. If the
|
|
187
|
+
original `opened` event failed (for example due to a bug or transient
|
|
188
|
+
error), no Gerrit change exists and every following update fails with:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
❌ UPDATE FAILED: Cannot update non-existent Gerrit change
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The **create missing change** command resolves this without manual
|
|
195
|
+
intervention in Gerrit. Two mechanisms trigger it:
|
|
196
|
+
|
|
197
|
+
#### 1. PR Comment Directive
|
|
198
|
+
|
|
199
|
+
Add a comment on the stuck pull request:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
@github2gerrit create missing change
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Then re-trigger the workflow (push a trivial change or re-run the
|
|
206
|
+
workflow manually). GitHub2Gerrit detects the directive, switches
|
|
207
|
+
from UPDATE to CREATE mode, and pushes a new Gerrit change.
|
|
208
|
+
|
|
209
|
+
#### 2. CLI Flag
|
|
210
|
+
|
|
211
|
+
Outside GitHub Actions you can pass the flag directly:
|
|
212
|
+
|
|
213
|
+
```shell
|
|
214
|
+
github2gerrit \
|
|
215
|
+
--create-missing \
|
|
216
|
+
https://github.com/MyOrg/my-repo/pull/42
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Or set the environment variable:
|
|
220
|
+
|
|
221
|
+
```shell
|
|
222
|
+
export CREATE_MISSING=true
|
|
223
|
+
github2gerrit https://github.com/MyOrg/my-repo/pull/42
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### What Happens During Fallback
|
|
227
|
+
|
|
228
|
+
1. The tool attempts the normal UPDATE flow and finds no existing
|
|
229
|
+
Gerrit change.
|
|
230
|
+
2. It checks for `--create-missing` **or** scans PR comments for the
|
|
231
|
+
`@github2gerrit create missing change` directive.
|
|
232
|
+
3. If authorised, the operation mode switches from UPDATE to CREATE.
|
|
233
|
+
4. The tool posts a notice on the PR:
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
🔄 GitHub2Gerrit: No existing Gerrit change found for this PR.
|
|
237
|
+
Creating a new Gerrit change (fallback from UPDATE operation).
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
5. The pipeline continues as a normal CREATE — preparing commits,
|
|
241
|
+
pushing to Gerrit, posting the change URL back on the PR.
|
|
242
|
+
|
|
243
|
+
#### GitHub Actions Workflow Example
|
|
244
|
+
|
|
245
|
+
<!-- markdownlint-disable MD013 -->
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
- name: Submit PR to Gerrit
|
|
249
|
+
uses: lfreleng-actions/github2gerrit-action@main
|
|
250
|
+
with:
|
|
251
|
+
GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
|
|
252
|
+
CREATE_MISSING: "true" # always allow fallback
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
<!-- markdownlint-enable MD013 -->
|
|
256
|
+
|
|
257
|
+
> **Tip:** Setting `CREATE_MISSING` to `true` in your workflow means
|
|
258
|
+
> stuck PRs self-heal on the next `synchronize` event without requiring
|
|
259
|
+
> a comment directive.
|
|
260
|
+
|
|
150
261
|
## Close Merged PRs Feature
|
|
151
262
|
|
|
152
263
|
GitHub2Gerrit now includes **automatic PR closure** when Gerrit merges changes
|
|
@@ -101,6 +101,117 @@ If UPDATE fails to find existing change:
|
|
|
101
101
|
To create a new change, trigger the 'opened' workflow action.
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## PR Comment Commands
|
|
105
|
+
|
|
106
|
+
GitHub2Gerrit supports an extensible set of directives issued through
|
|
107
|
+
pull request comments. Add a comment containing `@github2gerrit`
|
|
108
|
+
followed by a command phrase and the tool will act on it during
|
|
109
|
+
the next workflow run.
|
|
110
|
+
|
|
111
|
+
### Command Format
|
|
112
|
+
|
|
113
|
+
<!-- markdownlint-disable MD013 -->
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
@github2gerrit <command>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
<!-- markdownlint-enable MD013 -->
|
|
120
|
+
|
|
121
|
+
- Commands are **case-insensitive** — `@github2gerrit Create Missing Change`
|
|
122
|
+
works the same as `@github2gerrit create missing change`.
|
|
123
|
+
- Only the **latest** occurrence of each command takes effect when the same
|
|
124
|
+
command appears in more than one comment.
|
|
125
|
+
- The tool logs unrecognised directives at debug level and ignores them.
|
|
126
|
+
|
|
127
|
+
### Available Commands
|
|
128
|
+
|
|
129
|
+
<!-- markdownlint-disable MD013 MD060 -->
|
|
130
|
+
|
|
131
|
+
| Command | Aliases | Description |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| `create missing change` | `create-missing`, `create missing` | Create a Gerrit change when an UPDATE operation cannot find an existing one |
|
|
134
|
+
|
|
135
|
+
<!-- markdownlint-enable MD013 MD060 -->
|
|
136
|
+
|
|
137
|
+
### Create Missing Change
|
|
138
|
+
|
|
139
|
+
When a PR `synchronize` event fires, GitHub2Gerrit treats it as an
|
|
140
|
+
**UPDATE** operation and expects a Gerrit change to exist. If the
|
|
141
|
+
original `opened` event failed (for example due to a bug or transient
|
|
142
|
+
error), no Gerrit change exists and every following update fails with:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
❌ UPDATE FAILED: Cannot update non-existent Gerrit change
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The **create missing change** command resolves this without manual
|
|
149
|
+
intervention in Gerrit. Two mechanisms trigger it:
|
|
150
|
+
|
|
151
|
+
#### 1. PR Comment Directive
|
|
152
|
+
|
|
153
|
+
Add a comment on the stuck pull request:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
@github2gerrit create missing change
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Then re-trigger the workflow (push a trivial change or re-run the
|
|
160
|
+
workflow manually). GitHub2Gerrit detects the directive, switches
|
|
161
|
+
from UPDATE to CREATE mode, and pushes a new Gerrit change.
|
|
162
|
+
|
|
163
|
+
#### 2. CLI Flag
|
|
164
|
+
|
|
165
|
+
Outside GitHub Actions you can pass the flag directly:
|
|
166
|
+
|
|
167
|
+
```shell
|
|
168
|
+
github2gerrit \
|
|
169
|
+
--create-missing \
|
|
170
|
+
https://github.com/MyOrg/my-repo/pull/42
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or set the environment variable:
|
|
174
|
+
|
|
175
|
+
```shell
|
|
176
|
+
export CREATE_MISSING=true
|
|
177
|
+
github2gerrit https://github.com/MyOrg/my-repo/pull/42
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### What Happens During Fallback
|
|
181
|
+
|
|
182
|
+
1. The tool attempts the normal UPDATE flow and finds no existing
|
|
183
|
+
Gerrit change.
|
|
184
|
+
2. It checks for `--create-missing` **or** scans PR comments for the
|
|
185
|
+
`@github2gerrit create missing change` directive.
|
|
186
|
+
3. If authorised, the operation mode switches from UPDATE to CREATE.
|
|
187
|
+
4. The tool posts a notice on the PR:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
🔄 GitHub2Gerrit: No existing Gerrit change found for this PR.
|
|
191
|
+
Creating a new Gerrit change (fallback from UPDATE operation).
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
5. The pipeline continues as a normal CREATE — preparing commits,
|
|
195
|
+
pushing to Gerrit, posting the change URL back on the PR.
|
|
196
|
+
|
|
197
|
+
#### GitHub Actions Workflow Example
|
|
198
|
+
|
|
199
|
+
<!-- markdownlint-disable MD013 -->
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
- name: Submit PR to Gerrit
|
|
203
|
+
uses: lfreleng-actions/github2gerrit-action@main
|
|
204
|
+
with:
|
|
205
|
+
GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
|
|
206
|
+
CREATE_MISSING: "true" # always allow fallback
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
<!-- markdownlint-enable MD013 -->
|
|
210
|
+
|
|
211
|
+
> **Tip:** Setting `CREATE_MISSING` to `true` in your workflow means
|
|
212
|
+
> stuck PRs self-heal on the next `synchronize` event without requiring
|
|
213
|
+
> a comment directive.
|
|
214
|
+
|
|
104
215
|
## Close Merged PRs Feature
|
|
105
216
|
|
|
106
217
|
GitHub2Gerrit now includes **automatic PR closure** when Gerrit merges changes
|
|
@@ -141,6 +141,10 @@ inputs:
|
|
|
141
141
|
description: "Abandon Gerrit changes when their GitHub PRs are closed"
|
|
142
142
|
required: false
|
|
143
143
|
default: "true"
|
|
144
|
+
CREATE_MISSING:
|
|
145
|
+
description: "Create a Gerrit change when an UPDATE operation cannot find an existing one"
|
|
146
|
+
required: false
|
|
147
|
+
default: "false"
|
|
144
148
|
|
|
145
149
|
outputs:
|
|
146
150
|
gerrit_change_request_url:
|
|
@@ -164,7 +168,7 @@ runs:
|
|
|
164
168
|
|
|
165
169
|
- name: "Setup uv"
|
|
166
170
|
# yamllint disable-line rule:line-length
|
|
167
|
-
uses: astral-sh/setup-uv@
|
|
171
|
+
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
|
168
172
|
with:
|
|
169
173
|
enable-cache: false
|
|
170
174
|
|
|
@@ -300,6 +304,7 @@ runs:
|
|
|
300
304
|
AUTOMATION_ONLY: ${{ inputs.AUTOMATION_ONLY }}
|
|
301
305
|
CLEANUP_ABANDONED: ${{ inputs.CLEANUP_ABANDONED }}
|
|
302
306
|
CLEANUP_GERRIT: ${{ inputs.CLEANUP_GERRIT }}
|
|
307
|
+
CREATE_MISSING: ${{ inputs.CREATE_MISSING }}
|
|
303
308
|
|
|
304
309
|
# Optional Gerrit overrides (when .gitreview is missing)
|
|
305
310
|
GERRIT_SERVER: ${{ inputs.GERRIT_SERVER }}
|
|
@@ -557,7 +557,7 @@ def main(
|
|
|
557
557
|
),
|
|
558
558
|
allow_duplicates: bool = typer.Option(
|
|
559
559
|
True,
|
|
560
|
-
"--allow-duplicates",
|
|
560
|
+
"--allow-duplicates/--no-allow-duplicates",
|
|
561
561
|
envvar="ALLOW_DUPLICATES",
|
|
562
562
|
help="Allow submitting duplicate changes without error.",
|
|
563
563
|
),
|
|
@@ -582,7 +582,7 @@ def main(
|
|
|
582
582
|
),
|
|
583
583
|
close_merged_prs: bool = typer.Option(
|
|
584
584
|
True,
|
|
585
|
-
"--close-merged-prs",
|
|
585
|
+
"--close-merged-prs/--no-close-merged-prs",
|
|
586
586
|
envvar="CLOSE_MERGED_PRS",
|
|
587
587
|
help="Close GitHub PRs when corresponding Gerrit changes are merged.",
|
|
588
588
|
),
|
|
@@ -700,7 +700,7 @@ def main(
|
|
|
700
700
|
),
|
|
701
701
|
preserve_github_prs: bool = typer.Option(
|
|
702
702
|
True,
|
|
703
|
-
"--preserve-github-prs",
|
|
703
|
+
"--preserve-github-prs/--no-preserve-github-prs",
|
|
704
704
|
envvar="PRESERVE_GITHUB_PRS",
|
|
705
705
|
help="Do not close GitHub PRs after pushing to Gerrit.",
|
|
706
706
|
),
|
|
@@ -776,6 +776,16 @@ def main(
|
|
|
776
776
|
"--version",
|
|
777
777
|
help="Show version and exit.",
|
|
778
778
|
),
|
|
779
|
+
create_missing: bool = typer.Option(
|
|
780
|
+
False,
|
|
781
|
+
"--create-missing/--no-create-missing",
|
|
782
|
+
envvar="CREATE_MISSING",
|
|
783
|
+
help=(
|
|
784
|
+
"Create a Gerrit change when an UPDATE operation cannot find "
|
|
785
|
+
"an existing one. Also triggered by '@github2gerrit create "
|
|
786
|
+
"missing change' PR comment."
|
|
787
|
+
),
|
|
788
|
+
),
|
|
779
789
|
automation_only: bool = typer.Option(
|
|
780
790
|
True,
|
|
781
791
|
"--automation-only/--no-automation-only",
|
|
@@ -824,44 +834,59 @@ def main(
|
|
|
824
834
|
typer.echo("Version information not available")
|
|
825
835
|
sys.exit(int(ExitCode.SUCCESS))
|
|
826
836
|
|
|
827
|
-
# Override boolean parameters with properly parsed environment variables
|
|
828
|
-
# This ensures that string "false" from GitHub Actions is handled
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
837
|
+
# Override boolean parameters with properly parsed environment variables.
|
|
838
|
+
# This ensures that string "false" from GitHub Actions is handled
|
|
839
|
+
# correctly (Typer/Click treats any non-empty string as truthy).
|
|
840
|
+
#
|
|
841
|
+
# We only apply the env-var override when the parameter was NOT
|
|
842
|
+
# explicitly provided on the command line, so that CLI flags always
|
|
843
|
+
# take precedence over environment variables.
|
|
844
|
+
def _env_bool_override(
|
|
845
|
+
param_name: str, env_var: str, current: bool
|
|
846
|
+
) -> bool:
|
|
847
|
+
"""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:
|
|
850
|
+
return current
|
|
851
|
+
env_val = os.getenv(env_var)
|
|
852
|
+
if env_val is not None:
|
|
853
|
+
return parse_bool_env(env_val)
|
|
854
|
+
return current
|
|
855
|
+
|
|
856
|
+
submit_single_commits = _env_bool_override(
|
|
857
|
+
"submit_single_commits", "SUBMIT_SINGLE_COMMITS", submit_single_commits
|
|
858
|
+
)
|
|
859
|
+
use_pr_as_commit = _env_bool_override(
|
|
860
|
+
"use_pr_as_commit", "USE_PR_AS_COMMIT", use_pr_as_commit
|
|
861
|
+
)
|
|
862
|
+
preserve_github_prs = _env_bool_override(
|
|
863
|
+
"preserve_github_prs", "PRESERVE_GITHUB_PRS", preserve_github_prs
|
|
864
|
+
)
|
|
865
|
+
dry_run = _env_bool_override("dry_run", "DRY_RUN", dry_run)
|
|
866
|
+
allow_duplicates = _env_bool_override(
|
|
867
|
+
"allow_duplicates", "ALLOW_DUPLICATES", allow_duplicates
|
|
868
|
+
)
|
|
869
|
+
ci_testing = _env_bool_override("ci_testing", "CI_TESTING", ci_testing)
|
|
870
|
+
similarity_files = _env_bool_override(
|
|
871
|
+
"similarity_files", "SIMILARITY_FILES", similarity_files
|
|
872
|
+
)
|
|
873
|
+
allow_orphan_changes = _env_bool_override(
|
|
874
|
+
"allow_orphan_changes", "ALLOW_ORPHAN_CHANGES", allow_orphan_changes
|
|
875
|
+
)
|
|
876
|
+
persist_single_mapping_comment = _env_bool_override(
|
|
877
|
+
"persist_single_mapping_comment",
|
|
878
|
+
"PERSIST_SINGLE_MAPPING_COMMENT",
|
|
879
|
+
persist_single_mapping_comment,
|
|
880
|
+
)
|
|
881
|
+
log_reconcile_json = _env_bool_override(
|
|
882
|
+
"log_reconcile_json", "LOG_RECONCILE_JSON", log_reconcile_json
|
|
883
|
+
)
|
|
884
|
+
create_missing = _env_bool_override(
|
|
885
|
+
"create_missing", "CREATE_MISSING", create_missing
|
|
886
|
+
)
|
|
887
|
+
automation_only = _env_bool_override(
|
|
888
|
+
"automation_only", "AUTOMATION_ONLY", automation_only
|
|
889
|
+
)
|
|
865
890
|
|
|
866
891
|
# Store netrc options in environment for use by processing functions
|
|
867
892
|
os.environ["G2G_NO_NETRC"] = "true" if no_netrc else "false"
|
|
@@ -1002,6 +1027,7 @@ def main(
|
|
|
1002
1027
|
"true" if persist_single_mapping_comment else "false"
|
|
1003
1028
|
)
|
|
1004
1029
|
os.environ["LOG_RECONCILE_JSON"] = "true" if log_reconcile_json else "false"
|
|
1030
|
+
os.environ["CREATE_MISSING"] = "true" if create_missing else "false"
|
|
1005
1031
|
os.environ["AUTOMATION_ONLY"] = "true" if automation_only else "false"
|
|
1006
1032
|
# URL mode handling
|
|
1007
1033
|
if target_url:
|
|
@@ -1161,6 +1187,7 @@ def _build_inputs_from_env() -> Inputs:
|
|
|
1161
1187
|
"PERSIST_SINGLE_MAPPING_COMMENT", True
|
|
1162
1188
|
),
|
|
1163
1189
|
log_reconcile_json=env_bool("LOG_RECONCILE_JSON", True),
|
|
1190
|
+
create_missing=env_bool("CREATE_MISSING", False),
|
|
1164
1191
|
)
|
|
1165
1192
|
|
|
1166
1193
|
|
|
@@ -1441,7 +1468,7 @@ def _process_bulk(data: Inputs, gh: GitHubContext) -> bool:
|
|
|
1441
1468
|
if show_progress and RICH_AVAILABLE:
|
|
1442
1469
|
summary = progress_tracker.get_summary()
|
|
1443
1470
|
safe_console_print(
|
|
1444
|
-
f"
|
|
1471
|
+
f"⏳ Total time: {summary.get('elapsed_time', 'unknown')}"
|
|
1445
1472
|
)
|
|
1446
1473
|
safe_console_print(f"📊 PRs processed: {processed_count}")
|
|
1447
1474
|
safe_console_print(f"✅ Succeeded: {succeeded_count}")
|
|
@@ -1486,7 +1513,7 @@ def _process_single(
|
|
|
1486
1513
|
|
|
1487
1514
|
try:
|
|
1488
1515
|
if progress_tracker:
|
|
1489
|
-
progress_tracker.update_operation("
|
|
1516
|
+
progress_tracker.update_operation("📂 Preparing local checkout")
|
|
1490
1517
|
log.debug(
|
|
1491
1518
|
"Preparing workspace checkout in temporary directory: %s",
|
|
1492
1519
|
workspace,
|
|
@@ -1518,7 +1545,9 @@ def _process_single(
|
|
|
1518
1545
|
)
|
|
1519
1546
|
|
|
1520
1547
|
if progress_tracker:
|
|
1521
|
-
progress_tracker.update_operation(
|
|
1548
|
+
progress_tracker.update_operation(
|
|
1549
|
+
"🔍 Extracting commit information"
|
|
1550
|
+
)
|
|
1522
1551
|
|
|
1523
1552
|
log.debug("Extracting commit information from PR")
|
|
1524
1553
|
log.debug("PR commits range: base_sha..head_sha (not available)")
|
|
@@ -1781,6 +1810,7 @@ def _load_effective_inputs() -> Inputs:
|
|
|
1781
1810
|
allow_orphan_changes=data.allow_orphan_changes,
|
|
1782
1811
|
persist_single_mapping_comment=data.persist_single_mapping_comment,
|
|
1783
1812
|
log_reconcile_json=data.log_reconcile_json,
|
|
1813
|
+
create_missing=data.create_missing,
|
|
1784
1814
|
)
|
|
1785
1815
|
log.debug("Derived reviewers: %s", data.reviewers_email)
|
|
1786
1816
|
except Exception as exc:
|
|
@@ -1859,7 +1889,7 @@ def _process_close_gerrit_change(
|
|
|
1859
1889
|
pr_url = extract_pr_url_from_gerrit_change(gerrit_change_url)
|
|
1860
1890
|
if not pr_url:
|
|
1861
1891
|
no_action_msg = (
|
|
1862
|
-
"
|
|
1892
|
+
"✅ No action required: Gerrit change did NOT originate in GitHub"
|
|
1863
1893
|
)
|
|
1864
1894
|
log.debug(no_action_msg)
|
|
1865
1895
|
safe_console_print(no_action_msg)
|
|
@@ -2572,7 +2602,7 @@ def _process() -> None:
|
|
|
2572
2602
|
style="green" if pipeline_success else "red",
|
|
2573
2603
|
)
|
|
2574
2604
|
safe_console_print(
|
|
2575
|
-
f"
|
|
2605
|
+
f"⏳ Total time: {summary.get('elapsed_time', 'unknown')}"
|
|
2576
2606
|
)
|
|
2577
2607
|
if summary.get("prs_processed", 0) > 0:
|
|
2578
2608
|
safe_console_print(f"📊 PRs processed: {summary['prs_processed']}")
|
|
@@ -2804,13 +2834,13 @@ def _get_ssh_agent_status() -> str:
|
|
|
2804
2834
|
elif has_private_key:
|
|
2805
2835
|
# SSH key explicitly provided - don't use agent
|
|
2806
2836
|
if agent_running:
|
|
2807
|
-
return "
|
|
2837
|
+
return "✅ Available, Unused"
|
|
2808
2838
|
else:
|
|
2809
2839
|
return "❎ Unavailable, Unused"
|
|
2810
2840
|
elif use_ssh_agent and agent_running:
|
|
2811
2841
|
return "✅ Available, Used"
|
|
2812
2842
|
elif agent_running:
|
|
2813
|
-
return "
|
|
2843
|
+
return "✅ Available, Unused"
|
|
2814
2844
|
else:
|
|
2815
2845
|
return "❎ Unavailable, Unused"
|
|
2816
2846
|
|
|
@@ -2947,11 +2977,11 @@ def _display_effective_config(data: Inputs, gh: GitHubContext) -> None:
|
|
|
2947
2977
|
|
|
2948
2978
|
# Show cleanup abandoned status if enabled
|
|
2949
2979
|
if cleanup_abandoned:
|
|
2950
|
-
config_info["CLEANUP_ABANDONED"] = "
|
|
2980
|
+
config_info["CLEANUP_ABANDONED"] = "✅"
|
|
2951
2981
|
|
|
2952
2982
|
# Show Gerrit cleanup status if enabled
|
|
2953
2983
|
if cleanup_gerrit:
|
|
2954
|
-
config_info["CLEANUP_GERRIT"] = "
|
|
2984
|
+
config_info["CLEANUP_GERRIT"] = "✅"
|
|
2955
2985
|
|
|
2956
2986
|
# Display the configuration table
|
|
2957
2987
|
display_pr_info(config_info, "GitHub2Gerrit Configuration")
|