kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.1 KiB
202 lines
5.1 KiB
#!/usr/bin/env ruby
|
|
|
|
# A sneaky wrapper around Rubocop that allows you to run it only against
|
|
# the recent changes, as opposed to the whole project. It lets you
|
|
# enforce the style guide for new/modified code only, as opposed to
|
|
# having to restyle everything or adding cops incrementally. It relies
|
|
# on git to figure out which files to check.
|
|
#
|
|
# Here are some options you can pass in addition to the ones in rubocop:
|
|
#
|
|
# --local Check only the changes you are about to push
|
|
# to the remote repository.
|
|
#
|
|
# --staged Check only changes that are currently staged.
|
|
#
|
|
# --uncommitted Check only changes in files that have not been
|
|
# --index committed (i.e. either in working directory or
|
|
# staged).
|
|
#
|
|
# --against REFSPEC Check changes since REFSPEC. This can be
|
|
# anything that git will recognize.
|
|
#
|
|
# --branch Check only changes in the current branch.
|
|
#
|
|
# Caveat emptor:
|
|
#
|
|
# * Monkey patching ahead. This script relies on Rubocop internals and
|
|
# has been tested against 1.30.1. Newer (or older) versions might
|
|
# break it.
|
|
#
|
|
# * While it does try to check modified lines only, there might be some
|
|
# quirks. It might not show offenses in modified code if they are
|
|
# reported at unmodified lines. It might also show offenses in
|
|
# unmodified code if they are reported in modified lines.
|
|
|
|
# Inspired and adapted from
|
|
# https://gist.github.com/skanev/9d4bec97d5a6825eaaf6
|
|
# https://gist.github.com/MaxLap/ea4b6d1df81de3024562798b5501b9c8
|
|
|
|
require 'rubocop'
|
|
|
|
module DirtyCop
|
|
extend self # In your face, style guide!
|
|
|
|
def bury_evidence?(file, line)
|
|
!report_offense_at?(file, line)
|
|
end
|
|
|
|
def bury_evidences(file, offenses)
|
|
offenses.reject { |o| bury_evidence?(file, o.line) }
|
|
end
|
|
|
|
def staged_changes_only?
|
|
!!@staged_changes_only
|
|
end
|
|
|
|
def uncovered_targets
|
|
@files
|
|
end
|
|
|
|
def cover_up_unmodified(ref)
|
|
@files = files_modified_since(ref)
|
|
@line_filter = build_line_filter(@files, ref)
|
|
end
|
|
|
|
def process_bribe
|
|
eat_a_donut if ARGV.empty?
|
|
|
|
ref = nil
|
|
|
|
loop do
|
|
arg = ARGV.shift
|
|
case arg
|
|
when '--local'
|
|
ref = `git rev-parse --abbrev-ref --symbolic-full-name @{u}`
|
|
exit 1 unless $?.success?
|
|
when '--staged'
|
|
ref = '--cached'
|
|
@staged_changes_only = true
|
|
ARGV << "--cache=false"
|
|
when '--against'
|
|
ref = ARGV.shift
|
|
when '--uncommitted', '--index'
|
|
ref = 'HEAD'
|
|
when '--branch'
|
|
ref = `git merge-base HEAD dev`
|
|
else
|
|
ARGV.unshift arg
|
|
break
|
|
end
|
|
end
|
|
|
|
return unless ref
|
|
|
|
cover_up_unmodified ref.chomp
|
|
end
|
|
|
|
private
|
|
|
|
def report_offense_at?(file, line)
|
|
@line_filter && @line_filter.fetch(file)[line]
|
|
end
|
|
|
|
def files_modified_since(ref)
|
|
`git diff --diff-filter=AM --name-only #{ref}`
|
|
.lines
|
|
.map(&:chomp)
|
|
.map { |file| File.absolute_path(file) }
|
|
end
|
|
|
|
def build_line_filter(suspects, ref)
|
|
result = {}
|
|
|
|
suspects.each do |file|
|
|
result[file] = lines_modified_since(file, ref)
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def lines_modified_since(file, ref)
|
|
ranges =
|
|
`git diff -p -U0 #{ref} #{file}`
|
|
.lines
|
|
.grep(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) { $1.to_i...($1.to_i + ($2 || 1).to_i) }
|
|
.reverse
|
|
|
|
return [] if ranges.empty?
|
|
|
|
mask = Array.new(ranges.first.end)
|
|
|
|
ranges.each do |range|
|
|
range.each do |line|
|
|
mask[line] = true
|
|
end
|
|
end
|
|
|
|
mask
|
|
end
|
|
|
|
def eat_a_donut
|
|
puts "#$PROGRAM_NAME: The dirty cop Alex Murphy could have been"
|
|
puts
|
|
puts File.read(__FILE__)[/(?:^#(?:[^!].*)?\n)+/s].gsub(/^#/, ' ')
|
|
exit
|
|
end
|
|
end
|
|
|
|
module RuboCop
|
|
class TargetFinder
|
|
alias find_unpatched find
|
|
|
|
def find(...)
|
|
every_files = find_unpatched(...)
|
|
modified_files = DirtyCop.uncovered_targets
|
|
|
|
if modified_files
|
|
every_files & modified_files
|
|
else
|
|
every_files
|
|
end
|
|
end
|
|
end
|
|
|
|
class Runner
|
|
alias inspect_file_unpatched inspect_file
|
|
|
|
def inspect_file(file, *args)
|
|
offenses, updated = inspect_file_unpatched(file, *args)
|
|
offenses = offenses.reject { |o| DirtyCop.bury_evidence?(file.path, o.line) }
|
|
[offenses, updated]
|
|
end
|
|
|
|
alias add_redundant_disables_unpatched add_redundant_disables
|
|
def add_redundant_disables(file, offenses, source)
|
|
offenses = add_redundant_disables_unpatched(file, offenses, source)
|
|
DirtyCop.bury_evidences(file, offenses)
|
|
end
|
|
end
|
|
|
|
class ProcessedSource
|
|
class << self
|
|
alias from_file_unpatched from_file
|
|
end
|
|
|
|
def self.from_file(path, ruby_version)
|
|
if DirtyCop.staged_changes_only?
|
|
pathname = Pathname.new(path)
|
|
git_root = Pathname.new(`git rev-parse --show-toplevel`.strip)
|
|
git_relative_path = pathname.relative_path_from(git_root).to_s
|
|
source = `git show :#{git_relative_path}`
|
|
new(source, ruby_version, path)
|
|
else
|
|
from_file_unpatched(path, ruby_version)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
DirtyCop.process_bribe
|
|
|
|
exit RuboCop::CLI.new.run
|
|
|