Source code for dbx_python_cli.commands.switch

"""Switch command for switching git branches in repositories."""

import json
import subprocess
from pathlib import Path

import typer

from dbx_python_cli.utils.repo import (
    get_base_dir,
    get_config,
    get_projects_dir,
    get_repo_groups,
    is_flat_mode,
)
from dbx_python_cli.utils.repo import find_all_repos, find_repo_by_name

# Create a Typer app that will act as a single command
app = typer.Typer(
    help="Git branch switching commands",
    no_args_is_help=True,
    invoke_without_command=True,
    context_settings={
        "allow_interspersed_args": False,
        "help_option_names": ["-h", "--help"],
    },
)


[docs] @app.callback() def switch_callback( ctx: typer.Context, repo_name: str = typer.Argument(None, help="Repository name to switch branches in"), branch_name: str = typer.Argument(None, help="Branch name to switch to"), list_repos: bool = typer.Option( False, "--list", "-l", help="Show repository status (cloned vs available)", ), group: str = typer.Option( None, "--group", "-g", help="Switch branches in all repositories in a group", ), project: str = typer.Option( None, "--project", "-p", help="Switch branches in a specific project", ), create: bool = typer.Option( False, "--create", "-c", help="Create the branch if it doesn't exist", ), ): """Switch to a branch in a cloned repository, group of repositories, or project. Usage:: dbx switch <repo_name> <branch_name> dbx switch -g <group_name> <branch_name> dbx switch -p <project_name> <branch_name> Examples:: dbx switch mongo-python-driver PYTHON-5683 # Switch to branch dbx switch mongo-python-driver main # Switch to main dbx switch -c mongo-python-driver feature-123 # Create and switch to new branch dbx switch -g pymongo PYTHON-5683 # Switch all repos in group dbx switch -p myproject feature-branch # Switch project branch """ # Get verbose flag from parent context verbose = ctx.obj.get("verbose", False) if ctx.obj else False try: config = get_config() base_dir = get_base_dir(config) if verbose: typer.echo(f"[verbose] Using base directory: {base_dir}") typer.echo(f"[verbose] Config:\n{json.dumps(config, indent=4)}\n") except Exception as e: typer.echo(f"❌ Error: {e}", err=True) raise typer.Exit(1) # Handle --list flag if list_repos: from dbx_python_cli.utils.repo import list_repos as list_repos_func output = list_repos_func(base_dir, config=config) if output: typer.echo(f"Base directory: {base_dir}\n") typer.echo(output) typer.echo( "\nLegend: ✓ = cloned, ○ = available to clone, ? = cloned but not in config" ) else: typer.echo(f"Base directory: {base_dir}\n") typer.echo("No repositories found.") typer.echo("\nClone repositories using: dbx clone -g <group>") return # Handle group option if group: # When using -g, the first positional arg is the branch name actual_branch_name = repo_name if repo_name else branch_name if not actual_branch_name: typer.echo("❌ Error: Branch name is required", err=True) typer.echo("\nUsage: dbx switch -g <group> <branch_name>") raise typer.Exit(1) groups = get_repo_groups(config) if group not in groups: typer.echo( f"❌ Error: Group '{group}' not found in configuration.", err=True ) typer.echo(f"Available groups: {', '.join(groups.keys())}", err=True) raise typer.Exit(1) # Find all repos in the group all_repos = find_all_repos(base_dir, config) group_repos = [r for r in all_repos if r["group"] == group] if not group_repos: typer.echo( f"❌ Error: No repositories found for group '{group}'.", err=True ) typer.echo(f"\nClone repositories using: dbx clone -g {group}") raise typer.Exit(1) typer.echo( f"Switching to branch '{actual_branch_name}' in {len(group_repos)} repository(ies) in group '{group}':\n" ) for repo_info in group_repos: _run_git_switch( repo_info["path"], repo_info["name"], actual_branch_name, create, verbose, ) return # Handle project option if project: # When using -p, the first positional arg is the branch name actual_branch_name = repo_name if repo_name else branch_name if not actual_branch_name: typer.echo("❌ Error: Branch name is required", err=True) typer.echo("\nUsage: dbx switch -p <project> <branch_name>") raise typer.Exit(1) projects_dir = get_projects_dir(base_dir, is_flat_mode(config)) project_path = projects_dir / project if not project_path.exists(): typer.echo( f"❌ Error: Project '{project}' not found at {project_path}", err=True ) raise typer.Exit(1) _run_git_switch(project_path, project, actual_branch_name, create, verbose) return # Require repo_name and branch_name if not listing, not using group, and not using project if not repo_name or not branch_name: typer.echo("❌ Error: Repository name and branch name are required", err=True) typer.echo("\nUsage: dbx switch <repo_name> <branch_name>") typer.echo(" or: dbx switch -g <group> <branch_name>") typer.echo(" or: dbx switch -p <project> <branch_name>") typer.echo(" or: dbx switch --list") raise typer.Exit(1) # Find the repository repo = find_repo_by_name(repo_name, base_dir, config) if not repo: typer.echo(f"❌ Error: Repository '{repo_name}' not found", err=True) typer.echo("\nRun 'dbx switch --list' to see available repositories") raise typer.Exit(1) repo_path = Path(repo["path"]) _run_git_switch(repo_path, repo_name, branch_name, create, verbose)
def _run_git_switch( repo_path: Path, name: str, branch_name: str, create: bool = False, verbose: bool = False, ): """Switch to a branch in a repository or project.""" # Check if it's a git repository if not (repo_path / ".git").exists(): typer.echo(f"⚠️ {name}: Not a git repository (skipping)", err=True) return # Build git switch command if create: git_cmd = ["git", "switch", "-c", branch_name] typer.echo(f"🔀 {name}: Creating and switching to branch '{branch_name}'") else: git_cmd = ["git", "switch", branch_name] typer.echo(f"🔀 {name}: Switching to branch '{branch_name}'") if verbose: typer.echo(f"[verbose] Running command: {' '.join(git_cmd)}") typer.echo(f"[verbose] Working directory: {repo_path}\n") # Run git switch in the repository result = subprocess.run( git_cmd, cwd=str(repo_path), check=False, capture_output=True, text=True, ) if result.returncode == 0: typer.echo(f"✅ {name}: Successfully switched to '{branch_name}'") # Check if the branch is tracking a remote branch tracking_result = subprocess.run( ["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=str(repo_path), check=False, capture_output=True, text=True, ) if tracking_result.returncode == 0 and tracking_result.stdout.strip(): tracking_branch = tracking_result.stdout.strip() typer.echo(f" 📍 Tracking: {tracking_branch}") elif verbose: typer.echo(" ℹ️ Not tracking any remote branch") else: typer.echo(f"❌ {name}: Failed to switch to '{branch_name}'", err=True) if result.stderr: # Show the error message from git error_msg = result.stderr.strip() typer.echo(f" {error_msg}", err=True)