Metadata-Version: 2.4
Name: gitinspect
Version: 0.1.1
Summary: Compare environment-level changes between two git branches or commits
Author-email: Naman Bhola <bhola.naman02@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/namanbhola1888/diffenv
Project-URL: Repository, https://github.com/namanbhola1888/diffenv
Project-URL: Bug Tracker, https://github.com/namanbhola1888/diffenv/issues
Keywords: git,diff,environment,dependencies,devops,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Version Control :: Git
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.12.0
Requires-Dist: rich>=13.0.0
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Requires-Dist: build>=1.0.0; extra == "dev"
Requires-Dist: twine>=5.0.0; extra == "dev"

# GitInspect

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.

```bash
gitinspect main feature/new-auth
```

```text
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. `gitinspect` reads both refs directly from git (no checkout required) and reports only what's environment-relevant, in one pass.

## Installation

```bash
pip install gitinspect
```

Requires Python 3.10+ and git installed and available on `PATH`.

## CLI usage

```bash
gitinspect <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

```bash
# Compare two branches with colored terminal output
gitinspect main feature/new-auth

# Plain text — safe for piping/redirecting
gitinspect main feature/new-auth --format text > changes.txt

# JSON — for CI pipelines or scripting
gitinspect main feature/new-auth --format json | jq '.dependencies'

# Compare commits or tags directly
gitinspect v1.2.0 v1.3.0

# Run against a repo elsewhere on disk
gitinspect main develop --repo-path ~/projects/my-app

# Non-interactive: auto-fetch missing refs instead of prompting
gitinspect main origin-only-branch --auto-fetch
```

### Exit codes

`gitinspect` 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, `gitinspect` will:

1. Check whether it exists on the `origin` remote.
2. Prompt you to confirm fetching it (unless `--auto-fetch` is set).
3. 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

```python
from gitinspect 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.

```python
from gitinspect import compare
from gitinspect.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.lock`
- `package.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 via `git 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 through `get_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

```bash
git clone https://github.com/namanbhola1888/diffenv
cd diffenv
pip install -e ".[dev]"
```

### Running tests

```bash
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.

```bash
# Run with coverage
pytest --cov=gitinspect --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

```bash
# 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/ gitinspect

# 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
