Class: ActiveMutator::SinceFilter
- Inherits:
-
Object
- Object
- ActiveMutator::SinceFilter
- Defined in:
- lib/active_mutator/since_filter.rb
Overview
Restricts subjects to methods overlapping lines changed since a git ref.
Known v1 limit: git diff <ref> omits untracked files, so brand-new
uncommitted files are skipped.
Constant Summary collapse
- HUNK =
/\A@@ [^+]*\+(\d+)(?:,(\d+))? @@/
Class Method Summary collapse
Instance Method Summary collapse
- #cover?(subject) ⇒ Boolean
-
#initialize(ref:, root:) ⇒ SinceFilter
constructor
A new instance of SinceFilter.
Constructor Details
#initialize(ref:, root:) ⇒ SinceFilter
Returns a new instance of SinceFilter.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/active_mutator/since_filter.rb', line 23 def initialize(ref:, root:) @root = root diff = IO.popen( ["git", "-C", root, "diff", "--unified=0", ref, "--", "*.rb"], &:read ) raise Error, "git diff #{ref} failed" unless $?.success? @changed = self.class.parse(diff) untracked = IO.popen( ["git", "-C", root, "ls-files", "--others", "--exclude-standard", "--", "*.rb"], &:read ) # Untracked files are invisible to `git diff` but are agentic TDD's most # common case (brand-new file + spec). Whole-file sentinel: every line # counts as changed. untracked.each_line { |l| @changed[l.strip] = :all unless l.strip.empty? } end |
Class Method Details
.parse(diff_text) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/active_mutator/since_filter.rb', line 8 def self.parse(diff_text) changed = Hash.new { |h, k| h[k] = [] } current = nil diff_text.each_line do |line| if line.start_with?("+++ b/") current = line.delete_prefix("+++ b/").strip elsif current && (match = HUNK.match(line)) start = match[1].to_i count = (match[2] || "1").to_i count.times { |i| changed[current] << start + i } end end changed.reject { |_, lines| lines.empty? } end |
Instance Method Details
#cover?(subject) ⇒ Boolean
40 41 42 43 44 45 46 |
# File 'lib/active_mutator/since_filter.rb', line 40 def cover?(subject) lines = @changed[subject.file.delete_prefix("#{@root}/")] return false unless lines return true if lines == :all lines.any? { |line| subject.line_range.cover?(line) } end |