Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
20 changes: 10 additions & 10 deletions lib/rubycritic/cli/options/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
22 changes: 21 additions & 1 deletion lib/rubycritic/source_control_systems/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Copy link
Contributor Author

@JuanVqz JuanVqz Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFI: This was the main reason we could not run the RubyCritic command outside of the project; the Git class checks for the existence of git in the current path (pwd), and if it exists, it returns a supported? value as true.

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
Expand Down
82 changes: 71 additions & 11 deletions lib/rubycritic/source_control_systems/git/churn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,24 +44,53 @@ 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
@paths.map { |path| git_log_command(path) }
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
Expand Down Expand Up @@ -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)
Expand All @@ -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