diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bb6e9f..e95e82ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * [BUGFIX] Work around issue preventing feature execution on Ruby 3.5.0dev (by [@faisal][]) * [CHANGE] Add changes or suppress warnings for issues found by newer rubocop (by [@faisal][]) * [CHANGE] Update CI checkout action to v4 (by [@faisal][]) +* [CHANGE] Run RubyCritic outside of the project without losing the churn value (by [@juanvqz][]) # v4.9.2 / 2025-04-08 [(commits)](https://github.com/whitesmith/rubycritic/compare/v4.9.1...v4.9.2) diff --git a/lib/rubycritic/cli/options/file.rb b/lib/rubycritic/cli/options/file.rb index 1e1c4413..a6cda245 100644 --- a/lib/rubycritic/cli/options/file.rb +++ b/lib/rubycritic/cli/options/file.rb @@ -24,11 +24,11 @@ def to_h root: root, coverage_path: coverage_path, formats: formats, - deduplicate_symlinks: deduplicate_symlinks, + deduplicate_symlinks: deduplicate_symlinks?, paths: paths, - suppress_ratings: suppress_ratings, + suppress_ratings: suppress_ratings?, minimum_score: minimum_score, - no_browser: no_browser, + no_browser: no_browser?, base_branch: base_branch, feature_branch: feature_branch, threshold_score: threshold_score @@ -68,16 +68,16 @@ def threshold_score options['threshold_score'] end - def deduplicate_symlinks - value_for(options['deduplicate_symlinks']) + def deduplicate_symlinks? + value_for?(options['deduplicate_symlinks']) end - def suppress_ratings - value_for(options['suppress_ratings']) + def suppress_ratings? + value_for?(options['suppress_ratings']) end - def no_browser - value_for(options['no_browser']) + def no_browser? + value_for?(options['no_browser']) end def formats @@ -96,7 +96,7 @@ def paths options['paths'] end - def value_for(value) + def value_for?(value) value = value.to_s value == 'true' unless value.empty? end diff --git a/lib/rubycritic/source_control_systems/git.rb b/lib/rubycritic/source_control_systems/git.rb index 428cd151..5f6780f7 100644 --- a/lib/rubycritic/source_control_systems/git.rb +++ b/lib/rubycritic/source_control_systems/git.rb @@ -21,8 +21,28 @@ def git(arg) self.class.git(arg) end + # :reek:DuplicateMethodCall + # :reek:NilCheck def self.supported? - git('branch 2>&1') && $CHILD_STATUS.success? + return true if git('branch 2>&1') && $CHILD_STATUS.success? + + return false if Config.paths.nil? || Config.paths.empty? + + Config.paths.any? do |path| + absolute_path = File.expand_path(path) + check_git_repository?(absolute_path) + end + end + + # :reek:DuplicateMethodCall + def self.check_git_repository?(path) + current_path = File.expand_path(path) + while current_path != File.dirname(current_path) + return true if Dir.exist?(File.join(current_path, '.git')) + + current_path = File.dirname(current_path) + end + false end def self.to_s diff --git a/lib/rubycritic/source_control_systems/git/churn.rb b/lib/rubycritic/source_control_systems/git/churn.rb index 83c84fa1..01b0c191 100644 --- a/lib/rubycritic/source_control_systems/git/churn.rb +++ b/lib/rubycritic/source_control_systems/git/churn.rb @@ -20,12 +20,16 @@ def current(name) end end + # :reek:TooManyInstanceVariables + # rubocop:disable Metrics/ClassLength class Churn + # :reek:TooManyStatements def initialize(churn_after: nil, paths: ['.']) @churn_after = churn_after @paths = Array(paths) @date = nil @stats = {} + @git_root = find_git_root call end @@ -40,16 +44,32 @@ def date_of_last_commit(path) private + # :reek:DuplicateMethodCall + def find_git_root + @paths.each do |path| + current_path = File.expand_path(path) + while current_path != File.dirname(current_path) + return current_path if Dir.exist?(File.join(current_path, '.git')) + + current_path = File.dirname(current_path) + end + end + Dir.pwd + end + def call git_log_commands.each { |log_command| exec_git_command(log_command) } end def exec_git_command(command) - Git - .git(command) - .split("\n") - .reject(&:empty?) - .each { |line| process_line(line) } + # Run git command from the git repository root + Dir.chdir(@git_root) do + Git + .git(command) + .split("\n") + .reject(&:empty?) + .each { |line| process_line(line) } + end end def git_log_commands @@ -57,7 +77,20 @@ def git_log_commands end def git_log_command(path) - "log --all --date=iso --follow --format='format:date:%x09%ad' --name-status #{after_clause}#{path}" + # Convert absolute paths to relative paths from git root + relative_path = make_relative_to_git_root(path) + "log --all --date=iso --follow --format='format:date:%x09%ad' --name-status #{after_clause}#{relative_path}" + end + + def make_relative_to_git_root(path) + absolute_path = File.expand_path(path) + if absolute_path.start_with?(@git_root) + # Convert to relative path from git root + absolute_path[(@git_root.length + 1)..] || '.' + else + # If path is not within git root, use as is + path + end end def after_clause @@ -87,13 +120,18 @@ def process_rename(from, to) process_file(to) end + # :reek:DuplicateMethodCall def filename_for_subdirectory(filename) - git_path = Git.git('rev-parse --show-toplevel') - cd_path = Dir.pwd - if cd_path.length > git_path.length - filename = filename.sub(/^#{Regexp.escape("#{File.basename(cd_path)}/")}/, '') + if @git_root == Dir.pwd + git_path = Git.git('rev-parse --show-toplevel') + cd_path = Dir.pwd + if cd_path.length > git_path.length + filename = filename.sub(/^#{Regexp.escape("#{File.basename(cd_path)}/")}/, '') + end + [filename] + else + filename end - [filename] end def process_file(filename) @@ -109,10 +147,32 @@ def renames @renames ||= Renames.new end + # :reek:TooManyStatements + # rubocop:disable Metrics/MethodLength def stats(path) + # Try the path as-is first + result = @stats.fetch(path, nil) + return result if result + + # If not found, try converting absolute path to relative path from git root + absolute_path = File.expand_path(path) + if absolute_path.start_with?(@git_root) + relative_path = absolute_path[(@git_root.length + 1)..] + return @stats.fetch(relative_path, Stats.new(0)) + end + + # If still not found, try converting relative path to absolute path + unless path.start_with?('/') + absolute_path = File.expand_path(path, @git_root) + return @stats.fetch(absolute_path, Stats.new(0)) + end + + # Default fallback @stats.fetch(path, Stats.new(0)) end + # rubocop:enable Metrics/MethodLength end + # rubocop:enable Metrics/ClassLength end end end