Files
pkgmgr/pkgmgr/core/git/__init__.py
Kevin Veen-Birkenbach d50891dfe5 Refactor: Restructure pkgmgr into actions/, core/, and cli/ (full module breakup)
This commit introduces a large-scale structural refactor of the pkgmgr
codebase. All functionality has been moved from the previous flat
top-level layout into three clearly separated namespaces:

  • pkgmgr.actions      – high-level operations invoked by the CLI
  • pkgmgr.core         – pure logic, helpers, repository utilities,
                          versioning, git helpers, config IO, and
                          command resolution
  • pkgmgr.cli          – parser, dispatch, context, and command
                          handlers

Key improvements:
  - Moved all “branch”, “release”, “changelog”, repo-management
    actions, installer pipelines, and proxy execution logic into
    pkgmgr.actions.<domain>.
  - Reworked installer structure under
        pkgmgr.actions.repository.install.installers
    including OS-package installers, Nix, Python, and Makefile.
  - Consolidated all low-level functionality under pkgmgr.core:
        • git helpers → core/git
        • config load/save → core/config
        • repository helpers → core/repository
        • versioning & semver → core/version
        • command helpers (alias, resolve, run, ink) → core/command
  - Replaced pkgmgr.cli_core with pkgmgr.cli and updated all imports.
  - Added minimal __init__.py files for clean package exposure.
  - Updated all E2E, integration, and unit tests with new module paths.
  - Fixed patch targets so mocks point to the new structure.
  - Ensured backward compatibility at the CLI boundary (pkgmgr entry point unchanged).

This refactor produces a cleaner, layered architecture:
  - `core` = logic
  - `actions` = orchestrated behaviour
  - `cli` = user interface

Reference: ChatGPT-assisted refactor discussion
https://chatgpt.com/share/6938221c-e24c-800f-8317-7732cedf39b9
2025-12-09 14:20:19 +01:00

93 lines
2.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Lightweight helper functions around Git commands.
These helpers are intentionally small wrappers so that higher-level
logic (release, version, changelog) does not have to deal with the
details of subprocess handling.
"""
from __future__ import annotations
import subprocess
from typing import List, Optional
class GitError(RuntimeError):
"""Raised when a Git command fails in an unexpected way."""
def run_git(args: List[str], cwd: str = ".") -> str:
"""
Run a Git command and return its stdout as a stripped string.
Raises GitError if the command fails.
"""
cmd = ["git"] + args
try:
result = subprocess.run(
cmd,
cwd=cwd,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
except subprocess.CalledProcessError as exc:
raise GitError(
f"Git command failed in {cwd!r}: {' '.join(cmd)}\n"
f"Exit code: {exc.returncode}\n"
f"STDOUT:\n{exc.stdout}\n"
f"STDERR:\n{exc.stderr}"
) from exc
return result.stdout.strip()
def get_tags(cwd: str = ".") -> List[str]:
"""
Return a list of all tags in the repository in `cwd`.
If there are no tags, an empty list is returned.
"""
try:
output = run_git(["tag"], cwd=cwd)
except GitError as exc:
# If the repo has no tags or is not a git repo, surface a clear error.
# You can decide later if you want to treat this differently.
if "not a git repository" in str(exc):
raise
# No tags: stdout may just be empty; treat this as empty list.
return []
if not output:
return []
return [line.strip() for line in output.splitlines() if line.strip()]
def get_head_commit(cwd: str = ".") -> Optional[str]:
"""
Return the current HEAD commit hash, or None if it cannot be determined.
"""
try:
output = run_git(["rev-parse", "HEAD"], cwd=cwd)
except GitError:
return None
return output or None
def get_current_branch(cwd: str = ".") -> Optional[str]:
"""
Return the current branch name, or None if it cannot be determined.
Note: In detached HEAD state this will return 'HEAD'.
"""
try:
output = run_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
except GitError:
return None
return output or None