spectracer 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e39e77867aba49acbb2588d38fdc61170f07b9af833bb44d504e7bedab87f27c
4
- data.tar.gz: c91f38353628064001defc02dfd81e32d8a9430c4ac8a0bc61fe2cd9d4a05126
3
+ metadata.gz: 0f3390f1fff13f710bc6da01e4ada62a534a13ec3e6132f4ac0a90152acd3941
4
+ data.tar.gz: b51108f46238ba68383dcff7488150d0665b737208ea0b177850dcd8da3986c8
5
5
  SHA512:
6
- metadata.gz: 376dbbea28bf621eeef6c0d7ebbe92f7ff55e02a847aef7de6107864a106b03f9f9dfdae02730c531d163f286e325a9287a73cca836b51a10b3e058a63f53313
7
- data.tar.gz: 42d852390be02438209c7cc54988b4f334a96a460d304675322bb66413f33ac1a94e34bf27a57f960e05e60b7693433e13b61dec846fb2d766a7dde385379226
6
+ metadata.gz: a70a1963b275b76fc5a6f135f3d51810990fbdba6a4e0d212f4cd9284156423d14730669b7ee8e9fc8574fbd365efd768785ccc1af561a73e85caf06880147f0
7
+ data.tar.gz: 071dbe59306dd5b29e4af182697ca5ed42e1d7ff66f999acde6f6258813401eb247b3968570dad5349bc471f424768f6beed38fd99a1623561b01c94df801402
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0] - 2026-02-01
4
+
5
+ ### Added
6
+
7
+ - PathFilter class to exclude gem paths from dependency tracing
8
+ - Local pre-commit hook support: detect affected specs from uncommitted changes
9
+ - Automatic default branch detection from `origin/HEAD` with fallback to `main`/`master`
10
+ - E2E tests for full tracing flow, gem path filtering, and local usage scenarios
11
+ - CI pipeline annotation step to display collected dependency tree as pretty JSON
12
+
13
+ ### Changed
14
+
15
+ - GitAdapter now supports both CI and local modes for changed file detection
16
+ - Changed files detection includes uncommitted changes (staged + unstaged) plus branch diff
17
+ - Improved gem path filtering using both `Gem.path` and `Bundler.bundle_path`
18
+
3
19
  ## [1.0.1] - 2026-01-31
4
20
 
5
21
  ### Fixed
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spectracer
4
+ module Core
5
+ class PathFilter
6
+ def initialize(
7
+ gem_paths: Gem.path,
8
+ bundler_path: defined?(Bundler) ? Bundler.bundle_path.to_s : nil
9
+ )
10
+ @excluded_prefixes = build_excluded_prefixes(gem_paths, bundler_path)
11
+ end
12
+
13
+ def gem_path?(path)
14
+ @excluded_prefixes.any? { |prefix| path.start_with?(prefix) }
15
+ end
16
+
17
+ def app_path?(path)
18
+ !gem_path?(path)
19
+ end
20
+
21
+ private
22
+
23
+ def build_excluded_prefixes(gem_paths, bundler_path)
24
+ prefixes = gem_paths.map { |p| ensure_trailing_slash(p) }
25
+ prefixes << ensure_trailing_slash(bundler_path) if bundler_path
26
+ prefixes.uniq
27
+ end
28
+
29
+ def ensure_trailing_slash(path)
30
+ path.end_with?("/") ? path : "#{path}/"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -33,21 +33,75 @@ module Spectracer
33
33
  []
34
34
  end
35
35
 
36
- def changed_files_against(target_branch, cached: false)
37
- target_ref = "origin/#{target_branch}"
36
+ def changed_files_against(target_branch, include_uncommitted: true)
37
+ target_ref = resolve_target_ref(target_branch)
38
38
 
39
- diff = if cached
40
- git.diff(target_ref, "HEAD")
39
+ committed_files = git.diff(target_ref, "HEAD").stats[:files].keys
40
+
41
+ if include_uncommitted
42
+ uncommitted_files = uncommitted_changed_files
43
+ (committed_files + uncommitted_files).uniq
41
44
  else
42
- git.diff(target_ref)
45
+ committed_files
43
46
  end
44
-
45
- diff.stats[:files].keys
46
47
  rescue Git::Error => e
47
48
  @logger&.warn("Failed to diff against #{target_branch}: #{e.message}")
48
49
  []
49
50
  end
50
51
 
52
+ def resolve_target_ref(branch)
53
+ remote_ref = "origin/#{branch}"
54
+ return remote_ref if ref_exists?(remote_ref)
55
+
56
+ return branch if ref_exists?(branch)
57
+
58
+ @logger&.warn("Neither origin/#{branch} nor #{branch} found, using HEAD~1")
59
+ "HEAD~1"
60
+ end
61
+
62
+ def ref_exists?(ref)
63
+ git.object(ref)
64
+ true
65
+ rescue Git::Error
66
+ false
67
+ end
68
+
69
+ def uncommitted_changed_files
70
+ staged = git.diff("HEAD").stats[:files].keys
71
+ unstaged = git.status.changed.keys + git.status.added.keys + git.status.deleted.keys
72
+ (staged + unstaged).uniq
73
+ rescue Git::Error => e
74
+ @logger&.warn("Failed to get uncommitted changes: #{e.message}")
75
+ []
76
+ end
77
+
78
+ def local_changed_files
79
+ uncommitted = uncommitted_changed_files
80
+
81
+ branch_diff = local_branch_diff_files
82
+ (uncommitted + branch_diff).uniq
83
+ end
84
+
85
+ def local_branch_diff_files
86
+ current = current_branch
87
+ default_branch = detect_default_branch
88
+
89
+ return [] unless default_branch
90
+ return [] if current == default_branch
91
+
92
+ git.diff(default_branch, "HEAD").stats[:files].keys
93
+ rescue Git::Error => e
94
+ @logger&.warn("Failed to get branch diff: #{e.message}")
95
+ []
96
+ end
97
+
98
+ def detect_default_branch
99
+ remote_head = git.lib.send(:command, "symbolic-ref", "refs/remotes/origin/HEAD", "--short").strip
100
+ remote_head.sub("origin/", "")
101
+ rescue
102
+ %w[main master].find { |b| ref_exists?(b) }
103
+ end
104
+
51
105
  private
52
106
 
53
107
  def git
@@ -6,10 +6,12 @@ module Spectracer
6
6
  def initialize(
7
7
  paths: Spectracer::Core::Paths.new,
8
8
  store: Spectracer::IO::DependencyStore.new,
9
+ path_filter: Spectracer::Core::PathFilter.new,
9
10
  logger: nil
10
11
  )
11
12
  @paths = paths
12
13
  @store = store
14
+ @path_filter = path_filter
13
15
  @logger = logger
14
16
  end
15
17
 
@@ -38,6 +40,8 @@ module Spectracer
38
40
 
39
41
  data.each do |spec_file, dependencies|
40
42
  dependencies.each do |dep|
43
+ next if @path_filter.gem_path?(dep)
44
+
41
45
  inverse[dep] << spec_file unless inverse[dep].include?(spec_file)
42
46
  end
43
47
  end
@@ -7,11 +7,13 @@ module Spectracer
7
7
  paths: Spectracer::Core::Paths.new,
8
8
  store: Spectracer::IO::DependencyStore.new,
9
9
  repository: Spectracer::Providers::Repository.new,
10
+ path_filter: Spectracer::Core::PathFilter.new,
10
11
  logger: nil
11
12
  )
12
13
  @paths = paths
13
14
  @store = store
14
15
  @repository = repository
16
+ @path_filter = path_filter
15
17
  @logger = logger
16
18
  @current_spec_file = nil
17
19
  @spec_file_dependencies = Hash.new { |h, k| h[k] = Set.new }
@@ -42,6 +44,7 @@ module Spectracer
42
44
  def tracepoint
43
45
  @tracepoint ||= TracePoint.new(:call) do |tp|
44
46
  path = tp.path
47
+ next if @path_filter.gem_path?(path)
45
48
  next unless path.start_with?(repository_root)
46
49
  next if @current_spec_file.nil?
47
50
 
@@ -12,11 +12,14 @@ module Spectracer
12
12
  def call
13
13
  default_branch = @env.fetch("BUILDKITE_PIPELINE_DEFAULT_BRANCH", "main")
14
14
  current_branch = @env.fetch("BUILDKITE_BRANCH") { @git_adapter.current_branch }
15
+ is_ci = @env.key?("BUILDKITE_BUILD_ID")
15
16
 
16
- files = if current_branch == default_branch
17
+ files = if is_ci && current_branch == default_branch
17
18
  changed_files_for_latest_commit(current_branch)
18
- else
19
+ elsif is_ci
19
20
  changed_files_against_default_branch(default_branch)
21
+ else
22
+ changed_files_for_local
20
23
  end
21
24
 
22
25
  @logger&.debug("Changed files: #{files.inspect}")
@@ -33,7 +36,12 @@ module Spectracer
33
36
  end
34
37
 
35
38
  def changed_files_against_default_branch(default_branch)
36
- @git_adapter.changed_files_against(default_branch, cached: true)
39
+ @git_adapter.changed_files_against(default_branch, include_uncommitted: true)
40
+ end
41
+
42
+ def changed_files_for_local
43
+ @logger&.debug("Running locally, checking uncommitted changes + branch diff")
44
+ @git_adapter.local_changed_files
37
45
  end
38
46
  end
39
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spectracer
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/spectracer.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "spectracer/version"
4
4
  require_relative "spectracer/logger"
5
5
 
6
6
  require_relative "spectracer/core/paths"
7
+ require_relative "spectracer/core/path_filter"
7
8
  require_relative "spectracer/core/spec_selector"
8
9
 
9
10
  require_relative "spectracer/io/command_runner"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spectracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitch Smith
@@ -38,6 +38,7 @@ files:
38
38
  - README.md
39
39
  - lib/spectracer.default.yml
40
40
  - lib/spectracer.rb
41
+ - lib/spectracer/core/path_filter.rb
41
42
  - lib/spectracer/core/paths.rb
42
43
  - lib/spectracer/core/spec_selector.rb
43
44
  - lib/spectracer/integrations/minitest.rb
@@ -80,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  requirements: []
83
- rubygems_version: 3.7.2
84
+ rubygems_version: 4.0.5
84
85
  specification_version: 4
85
86
  summary: Intelligent test selection based on code changes
86
87
  test_files: []