Coverage for src/rhiza_tools/commands/bump/git.py: 100%
48 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-30 13:37 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-30 13:37 +0000
1"""Git operations specific to the bump command.
3This module wraps the git plumbing that ``bump_command`` uses: switching to a
4target branch before bumping, pushing the resulting commit to the remote, and
5restoring the original branch afterwards. Keeping these side-effecting git calls
6separate from the pure orchestration in ``bump.py`` makes each piece easier to
7test in isolation.
9All symbols defined here are re-exported by ``bump.py`` so the public import
10surface is unchanged.
11"""
13from __future__ import annotations
15import questionary as qs
16import typer
17from loguru import logger
19from rhiza_tools import console
20from rhiza_tools.commands._shared import (
21 COOL_STYLE,
22 NON_INTERACTIVE_ERRORS,
23 run_git_command,
24)
27def _handle_branch_checkout(branch: str | None, dry_run: bool) -> str | None:
28 """Handle branch checkout if specified.
30 Args:
31 branch: Branch to checkout, or None.
32 dry_run: If True, only simulate checkout.
34 Returns:
35 Original branch name if we switched, None otherwise.
37 Raises:
38 typer.Exit: If checkout fails.
39 """
40 if not branch:
41 return None
43 # Get current branch
44 result = run_git_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], check=False)
45 if result.returncode != 0:
46 return None
48 current_branch = result.stdout.strip()
49 if current_branch == branch:
50 return None
52 console.info(f"Switching from {current_branch} to {branch}")
53 if not dry_run:
54 result = run_git_command(["git", "checkout", branch], check=False)
55 if result.returncode != 0:
56 console.error(f"Failed to checkout branch {branch}: {result.stderr}")
57 console.error(f"Ensure the branch '{branch}' exists: git branch -a")
58 raise typer.Exit(code=1)
59 else:
60 console.info(f"[DRY-RUN] Would checkout branch {branch}")
62 return current_branch
65def _handle_push_to_remote(version: str | None) -> None:
66 """Handle pushing changes to remote.
68 Args:
69 version: Version argument (None means interactive mode).
71 Raises:
72 typer.Exit: If push fails.
73 """
74 # Interactive prompt if not in non-interactive mode and version was not specified
75 if not version:
76 try:
77 if not qs.confirm("Push changes to remote?", default=False, style=COOL_STYLE).ask():
78 console.info("Push cancelled by user")
79 return
80 except NON_INTERACTIVE_ERRORS:
81 # In testing or non-interactive environment, do not proceed with push
82 console.info("Push cancelled - non-interactive environment detected")
83 logger.debug("Running in non-interactive environment, skipping push")
84 return
86 console.info("Pushing changes to remote...")
87 result = run_git_command(["git", "push"], check=False)
88 if result.returncode == 0:
89 console.success("Changes pushed to remote successfully!")
90 else:
91 console.error(f"Failed to push changes: {result.stderr}")
92 console.error("The version bump has been applied locally but could not be pushed.")
93 console.error("To recover:")
94 console.error(" Push manually: git push")
95 console.error(" Or undo bump: git reset --hard HEAD~1")
96 raise typer.Exit(code=1)
99def _restore_original_branch(original_branch: str | None, dry_run: bool) -> None:
100 """Restore original branch if we switched.
102 Args:
103 original_branch: Original branch to restore, or None.
104 dry_run: If True, don't actually restore (since we didn't actually switch).
105 """
106 if original_branch and not dry_run:
107 console.info(f"Returning to original branch {original_branch}")
108 run_git_command(["git", "checkout", original_branch], check=False)