github2gerrit 1.0.3__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.pre-commit-config.yaml +2 -2
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/PKG-INFO +42 -1
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/README.md +41 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/action.yaml +3 -3
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/cli.py +65 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/core.py +18 -42
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/duplicate_detection.py +10 -9
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/gerrit_rest.py +57 -15
- github2gerrit-1.0.4/src/github2gerrit/netrc.py +832 -0
- github2gerrit-1.0.4/tests/test_cli_netrc_options.py +345 -0
- github2gerrit-1.0.4/tests/test_netrc.py +752 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/uv.lock +25 -25
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.editorconfig +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.gitignore +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.gitlint +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.markdownlint.yaml +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.readthedocs.yml +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/.yamllint +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/LICENSE +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/LICENSES/Apache-2.0.txt +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/REUSE.toml +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/docs/RELEASE-v0.2.0.md +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/docs/github2gerrit_token_permissions_classic.png +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/pyproject.toml +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/sitecustomize.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/__init__.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/commit_normalization.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/config.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/constants.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/error_codes.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/external_api.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/gerrit_pr_closer.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/gerrit_query.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/gerrit_urls.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/github_api.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/gitutils.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/mapping_comment.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/models.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/orchestrator/__init__.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/pr_content_filter.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/reconcile_matcher.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/rich_display.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/rich_logging.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/similarity.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/ssh_agent_setup.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/ssh_common.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/ssh_config_parser.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/ssh_discovery.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/trailers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/src/github2gerrit/utils.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/conftest.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/fixtures/__init__.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/fixtures/make_repo.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/fixtures/ssh_config_samples.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_action_environment_mapping.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_action_outputs.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_action_pr_number_handling.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_action_step_validation.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_automation_only.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_change_id_deduplication.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_cli.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_cli_helpers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_cli_outputs_file.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_cli_url_and_dryrun.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_commit_normalization.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_composite_action_coverage.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_config_and_reviewers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_config_helpers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_close_pr_policy.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_config_and_errors.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_gerrit_backref_comment.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_gerrit_push_errors.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_gerrit_rest_results.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_integration_fixture_repo.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_prepare_commits.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_ssh_setup.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_core_ssrf_protection.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_duplicate_detection.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_email_case_normalization.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_error_codes.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_external_api_framework.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_force_flag_cli.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_change_id_footer.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_change_status_checks.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_pr_closer.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_rest_client.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_urls.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gerrit_urls_more.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ghe_and_gitreview_args.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_github_api_error_handling.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_github_api_helpers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_github_api_retry_and_helpers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_gitutils_helpers.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_mapping_comment_additional.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_mapping_comment_digest_and_backref.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_metadata_and_reconciliation.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_metadata_trailer_separation_bug.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_misc_small_coverage.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_orphan_rest_side_effects.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_pr_content_filter.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_pr_content_filter_integration.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_pr_update_detection.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_reconciliation_extracted_module.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_reconciliation_plan_and_orphans.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_reconciliation_scenarios.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_agent.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_agent_ownership.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_artifact_prevention.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_common.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_discovery.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_ssh_discovery_dry_run.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_trailers_additional.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_url_parser.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/test_utils.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/unit/test_config_integration.py +0 -0
- {github2gerrit-1.0.3 → github2gerrit-1.0.4}/tests/unit/test_ssh_config_parser.py +0 -0
|
@@ -59,7 +59,7 @@ repos:
|
|
|
59
59
|
types: [yaml]
|
|
60
60
|
|
|
61
61
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
62
|
-
rev:
|
|
62
|
+
rev: 45ef068da5f21267bb2a7ec4a623092959f09ce5 # frozen: v0.14.14
|
|
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: ccf21790019848af3eb4464be2a9d5efed6358f3 # frozen: 0.36.1
|
|
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.4
|
|
4
4
|
Summary: Submit a GitHub pull request to a Gerrit repository.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit-action
|
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit-action
|
|
@@ -1270,6 +1270,47 @@ GERRIT_HTTP_USER = "bot-user"
|
|
|
1270
1270
|
GERRIT_HTTP_PASSWORD = "${ENV:ODL_GERRIT_TOKEN}"
|
|
1271
1271
|
```
|
|
1272
1272
|
|
|
1273
|
+
### Using .netrc Files
|
|
1274
|
+
|
|
1275
|
+
GitHub2Gerrit supports loading Gerrit credentials from `.netrc` files, following
|
|
1276
|
+
the standard format used by curl and other tools.
|
|
1277
|
+
|
|
1278
|
+
**Search order:**
|
|
1279
|
+
|
|
1280
|
+
1. `.netrc` in the current directory
|
|
1281
|
+
2. `~/.netrc` in your home directory
|
|
1282
|
+
3. `~/_netrc` (Windows fallback)
|
|
1283
|
+
|
|
1284
|
+
**Example `.netrc` file:**
|
|
1285
|
+
|
|
1286
|
+
```text
|
|
1287
|
+
machine gerrit.onap.org login myuser password mytoken
|
|
1288
|
+
machine gerrit.opendaylight.org login myuser password anothertoken
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
**CLI options:**
|
|
1292
|
+
|
|
1293
|
+
| Option | Description |
|
|
1294
|
+
| ------ | ----------- |
|
|
1295
|
+
| `--no-netrc` | Disable .netrc file lookup |
|
|
1296
|
+
| `--netrc-file PATH` | Use a specific .netrc file |
|
|
1297
|
+
| `--netrc-optional` | Do not fail if .netrc file is missing (default) |
|
|
1298
|
+
| `--netrc-required` | Require a .netrc file and fail if missing |
|
|
1299
|
+
|
|
1300
|
+
By default, `.netrc` lookup is optional (`--netrc-optional`): if the tool
|
|
1301
|
+
finds no `.netrc` file, it continues and falls back to environment variables.
|
|
1302
|
+
Use `--netrc-required` to enforce that a `.netrc` file must be present.
|
|
1303
|
+
|
|
1304
|
+
When a `.netrc` file is present, the tool loads credentials automatically.
|
|
1305
|
+
Explicit environment variables or CLI arguments take precedence over `.netrc`
|
|
1306
|
+
entries.
|
|
1307
|
+
|
|
1308
|
+
**Credential Priority Order:**
|
|
1309
|
+
|
|
1310
|
+
1. **CLI arguments** (highest priority)
|
|
1311
|
+
2. **`.netrc` file** (if not disabled with `--no-netrc`)
|
|
1312
|
+
3. **Environment variables** (e.g., `GERRIT_HTTP_USER`, `GERRIT_HTTP_PASSWORD`)
|
|
1313
|
+
|
|
1273
1314
|
The tool loads configuration from `~/.config/github2gerrit/configuration.txt`
|
|
1274
1315
|
by default, or from the path specified in the `G2G_CONFIG_PATH` environment
|
|
1275
1316
|
variable.
|
|
@@ -1225,6 +1225,47 @@ GERRIT_HTTP_USER = "bot-user"
|
|
|
1225
1225
|
GERRIT_HTTP_PASSWORD = "${ENV:ODL_GERRIT_TOKEN}"
|
|
1226
1226
|
```
|
|
1227
1227
|
|
|
1228
|
+
### Using .netrc Files
|
|
1229
|
+
|
|
1230
|
+
GitHub2Gerrit supports loading Gerrit credentials from `.netrc` files, following
|
|
1231
|
+
the standard format used by curl and other tools.
|
|
1232
|
+
|
|
1233
|
+
**Search order:**
|
|
1234
|
+
|
|
1235
|
+
1. `.netrc` in the current directory
|
|
1236
|
+
2. `~/.netrc` in your home directory
|
|
1237
|
+
3. `~/_netrc` (Windows fallback)
|
|
1238
|
+
|
|
1239
|
+
**Example `.netrc` file:**
|
|
1240
|
+
|
|
1241
|
+
```text
|
|
1242
|
+
machine gerrit.onap.org login myuser password mytoken
|
|
1243
|
+
machine gerrit.opendaylight.org login myuser password anothertoken
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
**CLI options:**
|
|
1247
|
+
|
|
1248
|
+
| Option | Description |
|
|
1249
|
+
| ------ | ----------- |
|
|
1250
|
+
| `--no-netrc` | Disable .netrc file lookup |
|
|
1251
|
+
| `--netrc-file PATH` | Use a specific .netrc file |
|
|
1252
|
+
| `--netrc-optional` | Do not fail if .netrc file is missing (default) |
|
|
1253
|
+
| `--netrc-required` | Require a .netrc file and fail if missing |
|
|
1254
|
+
|
|
1255
|
+
By default, `.netrc` lookup is optional (`--netrc-optional`): if the tool
|
|
1256
|
+
finds no `.netrc` file, it continues and falls back to environment variables.
|
|
1257
|
+
Use `--netrc-required` to enforce that a `.netrc` file must be present.
|
|
1258
|
+
|
|
1259
|
+
When a `.netrc` file is present, the tool loads credentials automatically.
|
|
1260
|
+
Explicit environment variables or CLI arguments take precedence over `.netrc`
|
|
1261
|
+
entries.
|
|
1262
|
+
|
|
1263
|
+
**Credential Priority Order:**
|
|
1264
|
+
|
|
1265
|
+
1. **CLI arguments** (highest priority)
|
|
1266
|
+
2. **`.netrc` file** (if not disabled with `--no-netrc`)
|
|
1267
|
+
3. **Environment variables** (e.g., `GERRIT_HTTP_USER`, `GERRIT_HTTP_PASSWORD`)
|
|
1268
|
+
|
|
1228
1269
|
The tool loads configuration from `~/.config/github2gerrit/configuration.txt`
|
|
1229
1270
|
by default, or from the path specified in the `G2G_CONFIG_PATH` environment
|
|
1230
1271
|
variable.
|
|
@@ -158,19 +158,19 @@ runs:
|
|
|
158
158
|
steps:
|
|
159
159
|
- name: "Setup Python"
|
|
160
160
|
# yamllint disable-line rule:line-length
|
|
161
|
-
uses: actions/setup-python@
|
|
161
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
162
162
|
with:
|
|
163
163
|
python-version-file: '${{ github.action_path }}/pyproject.toml'
|
|
164
164
|
|
|
165
165
|
- name: "Setup uv"
|
|
166
166
|
# yamllint disable-line rule:line-length
|
|
167
|
-
uses: astral-sh/setup-uv@
|
|
167
|
+
uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7.2.1
|
|
168
168
|
with:
|
|
169
169
|
enable-cache: false
|
|
170
170
|
|
|
171
171
|
- name: "Checkout repository"
|
|
172
172
|
# yamllint disable-line rule:line-length
|
|
173
|
-
uses: actions/checkout@
|
|
173
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
174
174
|
with:
|
|
175
175
|
fetch-depth: ${{ inputs.FETCH_DEPTH }}
|
|
176
176
|
# Ensure we are on the PR's head SHA when triggered by PR events
|
|
@@ -69,6 +69,8 @@ from .gitutils import enumerate_reviewer_emails
|
|
|
69
69
|
from .gitutils import git
|
|
70
70
|
from .models import GitHubContext
|
|
71
71
|
from .models import Inputs
|
|
72
|
+
from .netrc import NetrcParseError
|
|
73
|
+
from .netrc import get_credentials_for_host
|
|
72
74
|
from .rich_display import RICH_AVAILABLE
|
|
73
75
|
from .rich_display import DummyProgressTracker
|
|
74
76
|
from .rich_display import G2GProgressTracker
|
|
@@ -778,6 +780,29 @@ def main(
|
|
|
778
780
|
envvar="AUTOMATION_ONLY",
|
|
779
781
|
help="Accept pull requests from known automation tools.",
|
|
780
782
|
),
|
|
783
|
+
no_netrc: bool = typer.Option(
|
|
784
|
+
False,
|
|
785
|
+
"--no-netrc",
|
|
786
|
+
help="Disable .netrc credential lookup for Gerrit HTTP authentication",
|
|
787
|
+
envvar="G2G_NO_NETRC",
|
|
788
|
+
),
|
|
789
|
+
netrc_file: Path | None = typer.Option( # noqa: B008
|
|
790
|
+
None,
|
|
791
|
+
"--netrc-file",
|
|
792
|
+
help="Explicit path to .netrc file for Gerrit HTTP credentials",
|
|
793
|
+
envvar="G2G_NETRC_FILE",
|
|
794
|
+
exists=True,
|
|
795
|
+
file_okay=True,
|
|
796
|
+
dir_okay=False,
|
|
797
|
+
readable=True,
|
|
798
|
+
resolve_path=True,
|
|
799
|
+
),
|
|
800
|
+
netrc_optional: bool = typer.Option(
|
|
801
|
+
True,
|
|
802
|
+
"--netrc-optional/--netrc-required",
|
|
803
|
+
help="Whether to fail if .netrc file is not found (default: optional)",
|
|
804
|
+
envvar="G2G_NETRC_OPTIONAL",
|
|
805
|
+
),
|
|
781
806
|
) -> None:
|
|
782
807
|
"""
|
|
783
808
|
Tool to convert GitHub pull requests into Gerrit changes
|
|
@@ -836,6 +861,46 @@ def main(
|
|
|
836
861
|
if os.getenv("AUTOMATION_ONLY"):
|
|
837
862
|
automation_only = parse_bool_env(os.getenv("AUTOMATION_ONLY"))
|
|
838
863
|
|
|
864
|
+
# Store netrc options in environment for use by processing functions
|
|
865
|
+
os.environ["G2G_NO_NETRC"] = "true" if no_netrc else "false"
|
|
866
|
+
if netrc_file:
|
|
867
|
+
os.environ["G2G_NETRC_FILE"] = str(netrc_file)
|
|
868
|
+
os.environ["G2G_NETRC_OPTIONAL"] = "true" if netrc_optional else "false"
|
|
869
|
+
|
|
870
|
+
# Handle netrc credential loading if enabled
|
|
871
|
+
if not no_netrc:
|
|
872
|
+
# Get the Gerrit server from environment or CLI
|
|
873
|
+
gerrit_host = gerrit_server or os.getenv("GERRIT_SERVER", "")
|
|
874
|
+
if gerrit_host:
|
|
875
|
+
try:
|
|
876
|
+
netrc_creds = get_credentials_for_host(
|
|
877
|
+
host=gerrit_host,
|
|
878
|
+
netrc_file=netrc_file,
|
|
879
|
+
use_netrc=True,
|
|
880
|
+
netrc_optional=netrc_optional,
|
|
881
|
+
)
|
|
882
|
+
if netrc_creds:
|
|
883
|
+
# Set HTTP credentials from netrc if not already set
|
|
884
|
+
if not os.getenv("GERRIT_HTTP_USER"):
|
|
885
|
+
os.environ["GERRIT_HTTP_USER"] = netrc_creds.login
|
|
886
|
+
if not os.getenv("GERRIT_HTTP_PASSWORD"):
|
|
887
|
+
os.environ["GERRIT_HTTP_PASSWORD"] = (
|
|
888
|
+
netrc_creds.password
|
|
889
|
+
)
|
|
890
|
+
log.debug(
|
|
891
|
+
"Loaded Gerrit HTTP credentials for %s from .netrc",
|
|
892
|
+
gerrit_host,
|
|
893
|
+
)
|
|
894
|
+
except FileNotFoundError:
|
|
895
|
+
if not netrc_optional:
|
|
896
|
+
safe_typer_echo(
|
|
897
|
+
"❌ No .netrc file found and --netrc-required set"
|
|
898
|
+
)
|
|
899
|
+
sys.exit(int(ExitCode.CONFIGURATION_ERROR))
|
|
900
|
+
except NetrcParseError as e:
|
|
901
|
+
safe_typer_echo(f"❌ Failed to parse .netrc file: {e}")
|
|
902
|
+
sys.exit(int(ExitCode.CONFIGURATION_ERROR))
|
|
903
|
+
|
|
839
904
|
# Set up logging level based on verbose flag
|
|
840
905
|
if verbose:
|
|
841
906
|
os.environ["G2G_LOG_LEVEL"] = "DEBUG"
|
|
@@ -307,29 +307,21 @@ class Orchestrator:
|
|
|
307
307
|
return True
|
|
308
308
|
|
|
309
309
|
try:
|
|
310
|
-
#
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
http_pass = os.getenv("GERRIT_HTTP_PASSWORD", "").strip()
|
|
310
|
+
# Use centralized URL builder with automatic credential resolution.
|
|
311
|
+
# Credential priority: CLI args > .netrc > environment variables.
|
|
312
|
+
# This call uses .netrc > environment (no CLI args passed).
|
|
313
|
+
from .gerrit_rest import build_client_for_host
|
|
316
314
|
|
|
317
|
-
|
|
315
|
+
client = build_client_for_host(gerrit.host)
|
|
316
|
+
|
|
317
|
+
# Check if client has authentication
|
|
318
|
+
if not client.is_authenticated:
|
|
318
319
|
log.debug(
|
|
319
320
|
"Cannot update Gerrit change metadata: "
|
|
320
|
-
"
|
|
321
|
+
"No credentials found (check .netrc or environment)"
|
|
321
322
|
)
|
|
322
323
|
return False
|
|
323
324
|
|
|
324
|
-
# Use centralized URL builder
|
|
325
|
-
from .gerrit_rest import build_client_for_host
|
|
326
|
-
|
|
327
|
-
client = build_client_for_host(
|
|
328
|
-
gerrit.host,
|
|
329
|
-
http_user=http_user,
|
|
330
|
-
http_password=http_pass,
|
|
331
|
-
)
|
|
332
|
-
|
|
333
325
|
encoded_id = urllib.parse.quote(change_id, safe="")
|
|
334
326
|
|
|
335
327
|
# Get current commit message to preserve G2G metadata and trailers
|
|
@@ -4229,14 +4221,9 @@ class Orchestrator:
|
|
|
4229
4221
|
# Create centralized URL builder (auto-discovers base path)
|
|
4230
4222
|
url_builder = create_gerrit_url_builder(gerrit.host)
|
|
4231
4223
|
|
|
4232
|
-
# Get authentication credentials
|
|
4233
|
-
http_user = (
|
|
4234
|
-
os.getenv("GERRIT_HTTP_USER", "").strip()
|
|
4235
|
-
or os.getenv("GERRIT_SSH_USER_G2G", "").strip()
|
|
4236
|
-
)
|
|
4237
|
-
http_pass = os.getenv("GERRIT_HTTP_PASSWORD", "").strip()
|
|
4238
|
-
|
|
4239
4224
|
# Query changes using centralized REST client
|
|
4225
|
+
# Credential priority: CLI args > .netrc > environment variables.
|
|
4226
|
+
# This call uses .netrc > environment (no CLI args passed).
|
|
4240
4227
|
urls: list[str] = []
|
|
4241
4228
|
nums: list[str] = []
|
|
4242
4229
|
shas: list[str] = []
|
|
@@ -4256,8 +4243,6 @@ class Orchestrator:
|
|
|
4256
4243
|
gerrit.host,
|
|
4257
4244
|
timeout=8.0,
|
|
4258
4245
|
max_attempts=5,
|
|
4259
|
-
http_user=http_user or None,
|
|
4260
|
-
http_password=http_pass or None,
|
|
4261
4246
|
)
|
|
4262
4247
|
try:
|
|
4263
4248
|
log.debug("Gerrit API base URL (discovered): %s", api_base_url)
|
|
@@ -5349,13 +5334,9 @@ class Orchestrator:
|
|
|
5349
5334
|
raise OrchestratorError(msg) from exc
|
|
5350
5335
|
|
|
5351
5336
|
# Gerrit REST reachability and optional auth check
|
|
5337
|
+
# Credential priority: CLI args > .netrc > environment variables.
|
|
5352
5338
|
base_path = os.getenv("GERRIT_HTTP_BASE_PATH", "").strip().strip("/")
|
|
5353
|
-
|
|
5354
|
-
os.getenv("GERRIT_HTTP_USER", "").strip()
|
|
5355
|
-
or os.getenv("GERRIT_SSH_USER_G2G", "").strip()
|
|
5356
|
-
)
|
|
5357
|
-
http_pass = os.getenv("GERRIT_HTTP_PASSWORD", "").strip()
|
|
5358
|
-
self._verify_gerrit_rest(gerrit.host, base_path, http_user, http_pass)
|
|
5339
|
+
self._verify_gerrit_rest(gerrit.host, base_path)
|
|
5359
5340
|
|
|
5360
5341
|
# GitHub token and metadata checks
|
|
5361
5342
|
try:
|
|
@@ -5403,33 +5384,28 @@ class Orchestrator:
|
|
|
5403
5384
|
self,
|
|
5404
5385
|
host: str,
|
|
5405
5386
|
base_path: str,
|
|
5406
|
-
http_user: str,
|
|
5407
|
-
http_pass: str,
|
|
5408
5387
|
) -> None:
|
|
5409
5388
|
"""Probe Gerrit REST endpoint with optional auth.
|
|
5410
5389
|
|
|
5411
5390
|
Uses the centralized gerrit_rest client to ensure proper base path
|
|
5412
5391
|
handling and consistent API interactions.
|
|
5392
|
+
|
|
5393
|
+
Credential priority: CLI args > .netrc > environment variables.
|
|
5413
5394
|
"""
|
|
5414
5395
|
from .gerrit_rest import build_client_for_host
|
|
5415
5396
|
|
|
5416
5397
|
try:
|
|
5417
|
-
# Use centralized client builder
|
|
5398
|
+
# Use centralized client builder for base path and credentials
|
|
5418
5399
|
client = build_client_for_host(
|
|
5419
5400
|
host,
|
|
5420
5401
|
timeout=8.0,
|
|
5421
5402
|
max_attempts=3,
|
|
5422
|
-
http_user=http_user,
|
|
5423
|
-
http_password=http_pass,
|
|
5424
5403
|
)
|
|
5425
5404
|
|
|
5426
5405
|
# Test connectivity with appropriate endpoint
|
|
5427
|
-
if
|
|
5406
|
+
if client.is_authenticated:
|
|
5428
5407
|
_ = client.get("/accounts/self")
|
|
5429
|
-
log.debug(
|
|
5430
|
-
"Gerrit REST authenticated access verified for user '%s'",
|
|
5431
|
-
http_user,
|
|
5432
|
-
)
|
|
5408
|
+
log.debug("Gerrit REST authenticated access verified")
|
|
5433
5409
|
else:
|
|
5434
5410
|
_ = client.get("/dashboard/self")
|
|
5435
5411
|
log.debug("Gerrit REST endpoint reachable (unauthenticated)")
|
|
@@ -268,22 +268,23 @@ class DuplicateDetector:
|
|
|
268
268
|
return None
|
|
269
269
|
|
|
270
270
|
def _build_gerrit_rest_client(self, gerrit_host: str) -> Any | None:
|
|
271
|
-
"""Build a Gerrit REST API client using centralized framework.
|
|
272
|
-
from .gerrit_rest import build_client_for_host
|
|
271
|
+
"""Build a Gerrit REST API client using centralized framework.
|
|
273
272
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
)
|
|
278
|
-
|
|
273
|
+
Credential resolution is handled by build_client_for_host with priority:
|
|
274
|
+
1. Explicit CLI arguments (if passed to build_client_for_host)
|
|
275
|
+
2. .netrc file
|
|
276
|
+
3. Environment variables (GERRIT_HTTP_USER/GERRIT_HTTP_PASSWORD)
|
|
277
|
+
|
|
278
|
+
This method does not pass explicit credentials, so only .netrc and
|
|
279
|
+
environment variables are used.
|
|
280
|
+
"""
|
|
281
|
+
from .gerrit_rest import build_client_for_host
|
|
279
282
|
|
|
280
283
|
try:
|
|
281
284
|
return build_client_for_host(
|
|
282
285
|
gerrit_host,
|
|
283
286
|
timeout=8.0,
|
|
284
287
|
max_attempts=3,
|
|
285
|
-
http_user=http_user or None,
|
|
286
|
-
http_password=http_pass or None,
|
|
287
288
|
)
|
|
288
289
|
except Exception as exc:
|
|
289
290
|
log.debug("Failed to create Gerrit REST client: %s", exc)
|
|
@@ -30,11 +30,11 @@ from __future__ import annotations
|
|
|
30
30
|
import base64
|
|
31
31
|
import json
|
|
32
32
|
import logging
|
|
33
|
-
import os
|
|
34
33
|
import urllib.error
|
|
35
34
|
import urllib.parse
|
|
36
35
|
import urllib.request
|
|
37
36
|
from dataclasses import dataclass
|
|
37
|
+
from pathlib import Path
|
|
38
38
|
from typing import Any
|
|
39
39
|
from typing import Final
|
|
40
40
|
from urllib.parse import urljoin
|
|
@@ -43,6 +43,8 @@ from .external_api import ApiType
|
|
|
43
43
|
from .external_api import RetryPolicy
|
|
44
44
|
from .external_api import external_api_call
|
|
45
45
|
from .gerrit_urls import create_gerrit_url_builder
|
|
46
|
+
from .netrc import GerritCredentials
|
|
47
|
+
from .netrc import resolve_gerrit_credentials
|
|
46
48
|
from .utils import log_exception_conditionally
|
|
47
49
|
|
|
48
50
|
|
|
@@ -156,6 +158,11 @@ class GerritRestClient:
|
|
|
156
158
|
|
|
157
159
|
# Public API
|
|
158
160
|
|
|
161
|
+
@property
|
|
162
|
+
def is_authenticated(self) -> bool:
|
|
163
|
+
"""Return True if client has authentication credentials."""
|
|
164
|
+
return self._auth is not None
|
|
165
|
+
|
|
159
166
|
def get(self, path: str) -> Any:
|
|
160
167
|
"""HTTP GET, returning parsed JSON."""
|
|
161
168
|
return self._request_json_with_retry("GET", path)
|
|
@@ -290,37 +297,72 @@ def build_client_for_host(
|
|
|
290
297
|
max_attempts: int = 5,
|
|
291
298
|
http_user: str | None = None,
|
|
292
299
|
http_password: str | None = None,
|
|
300
|
+
use_netrc: bool = True,
|
|
301
|
+
netrc_file: Path | None = None,
|
|
302
|
+
credentials: GerritCredentials | None = None,
|
|
293
303
|
) -> GerritRestClient:
|
|
294
304
|
"""
|
|
295
305
|
Build a GerritRestClient for a given host using the centralized URL builder.
|
|
296
306
|
|
|
297
307
|
- Uses auto-discovered or environment-provided base path.
|
|
298
|
-
- Reads HTTP auth from
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
308
|
+
- Reads HTTP auth from multiple sources in priority order:
|
|
309
|
+
1. Pre-resolved GerritCredentials object (if provided)
|
|
310
|
+
2. Explicit http_user/http_password arguments
|
|
311
|
+
3. .netrc file (if use_netrc=True)
|
|
312
|
+
4. Environment variables: GERRIT_HTTP_USER / GERRIT_HTTP_PASSWORD
|
|
313
|
+
If user is not provided, falls back to GERRIT_SSH_USER_G2G per project
|
|
314
|
+
norms.
|
|
302
315
|
|
|
303
316
|
Args:
|
|
304
317
|
host: Gerrit hostname (no scheme)
|
|
305
318
|
timeout: Request timeout in seconds.
|
|
306
319
|
max_attempts: Max retry attempts for transient failures.
|
|
307
|
-
http_user: Optional HTTP user.
|
|
308
|
-
http_password: Optional HTTP password/token.
|
|
320
|
+
http_user: Optional HTTP user (deprecated, use credentials).
|
|
321
|
+
http_password: Optional HTTP password/token (deprecated, use credentials).
|
|
322
|
+
use_netrc: Whether to try .netrc for credentials (default: True).
|
|
323
|
+
netrc_file: Explicit path to a .netrc file (optional).
|
|
324
|
+
credentials: Pre-resolved GerritCredentials object (preferred).
|
|
309
325
|
|
|
310
326
|
Returns:
|
|
311
327
|
Configured GerritRestClient.
|
|
312
328
|
"""
|
|
313
329
|
builder = create_gerrit_url_builder(host)
|
|
314
330
|
base_url = builder.api_url()
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
|
|
332
|
+
# Use pre-resolved credentials if provided
|
|
333
|
+
if credentials is not None and credentials.is_valid:
|
|
334
|
+
log.debug(
|
|
335
|
+
"Using pre-resolved credentials from %s",
|
|
336
|
+
credentials.auth_method_display(),
|
|
337
|
+
)
|
|
338
|
+
auth: tuple[str, str] | None = (
|
|
339
|
+
credentials.username,
|
|
340
|
+
credentials.password,
|
|
341
|
+
)
|
|
342
|
+
return GerritRestClient(
|
|
343
|
+
base_url=base_url,
|
|
344
|
+
auth=auth,
|
|
345
|
+
timeout=timeout,
|
|
346
|
+
max_attempts=max_attempts,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Otherwise, resolve credentials using centralized function
|
|
350
|
+
resolved = resolve_gerrit_credentials(
|
|
351
|
+
host=host,
|
|
352
|
+
explicit_username=http_user,
|
|
353
|
+
explicit_password=http_password,
|
|
354
|
+
use_netrc=use_netrc,
|
|
355
|
+
netrc_file=netrc_file,
|
|
356
|
+
env_username_var="GERRIT_HTTP_USER",
|
|
357
|
+
env_password_var="GERRIT_HTTP_PASSWORD", # noqa: S106
|
|
358
|
+
fallback_env_username_var="GERRIT_SSH_USER_G2G",
|
|
359
|
+
fallback_env_password_var=None,
|
|
319
360
|
)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
|
|
362
|
+
auth = None
|
|
363
|
+
if resolved is not None and resolved.is_valid:
|
|
364
|
+
auth = (resolved.username, resolved.password)
|
|
365
|
+
|
|
324
366
|
return GerritRestClient(
|
|
325
367
|
base_url=base_url, auth=auth, timeout=timeout, max_attempts=max_attempts
|
|
326
368
|
)
|