Module: CommitGpt::DiffHelpers
- Included in:
- CommitAi
- Defined in:
- lib/commitgpt/diff_helpers.rb
Overview
Helper methods for handling git diffs rubocop:disable Metrics/ModuleLength
Constant Summary collapse
- LOCK_FILES =
Lock files to exclude from diff but detect changes
%w[Gemfile.lock package-lock.json yarn.lock pnpm-lock.yaml].freeze
Instance Method Summary collapse
- #detect_lock_file_changes ⇒ Object
- #git_diff ⇒ Object
- #prompt_diff_handling(current_len, max_len) ⇒ Object
- #prompt_no_staged_changes ⇒ Object
- #split_diff_by_length(diff, max_len) ⇒ Object
Instance Method Details
#detect_lock_file_changes ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/commitgpt/diff_helpers.rb', line 155 def detect_lock_file_changes # Check both staged and unstaged changes for lock files staged_files = `git diff --cached --name-only`.chomp.split("\n") unstaged_files = `git diff --name-only`.chomp.split("\n") changed_files = (staged_files + unstaged_files).uniq updated_locks = LOCK_FILES.select { |lock| changed_files.include?(lock) } return nil if updated_locks.empty? updated_locks.map { |f| "#{f} updated (dependency changes)" }.join(', ') end |
#git_diff ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/commitgpt/diff_helpers.rb', line 10 def git_diff exclusions = LOCK_FILES.map { |f| "\":(exclude)#{f}\"" }.join(' ') diff_cached = `git diff --cached . #{exclusions}`.chomp diff_unstaged = `git diff . #{exclusions}`.chomp # Detect lock file changes and build summary @lock_file_summary = detect_lock_file_changes if !diff_unstaged.empty? if diff_cached.empty? # Scenario: Only unstaged changes choice = prompt_no_staged_changes case choice when :add_all puts '→ Running git add .'.yellow system('git add .') diff_cached = `git diff --cached . #{exclusions}`.chomp if diff_cached.empty? puts '✖ Still no changes to commit.'.red return nil end when :exit return nil end else # Scenario: Mixed state (some staged, some not) puts '⚠ You have both staged and unstaged changes:'.yellow staged_files = `git diff --cached --name-status . #{exclusions}`.chomp unstaged_files = `git diff --name-status . #{exclusions}`.chomp puts "\n #{'Staged changes:'.green}" puts staged_files.gsub(/^/, ' ') puts "\n #{'Unstaged changes:'.red}" puts unstaged_files.gsub(/^/, ' ') puts '' prompt = TTY::Prompt.new choice = prompt.select('How to proceed?') do || .choice 'Include unstaged changes (git add .)', :add_all .choice 'Use staged changes only', :staged_only .choice 'Exit', :exit end case choice when :add_all puts '→ Running git add .'.yellow system('git add .') diff_cached = `git diff --cached . #{exclusions}`.chomp when :exit return nil end end elsif diff_cached.empty? # Scenario: No changes at all (staged or unstaged) # Check if there are ANY unstaged files (maybe untracked?) # git status --porcelain includes untracked files git_status = `git status --porcelain`.chomp if git_status.empty? puts '⚠ No changes to commit. Working tree clean.'.yellow return nil else # Only untracked files? Or ignored files? # If diff_unstaged is empty but git status is not, it usually means untracked files. # Let's offer to add them too. choice = prompt_no_staged_changes case choice when :add_all puts '→ Running git add .'.yellow system('git add .') diff_cached = `git diff --cached . #{exclusions}`.chomp when :exit return nil end end end diff = diff_cached # Prepend lock file summary to diff if present diff = "#{@lock_file_summary}\n\n#{diff}" if @lock_file_summary if diff.length > diff_len choice = prompt_diff_handling(diff.length, diff_len) case choice when :chunked @chunked_mode = true puts "→ Smart chunked mode: splitting #{diff.length} chars into ~#{(diff.length.to_f / diff_len).ceil} segments...".yellow when :truncate puts "→ Truncating diff to #{diff_len} chars...".yellow diff = diff[0...diff_len] when :unlimited puts "→ Using full diff (#{diff.length} chars)...".yellow when :exit return nil end end diff end |
#prompt_diff_handling(current_len, max_len) ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/commitgpt/diff_helpers.rb', line 125 def prompt_diff_handling(current_len, max_len) puts "⚠ The diff is too large (#{current_len} chars, max #{max_len}).".yellow prompt = TTY::Prompt.new begin prompt.select('Choose an option:') do || .choice 'Smart chunked: split into segments and synthesize (recommended)', :chunked .choice "Use first #{max_len} characters to generate commit message", :truncate .choice 'Use unlimited characters (may fail or be slow)', :unlimited .choice 'Exit', :exit end rescue TTY::Reader::InputInterrupt, Interrupt :exit end end |
#prompt_no_staged_changes ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/commitgpt/diff_helpers.rb', line 112 def prompt_no_staged_changes puts '⚠ No staged changes found (but unstaged/untracked files exist).'.yellow prompt = TTY::Prompt.new begin prompt.select('Choose an option:') do || .choice "Run 'git add .' to stage all changes", :add_all .choice 'Exit (stage files manually)', :exit end rescue TTY::Reader::InputInterrupt, Interrupt :exit end end |
#split_diff_by_length(diff, max_len) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/commitgpt/diff_helpers.rb', line 140 def split_diff_by_length(diff, max_len) chunks = [] current_chunk = '' diff.each_line do |line| if current_chunk.length + line.length > max_len && !current_chunk.empty? chunks << current_chunk current_chunk = '' end current_chunk += line end chunks << current_chunk unless current_chunk.empty? chunks end |