Architecture

dist-git-manager uses a plugin-based architecture that separates concerns and allows easy extension.

Overview

┌─────────────────────────────────────────────────────────┐
│                    CLI Interface                         │
│   (config-driven OR CLI-only, single repo mode)         │
└─────────────────────────────────────────────────────────┘
                           │
┌─────────────────────────────────────────────────────────┐
│                 DistGitManager                          │
│         (orchestrates entire workflow)                  │
└─────────────────────────────────────────────────────────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
   ┌────────┴────────┐   ┌┴──────┐   ┌──┴───────┐
   │ VersionSource   │   │ Spec  │   │   Git    │
   │   (plugin)      │   │Manager│   │ Manager  │
   └─────────────────┘   └───────┘   └──────────┘

Core Components

DistGitManager

Location: src/dist_git_manager/core/manager.py

The main orchestrator that:

  1. Loads configuration

  2. Instantiates plugins (version sources and spec managers)

  3. For each project:

    • Fetches latest version from version source

    • Clones/updates git repository

    • Determines current version from spec file

    • If new version available:

      • Determines target branch (via BranchManager)

      • Updates spec file (via SpecManager)

      • Creates/updates release branches hierarchically

      • Commits and optionally pushes changes

  4. Implements file locking to prevent concurrent runs

  5. Continues processing on individual project failures

GitOperations

Location: src/dist_git_manager/core/git_ops.py

Wrapper around git commands with:

  • Dry-run support (logs commands without executing)

  • Clone, fetch, branch, checkout operations

  • Commit with custom author/email

  • Push with auto-push option

  • Fast-forward merging

BranchManager

Location: src/dist_git_manager/core/branch_mgr.py

Handles hierarchical branch management:

For SemVer releases:

  • Detects release series (X.Y) from version

  • Creates release-X.Y branches

  • Determines superior branches that should fast-forward merge

  • Performs hierarchical merges (release-1.20 → release-1 → main)

For rolling releases:

  • Simply checks out default branch

Key methods:

  • handle_semver_update(): Determine and checkout target branch

  • finalize_semver_update(): Create branches and perform merges

  • handle_rolling_update(): Checkout default branch

VersionParser

Location: src/dist_git_manager/core/version_parser.py

Parses and compares versions:

  • Supports SemVer (X.Y.Z)

  • Supports PEP 440 (Python versions)

  • Supports Cargo versions (Rust)

  • Extracts release tracks (major.minor)

  • Compares versions to determine if newer

Plugin Architecture

Version Sources

Base class: src/dist_git_manager/version_sources/base.py

Abstract interface:

class VersionSource(ABC):
    @abstractmethod
    def get_latest_version(
        self, identifier: str, release_type: str = "semver"
    ) -> Optional[str]:
        """Get latest version for a package."""
        pass

Implementations:

  • AnityaVersionSource: Queries Anitya Release Monitoring API

  • CratesIOVersionSource: Parses crates.io sparse index (NDJSON)

  • PyPIVersionSource: Queries PyPI JSON API

Adding a new version source:

  1. Create src/dist_git_manager/version_sources/new_source.py

  2. Inherit from VersionSource

  3. Implement get_latest_version()

  4. Register in DistGitManager._get_version_source()

  5. Add to CLI choices and validation

Spec Managers

Base class: src/dist_git_manager/spec_managers/base.py

Abstract interface:

class SpecManager(ABC):
    @abstractmethod
    def update_spec(
        self, repo_path: Path, branch: str,
        package_name: str, version: str, dry_run: bool
    ) -> None:
        """Update spec file to new version."""
        pass

    @abstractmethod
    def parse_version(self, spec_path: Path) -> Optional[str]:
        """Extract current version from spec file."""
        pass

    @abstractmethod
    def find_spec_file(
        self, repo_path: Path, package_name: str
    ) -> Optional[Path]:
        """Find spec file in repository."""
        pass

Implementations:

  • PackitSpecManager: Uses Packit to update existing specs

  • Rust2RpmSpecManager: Generates specs from crates.io metadata

  • PyP2SpecSpecManager: Generates specs from PyPI metadata

Adding a new spec manager:

  1. Create src/dist_git_manager/spec_managers/new_manager.py

  2. Inherit from SpecManager

  3. Implement required methods

  4. Register in DistGitManager._get_spec_manager()

  5. Add to CLI choices and validation

Configuration System

Location: src/dist_git_manager/config.py

The Config class:

  • Loads YAML configuration files

  • Builds configuration from CLI arguments

  • Validates configuration (required fields, valid values)

  • Supports global plugin defaults with per-project overrides

  • Provides accessor methods (get_project_version_source(), etc.)

Two modes:

  1. Config-driven: Load from YAML file with Config.from_file()

  2. CLI-only: Build from args with Config.from_cli_args()

CLI can override YAML values when both provided.

Optional Components

DNFPackageChecker

Location: src/dist_git_manager/core/dnf_checker.py

Checks if packages already exist in DNF repositories:

  • Supports different provider templates:

    • crate({name}) for Rust

    • python3dist({name}) for Python

    • {name} for general packages

  • Implements caching for performance

  • Gracefully degrades if DNF unavailable

DependencyResolver

Location: src/dist_git_manager/dependency/resolver.py

Framework for recursive dependency resolution:

  • Abstract base class for language-specific resolvers

  • Cycle detection

  • Integration with DNFPackageChecker to skip existing packages

  • CratesDependencyResolver implementation for Rust

Testing Architecture

Unit Tests

Located in tests/unit/:

  • Mock external dependencies (HTTP, subprocess, filesystem)

  • Test components in isolation

  • Fast, offline, high coverage

Fixtures:

  • responses library for HTTP mocking

  • unittest.mock for subprocess mocking

  • tempfile for filesystem isolation

Integration Tests

Located in tests/integration/:

  • Use real git repos in temp directories

  • Mock HTTP APIs and tool subprocess calls

  • Test full workflows end-to-end

  • Verify branch states and file changes

Error Handling

Custom exception hierarchy:

  • DistGitManagerError: Base exception

  • ConfigError: Configuration validation failures

  • GitError: Git operation failures

  • VersionSourceError: Version fetching failures

  • SpecManagerError: Spec update failures

  • BranchManagementError: Branch operation failures

  • DependencyResolutionError: Dependency resolution failures

  • LockError: File locking failures

Logging

Location: src/dist_git_manager/logger.py

  • Centralized logger setup

  • Supports verbose mode

  • Structured logging for different components

  • Debug level includes subprocess commands

Data Flow

  1. CLI parses arguments or loads YAML config

  2. Config validates and builds configuration object

  3. DistGitManager acquires lock and processes each project:

    1. Instantiates VersionSource plugin

    2. Fetches latest version

    3. GitOperations clones/updates repository

    4. Instantiates SpecManager plugin

    5. Parses current version from spec

    6. If update needed:

      • BranchManager determines target branch

      • SpecManager updates spec file

      • GitOperations commits changes

      • BranchManager creates/merges branches

      • GitOperations pushes if enabled

  4. DistGitManager releases lock

Design Principles

Separation of Concerns

Each component has a single, well-defined responsibility.

Plugin Architecture

Easy to add new version sources and spec managers without modifying core code.

Dry-Run Support

All components support dry-run mode for safe testing.

Offline Testing

External dependencies (HTTP, subprocess) are mocked for fast, reliable tests.

Graceful Degradation

Optional features (DNF checking) degrade gracefully when dependencies unavailable.

Configuration Flexibility

Supports both YAML config-driven and CLI-only modes.