diff --git a/CHANGELOG.md b/CHANGELOG.md index 7837ad2..766024a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Unreleased -[no unreleased changes yet] +- Search back through renames ## v0.10.0 (2024-06-28) - Enforce only major and minor parts of required Ruby version (loosening the diff --git a/lib/fcom/querier.rb b/lib/fcom/querier.rb index b2b83c4..753b82f 100644 --- a/lib/fcom/querier.rb +++ b/lib/fcom/querier.rb @@ -5,6 +5,7 @@ # This class executes a system command to retrieve the git history, which is passed through `rg` # (ripgrep), and then ultimately is fed back to `fcom` for parsing. class Fcom::Querier + prepend MemoWise include ::Fcom::OptionsHelpers def initialize(options) @@ -13,6 +14,7 @@ def initialize(options) # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/MethodLength def query expression_to_match = search_string expression_to_match = Regexp.escape(expression_to_match).gsub('\\ ', ' ') unless regex_mode? @@ -24,37 +26,88 @@ def query quote = expression_to_match.include?('"') ? "'" : '"' - command = <<~COMMAND.squish - git log - --format="commit %s|%H|%an|%cr (%ci)" - --patch - --full-diff - --no-textconv - #{%(--author="#{author}") if author} - #{"--since=#{days}.day" unless days.nil?} - -- - #{path} - | - - rg #{quote}(#{expression_to_match})|(^commit )|(^diff )#{quote} - --color never - #{'--ignore-case' if ignore_case?} - #{@options[:rg_options]} - | - - #{'exe/' if development?}fcom #{quote}#{search_string}#{quote} - #{"--days #{days}" if days} - #{'--regex' if regex_mode?} - #{'--debug' if debug?} - #{'--ignore-case' if ignore_case?} - --path #{path} - --parse-mode - --repo #{repo} - COMMAND - - Fcom.logger.debug("Executing command: #{command}") - system(command) + commands = + filename_by_most_recent_containing_commit.map do |commit, path_at_commit| + <<~COMMAND.squish + git log + --format="commit %s|%H|%an|%cr (%ci)" + --patch + --full-diff + --no-textconv + #{%(--author="#{author}") if author} + #{"--since=#{days}.day" unless days.nil?} + #{commit} + -- + #{path_at_commit} + | + + rg #{quote}(#{expression_to_match})|(^commit )|(^diff )#{quote} + --color never + #{'--ignore-case' if ignore_case?} + #{@options[:rg_options]} + | + + #{'exe/' if development?}fcom #{quote}#{search_string}#{quote} + #{"--days #{days}" if days} + #{'--regex' if regex_mode?} + #{'--debug' if debug?} + #{'--ignore-case' if ignore_case?} + --path #{path_at_commit} + --parse-mode + --repo #{repo} + COMMAND + end + + previous_command_generated_output = false + + commands.each do |command| + Fcom.logger.debug("Executing command: #{command}") + output = `#{command}` + + if output.empty? + previous_command_generated_output = false + else + if previous_command_generated_output + puts("\n\n") # print blank lines for spacing + end + + previous_command_generated_output = true + + puts(output) + end + end end + # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity # rubocop:enable Metrics/CyclomaticComplexity + + private + + memo_wise \ + def filename_by_most_recent_containing_commit + { + most_recent_commit_with_file => path, + }.merge(renames.transform_keys { "#{_1}^" }) + end + + memo_wise \ + def most_recent_commit_with_file + if system(%(test -e "#{path}")) + 'HEAD' + else + `git log --all -1 --format="%H" -- "#{path}"`.rstrip + end + end + + memo_wise \ + def renames + `git log HEAD --format=%H --name-status --follow --diff-filter=R -- '#{path}'`. + split(/\n(?=[0-9a-f]{40})/). + to_h do |sha_and_name_info| + sha_and_name_info. + match(/(?[0-9a-f]{40})\n\nR\d+\s+(?\S+)?/). + named_captures. + values_at('sha', 'previous_name') + end + end end diff --git a/spec/fcom/querier_spec.rb b/spec/fcom/querier_spec.rb index 279902c..8f929f7 100644 --- a/spec/fcom/querier_spec.rb +++ b/spec/fcom/querier_spec.rb @@ -8,40 +8,58 @@ describe '#query' do subject(:query) { querier.query } - it 'executes a #system call with the expected command' do - # rubocop:disable RSpec/AnyInstance - expect_any_instance_of(Kernel). - to receive(:system). - with(<<~COMMAND.squish) - git log --format="commit %s|%H|%an|%cr (%ci)" --patch --full-diff --no-textconv -- . | - rg "(the_search_string)|(^commit )|(^diff )" --color never | - fcom "the_search_string" --path . --parse-mode --repo testuser/testrepo - COMMAND - # rubocop:enable RSpec/AnyInstance - - query - end + context 'when a system call indicates that the current directory exists' do + before do + expect(querier). + to receive(:system). + with('test -e "."'). + and_return(true) + end - context 'when an author option is provided' do - let(:options) { stubbed_slop_options('the_search_string --author "David Runger"') } + context 'when there have been no file renames' do + before do + expect(querier). + to receive(:`). + with("git log HEAD --format=%H --name-status --follow --diff-filter=R -- '.'"). + and_return('') + end - it 'executes a #system call with the expected command' do - # rubocop:disable RSpec/AnyInstance - expect_any_instance_of(Kernel). - to receive(:system). - with(<<~COMMAND.squish) - git log - --format="commit %s|%H|%an|%cr (%ci)" - --patch --full-diff --no-textconv - --author="David Runger" - -- . - | - rg "(the_search_string)|(^commit )|(^diff )" --color never | - fcom "the_search_string" --path . --parse-mode --repo testuser/testrepo - COMMAND - # rubocop:enable RSpec/AnyInstance - - query + it 'executes a backticks system call with the expected command' do + expect(querier). + to receive(:`). + with(<<~COMMAND.squish). + git log --format="commit %s|%H|%an|%cr (%ci)" --patch --full-diff --no-textconv + HEAD -- . | + rg "(the_search_string)|(^commit )|(^diff )" --color never | + fcom "the_search_string" --path . --parse-mode --repo testuser/testrepo + COMMAND + and_return('') + + query + end + + context 'when an author option is provided' do + let(:options) { stubbed_slop_options('the_search_string --author "David Runger"') } + + it 'executes a backticks system call with the expected command' do + expect(querier). + to receive(:`). + with(<<~COMMAND.squish). + git log + --format="commit %s|%H|%an|%cr (%ci)" + --patch --full-diff --no-textconv + --author="David Runger" + HEAD + -- . + | + rg "(the_search_string)|(^commit )|(^diff )" --color never | + fcom "the_search_string" --path . --parse-mode --repo testuser/testrepo + COMMAND + and_return('') + + query + end + end end end end