Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .git_archival.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
commit_sha: $Format:%H$
short_sha: $Format:%h$
timestamp: $Format:%aI$
author_name: $Format:%an$
author_email: $Format:%ae$
ref_names: $Format:%D$
commit_message:
$Format:%B$
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.git_archival.txt export-subst
20 changes: 14 additions & 6 deletions .github/workflows/_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,11 @@ on:
description: "The type of build (e.g., nightly, release)"
required: true
type: string
secrets:
pypi_token:
description: "PyPI API Token for publishing"
required: false
permissions:
contents: read
contents: write # For GitHub Releases
id-token: write # For PyPI OIDC trusted publishing
packages: write # To publish to GitHub Container Registry or GitHub Packages
attestations: write # For artifact attestations
jobs:
publish:
name: "Publish (${{ inputs.build_type }})"
Expand All @@ -45,9 +42,20 @@ jobs:
with:
name: build-artifact-${{ inputs.build_type }}-${{ github.run_id }}
path: ${{ inputs.build_location }}
- name: Generate artifact attestations
if: ${{ inputs.build_location != '' }}
uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v1.5.1
with:
subject-path: ${{ inputs.build_location }}/*
- name: Publish to PyPI
if: ${{ inputs.build_location != '' }}
uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
with:
packages-dir: ${{ inputs.build_location }}
password: ${{ secrets.pypi_token }}
- name: Create GitHub Release
if: ${{ github.ref_type == 'tag' }}
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
with:
files: ${{ inputs.build_location }}/*
generate_release_notes: true
prerelease: ${{ inputs.build_type != 'release' }}
2 changes: 0 additions & 2 deletions .github/workflows/_security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ jobs:
uses: trufflesecurity/trufflehog@8a12e8e2fb6f3c4a4294a8e63b3659af6c08cfe3 # main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
dependency-audit:
name: "Dependency Vulnerability Scan"
runs-on: ubuntu-latest
Expand Down
43 changes: 40 additions & 3 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,48 @@ on:
permissions:
contents: write
packages: write
id-token: write
security-events: write
actions: read
jobs:

# ── Stage 0: Check Changes ─────────────────────────────────────────────────
check-changes:
name: "Check for Source Changes"
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Check for recent commits
id: check
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
COMMITS=$(git log --since="24 hours ago" --oneline)
if [ -z "$COMMITS" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
fi

# ── Stage 1: Security Audit ────────────────────────────────────────────────
security:
name: "Security Audit"
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_security.yml

# ── Stage 2: Extended Test Suite ───────────────────────────────────────────
test:
name: "Extended Tests"
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_tests.yml
with:
test_matrix: >-
Expand All @@ -41,13 +71,17 @@ jobs:
# ── Stage 3: Link Check ────────────────────────────────────────────────────
link-check:
name: "Link Check"
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_link-check.yml
with:
fail_on_error: false

# ── Stage 4: Alpha Docs ────────────────────────────────────────────────────
docs:
name: "Nightly Docs"
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_docs.yml
with:
build_type: "nightly"
Expand All @@ -58,19 +92,22 @@ jobs:
build:
name: "Nightly Build"
needs:
- check-changes
- test
- security
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_build_package.yml
with:
build_type: "nightly"

# ── Stage 6: Publish Artifacts ─────────────────────────────────────────────
publish:
name: "Publish Nightly"
needs: build
needs:
- check-changes
- build
if: needs.check-changes.outputs.has_changes == 'true'
uses: ./.github/workflows/_publish.yml
with:
build_type: "nightly"
build_location: "dist/"
secrets:
pypi_token: ${{ secrets.PYPI_API_TOKEN }}
5 changes: 1 addition & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Strict semver: v1.2.3
- "v[0-9]+.[0-9]+.[0-9]+-*" # Pre-releases: v1.2.3-rc.1
permissions:
contents: write
packages: write
Expand Down Expand Up @@ -53,7 +52,7 @@ jobs:
uses: ./.github/workflows/_docs.yml
with:
build_type: "release"
alias: "${{ github.ref_name }}"
alias: "latest"
include_coverage: true

# ── Stage 5: Immutable Build ───────────────────────────────────────────────
Expand All @@ -74,5 +73,3 @@ jobs:
with:
build_type: "release"
build_location: "dist/"
secrets:
pypi_token: ${{ secrets.PYPI_API_TOKEN }}
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repos:
- id: check-yaml
args:
- --unsafe
- id: check-added-large-files
- id: check-merge-conflict
- repo: local
hooks:
Expand Down
60 changes: 44 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</p>

<p align="center">
<em>Opinionated PEP 440 Python versioning for Git repos and submodules. Enforces CI/User authority and generates rich version.py files with deep metadata for auditability. Native Hatch & Setuptools support. Simple, predictable, and foolproof automation.</em>
<em>Simple, predictable, and opinionated versioning for Python projects.</em>
</p>

<p align="center">
Expand Down Expand Up @@ -56,21 +56,28 @@ ______________________________________________________________________
</picture>
</p>

Welcome to GitVersioned!
GitVersioned is a PEP 440-compliant Python versioning tool for Git repositories. It leverages Git history and CI environments as the ultimate source of truth to provide predictable, automated release versioning with zero runtime dependencies.

GitVersioned is a tool that provides an opinionated, PEP 440-compliant Python versioning strategy for Git repositories and submodules. It enforces CI and user authority over versioning, and generates rich `version.py` files with deep metadata for full auditability.
### Why GitVersioned?

### Why Use GitVersioned?
GitVersioned combines strict PEP 440 compliance with extreme customizability, designed to drop cleanly into modern Python build systems.

- **Auditability:** Deep metadata ensures every build is traceable back to its exact state.
- **Predictability:** Simple, foolproof automation for generating versions.
- **Native Support:** First-class support for modern build systems like Hatch and Setuptools.
- **Ironclad Auditability & Metadata:** Generates rich `version.py` files packed with Git hashes, branch names, and build context to ensure every artifact is fully traceable.
- **Predictability & Authority:** Enforces CI-driven and user-defined authority. Supports dynamic version resolution from tags, branches, commits, local files, archives (e.g., GitHub ZIP downloads), or custom Python hooks.
- **Native Integrations & CI/CD Ready:** Offers out-of-the-box support for **Hatch** and **Setuptools** and plugs effortlessly into modern CI pipelines (e.g., GitHub Actions) without tangled configurations.
- **Deep Customizability:** Features 25+ configuration settings via `pyproject.toml`, `setup.cfg`, or environment variables. Provides advanced ExStr templates and custom format strings for full control over metadata generation and release formatting.
- **Archive Fallback:** Seamlessly resolves version data from GitHub ZIP downloads or source archives when the `.git` directory is missing.
- **Submodule Awareness:** Intelligently handles versioning across complex Git submodule structures.

## What's New
### Ecosystem Comparison

**Welcome to GitVersioned!**

GitVersioned is currently under active development. Keep an eye on this section for future release highlights and new features once the initial implementation is complete.
| Tool | Versioning Logic & PEP 440 | Sourcing & Configuration | Auditability & Metadata | Integrations & DX |
| ------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **`GitVersioned`** | **Predictable & Robust.** Enforces strict PEP 440 compliance with flow-based auto-incrementing logic. | **Omni-Source.** Dynamically resolves from Git, archives, env vars, files, or custom Python hooks. | **Unmatched.** Generates rich `version.py` files packed with Git, build, and host environment data. | **Superior.** 2-line setup, zero runtime deps, and native Hatch/Setuptools support. |
| `setuptools-scm` | Heuristic. Relies on "guess-next-version" logic which can lead to unpredictable bumps. | VCS-First. Primarily Git/Hg tags; environment overrides are manual and difficult to manage. | Minimal. Outputs a simple version string with basic stdout build logging. | Standard. Industry default but requires build-time dependencies and is tied to Setuptools. |
| `versioneer` | Rigid. Uses basic tag-plus-distance logic with little room for customization. | VCS-Only. Hardcoded to read from repository tags and hashes. | None. Injects a hardcoded Python module; executes silently with no tracing. | Legacy. Requires vendoring ~2,000 lines of code directly into your repository. |
| `versioningit` | Manual. Highly configurable but places the burden of PEP 440 compliance on complex user regex/templates. | Modular. Supports many sources but requires extensive individual manual configuration. | Moderate. Customizable output via templates but lacks a unified, structured metadata model. | Complex. Steep learning curve and plugin-heavy architecture. |
| `hatch-vcs` | Basic. Inherits the standard "guess" logic from the broader SCM ecosystem. | Optimized. Tied tightly to Hatchling profiles and its specific environment. | Internal. Logging and metadata are strictly restricted to the Hatch ecosystem. | Niche. High ease of use for Hatch users, but inapplicable for other build backends. |

## Quick Start

Expand All @@ -87,19 +94,40 @@ build-backend = "hatchling.build"
source = "gitversioned"
```

To ensure `gitversioned` can resolve the version when users download your repository as a ZIP file (e.g., from GitHub) where the `.git` directory is missing, configure **Archive Support**:

1. Create a `.git_archival.txt` file in your repository root with the following format variables:
```text
commit_sha: $Format:%H$
short_sha: $Format:%h$
timestamp: $Format:%aI$
author_name: $Format:%an$
author_email: $Format:%ae$
ref_names: $Format:%D$
commit_message:
$Format:%B$
```
1. Enable variable substitution during archive creation by adding the following to your `.gitattributes` file:
```text
.git_archival.txt export-subst
```

For full installation options, Setuptools alternatives, and step-by-step onboarding, see the **[Getting Started guide](https://markurtz.github.io/git-versioned/getting-started/)**.

## Core Concepts

This project is built using modern Python tooling, ensuring a stable and typed foundation. It utilizes Ruff and Mypy for code quality.
GitVersioned is built using modern Python tooling, enforcing strict code quality standards with Ruff and Mypy, and providing a robust Pydantic-driven settings architecture for configuration resolution.

### Component Architecture

The project is organized into several key areas:
The repository is structured to separate documentation, application logic, and testing cleanly:

- `docs/`: Project documentation and guides.
- `src/gitversioned`: The core application logic and versioning handlers.
- `tests/`: Automated test suite ensuring correctness and reliability.
- `src/gitversioned/`: The primary application source code. Contains core logic for Git interaction, version resolution, and template generation.
- `plugins/`: Native integrations for build backends like Hatchling (`hatchling_plugin.py`) and Setuptools (`setuptools_plugin.py`).
- `tests/`: Comprehensive test suite ensuring reliability, organized into `unit/`, `integration/`, and `e2e/`.
- `docs/`: Source code for the MkDocs Material documentation site, including step-by-step guides, references, and getting started tutorials.
- `examples/`: Runnable reference projects demonstrating real-world configurations across various build systems and workflows.
- `.github/workflows/`: Advanced CI/CD pipelines governing the project lifecycle, built around reusable workflow templates.

## Advanced Usage

Expand Down
Binary file added docs/assets/branding/icon-black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/assets/branding/icon-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/branding/icon-blue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading