Skip to main content

Overview

The VersionController class provides a type-safe wrapper around Git for version control operations. It offers methods for repository initialization, staging, committing, pushing, tagging, and configuration.

Location

pyrig.rig.tools.version_controller.VersionController (rig/tools/version_controller.py:24)

Quick Start

from pyrig.rig.tools.version_controller import VersionController

# Initialize repository
VersionController.I.init_args().run()

# Stage all changes
VersionController.I.add_all_args().run()

# Commit with message
VersionController.I.commit_no_verify_args(msg="Initial commit").run()

# Push to remote
VersionController.I.push_args().run()

Repository Setup

Initialize Repository

def init_args(self, *args: str) -> Args:
    """Construct git init arguments.
    
    Returns:
        Args for 'git init'.
    """
    return self.args("init", *args)

# Usage
VersionController.I.init_args().run()
VersionController.I.init_args("--initial-branch=main").run()

Configuration Methods

def default_branch(self) -> str:
    """Get the default branch name.
    
    Returns:
        Default branch name ('main').
    """
    return "main"

def ignore_filename(self) -> str:
    """Get the filename for .gitignore.
    
    Returns:
        Filename for .gitignore ('.gitignore').
    """
    return ".gitignore"

def default_ruleset_name(self) -> str:
    """Get the default branch protection ruleset name.
    
    Returns:
        Default ruleset name ('main-protection').
    """
    return f"{self.default_branch()}-protection"

Staging Changes

def add_args(self, *args: str) -> Args:
    """Construct git add arguments.
    
    Args:
        *args: Files or paths to add.
    
    Returns:
        Args for 'git add'.
    """
    return self.args("add", *args)

# Usage
VersionController.I.add_args("README.md").run()
VersionController.I.add_args("src/", "tests/").run()

Committing Changes

def commit_args(self, *args: str) -> Args:
    """Construct git commit arguments.
    
    Returns:
        Args for 'git commit'.
    """
    return self.args("commit", *args)

# Usage
VersionController.I.commit_args("-m", "Add feature").run()

Remote Operations

Pushing Changes

def push_args(self, *args: str) -> Args:
    """Construct git push arguments.
    
    Returns:
        Args for 'git push'.
    """
    return self.args("push", *args)

# Usage
VersionController.I.push_args().run()
VersionController.I.push_args("--force-with-lease").run()

Tagging

def tag_args(self, *args: str, tag: str) -> Args:
    """Construct git tag arguments.
    
    Args:
        tag: Tag name (keyword-only).
    
    Returns:
        Args for 'git tag <tag>'.
    """
    return self.args("tag", tag, *args)

# Usage
VersionController.I.tag_args(tag="v1.0.0").run()
VersionController.I.tag_args(tag="v1.0.0", "-a", "-m", "Release 1.0.0").run()

# Create and push a tag
VersionController.I.tag_args(tag="v1.0.0").run()
VersionController.I.push_origin_tag_args(tag="v1.0.0").run()

Git Configuration

Global Configuration

def config_global_user_name_args(self, *args: str, name: str) -> Args:
    """Construct git config arguments for global user name.
    
    Args:
        name: Name (keyword-only).
    
    Returns:
        Args for 'git config --global user.name <name>'.
    """
    return self.config_global_args("user.name", name, *args)

# Usage
VersionController.I.config_global_user_name_args(name="John Doe").run()

Local Configuration

def config_local_user_name_args(self, *args: str, name: str) -> Args:
    """Construct git config arguments for local user name.
    
    Returns:
        Args for 'git config --local user.name <name>'.
    """
    return self.config_local_args("user.name", name, *args)

# Usage
VersionController.I.config_local_user_name_args(name="Bot").run()

Reading Configuration

Get Configuration Values

def config_get_user_name_args(self, *args: str) -> Args:
    """Construct git config get user name arguments.
    
    Returns:
        Args for 'git config --get user.name'.
    """
    return self.config_get_args("user.name", *args)

def config_get_user_email_args(self, *args: str) -> Args:
    """Construct git config get user email arguments.
    
    Returns:
        Args for 'git config --get user.email'.
    """
    return self.config_get_args("user.email", *args)

def config_remote_origin_url_args(self, *args: str) -> Args:
    """Construct git config get remote origin URL arguments.
    
    Returns:
        Args for 'git config --get remote.origin.url'.
    """
    return self.config_get_args("remote.origin.url", *args)

# Usage
result = VersionController.I.config_get_user_name_args().run()
username = result.stdout.strip()

result = VersionController.I.config_get_user_email_args().run()
email = result.stdout.strip()

Helper Methods

def username(self) -> str:
    """Get git username from config.
    
    Returns:
        Configured git username.
    """
    args = self.config_get_user_name_args()
    stdout = args.run_cached().stdout
    return stdout.strip()

def email(self) -> str:
    """Get the email from git config.
    
    Returns:
        Email address.
    """
    args = self.config_get_user_email_args()
    stdout = args.run_cached().stdout
    return stdout.strip()

def repo_remote(self, *, check: bool = True) -> str:
    """Get the remote origin URL from git config.
    
    Args:
        check: Whether to raise exception if command fails.
    
    Returns:
        Remote origin URL (HTTPS or SSH format).
        Empty string if check=False and no remote.
    """
    args = self.config_remote_origin_url_args()
    stdout = args.run_cached(check=check).stdout
    return stdout.strip()

# Usage
print(f"User: {VersionController.I.username()}")
print(f"Email: {VersionController.I.email()}")
print(f"Remote: {VersionController.I.repo_remote()}")

Diffing

def diff_args(self, *args: str) -> Args:
    """Construct git diff arguments.
    
    Returns:
        Args for 'git diff'.
    """
    return self.args("diff", *args)

def diff(self) -> str:
    """Get the diff output.
    
    Returns:
        Diff output.
    """
    args = self.diff_args()
    completed_process = args.run(check=False)
    return completed_process.stdout

# Usage
diff_output = VersionController.I.diff()
if diff_output:
    print("Uncommitted changes:")
    print(diff_output)

Repository Information

Get Owner and Name

@classmethod
def repo_owner_and_name(
    cls,
    *,
    check_repo_url: bool = True,
    url_encode: bool = False
) -> tuple[str, str]:
    """Get the repository owner and name.
    
    Parses the git remote origin URL to extract owner and repo name.
    Falls back to the git username and current working directory name
    if no remote is configured.
    
    Args:
        check_repo_url: Whether to raise on missing remote. Defaults to True.
        url_encode: Whether to percent-encode the returned strings.
    
    Returns:
        Tuple of (owner, repository_name).
    """

# Usage
owner, repo = VersionController.repo_owner_and_name()
print(f"Repository: {owner}/{repo}")

# For URL construction
owner, repo = VersionController.repo_owner_and_name(url_encode=True)
url = f"https://github.com/{owner}/{repo}"

Gitignore Management

def ignore_path(self) -> Path:
    """Get the path to the .gitignore file.
    
    Returns:
        Path to .gitignore.
    """
    return Path(self.ignore_filename())

def loaded_ignore(self) -> list[str]:
    """Get the loaded gitignore patterns.
    
    Returns:
        List of gitignore patterns.
    """
    return self.ignore_path().read_text(encoding="utf-8").splitlines()

# Usage
ignore_patterns = VersionController.I.loaded_ignore()
for pattern in ignore_patterns:
    print(pattern)

Complete Workflow Example

from pyrig.rig.tools.version_controller import VersionController
from pyrig.rig.tools.package_manager import PackageManager

# Initialize new repository
VersionController.I.init_args().run()

# Configure git
VersionController.I.config_local_user_name_args(name="Developer").run()
VersionController.I.config_local_user_email_args(
    email="[email protected]"
).run()

# Make initial commit
VersionController.I.add_all_args().run()
VersionController.I.commit_no_verify_args(msg="Initial commit").run()

# Add dependency and commit
PackageManager.I.add_dependencies_args("requests").run()
VersionController.I.add_pyproject_toml_and_lock_file_args().run()
VersionController.I.commit_no_verify_args(msg="Add requests dependency").run()

# Create and push a release tag
VersionController.I.tag_args(tag="v1.0.0", "-a", "-m", "Release 1.0.0").run()
VersionController.I.push_origin_args().run()
VersionController.I.push_origin_tag_args(tag="v1.0.0").run()

# Check for uncommitted changes
if VersionController.I.has_unstaged_diff():
    diff = VersionController.I.diff()
    print("Uncommitted changes detected:")
    print(diff)

Customization

from pyrig.rig.tools.version_controller import VersionController
from pyrig.src.processes import Args

class CustomVersionController(VersionController):
    """Version controller with custom defaults."""
    
    def default_branch(self) -> str:
        """Use 'develop' as default branch."""
        return "develop"
    
    def commit_args(self, *args: str) -> Args:
        """Always sign commits."""
        return super().commit_args("--gpg-sign", *args)
    
    def push_args(self, *args: str) -> Args:
        """Always use --force-with-lease."""
        return super().push_args("--force-with-lease", *args)

See Also

Git Documentation

Official Git documentation

Package Manager

UV package manager wrapper

Tools Overview

Learn about the Tool pattern

Project Tester

Run tests with pytest

Build docs developers (and LLMs) love