github2gerrit 1.3.0__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/PKG-INFO +1 -1
  2. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/sitecustomize.py +6 -0
  3. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/cli.py +23 -8
  4. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/core.py +0 -17
  5. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/duplicate_detection.py +0 -9
  6. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/external_api.py +2 -2
  7. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gerrit_rest.py +0 -14
  8. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/github_api.py +22 -12
  9. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gitreview.py +0 -5
  10. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gitutils.py +2 -1
  11. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/ssh_config_parser.py +3 -2
  12. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/ssh_discovery.py +0 -1
  13. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_config_helpers.py +6 -1
  14. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_config_and_errors.py +5 -1
  15. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_ssh_setup.py +8 -2
  16. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_external_api_framework.py +2 -2
  17. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gitutils_helpers.py +6 -4
  18. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_url_parser.py +5 -1
  19. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.editorconfig +0 -0
  20. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.gitignore +0 -0
  21. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.gitlint +0 -0
  22. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.markdownlint.yaml +0 -0
  23. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.pre-commit-config.yaml +0 -0
  24. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.readthedocs.yml +0 -0
  25. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/.yamllint +0 -0
  26. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/LICENSE +0 -0
  27. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/LICENSES/Apache-2.0.txt +0 -0
  28. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/README.md +0 -0
  29. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/REUSE.toml +0 -0
  30. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/SECURITY.md +0 -0
  31. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/action.yaml +0 -0
  32. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/docs/COMMIT_RULES.md +0 -0
  33. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
  34. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
  35. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/docs/RELEASE-v0.2.0.md +0 -0
  36. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/docs/github2gerrit_token_permissions_classic.png +0 -0
  37. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/pyproject.toml +0 -0
  38. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/__init__.py +0 -0
  39. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/commit_normalization.py +0 -0
  40. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/commit_rules.py +0 -0
  41. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/config.py +0 -0
  42. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/constants.py +0 -0
  43. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/error_codes.py +0 -0
  44. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gerrit_pr_closer.py +0 -0
  45. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gerrit_query.py +0 -0
  46. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gerrit_ssh.py +0 -0
  47. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/gerrit_urls.py +0 -0
  48. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/mapping_comment.py +0 -0
  49. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/models.py +0 -0
  50. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/netrc.py +0 -0
  51. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/orchestrator/__init__.py +0 -0
  52. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
  53. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/pr_commands.py +0 -0
  54. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/pr_content_filter.py +0 -0
  55. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/reconcile_matcher.py +0 -0
  56. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/rich_display.py +0 -0
  57. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/rich_logging.py +0 -0
  58. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/similarity.py +0 -0
  59. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/ssh_agent_setup.py +0 -0
  60. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/ssh_common.py +0 -0
  61. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/trailers.py +0 -0
  62. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/src/github2gerrit/utils.py +0 -0
  63. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/conftest.py +0 -0
  64. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/fixtures/__init__.py +0 -0
  65. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/fixtures/make_repo.py +0 -0
  66. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/fixtures/ssh_config_samples.py +0 -0
  67. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_action_environment_mapping.py +0 -0
  68. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_action_outputs.py +0 -0
  69. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_action_pr_number_handling.py +0 -0
  70. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_action_step_validation.py +0 -0
  71. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_automation_only.py +0 -0
  72. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_change_id_deduplication.py +0 -0
  73. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_clean_squash_title.py +0 -0
  74. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_cli.py +0 -0
  75. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_cli_helpers.py +0 -0
  76. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_cli_netrc_options.py +0 -0
  77. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_cli_outputs_file.py +0 -0
  78. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_cli_url_and_dryrun.py +0 -0
  79. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_commit_normalization.py +0 -0
  80. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_commit_rules.py +0 -0
  81. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_composite_action_coverage.py +0 -0
  82. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_config_and_reviewers.py +0 -0
  83. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_close_pr_policy.py +0 -0
  84. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_gerrit_backref_comment.py +0 -0
  85. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_gerrit_push_errors.py +0 -0
  86. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_gerrit_rest_results.py +0 -0
  87. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_integration_fixture_repo.py +0 -0
  88. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_prepare_commits.py +0 -0
  89. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_shallow_clone.py +0 -0
  90. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_core_ssrf_protection.py +0 -0
  91. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_dependency_supersession.py +0 -0
  92. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_dns_validation_and_no_gerrit.py +0 -0
  93. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_duplicate_detection.py +0 -0
  94. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_email_case_normalization.py +0 -0
  95. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_error_codes.py +0 -0
  96. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_force_flag_cli.py +0 -0
  97. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_change_id_footer.py +0 -0
  98. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_change_status_checks.py +0 -0
  99. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_pr_closer.py +0 -0
  100. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_rest_client.py +0 -0
  101. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_ssh_abandon.py +0 -0
  102. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_urls.py +0 -0
  103. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gerrit_urls_more.py +0 -0
  104. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ghe_and_gitreview_args.py +0 -0
  105. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_github_api_error_handling.py +0 -0
  106. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_github_api_helpers.py +0 -0
  107. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_github_api_retry_and_helpers.py +0 -0
  108. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_gitreview.py +0 -0
  109. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_issue_157_regressions.py +0 -0
  110. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_mapping_comment_additional.py +0 -0
  111. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_mapping_comment_digest_and_backref.py +0 -0
  112. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_metadata_and_reconciliation.py +0 -0
  113. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_metadata_trailer_separation_bug.py +0 -0
  114. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_misc_small_coverage.py +0 -0
  115. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_netrc.py +0 -0
  116. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_orphan_rest_side_effects.py +0 -0
  117. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_pr_commands.py +0 -0
  118. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_pr_content_filter.py +0 -0
  119. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_pr_content_filter_integration.py +0 -0
  120. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_pr_update_detection.py +0 -0
  121. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_reconciliation_extracted_module.py +0 -0
  122. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_reconciliation_plan_and_orphans.py +0 -0
  123. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_reconciliation_scenarios.py +0 -0
  124. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_agent.py +0 -0
  125. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_agent_ownership.py +0 -0
  126. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_artifact_prevention.py +0 -0
  127. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_common.py +0 -0
  128. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_discovery.py +0 -0
  129. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_ssh_discovery_dry_run.py +0 -0
  130. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_trailers_additional.py +0 -0
  131. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/test_utils.py +0 -0
  132. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/unit/test_config_integration.py +0 -0
  133. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/tests/unit/test_ssh_config_parser.py +0 -0
  134. {github2gerrit-1.3.0 → github2gerrit-1.3.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github2gerrit
3
- Version: 1.3.0
3
+ Version: 1.3.2
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
@@ -57,6 +57,8 @@ def _dbg(msg: str) -> None:
57
57
  try:
58
58
  print(f"[sitecustomize] {msg}", file=sys.stderr)
59
59
  except Exception:
60
+ # Debug logging must never disrupt interpreter startup; if
61
+ # writing to stderr fails, silently drop the message.
60
62
  pass
61
63
 
62
64
 
@@ -125,6 +127,8 @@ def _collect_base_dirs(repo_root: Path) -> set[Path]:
125
127
  try:
126
128
  bases.add(Path.cwd())
127
129
  except Exception:
130
+ # CWD may be unavailable (e.g. deleted); skip it and continue
131
+ # collecting the remaining base directories.
128
132
  pass
129
133
  # Repository root (directory containing this file)
130
134
  bases.add(repo_root)
@@ -134,6 +138,8 @@ def _collect_base_dirs(repo_root: Path) -> set[Path]:
134
138
  try:
135
139
  bases.add(Path(gw))
136
140
  except Exception:
141
+ # A malformed GITHUB_WORKSPACE value should not abort cleanup;
142
+ # skip it and continue with the other base directories.
137
143
  pass
138
144
  return bases
139
145
 
@@ -385,12 +385,15 @@ else:
385
385
 
386
386
 
387
387
  class _FormatterProto(Protocol):
388
- def write_usage(self, prog: str, args: str, prefix: str = ...) -> None: ...
388
+ def write_usage(self, prog: str, args: str, prefix: str = ...) -> None:
389
+ """Write the program usage line."""
389
390
 
390
391
 
391
392
  class _ContextProto(Protocol):
392
393
  @property
393
- def command_path(self) -> str: ...
394
+ def command_path(self) -> str:
395
+ """Return the full command path."""
396
+ raise NotImplementedError
394
397
 
395
398
 
396
399
  class _SingleUsageGroup(BaseGroup):
@@ -471,14 +474,23 @@ def _resolve_issue_id_from_json(json_str: str, github_actor: str) -> str:
471
474
  return ""
472
475
 
473
476
 
474
- # Show version information when --help is used or in GitHub Actions mode
475
- if "--help" in sys.argv or _is_github_actions_context():
477
+ def _print_version_banner() -> None:
478
+ """Print the resolved package version, or a fallback notice.
479
+
480
+ Defined as a function so the ``print`` calls do not execute at module
481
+ scope during import; the banner is only emitted when explicitly invoked.
482
+ """
476
483
  try:
477
484
  app_version = get_version("github2gerrit")
478
485
  print(f"🏷️ github2gerrit version {app_version}")
479
486
  except Exception:
480
487
  print("⚠️ github2gerrit version information not available")
481
488
 
489
+
490
+ # Show version information when --help is used or in GitHub Actions mode
491
+ if "--help" in sys.argv or _is_github_actions_context():
492
+ _print_version_banner()
493
+
482
494
  app: typer.Typer = typer.Typer(
483
495
  add_completion=False,
484
496
  no_args_is_help=False,
@@ -504,9 +516,12 @@ def _resolve_org(default_org: str | None) -> str:
504
516
  if TYPE_CHECKING:
505
517
  F = TypeVar("F", bound=Callable[..., object])
506
518
 
507
- def typed_app_command(
508
- *args: object, **kwargs: object
509
- ) -> Callable[[F], F]: ...
519
+ # Typed stub so basedpyright treats @typed_app_command as a typed
520
+ # decorator. The body raises rather than using an ellipsis so static
521
+ # analysers do not model it as a procedure that returns None; this
522
+ # branch is never executed at runtime.
523
+ def typed_app_command(*args: object, **kwargs: object) -> Callable[[F], F]:
524
+ raise NotImplementedError
510
525
  else:
511
526
  typed_app_command = app.command
512
527
 
@@ -865,7 +880,7 @@ def main(
865
880
  ) -> bool:
866
881
  """Return *current* if the CLI flag was explicit, else parse env."""
867
882
  source = ctx.get_parameter_source(param_name) # pyright: ignore[reportAttributeAccessIssue]
868
- if source == click.core.ParameterSource.COMMANDLINE: # pyright: ignore[reportAttributeAccessIssue]
883
+ if source == click.core.ParameterSource.COMMANDLINE: # pyright: ignore[reportAttributeAccessIssue, reportUnnecessaryComparison]
869
884
  return current
870
885
  env_val = os.getenv(env_var)
871
886
  if env_val is not None:
@@ -80,19 +80,10 @@ from .utils import is_verbose_mode
80
80
 
81
81
 
82
82
  try:
83
- from pygerrit2 import GerritRestAPI
84
- from pygerrit2 import HTTPBasicAuth
85
- except ImportError:
86
- GerritRestAPI = None
87
- HTTPBasicAuth = None
88
-
89
- try:
90
- from .ssh_discovery import SSHDiscoveryError
91
83
  from .ssh_discovery import auto_discover_gerrit_host_keys
92
84
  except ImportError:
93
85
  # Fallback if ssh_discovery module is not available
94
86
  auto_discover_gerrit_host_keys = None # type: ignore[assignment]
95
- SSHDiscoveryError = Exception # type: ignore[misc,assignment]
96
87
 
97
88
  try:
98
89
  from .ssh_agent_setup import SSHAgentManager
@@ -113,7 +104,6 @@ log = logging.getLogger("github2gerrit.core")
113
104
 
114
105
 
115
106
  # Error message constants to comply with TRY003
116
- _MSG_ISSUE_ID_MULTILINE = "Issue ID must be single line"
117
107
  _MSG_MISSING_PR_CONTEXT = "missing PR context"
118
108
  _MSG_BAD_REPOSITORY_CONTEXT = "bad repository context"
119
109
  _MSG_MISSING_GERRIT_SERVER = (
@@ -121,10 +111,6 @@ _MSG_MISSING_GERRIT_SERVER = (
121
111
  "input/environment variable, .gitreview file, or action configuration."
122
112
  )
123
113
  _MSG_MISSING_GERRIT_PROJECT = "missing GERRIT_PROJECT"
124
- _MSG_PYGERRIT2_REQUIRED_REST = "pygerrit2 is required to query Gerrit REST API"
125
- _MSG_PYGERRIT2_REQUIRED_AUTH = "pygerrit2 is required for HTTP authentication"
126
- _MSG_PYGERRIT2_MISSING = "pygerrit2 missing"
127
- _MSG_PYGERRIT2_AUTH_MISSING = "pygerrit2 auth missing"
128
114
  _MSG_DNS_RESOLUTION_FAILED = "DNS resolution failed for '%s'"
129
115
 
130
116
 
@@ -4429,7 +4415,6 @@ class Orchestrator:
4429
4415
  List of email addresses that were not found in Gerrit
4430
4416
  """
4431
4417
  combined_output = f"{exc.stdout}\n{exc.stderr}"
4432
- import re
4433
4418
 
4434
4419
  # Pattern to match: Account 'email@domain.com' not found
4435
4420
  pattern = r"Account\s+'([^']+)'\s+not\s+found"
@@ -4903,7 +4888,6 @@ class Orchestrator:
4903
4888
  # Extract specific rejection reason from output
4904
4889
  # Handle multiline rejection messages by looking in normalized
4905
4890
  # output
4906
- import re
4907
4891
 
4908
4892
  # Look for the rejection pattern in the normalized output
4909
4893
  rejection_match = re.search(
@@ -5299,7 +5283,6 @@ class Orchestrator:
5299
5283
  self, workspace: Path, gh: GitHubContext, inputs: Inputs
5300
5284
  ) -> None:
5301
5285
  """Fallback to GitHub API archive download (ported from CLI)."""
5302
- import json
5303
5286
  import zipfile
5304
5287
 
5305
5288
  repo_full = gh.repository.strip() if gh.repository else ""
@@ -32,15 +32,6 @@ from .rich_display import safe_console_print
32
32
  from .trailers import extract_github_metadata
33
33
 
34
34
 
35
- # Optional Gerrit REST API support
36
- try:
37
- from pygerrit2 import GerritRestAPI
38
- from pygerrit2 import HTTPBasicAuth
39
- except ImportError:
40
- GerritRestAPI = None
41
- HTTPBasicAuth = None
42
-
43
-
44
35
  log = logging.getLogger(__name__)
45
36
 
46
37
  __all__ = [
@@ -327,7 +327,7 @@ def external_api_call(
327
327
  policy=policy,
328
328
  )
329
329
 
330
- last_exc: BaseException | None = None
330
+ last_exc: Exception | None = None
331
331
 
332
332
  for attempt in range(1, policy.max_attempts + 1):
333
333
  context.attempt = attempt
@@ -347,7 +347,7 @@ def external_api_call(
347
347
 
348
348
  # Call the actual function
349
349
  result = func(*args, **kwargs)
350
- except BaseException as exc:
350
+ except Exception as exc:
351
351
  last_exc = exc
352
352
  duration = time.time() - context.start_time
353
353
 
@@ -68,19 +68,6 @@ _MSG_PYGERRIT2_REQUIRED_AUTH: Final[str] = (
68
68
  "pygerrit2 is required for HTTP authentication"
69
69
  )
70
70
 
71
- _TRANSIENT_ERR_SUBSTRINGS: Final[tuple[str, ...]] = (
72
- "timed out",
73
- "temporarily unavailable",
74
- "temporary failure",
75
- "connection reset",
76
- "connection aborted",
77
- "broken pipe",
78
- "connection refused",
79
- "bad gateway",
80
- "service unavailable",
81
- "gateway timeout",
82
- )
83
-
84
71
  # HTTP status codes that indicate an authentication/authorization problem
85
72
  # rather than a transient fault or a bug. These are expected when no
86
73
  # Gerrit REST credentials are available (or they are insufficient for the
@@ -321,7 +308,6 @@ class GerritRestClient:
321
308
  )
322
309
 
323
310
  with urllib.request.urlopen(req, timeout=self._timeout) as resp:
324
- status = getattr(resp, "status", None)
325
311
  content = resp.read()
326
312
  # Gerrit prepends ")]}'" in JSON responses to prevent JSON
327
313
  # hijacking; strip if present
@@ -23,7 +23,6 @@ from collections.abc import Iterable
23
23
  from importlib import import_module
24
24
  from typing import Any
25
25
  from typing import Protocol
26
- from typing import TypeVar
27
26
  from typing import cast
28
27
 
29
28
  from .error_codes import ExitCode
@@ -35,8 +34,6 @@ from .external_api import external_api_call
35
34
 
36
35
  # Error message constants to comply with TRY003
37
36
  _MSG_PYGITHUB_REQUIRED = "PyGithub required"
38
- _MSG_MISSING_GITHUB_TOKEN = "missing GITHUB_TOKEN" # noqa: S105
39
- _MSG_BAD_GITHUB_REPOSITORY = "bad GITHUB_REPOSITORY"
40
37
 
41
38
 
42
39
  class GithubExceptionType(Exception):
@@ -82,8 +79,12 @@ class GhIssueComment(Protocol):
82
79
 
83
80
 
84
81
  class GhIssue(Protocol):
85
- def get_comments(self) -> Iterable[GhIssueComment]: ...
86
- def create_comment(self, body: str) -> None: ...
82
+ def get_comments(self) -> Iterable[GhIssueComment]:
83
+ """Return the comments on the issue."""
84
+ raise NotImplementedError
85
+
86
+ def create_comment(self, body: str) -> None:
87
+ """Create a new comment on the issue."""
87
88
 
88
89
 
89
90
  class GhPullRequest(Protocol):
@@ -91,17 +92,28 @@ class GhPullRequest(Protocol):
91
92
  title: str | None
92
93
  body: str | None
93
94
 
94
- def as_issue(self) -> GhIssue: ...
95
- def edit(self, *, state: str) -> None: ...
95
+ def as_issue(self) -> GhIssue:
96
+ """Return the issue view of this pull request."""
97
+ raise NotImplementedError
98
+
99
+ def edit(self, *, state: str) -> None:
100
+ """Edit the pull request state."""
96
101
 
97
102
 
98
103
  class GhRepository(Protocol):
99
- def get_pull(self, number: int) -> GhPullRequest: ...
100
- def get_pulls(self, state: str) -> Iterable[GhPullRequest]: ...
104
+ def get_pull(self, number: int) -> GhPullRequest:
105
+ """Return the pull request with the given number."""
106
+ raise NotImplementedError
107
+
108
+ def get_pulls(self, state: str) -> Iterable[GhPullRequest]:
109
+ """Return the pull requests with the given state."""
110
+ raise NotImplementedError
101
111
 
102
112
 
103
113
  class GhClient(Protocol):
104
- def get_repo(self, full: str) -> GhRepository: ...
114
+ def get_repo(self, full: str) -> GhRepository:
115
+ """Return the repository for the given full name."""
116
+ raise NotImplementedError
105
117
 
106
118
 
107
119
  __all__ = [
@@ -120,8 +132,6 @@ __all__ = [
120
132
 
121
133
  log = logging.getLogger("github2gerrit.github_api")
122
134
 
123
- _T = TypeVar("_T")
124
-
125
135
 
126
136
  def _getenv_str(name: str) -> str:
127
137
  val = os.getenv(name, "")
@@ -151,11 +151,6 @@ def derive_base_path(host: str) -> str | None:
151
151
  return _KNOWN_BASE_PATHS.get(host.lower().strip())
152
152
 
153
153
 
154
- # Keep a private alias so internal callers don't break if they were
155
- # referencing the underscore-prefixed name during development.
156
- _derive_base_path = derive_base_path
157
-
158
-
159
154
  # ───────────────────────────────────────────────────────────────────────
160
155
  # Pure parser
161
156
  # ───────────────────────────────────────────────────────────────────────
@@ -555,6 +555,8 @@ def git_commit_amend(
555
555
  if committer_email and _has_committer_signoff(body):
556
556
  effective_signoff = False
557
557
  except GitError:
558
+ # Unable to read the current commit body (e.g. no HEAD yet);
559
+ # fall back to honoring the requested signoff setting.
558
560
  pass
559
561
  except Exception:
560
562
  # Best effort only; default to requested signoff
@@ -613,7 +615,6 @@ def git_commit_new(
613
615
  _tf.flush()
614
616
  tmp_path = Path(_tf.name)
615
617
  message_file = tmp_path
616
- message = None
617
618
 
618
619
  # Determine whether to add -s; only suppress if message already has a
619
620
  # sign-off for current committer
@@ -272,8 +272,9 @@ class SSHConfig:
272
272
  try:
273
273
  return bool(re.match(regex_pattern, hostname))
274
274
  except re.error:
275
- # If regex is invalid, fall back to exact match
276
- return pattern == hostname
275
+ # An invalid regex cannot match. The exact-match case is
276
+ # already handled above, so there is nothing left to match.
277
+ return False
277
278
 
278
279
 
279
280
  # Global cache for SSH config instances to avoid re-parsing
@@ -379,7 +379,6 @@ def save_host_keys_to_config(
379
379
  new_lines.append(f"[{organization}]")
380
380
  escaped_keys = host_keys.replace("\n", "\\n")
381
381
  new_lines.append(f'GERRIT_KNOWN_HOSTS = "{escaped_keys}"')
382
- gerrit_known_hosts_updated = True
383
382
 
384
383
  # If section existed but didn't have GERRIT_KNOWN_HOSTS, add it
385
384
  elif not gerrit_known_hosts_updated:
@@ -28,7 +28,12 @@ from github2gerrit.config import overlay_missing
28
28
  if TYPE_CHECKING:
29
29
  F = TypeVar("F", bound=Callable[..., object])
30
30
 
31
- def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]: ...
31
+ # Typed stub so basedpyright treats @parametrize as a typed decorator
32
+ # (reportUntypedFunctionDecorator). The body raises rather than using
33
+ # an ellipsis so static analysers do not model it as a procedure that
34
+ # returns None; this code is never executed at runtime.
35
+ def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]:
36
+ raise NotImplementedError
32
37
  else:
33
38
  from pytest import mark
34
39
 
@@ -287,7 +287,11 @@ def test_configure_git_raises_on_git_review_init_failure(
287
287
  if TYPE_CHECKING:
288
288
  F = TypeVar("F", bound=Callable[..., object])
289
289
 
290
- def fixture(*args: object, **kwargs: object) -> Callable[[F], F]: ...
290
+ # Typed stub (raising body, never executed) so basedpyright treats
291
+ # @fixture as a typed decorator and static analysers do not model it
292
+ # as a procedure returning None.
293
+ def fixture(*args: object, **kwargs: object) -> Callable[[F], F]:
294
+ raise NotImplementedError
291
295
  else:
292
296
  fixture = pytest.fixture
293
297
 
@@ -52,8 +52,14 @@ def snapshot_dir_state(directory: Path) -> dict[str, str]:
52
52
  if TYPE_CHECKING:
53
53
  F = TypeVar("F", bound=Callable[..., object])
54
54
 
55
- def fixture(*args: object, **kwargs: object) -> Callable[[F], F]: ...
56
- def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]: ...
55
+ # Typed stubs (raising bodies, never executed) so basedpyright treats
56
+ # @fixture/@parametrize as typed decorators and static analysers do not
57
+ # model them as procedures returning None.
58
+ def fixture(*args: object, **kwargs: object) -> Callable[[F], F]:
59
+ raise NotImplementedError
60
+
61
+ def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]:
62
+ raise NotImplementedError
57
63
  else:
58
64
  from pytest import fixture as _fixture
59
65
  from pytest import mark as _mark
@@ -222,6 +222,8 @@ def test_curl_download_success(
222
222
  output_path = Path(cmd[o_index + 1])
223
223
  output_path.write_text("downloaded content")
224
224
  except (ValueError, IndexError):
225
+ # No "-o" flag (or no value after it) in the mocked curl
226
+ # command; nothing to write, so behave like a no-op download.
225
227
  pass
226
228
  return MockCompletedProcess()
227
229
 
@@ -278,8 +280,6 @@ def test_log_api_metrics_summary(
278
280
  monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
279
281
  ) -> None:
280
282
  """Test API metrics summary logging."""
281
- import logging
282
-
283
283
  # Set up some metrics
284
284
  reset_api_metrics()
285
285
 
@@ -222,7 +222,7 @@ def test_git_last_commit_trailers_parsing_edge_cases(
222
222
  ) -> None:
223
223
  # Patch git_show to return a crafted commit message body with various
224
224
  # trailer forms.
225
- import github2gerrit.gitutils as gitutils
225
+ from github2gerrit.gitutils import git_last_commit_trailers
226
226
 
227
227
  body = (
228
228
  "Subject line\n\n"
@@ -235,10 +235,12 @@ def test_git_last_commit_trailers_parsing_edge_cases(
235
235
  "Change-Id: Iabc123abc123abc123abc123abc123abc123ab\n"
236
236
  )
237
237
 
238
- monkeypatch.setattr(gitutils, "git_show", lambda rev, **kw: body)
238
+ monkeypatch.setattr(
239
+ "github2gerrit.gitutils.git_show", lambda rev, **kw: body
240
+ )
239
241
 
240
242
  # Without filter: expect only proper footer trailers collected.
241
- trailers_all = gitutils.git_last_commit_trailers()
243
+ trailers_all = git_last_commit_trailers()
242
244
  # Body "Key:" lines should not be extracted as trailers
243
245
  assert "Key" not in trailers_all
244
246
  # 'Empty' should be ignored due to empty value
@@ -254,7 +256,7 @@ def test_git_last_commit_trailers_parsing_edge_cases(
254
256
  ]
255
257
 
256
258
  # With filter keys: only Change-Id should be returned.
257
- trailers_change = gitutils.git_last_commit_trailers(keys=["Change-Id"])
259
+ trailers_change = git_last_commit_trailers(keys=["Change-Id"])
258
260
  assert list(trailers_change.keys()) == ["Change-Id"]
259
261
  assert trailers_change["Change-Id"] == [
260
262
  "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
@@ -11,7 +11,11 @@ from typing import TypeVar
11
11
  if TYPE_CHECKING:
12
12
  F = TypeVar("F", bound=Callable[..., object])
13
13
 
14
- def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]: ...
14
+ # Typed stub (raising body, never executed) so basedpyright treats
15
+ # @parametrize as a typed decorator and static analysers do not model
16
+ # it as a procedure returning None.
17
+ def parametrize(*args: object, **kwargs: object) -> Callable[[F], F]:
18
+ raise NotImplementedError
15
19
  else:
16
20
  from pytest import mark
17
21
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes