Skip to main content

gitvars-render

gitvars exists so that your deployed assets can point back to their source.

Git knows the commit, its author, and the code behind a change. gitvars carries that context into the files you deploy. Add placeholders to your configuration, code, or documentation, and replace them with Git metadata during your build— including links to the exact commit and source file.

Why gitvars exists

I built it for a recurring need in data engineering: labeling data assets and pipelines with the version of code behind them. When inspecting a table or an Airflow DAG, I wanted a direct route back to the change that produced it. Embedding Git metadata in labels, descriptions, and configuration makes that connection easier to follow when investigating an issue or understanding a deployment.

The same idea works for Terraform resources, application configuration, and documentation. The example page uses gitvars to fill in its Git metadata at build time.

Installation

The Python distribution is named gitvars-render and installs the gitvars command. The Python import remains gitvars. It requires Python 3.11+ and Git on PATH, with no runtime dependencies or network calls during rendering.

Install the package from a source checkout into your Python environment, replacing /path/to/gitvars-render with the directory containing its pyproject.toml:

python -m pip install /path/to/gitvars-render

Once this project is published to PyPI, install its release by package name:

python -m pip install gitvars-render

Usage

Run gitvars in the Git repository containing the files you want to process. There are two commands:

Command Behavior
gitvars render [PATH ...] Replace placeholders in selected files, in place by default, or write copies with --output-dir.
gitvars check [PATH ...] Read selected files without writing. Exit 1 if rendering would change any file, or 0 if no changes are needed.

Add --dry-run to render to preview a unified diff without writing anything. Both commands read configuration from the nearest .gitvars found by searching upward from the working directory, not from each input path. Use --config FILE to select a specific configuration file. A configuration file is optional: you can supply file selection and other options on the command line instead.

Select files and directories

Both commands accept zero, one, or several positional paths. Paths are files or directories relative to your working directory; absolute paths also work. You may mix files and directories, but every input must belong to the same Git repository. Overlapping inputs are deduplicated.

Inputs Selection
No paths Scan the current directory recursively.
One or more directories Scan those directories recursively.
One or more files Select those files directly; no include patterns are required.

Paths choose where to scan; include patterns choose which files to render. Directory scans require include patterns in .gitvars or repeatable --include options. Globs are relative to the repository root. Exclusions still apply to explicitly supplied files, even though include patterns are unnecessary for them.

For example, preview SQL files under models/ and Markdown files under docs/, without requiring a configuration file:

gitvars render models docs --include '**/*.sql' --include '**/*.md' --dry-run

Or preview two specific files without --include:

gitvars render models/model.sql docs/pipeline.md --dry-run

To keep selection in configuration, create .gitvars at the repository root:

include = ["**/*.sql", "**/*.md"]
exclude = ["build/**", "dist/**", ".venv/**"]

With that configuration, no CLI --include is needed. Narrow the scan to the two directories, or omit paths to scan the current directory:

gitvars render models docs --dry-run
gitvars check models docs
gitvars render --dry-run

For the complete configuration options and matching rules, see Configuration.

Choose where rendering writes

render defaults to in-place replacement. This is intended for fresh CI checkouts: substitution consumes the placeholders. Local use is supported, but every selected input must be tracked and have no staged or unstaged changes. Unrelated modified files do not block rendering. Restore the templates from Git before rendering them again for a different commit.

Using the configuration above, render both directories in place:

gitvars render models docs

For generated artifacts, keep templates intact and render copies instead:

gitvars render models docs --output-dir build

models/nested/model.sql becomes build/models/nested/model.sql: paths are always relative to the repository root, without flattening. Unselected files in input directories are copied unchanged as bytes, including images and other assets. Excluded files are omitted.

The output directory must be empty or absent. Use -f / --force to clear a nonempty output directory before generating it again. Its contents are removed, including files unrelated to the previous run. The flag never permits overlapping inputs and outputs: an output directory cannot be inside an input directory, equal an input, or contain an input. Symlink destinations and destinations inside .git are refused. A common safe layout is sibling models/, docs/, and build/ directories, with only models docs as the inputs. Omitting the paths would scan the current directory, so a build/ destination inside it would be rejected even if build/** is excluded.

gitvars render models docs --output-dir build --force
gitvars render models docs --output-dir build --force --dry-run

--dry-run prints unified diffs without changing any files, creating directories, or clearing existing output. check reports whether rendering would change selected files. Both can inspect dirty or untracked inputs.

Configuration

Use TOML in .gitvars. The nearest file found by searching upward from the working directory wins. --config FILE overrides discovery. A directory input requires include patterns from configuration or --include; with no paths, the input is the working directory.

prefix = "__git_"
escape = true
remote = "origin"
provider = "auto"
include = ["**/*.sql", "**/*.md", "**/*.yml", "**/*.yaml"]
exclude = ["build/**", "dist/**", ".venv/**"]
exclude_regex = ['(^|/)README\.md$']
# Optional repository web root, including owner/group and repository:
# url_prefix = "https://git.company.com/team/analytics"
# provider = "gitlab"  # Required for self-hosted hosts.

All selection patterns are repository-relative, regardless of the working directory or configuration location. Paths use /, and matching is case-sensitive:

Pattern Matches
*.md Markdown files at the repository root
**/*.md Markdown files at any depth, including the root
build/** The root build directory and everything beneath it
templates/?.sql One-character SQL filenames directly under templates/

*, ?, and character classes match within a path segment. A standalone ** segment matches zero or more directories. Globs match the whole relative path; regex exclusions search it. Matching an excluded directory excludes its subtree. There are no negation/reinclude rules. .gitignore is not implicitly consulted.

CLI --include, --exclude, and --exclude-regex are repeatable and extend their configuration lists. CLI --prefix and --escape / --no-escape override scalar configuration. Matching any exclusion wins over inclusion or an explicit file.

Symlinks, .git, and nested repositories are skipped during traversal. Explicit inputs from different repositories are rejected. All files in an invocation use one commit snapshot. A repository without a commit at HEAD is an error.

Placeholders

Placeholder Value
__git_commit__ Full HEAD commit SHA
__git_commit_short__ Git's abbreviated SHA
__git_author_name__ Commit author name
__git_author_email__ Commit author email
__git_committer_name__ Committer name
__git_committer_email__ Committer email
__git_commit_subject__ Commit subject
__git_commit_message__ Full commit message, including newlines
__git_commit_date__ Author date in ISO 8601 with timezone
__git_branch__ Current branch; empty in detached HEAD
__git_tag__ Highest version-ordered tag pointing at HEAD; empty if none
__git_repository__ Repository name
__git_commit_url__ Commit browser URL, if available
__git_file_url__ Input-file permalink at HEAD, if the file exists there

Commit metadata describes checked-out HEAD, including when output-directory inputs have local edits. It does not identify the deployer or the last author of each file. File links refer to the version committed at HEAD, which can differ from a dirty local input. Tags are ordered using Git's version sort, not creation date; only tags available in the local checkout participate.

Supported placeholders are replaced everywhere, including Markdown inline code and fenced code blocks. Unknown or incomplete placeholders are left untouched. Prefix strings are literal, not regexes. Unavailable known values become empty strings, with warnings under --verbose.

Escape a supported placeholder with an immediately preceding backslash:

__git_commit__    -> the actual SHA
\__git_commit__   -> literal __git_commit__

One escape backslash is consumed; all other backslashes remain unchanged. Disable escape handling with escape = false or --no-escape. Then a backslash is ordinary text: \__git_commit__ becomes a backslash followed by the SHA. Replacement is always one-pass; inserted metadata and escaped tokens are never processed again during that invocation.

Values are inserted verbatim by default. Use --html-escape with render or check to encode inserted values for HTML text and quoted attributes, including &, <, >, and quotes. Template text and literal placeholders stay unchanged. This is separate from backslash placeholder escaping. Template authors remain responsible for quoting and escaping values for other output formats.

Repository URLs

GitHub, GitLab, and Bitbucket Cloud are supported, including common HTTPS, SCP-style SSH, and ssh:// remotes. provider = "auto" recognizes github.com, gitlab.com, and bitbucket.org. Self-hosted GitHub/GitLab installations require an explicit provider. A self-hosted url_prefix with auto is a configuration error; an unrecognized remote without an override simply produces empty links.

url_prefix takes precedence over the selected remote for links and repository names. Name discovery uses its final path component, then the selected remote's, then the local repository-root directory name. A trailing .git is removed from URL-derived names. The default remote is origin. No hosting APIs are used. File URLs use the original input's repository-relative path, with components URL-encoded, and the full commit SHA. A file absent from HEAD has no permalink.

Files, failures, and exit codes

Only selected files are decoded as UTF-8; there is no binary detection. A selected file that cannot be decoded produces an error. Original newline characters, including mixed line endings and the UTF-8 BOM, are preserved outside substituted values. Multiline metadata is inserted verbatim. File permissions are preserved; unchanged in-place files are not rewritten. Each changed file is replaced atomically.

Inputs, repository membership, selections, and destination safety are checked before writing or clearing output. In-place clean-input checks also happen before writing. Files are then processed sequentially: there is no bulk token validation or automatic rollback. A later decoding, reading, or writing failure can leave earlier files rendered. Restore affected in-place files from Git, or delete and regenerate output-directory contents.

Exit Meaning
0 Successful render/dry-run, even with changes; or check needs no changes
1 Check found files rendering would change
2 Invalid arguments/configuration, missing Git/commit/repository, dirty in-place inputs, or an I/O/encoding error

All logging and diagnostics go to stdout. Default output is a concise summary; --verbose adds paths and unavailable-value warnings, and --quiet suppresses routine logging. Dry-run diffs and errors remain visible with --quiet.

Development and demo

uv sync --locked --extra demo
uv run --locked --extra demo python -m unittest discover -s tests -v

uv.lock is versioned so local development, CI, and demo builds use the same resolved dependencies. --locked fails if it needs updating. After changing dependencies, run uv lock and commit both pyproject.toml and uv.lock. The runtime package still has no dependencies; the default development group adds the version-comparison helper used by release automation.

To build and preview the page locally:

uv run --locked --extra demo python demo/build.py
uv run --locked --extra demo gitvars render demo/index.html --html-escape --output-dir build --force
Path Purpose
README.md Page content, maintained in Markdown.
demo/layout.html Tracked HTML layout and styles.
demo/index.html Generated HTML with Git placeholders intact; ignored by Git.
build/demo/index.html Final rendered page with actual Git values; ignored by Git. Open this file in your browser.

demo/build.py only combines the README and layout into demo/index.html, leaving the Git placeholders intact. The separate gitvars render command replaces them in a copy at build/demo/index.html, preserving the input path under build/. --force clears an existing build/ directory before rendering. Neither generated HTML file is committed. Run both commands again after editing the README or layout. CI uses the same two steps on pushes and pull requests.

The table is inserted immediately after the introduction. Its three columns show the literal variable, its replaced value, and its explanation. README examples stay literal; only the table's value cells are substituted. Values are escaped for HTML, including multiline commit messages. The file URL cell is empty because the generated input is absent from HEAD; the commit URL still links to the source revision. Both commands run once and exit. The optional demo dependencies are needed only to convert the README to HTML; the gitvars CLI itself still has no runtime dependencies.

GitHub Pages output

On a push to main, the Pages workflow first runs demo/build.py, then invokes gitvars render to produce build/demo/index.html. It uploads build/demo/ as the Pages artifact. That directory becomes the website root, so its index.html is the homepage. With the default project-site URL, it appears at https://andresvelasco.github.io/gitvars-render/, without build/demo/ in the URL. The workflow can also be run manually.

Select GitHub Actions as the repository's Pages publishing source to enable deployment, following the GitHub Pages workflow documentation. Generated output is not committed, so the page can link to the exact source commit without a self-referential SHA update.

Publishing releases

The distribution on PyPI and TestPyPI is gitvars-render. The GitHub repository is AndresVelasco/gitvars-render; the executable and Python import remain gitvars. pyproject.toml is the single version source, and gitvars --version reads the installed distribution's metadata.

The publishing workflow is .github/workflows/publish.yml:

Event Result after successful checks
Pull request Tests and distribution builds; no publishing.
Push to main with no release tags yet Publish a development build to TestPyPI.
Push to main with an unchanged released version Publish a development build to TestPyPI.
Push to main with a version above the latest release tag Create the matching vMAJOR.MINOR.PATCH tag and publish to PyPI.
Push of a matching version tag Verify the tag, version, and tests, then publish to PyPI.

With no existing release tags, the first successful main build publishes 0.1.0.dev... to TestPyPI. After verifying that build, request the initial production release explicitly:

git tag v0.1.0
git push origin v0.1.0

This starts the production publishing workflow, including tests and a check that the tag matches the version in pyproject.toml. For subsequent releases, update the version instead:

uv version 0.1.1

Commit the updated pyproject.toml and uv.lock, preferably in a release PR, and merge into main. Release versions use stable MAJOR.MINOR.PATCH numbers. The workflow compares versions numerically, rejects version regressions and tag/version mismatches, and never moves an existing tag. It tags the commit CI actually tested, even if main has advanced. Tag creation and publishing are jobs in the same workflow, so no additional token is needed to trigger another workflow from an automated tag.

After v0.1.0, ordinary pushes publish versions such as 0.1.1.dev1230001: the next patch version plus a serial derived from the workflow run ID and attempt. Only the temporary build checkout gets this development version; it is not committed. Re-running a failed release on its tagged commit retries publishing and skips distributions already uploaded. Wheel and source distributions are installed and checked before publishing.

Configure pending or existing trusted publishers separately on PyPI and TestPyPI, and create the corresponding GitHub environments:

Setting PyPI TestPyPI
Project name gitvars-render gitvars-render
Repository owner AndresVelasco AndresVelasco
Repository name gitvars-render gitvars-render
Workflow filename publish.yml publish.yml
Environment pypi testpypi

Publishing uses OIDC trusted publishing; no PyPI API token is stored in GitHub. The first upload creates a project configured with a pending publisher. See the PyPI trusted-publishing documentation.

CI is configured to run the standard-library test suite on Python 3.11–3.14 on Linux, plus Python 3.12 on Windows and macOS. The local implementation has been validated on Python 3.12 and Git 2.43; hosted CI results determine broader compatibility.

Metadata

Release files for gitvars-render 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gitvars-render 0.1.0
File Size Uploaded Provenance Actions
gitvars_render-0.1.0.tar.gz 39.7 kB With provenance Details for gitvars_render-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for gitvars-render 0.1.0
File Interpreter ABI Platform Actions
gitvars_render-0.1.0-py3-none-any.whl Python 3 none any Details for gitvars_render-0.1.0-py3-none-any.whl

Total release size: 60.0 kB

Release files / gitvars_render-0.1.0.tar.gz

Download URL gitvars_render-0.1.0.tar.gz
Size 39.7 kB
Tags Source
SHA-256 checksum
How to use checksums
7f40a71c430fd6af9dcc4fb8267eed38efff62cbf667af558837fb65967a5119
BLAKE2b-256 checksum
How to use checksums
7bd4c3ba1ff275506d37be81beff54bcc430d81d85aab1c262cca17ef880c298
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Release files / gitvars_render-0.1.0-py3-none-any.whl

Download URL gitvars_render-0.1.0-py3-none-any.whl
Size 20.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
15d1f94af4f3d174d48fc845a0b24ea4dd7536e32e084a18e79c686a95941b0f
BLAKE2b-256 checksum
How to use checksums
f022f5d434f6d18cd3093f031930ae73fab4039715428401849d8342e3353603
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Security for gitvars-render 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for gitvars_render-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page