Source code for scitex_git._remote

#!/usr/bin/env python3
# File: /home/ywatanabe/proj/scitex-code/src/scitex/git/remote.py

"""
Git remote operations.
"""

from pathlib import Path
from typing import Optional

from logging import getLogger
from ._vendor_sh import sh

from ._constants import EXIT_FAILURE, EXIT_SUCCESS
from ._utils import _in_directory

logger = getLogger(__name__)


[docs] def get_remote_url( repo_path: Path, remote_name: str = "origin", verbose: bool = False ) -> Optional[str]: """ Get remote URL for a git repository. Parameters ---------- repo_path : Path Git repository path remote_name : str Remote name (default: origin) verbose : bool Enable verbose output Returns ------- Optional[str] Remote URL if found, None otherwise Notes ----- Returns None if remote doesn't exist or repo is not a git repository. Check stderr via sh() directly if detailed error info is needed. """ if not (repo_path / ".git").exists(): logger.warning(f"Not a git repository: {repo_path}") return None with _in_directory(repo_path): result = sh( ["git", "config", "--get", f"remote.{remote_name}.url"], verbose=verbose, return_as="dict", ) if result["success"]: return result["stdout"].strip() logger.debug(f"Remote '{remote_name}' not found in {repo_path}") return None
def _validate_git_url(url: str) -> bool: """ Validate git URL format. Parameters ---------- url : str Git URL to validate Returns ------- bool True if valid, False otherwise """ valid_hosts = ("github.com", "gitlab.com", "bitbucket.org") if url.startswith("https://"): for host in valid_hosts: if f"https://{host}/" in url: return True elif url.startswith("git@"): for host in valid_hosts: if url.startswith(f"git@{host}:"): return True return False def _normalize_git_url(url: str) -> str: """ Normalize git URL for comparison. Handles HTTPS and SSH formats for GitHub, GitLab, Bitbucket. Parameters ---------- url : str Git URL to normalize Returns ------- str Normalized URL """ url = url.rstrip("/") if url.startswith("git@"): url = url.replace(":", "/", 1).replace("git@", "https://") if url.endswith(".git"): url = url[:-4] return url
[docs] def is_cloned_from( repo_path: Path, expected_url: str, remote_name: str = "origin" ) -> bool: """ Check if directory is a git repository cloned from specific URL. Handles both HTTPS and SSH URL formats. Parameters ---------- repo_path : Path Directory to check expected_url : str Expected remote URL remote_name : str Remote name to check (default: origin) Returns ------- bool True if directory is cloned from expected URL, False otherwise """ if not (repo_path / ".git").exists(): return False actual_url = get_remote_url(repo_path, remote_name) if actual_url is None: return False return _normalize_git_url(actual_url) == _normalize_git_url(expected_url)
[docs] def ls_remote( url: str, ref: Optional[str] = None, verbose: bool = False, ) -> Optional[str]: """ Get commit hash of a remote ref via ``git ls-remote``. Parameters ---------- url : str Git repository URL. ref : str, optional Branch name, tag, or ref pattern. If None, returns HEAD. verbose : bool Enable verbose output. Returns ------- Optional[str] Commit SHA-1 hash (40 hex chars), or None on failure. Examples -------- >>> ls_remote("https://github.com/user/repo.git") 'abc123...' >>> ls_remote("https://github.com/user/repo.git", ref="main") 'abc123...' >>> ls_remote("https://github.com/user/repo.git", ref="v1.0.0") 'def456...' """ cmd = ["git", "ls-remote"] if ref is None: cmd.append("--symref") cmd.append(url) if ref is not None: cmd.append(ref) result = sh(cmd, verbose=verbose, return_as="dict") if not result["success"]: logger.debug(f"ls-remote failed for {url}: {result.get('stderr', '')}") return None stdout = result["stdout"].strip() if not stdout: return None # Parse first line: "<hash>\t<ref>" for line in stdout.splitlines(): parts = line.split("\t", 1) if len(parts) == 2 and len(parts[0]) == 40: return parts[0] return None
[docs] def get_head_hash( repo_path: Path, verbose: bool = False, ) -> Optional[str]: """ Get HEAD commit hash of a local git repository. Parameters ---------- repo_path : Path Git repository path (must contain .git/). verbose : bool Enable verbose output. Returns ------- Optional[str] Commit SHA-1 hash, or None if not a git repo. """ if not (repo_path / ".git").exists(): return None with _in_directory(repo_path): result = sh( ["git", "rev-parse", "HEAD"], verbose=verbose, return_as="dict", ) if result["success"]: return result["stdout"].strip() return None
def main(args): if args.action == "get-url": url = get_remote_url(args.repo_path, args.remote_name, args.verbose) if url: print(url) return EXIT_SUCCESS return EXIT_FAILURE elif args.action == "check-origin": if not args.expected_url: logger.error("Expected URL required for check-origin action") return EXIT_FAILURE result = is_cloned_from(args.repo_path, args.expected_url, args.remote_name) print(result) return EXIT_SUCCESS if result else EXIT_FAILURE def parse_args(): """Parse command line arguments.""" import argparse parser = argparse.ArgumentParser() parser.add_argument("--action", choices=["get-url", "check-origin"], required=True) parser.add_argument("--repo-path", type=Path, required=True) parser.add_argument("--expected-url", help="Expected URL for check-origin action") parser.add_argument("--remote-name", default="origin") parser.add_argument("--verbose", action="store_true") return parser.parse_args() def run_session(): """Initialize scitex framework, run main function, and cleanup.""" from ._session import run_with_session run_with_session(parse_args, main) __all__ = [ "get_remote_url", "is_cloned_from", "ls_remote", "get_head_hash", "_validate_git_url", ] if __name__ == "__main__": run_session() # EOF