github2gerrit 0.1.15__py3-none-any.whl → 0.1.16__py3-none-any.whl

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/cli.py CHANGED
@@ -36,6 +36,7 @@ from .github_api import get_pr_title_body
36
36
  from .github_api import get_pull
37
37
  from .github_api import get_repo_from_env
38
38
  from .github_api import iter_open_pulls
39
+ from .gitutils import CommandError
39
40
  from .gitutils import run_cmd
40
41
  from .models import GitHubContext
41
42
  from .models import Inputs
@@ -246,7 +247,7 @@ if "--help" in sys.argv or _is_github_actions_context():
246
247
 
247
248
  app: typer.Typer = typer.Typer(
248
249
  add_completion=False,
249
- no_args_is_help=False,
250
+ no_args_is_help=True,
250
251
  cls=cast(Any, _SingleUsageGroup),
251
252
  rich_markup_mode="rich",
252
253
  help="Tool to convert GitHub pull requests into Gerrit changes",
@@ -990,15 +991,56 @@ def _process_single(
990
991
  else:
991
992
  progress_tracker.change_updated()
992
993
  except Exception as exc:
993
- error_msg = str(exc)
994
- log.debug("Execution failed; continuing to write outputs: %s", exc)
994
+ # Enhanced error handling for CommandError to show git command
995
+ # details
996
+ if isinstance(exc, CommandError):
997
+ # Always show the basic error message
998
+ cmd_str = " ".join(exc.cmd) if exc.cmd else "unknown command"
999
+ basic_error = f"Git command failed: {cmd_str}"
1000
+ if exc.returncode is not None:
1001
+ basic_error += f" (exit code: {exc.returncode})"
1002
+
1003
+ # In verbose mode, show detailed stdout/stderr
1004
+ if is_verbose_mode():
1005
+ detailed_msg = basic_error
1006
+ if exc.stdout and exc.stdout.strip():
1007
+ detailed_msg += f"\nGit stdout: {exc.stdout.strip()}"
1008
+ if exc.stderr and exc.stderr.strip():
1009
+ detailed_msg += f"\nGit stderr: {exc.stderr.strip()}"
1010
+
1011
+ # Show debugging suggestion for merge failures
1012
+ if "merge --squash" in " ".join(exc.cmd or []):
1013
+ detailed_msg += (
1014
+ "\n💡 For local debugging: python "
1015
+ "test_merge_failure.py --verbose"
1016
+ )
995
1017
 
996
- # Always show the actual error to the user, not just in debug mode
997
- if progress_tracker:
998
- progress_tracker.add_error(f"Execution failed: {error_msg}")
1018
+ safe_console_print(f"❌ {detailed_msg}", style="red")
1019
+ if progress_tracker:
1020
+ progress_tracker.add_error(basic_error)
1021
+ if exc.stderr and exc.stderr.strip():
1022
+ progress_tracker.add_error(
1023
+ f"Details: {exc.stderr.strip()}"
1024
+ )
1025
+ else:
1026
+ # In non-verbose mode, show basic error with hint to enable
1027
+ # verbose
1028
+ hint_msg = (
1029
+ basic_error
1030
+ + "\n💡 Run with VERBOSE=true for detailed git output"
1031
+ )
1032
+ safe_console_print(f"❌ {hint_msg}", style="red")
1033
+ if progress_tracker:
1034
+ progress_tracker.add_error(basic_error)
999
1035
  else:
1000
- # If no progress tracker, show error directly
1001
- safe_console_print(f"❌ Error: {error_msg}", style="red")
1036
+ # For other exceptions, use original handling
1037
+ error_msg = str(exc)
1038
+ if progress_tracker:
1039
+ progress_tracker.add_error(f"Execution failed: {error_msg}")
1040
+ else:
1041
+ safe_console_print(f"❌ Error: {error_msg}", style="red")
1042
+
1043
+ log.debug("Execution failed; continuing to write outputs: %s", exc)
1002
1044
 
1003
1045
  # In verbose mode, also log the full exception with traceback
1004
1046
  if is_verbose_mode():
github2gerrit/core.py CHANGED
@@ -2028,10 +2028,99 @@ class Orchestrator:
2028
2028
  # Create temp branch from base and merge-squash PR head
2029
2029
  tmp_branch = f"g2g_tmp_{gh.pr_number or 'pr'!s}_{os.getpid()}"
2030
2030
  os.environ["G2G_TMP_BRANCH"] = tmp_branch
2031
+
2032
+ log.debug(
2033
+ "Git merge preparation: base_sha=%s, head_sha=%s, tmp_branch=%s",
2034
+ base_sha,
2035
+ head_sha,
2036
+ tmp_branch,
2037
+ )
2038
+
2039
+ # Check if we have any commits to merge
2040
+ try:
2041
+ merge_base = run_cmd(
2042
+ ["git", "merge-base", base_sha, head_sha], cwd=self.workspace
2043
+ ).stdout.strip()
2044
+ log.debug("Merge base: %s", merge_base)
2045
+
2046
+ # Check if there are any commits between base and head
2047
+ commits_to_merge = run_cmd(
2048
+ ["git", "rev-list", f"{base_sha}..{head_sha}"],
2049
+ cwd=self.workspace,
2050
+ ).stdout.strip()
2051
+ if not commits_to_merge:
2052
+ log.warning(
2053
+ "No commits found between base (%s) and head (%s)",
2054
+ base_sha,
2055
+ head_sha,
2056
+ )
2057
+ else:
2058
+ commit_count = len(commits_to_merge.splitlines())
2059
+ log.debug("Found %d commits to merge", commit_count)
2060
+
2061
+ except Exception as debug_exc:
2062
+ log.warning("Failed to analyze merge situation: %s", debug_exc)
2063
+
2031
2064
  run_cmd(
2032
2065
  ["git", "checkout", "-b", tmp_branch, base_sha], cwd=self.workspace
2033
2066
  )
2034
- run_cmd(["git", "merge", "--squash", head_sha], cwd=self.workspace)
2067
+
2068
+ # Show git status before attempting merge
2069
+ try:
2070
+ status_output = run_cmd(
2071
+ ["git", "status", "--porcelain"], cwd=self.workspace
2072
+ ).stdout
2073
+ if status_output.strip():
2074
+ log.debug(
2075
+ "Git status before merge (modified files detected):\n%s",
2076
+ status_output,
2077
+ )
2078
+ else:
2079
+ log.debug("Git status before merge: working directory clean")
2080
+
2081
+ # Show current branch
2082
+ current_branch = run_cmd(
2083
+ ["git", "branch", "--show-current"], cwd=self.workspace
2084
+ ).stdout.strip()
2085
+ log.debug("Current branch before merge: %s", current_branch)
2086
+
2087
+ except Exception as status_exc:
2088
+ log.warning("Failed to get git status before merge: %s", status_exc)
2089
+
2090
+ log.debug("About to run: git merge --squash %s", head_sha)
2091
+ try:
2092
+ run_cmd(["git", "merge", "--squash", head_sha], cwd=self.workspace)
2093
+ except CommandError as merge_exc:
2094
+ # Enhanced error handling for git merge failures
2095
+ error_details = self._analyze_merge_failure(
2096
+ merge_exc, base_sha, head_sha
2097
+ )
2098
+
2099
+ # Try to provide recovery suggestions
2100
+ recovery_msg = self._suggest_merge_recovery(
2101
+ merge_exc, base_sha, head_sha
2102
+ )
2103
+
2104
+ # Log detailed error information
2105
+ log.exception("Git merge --squash failed: %s", error_details)
2106
+ if recovery_msg:
2107
+ log.exception("Suggested recovery: %s", recovery_msg)
2108
+
2109
+ # Enhanced debugging if verbose mode is enabled
2110
+ from .utils import is_verbose_mode
2111
+
2112
+ if is_verbose_mode():
2113
+ self._debug_merge_failure_context(base_sha, head_sha)
2114
+
2115
+ # Re-raise with enhanced context
2116
+ raise OrchestratorError(
2117
+ f"Failed to merge PR commits: {error_details}"
2118
+ + (
2119
+ f"\nSuggested recovery: {recovery_msg}"
2120
+ if recovery_msg
2121
+ else ""
2122
+ )
2123
+ ) from merge_exc
2035
2124
 
2036
2125
  def _collect_log_lines() -> list[str]:
2037
2126
  body = run_cmd(
@@ -4260,6 +4349,173 @@ class Orchestrator:
4260
4349
  except Exception as exc:
4261
4350
  log.debug("File validation failed (non-critical): %s", exc)
4262
4351
 
4352
+ def _analyze_merge_failure(
4353
+ self, merge_exc: CommandError, base_sha: str, head_sha: str
4354
+ ) -> str:
4355
+ """Analyze git merge failure and provide detailed error information."""
4356
+ error_parts = []
4357
+
4358
+ # Include basic command info
4359
+ if merge_exc.cmd:
4360
+ error_parts.append(f"Command: {' '.join(merge_exc.cmd)}")
4361
+ if merge_exc.returncode is not None:
4362
+ error_parts.append(f"Exit code: {merge_exc.returncode}")
4363
+
4364
+ # Analyze stderr for common patterns
4365
+ stderr = merge_exc.stderr or ""
4366
+ if "conflict" in stderr.lower():
4367
+ error_parts.append("Merge conflicts detected")
4368
+ if "abort" in stderr.lower():
4369
+ error_parts.append("Merge was aborted")
4370
+ if "fatal" in stderr.lower():
4371
+ error_parts.append("Fatal git error occurred")
4372
+
4373
+ # Include actual git output
4374
+ if merge_exc.stdout and merge_exc.stdout.strip():
4375
+ error_parts.append(f"Git output: {merge_exc.stdout.strip()}")
4376
+ if stderr and stderr.strip():
4377
+ error_parts.append(f"Git error: {stderr.strip()}")
4378
+
4379
+ return (
4380
+ "; ".join(error_parts) if error_parts else "Unknown merge failure"
4381
+ )
4382
+
4383
+ def _suggest_merge_recovery(
4384
+ self, merge_exc: CommandError, base_sha: str, head_sha: str
4385
+ ) -> str:
4386
+ """Suggest recovery actions based on merge failure analysis."""
4387
+ stderr = (merge_exc.stderr or "").lower()
4388
+
4389
+ if "conflict" in stderr:
4390
+ return "Check for merge conflicts in the PR files and resolve them"
4391
+ elif "fatal: refusing to merge unrelated histories" in stderr:
4392
+ return (
4393
+ "The branches have unrelated histories - check if the PR "
4394
+ "branch is based on the correct target"
4395
+ )
4396
+ elif "nothing to commit" in stderr:
4397
+ return (
4398
+ "No changes to merge - the PR may already be merged or have "
4399
+ "no differences"
4400
+ )
4401
+ elif "abort" in stderr:
4402
+ return (
4403
+ "Previous merge operation may have been interrupted - check "
4404
+ "repository state"
4405
+ )
4406
+
4407
+ # Try to provide generic guidance
4408
+ try:
4409
+ # Check if commits exist between base and head
4410
+ commits_cmd = ["git", "rev-list", f"{base_sha}..{head_sha}"]
4411
+ commits_result = run_cmd(
4412
+ commits_cmd, cwd=self.workspace, check=False
4413
+ )
4414
+ if (
4415
+ commits_result.returncode == 0
4416
+ and not commits_result.stdout.strip()
4417
+ ):
4418
+ return (
4419
+ "No commits found between base and head - PR may be empty "
4420
+ "or already merged"
4421
+ )
4422
+ except Exception as e:
4423
+ log.debug(
4424
+ "Failed to check commit range for recovery suggestion: %s", e
4425
+ )
4426
+
4427
+ return (
4428
+ "Review git repository state and ensure PR branch is properly "
4429
+ "synchronized with target"
4430
+ )
4431
+
4432
+ def _debug_merge_failure_context(
4433
+ self, base_sha: str, head_sha: str
4434
+ ) -> None:
4435
+ """Provide extensive debugging context for merge failures when verbose mode is enabled.""" # noqa: E501
4436
+ log.error("=== VERBOSE MODE: Extended merge failure analysis ===")
4437
+
4438
+ try:
4439
+ # Show detailed git log between base and head
4440
+ log_result = run_cmd(
4441
+ [
4442
+ "git",
4443
+ "log",
4444
+ "--oneline",
4445
+ "--graph",
4446
+ f"{base_sha}..{head_sha}",
4447
+ ],
4448
+ cwd=self.workspace,
4449
+ check=False,
4450
+ )
4451
+ if log_result.returncode == 0:
4452
+ log.error("Commits to be merged:\n%s", log_result.stdout)
4453
+ else:
4454
+ log.error("Failed to get commit log: %s", log_result.stderr)
4455
+
4456
+ # Show file differences
4457
+ diff_result = run_cmd(
4458
+ ["git", "diff", "--name-status", base_sha, head_sha],
4459
+ cwd=self.workspace,
4460
+ check=False,
4461
+ )
4462
+ if diff_result.returncode == 0:
4463
+ log.error(
4464
+ "Files changed between base and head:\n%s",
4465
+ diff_result.stdout,
4466
+ )
4467
+
4468
+ # Show merge-base information
4469
+ merge_base_result = run_cmd(
4470
+ ["git", "merge-base", "--is-ancestor", base_sha, head_sha],
4471
+ cwd=self.workspace,
4472
+ check=False,
4473
+ )
4474
+ if merge_base_result.returncode == 0:
4475
+ log.error(
4476
+ "Base SHA %s is an ancestor of head SHA %s",
4477
+ base_sha[:8],
4478
+ head_sha[:8],
4479
+ )
4480
+ else:
4481
+ log.error(
4482
+ "Base SHA %s is NOT an ancestor of head SHA %s",
4483
+ base_sha[:8],
4484
+ head_sha[:8],
4485
+ )
4486
+
4487
+ # Show current repository state
4488
+ status_result = run_cmd(
4489
+ ["git", "status", "--porcelain"],
4490
+ cwd=self.workspace,
4491
+ check=False,
4492
+ )
4493
+ if status_result.stdout.strip():
4494
+ log.error(
4495
+ "Repository has uncommitted changes:\n%s",
4496
+ status_result.stdout,
4497
+ )
4498
+
4499
+ # Show current branch and HEAD
4500
+ branch_result = run_cmd(
4501
+ ["git", "branch", "--show-current"],
4502
+ cwd=self.workspace,
4503
+ check=False,
4504
+ )
4505
+ if branch_result.returncode == 0:
4506
+ log.error("Current branch: %s", branch_result.stdout.strip())
4507
+
4508
+ head_result = run_cmd(
4509
+ ["git", "rev-parse", "HEAD"], cwd=self.workspace, check=False
4510
+ )
4511
+ if head_result.returncode == 0:
4512
+ log.error("Current HEAD: %s", head_result.stdout.strip())
4513
+
4514
+ except Exception:
4515
+ log.exception("Failed to gather debug context")
4516
+
4517
+ log.error("=== End verbose merge failure analysis ===")
4518
+
4263
4519
 
4264
4520
  # ---------------------
4265
4521
  # Utility functions
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github2gerrit
3
- Version: 0.1.15
3
+ Version: 0.1.16
4
4
  Summary: Submit a GitHub pull request to a Gerrit repository.
5
5
  Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit
6
6
  Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit
@@ -1,8 +1,8 @@
1
1
  github2gerrit/__init__.py,sha256=N1Vj1HJ28LKCJLAynQdm5jFGQQAz9YSMzZhEfvbBgow,886
2
- github2gerrit/cli.py,sha256=t0jR0bN_EGtqzSguFyWdCzvCXvBBHQZQhqBddUnLHyk,65769
2
+ github2gerrit/cli.py,sha256=edQGODSo0A1RfvO0C7zAjWwIZCnt4RT67BnJwgL0MAY,67843
3
3
  github2gerrit/commit_normalization.py,sha256=5HqUJ7p3WQzUYNTkganIsu82aW1wZrh7J7ivQvdCYXw,17033
4
4
  github2gerrit/config.py,sha256=khuXAfmOc2wqO4r1Cpp8PYk04AO-JpmnJvSWQZ3fdL8,22315
5
- github2gerrit/core.py,sha256=ScUS29VeyjGB6Ha3omnk495eLsHXBsz-ROmAbVqVQBo,162943
5
+ github2gerrit/core.py,sha256=hf7XDZ83PD_dYu79u-9u_pboDqu_iL9TpdIaa64_q-0,172360
6
6
  github2gerrit/duplicate_detection.py,sha256=HSL1IpyfPk0yLkzCKA8mWadYB3qENR6Ar3AfiH59lCI,28766
7
7
  github2gerrit/external_api.py,sha256=9483kkgIs1ECOl_f0lcGb8GrJQF9IfYmWfBQwUJT9hk,18480
8
8
  github2gerrit/gerrit_query.py,sha256=7AR9UEwZxtTb9V-l17UDdtWvlvROlnIeJVMa57ilf6s,8343
@@ -24,8 +24,8 @@ github2gerrit/trailers.py,sha256=9w0vIxPNBNQp56sIy-MF62d22Rm6vY-msh9ao1lX0rQ,838
24
24
  github2gerrit/utils.py,sha256=1CKTsQo_FO3eyVjzNUT3XyFnyObIxyEFLeSmVKzavVo,3397
25
25
  github2gerrit/orchestrator/__init__.py,sha256=HAEcdCAHOFr8LsdIwAdcIcFZn_ayMbX9rdVUULp8410,864
26
26
  github2gerrit/orchestrator/reconciliation.py,sha256=qLhmjnIblSa_PVmBblWJ1gSvJ6Gto2AIVOVdYvT6zbk,18364
27
- github2gerrit-0.1.15.dist-info/METADATA,sha256=1q7uAI2-erRFQNi23SQq64aYQVUa6Khj426xUKqWhK0,33833
28
- github2gerrit-0.1.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- github2gerrit-0.1.15.dist-info/entry_points.txt,sha256=MxN2_liIKo3-xJwtAulAeS5GcOS6JS96nvwOQIkP3W8,56
30
- github2gerrit-0.1.15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
31
- github2gerrit-0.1.15.dist-info/RECORD,,
27
+ github2gerrit-0.1.16.dist-info/METADATA,sha256=02ry4GJBvmh4lqF27joxLA_s9be7UqOv95-89plq_fQ,33833
28
+ github2gerrit-0.1.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ github2gerrit-0.1.16.dist-info/entry_points.txt,sha256=MxN2_liIKo3-xJwtAulAeS5GcOS6JS96nvwOQIkP3W8,56
30
+ github2gerrit-0.1.16.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
31
+ github2gerrit-0.1.16.dist-info/RECORD,,