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:
Loads configuration
Instantiates plugins (version sources and spec managers)
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
Implements file locking to prevent concurrent runs
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.YbranchesDetermines 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 branchfinalize_semver_update(): Create branches and perform mergeshandle_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 APICratesIOVersionSource: Parses crates.io sparse index (NDJSON)PyPIVersionSource: Queries PyPI JSON API
Adding a new version source:
Create
src/dist_git_manager/version_sources/new_source.pyInherit from
VersionSourceImplement
get_latest_version()Register in
DistGitManager._get_version_source()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 specsRust2RpmSpecManager: Generates specs from crates.io metadataPyP2SpecSpecManager: Generates specs from PyPI metadata
Adding a new spec manager:
Create
src/dist_git_manager/spec_managers/new_manager.pyInherit from
SpecManagerImplement required methods
Register in
DistGitManager._get_spec_manager()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:
Config-driven: Load from YAML file with
Config.from_file()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 Rustpython3dist({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:
responseslibrary for HTTP mockingunittest.mockfor subprocess mockingtempfilefor 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 exceptionConfigError: Configuration validation failuresGitError: Git operation failuresVersionSourceError: Version fetching failuresSpecManagerError: Spec update failuresBranchManagementError: Branch operation failuresDependencyResolutionError: Dependency resolution failuresLockError: 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
CLI parses arguments or loads YAML config
Config validates and builds configuration object
DistGitManager acquires lock and processes each project:
Instantiates VersionSource plugin
Fetches latest version
GitOperations clones/updates repository
Instantiates SpecManager plugin
Parses current version from spec
If update needed:
BranchManager determines target branch
SpecManager updates spec file
GitOperations commits changes
BranchManager creates/merges branches
GitOperations pushes if enabled
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.