Metadata-Version: 2.4
Name: semver-ratchet
Version: 1.4.0
Summary: Forward-only versioning for Trunk-Based Development
Author: semver-ratchet contributors
License: MIT
Project-URL: Homepage, https://github.com/arthurcollet/semver-ratchet
Project-URL: Repository, https://github.com/arthurcollet/semver-ratchet
Project-URL: Documentation, https://github.com/arthurcollet/semver-ratchet/blob/main/README.md
Keywords: semver,versioning,git,ci-cd,trunk-based
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Version Control
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# semver-ratchet (Python)

Python implementation of forward-only versioning for Trunk-Based Development.

## Dynamic Versioning

semver-ratchet calculates versions dynamically from your git state. Use it as a build backend to inject versions at build time without maintaining version files.

### Feature Branches: `0.{CRC32(branch_name)}.{GitDistance}`

Feature branches get ephemeral versions. The minor component is a CRC32 hash of the branch name, and the patch is the commit distance from the trunk branch.

### Main Branch: `Major.Minor.Patch`

The main branch follows [Semantic Versioning 2.0.0](../docs/SEMVER.md). Bumps are determined by `[MAJOR]`, `[MINOR]`, or `[PATCH]` tags in commit messages.

### Dynamic Version in pyproject.toml

Use semver-ratchet as your build backend for fully automatic versioning:

```toml
[build-system]
requires = ["semver-ratchet", "setuptools>=79.0.1"]
build-backend = "semver_ratchet.build"

[project]
name = "your-project"
dynamic = ["version"]
```

When setuptools builds your project, it calls `semver_ratchet.build.get_version()` which reads git state and returns the calculated version.

### Alternative: Build-time Script

```python
# setup.py or build script
import subprocess


def get_version():
    result = subprocess.run(["semver-ratchet", "version", "--no-verify"], capture_output=True, text=True, check=True)
    return result.stdout.strip()
```

---

## Installation

### As a CLI Tool

```bash
uv pip install semver-ratchet
```

### As a Library Dependency

```toml
# pyproject.toml
[project]
dependencies = ["semver-ratchet"]
```

### As a Build Backend

```toml
[build-system]
requires = ["semver-ratchet", "setuptools>=79.0.1"]
build-backend = "semver_ratchet.build"

[project]
dynamic = ["version"]
```

**⚠️ Caveat:** The build backend resolves config from `pyproject.toml` by walking up from `os.getcwd()`. This works when building from the repo root (`pip install .`, `python -m build`), but **fails silently** in isolated build environments (uv build frontends, PEP 517 isolated builds) where the cwd is a temp directory. In those cases, the version falls back to `0.0.0`.

**For CI with isolated builds**, use semver-ratchet as a **runtime dependency** instead:
```toml
[build-system]
requires = ["setuptools>=61", "setuptools_scm[toml]"]
build-backend = "setuptools.build_meta"

[project]
dependencies = ["semver-ratchet"]
```

See [docs/INTEGRATION.md](../docs/INTEGRATION.md) for detailed workarounds.

---

## Usage

### CLI

```bash
# Get current version
semver-ratchet version

# With custom trunk name
semver-ratchet --trunk-name develop version

# Skip git history verification
semver-ratchet --no-verify version

# Display versioning info
semver-ratchet info

# Create and push tag
semver-ratchet tag --push
```

### Module

```bash
python -m semver_ratchet version
```

### Library API

```python
import semver_ratchet

# Get current version
version = semver_ratchet.get_version()
print(version)  # e.g., "1.0.0" or "0.12345678.5"

# With custom trunk name
version = semver_ratchet.get_version(trunk_name="develop")

# Get compatible versions for dependency pinning
compatible = semver_ratchet.get_compatible_versions("1.2.3")
print(compatible)  # ["1.2.3", "1.2", "1"]

# Calculate CRC32 of a branch name
crc32 = semver_ratchet.calculate_crc32_unsigned("feature/login")

# Create a git tag (main branch only)
semver_ratchet.create_git_tag("1.0.0")

# Push tag to remote
semver_ratchet.push_git_tag("1.0.0")

# Verify git history
semver_ratchet.verify_git_history(50)
```

---

## Configuration

### Config Precedence Chain

```
CLI flag  >  Environment variable  >  pyproject.toml  >  Default
```

### CLI Flags

| Flag | Description | Default |
| :--- | :--- | :--- |
| `--trunk-name <name>` | Trunk branch name | `main` |
| `--default-bump {major,minor,patch}` | Default bump type | `minor` |
| `--no-verify` | Skip git history verification | `false` |

### Environment Variables

| Variable | Description | Default |
| :--- | :--- | :--- |
| `RATCHET_TRUNK_NAME` | Trunk branch name | `main` |
| `RATCHET_DEFAULT_BUMP` | Default bump type | `minor` |
| `RATCHET_GIT_PATH` | Custom git binary path | `git` |
| `RATCHET_VERIFY_MINDEPTH` | Minimum git history depth | `50` |
| `RATCHET_MANUAL_VERSION` | Manual version override | *(none)* |
| `RATCHET_OVERRIDE` | *(deprecated)* Same as above | *(none)* |

### Config File (`pyproject.toml`)

```toml
[tool.semver-ratchet]
trunk-name = "main"
default-bump = "minor"
verify-mindepth = 50
git-path = "/usr/local/bin/git"
```

Keys accept both kebab-case and snake_case.

---

## API Reference

### `get_version(default_bump=None, verify_history=True, trunk_name=None, adapter=None)`

Get the appropriate version based on current branch.

- **Parameters:**
  - `default_bump` (str, optional): Default bump type
  - `verify_history` (bool): Verify sufficient git history (default: `True`)
  - `trunk_name` (str, optional): Trunk branch name override
  - `adapter` (GitAdapter, optional): Custom git adapter (for testing)
- **Returns:** `str` — Version string

### `calculate_feature_version(verify_history=True, trunk_name=None, adapter=None)`

Calculate version for a feature branch.

- **Returns:** `str` — Version in format `0.{CRC32}.{Distance}`

### `calculate_main_version(default_bump="patch", verify_history=True, trunk_name=None, adapter=None)`

Calculate version for main branch using SemVer.

- **Returns:** `str` — Version in format `Major.Minor.Patch`

### `get_compatible_versions(version)`

Generate compatible version strings for dependency pinning.

- **Parameters:** `version` (str) — Full version string
- **Returns:** `list[str]` — Array of compatible versions

### `calculate_crc32_unsigned(s)`

Calculate CRC32 checksum as unsigned 32-bit integer.

- **Parameters:** `s` (str) — String to hash
- **Returns:** `int` — Unsigned 32-bit CRC32 value

### `get_current_branch(adapter=None)`

Get the current Git branch name.

- **Returns:** `str`

### `is_main_branch(trunk_name=None, adapter=None)`

Check if current branch is the trunk branch.

- **Returns:** `bool`

### `determine_version_bump(commit_messages, default_bump="patch")`

Determine version bump from commit messages.

- **Returns:** `str` — `"major"`, `"minor"`, or `"patch"`

### `create_git_tag(version, force=False)`

Create a Git tag for the given version.

- **Returns:** `bool`

### `push_git_tag(version, remote="origin")`

Push a Git tag to remote repository.

- **Returns:** `bool`

### `verify_git_history(fetch_depth=50)`

Verify sufficient git history is available.

- **Raises:** `RuntimeError` if insufficient history

### `parse_semver(version)`

Parse a SemVer string into components.

- **Returns:** `tuple[int, int, int]` — `(major, minor, patch)`

### `compare_semver(v1, v2)`

Compare two SemVer versions.

- **Returns:** `int` — `-1` if v1 < v2, `0` if equal, `1` if v1 > v2

---

## Testing

```bash
# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=semver_ratchet

# Type checking
uvx ty check

# Linting
uvx ruff check .
```

---

## License

MIT License. See [LICENSE](../LICENSE) for details.
