git_auto 0.1.0 β 0.1.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -1
- data/README.md +29 -18
- data/lib/git_auto/commands/commit_message_command.rb +1 -1
- data/lib/git_auto/config/settings.rb +16 -15
- data/lib/git_auto/formatters/diff_formatter.rb +1 -1
- data/lib/git_auto/services/ai_service.rb +12 -21
- data/lib/git_auto/services/git_service.rb +34 -53
- data/lib/git_auto/services/history_service.rb +0 -4
- data/lib/git_auto/validators/commit_message_validator.rb +1 -1
- data/lib/git_auto/version.rb +1 -1
- data/lib/git_auto.rb +0 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75fb5c81e9b1d07b927497d979188dc2e936711395a8b9aa6919065d6ed0903b
|
4
|
+
data.tar.gz: 612044fb323c934e36209942ef16004e7acdb7e441641da6b4e016a976a4cd37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 578fd8cf70256433e05bb75ea58df3f216170e5f5f20f8b54547c8f23fd17758637eb2c5aa0fc895cf5c69c8ec32f1db23cb6c1da67773d93747917e722ea2fe
|
7
|
+
data.tar.gz: dd583910a1e13dcc2deb729e4a2d78ed60efde03617e6521370f11a2efcf094718ff1257e203660eb243e61ea250934f6a6a1f2d14a8cf06e014226159ede5aa
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# GitAuto π€β¨
|
2
2
|
|
3
|
+
[](https://rubygems.org/gems/git_auto)
|
4
|
+
|
3
5
|
> AI-powered commit messages that make sense
|
4
6
|
|
5
7
|
GitAuto is a Ruby gem that streamlines your git workflow by automatically generating meaningful commit messages using AI. Say goodbye to generic commit messages and hello to clear, consistent, and informative descriptions of your changes.
|
@@ -17,8 +19,20 @@ GitAuto is a Ruby gem that streamlines your git workflow by automatically genera
|
|
17
19
|
- Anthropic (Claude 3.5 Sonnet, Claude 3.5 Haiku)
|
18
20
|
- π **Secure Storage**: Your API keys are encrypted using AES-256-CBC and stored securely
|
19
21
|
|
22
|
+
## Requirements βοΈ
|
23
|
+
|
24
|
+
- Ruby >= 3.0.0
|
25
|
+
- Git repository with staged changes
|
26
|
+
- ποΈ One magical ingredient: an API key! Choose your AI companion:
|
27
|
+
- π OpenAI API key ([Get one here](https://platform.openai.com/api-keys))
|
28
|
+
- ποΈ Anthropic API key ([Get one here](https://console.anthropic.com/))
|
29
|
+
|
30
|
+
That's it! Say goodbye to "misc fixes" and hello to commits that actually tell a story. Your future self will thank you! π©β¨
|
31
|
+
|
20
32
|
## Installation π
|
21
33
|
|
34
|
+
Install the gem from [RubyGems](https://rubygems.org/gems/git_auto):
|
35
|
+
|
22
36
|
```bash
|
23
37
|
gem install git_auto
|
24
38
|
```
|
@@ -29,20 +43,6 @@ Or add to your Gemfile:
|
|
29
43
|
gem 'git_auto'
|
30
44
|
```
|
31
45
|
|
32
|
-
## Usage π οΈ
|
33
|
-
|
34
|
-
1. Stage your changes as usual:
|
35
|
-
```bash
|
36
|
-
git add .
|
37
|
-
```
|
38
|
-
|
39
|
-
2. Generate a commit message:
|
40
|
-
```bash
|
41
|
-
git-auto commit
|
42
|
-
```
|
43
|
-
|
44
|
-
3. Review, edit if needed, and confirm!
|
45
|
-
|
46
46
|
## Setup and Configuration π§
|
47
47
|
|
48
48
|
### Initial Setup
|
@@ -84,14 +84,25 @@ GitAuto can also be configured through environment variables:
|
|
84
84
|
- `GIT_AUTO_MODEL`: OpenAI model to use (default: gpt-3.5-turbo)
|
85
85
|
- `GIT_AUTO_SECRET`: Custom encryption key for storing API keys (optional)
|
86
86
|
|
87
|
-
##
|
87
|
+
## Usage π οΈ
|
88
88
|
|
89
|
-
|
90
|
-
|
89
|
+
1. Stage your changes as usual:
|
90
|
+
```bash
|
91
|
+
git add .
|
92
|
+
```
|
93
|
+
|
94
|
+
2. Generate a commit message:
|
95
|
+
```bash
|
96
|
+
git-auto commit
|
97
|
+
```
|
98
|
+
|
99
|
+
3. Review, edit if needed, and confirm!
|
91
100
|
|
92
101
|
## Screenshots πΈ
|
93
102
|
|
94
|
-
|
103
|
+

|
104
|
+

|
105
|
+

|
95
106
|
|
96
107
|
## Roadmap πΊοΈ
|
97
108
|
|
@@ -48,7 +48,7 @@ module GitAuto
|
|
48
48
|
|
49
49
|
# Repository and Change Validation Methods
|
50
50
|
def validate_repository(status)
|
51
|
-
return if status[:
|
51
|
+
return if status[:has_staged_changes]
|
52
52
|
|
53
53
|
puts "βΉοΈ Status:".blue
|
54
54
|
puts " Branch: #{status[:branch]}"
|
@@ -6,6 +6,8 @@ require "fileutils"
|
|
6
6
|
module GitAuto
|
7
7
|
module Config
|
8
8
|
class Settings
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
9
11
|
CONFIG_DIR = File.expand_path("~/.git_auto")
|
10
12
|
CONFIG_FILE = File.join(CONFIG_DIR, "config.yml")
|
11
13
|
|
@@ -65,30 +67,29 @@ module GitAuto
|
|
65
67
|
private
|
66
68
|
|
67
69
|
def ensure_config_dir
|
68
|
-
FileUtils.mkdir_p(CONFIG_DIR)
|
70
|
+
FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
|
69
71
|
end
|
70
72
|
|
71
73
|
def load_settings
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
DEFAULT_SETTINGS.dup
|
74
|
+
if File.exist?(CONFIG_FILE)
|
75
|
+
YAML.load_file(CONFIG_FILE).transform_keys(&:to_sym)
|
76
|
+
else
|
77
|
+
DEFAULT_SETTINGS.dup
|
78
|
+
end
|
78
79
|
end
|
79
80
|
|
80
81
|
def validate_settings!(options)
|
81
82
|
if options[:ai_provider] && !SUPPORTED_PROVIDERS.key?(options[:ai_provider])
|
82
|
-
raise Error, "Unsupported AI provider
|
83
|
+
raise Error, "Unsupported AI provider: #{options[:ai_provider]}"
|
83
84
|
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
86
|
+
if options[:ai_model]
|
87
|
+
provider = options[:ai_provider] || @settings[:ai_provider]
|
88
|
+
valid_models = SUPPORTED_PROVIDERS[provider][:models].values
|
89
|
+
unless valid_models.include?(options[:ai_model])
|
90
|
+
raise Error, "Unsupported AI model: #{options[:ai_model]}"
|
91
|
+
end
|
92
|
+
end
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
@@ -15,7 +15,7 @@ module GitAuto
|
|
15
15
|
current_file = extract_file_name(line)
|
16
16
|
formatted << "\nChanges in #{current_file}:"
|
17
17
|
when /^index |^---|\+\+\+/
|
18
|
-
next
|
18
|
+
next
|
19
19
|
when /^@@ .* @@/
|
20
20
|
formatted << format_hunk_header(line)
|
21
21
|
when /^\+/
|
@@ -147,7 +147,7 @@ module GitAuto
|
|
147
147
|
rescue StandardError => e
|
148
148
|
retries += 1
|
149
149
|
if retries < MAX_RETRIES
|
150
|
-
sleep(retries * BACKOFF_BASE)
|
150
|
+
sleep(retries * BACKOFF_BASE)
|
151
151
|
retry
|
152
152
|
end
|
153
153
|
raise e
|
@@ -202,7 +202,8 @@ module GitAuto
|
|
202
202
|
temperature: temperature
|
203
203
|
}
|
204
204
|
|
205
|
-
|
205
|
+
# Uncomment the following line to see the API request and response details for debugging
|
206
|
+
# log_api_request("openai", payload, temperature) if ENV["DEBUG"]
|
206
207
|
|
207
208
|
response = HTTP.auth("Bearer #{api_key}")
|
208
209
|
.headers(accept: "application/json")
|
@@ -266,7 +267,9 @@ module GitAuto
|
|
266
267
|
]
|
267
268
|
}
|
268
269
|
|
269
|
-
|
270
|
+
# Uncomment the following lines to see the API request and response details for debugging
|
271
|
+
# log_api_request("claude", payload, temperature)
|
272
|
+
# log_api_response(response.body)
|
270
273
|
|
271
274
|
response = HTTP.headers({
|
272
275
|
"Content-Type" => "application/json",
|
@@ -274,8 +277,6 @@ module GitAuto
|
|
274
277
|
"anthropic-version" => "2023-06-01"
|
275
278
|
}).post(CLAUDE_API_URL, json: payload)
|
276
279
|
|
277
|
-
log_api_response(response.body)
|
278
|
-
|
279
280
|
message = handle_response(response)
|
280
281
|
message = message.downcase.strip
|
281
282
|
message = message.sub(/\.$/, "") # Remove trailing period if present
|
@@ -300,29 +301,27 @@ module GitAuto
|
|
300
301
|
case response.code
|
301
302
|
when 200
|
302
303
|
json = JSON.parse(response.body.to_s)
|
303
|
-
puts "Debug - API Response: #{json.inspect}"
|
304
|
+
# puts "Debug - API Response: #{json.inspect}"
|
304
305
|
case @settings.get(:ai_provider)
|
305
306
|
when "openai"
|
306
307
|
message = json.dig("choices", 0, "message", "content")
|
307
308
|
if message.nil? || message.empty?
|
308
|
-
puts "Debug - No content in response: #{json}"
|
309
|
+
# puts "Debug - No content in response: #{json}"
|
309
310
|
raise Error, "No message content in response"
|
310
311
|
end
|
311
312
|
message.split("\n").first.strip
|
312
313
|
when "claude"
|
313
314
|
content = json.dig("content", 0, "text")
|
314
|
-
puts "Debug - Claude content: #{content.inspect}"
|
315
|
+
# puts "Debug - Claude content: #{content.inspect}"
|
315
316
|
|
316
317
|
if content.nil? || content.empty?
|
317
|
-
puts "Debug - No content in response: #{json}"
|
318
|
+
# puts "Debug - No content in response: #{json}"
|
318
319
|
raise Error, "No message content in response"
|
319
320
|
end
|
320
321
|
|
321
|
-
# Split into lines and find the commit message
|
322
322
|
lines = content.split("\n").map(&:strip).reject(&:empty?)
|
323
|
-
puts "Debug - Lines: #{lines.inspect}"
|
323
|
+
# puts "Debug - Lines: #{lines.inspect}"
|
324
324
|
|
325
|
-
# Take the first non-empty line as it should be just the commit message
|
326
325
|
message = lines.first
|
327
326
|
|
328
327
|
if message.nil? || !message.match?(/^[a-z]+:/)
|
@@ -334,11 +333,9 @@ module GitAuto
|
|
334
333
|
when 401
|
335
334
|
raise APIKeyError, "Invalid API key" unless ENV["RACK_ENV"] == "test"
|
336
335
|
|
337
|
-
# Return mock response in test environment
|
338
336
|
@test_call_count ||= 0
|
339
337
|
@test_call_count += 1
|
340
338
|
|
341
|
-
# Simulate rate limiting after 3 calls
|
342
339
|
raise RateLimitError, "Rate limit exceeded. Please try again later." if @test_call_count > 3
|
343
340
|
|
344
341
|
"test commit message"
|
@@ -351,25 +348,19 @@ module GitAuto
|
|
351
348
|
end
|
352
349
|
|
353
350
|
def infer_scope_from_diff(diff)
|
354
|
-
# Extract the most common directory or file type from the diff
|
355
351
|
files = diff.scan(/^diff --git.*?b\/(.+)$/).flatten
|
356
352
|
return nil if files.empty?
|
357
353
|
|
358
|
-
# Try to get a meaningful scope from the file paths
|
359
354
|
scopes = files.map do |file|
|
360
355
|
parts = file.split('/')
|
361
356
|
if parts.length > 1
|
362
|
-
parts.first
|
357
|
+
parts.first
|
363
358
|
else
|
364
|
-
# For files in root, use the basename without extension
|
365
359
|
basename = File.basename(file, '.*')
|
366
360
|
|
367
|
-
# Filter out generic names and keep meaningful ones
|
368
361
|
if basename =~ /^(.*?)\d*$/
|
369
|
-
# Remove any trailing numbers
|
370
362
|
$1
|
371
363
|
else
|
372
|
-
# Keep the full name if it's meaningful
|
373
364
|
basename
|
374
365
|
end
|
375
366
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "English"
|
4
|
+
|
4
5
|
module GitAuto
|
5
6
|
module Services
|
6
7
|
class GitService
|
7
|
-
class
|
8
|
+
class Error < StandardError; end
|
8
9
|
|
9
10
|
def get_staged_diff
|
10
11
|
validate_git_repository!
|
@@ -19,46 +20,34 @@ module GitAuto
|
|
19
20
|
def commit(message)
|
20
21
|
validate_git_repository!
|
21
22
|
validate_staged_changes!
|
22
|
-
# Ensure we only use the first line for the commit message
|
23
23
|
first_line = message.split("\n").first.strip
|
24
24
|
execute_git_command("commit", "-m", first_line)
|
25
25
|
end
|
26
26
|
|
27
|
-
def get_commit_history(limit =
|
28
|
-
validate_git_repository!
|
29
|
-
# Check if there are any commits
|
30
|
-
if has_commits?
|
31
|
-
execute_git_command("log", "--pretty=format:%h|%s|%an|%ad", "--date=short",
|
32
|
-
"-#{limit}").split("\n").map do |line|
|
33
|
-
hash, subject, author, date = line.split("|")
|
34
|
-
{
|
35
|
-
hash: hash,
|
36
|
-
subject: subject,
|
37
|
-
author: author,
|
38
|
-
date: date
|
39
|
-
}
|
40
|
-
end
|
41
|
-
else
|
42
|
-
[]
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def current_branch
|
27
|
+
def get_commit_history(limit = nil)
|
47
28
|
validate_git_repository!
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
29
|
+
format = '%H%n%s%n%an%n%aI'
|
30
|
+
command = ["log", "--pretty=format:#{format}", "--no-merges"]
|
31
|
+
command << "-#{limit}" if limit
|
32
|
+
|
33
|
+
output = execute_git_command(*command)
|
34
|
+
return [] if output.empty?
|
35
|
+
|
36
|
+
output.split("\n\n").map do |commit|
|
37
|
+
hash, subject, author, date = commit.split("\n")
|
38
|
+
{
|
39
|
+
hash: hash,
|
40
|
+
subject: subject,
|
41
|
+
author: author,
|
42
|
+
date: date
|
43
|
+
}
|
52
44
|
end
|
53
45
|
end
|
54
46
|
|
55
47
|
def repository_status
|
56
|
-
validate_git_repository!
|
57
48
|
{
|
58
|
-
branch: current_branch,
|
59
|
-
staged_files: get_staged_files,
|
60
49
|
has_staged_changes: has_staged_changes?,
|
61
|
-
is_clean:
|
50
|
+
is_clean: is_clean?,
|
62
51
|
has_commits: has_commits?
|
63
52
|
}
|
64
53
|
end
|
@@ -66,49 +55,41 @@ module GitAuto
|
|
66
55
|
private
|
67
56
|
|
68
57
|
def validate_git_repository!
|
69
|
-
|
70
|
-
|
71
|
-
|
58
|
+
unless File.directory?(".git")
|
59
|
+
raise Error, "Not a git repository (or any of the parent directories)"
|
60
|
+
end
|
72
61
|
end
|
73
62
|
|
74
63
|
def validate_staged_changes!
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
def in_git_repository?
|
81
|
-
Dir.exist?(".git") || !execute_git_command("rev-parse", "--git-dir").empty?
|
82
|
-
rescue StandardError
|
83
|
-
false
|
64
|
+
unless has_staged_changes?
|
65
|
+
raise Error, "No changes staged for commit"
|
66
|
+
end
|
84
67
|
end
|
85
68
|
|
86
69
|
def has_staged_changes?
|
87
|
-
execute_git_command("diff", "--cached", "--quiet")
|
88
|
-
|
89
|
-
rescue GitError
|
90
|
-
true
|
70
|
+
!execute_git_command("diff", "--cached", "--quiet")
|
71
|
+
$CHILD_STATUS.exitstatus == 1
|
91
72
|
end
|
92
73
|
|
93
|
-
def
|
74
|
+
def is_clean?
|
94
75
|
execute_git_command("status", "--porcelain").empty?
|
95
76
|
end
|
96
77
|
|
97
78
|
def has_commits?
|
98
79
|
execute_git_command("rev-parse", "--verify", "HEAD")
|
99
80
|
true
|
100
|
-
rescue
|
81
|
+
rescue StandardError
|
101
82
|
false
|
102
83
|
end
|
103
84
|
|
104
85
|
def execute_git_command(*args)
|
105
|
-
|
86
|
+
output = IO.popen(["git", *args], err: [:child, :out], &:read)
|
106
87
|
|
107
|
-
|
88
|
+
unless $CHILD_STATUS.success?
|
89
|
+
raise Error, "Git command failed: git #{args.join(' ')}\n#{output}"
|
90
|
+
end
|
108
91
|
|
109
|
-
|
110
|
-
rescue Errno::ENOENT
|
111
|
-
raise GitError, "Git executable not found. Please ensure git is installed and in your PATH"
|
92
|
+
output
|
112
93
|
end
|
113
94
|
end
|
114
95
|
end
|
@@ -26,7 +26,6 @@ module GitAuto
|
|
26
26
|
metadata: metadata
|
27
27
|
})
|
28
28
|
|
29
|
-
# Keep only the last MAX_HISTORY_ENTRIES
|
30
29
|
history = history.take(MAX_HISTORY_ENTRIES)
|
31
30
|
|
32
31
|
save_history(history)
|
@@ -134,12 +133,9 @@ module GitAuto
|
|
134
133
|
end
|
135
134
|
|
136
135
|
def extract_phrases(message)
|
137
|
-
# Extract common verb phrases from the message
|
138
|
-
# Ignore type/scope for conventional commits
|
139
136
|
content = message.sub(/^[a-z]+(\([^)]+\))?:\s*/, "")
|
140
137
|
words = content.downcase.split(/[^a-z]+/).reject(&:empty?)
|
141
138
|
|
142
|
-
# Get 2-3 word phrases
|
143
139
|
phrases = []
|
144
140
|
words.each_cons(2) { |phrase| phrases << phrase.join(" ") }
|
145
141
|
words.each_cons(3) { |phrase| phrases << phrase.join(" ") }
|
@@ -22,7 +22,7 @@ module GitAuto
|
|
22
22
|
|
23
23
|
CONVENTIONAL_COMMIT_PATTERN = %r{
|
24
24
|
^(?<type>#{TYPES.keys.join("|")}) # Commit type
|
25
|
-
(\((?<scope>[a-z0-9/_-]+)\))?
|
25
|
+
(\((?<scope>[a-z0-9/_-]+)\))? # Optional scope in parentheses
|
26
26
|
:\s # Colon and space separator
|
27
27
|
(?<description>.+) # Commit description
|
28
28
|
}x
|
data/lib/git_auto/version.rb
CHANGED
data/lib/git_auto.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_auto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillermo Diaz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -264,5 +264,5 @@ requirements: []
|
|
264
264
|
rubygems_version: 3.5.23
|
265
265
|
signing_key:
|
266
266
|
specification_version: 4
|
267
|
-
summary: AI-powered git commit
|
267
|
+
summary: AI-powered git commit messages using OpenAI or Anthropic APIs
|
268
268
|
test_files: []
|