diffstory 0.2.0__tar.gz → 0.3.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.
- diffstory-0.3.0/PKG-INFO +242 -0
- diffstory-0.3.0/README.md +225 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/pyproject.toml +1 -1
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/__init__.py +1 -1
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/cli.py +59 -5
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/diff_parser.py +1 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/git_utils.py +8 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/html_generator.py +170 -53
- diffstory-0.3.0/src/diffstory.egg-info/PKG-INFO +242 -0
- diffstory-0.2.0/PKG-INFO +0 -207
- diffstory-0.2.0/README.md +0 -190
- diffstory-0.2.0/src/diffstory.egg-info/PKG-INFO +0 -207
- {diffstory-0.2.0 → diffstory-0.3.0}/setup.cfg +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/__main__.py +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory/syntax.py +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory.egg-info/SOURCES.txt +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory.egg-info/dependency_links.txt +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory.egg-info/entry_points.txt +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory.egg-info/requires.txt +0 -0
- {diffstory-0.2.0 → diffstory-0.3.0}/src/diffstory.egg-info/top_level.txt +0 -0
diffstory-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffstory
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Transform Git diffs into rich, interactive, self-contained HTML reports
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/lakshayjindal/diffstory
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: Pygments>=2.10
|
|
17
|
+
|
|
18
|
+
# DiffStory
|
|
19
|
+
|
|
20
|
+
**Transform Git diffs into rich, interactive, self-contained HTML reports.**
|
|
21
|
+
|
|
22
|
+
DiffStory turns any `git diff` into a beautiful, portable HTML report that answers not just *what* changed, but *who* changed it, *when*, and *why* — all offline, in a single file you can share, archive, or email.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install diffstory
|
|
26
|
+
cd my-repo
|
|
27
|
+
diffstory --staged -o review.html
|
|
28
|
+
# Open review.html in any browser — zero setup required
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
### Three Diff Views
|
|
36
|
+
|
|
37
|
+
| View | Description |
|
|
38
|
+
|---|---|
|
|
39
|
+
| **Unified** | Classic git-style diff with line numbers and syntax highlighting |
|
|
40
|
+
| **Side-by-Side** | Original (left) and modified (right) columns, visually aligned |
|
|
41
|
+
| **Inline Edit** | Word-level diff — shows exact token changes inline, green additions and red strikethrough removals. No more mental diffing. |
|
|
42
|
+
|
|
43
|
+
Switch between views instantly with the toolbar or keyboard shortcuts — no regeneration needed.
|
|
44
|
+
|
|
45
|
+
### Blame & Provenance
|
|
46
|
+
|
|
47
|
+
Every changed line carries its history. **Hover** any line to see a tooltip with author name, commit hash, subject, date, and relative time (e.g. "2h ago"). **Click** any line to open the commit drawer — a side panel with full metadata: commit body, author, committer, parents, files changed, insertions, and deletions.
|
|
48
|
+
|
|
49
|
+
### Search & Filtering
|
|
50
|
+
|
|
51
|
+
- **Global search** — find files by name, author, commit subject, or code content with live results
|
|
52
|
+
- **Filter chips** — narrow the view by file extension (`.py`, `.js`, `.html`, etc.) or change type (added, deleted, modified, renamed)
|
|
53
|
+
- **File sidebar** — searchable file list with add/delete indicators and collapse/expand
|
|
54
|
+
|
|
55
|
+
### Statistics Dashboard
|
|
56
|
+
|
|
57
|
+
A floating panel showing:
|
|
58
|
+
- Files changed, additions, deletions
|
|
59
|
+
- Added / deleted / modified / renamed file counts
|
|
60
|
+
- Author count and commit count (from blame)
|
|
61
|
+
- Contributor breakdown with commit counts
|
|
62
|
+
- Top 10 most-changed files
|
|
63
|
+
|
|
64
|
+
### Keyboard Navigation
|
|
65
|
+
|
|
66
|
+
| Key | Action |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `J` / `K` | Next / previous file |
|
|
69
|
+
| `F` or `/` | Focus global search |
|
|
70
|
+
| `U` / `S` / `I` | Unified / Side-by-side / Inline view |
|
|
71
|
+
| `D` | Toggle light/dark theme |
|
|
72
|
+
| `Esc` | Close drawer → close search → close stats |
|
|
73
|
+
|
|
74
|
+
### Deep Linking
|
|
75
|
+
|
|
76
|
+
Link directly to specific files and lines: `#file-0` scrolls to the first file, `#L-0-42` scrolls to line 42 in the first file. Shareable, stable anchors.
|
|
77
|
+
|
|
78
|
+
### Binary File Support
|
|
79
|
+
|
|
80
|
+
Binary files are detected and displayed with meaningful placeholders — image files get a preview icon, other binaries show metadata — preventing crashes and keeping the report clean.
|
|
81
|
+
|
|
82
|
+
### Customization
|
|
83
|
+
|
|
84
|
+
- **Light/Dark themes** — toggle instantly, persists across sessions
|
|
85
|
+
- **Config file** — `~/.diffstory.toml` or `.diffstory.toml` in your project sets defaults for verbose mode, debug output, and more
|
|
86
|
+
- **`--verbose` / `--debug` flags** — see what's happening under the hood
|
|
87
|
+
|
|
88
|
+
### Export Formats
|
|
89
|
+
|
|
90
|
+
Alongside the HTML report, export structured data:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
diffstory --staged --json --md --csv
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install diffstory
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Requires:** Python 3.10+ and Git.
|
|
105
|
+
|
|
106
|
+
To install from source:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git clone https://github.com/lakshayjindal/diffstory.git
|
|
110
|
+
cd diffstory
|
|
111
|
+
pip install -e .
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Usage
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Working tree diff
|
|
120
|
+
diffstory
|
|
121
|
+
|
|
122
|
+
# Staged changes (what will be committed)
|
|
123
|
+
diffstory --staged
|
|
124
|
+
|
|
125
|
+
# Compare commits
|
|
126
|
+
diffstory HEAD~3 HEAD
|
|
127
|
+
|
|
128
|
+
# Compare branches
|
|
129
|
+
diffstory main feature-branch
|
|
130
|
+
|
|
131
|
+
# Compare revisions with path restriction
|
|
132
|
+
diffstory HEAD~3 HEAD src/
|
|
133
|
+
|
|
134
|
+
# Generate from a diff file directly (no git repo needed)
|
|
135
|
+
diffstory --diff /path/to/patch.diff
|
|
136
|
+
|
|
137
|
+
# Custom output file
|
|
138
|
+
diffstory -o my-review.html
|
|
139
|
+
|
|
140
|
+
# Multiple export formats at once
|
|
141
|
+
diffstory --staged --json --md --csv -o release-v2.0
|
|
142
|
+
|
|
143
|
+
# Verbose mode
|
|
144
|
+
diffstory --staged --verbose
|
|
145
|
+
|
|
146
|
+
# Show version
|
|
147
|
+
diffstory --version
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Config File Example
|
|
151
|
+
|
|
152
|
+
Create `~/.diffstory.toml` or `.diffstory.toml` in your project:
|
|
153
|
+
|
|
154
|
+
```toml
|
|
155
|
+
[cli]
|
|
156
|
+
verbose = true
|
|
157
|
+
debug = false
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## HTML Report
|
|
163
|
+
|
|
164
|
+
Every generated HTML report is **fully self-contained**:
|
|
165
|
+
|
|
166
|
+
- All CSS inlined — no external stylesheets
|
|
167
|
+
- All JavaScript inlined — no external scripts
|
|
168
|
+
- All data (blame, commits, search index) embedded as JSON
|
|
169
|
+
- Works offline in any modern browser
|
|
170
|
+
- Safe to email, archive, or include in audit evidence
|
|
171
|
+
- Zero external dependencies at runtime
|
|
172
|
+
|
|
173
|
+
Open it, share it, attach it to a PR, or file it for compliance. It just works.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Project Structure
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
diffstory/
|
|
181
|
+
├── pyproject.toml # Build config & CLI entry point
|
|
182
|
+
├── README.md
|
|
183
|
+
├── requirements.md # Full product requirements & spec
|
|
184
|
+
├── deploy.sh # Build & publish script
|
|
185
|
+
├── .github/workflows/publish.yml # CI/CD for PyPI publishing
|
|
186
|
+
├── src/diffstory/
|
|
187
|
+
│ ├── __init__.py # Package version
|
|
188
|
+
│ ├── __main__.py # python -m diffstory support
|
|
189
|
+
│ ├── cli.py # CLI argument parsing & orchestration
|
|
190
|
+
│ ├── git_utils.py # Git subprocess wrappers (diff, blame, log)
|
|
191
|
+
│ ├── diff_parser.py # Unified diff parser → structured data
|
|
192
|
+
│ ├── syntax.py # Pygments syntax highlighting
|
|
193
|
+
│ └── html_generator.py # Self-contained HTML report generation
|
|
194
|
+
└── tests/
|
|
195
|
+
└── __init__.py
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Install in editable mode
|
|
204
|
+
pip install -e .
|
|
205
|
+
|
|
206
|
+
# Test with a quick repo
|
|
207
|
+
cd /tmp && mkdir test-diffstory && cd test-diffstory
|
|
208
|
+
git init && echo "hello" > test.py && git add -A && git commit -m "init"
|
|
209
|
+
echo "world" >> test.py
|
|
210
|
+
diffstory --staged
|
|
211
|
+
|
|
212
|
+
# Build the package
|
|
213
|
+
python -m build
|
|
214
|
+
|
|
215
|
+
# Deploy (version bump, build, publish)
|
|
216
|
+
./deploy.sh # patch bump
|
|
217
|
+
./deploy.sh minor # minor bump
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Design Philosophy
|
|
223
|
+
|
|
224
|
+
DiffStory was built to answer five questions about every changed line:
|
|
225
|
+
|
|
226
|
+
> **What changed? Who changed it? When? Why? How did it evolve?**
|
|
227
|
+
|
|
228
|
+
It consolidates `git diff`, `git blame`, `git log`, and GitHub-style review UX into a single, portable artifact — no server, no accounts, no data leaving your machine.
|
|
229
|
+
|
|
230
|
+
### Security
|
|
231
|
+
|
|
232
|
+
- Never uploads code or transmits data
|
|
233
|
+
- No telemetry, no analytics, no accounts
|
|
234
|
+
- No external API calls by default
|
|
235
|
+
- Never modifies your repository
|
|
236
|
+
- Generated reports are safe for air-gapped environments, client deliverables, and compliance audits
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# DiffStory
|
|
2
|
+
|
|
3
|
+
**Transform Git diffs into rich, interactive, self-contained HTML reports.**
|
|
4
|
+
|
|
5
|
+
DiffStory turns any `git diff` into a beautiful, portable HTML report that answers not just *what* changed, but *who* changed it, *when*, and *why* — all offline, in a single file you can share, archive, or email.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install diffstory
|
|
9
|
+
cd my-repo
|
|
10
|
+
diffstory --staged -o review.html
|
|
11
|
+
# Open review.html in any browser — zero setup required
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
### Three Diff Views
|
|
19
|
+
|
|
20
|
+
| View | Description |
|
|
21
|
+
|---|---|
|
|
22
|
+
| **Unified** | Classic git-style diff with line numbers and syntax highlighting |
|
|
23
|
+
| **Side-by-Side** | Original (left) and modified (right) columns, visually aligned |
|
|
24
|
+
| **Inline Edit** | Word-level diff — shows exact token changes inline, green additions and red strikethrough removals. No more mental diffing. |
|
|
25
|
+
|
|
26
|
+
Switch between views instantly with the toolbar or keyboard shortcuts — no regeneration needed.
|
|
27
|
+
|
|
28
|
+
### Blame & Provenance
|
|
29
|
+
|
|
30
|
+
Every changed line carries its history. **Hover** any line to see a tooltip with author name, commit hash, subject, date, and relative time (e.g. "2h ago"). **Click** any line to open the commit drawer — a side panel with full metadata: commit body, author, committer, parents, files changed, insertions, and deletions.
|
|
31
|
+
|
|
32
|
+
### Search & Filtering
|
|
33
|
+
|
|
34
|
+
- **Global search** — find files by name, author, commit subject, or code content with live results
|
|
35
|
+
- **Filter chips** — narrow the view by file extension (`.py`, `.js`, `.html`, etc.) or change type (added, deleted, modified, renamed)
|
|
36
|
+
- **File sidebar** — searchable file list with add/delete indicators and collapse/expand
|
|
37
|
+
|
|
38
|
+
### Statistics Dashboard
|
|
39
|
+
|
|
40
|
+
A floating panel showing:
|
|
41
|
+
- Files changed, additions, deletions
|
|
42
|
+
- Added / deleted / modified / renamed file counts
|
|
43
|
+
- Author count and commit count (from blame)
|
|
44
|
+
- Contributor breakdown with commit counts
|
|
45
|
+
- Top 10 most-changed files
|
|
46
|
+
|
|
47
|
+
### Keyboard Navigation
|
|
48
|
+
|
|
49
|
+
| Key | Action |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `J` / `K` | Next / previous file |
|
|
52
|
+
| `F` or `/` | Focus global search |
|
|
53
|
+
| `U` / `S` / `I` | Unified / Side-by-side / Inline view |
|
|
54
|
+
| `D` | Toggle light/dark theme |
|
|
55
|
+
| `Esc` | Close drawer → close search → close stats |
|
|
56
|
+
|
|
57
|
+
### Deep Linking
|
|
58
|
+
|
|
59
|
+
Link directly to specific files and lines: `#file-0` scrolls to the first file, `#L-0-42` scrolls to line 42 in the first file. Shareable, stable anchors.
|
|
60
|
+
|
|
61
|
+
### Binary File Support
|
|
62
|
+
|
|
63
|
+
Binary files are detected and displayed with meaningful placeholders — image files get a preview icon, other binaries show metadata — preventing crashes and keeping the report clean.
|
|
64
|
+
|
|
65
|
+
### Customization
|
|
66
|
+
|
|
67
|
+
- **Light/Dark themes** — toggle instantly, persists across sessions
|
|
68
|
+
- **Config file** — `~/.diffstory.toml` or `.diffstory.toml` in your project sets defaults for verbose mode, debug output, and more
|
|
69
|
+
- **`--verbose` / `--debug` flags** — see what's happening under the hood
|
|
70
|
+
|
|
71
|
+
### Export Formats
|
|
72
|
+
|
|
73
|
+
Alongside the HTML report, export structured data:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
diffstory --staged --json --md --csv
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install diffstory
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Requires:** Python 3.10+ and Git.
|
|
88
|
+
|
|
89
|
+
To install from source:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/lakshayjindal/diffstory.git
|
|
93
|
+
cd diffstory
|
|
94
|
+
pip install -e .
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Working tree diff
|
|
103
|
+
diffstory
|
|
104
|
+
|
|
105
|
+
# Staged changes (what will be committed)
|
|
106
|
+
diffstory --staged
|
|
107
|
+
|
|
108
|
+
# Compare commits
|
|
109
|
+
diffstory HEAD~3 HEAD
|
|
110
|
+
|
|
111
|
+
# Compare branches
|
|
112
|
+
diffstory main feature-branch
|
|
113
|
+
|
|
114
|
+
# Compare revisions with path restriction
|
|
115
|
+
diffstory HEAD~3 HEAD src/
|
|
116
|
+
|
|
117
|
+
# Generate from a diff file directly (no git repo needed)
|
|
118
|
+
diffstory --diff /path/to/patch.diff
|
|
119
|
+
|
|
120
|
+
# Custom output file
|
|
121
|
+
diffstory -o my-review.html
|
|
122
|
+
|
|
123
|
+
# Multiple export formats at once
|
|
124
|
+
diffstory --staged --json --md --csv -o release-v2.0
|
|
125
|
+
|
|
126
|
+
# Verbose mode
|
|
127
|
+
diffstory --staged --verbose
|
|
128
|
+
|
|
129
|
+
# Show version
|
|
130
|
+
diffstory --version
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Config File Example
|
|
134
|
+
|
|
135
|
+
Create `~/.diffstory.toml` or `.diffstory.toml` in your project:
|
|
136
|
+
|
|
137
|
+
```toml
|
|
138
|
+
[cli]
|
|
139
|
+
verbose = true
|
|
140
|
+
debug = false
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## HTML Report
|
|
146
|
+
|
|
147
|
+
Every generated HTML report is **fully self-contained**:
|
|
148
|
+
|
|
149
|
+
- All CSS inlined — no external stylesheets
|
|
150
|
+
- All JavaScript inlined — no external scripts
|
|
151
|
+
- All data (blame, commits, search index) embedded as JSON
|
|
152
|
+
- Works offline in any modern browser
|
|
153
|
+
- Safe to email, archive, or include in audit evidence
|
|
154
|
+
- Zero external dependencies at runtime
|
|
155
|
+
|
|
156
|
+
Open it, share it, attach it to a PR, or file it for compliance. It just works.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Project Structure
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
diffstory/
|
|
164
|
+
├── pyproject.toml # Build config & CLI entry point
|
|
165
|
+
├── README.md
|
|
166
|
+
├── requirements.md # Full product requirements & spec
|
|
167
|
+
├── deploy.sh # Build & publish script
|
|
168
|
+
├── .github/workflows/publish.yml # CI/CD for PyPI publishing
|
|
169
|
+
├── src/diffstory/
|
|
170
|
+
│ ├── __init__.py # Package version
|
|
171
|
+
│ ├── __main__.py # python -m diffstory support
|
|
172
|
+
│ ├── cli.py # CLI argument parsing & orchestration
|
|
173
|
+
│ ├── git_utils.py # Git subprocess wrappers (diff, blame, log)
|
|
174
|
+
│ ├── diff_parser.py # Unified diff parser → structured data
|
|
175
|
+
│ ├── syntax.py # Pygments syntax highlighting
|
|
176
|
+
│ └── html_generator.py # Self-contained HTML report generation
|
|
177
|
+
└── tests/
|
|
178
|
+
└── __init__.py
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Install in editable mode
|
|
187
|
+
pip install -e .
|
|
188
|
+
|
|
189
|
+
# Test with a quick repo
|
|
190
|
+
cd /tmp && mkdir test-diffstory && cd test-diffstory
|
|
191
|
+
git init && echo "hello" > test.py && git add -A && git commit -m "init"
|
|
192
|
+
echo "world" >> test.py
|
|
193
|
+
diffstory --staged
|
|
194
|
+
|
|
195
|
+
# Build the package
|
|
196
|
+
python -m build
|
|
197
|
+
|
|
198
|
+
# Deploy (version bump, build, publish)
|
|
199
|
+
./deploy.sh # patch bump
|
|
200
|
+
./deploy.sh minor # minor bump
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Design Philosophy
|
|
206
|
+
|
|
207
|
+
DiffStory was built to answer five questions about every changed line:
|
|
208
|
+
|
|
209
|
+
> **What changed? Who changed it? When? Why? How did it evolve?**
|
|
210
|
+
|
|
211
|
+
It consolidates `git diff`, `git blame`, `git log`, and GitHub-style review UX into a single, portable artifact — no server, no accounts, no data leaving your machine.
|
|
212
|
+
|
|
213
|
+
### Security
|
|
214
|
+
|
|
215
|
+
- Never uploads code or transmits data
|
|
216
|
+
- No telemetry, no analytics, no accounts
|
|
217
|
+
- No external API calls by default
|
|
218
|
+
- Never modifies your repository
|
|
219
|
+
- Generated reports are safe for air-gapped environments, client deliverables, and compliance audits
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
-
import os
|
|
7
6
|
import sys
|
|
7
|
+
import webbrowser
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Optional
|
|
10
10
|
|
|
@@ -15,6 +15,7 @@ from diffstory.git_utils import (
|
|
|
15
15
|
check_git_repo,
|
|
16
16
|
get_diff,
|
|
17
17
|
get_diff_with_renames,
|
|
18
|
+
get_git_root,
|
|
18
19
|
)
|
|
19
20
|
from diffstory.html_generator import generate_report
|
|
20
21
|
|
|
@@ -142,6 +143,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
142
143
|
help="Generate report from a diff file directly (no git repository needed)",
|
|
143
144
|
)
|
|
144
145
|
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--no-open",
|
|
148
|
+
action="store_true",
|
|
149
|
+
default=False,
|
|
150
|
+
help="Do not open the report in a browser after generation",
|
|
151
|
+
)
|
|
152
|
+
|
|
145
153
|
parser.add_argument(
|
|
146
154
|
"--verbose", "-v",
|
|
147
155
|
action="store_true",
|
|
@@ -280,6 +288,16 @@ def _export_csv(files, output_path: Path) -> None:
|
|
|
280
288
|
print(f" CSV: {output_path}")
|
|
281
289
|
|
|
282
290
|
|
|
291
|
+
def _open_in_browser(path: str) -> None:
|
|
292
|
+
"""Open the generated report in the default browser."""
|
|
293
|
+
try:
|
|
294
|
+
file_url = "file://" + path
|
|
295
|
+
webbrowser.open(file_url)
|
|
296
|
+
print(f" Opened in browser: {file_url}")
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print(f" Could not open browser: {e}", file=sys.stderr)
|
|
299
|
+
|
|
300
|
+
|
|
283
301
|
def _read_diff_from_file(path: str) -> str:
|
|
284
302
|
"""Read diff content from a file."""
|
|
285
303
|
try:
|
|
@@ -292,6 +310,31 @@ def _read_diff_from_file(path: str) -> str:
|
|
|
292
310
|
sys.exit(1)
|
|
293
311
|
|
|
294
312
|
|
|
313
|
+
def _resolve_output_path(given_path: str) -> str:
|
|
314
|
+
"""Resolve the output file path.
|
|
315
|
+
|
|
316
|
+
If the given path is the default and we're inside a git repo,
|
|
317
|
+
place it in a 'stories/' directory outside the git working tree
|
|
318
|
+
so that git does not track it.
|
|
319
|
+
"""
|
|
320
|
+
given = Path(given_path)
|
|
321
|
+
# Only redirect the default path — if the user explicitly passed -o, use as-is
|
|
322
|
+
if given.name != "diffstory-report.html":
|
|
323
|
+
return str(given.resolve())
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
git_root = get_git_root()
|
|
327
|
+
if git_root:
|
|
328
|
+
git_root = Path(git_root).resolve()
|
|
329
|
+
stories_dir = git_root.parent / "stories"
|
|
330
|
+
stories_dir.mkdir(parents=True, exist_ok=True)
|
|
331
|
+
return str(stories_dir / given.name)
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
return str(given.resolve())
|
|
336
|
+
|
|
337
|
+
|
|
295
338
|
def main() -> None:
|
|
296
339
|
"""Main entry point for the diffstory CLI."""
|
|
297
340
|
parser = build_parser()
|
|
@@ -305,6 +348,9 @@ def main() -> None:
|
|
|
305
348
|
if debug:
|
|
306
349
|
verbose = True # debug implies verbose
|
|
307
350
|
|
|
351
|
+
# Resolve output path — for the default, put it outside the git repo
|
|
352
|
+
output_path = _resolve_output_path(args.output)
|
|
353
|
+
|
|
308
354
|
# Handle --diff flag (read diff from file, no git needed)
|
|
309
355
|
if args.diff:
|
|
310
356
|
if verbose:
|
|
@@ -318,9 +364,9 @@ def main() -> None:
|
|
|
318
364
|
sys.exit(0)
|
|
319
365
|
has_exports = args.json or args.md or args.csv
|
|
320
366
|
if has_exports:
|
|
321
|
-
generate_exports(files,
|
|
367
|
+
generate_exports(files, output_path, args.json, args.md, args.csv)
|
|
322
368
|
try:
|
|
323
|
-
report_path = generate_report(files, output_path=
|
|
369
|
+
report_path = generate_report(files, output_path=output_path, repo_name="diff", verbose=verbose)
|
|
324
370
|
except Exception as e:
|
|
325
371
|
if debug:
|
|
326
372
|
import traceback
|
|
@@ -329,6 +375,10 @@ def main() -> None:
|
|
|
329
375
|
sys.exit(1)
|
|
330
376
|
print(f"\\n HTML: {report_path}")
|
|
331
377
|
print(" Report generated successfully!")
|
|
378
|
+
|
|
379
|
+
# Open in browser unless --no-open
|
|
380
|
+
if not args.no_open:
|
|
381
|
+
_open_in_browser(report_path)
|
|
332
382
|
return
|
|
333
383
|
|
|
334
384
|
# Validate Git repository
|
|
@@ -379,13 +429,13 @@ def main() -> None:
|
|
|
379
429
|
# Generate exports if requested
|
|
380
430
|
has_exports = args.json or args.md or args.csv
|
|
381
431
|
if has_exports:
|
|
382
|
-
generate_exports(files,
|
|
432
|
+
generate_exports(files, output_path, args.json, args.md, args.csv)
|
|
383
433
|
|
|
384
434
|
# Always generate HTML report
|
|
385
435
|
try:
|
|
386
436
|
report_path = generate_report(
|
|
387
437
|
files,
|
|
388
|
-
output_path=
|
|
438
|
+
output_path=output_path,
|
|
389
439
|
staged=args.staged,
|
|
390
440
|
commit_a=commit_a,
|
|
391
441
|
commit_b=commit_b,
|
|
@@ -400,3 +450,7 @@ def main() -> None:
|
|
|
400
450
|
|
|
401
451
|
print(f"\\n HTML: {report_path}")
|
|
402
452
|
print(" Report generated successfully!")
|
|
453
|
+
|
|
454
|
+
# Open in browser unless --no-open
|
|
455
|
+
if not args.no_open:
|
|
456
|
+
_open_in_browser(report_path)
|
|
@@ -45,6 +45,14 @@ def check_git_repo(cwd: Optional[Path] = None) -> bool:
|
|
|
45
45
|
return False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def get_git_root(cwd: Optional[Path] = None) -> Optional[str]:
|
|
49
|
+
"""Get the absolute path to the root of the Git repository."""
|
|
50
|
+
try:
|
|
51
|
+
return _run_git(["rev-parse", "--show-toplevel"], cwd=cwd).strip()
|
|
52
|
+
except GitError:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
48
56
|
def get_diff(
|
|
49
57
|
staged: bool = False,
|
|
50
58
|
commit_a: Optional[str] = None,
|