gitinspect 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitinspect-0.1.0/PKG-INFO +233 -0
- gitinspect-0.1.0/README.md +201 -0
- gitinspect-0.1.0/pyproject.toml +61 -0
- gitinspect-0.1.0/setup.cfg +4 -0
- gitinspect-0.1.0/src/diffenv/__init__.py +11 -0
- gitinspect-0.1.0/src/diffenv/api.py +72 -0
- gitinspect-0.1.0/src/diffenv/cli.py +155 -0
- gitinspect-0.1.0/src/diffenv/diff_layer.py +102 -0
- gitinspect-0.1.0/src/diffenv/exceptions.py +29 -0
- gitinspect-0.1.0/src/diffenv/formatter_layer.py +198 -0
- gitinspect-0.1.0/src/diffenv/git_layer.py +278 -0
- gitinspect-0.1.0/src/diffenv/logging_config.py +23 -0
- gitinspect-0.1.0/src/diffenv/models.py +107 -0
- gitinspect-0.1.0/src/diffenv/parser_layer.py +257 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/PKG-INFO +233 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/SOURCES.txt +25 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/dependency_links.txt +1 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/entry_points.txt +2 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/requires.txt +11 -0
- gitinspect-0.1.0/src/gitinspect.egg-info/top_level.txt +1 -0
- gitinspect-0.1.0/tests/test_api.py +83 -0
- gitinspect-0.1.0/tests/test_diff_layer.py +136 -0
- gitinspect-0.1.0/tests/test_exceptions.py +55 -0
- gitinspect-0.1.0/tests/test_formatter_layer.py +149 -0
- gitinspect-0.1.0/tests/test_git_layer.py +144 -0
- gitinspect-0.1.0/tests/test_models.py +150 -0
- gitinspect-0.1.0/tests/test_parser_layer.py +272 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitinspect
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compare environment-level changes between two git branches or commits
|
|
5
|
+
Author-email: Naman Bhola <bhola.naman02@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/namanbhola1888/diffenv
|
|
8
|
+
Project-URL: Repository, https://github.com/namanbhola1888/diffenv
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/namanbhola1888/diffenv/issues
|
|
10
|
+
Keywords: git,diff,environment,dependencies,devops,cli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: typer>=0.12.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
32
|
+
|
|
33
|
+
# GitAudit
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gitaudit main feature/new-auth
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Dependencies
|
|
43
|
+
- flask-session
|
|
44
|
+
+ requests==2.32.0 (was 2.28.0)
|
|
45
|
+
|
|
46
|
+
Environment Variables
|
|
47
|
+
+ JWT_SECRET
|
|
48
|
+
+ REDIS_URL
|
|
49
|
+
|
|
50
|
+
Python Runtime
|
|
51
|
+
3.11 → 3.12
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Why
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install gitaudit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Requires Python 3.10+ and git installed and available on `PATH`.
|
|
65
|
+
|
|
66
|
+
## CLI usage
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
gitaudit <ref_old> <ref_new> [OPTIONS]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Option | Description | Default |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `--format`, `-f` | Output format: `color`, `text`, or `json` | `color` |
|
|
75
|
+
| `--repo-path`, `-C` | Path to the git repository | `.` (current directory) |
|
|
76
|
+
| `--auto-fetch` | Fetch missing refs from `origin` automatically, no prompt | off |
|
|
77
|
+
| `--verbose`, `-v` | Print debug logs to stderr | off |
|
|
78
|
+
|
|
79
|
+
### Examples
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Compare two branches with colored terminal output
|
|
83
|
+
gitaudit main feature/new-auth
|
|
84
|
+
|
|
85
|
+
# Plain text — safe for piping/redirecting
|
|
86
|
+
gitaudit main feature/new-auth --format text > changes.txt
|
|
87
|
+
|
|
88
|
+
# JSON — for CI pipelines or scripting
|
|
89
|
+
gitaudit main feature/new-auth --format json | jq '.dependencies'
|
|
90
|
+
|
|
91
|
+
# Compare commits or tags directly
|
|
92
|
+
gitaudit v1.2.0 v1.3.0
|
|
93
|
+
|
|
94
|
+
# Run against a repo elsewhere on disk
|
|
95
|
+
gitaudit main develop --repo-path ~/projects/my-app
|
|
96
|
+
|
|
97
|
+
# Non-interactive: auto-fetch missing refs instead of prompting
|
|
98
|
+
gitaudit main origin-only-branch --auto-fetch
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Exit codes
|
|
102
|
+
|
|
103
|
+
`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."
|
|
104
|
+
|
|
105
|
+
| Code | Meaning |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `0` | No environment-level changes found |
|
|
108
|
+
| `1` | Changes were found |
|
|
109
|
+
| `2` | Error (bad ref, not a git repo, bad arguments, parse failure) |
|
|
110
|
+
|
|
111
|
+
### Missing branches
|
|
112
|
+
|
|
113
|
+
If a ref doesn't exist locally, `gitaudit` will:
|
|
114
|
+
|
|
115
|
+
1. Check whether it exists on the `origin` remote.
|
|
116
|
+
2. Prompt you to confirm fetching it (unless `--auto-fetch` is set).
|
|
117
|
+
3. Fetch it and proceed, or fail with a clear message if it can't be found anywhere.
|
|
118
|
+
|
|
119
|
+
No raw git errors are ever shown — only clean, actionable messages.
|
|
120
|
+
|
|
121
|
+
## Python SDK
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from gitaudit import compare
|
|
125
|
+
|
|
126
|
+
# Get a structured result
|
|
127
|
+
result = compare("main", "feature/new-auth")
|
|
128
|
+
for change in result.dep_changes:
|
|
129
|
+
print(change.name, change.old_version, "->", change.new_version)
|
|
130
|
+
|
|
131
|
+
# Or get pre-rendered output directly
|
|
132
|
+
text = compare("main", "feature/new-auth", output_format="text")
|
|
133
|
+
json_str = compare("main", "feature/new-auth", output_format="json")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from gitaudit import compare
|
|
140
|
+
from gitaudit.exceptions import RefNotFoundError, NotAGitRepoError, ParseError
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
result = compare("main", "feature/x", repo_path="/path/to/repo")
|
|
144
|
+
except RefNotFoundError as e:
|
|
145
|
+
print(f"Ref not found: {e.ref}")
|
|
146
|
+
except NotAGitRepoError:
|
|
147
|
+
print("Not a git repository")
|
|
148
|
+
except ParseError as e:
|
|
149
|
+
print(f"Could not parse {e.filename}")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## What it tracks
|
|
153
|
+
|
|
154
|
+
| File | What's compared |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `requirements.txt` | Package additions, removals, and `==` version pins |
|
|
157
|
+
| `pyproject.toml` | `[project.dependencies]` (PEP 621), `requires-python` |
|
|
158
|
+
| `.env.example` | Declared environment variable keys (values are not compared) |
|
|
159
|
+
| `.python-version` | Exact pinned Python version |
|
|
160
|
+
|
|
161
|
+
Dependency names are matched case-insensitively (PyPI convention). Environment variable keys are matched case-sensitively (OS convention).
|
|
162
|
+
|
|
163
|
+
**Not yet supported** (natural extension points for a future parser):
|
|
164
|
+
- Poetry's `[tool.poetry.dependencies]`
|
|
165
|
+
- `Pipfile` / `Pipfile.lock`
|
|
166
|
+
- `package.json` (Node environments)
|
|
167
|
+
- Lockfile-level diffing (`poetry.lock`, `requirements-lock.txt`)
|
|
168
|
+
|
|
169
|
+
## Architecture
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
git ref ──▶ git_layer ──▶ parser_layer ──▶ diff_layer ──▶ formatter_layer ──▶ output
|
|
173
|
+
(fetch file (parse text (compare two (render as
|
|
174
|
+
content, into typed ParsedSnapshots text/json/color)
|
|
175
|
+
no checkout) models) → DiffResult)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **`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.
|
|
179
|
+
- **`parser_layer`** — one function per file type, registered independently. Adding a new file type means writing one parser function — no other layer changes.
|
|
180
|
+
- **`diff_layer`** — pure data comparison between two parsed snapshots, no I/O.
|
|
181
|
+
- **`formatter_layer`** — one class per output format (`text`, `json`, `color`), all swappable via dependency injection through `get_formatter(name)`.
|
|
182
|
+
|
|
183
|
+
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.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
git clone https://github.com/namanbhola1888/diffenv
|
|
189
|
+
cd diffenv
|
|
190
|
+
pip install -e ".[dev]"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Running tests
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
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.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Run with coverage
|
|
203
|
+
pytest --cov=gitaudit --cov-report=term-missing
|
|
204
|
+
|
|
205
|
+
# Run only fast unit tests, skipping integration tests that shell out to git
|
|
206
|
+
pytest tests/test_models.py tests/test_parser_layer.py tests/test_diff_layer.py tests/test_formatter_layer.py tests/test_exceptions.py
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Building and publishing to PyPI
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# 1. Install build tooling
|
|
213
|
+
pip install --upgrade build twine
|
|
214
|
+
|
|
215
|
+
# 2. Build the distribution
|
|
216
|
+
python -m build
|
|
217
|
+
|
|
218
|
+
# 3. Check the build for issues
|
|
219
|
+
twine check dist/*
|
|
220
|
+
|
|
221
|
+
# 4. Upload to TestPyPI first (recommended)
|
|
222
|
+
twine upload --repository testpypi dist/*
|
|
223
|
+
pip install --index-url https://test.pypi.org/simple/ gitaudit
|
|
224
|
+
|
|
225
|
+
# 5. Upload to the real PyPI
|
|
226
|
+
twine upload dist/*
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
You'll need a PyPI account and an API token (set via `~/.pypirc` or the `TWINE_PASSWORD` environment variable with `__token__` as the username).
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# GitAudit
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
gitaudit main feature/new-auth
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Dependencies
|
|
11
|
+
- flask-session
|
|
12
|
+
+ requests==2.32.0 (was 2.28.0)
|
|
13
|
+
|
|
14
|
+
Environment Variables
|
|
15
|
+
+ JWT_SECRET
|
|
16
|
+
+ REDIS_URL
|
|
17
|
+
|
|
18
|
+
Python Runtime
|
|
19
|
+
3.11 → 3.12
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install gitaudit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python 3.10+ and git installed and available on `PATH`.
|
|
33
|
+
|
|
34
|
+
## CLI usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
gitaudit <ref_old> <ref_new> [OPTIONS]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Option | Description | Default |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `--format`, `-f` | Output format: `color`, `text`, or `json` | `color` |
|
|
43
|
+
| `--repo-path`, `-C` | Path to the git repository | `.` (current directory) |
|
|
44
|
+
| `--auto-fetch` | Fetch missing refs from `origin` automatically, no prompt | off |
|
|
45
|
+
| `--verbose`, `-v` | Print debug logs to stderr | off |
|
|
46
|
+
|
|
47
|
+
### Examples
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Compare two branches with colored terminal output
|
|
51
|
+
gitaudit main feature/new-auth
|
|
52
|
+
|
|
53
|
+
# Plain text — safe for piping/redirecting
|
|
54
|
+
gitaudit main feature/new-auth --format text > changes.txt
|
|
55
|
+
|
|
56
|
+
# JSON — for CI pipelines or scripting
|
|
57
|
+
gitaudit main feature/new-auth --format json | jq '.dependencies'
|
|
58
|
+
|
|
59
|
+
# Compare commits or tags directly
|
|
60
|
+
gitaudit v1.2.0 v1.3.0
|
|
61
|
+
|
|
62
|
+
# Run against a repo elsewhere on disk
|
|
63
|
+
gitaudit main develop --repo-path ~/projects/my-app
|
|
64
|
+
|
|
65
|
+
# Non-interactive: auto-fetch missing refs instead of prompting
|
|
66
|
+
gitaudit main origin-only-branch --auto-fetch
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Exit codes
|
|
70
|
+
|
|
71
|
+
`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."
|
|
72
|
+
|
|
73
|
+
| Code | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `0` | No environment-level changes found |
|
|
76
|
+
| `1` | Changes were found |
|
|
77
|
+
| `2` | Error (bad ref, not a git repo, bad arguments, parse failure) |
|
|
78
|
+
|
|
79
|
+
### Missing branches
|
|
80
|
+
|
|
81
|
+
If a ref doesn't exist locally, `gitaudit` will:
|
|
82
|
+
|
|
83
|
+
1. Check whether it exists on the `origin` remote.
|
|
84
|
+
2. Prompt you to confirm fetching it (unless `--auto-fetch` is set).
|
|
85
|
+
3. Fetch it and proceed, or fail with a clear message if it can't be found anywhere.
|
|
86
|
+
|
|
87
|
+
No raw git errors are ever shown — only clean, actionable messages.
|
|
88
|
+
|
|
89
|
+
## Python SDK
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from gitaudit import compare
|
|
93
|
+
|
|
94
|
+
# Get a structured result
|
|
95
|
+
result = compare("main", "feature/new-auth")
|
|
96
|
+
for change in result.dep_changes:
|
|
97
|
+
print(change.name, change.old_version, "->", change.new_version)
|
|
98
|
+
|
|
99
|
+
# Or get pre-rendered output directly
|
|
100
|
+
text = compare("main", "feature/new-auth", output_format="text")
|
|
101
|
+
json_str = compare("main", "feature/new-auth", output_format="json")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from gitaudit import compare
|
|
108
|
+
from gitaudit.exceptions import RefNotFoundError, NotAGitRepoError, ParseError
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
result = compare("main", "feature/x", repo_path="/path/to/repo")
|
|
112
|
+
except RefNotFoundError as e:
|
|
113
|
+
print(f"Ref not found: {e.ref}")
|
|
114
|
+
except NotAGitRepoError:
|
|
115
|
+
print("Not a git repository")
|
|
116
|
+
except ParseError as e:
|
|
117
|
+
print(f"Could not parse {e.filename}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## What it tracks
|
|
121
|
+
|
|
122
|
+
| File | What's compared |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `requirements.txt` | Package additions, removals, and `==` version pins |
|
|
125
|
+
| `pyproject.toml` | `[project.dependencies]` (PEP 621), `requires-python` |
|
|
126
|
+
| `.env.example` | Declared environment variable keys (values are not compared) |
|
|
127
|
+
| `.python-version` | Exact pinned Python version |
|
|
128
|
+
|
|
129
|
+
Dependency names are matched case-insensitively (PyPI convention). Environment variable keys are matched case-sensitively (OS convention).
|
|
130
|
+
|
|
131
|
+
**Not yet supported** (natural extension points for a future parser):
|
|
132
|
+
- Poetry's `[tool.poetry.dependencies]`
|
|
133
|
+
- `Pipfile` / `Pipfile.lock`
|
|
134
|
+
- `package.json` (Node environments)
|
|
135
|
+
- Lockfile-level diffing (`poetry.lock`, `requirements-lock.txt`)
|
|
136
|
+
|
|
137
|
+
## Architecture
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
git ref ──▶ git_layer ──▶ parser_layer ──▶ diff_layer ──▶ formatter_layer ──▶ output
|
|
141
|
+
(fetch file (parse text (compare two (render as
|
|
142
|
+
content, into typed ParsedSnapshots text/json/color)
|
|
143
|
+
no checkout) models) → DiffResult)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **`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.
|
|
147
|
+
- **`parser_layer`** — one function per file type, registered independently. Adding a new file type means writing one parser function — no other layer changes.
|
|
148
|
+
- **`diff_layer`** — pure data comparison between two parsed snapshots, no I/O.
|
|
149
|
+
- **`formatter_layer`** — one class per output format (`text`, `json`, `color`), all swappable via dependency injection through `get_formatter(name)`.
|
|
150
|
+
|
|
151
|
+
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.
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git clone https://github.com/namanbhola1888/diffenv
|
|
157
|
+
cd diffenv
|
|
158
|
+
pip install -e ".[dev]"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Running tests
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
pytest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Run with coverage
|
|
171
|
+
pytest --cov=gitaudit --cov-report=term-missing
|
|
172
|
+
|
|
173
|
+
# Run only fast unit tests, skipping integration tests that shell out to git
|
|
174
|
+
pytest tests/test_models.py tests/test_parser_layer.py tests/test_diff_layer.py tests/test_formatter_layer.py tests/test_exceptions.py
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Building and publishing to PyPI
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# 1. Install build tooling
|
|
181
|
+
pip install --upgrade build twine
|
|
182
|
+
|
|
183
|
+
# 2. Build the distribution
|
|
184
|
+
python -m build
|
|
185
|
+
|
|
186
|
+
# 3. Check the build for issues
|
|
187
|
+
twine check dist/*
|
|
188
|
+
|
|
189
|
+
# 4. Upload to TestPyPI first (recommended)
|
|
190
|
+
twine upload --repository testpypi dist/*
|
|
191
|
+
pip install --index-url https://test.pypi.org/simple/ gitaudit
|
|
192
|
+
|
|
193
|
+
# 5. Upload to the real PyPI
|
|
194
|
+
twine upload dist/*
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
You'll need a PyPI account and an API token (set via `~/.pypirc` or the `TWINE_PASSWORD` environment variable with `__token__` as the username).
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gitinspect"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Compare environment-level changes between two git branches or commits"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Naman Bhola", email = "bhola.naman02@gmail.com" }]
|
|
13
|
+
keywords = ["git", "diff", "environment", "dependencies", "devops", "cli"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
25
|
+
"Topic :: Utilities",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"typer>=0.12.0",
|
|
29
|
+
"rich>=13.0.0",
|
|
30
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=8.0.0",
|
|
36
|
+
"pytest-cov>=5.0.0",
|
|
37
|
+
"build>=1.0.0",
|
|
38
|
+
"twine>=5.0.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
gitinspect = "diffenv.cli:app"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/namanbhola1888/diffenv"
|
|
46
|
+
Repository = "https://github.com/namanbhola1888/diffenv"
|
|
47
|
+
"Bug Tracker" = "https://github.com/namanbhola1888/diffenv/issues"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["src"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.package-dir]
|
|
53
|
+
"" = "src"
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
addopts = "-v --tb=short"
|
|
58
|
+
|
|
59
|
+
[tool.coverage.run]
|
|
60
|
+
source = ["src/diffenv"]
|
|
61
|
+
omit = ["*/tests/*"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public Python SDK for diffenv.
|
|
3
|
+
|
|
4
|
+
from diffenv import compare
|
|
5
|
+
result = compare("main", "feature/new-auth")
|
|
6
|
+
|
|
7
|
+
# Or get pre-rendered output directly:
|
|
8
|
+
text = compare("main", "feature/new-auth", output_format="json")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from diffenv.models import DiffResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compare(
|
|
17
|
+
ref_old: str,
|
|
18
|
+
ref_new: str,
|
|
19
|
+
*,
|
|
20
|
+
repo_path: str = ".",
|
|
21
|
+
auto_fetch: bool = True,
|
|
22
|
+
output_format: str | None = None,
|
|
23
|
+
) -> DiffResult | str:
|
|
24
|
+
"""
|
|
25
|
+
Compare environment-level changes between two git refs.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
ref_old: Base ref (branch name, tag, or commit SHA).
|
|
29
|
+
ref_new: Target ref to compare against.
|
|
30
|
+
repo_path: Path to the git repository root. Defaults to CWD.
|
|
31
|
+
auto_fetch: If True (default for SDK use), missing refs are
|
|
32
|
+
fetched from the remote automatically. If False,
|
|
33
|
+
a missing ref raises RefNotFoundError immediately.
|
|
34
|
+
The SDK never blocks on an interactive prompt
|
|
35
|
+
(that behavior is CLI-only); auto_fetch is the
|
|
36
|
+
only switch controlling this.
|
|
37
|
+
output_format: If given ("text", "json", or "color"), returns a
|
|
38
|
+
pre-rendered string instead of a DiffResult object.
|
|
39
|
+
Leave as None to get the structured DiffResult for
|
|
40
|
+
programmatic use.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A DiffResult by default, or a rendered str if output_format is set.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
NotAGitRepoError: ``repo_path`` is not inside a git repository.
|
|
47
|
+
RefNotFoundError: A ref could not be resolved locally or remotely.
|
|
48
|
+
ParseError: A tracked file exists but could not be parsed.
|
|
49
|
+
DiffEnvError: Any other diffenv-specific failure.
|
|
50
|
+
"""
|
|
51
|
+
# Imports are deferred so the module loads fast even before deps are ready.
|
|
52
|
+
from diffenv.git_layer import GitClient
|
|
53
|
+
from diffenv.parser_layer import parse_snapshot
|
|
54
|
+
from diffenv.diff_layer import compute_diff
|
|
55
|
+
from diffenv.formatter_layer import get_formatter
|
|
56
|
+
|
|
57
|
+
# The SDK is a non-interactive context: there is no terminal to prompt
|
|
58
|
+
# against, so a missing ref should fail fast (raise RefNotFoundError)
|
|
59
|
+
# rather than block on input(). auto_fetch governs the actual decision;
|
|
60
|
+
# this callback only exists to prevent GitClient from falling back to
|
|
61
|
+
# its interactive y/N console prompt.
|
|
62
|
+
client = GitClient(repo_path=repo_path, confirm_callback=lambda ref: False)
|
|
63
|
+
|
|
64
|
+
snapshot_old = parse_snapshot(client.fetch_files(ref_old, auto_fetch=auto_fetch), ref=ref_old)
|
|
65
|
+
snapshot_new = parse_snapshot(client.fetch_files(ref_new, auto_fetch=auto_fetch), ref=ref_new)
|
|
66
|
+
|
|
67
|
+
result = compute_diff(snapshot_old, snapshot_new)
|
|
68
|
+
|
|
69
|
+
if output_format is None:
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
return get_formatter(output_format).render(result)
|