git-history-extraction 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.
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: git-history-extraction
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extract and analyze git commit history with support for filtering by trailers, date ranges, and AI-powered summarization for changelogs
|
|
5
|
+
Keywords: git,changelog,history,commit,trailers,ai,summarization,openai,gemini
|
|
6
|
+
Author: Michael Bianco
|
|
7
|
+
Author-email: Michael Bianco <mike@mikebian.co>
|
|
8
|
+
Requires-Dist: click>=8.0
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Project-URL: Repository, https://github.com/iloveitaly/git-history-extraction
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# git-history-extraction
|
|
14
|
+
|
|
15
|
+
A tool to extract and filter git commit history, making it easy to pipe to AI tools for changelog generation and summaries.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- Extract git commits with metadata (SHA, date, files, message)
|
|
20
|
+
- Filter commits by time range or starting commit
|
|
21
|
+
- Extract and filter git trailers (e.g., `Co-authored-by`, `User-Facing`)
|
|
22
|
+
- Output in simple text or JSON format
|
|
23
|
+
- Pipe output to AI tools (OpenAI, Gemini, Claude) for automated summarization
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
### Using uv (Recommended)
|
|
28
|
+
|
|
29
|
+
No installation needed! The tool can be run directly:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run git-history-extraction --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Install as Package
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install git-history-extraction
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Basic Examples
|
|
44
|
+
|
|
45
|
+
Extract commits from the last 24 hours (default):
|
|
46
|
+
```bash
|
|
47
|
+
git-history-extraction
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Extract commits from the last 7 days:
|
|
51
|
+
```bash
|
|
52
|
+
git-history-extraction --since "7 days ago"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Extract commits from a specific repository:
|
|
56
|
+
```bash
|
|
57
|
+
git-history-extraction --repo /path/to/repo --since "1 week ago"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Output Formats
|
|
61
|
+
|
|
62
|
+
Simple text format (default):
|
|
63
|
+
```bash
|
|
64
|
+
git-history-extraction --since "1 day ago"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
JSON format for piping to other tools:
|
|
68
|
+
```bash
|
|
69
|
+
git-history-extraction --since "1 day ago" --format json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Commit Range Selection
|
|
73
|
+
|
|
74
|
+
By time range:
|
|
75
|
+
```bash
|
|
76
|
+
git-history-extraction --since "2024-01-01"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
From a specific commit to HEAD:
|
|
80
|
+
```bash
|
|
81
|
+
git-history-extraction --since-commit abc1234
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Git Trailers
|
|
85
|
+
|
|
86
|
+
Extract specific trailers only (case-insensitive):
|
|
87
|
+
```bash
|
|
88
|
+
git-history-extraction --since "1 week ago" --trailers "co-authored-by,reviewed-by"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Piping to AI Tools for Summarization
|
|
92
|
+
|
|
93
|
+
This tool extracts and formats git history, making it easy to pipe to AI tools for summarization. The tool itself **does not perform AI summarization**—it prepares the data so you can use your preferred AI tool.
|
|
94
|
+
|
|
95
|
+
The tool enables you to extract targeted slices of git history for different audiences. For example, use git trailers like `User-Facing:` to mark end-user changes, then extract and pipe them to AI for changelogs or internal notifications.
|
|
96
|
+
|
|
97
|
+
### Creating Structured Git Trailers with AI
|
|
98
|
+
|
|
99
|
+
Combine with [aiautocommit](https://github.com/iloveitaly/aiautocommit) to automatically generate git trailers during commits. This creates a structured history that can be easily filtered and summarized for different audiences.
|
|
100
|
+
|
|
101
|
+
**Example custom commit prompt for aiautocommit:**
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
# IMPORTANT: Your Instructions
|
|
105
|
+
|
|
106
|
+
You are an expert software developer. Generate a commit message from the `git diff` output below using these rules:
|
|
107
|
+
|
|
108
|
+
## 1. Subject Line
|
|
109
|
+
|
|
110
|
+
- Use a conventional commit prefix:
|
|
111
|
+
- `feat`: New features
|
|
112
|
+
- `fix`: Bug fixes, including user-visible design or style fixes.
|
|
113
|
+
- `docs`: Changes only to internal documentation (e.g., `.md`, `.rst`) or code comments.
|
|
114
|
+
- `style`: Formatting, linting, or code style changes in code files.
|
|
115
|
+
- `refactor`: Code structure improvements (no behavior changes).
|
|
116
|
+
- `build`: Updates to build scripts or configs (e.g., `Makefile`, `Justfile`, `Dockerfile`, `package.json`).
|
|
117
|
+
- `deploy`: Deployment script or IAC updates.
|
|
118
|
+
- `test`: Changes to tests
|
|
119
|
+
- Add optional scope in parentheses when changes affect a specific module (e.g., `feat(auth): add login`)
|
|
120
|
+
- Limit to 50 characters after the prefix and scope.
|
|
121
|
+
- Use imperative mood (e.g., "improve layout").
|
|
122
|
+
- Describe the intent or outcome (e.g., "prevent text overflow" not "add break-all").
|
|
123
|
+
- Be specific about the change ("validate email format" not "improve validation").
|
|
124
|
+
|
|
125
|
+
## 2. Extended Commit Message
|
|
126
|
+
|
|
127
|
+
- Include only if changes have non-obvious implications, fix complex bugs, or introduce breaking changes.
|
|
128
|
+
- Separate from subject with one blank line.
|
|
129
|
+
- Use markdown bullets focusing on **why** the change was needed and **what impact** it has.
|
|
130
|
+
- Mention side effects, user impact, or important trade-offs.
|
|
131
|
+
|
|
132
|
+
## 3. User-facing Changes
|
|
133
|
+
|
|
134
|
+
If the change is something that a end-user (not internal admin!) would see, include a `User-facing:` git trailer with a sentence
|
|
135
|
+
or two explaining, to the user, what they would see differently because of this change.
|
|
136
|
+
|
|
137
|
+
## 4. General Guidelines
|
|
138
|
+
|
|
139
|
+
- Prioritize the purpose of changes over listing tools or properties used.
|
|
140
|
+
- Keep concise; avoid obvious or verbose details.
|
|
141
|
+
- Always generate a message based on the diff, even with limited context.
|
|
142
|
+
|
|
143
|
+
## 5. Scopes
|
|
144
|
+
|
|
145
|
+
Optional scopes (e.g., `feat(api):`):
|
|
146
|
+
|
|
147
|
+
- `match`: frontend or backend changes tied
|
|
148
|
+
- `site`: content, additional pages, etc for the static site content
|
|
149
|
+
- `internal-admin`: internal admin changes (including CMS)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With this setup, commits automatically get `User-facing:` trailers. You can then extract and summarize them:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Extract only user-facing changes from the last sprint
|
|
156
|
+
git-history-extraction --since "last monday" --trailers "User-facing" | \
|
|
157
|
+
gemini -i "Create a user-friendly changelog from these changes"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Using with Gemini CLI
|
|
161
|
+
|
|
162
|
+
Extract user-facing changes and generate a non-technical summary:
|
|
163
|
+
```bash
|
|
164
|
+
git-history-extraction --repo . --since "last monday" \
|
|
165
|
+
--trailers "User-Facing" | \
|
|
166
|
+
gemini -i "This is a compressed git history identifying user-facing changes. \
|
|
167
|
+
Can you write a 1-2 sentence overview of the changes, with a list of bullets \
|
|
168
|
+
identifying changes. This is for a non-technical internal audience, letting \
|
|
169
|
+
them know what the development team has done. Separate into 'new' and 'fixed' \
|
|
170
|
+
sections. Include a 'Updates Since' with the date of the first commit in the \
|
|
171
|
+
history. Remove fluff, keep it concise and information dense."
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Using the OpenAI Playground Script (Optional)
|
|
175
|
+
|
|
176
|
+
An optional playground script is included that demonstrates OpenAI integration:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Generate summary with OpenAI
|
|
180
|
+
git-history-extraction --since "1 week ago" --format json | \
|
|
181
|
+
uv run playground/summarize_commits.py
|
|
182
|
+
|
|
183
|
+
# Preview the prompt without calling OpenAI
|
|
184
|
+
git-history-extraction --since "1 week ago" --format json | \
|
|
185
|
+
uv run playground/summarize_commits.py --dump-prompt
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Requirements:**
|
|
189
|
+
- `OPENAI_API_KEY` environment variable
|
|
190
|
+
- The script uses GPT-4o-mini by default
|
|
191
|
+
|
|
192
|
+
This is just an example—you can pipe to any AI tool you prefer. See [playground/README.md](playground/README.md) for more details.
|
|
193
|
+
|
|
194
|
+
## Output Format
|
|
195
|
+
|
|
196
|
+
### Simple Format
|
|
197
|
+
|
|
198
|
+
Each commit is displayed with:
|
|
199
|
+
- **Commit:** SHA hash
|
|
200
|
+
- **Date:** ISO 8601 timestamp
|
|
201
|
+
- **Files:** Comma-separated list of changed files
|
|
202
|
+
- **Message:** Commit body with trailers removed
|
|
203
|
+
|
|
204
|
+
### JSON Format
|
|
205
|
+
|
|
206
|
+
Array of commit objects:
|
|
207
|
+
```json
|
|
208
|
+
[
|
|
209
|
+
{
|
|
210
|
+
"sha": "abc123...",
|
|
211
|
+
"date": "2024-10-31T08:00:00-06:00",
|
|
212
|
+
"body": "commit message with trailers",
|
|
213
|
+
"files": ["file1.py", "file2.md"]
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Options
|
|
219
|
+
|
|
220
|
+
| Option | Description | Default |
|
|
221
|
+
|--------|-------------|---------|
|
|
222
|
+
| `--since TEXT` | ISO date/time or relative time | `"24 hours ago"` |
|
|
223
|
+
| `--since-commit TEXT` | Start from specific commit (overrides `--since`) | None |
|
|
224
|
+
| `--repo DIRECTORY` | Path to git repository | `.` (current directory) |
|
|
225
|
+
| `--trailers TEXT` | Comma-separated trailer keys to extract | None (show all) |
|
|
226
|
+
| `--format [simple\|json]` | Output format | `simple` |
|
|
227
|
+
|
|
228
|
+
## How It Works
|
|
229
|
+
|
|
230
|
+
- Uses `git log` with custom formatting for efficient single-pass extraction
|
|
231
|
+
- Parses commit metadata, body, and file changes in one command
|
|
232
|
+
- Intelligently extracts git trailers from commit messages
|
|
233
|
+
- No per-commit subprocess calls for optimal performance
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
The main logic lives in `git_history_extraction/__init__.py`. The tool is structured as a Python package with:
|
|
238
|
+
|
|
239
|
+
- **main.py:** Entry point script
|
|
240
|
+
- **git_history_extraction/:** Core module with extraction logic
|
|
241
|
+
- **playground/:** Optional AI summarization scripts
|
|
242
|
+
|
|
243
|
+
### Running Tests
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pytest
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Limitations
|
|
250
|
+
|
|
251
|
+
- Large commit ranges may generate significant output; consider narrowing the time range
|
|
252
|
+
- This tool extracts and formats data only—AI summarization requires external tools
|
|
253
|
+
- Git must be available in PATH
|
|
254
|
+
|
|
255
|
+
## Requirements
|
|
256
|
+
|
|
257
|
+
- Python >= 3.9
|
|
258
|
+
- git
|
|
259
|
+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# git-history-extraction
|
|
2
|
+
|
|
3
|
+
A tool to extract and filter git commit history, making it easy to pipe to AI tools for changelog generation and summaries.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Extract git commits with metadata (SHA, date, files, message)
|
|
8
|
+
- Filter commits by time range or starting commit
|
|
9
|
+
- Extract and filter git trailers (e.g., `Co-authored-by`, `User-Facing`)
|
|
10
|
+
- Output in simple text or JSON format
|
|
11
|
+
- Pipe output to AI tools (OpenAI, Gemini, Claude) for automated summarization
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Using uv (Recommended)
|
|
16
|
+
|
|
17
|
+
No installation needed! The tool can be run directly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run git-history-extraction --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Install as Package
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install git-history-extraction
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Basic Examples
|
|
32
|
+
|
|
33
|
+
Extract commits from the last 24 hours (default):
|
|
34
|
+
```bash
|
|
35
|
+
git-history-extraction
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Extract commits from the last 7 days:
|
|
39
|
+
```bash
|
|
40
|
+
git-history-extraction --since "7 days ago"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Extract commits from a specific repository:
|
|
44
|
+
```bash
|
|
45
|
+
git-history-extraction --repo /path/to/repo --since "1 week ago"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Output Formats
|
|
49
|
+
|
|
50
|
+
Simple text format (default):
|
|
51
|
+
```bash
|
|
52
|
+
git-history-extraction --since "1 day ago"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
JSON format for piping to other tools:
|
|
56
|
+
```bash
|
|
57
|
+
git-history-extraction --since "1 day ago" --format json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Commit Range Selection
|
|
61
|
+
|
|
62
|
+
By time range:
|
|
63
|
+
```bash
|
|
64
|
+
git-history-extraction --since "2024-01-01"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
From a specific commit to HEAD:
|
|
68
|
+
```bash
|
|
69
|
+
git-history-extraction --since-commit abc1234
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Git Trailers
|
|
73
|
+
|
|
74
|
+
Extract specific trailers only (case-insensitive):
|
|
75
|
+
```bash
|
|
76
|
+
git-history-extraction --since "1 week ago" --trailers "co-authored-by,reviewed-by"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Piping to AI Tools for Summarization
|
|
80
|
+
|
|
81
|
+
This tool extracts and formats git history, making it easy to pipe to AI tools for summarization. The tool itself **does not perform AI summarization**—it prepares the data so you can use your preferred AI tool.
|
|
82
|
+
|
|
83
|
+
The tool enables you to extract targeted slices of git history for different audiences. For example, use git trailers like `User-Facing:` to mark end-user changes, then extract and pipe them to AI for changelogs or internal notifications.
|
|
84
|
+
|
|
85
|
+
### Creating Structured Git Trailers with AI
|
|
86
|
+
|
|
87
|
+
Combine with [aiautocommit](https://github.com/iloveitaly/aiautocommit) to automatically generate git trailers during commits. This creates a structured history that can be easily filtered and summarized for different audiences.
|
|
88
|
+
|
|
89
|
+
**Example custom commit prompt for aiautocommit:**
|
|
90
|
+
|
|
91
|
+
```markdown
|
|
92
|
+
# IMPORTANT: Your Instructions
|
|
93
|
+
|
|
94
|
+
You are an expert software developer. Generate a commit message from the `git diff` output below using these rules:
|
|
95
|
+
|
|
96
|
+
## 1. Subject Line
|
|
97
|
+
|
|
98
|
+
- Use a conventional commit prefix:
|
|
99
|
+
- `feat`: New features
|
|
100
|
+
- `fix`: Bug fixes, including user-visible design or style fixes.
|
|
101
|
+
- `docs`: Changes only to internal documentation (e.g., `.md`, `.rst`) or code comments.
|
|
102
|
+
- `style`: Formatting, linting, or code style changes in code files.
|
|
103
|
+
- `refactor`: Code structure improvements (no behavior changes).
|
|
104
|
+
- `build`: Updates to build scripts or configs (e.g., `Makefile`, `Justfile`, `Dockerfile`, `package.json`).
|
|
105
|
+
- `deploy`: Deployment script or IAC updates.
|
|
106
|
+
- `test`: Changes to tests
|
|
107
|
+
- Add optional scope in parentheses when changes affect a specific module (e.g., `feat(auth): add login`)
|
|
108
|
+
- Limit to 50 characters after the prefix and scope.
|
|
109
|
+
- Use imperative mood (e.g., "improve layout").
|
|
110
|
+
- Describe the intent or outcome (e.g., "prevent text overflow" not "add break-all").
|
|
111
|
+
- Be specific about the change ("validate email format" not "improve validation").
|
|
112
|
+
|
|
113
|
+
## 2. Extended Commit Message
|
|
114
|
+
|
|
115
|
+
- Include only if changes have non-obvious implications, fix complex bugs, or introduce breaking changes.
|
|
116
|
+
- Separate from subject with one blank line.
|
|
117
|
+
- Use markdown bullets focusing on **why** the change was needed and **what impact** it has.
|
|
118
|
+
- Mention side effects, user impact, or important trade-offs.
|
|
119
|
+
|
|
120
|
+
## 3. User-facing Changes
|
|
121
|
+
|
|
122
|
+
If the change is something that a end-user (not internal admin!) would see, include a `User-facing:` git trailer with a sentence
|
|
123
|
+
or two explaining, to the user, what they would see differently because of this change.
|
|
124
|
+
|
|
125
|
+
## 4. General Guidelines
|
|
126
|
+
|
|
127
|
+
- Prioritize the purpose of changes over listing tools or properties used.
|
|
128
|
+
- Keep concise; avoid obvious or verbose details.
|
|
129
|
+
- Always generate a message based on the diff, even with limited context.
|
|
130
|
+
|
|
131
|
+
## 5. Scopes
|
|
132
|
+
|
|
133
|
+
Optional scopes (e.g., `feat(api):`):
|
|
134
|
+
|
|
135
|
+
- `match`: frontend or backend changes tied
|
|
136
|
+
- `site`: content, additional pages, etc for the static site content
|
|
137
|
+
- `internal-admin`: internal admin changes (including CMS)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
With this setup, commits automatically get `User-facing:` trailers. You can then extract and summarize them:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Extract only user-facing changes from the last sprint
|
|
144
|
+
git-history-extraction --since "last monday" --trailers "User-facing" | \
|
|
145
|
+
gemini -i "Create a user-friendly changelog from these changes"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Using with Gemini CLI
|
|
149
|
+
|
|
150
|
+
Extract user-facing changes and generate a non-technical summary:
|
|
151
|
+
```bash
|
|
152
|
+
git-history-extraction --repo . --since "last monday" \
|
|
153
|
+
--trailers "User-Facing" | \
|
|
154
|
+
gemini -i "This is a compressed git history identifying user-facing changes. \
|
|
155
|
+
Can you write a 1-2 sentence overview of the changes, with a list of bullets \
|
|
156
|
+
identifying changes. This is for a non-technical internal audience, letting \
|
|
157
|
+
them know what the development team has done. Separate into 'new' and 'fixed' \
|
|
158
|
+
sections. Include a 'Updates Since' with the date of the first commit in the \
|
|
159
|
+
history. Remove fluff, keep it concise and information dense."
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Using the OpenAI Playground Script (Optional)
|
|
163
|
+
|
|
164
|
+
An optional playground script is included that demonstrates OpenAI integration:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Generate summary with OpenAI
|
|
168
|
+
git-history-extraction --since "1 week ago" --format json | \
|
|
169
|
+
uv run playground/summarize_commits.py
|
|
170
|
+
|
|
171
|
+
# Preview the prompt without calling OpenAI
|
|
172
|
+
git-history-extraction --since "1 week ago" --format json | \
|
|
173
|
+
uv run playground/summarize_commits.py --dump-prompt
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Requirements:**
|
|
177
|
+
- `OPENAI_API_KEY` environment variable
|
|
178
|
+
- The script uses GPT-4o-mini by default
|
|
179
|
+
|
|
180
|
+
This is just an example—you can pipe to any AI tool you prefer. See [playground/README.md](playground/README.md) for more details.
|
|
181
|
+
|
|
182
|
+
## Output Format
|
|
183
|
+
|
|
184
|
+
### Simple Format
|
|
185
|
+
|
|
186
|
+
Each commit is displayed with:
|
|
187
|
+
- **Commit:** SHA hash
|
|
188
|
+
- **Date:** ISO 8601 timestamp
|
|
189
|
+
- **Files:** Comma-separated list of changed files
|
|
190
|
+
- **Message:** Commit body with trailers removed
|
|
191
|
+
|
|
192
|
+
### JSON Format
|
|
193
|
+
|
|
194
|
+
Array of commit objects:
|
|
195
|
+
```json
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
"sha": "abc123...",
|
|
199
|
+
"date": "2024-10-31T08:00:00-06:00",
|
|
200
|
+
"body": "commit message with trailers",
|
|
201
|
+
"files": ["file1.py", "file2.md"]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Options
|
|
207
|
+
|
|
208
|
+
| Option | Description | Default |
|
|
209
|
+
|--------|-------------|---------|
|
|
210
|
+
| `--since TEXT` | ISO date/time or relative time | `"24 hours ago"` |
|
|
211
|
+
| `--since-commit TEXT` | Start from specific commit (overrides `--since`) | None |
|
|
212
|
+
| `--repo DIRECTORY` | Path to git repository | `.` (current directory) |
|
|
213
|
+
| `--trailers TEXT` | Comma-separated trailer keys to extract | None (show all) |
|
|
214
|
+
| `--format [simple\|json]` | Output format | `simple` |
|
|
215
|
+
|
|
216
|
+
## How It Works
|
|
217
|
+
|
|
218
|
+
- Uses `git log` with custom formatting for efficient single-pass extraction
|
|
219
|
+
- Parses commit metadata, body, and file changes in one command
|
|
220
|
+
- Intelligently extracts git trailers from commit messages
|
|
221
|
+
- No per-commit subprocess calls for optimal performance
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
The main logic lives in `git_history_extraction/__init__.py`. The tool is structured as a Python package with:
|
|
226
|
+
|
|
227
|
+
- **main.py:** Entry point script
|
|
228
|
+
- **git_history_extraction/:** Core module with extraction logic
|
|
229
|
+
- **playground/:** Optional AI summarization scripts
|
|
230
|
+
|
|
231
|
+
### Running Tests
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pytest
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Limitations
|
|
238
|
+
|
|
239
|
+
- Large commit ranges may generate significant output; consider narrowing the time range
|
|
240
|
+
- This tool extracts and formats data only—AI summarization requires external tools
|
|
241
|
+
- Git must be available in PATH
|
|
242
|
+
|
|
243
|
+
## Requirements
|
|
244
|
+
|
|
245
|
+
- Python >= 3.9
|
|
246
|
+
- git
|
|
247
|
+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_commit_files(sha: str, repo_path: Path | None = None) -> list[str]:
|
|
8
|
+
cmd = ["git", "diff-tree", "--no-commit-id", "--name-only", "-r", sha]
|
|
9
|
+
result = subprocess.run(
|
|
10
|
+
cmd,
|
|
11
|
+
capture_output=True,
|
|
12
|
+
text=True,
|
|
13
|
+
check=True,
|
|
14
|
+
cwd=str(repo_path) if repo_path else None,
|
|
15
|
+
)
|
|
16
|
+
return [l.strip() for l in result.stdout.splitlines() if l.strip()]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_git_commits(since, since_commit=None, repo_path: Path | None = None):
|
|
20
|
+
rec_sep = "\x1e"
|
|
21
|
+
fld_sep = "\x1f"
|
|
22
|
+
end_hdr = "\x1d"
|
|
23
|
+
|
|
24
|
+
pretty = f"{rec_sep}%H{fld_sep}%cI{fld_sep}%B{end_hdr}"
|
|
25
|
+
if since_commit:
|
|
26
|
+
cmd = [
|
|
27
|
+
"git",
|
|
28
|
+
"log",
|
|
29
|
+
f"{since_commit}..HEAD",
|
|
30
|
+
f"--pretty=format:{pretty}",
|
|
31
|
+
"--name-only",
|
|
32
|
+
]
|
|
33
|
+
else:
|
|
34
|
+
cmd = [
|
|
35
|
+
"git",
|
|
36
|
+
"log",
|
|
37
|
+
f"--since={since}",
|
|
38
|
+
f"--pretty=format:{pretty}",
|
|
39
|
+
"--name-only",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
cmd,
|
|
44
|
+
capture_output=True,
|
|
45
|
+
text=True,
|
|
46
|
+
check=True,
|
|
47
|
+
cwd=str(repo_path) if repo_path else None,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
commits: list[dict] = []
|
|
51
|
+
stream = result.stdout
|
|
52
|
+
for block in stream.split(rec_sep):
|
|
53
|
+
if not block.strip():
|
|
54
|
+
continue
|
|
55
|
+
if end_hdr not in block:
|
|
56
|
+
continue
|
|
57
|
+
header, files_blob = block.split(end_hdr, 1)
|
|
58
|
+
parts = header.split(fld_sep, 2)
|
|
59
|
+
if len(parts) != 3:
|
|
60
|
+
continue
|
|
61
|
+
sha, date_iso, body = parts
|
|
62
|
+
files = [l.strip() for l in files_blob.splitlines() if l.strip()]
|
|
63
|
+
commits.append(
|
|
64
|
+
{
|
|
65
|
+
"sha": sha.strip(),
|
|
66
|
+
"date": date_iso.strip(),
|
|
67
|
+
"body": body.strip(),
|
|
68
|
+
"files": files,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return commits
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def remove_git_trailers(commit_body: str) -> str:
|
|
76
|
+
lines = commit_body.splitlines()
|
|
77
|
+
trailer_regex = re.compile(r"^\s*[-*]?\s*[^:]+:\s*.*$")
|
|
78
|
+
|
|
79
|
+
while lines and not lines[-1].strip():
|
|
80
|
+
lines.pop()
|
|
81
|
+
|
|
82
|
+
while lines and trailer_regex.match(lines[-1]):
|
|
83
|
+
lines.pop()
|
|
84
|
+
|
|
85
|
+
while lines and not lines[-1].strip():
|
|
86
|
+
lines.pop()
|
|
87
|
+
|
|
88
|
+
return "\n".join(lines)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def extract_git_trailers(commit_body: str) -> list[tuple[str, str]]:
|
|
92
|
+
lines = commit_body.splitlines()
|
|
93
|
+
trailer_regex = re.compile(r"^\s*[-*]?\s*([^:]+):\s*(.*)$")
|
|
94
|
+
|
|
95
|
+
idx = len(lines) - 1
|
|
96
|
+
while idx >= 0 and not lines[idx].strip():
|
|
97
|
+
idx -= 1
|
|
98
|
+
|
|
99
|
+
collected: list[tuple[str, str]] = []
|
|
100
|
+
seen_keys: set[tuple[str, str]] = set()
|
|
101
|
+
j = idx
|
|
102
|
+
while j >= 0:
|
|
103
|
+
m = trailer_regex.match(lines[j])
|
|
104
|
+
if not m:
|
|
105
|
+
break
|
|
106
|
+
pair = (m.group(1), m.group(2))
|
|
107
|
+
collected.append(pair)
|
|
108
|
+
seen_keys.add((m.group(1).strip().lower(), m.group(2).strip()))
|
|
109
|
+
j -= 1
|
|
110
|
+
collected.reverse()
|
|
111
|
+
|
|
112
|
+
for line in lines:
|
|
113
|
+
m = trailer_regex.match(line)
|
|
114
|
+
if not m:
|
|
115
|
+
continue
|
|
116
|
+
key_norm = (m.group(1).strip().lower(), m.group(2).strip())
|
|
117
|
+
if key_norm in seen_keys:
|
|
118
|
+
continue
|
|
119
|
+
collected.append((m.group(1), m.group(2)))
|
|
120
|
+
seen_keys.add(key_norm)
|
|
121
|
+
|
|
122
|
+
return collected
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@click.command()
|
|
126
|
+
@click.option(
|
|
127
|
+
"--since",
|
|
128
|
+
type=str,
|
|
129
|
+
default="24 hours ago",
|
|
130
|
+
help="ISO date/time or relative time (default: '24 hours ago')",
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--since-commit",
|
|
134
|
+
type=str,
|
|
135
|
+
default=None,
|
|
136
|
+
help="Specific commit sha to start from (e.g. abc123). Overrides --since if provided.",
|
|
137
|
+
)
|
|
138
|
+
@click.option(
|
|
139
|
+
"--repo",
|
|
140
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
141
|
+
default=Path("."),
|
|
142
|
+
help="Path to the git repository to summarize.",
|
|
143
|
+
)
|
|
144
|
+
@click.option(
|
|
145
|
+
"--trailers",
|
|
146
|
+
type=str,
|
|
147
|
+
default=None,
|
|
148
|
+
help="Comma-separated trailer key(s) to output (case-insensitive).",
|
|
149
|
+
)
|
|
150
|
+
@click.option(
|
|
151
|
+
"--format",
|
|
152
|
+
type=click.Choice(["simple", "json"]),
|
|
153
|
+
default="simple",
|
|
154
|
+
help="Output format (default: simple)",
|
|
155
|
+
)
|
|
156
|
+
def main(since: str, since_commit: str | None, repo: Path, trailers: str | None, format: str):
|
|
157
|
+
commits = get_git_commits(since, since_commit, repo_path=repo)
|
|
158
|
+
if not commits:
|
|
159
|
+
click.echo("No commits found using the specified parameters.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
if trailers is not None:
|
|
163
|
+
selectors = {part.strip().lower() for part in trailers.split(",") if part.strip()}
|
|
164
|
+
out_lines: list[str] = []
|
|
165
|
+
for c in commits:
|
|
166
|
+
trailer_items = extract_git_trailers(c["body"]) or []
|
|
167
|
+
if selectors:
|
|
168
|
+
trailer_items = [t for t in trailer_items if t[0].lower() in selectors]
|
|
169
|
+
if not trailer_items:
|
|
170
|
+
continue
|
|
171
|
+
files = ", ".join(c.get("files", []))
|
|
172
|
+
out_lines.append(f"Commit: {c['sha']}\nDate: {c['date']}\nFiles: {files}")
|
|
173
|
+
out_lines.extend([f"{k}: {v}" for k, v in trailer_items])
|
|
174
|
+
out_lines.append("")
|
|
175
|
+
click.echo("\n".join(out_lines).rstrip())
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if format == "json":
|
|
179
|
+
import json
|
|
180
|
+
click.echo(json.dumps(commits, indent=2))
|
|
181
|
+
else:
|
|
182
|
+
for c in commits:
|
|
183
|
+
body = remove_git_trailers(c["body"]) or "(no message)"
|
|
184
|
+
files = ", ".join(c.get("files", []))
|
|
185
|
+
click.echo(f"Commit: {c['sha']}")
|
|
186
|
+
click.echo(f"Date: {c['date']}")
|
|
187
|
+
click.echo(f"Files: {files}")
|
|
188
|
+
click.echo(f"\n{body}\n")
|
|
189
|
+
click.echo("-" * 80)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "git-history-extraction"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Extract and analyze git commit history with support for filtering by trailers, date ranges, and AI-powered summarization for changelogs"
|
|
5
|
+
keywords = ["git", "changelog", "history", "commit", "trailers", "ai", "summarization", "openai", "gemini"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = ["click>=8.0"]
|
|
9
|
+
authors = [{ name = "Michael Bianco", email = "mike@mikebian.co" }]
|
|
10
|
+
urls = { "Repository" = "https://github.com/iloveitaly/git-history-extraction" }
|
|
11
|
+
|
|
12
|
+
# additional packaging information: https://packaging.python.org/en/latest/specifications/core-metadata/#license
|
|
13
|
+
[project.scripts]
|
|
14
|
+
git-history-extraction = "git_history_extraction:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.8.11,<0.9.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.uv.build-backend]
|
|
21
|
+
# avoids the src/ directory structure
|
|
22
|
+
module-root = ""
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = ["pytest>=8.3.3"]
|
|
26
|
+
|