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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18b1522d4ba03cddfe1536bb6ee06cab7b12c7b3368c1883ebda20825c89673f
4
- data.tar.gz: 62e9d5d3a7f0b89292ed993ab601a63fa91134abb49ecda8bbbc349384a3aff8
3
+ metadata.gz: 75fb5c81e9b1d07b927497d979188dc2e936711395a8b9aa6919065d6ed0903b
4
+ data.tar.gz: 612044fb323c934e36209942ef16004e7acdb7e441641da6b4e016a976a4cd37
5
5
  SHA512:
6
- metadata.gz: e3ee6ea8c6e9de921c13bb63b0c5d0e9232754b3cca4c39d1bc2de4b6cabb54ad1f0800ced7cafbaa60a13f22d5b5cd0ebff85541912c5b7dd670fd8d0982f86
7
- data.tar.gz: a9f369e2008ed297e21fa88f049ba36ed3446ad72ecdc845537bcd20529c947bc31df69fc7195b554ff7b094ce139ff8717735b9b3b5124099ab86292b3a7bce
6
+ metadata.gz: 578fd8cf70256433e05bb75ea58df3f216170e5f5f20f8b54547c8f23fd17758637eb2c5aa0fc895cf5c69c8ec32f1db23cb6c1da67773d93747917e722ea2fe
7
+ data.tar.gz: dd583910a1e13dcc2deb729e4a2d78ed60efde03617e6521370f11a2efcf094718ff1257e203660eb243e61ea250934f6a6a1f2d14a8cf06e014226159ede5aa
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
- ## [Unreleased]
1
+ ## [0.1.1] - 2024-12-13
2
+
3
+ - Remove debug logging output from API requests for cleaner user experience
4
+ - Add commented debugging options for developers
2
5
 
3
6
  ## [0.1.0] - 2024-12-12
4
7
 
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # GitAuto πŸ€–βœ¨
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/git_auto.svg)](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
- ## Requirements βš™οΈ
87
+ ## Usage πŸ› οΈ
88
88
 
89
- - Ruby >= 3.0.0
90
- - Git repository with staged changes
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
- *Coming soon...*
103
+ ![GitAuto Setup](./screenshots/setup.png)
104
+ ![GitAuto Config](./screenshots/config.png)
105
+ ![GitAuto Commit](./screenshots/commit.png)
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[:is_clean] || status[:has_staged_changes]
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
- return DEFAULT_SETTINGS.dup unless File.exist?(CONFIG_FILE)
73
-
74
- user_settings = YAML.load_file(CONFIG_FILE) || {}
75
- DEFAULT_SETTINGS.merge(user_settings)
76
- rescue StandardError
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. Available providers: #{SUPPORTED_PROVIDERS.keys.join(", ")}"
83
+ raise Error, "Unsupported AI provider: #{options[:ai_provider]}"
83
84
  end
84
85
 
85
- return unless options[:ai_model]
86
-
87
- provider = options[:ai_provider] || get(:ai_provider)
88
- available_models = SUPPORTED_PROVIDERS[provider][:models]
89
- return if available_models.values.include?(options[:ai_model])
90
-
91
- raise Error, "Unsupported model for #{provider}. Available models: #{available_models.keys.join(", ")}"
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 # Skip index and file indicator lines
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) # Exponential backoff
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
- log_api_request("openai", payload, temperature) if ENV["DEBUG"]
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
- log_api_request("claude", payload, temperature)
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 # Use first directory as scope
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 GitError < StandardError; end
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 = 10)
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
- begin
49
- execute_git_command("rev-parse", "--abbrev-ref", "HEAD")
50
- rescue GitError
51
- "main" # Default branch name for new repositories
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: working_directory_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
- return true if in_git_repository?
70
-
71
- raise GitError, "Not a git repository. Initialize one with 'git init'"
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
- return true if has_staged_changes?
76
-
77
- raise GitError, "No changes staged for commit. Use 'git add' to stage changes"
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
- false
89
- rescue GitError
90
- true
70
+ !execute_git_command("diff", "--cached", "--quiet")
71
+ $CHILD_STATUS.exitstatus == 1
91
72
  end
92
73
 
93
- def working_directory_clean?
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 GitError
81
+ rescue StandardError
101
82
  false
102
83
  end
103
84
 
104
85
  def execute_git_command(*args)
105
- result = IO.popen(["git", *args], err: [:child, :out], &:read)
86
+ output = IO.popen(["git", *args], err: [:child, :out], &:read)
106
87
 
107
- raise GitError, "Git command failed: #{result.strip}" unless $CHILD_STATUS.success?
88
+ unless $CHILD_STATUS.success?
89
+ raise Error, "Git command failed: git #{args.join(' ')}\n#{output}"
90
+ end
108
91
 
109
- result.strip
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/_-]+)\))? # Optional scope in parentheses
25
+ (\((?<scope>[a-z0-9/_-]+)\))? # Optional scope in parentheses
26
26
  :\s # Colon and space separator
27
27
  (?<description>.+) # Commit description
28
28
  }x
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitAuto
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/git_auto.rb CHANGED
@@ -42,7 +42,6 @@ module GitAuto
42
42
  end
43
43
  end
44
44
 
45
- # Register the hooks with RubyGems
46
45
  Gem.post_install do |installer|
47
46
  GitAuto.install if installer.spec.name == "git_auto"
48
47
  end
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.0
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-14 00:00:00.000000000 Z
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 message generator
267
+ summary: AI-powered git commit messages using OpenAI or Anthropic APIs
268
268
  test_files: []