Compare environment-level changes between two git branches or commits
Project description
GitAudit
Compare environment-level changes between two git branches or commits — dependencies, environment variables, and Python runtime version — in one unified view. No more manually diffing requirements.txt, .env.example, and .python-version across branches by hand.
gitaudit main feature/new-auth
Dependencies
- flask-session
+ requests==2.32.0 (was 2.28.0)
Environment Variables
+ JWT_SECRET
+ REDIS_URL
Python Runtime
3.11 → 3.12
Why
When reviewing a PR or preparing to merge a branch, "what changed in the environment" is usually scattered across several files and requires manual cross-referencing. gitaudit reads both refs directly from git (no checkout required) and reports only what's environment-relevant, in one pass.
Installation
pip install gitaudit
Requires Python 3.10+ and git installed and available on PATH.
CLI usage
gitaudit <ref_old> <ref_new> [OPTIONS]
| Option | Description | Default |
|---|---|---|
--format, -f |
Output format: color, text, or json |
color |
--repo-path, -C |
Path to the git repository | . (current directory) |
--auto-fetch |
Fetch missing refs from origin automatically, no prompt |
off |
--verbose, -v |
Print debug logs to stderr | off |
Examples
# Compare two branches with colored terminal output
gitaudit main feature/new-auth
# Plain text — safe for piping/redirecting
gitaudit main feature/new-auth --format text > changes.txt
# JSON — for CI pipelines or scripting
gitaudit main feature/new-auth --format json | jq '.dependencies'
# Compare commits or tags directly
gitaudit v1.2.0 v1.3.0
# Run against a repo elsewhere on disk
gitaudit main develop --repo-path ~/projects/my-app
# Non-interactive: auto-fetch missing refs instead of prompting
gitaudit main origin-only-branch --auto-fetch
Exit codes
gitaudit is CI-friendly: it exits 0 when no environment changes are found, and 1 when changes are detected — useful for gating a build on "did the environment change without sign-off."
| Code | Meaning |
|---|---|
0 |
No environment-level changes found |
1 |
Changes were found |
2 |
Error (bad ref, not a git repo, bad arguments, parse failure) |
Missing branches
If a ref doesn't exist locally, gitaudit will:
- Check whether it exists on the
originremote. - Prompt you to confirm fetching it (unless
--auto-fetchis set). - Fetch it and proceed, or fail with a clear message if it can't be found anywhere.
No raw git errors are ever shown — only clean, actionable messages.
Python SDK
from gitaudit import compare
# Get a structured result
result = compare("main", "feature/new-auth")
for change in result.dep_changes:
print(change.name, change.old_version, "->", change.new_version)
# Or get pre-rendered output directly
text = compare("main", "feature/new-auth", output_format="text")
json_str = compare("main", "feature/new-auth", output_format="json")
By default, the SDK auto-fetches missing refs (auto_fetch=True) since scripted use can't respond to an interactive prompt. Pass auto_fetch=False to raise RefNotFoundError instead.
from gitaudit import compare
from gitaudit.exceptions import RefNotFoundError, NotAGitRepoError, ParseError
try:
result = compare("main", "feature/x", repo_path="/path/to/repo")
except RefNotFoundError as e:
print(f"Ref not found: {e.ref}")
except NotAGitRepoError:
print("Not a git repository")
except ParseError as e:
print(f"Could not parse {e.filename}")
What it tracks
| File | What's compared |
|---|---|
requirements.txt |
Package additions, removals, and == version pins |
pyproject.toml |
[project.dependencies] (PEP 621), requires-python |
.env.example |
Declared environment variable keys (values are not compared) |
.python-version |
Exact pinned Python version |
Dependency names are matched case-insensitively (PyPI convention). Environment variable keys are matched case-sensitively (OS convention).
Not yet supported (natural extension points for a future parser):
- Poetry's
[tool.poetry.dependencies] Pipfile/Pipfile.lockpackage.json(Node environments)- Lockfile-level diffing (
poetry.lock,requirements-lock.txt)
Architecture
git ref ──▶ git_layer ──▶ parser_layer ──▶ diff_layer ──▶ formatter_layer ──▶ output
(fetch file (parse text (compare two (render as
content, into typed ParsedSnapshots text/json/color)
no checkout) models) → DiffResult)
git_layer— reads file content at any ref viagit show, never touches your working tree or index. Handles missing-branch detection and remote fetching.parser_layer— one function per file type, registered independently. Adding a new file type means writing one parser function — no other layer changes.diff_layer— pure data comparison between two parsed snapshots, no I/O.formatter_layer— one class per output format (text,json,color), all swappable via dependency injection throughget_formatter(name).
Everything is fully offline — no network calls except an optional git fetch when a ref is missing locally, and no AI/external APIs are used anywhere.
Development
git clone https://github.com/namanbhola1888/diffenv
cd diffenv
pip install -e ".[dev]"
Running tests
pytest
Tests are split into fast unit tests (test_models.py, test_parser_layer.py, test_diff_layer.py, test_formatter_layer.py, test_exceptions.py — no I/O, no git) and integration tests (test_git_layer.py, test_api.py) that build real temporary git repositories via fixtures in conftest.py rather than mocking subprocess calls, since git_layer's correctness depends on actually invoking git.
# Run with coverage
pytest --cov=gitaudit --cov-report=term-missing
# Run only fast unit tests, skipping integration tests that shell out to git
pytest tests/test_models.py tests/test_parser_layer.py tests/test_diff_layer.py tests/test_formatter_layer.py tests/test_exceptions.py
Building and publishing to PyPI
# 1. Install build tooling
pip install --upgrade build twine
# 2. Build the distribution
python -m build
# 3. Check the build for issues
twine check dist/*
# 4. Upload to TestPyPI first (recommended)
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ gitaudit
# 5. Upload to the real PyPI
twine upload dist/*
You'll need a PyPI account and an API token (set via ~/.pypirc or the TWINE_PASSWORD environment variable with __token__ as the username).
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gitinspect-0.1.0.tar.gz.
File metadata
- Download URL: gitinspect-0.1.0.tar.gz
- Upload date:
- Size: 28.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d82a931e9ac818539798991f19fa72b8460f2f9e090e4a46b61bced0c5f948d
|
|
| MD5 |
800d83bdc5433779ccc5bf2c674d4dcc
|
|
| BLAKE2b-256 |
f7422b8c3742bd233bd4792e7f05003f84306b40519502e101397a16513e35b7
|
File details
Details for the file gitinspect-0.1.0-py3-none-any.whl.
File metadata
- Download URL: gitinspect-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b768dbfa865a671ec4f978e31af9fa6d4533cada320b90753341286e498a75a5
|
|
| MD5 |
28a48c82ac702ea1cc2bb11a1774694c
|
|
| BLAKE2b-256 |
6226dae357d8131d93bdbcb4f5026c3bf925d571d6ea65f2a20214ac9e06fab6
|