git-pkgs 0.3.0 → 0.5.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.
@@ -6,8 +6,16 @@ module Git
6
6
  class Init
7
7
  include Output
8
8
 
9
- BATCH_SIZE = 100
10
- SNAPSHOT_INTERVAL = 20 # Store snapshot every N dependency-changing commits
9
+ DEFAULT_BATCH_SIZE = 500
10
+ DEFAULT_SNAPSHOT_INTERVAL = 50
11
+
12
+ def batch_size
13
+ Git::Pkgs.batch_size || DEFAULT_BATCH_SIZE
14
+ end
15
+
16
+ def snapshot_interval
17
+ Git::Pkgs.snapshot_interval || DEFAULT_SNAPSHOT_INTERVAL
18
+ end
11
19
 
12
20
  def initialize(args)
13
21
  @args = args
@@ -17,8 +25,11 @@ module Git
17
25
  def run
18
26
  repo = Repository.new
19
27
 
28
+ branch_name = @options[:branch] || repo.default_branch
29
+ error "Branch '#{branch_name}' not found. Check 'git branch -a' for available branches." unless repo.branch_exists?(branch_name)
30
+
20
31
  if Database.exists?(repo.git_dir) && !@options[:force]
21
- puts "Database already exists. Use --force to rebuild."
32
+ info "Database already exists. Use --force to rebuild."
22
33
  return
23
34
  end
24
35
 
@@ -27,35 +38,36 @@ module Git
27
38
  Database.create_schema(with_indexes: false)
28
39
  Database.optimize_for_bulk_writes
29
40
 
30
- branch_name = @options[:branch] || repo.default_branch
31
- error "Branch '#{branch_name}' not found" unless repo.branch_exists?(branch_name)
32
-
33
41
  branch = Models::Branch.find_or_create(branch_name)
34
42
  analyzer = Analyzer.new(repo)
35
43
 
36
- puts "Analyzing branch: #{branch_name}"
44
+ info "Analyzing branch: #{branch_name}"
37
45
 
46
+ print "Loading commits..." unless Git::Pkgs.quiet
38
47
  walker = repo.walk(branch_name, @options[:since])
39
48
  commits = walker.to_a
40
49
  total = commits.size
50
+ print "\rPrefetching diffs..." unless Git::Pkgs.quiet
51
+ repo.prefetch_blob_paths(commits)
52
+ print "\r#{' ' * 20}\r" unless Git::Pkgs.quiet
41
53
 
42
54
  stats = bulk_process_commits(commits, branch, analyzer, total)
43
55
 
44
56
  branch.update(last_analyzed_sha: repo.branch_target(branch_name))
45
57
 
46
- print "\rCreating indexes..."
58
+ print "\rCreating indexes...#{' ' * 20}" unless Git::Pkgs.quiet
47
59
  Database.create_bulk_indexes
48
60
  Database.optimize_for_reads
49
61
 
50
62
  cache_stats = analyzer.cache_stats
51
63
 
52
- puts "\rDone!#{' ' * 20}"
53
- puts "Analyzed #{total} commits"
54
- puts "Found #{stats[:dependency_commits]} commits with dependency changes"
55
- puts "Stored #{stats[:snapshots_stored]} snapshots (every #{SNAPSHOT_INTERVAL} changes)"
56
- puts "Blob cache: #{cache_stats[:cached_blobs]} unique blobs, #{cache_stats[:blobs_with_hits]} had cache hits"
64
+ info "\rDone!#{' ' * 20}"
65
+ info "Analyzed #{total} commits"
66
+ info "Found #{stats[:dependency_commits]} commits with dependency changes"
67
+ info "Stored #{stats[:snapshots_stored]} snapshots (every #{snapshot_interval} changes)"
68
+ info "Blob cache: #{cache_stats[:cached_blobs]} unique blobs, #{cache_stats[:blobs_with_hits]} had cache hits"
57
69
 
58
- if @options[:hooks]
70
+ unless @options[:no_hooks]
59
71
  Commands::Hooks.new(["--install"]).run
60
72
  end
61
73
  end
@@ -73,6 +85,7 @@ module Git
73
85
  dependency_commit_count = 0
74
86
  snapshots_stored = 0
75
87
  processed = 0
88
+ last_processed_sha = nil
76
89
 
77
90
  flush = lambda do
78
91
  return if pending_commits.empty?
@@ -134,9 +147,11 @@ module Git
134
147
  pending_snapshots.clear
135
148
  end
136
149
 
150
+ progress_interval = [total / 100, 10].max
151
+
137
152
  commits.each do |rugged_commit|
138
153
  processed += 1
139
- print "\rProcessing commit #{processed}/#{total}..." if processed % 50 == 0 || processed == total
154
+ print "\rProcessing commit #{processed}/#{total}..." if !Git::Pkgs.quiet && (processed % progress_interval == 0 || processed == total)
140
155
 
141
156
  next if rugged_commit.parents.length > 1 # skip merge commits
142
157
 
@@ -160,6 +175,8 @@ module Git
160
175
  position: processed
161
176
  }
162
177
 
178
+ last_processed_sha = rugged_commit.oid
179
+
163
180
  if has_changes
164
181
  dependency_commit_count += 1
165
182
 
@@ -188,7 +205,7 @@ module Git
188
205
  snapshot = result[:snapshot]
189
206
 
190
207
  # Store snapshot at intervals
191
- if dependency_commit_count % SNAPSHOT_INTERVAL == 0
208
+ if dependency_commit_count % snapshot_interval == 0
192
209
  snapshot.each do |(manifest_path, name), dep_info|
193
210
  pending_snapshots << {
194
211
  sha: rugged_commit.oid,
@@ -203,16 +220,15 @@ module Git
203
220
  end
204
221
  end
205
222
 
206
- flush.call if pending_commits.size >= BATCH_SIZE
223
+ flush.call if pending_commits.size >= batch_size
207
224
  end
208
225
 
209
- # Always store final snapshot for HEAD
210
- if snapshot.any?
211
- last_sha = commits.last&.oid
212
- if last_sha && !pending_snapshots.any? { |s| s[:sha] == last_sha }
226
+ # Always store final snapshot for the last processed commit
227
+ if snapshot.any? && last_processed_sha
228
+ unless pending_snapshots.any? { |s| s[:sha] == last_processed_sha }
213
229
  snapshot.each do |(manifest_path, name), dep_info|
214
230
  pending_snapshots << {
215
- sha: last_sha,
231
+ sha: last_processed_sha,
216
232
  manifest_path: manifest_path,
217
233
  name: name,
218
234
  ecosystem: dep_info[:ecosystem],
@@ -247,8 +263,8 @@ module Git
247
263
  options[:force] = true
248
264
  end
249
265
 
250
- opts.on("--hooks", "Install git hooks for auto-updating") do
251
- options[:hooks] = true
266
+ opts.on("--no-hooks", "Skip installing git hooks") do
267
+ options[:no_hooks] = true
252
268
  end
253
269
 
254
270
  opts.on("-h", "--help", "Show this help") do
@@ -20,7 +20,7 @@ module Git
20
20
  commit_sha = @options[:commit] || repo.head_sha
21
21
  target_commit = Models::Commit.find_by(sha: commit_sha)
22
22
 
23
- error "Commit #{commit_sha[0, 7]} not found in database" unless target_commit
23
+ error "Commit #{commit_sha[0, 7]} not in database. Run 'git pkgs update' to index new commits." unless target_commit
24
24
 
25
25
  deps = compute_dependencies_at_commit(target_commit, repo)
26
26
 
@@ -90,7 +90,7 @@ module Git
90
90
  # Replay changes from snapshot to target
91
91
  if snapshot_commit && snapshot_commit.id != target_commit.id
92
92
  changes = Models::DependencyChange
93
- .joins(:commit, :manifest)
93
+ .joins(:commit)
94
94
  .where(commits: { id: branch.commit_ids })
95
95
  .where("commits.committed_at > ? AND commits.committed_at <= ?",
96
96
  snapshot_commit.committed_at, target_commit.committed_at)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "time"
4
-
5
3
  module Git
6
4
  module Pkgs
7
5
  module Commands
@@ -20,6 +18,7 @@ module Git
20
18
  Database.connect(repo.git_dir)
21
19
 
22
20
  commits = Models::Commit
21
+ .includes(:dependency_changes)
23
22
  .where(has_dependency_changes: true)
24
23
  .order(committed_at: :desc)
25
24
 
@@ -44,8 +43,8 @@ module Git
44
43
 
45
44
  def output_text(commits)
46
45
  commits.each do |commit|
47
- changes = commit.dependency_changes
48
- changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
46
+ changes = commit.dependency_changes.to_a
47
+ changes = changes.select { |c| c.ecosystem == @options[:ecosystem] } if @options[:ecosystem]
49
48
  next if changes.empty?
50
49
 
51
50
  puts "#{commit.short_sha} #{commit.message&.lines&.first&.strip}"
@@ -77,8 +76,8 @@ module Git
77
76
  require "json"
78
77
 
79
78
  data = commits.map do |commit|
80
- changes = commit.dependency_changes
81
- changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
79
+ changes = commit.dependency_changes.to_a
80
+ changes = changes.select { |c| c.ecosystem == @options[:ecosystem] } if @options[:ecosystem]
82
81
 
83
82
  {
84
83
  sha: commit.sha,
@@ -102,12 +101,6 @@ module Git
102
101
  puts JSON.pretty_generate(data)
103
102
  end
104
103
 
105
- def parse_time(str)
106
- Time.parse(str)
107
- rescue ArgumentError
108
- error "Invalid date format: #{str}"
109
- end
110
-
111
104
  def parse_options
112
105
  options = {}
113
106
 
@@ -20,10 +20,10 @@ module Git
20
20
  Database.connect(repo.git_dir)
21
21
 
22
22
  sha = repo.rev_parse(ref)
23
- error "Could not resolve '#{ref}'" unless sha
23
+ error "Could not resolve '#{ref}'. Check that the ref exists with 'git rev-parse #{ref}'." unless sha
24
24
 
25
- commit = find_or_create_commit(repo, sha)
26
- error "Commit '#{sha[0..7]}' not found" unless commit
25
+ commit = Models::Commit.find_or_create_from_repo(repo, sha)
26
+ error "Commit '#{sha[0..7]}' not in database. Run 'git pkgs update' to index new commits." unless commit
27
27
 
28
28
  changes = Models::DependencyChange
29
29
  .includes(:commit, :manifest)
@@ -107,26 +107,6 @@ module Git
107
107
  puts JSON.pretty_generate(data)
108
108
  end
109
109
 
110
- def find_or_create_commit(repo, sha)
111
- commit = Models::Commit.find_by(sha: sha) ||
112
- Models::Commit.where("sha LIKE ?", "#{sha}%").first
113
- return commit if commit
114
-
115
- rugged_commit = repo.lookup(sha)
116
- return nil unless rugged_commit
117
-
118
- Models::Commit.create!(
119
- sha: rugged_commit.oid,
120
- message: rugged_commit.message,
121
- author_name: rugged_commit.author[:name],
122
- author_email: rugged_commit.author[:email],
123
- committed_at: rugged_commit.time,
124
- has_dependency_changes: false
125
- )
126
- rescue Rugged::OdbError
127
- nil
128
- end
129
-
130
110
  def parse_options
131
111
  options = {}
132
112
 
@@ -20,7 +20,7 @@ module Git
20
20
  branch_name = @options[:branch] || repo.default_branch
21
21
  branch = Models::Branch.find_by(name: branch_name)
22
22
 
23
- error "No analysis found for branch '#{branch_name}'" unless branch&.last_analyzed_sha
23
+ error "No analysis found for branch '#{branch_name}'. Run 'git pkgs init' first." unless branch&.last_analyzed_sha
24
24
 
25
25
  current_commit = Models::Commit.find_by(sha: branch.last_analyzed_sha)
26
26
  snapshots = current_commit&.dependency_snapshots&.includes(:manifest) || []
@@ -34,19 +34,38 @@ module Git
34
34
  return
35
35
  end
36
36
 
37
+ # Batch fetch all changes for current dependencies
38
+ snapshot_keys = snapshots.map { |s| [s.name, s.manifest_id] }.to_set
39
+ manifest_ids = snapshots.map(&:manifest_id).uniq
40
+ names = snapshots.map(&:name).uniq
41
+
42
+ all_changes = Models::DependencyChange
43
+ .includes(:commit)
44
+ .where(manifest_id: manifest_ids, name: names)
45
+ .to_a
46
+
47
+ # Group by (name, manifest_id) and find latest by committed_at
48
+ latest_by_key = {}
49
+ all_changes.each do |change|
50
+ key = [change.name, change.manifest_id]
51
+ next unless snapshot_keys.include?(key)
52
+
53
+ existing = latest_by_key[key]
54
+ if existing.nil? || change.commit.committed_at > existing.commit.committed_at
55
+ latest_by_key[key] = change
56
+ end
57
+ end
58
+
37
59
  # Find last update for each dependency
38
60
  outdated_data = []
61
+ now = Time.now
39
62
 
40
63
  snapshots.each do |snapshot|
41
- last_change = Models::DependencyChange
42
- .includes(:commit)
43
- .where(name: snapshot.name, manifest: snapshot.manifest)
44
- .order("commits.committed_at DESC")
45
- .first
64
+ last_change = latest_by_key[[snapshot.name, snapshot.manifest_id]]
46
65
 
47
66
  next unless last_change
48
67
 
49
- days_since_update = ((Time.now - last_change.commit.committed_at) / 86400).to_i
68
+ days_since_update = ((now - last_change.commit.committed_at) / 86400).to_i
50
69
 
51
70
  outdated_data << {
52
71
  name: snapshot.name,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "time"
4
-
5
3
  module Git
6
4
  module Pkgs
7
5
  module Commands
@@ -93,11 +91,16 @@ module Git
93
91
  manifests = Models::Manifest.all
94
92
  manifests = manifests.where(ecosystem: ecosystem) if ecosystem
95
93
 
94
+ manifest_ids = manifests.pluck(:id)
95
+ change_counts_query = Models::DependencyChange
96
+ .joins(:commit)
97
+ .where(manifest_id: manifest_ids)
98
+ change_counts_query = change_counts_query.where("commits.committed_at >= ?", since_time) if since_time
99
+ change_counts_query = change_counts_query.where("commits.committed_at <= ?", until_time) if until_time
100
+ change_counts = change_counts_query.group(:manifest_id).count
101
+
96
102
  data[:manifests] = manifests.map do |manifest|
97
- manifest_changes = manifest.dependency_changes.joins(:commit)
98
- manifest_changes = manifest_changes.where("commits.committed_at >= ?", since_time) if since_time
99
- manifest_changes = manifest_changes.where("commits.committed_at <= ?", until_time) if until_time
100
- { path: manifest.path, ecosystem: manifest.ecosystem, changes: manifest_changes.count }
103
+ { path: manifest.path, ecosystem: manifest.ecosystem, changes: change_counts[manifest.id] || 0 }
101
104
  end
102
105
 
103
106
  data
@@ -199,12 +202,6 @@ module Git
199
202
  end
200
203
  end
201
204
 
202
- def parse_time(str)
203
- Time.parse(str)
204
- rescue ArgumentError
205
- error "Invalid date format: #{str}"
206
- end
207
-
208
205
  def parse_options
209
206
  options = {}
210
207
 
@@ -21,7 +21,7 @@ module Git
21
21
  commit_sha = @options[:commit] || repo.head_sha
22
22
  commit = find_commit_with_snapshot(commit_sha, repo)
23
23
 
24
- error "No dependency data found for commit #{commit_sha[0, 7]}" unless commit
24
+ error "No dependency data for commit #{commit_sha[0, 7]}. Run 'git pkgs update' to index new commits." unless commit
25
25
 
26
26
  # Get current snapshots
27
27
  snapshots = commit.dependency_snapshots.includes(:manifest)
@@ -26,7 +26,7 @@ module Git
26
26
  current_sha = repo.branch_target(branch_name)
27
27
 
28
28
  if since_sha == current_sha
29
- puts "Already up to date."
29
+ info "Already up to date."
30
30
  return
31
31
  end
32
32
 
@@ -50,17 +50,19 @@ module Git
50
50
  walker = repo.walk(branch_name, since_sha)
51
51
  commits = walker.to_a
52
52
  total = commits.size
53
+ repo.prefetch_blob_paths(commits)
54
+
53
55
  processed = 0
54
56
  dependency_commits = 0
55
57
  last_position = Models::BranchCommit.where(branch: branch).maximum(:position) || 0
56
58
 
57
- puts "Updating branch: #{branch_name}"
58
- puts "Found #{total} new commits"
59
+ info "Updating branch: #{branch_name}"
60
+ info "Found #{total} new commits"
59
61
 
60
62
  ActiveRecord::Base.transaction do
61
63
  commits.each do |rugged_commit|
62
64
  processed += 1
63
- print "\rProcessing commit #{processed}/#{total}..."
65
+ print "\rProcessing commit #{processed}/#{total}..." unless Git::Pkgs.quiet
64
66
 
65
67
  result = analyzer.analyze_commit(rugged_commit, snapshot)
66
68
 
@@ -114,9 +116,9 @@ module Git
114
116
  branch.update(last_analyzed_sha: current_sha)
115
117
  end
116
118
 
117
- puts "\nDone!"
118
- puts "Processed #{total} new commits"
119
- puts "Found #{dependency_commits} commits with dependency changes"
119
+ info "\nDone!"
120
+ info "Processed #{total} new commits"
121
+ info "Found #{dependency_commits} commits with dependency changes"
120
122
  end
121
123
 
122
124
  def parse_options
@@ -21,13 +21,13 @@ module Git
21
21
  current = Database::SCHEMA_VERSION
22
22
 
23
23
  if stored >= current
24
- puts "Database is up to date (version #{current})"
24
+ info "Database is up to date (version #{current})"
25
25
  return
26
26
  end
27
27
 
28
- puts "Upgrading database from version #{stored} to #{current}..."
29
- puts "This requires re-indexing the repository."
30
- puts
28
+ info "Upgrading database from version #{stored} to #{current}..."
29
+ info "This requires re-indexing the repository."
30
+ info ""
31
31
 
32
32
  # Run init --force
33
33
  Init.new(["--force"]).run
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Pkgs
5
+ module Commands
6
+ class Where
7
+ include Output
8
+
9
+ def initialize(args)
10
+ @args = args
11
+ @options = parse_options
12
+ end
13
+
14
+ def run
15
+ name = @args.first
16
+
17
+ error "Usage: git pkgs where <package-name>" unless name
18
+
19
+ repo = Repository.new
20
+ require_database(repo)
21
+
22
+ Database.connect(repo.git_dir)
23
+
24
+ workdir = File.dirname(repo.git_dir)
25
+ branch = Models::Branch.find_by(name: @options[:branch] || repo.default_branch)
26
+
27
+ unless branch
28
+ error "Branch not found. Run 'git pkgs init' first."
29
+ end
30
+
31
+ snapshots = Models::DependencySnapshot.current_for_branch(branch)
32
+ snapshots = snapshots.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
33
+
34
+ manifest_paths = snapshots.for_package(name).joins(:manifest).pluck("manifests.path").uniq
35
+
36
+ if manifest_paths.empty?
37
+ empty_result "Package '#{name}' not found in current dependencies"
38
+ return
39
+ end
40
+
41
+ results = manifest_paths.flat_map do |path|
42
+ find_in_manifest(name, File.join(workdir, path), path)
43
+ end
44
+
45
+ if results.empty?
46
+ empty_result "Package '#{name}' tracked but not found in current files"
47
+ return
48
+ end
49
+
50
+ if @options[:format] == "json"
51
+ output_json(results)
52
+ else
53
+ paginate { output_text(results, name) }
54
+ end
55
+ end
56
+
57
+ def find_in_manifest(name, full_path, display_path)
58
+ return [] unless File.exist?(full_path)
59
+
60
+ lines = File.readlines(full_path)
61
+ matches = []
62
+
63
+ lines.each_with_index do |line, idx|
64
+ next unless line.include?(name)
65
+
66
+ match = { path: display_path, line: idx + 1, content: line.rstrip }
67
+
68
+ if context_lines > 0
69
+ match[:before] = context_before(lines, idx)
70
+ match[:after] = context_after(lines, idx)
71
+ end
72
+
73
+ matches << match
74
+ end
75
+
76
+ matches
77
+ end
78
+
79
+ def context_lines
80
+ @options[:context] || 0
81
+ end
82
+
83
+ def context_before(lines, idx)
84
+ start_idx = [0, idx - context_lines].max
85
+ (start_idx...idx).map { |i| { line: i + 1, content: lines[i].rstrip } }
86
+ end
87
+
88
+ def context_after(lines, idx)
89
+ end_idx = [lines.length - 1, idx + context_lines].min
90
+ ((idx + 1)..end_idx).map { |i| { line: i + 1, content: lines[i].rstrip } }
91
+ end
92
+
93
+ def output_text(results, name)
94
+ results.each_with_index do |result, i|
95
+ puts "--" if i > 0 && context_lines > 0
96
+
97
+ result[:before]&.each do |ctx|
98
+ puts format_context_line(result[:path], ctx[:line], ctx[:content])
99
+ end
100
+
101
+ puts format_match_line(result[:path], result[:line], result[:content], name)
102
+
103
+ result[:after]&.each do |ctx|
104
+ puts format_context_line(result[:path], ctx[:line], ctx[:content])
105
+ end
106
+ end
107
+ end
108
+
109
+ def format_match_line(path, line_num, content, name)
110
+ path_str = Color.magenta(path)
111
+ line_str = Color.green(line_num.to_s)
112
+ highlighted = content.gsub(name, Color.red(name))
113
+ "#{path_str}:#{line_str}:#{highlighted}"
114
+ end
115
+
116
+ def format_context_line(path, line_num, content)
117
+ path_str = Color.magenta(path)
118
+ line_str = Color.green(line_num.to_s)
119
+ content_str = Color.dim(content)
120
+ "#{path_str}-#{line_str}-#{content_str}"
121
+ end
122
+
123
+ def output_json(results)
124
+ require "json"
125
+ puts JSON.pretty_generate(results)
126
+ end
127
+
128
+ def parse_options
129
+ options = {}
130
+
131
+ parser = OptionParser.new do |opts|
132
+ opts.banner = "Usage: git pkgs where <package-name> [options]"
133
+
134
+ opts.on("-b", "--branch=NAME", "Branch to search (default: current)") do |v|
135
+ options[:branch] = v
136
+ end
137
+
138
+ opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
139
+ options[:ecosystem] = v
140
+ end
141
+
142
+ opts.on("-C", "--context=NUM", Integer, "Show NUM lines of context") do |v|
143
+ options[:context] = v
144
+ end
145
+
146
+ opts.on("-f", "--format=FORMAT", "Output format (text, json)") do |v|
147
+ options[:format] = v
148
+ end
149
+
150
+ opts.on("--no-pager", "Do not pipe output into a pager") do
151
+ options[:no_pager] = true
152
+ end
153
+
154
+ opts.on("-h", "--help", "Show this help") do
155
+ puts opts
156
+ exit
157
+ end
158
+ end
159
+
160
+ parser.parse!(@args)
161
+ options
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bibliothecary"
4
+
5
+ module Git
6
+ module Pkgs
7
+ module Config
8
+ # Ecosystems that require remote parsing services - disabled by default
9
+ REMOTE_ECOSYSTEMS = %w[carthage clojars hackage hex swiftpm].freeze
10
+
11
+ # File patterns ignored by default (SBOM formats not supported)
12
+ DEFAULT_IGNORED_FILES = %w[
13
+ cyclonedx.xml
14
+ cyclonedx.json
15
+ *.cdx.xml
16
+ *.cdx.json
17
+ *.spdx
18
+ *.spdx.json
19
+ ].freeze
20
+
21
+ def self.ignored_dirs
22
+ @ignored_dirs ||= read_config_list("pkgs.ignoredDirs")
23
+ end
24
+
25
+ def self.ignored_files
26
+ @ignored_files ||= read_config_list("pkgs.ignoredFiles")
27
+ end
28
+
29
+ def self.ecosystems
30
+ @ecosystems ||= read_config_list("pkgs.ecosystems")
31
+ end
32
+
33
+ def self.configure_bibliothecary
34
+ dirs = ignored_dirs
35
+ files = DEFAULT_IGNORED_FILES + ignored_files
36
+
37
+ Bibliothecary.configure do |config|
38
+ config.ignored_dirs += dirs unless dirs.empty?
39
+ config.ignored_files += files
40
+ end
41
+ end
42
+
43
+ def self.filter_ecosystem?(platform)
44
+ platform_lower = platform.to_s.downcase
45
+
46
+ # Remote ecosystems are disabled unless explicitly enabled
47
+ if REMOTE_ECOSYSTEMS.include?(platform_lower)
48
+ return !ecosystems.map(&:downcase).include?(platform_lower)
49
+ end
50
+
51
+ # If no filter configured, allow all non-remote ecosystems
52
+ return false if ecosystems.empty?
53
+
54
+ # Otherwise, only allow explicitly listed ecosystems
55
+ !ecosystems.map(&:downcase).include?(platform_lower)
56
+ end
57
+
58
+ def self.remote_ecosystem?(platform)
59
+ REMOTE_ECOSYSTEMS.include?(platform.to_s.downcase)
60
+ end
61
+
62
+ def self.reset!
63
+ @ignored_dirs = nil
64
+ @ignored_files = nil
65
+ @ecosystems = nil
66
+ end
67
+
68
+ def self.read_config_list(key)
69
+ `git config --get-all #{key} 2>/dev/null`.split("\n").map(&:strip).reject(&:empty?)
70
+ end
71
+ end
72
+ end
73
+ end