Skip to content

Index

core

Core logic — subprocess runners and semver computation.

GitIdentity

Bases: BaseModel

A git author identity.

Source code in packages/axm-git/src/axm_git/core/identity.py
Python
class GitIdentity(BaseModel):  # type: ignore[explicit-any]  # pydantic BaseModel exposes Any in its API
    """A git author identity."""

    name: str
    email: str

GitProfileConfig

Bases: BaseModel

Full git-profiles.toml configuration.

Source code in packages/axm-git/src/axm_git/core/identity.py
Python
class GitProfileConfig(BaseModel):  # type: ignore[explicit-any]  # pydantic BaseModel exposes Any in its API
    """Full git-profiles.toml configuration."""

    default: GitIdentity
    profiles: dict[str, GitIdentity] = {}
    schedule: Schedule = Schedule()
    workspace_paths: list[Path] = []
    timezone: str = "Europe/Paris"

author_args(identity)

Build --author arguments for a git command.

Source code in packages/axm-git/src/axm_git/core/identity.py
Python
def author_args(identity: GitIdentity | None) -> list[str]:
    """Build ``--author`` arguments for a git command."""
    if identity is None:
        return []
    return ["--author", f"{identity.name} <{identity.email}>"]

load_config(config_path=None)

Load and validate a git-profiles TOML config file.

File-absent returns None silently. File-present-but-malformed returns None and emits a WARNING referencing path and the exception class. After successful parse, also warns when schedule.rules is non-empty but workspace_paths is empty (governance config is configured but cannot apply).

Source code in packages/axm-git/src/axm_git/core/identity.py
Python
def load_config(config_path: Path | None = None) -> GitProfileConfig | None:
    """Load and validate a git-profiles TOML config file.

    File-absent returns ``None`` silently. File-present-but-malformed
    returns ``None`` and emits a ``WARNING`` referencing *path* and the
    exception class. After successful parse, also warns when
    ``schedule.rules`` is non-empty but ``workspace_paths`` is empty
    (governance config is configured but cannot apply).
    """
    path = config_path or _DEFAULT_CONFIG_PATH
    try:
        data = path.read_bytes()
    except FileNotFoundError:
        return None
    except OSError as exc:
        logger.warning(
            "Cannot read git-profiles config at %s: %s", path, exc.__class__.__name__
        )
        return None
    if not data:
        return None
    try:
        parsed: dict[str, object] = tomllib.loads(data.decode())
        config = GitProfileConfig.model_validate(parsed)
    except (tomllib.TOMLDecodeError, ValueError, KeyError) as exc:
        logger.warning(
            "Invalid git-profiles config at %s: %s", path, exc.__class__.__name__
        )
        return None
    if config.schedule.rules and not config.workspace_paths:
        logger.warning(
            "git-profiles config at %s defines schedule.rules but "
            "workspace_paths is empty — schedule is inert",
            path,
        )
    return config

resolve_identity(workspace_path, *, now=None, profile_override=None, config_path=None)

Resolve the git identity for the given workspace.

Returns None when no config is available or an unknown profile is requested via profile_override.

Source code in packages/axm-git/src/axm_git/core/identity.py
Python
def resolve_identity(
    workspace_path: Path,
    *,
    now: datetime | None = None,
    profile_override: str | None = None,
    config_path: Path | None = None,
) -> GitIdentity | None:
    """Resolve the git identity for the given workspace.

    Returns ``None`` when no config is available or an unknown profile
    is requested via *profile_override*.
    """
    config = load_config(config_path)
    if config is None:
        return None

    override = resolve_by_override(config, profile_override)
    if profile_override is not None:
        return override

    tz = ZoneInfo(config.timezone)
    if now is None:
        effective_now = datetime.now(tz=tz)
    elif now.tzinfo is None:
        effective_now = now
    else:
        effective_now = now.astimezone(tz)
    return resolve_by_schedule(config, workspace_path, effective_now) or config.default