gitmeup 1.0.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.
- gitmeup-1.0.0/LICENSE +21 -0
- gitmeup-1.0.0/PKG-INFO +142 -0
- gitmeup-1.0.0/README.md +102 -0
- gitmeup-1.0.0/gitmeup/__init__.py +1 -0
- gitmeup-1.0.0/gitmeup/cli.py +271 -0
- gitmeup-1.0.0/gitmeup.egg-info/PKG-INFO +142 -0
- gitmeup-1.0.0/gitmeup.egg-info/SOURCES.txt +11 -0
- gitmeup-1.0.0/gitmeup.egg-info/dependency_links.txt +1 -0
- gitmeup-1.0.0/gitmeup.egg-info/entry_points.txt +2 -0
- gitmeup-1.0.0/gitmeup.egg-info/requires.txt +2 -0
- gitmeup-1.0.0/gitmeup.egg-info/top_level.txt +1 -0
- gitmeup-1.0.0/pyproject.toml +29 -0
- gitmeup-1.0.0/setup.cfg +4 -0
gitmeup-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 @ikramagix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
21
|
+
IN THE SOFTWARE.
|
gitmeup-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitmeup
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: LLM-assisted conventional commits generator for git repositories. Automatically generates git add and commit lines based on git diff and status.
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2025 @ikramagix
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in
|
|
17
|
+
all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
25
|
+
IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/ikramagix/gitmeup
|
|
28
|
+
Project-URL: Source, https://github.com/ikramagix/gitmeup
|
|
29
|
+
Project-URL: Issues, https://github.com/ikramagix/gitmeup/issues
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Requires-Python: >=3.10
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: google-genai>=0.6.0
|
|
38
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# gitmeup
|
|
42
|
+
|
|
43
|
+
`gitmeup` is a lightweight command-line tool that automates Conventional Commit generation using an LLM.
|
|
44
|
+
It analyses your working tree using `git diff --stat`, `git status`, and a filtered `git diff`, then produces
|
|
45
|
+
atomically grouped `git add` and `git commit` commands with strict path-quoting rules.
|
|
46
|
+
|
|
47
|
+
This improves commit hygiene in large or fast-moving repositories while reducing cognitive overhead.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Extracts:
|
|
52
|
+
- `git diff --stat`
|
|
53
|
+
- `git status --short`
|
|
54
|
+
- `git diff` excluding image and binary noise
|
|
55
|
+
- Sends structured context to an LLM
|
|
56
|
+
- Produces:
|
|
57
|
+
- Precise Conventional Commits
|
|
58
|
+
- Batched `git add` / `git commit` sequences
|
|
59
|
+
- Proper double-quote path escaping
|
|
60
|
+
- Dry-run or apply mode
|
|
61
|
+
- Minimal overhead, only Python and git required
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
### Local development (editable)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/ikramagix/gitmeup
|
|
69
|
+
cd gitmeup
|
|
70
|
+
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
|
|
74
|
+
pip install --upgrade pip
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### System-wide (user environment)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install git+https://github.com/ikramagix/gitmeup
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Ensure `GEMINI_API_KEY` is configured:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export GEMINI_API_KEY="your-key"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You also need a model with:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
export GITMEUP_MODEL="a gemini model"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Usage
|
|
97
|
+
|
|
98
|
+
From any git repository with uncommitted changes:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
gitmeup
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Dry-run prints proposed commands. To apply staged operations:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
gitmeup --apply
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Specify a model:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
gitmeup --model gpt-4.1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Specify an API key manually:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
gitmeup --api-key sk-....
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Example
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
gitmeup
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Sample output:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git add src/app/main.py
|
|
132
|
+
git add src/app/utils/helpers.py
|
|
133
|
+
git commit -m "refactor(core): clean helper logic"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT License. See `LICENSE` for details.
|
|
139
|
+
|
|
140
|
+
## Maintainer
|
|
141
|
+
|
|
142
|
+
Created and maintained by **@ikramagix**.
|
gitmeup-1.0.0/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# gitmeup
|
|
2
|
+
|
|
3
|
+
`gitmeup` is a lightweight command-line tool that automates Conventional Commit generation using an LLM.
|
|
4
|
+
It analyses your working tree using `git diff --stat`, `git status`, and a filtered `git diff`, then produces
|
|
5
|
+
atomically grouped `git add` and `git commit` commands with strict path-quoting rules.
|
|
6
|
+
|
|
7
|
+
This improves commit hygiene in large or fast-moving repositories while reducing cognitive overhead.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Extracts:
|
|
12
|
+
- `git diff --stat`
|
|
13
|
+
- `git status --short`
|
|
14
|
+
- `git diff` excluding image and binary noise
|
|
15
|
+
- Sends structured context to an LLM
|
|
16
|
+
- Produces:
|
|
17
|
+
- Precise Conventional Commits
|
|
18
|
+
- Batched `git add` / `git commit` sequences
|
|
19
|
+
- Proper double-quote path escaping
|
|
20
|
+
- Dry-run or apply mode
|
|
21
|
+
- Minimal overhead, only Python and git required
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### Local development (editable)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/ikramagix/gitmeup
|
|
29
|
+
cd gitmeup
|
|
30
|
+
|
|
31
|
+
python3 -m venv .venv
|
|
32
|
+
source .venv/bin/activate
|
|
33
|
+
|
|
34
|
+
pip install --upgrade pip
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### System-wide (user environment)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install git+https://github.com/ikramagix/gitmeup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ensure `GEMINI_API_KEY` is configured:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export GEMINI_API_KEY="your-key"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You also need a model with:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export GITMEUP_MODEL="a gemini model"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
From any git repository with uncommitted changes:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gitmeup
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Dry-run prints proposed commands. To apply staged operations:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
gitmeup --apply
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Specify a model:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gitmeup --model gpt-4.1
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Specify an API key manually:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
gitmeup --api-key sk-....
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Example
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gitmeup
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Sample output:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git add src/app/main.py
|
|
92
|
+
git add src/app/utils/helpers.py
|
|
93
|
+
git commit -m "refactor(core): clean helper logic"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT License. See `LICENSE` for details.
|
|
99
|
+
|
|
100
|
+
## Maintainer
|
|
101
|
+
|
|
102
|
+
Created and maintained by **@ikramagix**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import shlex
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from google import genai
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SYSTEM_PROMPT = dedent(
|
|
14
|
+
"""
|
|
15
|
+
You are a Conventional Commits writer. You generate precise commit messages that follow Conventional Commits 1.0.0:
|
|
16
|
+
|
|
17
|
+
<type>[optional scope]: <description>
|
|
18
|
+
|
|
19
|
+
Valid types include: feat, fix, chore, docs, style, refactor, perf, test, ci, and revert.
|
|
20
|
+
Use "!" or a BREAKING CHANGE footer for breaking changes.
|
|
21
|
+
Avoid non-standard types.
|
|
22
|
+
Suggest splitting changes into multiple commits when appropriate, and reflect that by outputting multiple git commit commands.
|
|
23
|
+
|
|
24
|
+
You receive:
|
|
25
|
+
- A `git diff --stat` output
|
|
26
|
+
- A `git status` output
|
|
27
|
+
- A `git diff` output where binary/image formats may have been excluded from the diff body
|
|
28
|
+
|
|
29
|
+
RULES FOR DECIDING COMMITS:
|
|
30
|
+
- Keep each commit atomic and semantically focused (feature, refactor, docs, locales, tests, CI, assets, etc.).
|
|
31
|
+
- Never invent files; operate only on files that appear in the provided git status or diff.
|
|
32
|
+
- If staged vs unstaged is unclear, assume everything is unstaged and must be added.
|
|
33
|
+
- If the changes are heterogeneous, split them into multiple commits and multiple batches.
|
|
34
|
+
|
|
35
|
+
STRICT PATH QUOTING (MANDATORY):
|
|
36
|
+
You output git commands that the user will paste directly in a POSIX shell.
|
|
37
|
+
|
|
38
|
+
For every path in git add/rm/mv:
|
|
39
|
+
- Quote the path with double quotes only if it contains characters outside the safe set [A-Za-z0-9._/\\-].
|
|
40
|
+
- Always quote paths containing: space, tab, (, ), [, ], {, }, &, |, ;, *, ?, !, ~, $, `, ', ", <, >, #, %, or any non-ASCII character.
|
|
41
|
+
- Never quote safe paths unnecessarily.
|
|
42
|
+
- Do not invent or "fix" paths; use exactly the paths you see, correctly quoted.
|
|
43
|
+
|
|
44
|
+
COMMAND GROUPING AND ORDER:
|
|
45
|
+
- Group files into small, meaningful batches.
|
|
46
|
+
- For each batch:
|
|
47
|
+
- First output one or more git add/rm/mv commands.
|
|
48
|
+
- Immediately after those, output one git commit -m "type[optional scope]: description" for that batch.
|
|
49
|
+
- Do not include git push or any remote-related commands.
|
|
50
|
+
|
|
51
|
+
OUTPUT FORMAT (VERY IMPORTANT):
|
|
52
|
+
- Respond with one fenced code block with language "bash".
|
|
53
|
+
- Inside that block, output only executable commands, one per line.
|
|
54
|
+
- No prose or comments.
|
|
55
|
+
- You may separate batches with a single blank line between them.
|
|
56
|
+
|
|
57
|
+
STYLE OF COMMIT MESSAGES:
|
|
58
|
+
- Descriptions are short, imperative, and specific.
|
|
59
|
+
"""
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load_env() -> None:
|
|
64
|
+
"""
|
|
65
|
+
Load configuration from env files, without committing secrets.
|
|
66
|
+
|
|
67
|
+
Precedence:
|
|
68
|
+
- Existing environment variables are kept.
|
|
69
|
+
- ~/.gitmeup.env (global, for secrets)
|
|
70
|
+
- ./.env in the current working directory (per-project overrides)
|
|
71
|
+
- CLI --api-key and --model override everything.
|
|
72
|
+
"""
|
|
73
|
+
# Global secrets and defaults: ~/.gitmeup.env
|
|
74
|
+
load_dotenv(dotenv_path=Path.home() / ".gitmeup.env", override=False)
|
|
75
|
+
# Project local overrides: .env in the repo where gitmeup is run
|
|
76
|
+
load_dotenv(dotenv_path=Path.cwd() / ".env", override=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_git(args, check=True):
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
["git"] + args,
|
|
82
|
+
stdout=subprocess.PIPE,
|
|
83
|
+
stderr=subprocess.PIPE,
|
|
84
|
+
text=True,
|
|
85
|
+
)
|
|
86
|
+
if check and result.returncode != 0:
|
|
87
|
+
print(f"git {' '.join(args)} failed:\n{result.stderr}", file=sys.stderr)
|
|
88
|
+
sys.exit(result.returncode)
|
|
89
|
+
return result.stdout
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def ensure_repo():
|
|
93
|
+
try:
|
|
94
|
+
out = run_git(["rev-parse", "--is-inside-work-tree"], check=True).strip()
|
|
95
|
+
except SystemExit:
|
|
96
|
+
print("gitmeup: not inside a git repository.", file=sys.stderr)
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
if out != "true":
|
|
99
|
+
print("gitmeup: not inside a git repository.", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def collect_context():
|
|
104
|
+
diff_stat = run_git(["diff", "--stat"], check=False)
|
|
105
|
+
status = run_git(["status", "--short"], check=False)
|
|
106
|
+
diff_args = [
|
|
107
|
+
"diff",
|
|
108
|
+
"--",
|
|
109
|
+
".",
|
|
110
|
+
":(exclude)*.png",
|
|
111
|
+
":(exclude)*.jpg",
|
|
112
|
+
":(exclude)*.jpeg",
|
|
113
|
+
":(exclude)*.gif",
|
|
114
|
+
":(exclude)*.svg",
|
|
115
|
+
":(exclude)*.webp",
|
|
116
|
+
]
|
|
117
|
+
diff = run_git(diff_args, check=False)
|
|
118
|
+
return diff_stat, status, diff
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_user_prompt(diff_stat, status, diff):
|
|
122
|
+
parts = [
|
|
123
|
+
"# git diff --stat",
|
|
124
|
+
diff_stat.strip() or "(no diff stat)",
|
|
125
|
+
"",
|
|
126
|
+
"# git status --short",
|
|
127
|
+
status.strip() or "(no status)",
|
|
128
|
+
"",
|
|
129
|
+
"# git diff (images/binaries may be excluded)",
|
|
130
|
+
diff.strip() or "(no textual diff)",
|
|
131
|
+
"",
|
|
132
|
+
"# TASK",
|
|
133
|
+
"Based on the changes above, propose git add/rm/mv and git commit commands as per the instructions.",
|
|
134
|
+
]
|
|
135
|
+
return "\n".join(parts)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def call_llm(model, api_key, user_prompt):
|
|
139
|
+
client = genai.Client(api_key=api_key)
|
|
140
|
+
resp = client.models.generate_content(
|
|
141
|
+
model=model,
|
|
142
|
+
contents=user_prompt,
|
|
143
|
+
config={
|
|
144
|
+
"system_instruction": SYSTEM_PROMPT,
|
|
145
|
+
"temperature": 0.0,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
return resp.text
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def extract_bash_block(text):
|
|
152
|
+
"""Extract first ```bash ... ``` block. Return its inner content."""
|
|
153
|
+
in_block = False
|
|
154
|
+
lang_ok = False
|
|
155
|
+
lines = []
|
|
156
|
+
|
|
157
|
+
for line in text.splitlines():
|
|
158
|
+
if line.startswith("```"):
|
|
159
|
+
fence = line.strip()
|
|
160
|
+
if not in_block:
|
|
161
|
+
lang = fence[3:].strip()
|
|
162
|
+
lang_ok = lang == "" or lang.lower() in {"bash", "sh", "shell"}
|
|
163
|
+
in_block = True
|
|
164
|
+
continue
|
|
165
|
+
else:
|
|
166
|
+
break
|
|
167
|
+
elif in_block and lang_ok:
|
|
168
|
+
lines.append(line)
|
|
169
|
+
|
|
170
|
+
return "\n".join(lines).strip()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def parse_commands(block):
|
|
174
|
+
commands = []
|
|
175
|
+
for raw in block.splitlines():
|
|
176
|
+
line = raw.strip()
|
|
177
|
+
if not line:
|
|
178
|
+
continue
|
|
179
|
+
commands.append(shlex.split(line))
|
|
180
|
+
return commands
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def run_commands(commands, apply):
|
|
184
|
+
print("Proposed commands:\n")
|
|
185
|
+
for cmd in commands:
|
|
186
|
+
print(" ".join(shlex.quote(part) for part in cmd))
|
|
187
|
+
|
|
188
|
+
if not apply:
|
|
189
|
+
print("\nDry run: not executing commands. Re-run with --apply to execute.")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
print("\nExecuting commands...\n")
|
|
193
|
+
for cmd in commands:
|
|
194
|
+
print("+", " ".join(shlex.quote(part) for part in cmd))
|
|
195
|
+
result = subprocess.run(cmd)
|
|
196
|
+
if result.returncode != 0:
|
|
197
|
+
print(
|
|
198
|
+
f"Command failed with exit code {result.returncode}. Aborting.",
|
|
199
|
+
file=sys.stderr,
|
|
200
|
+
)
|
|
201
|
+
sys.exit(result.returncode)
|
|
202
|
+
|
|
203
|
+
print("\nCommands executed.\n")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main(argv=None):
|
|
207
|
+
# Load env from ~/.gitmeup.env and ./ .env before reading os.environ
|
|
208
|
+
load_env()
|
|
209
|
+
|
|
210
|
+
parser = argparse.ArgumentParser(
|
|
211
|
+
prog="gitmeup",
|
|
212
|
+
description="Generate Conventional Commits from current git changes using Gemini.",
|
|
213
|
+
)
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
"--model",
|
|
216
|
+
default=os.environ.get("GITMEUP_MODEL", "gemini-2.0-flash-001"),
|
|
217
|
+
help="Gemini model name (default: gemini-2.0-flash-001 or $GITMEUP_MODEL).",
|
|
218
|
+
)
|
|
219
|
+
parser.add_argument(
|
|
220
|
+
"--apply",
|
|
221
|
+
action="store_true",
|
|
222
|
+
help="Execute generated git commands. Without this flag, just print them.",
|
|
223
|
+
)
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"--api-key",
|
|
226
|
+
default=os.environ.get("GEMINI_API_KEY"),
|
|
227
|
+
help="Gemini API key (default: $GEMINI_API_KEY).",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
args = parser.parse_args(argv)
|
|
231
|
+
|
|
232
|
+
if not args.api_key:
|
|
233
|
+
print(
|
|
234
|
+
"Missing Gemini API key. Set GEMINI_API_KEY or use --api-key.",
|
|
235
|
+
file=sys.stderr,
|
|
236
|
+
)
|
|
237
|
+
sys.exit(1)
|
|
238
|
+
|
|
239
|
+
ensure_repo()
|
|
240
|
+
|
|
241
|
+
porcelain = run_git(["status", "--porcelain"], check=False)
|
|
242
|
+
if porcelain.strip() == "":
|
|
243
|
+
print("Working tree clean. Nothing to commit.")
|
|
244
|
+
sys.exit(0)
|
|
245
|
+
|
|
246
|
+
diff_stat, status, diff = collect_context()
|
|
247
|
+
prompt = build_user_prompt(diff_stat, status, diff)
|
|
248
|
+
raw_output = call_llm(args.model, args.api_key, prompt)
|
|
249
|
+
|
|
250
|
+
bash_block = extract_bash_block(raw_output)
|
|
251
|
+
|
|
252
|
+
if not bash_block:
|
|
253
|
+
print(
|
|
254
|
+
"gitmeup: failed to extract bash command block from model output.",
|
|
255
|
+
file=sys.stderr,
|
|
256
|
+
)
|
|
257
|
+
print("Raw output:\n", raw_output)
|
|
258
|
+
sys.exit(1)
|
|
259
|
+
|
|
260
|
+
commands = parse_commands(bash_block)
|
|
261
|
+
run_commands(commands, apply=args.apply)
|
|
262
|
+
|
|
263
|
+
print("\nFinal git status:\n")
|
|
264
|
+
print(run_git(["status", "-sb"], check=False))
|
|
265
|
+
|
|
266
|
+
print("Review your history with:")
|
|
267
|
+
print(" git log --oneline --graph --decorate -n 10")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
main()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitmeup
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: LLM-assisted conventional commits generator for git repositories. Automatically generates git add and commit lines based on git diff and status.
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2025 @ikramagix
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in
|
|
17
|
+
all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
25
|
+
IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/ikramagix/gitmeup
|
|
28
|
+
Project-URL: Source, https://github.com/ikramagix/gitmeup
|
|
29
|
+
Project-URL: Issues, https://github.com/ikramagix/gitmeup/issues
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Requires-Python: >=3.10
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: google-genai>=0.6.0
|
|
38
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# gitmeup
|
|
42
|
+
|
|
43
|
+
`gitmeup` is a lightweight command-line tool that automates Conventional Commit generation using an LLM.
|
|
44
|
+
It analyses your working tree using `git diff --stat`, `git status`, and a filtered `git diff`, then produces
|
|
45
|
+
atomically grouped `git add` and `git commit` commands with strict path-quoting rules.
|
|
46
|
+
|
|
47
|
+
This improves commit hygiene in large or fast-moving repositories while reducing cognitive overhead.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Extracts:
|
|
52
|
+
- `git diff --stat`
|
|
53
|
+
- `git status --short`
|
|
54
|
+
- `git diff` excluding image and binary noise
|
|
55
|
+
- Sends structured context to an LLM
|
|
56
|
+
- Produces:
|
|
57
|
+
- Precise Conventional Commits
|
|
58
|
+
- Batched `git add` / `git commit` sequences
|
|
59
|
+
- Proper double-quote path escaping
|
|
60
|
+
- Dry-run or apply mode
|
|
61
|
+
- Minimal overhead, only Python and git required
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
### Local development (editable)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/ikramagix/gitmeup
|
|
69
|
+
cd gitmeup
|
|
70
|
+
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
|
|
74
|
+
pip install --upgrade pip
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### System-wide (user environment)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install git+https://github.com/ikramagix/gitmeup
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Ensure `GEMINI_API_KEY` is configured:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export GEMINI_API_KEY="your-key"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You also need a model with:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
export GITMEUP_MODEL="a gemini model"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Usage
|
|
97
|
+
|
|
98
|
+
From any git repository with uncommitted changes:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
gitmeup
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Dry-run prints proposed commands. To apply staged operations:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
gitmeup --apply
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Specify a model:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
gitmeup --model gpt-4.1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Specify an API key manually:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
gitmeup --api-key sk-....
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Example
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
gitmeup
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Sample output:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git add src/app/main.py
|
|
132
|
+
git add src/app/utils/helpers.py
|
|
133
|
+
git commit -m "refactor(core): clean helper logic"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT License. See `LICENSE` for details.
|
|
139
|
+
|
|
140
|
+
## Maintainer
|
|
141
|
+
|
|
142
|
+
Created and maintained by **@ikramagix**.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gitmeup/__init__.py
|
|
5
|
+
gitmeup/cli.py
|
|
6
|
+
gitmeup.egg-info/PKG-INFO
|
|
7
|
+
gitmeup.egg-info/SOURCES.txt
|
|
8
|
+
gitmeup.egg-info/dependency_links.txt
|
|
9
|
+
gitmeup.egg-info/entry_points.txt
|
|
10
|
+
gitmeup.egg-info/requires.txt
|
|
11
|
+
gitmeup.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitmeup
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gitmeup"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "LLM-assisted conventional commits generator for git repositories. Automatically generates git add and commit lines based on git diff and status."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { file = "LICENSE" }
|
|
8
|
+
dependencies = [
|
|
9
|
+
"google-genai>=0.6.0",
|
|
10
|
+
"python-dotenv>=1.0.0",
|
|
11
|
+
]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
gitmeup = "gitmeup.cli:main"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/ikramagix/gitmeup"
|
|
24
|
+
Source = "https://github.com/ikramagix/gitmeup"
|
|
25
|
+
Issues = "https://github.com/ikramagix/gitmeup/issues"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=61", "wheel"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|
gitmeup-1.0.0/setup.cfg
ADDED