Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- `git pkgs init` now installs git hooks by default (use `--no-hooks` to skip)
- Fix N+1 queries in `blame`, `stale`, `stats`, and `log` commands
- Configuration via git config: `pkgs.ecosystems`, `pkgs.ignoredDirs`, `pkgs.ignoredFiles`
- `git pkgs info --ecosystems` to show available ecosystems and their status

## [0.4.0] - 2026-01-04

Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,21 @@ git-pkgs respects [standard git configuration](https://git-scm.com/docs/git-conf

**Pager** follows git's precedence: `GIT_PAGER` env, `core.pager` config, `PAGER` env, then `less -FRSX`. Use `--no-pager` flag or `git config core.pager cat` to disable.

**Ecosystem filtering** lets you limit which package ecosystems are tracked:

```bash
git config --add pkgs.ecosystems rubygems
git config --add pkgs.ecosystems npm
git pkgs info --ecosystems # show enabled/disabled ecosystems
```

**Ignored paths** let you skip directories or files from analysis:

```bash
git config --add pkgs.ignoredDirs third_party
git config --add pkgs.ignoredFiles test/fixtures/package.json
```

**Environment variables:**

- `GIT_DIR` - git directory location (standard git variable)
Expand All @@ -399,7 +414,11 @@ Optimizations:

git-pkgs uses [ecosystems-bibliothecary](https://github.com/ecosyste-ms/bibliothecary) for parsing, supporting:

Actions, Anaconda, BentoML, Bower, Cargo, CocoaPods, Cog, CPAN, CRAN, CycloneDX, Docker, Dub, DVC, Elm, Go, Haxelib, Homebrew, Julia, Maven, Meteor, MLflow, npm, NuGet, Ollama, Packagist, Pub, PyPI, RubyGems, Shards, SPDX, Vcpkg
Actions, BentoML, Bower, Cargo, CocoaPods, Cog, Conda, CPAN, CRAN, Docker, Dub, DVC, Elm, Go, Haxelib, Homebrew, Julia, Maven, Meteor, MLflow, npm, NuGet, Ollama, Packagist, Pub, PyPI, RubyGems, Shards, Vcpkg

Some ecosystems require remote parsing services and are disabled by default: Carthage, Clojars, Hackage, Hex, SwiftPM. Enable with `git config --add pkgs.ecosystems <name>`.

SBOM formats (CycloneDX, SPDX) are not supported as they duplicate information from the actual lockfiles.

## Contributing

Expand Down
1 change: 1 addition & 0 deletions lib/git/pkgs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative "pkgs/version"
require_relative "pkgs/output"
require_relative "pkgs/color"
require_relative "pkgs/config"
require_relative "pkgs/cli"
require_relative "pkgs/database"
require_relative "pkgs/repository"
Expand Down
4 changes: 2 additions & 2 deletions lib/git/pkgs/analyzer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ class Analyzer
Podfile Podfile.lock *.podspec *.podspec.json
packages.config packages.lock.json Project.json Project.lock.json
*.nuspec paket.lock *.csproj project.assets.json
cyclonedx.xml cyclonedx.json *.cdx.xml *.cdx.json
*.spdx *.spdx.json
bower.json bentofile.yaml
META.json META.yml
environment.yml environment.yaml
Expand Down Expand Up @@ -56,6 +54,7 @@ class Analyzer
def initialize(repository)
@repository = repository
@blob_cache = {}
Config.configure_bibliothecary
end

# Quick check if any paths might be manifests (fast regex check)
Expand Down Expand Up @@ -262,6 +261,7 @@ def parse_manifest_by_oid(blob_oid, manifest_path)
return nil unless content

result = Bibliothecary.analyse_file(manifest_path, content).first
result = nil if result && Config.filter_ecosystem?(result[:platform])
@blob_cache[cache_key] = { result: result, hits: 0 }
result
end
Expand Down
2 changes: 2 additions & 0 deletions lib/git/pkgs/commands/diff_driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class DiffDriver
def initialize(args)
@args = args
@options = parse_options
Config.configure_bibliothecary
end

def run
Expand Down Expand Up @@ -132,6 +133,7 @@ def parse_deps(path, content)

result = Bibliothecary.analyse_file(path, content).first
return {} unless result
return {} if Config.filter_ecosystem?(result[:platform])

result[:dependencies].map { |d| [d[:name], d] }.to_h
rescue StandardError
Expand Down
66 changes: 65 additions & 1 deletion lib/git/pkgs/commands/info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ def initialize(args)
end

def run
if @options[:ecosystems]
output_ecosystems
return
end

repo = Repository.new
require_database(repo)

Expand Down Expand Up @@ -77,6 +82,61 @@ def run
end
end

def output_ecosystems
require "bibliothecary"

all_ecosystems = Bibliothecary::Parsers.constants.map do |c|
parser = Bibliothecary::Parsers.const_get(c)
parser.platform_name if parser.respond_to?(:platform_name)
end.compact.sort

configured = Config.ecosystems
filtering = configured.any?

puts "Available Ecosystems"
puts "=" * 40
puts

enabled_ecos = []
disabled_ecos = []

all_ecosystems.each do |eco|
if Config.filter_ecosystem?(eco)
remote = Config.remote_ecosystem?(eco)
disabled_ecos << { name: eco, remote: remote }
else
enabled_ecos << eco
end
end

puts "Enabled:"
if enabled_ecos.any?
enabled_ecos.each { |eco| puts " #{Color.green(eco)}" }
else
puts " (none)"
end

puts
puts "Disabled:"
if disabled_ecos.any?
disabled_ecos.each do |eco|
suffix = eco[:remote] ? " (remote)" : ""
puts " #{eco[:name]}#{suffix}"
end
else
puts " (none)"
end

puts
if filtering
puts "Filtering: only #{configured.join(', ')}"
else
puts "All local ecosystems enabled"
end
puts "Remote ecosystems require explicit opt-in"
puts "Configure with: git config --add pkgs.ecosystems <name>"
end

def format_size(bytes)
units = %w[B KB MB GB]
unit_index = 0
Expand All @@ -94,7 +154,11 @@ def parse_options
options = {}

parser = OptionParser.new do |opts|
opts.banner = "Usage: git pkgs info"
opts.banner = "Usage: git pkgs info [options]"

opts.on("--ecosystems", "Show available ecosystems and filter status") do
options[:ecosystems] = true
end

opts.on("-h", "--help", "Show this help") do
puts opts
Expand Down
73 changes: 73 additions & 0 deletions lib/git/pkgs/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require "bibliothecary"

module Git
module Pkgs
module Config
# Ecosystems that require remote parsing services - disabled by default
REMOTE_ECOSYSTEMS = %w[carthage clojars hackage hex swiftpm].freeze

# File patterns ignored by default (SBOM formats not supported)
DEFAULT_IGNORED_FILES = %w[
cyclonedx.xml
cyclonedx.json
*.cdx.xml
*.cdx.json
*.spdx
*.spdx.json
].freeze

def self.ignored_dirs
@ignored_dirs ||= read_config_list("pkgs.ignoredDirs")
end

def self.ignored_files
@ignored_files ||= read_config_list("pkgs.ignoredFiles")
end

def self.ecosystems
@ecosystems ||= read_config_list("pkgs.ecosystems")
end

def self.configure_bibliothecary
dirs = ignored_dirs
files = DEFAULT_IGNORED_FILES + ignored_files

Bibliothecary.configure do |config|
config.ignored_dirs += dirs unless dirs.empty?
config.ignored_files += files
end
end

def self.filter_ecosystem?(platform)
platform_lower = platform.to_s.downcase

# Remote ecosystems are disabled unless explicitly enabled
if REMOTE_ECOSYSTEMS.include?(platform_lower)
return !ecosystems.map(&:downcase).include?(platform_lower)
end

# If no filter configured, allow all non-remote ecosystems
return false if ecosystems.empty?

# Otherwise, only allow explicitly listed ecosystems
!ecosystems.map(&:downcase).include?(platform_lower)
end

def self.remote_ecosystem?(platform)
REMOTE_ECOSYSTEMS.include?(platform.to_s.downcase)
end

def self.reset!
@ignored_dirs = nil
@ignored_files = nil
@ecosystems = nil
end

def self.read_config_list(key)
`git config --get-all #{key} 2>/dev/null`.split("\n").map(&:strip).reject(&:empty?)
end
end
end
end
Loading