git-pkgs 0.1.0 → 0.2.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: f93da5d41eb8fd2680e00b173dec9eb75e139ad0ab5592f04804e70f31434d28
4
- data.tar.gz: 2449898a56c1a1d9b63858095d4c6b5c057b21282700682ce77ed4484744d3bb
3
+ metadata.gz: 7ff2285e54475944f6c8a9cb98ae9f5efcb78ee0aa74dbea6d46553bbf71caa2
4
+ data.tar.gz: 81aee90f3e0b9f573e1f9e442dc4c487f07957c320d70ca24a809929248279a3
5
5
  SHA512:
6
- metadata.gz: 2ea9c7449ef255277f93095601dbaeb59479521333761d1b3dd96d2efc5b96b3caa077d70144d42a3edac2c77fdec5c3ff6125f36d0510ac10a5652a31d99951
7
- data.tar.gz: b7d935a383ac2374efb1bb9279a93de95940beae2a75b219941ea70c98a67a27d2014ef33424146f69d08652403c25fe20d88e5474290231b5cf790435780075
6
+ metadata.gz: ffdcb5fe7cc217b105f10018ba24101131120fdfcc8305f3142fcc11f29e7f77114e95c0a2abbe661fcac3a74e4ea0662031dc6a997020225bdabcc9b6ab754b
7
+ data.tar.gz: 204dc587c61ccb128957910784ccab0cb84c667627dff1fb81d18a6b6779abc34cfff3a838f06146c954d1a50b9ac095a036a67c898d12699c530476b9b3d31c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-01-02
4
+
5
+ - `git pkgs show` command to display dependency changes in a single commit
6
+ - `git pkgs history` now supports `--author`, `--since`, and `--until` filters
7
+ - `git pkgs stats --by-author` shows who added the most dependencies
8
+ - `git pkgs stats --ecosystem=X` filters statistics by ecosystem
9
+
10
+ ## [0.1.1] - 2026-01-01
11
+
12
+ - `git pkgs history` now works without a package argument to show all dependency changes
13
+ - `git pkgs diff` supports git refs (HEAD~10, branch names, tags) not just SHAs
14
+ - `git pkgs diff` lazily inserts commits not found in the database
15
+ - Expanded manifest file pattern matching for all supported ecosystems
16
+ - Switched to ecosystems-bibliothecary
17
+
3
18
  ## [0.1.0] - 2026-01-01
4
19
 
5
20
  - Initial release
data/README.md CHANGED
@@ -4,11 +4,9 @@ A git subcommand for tracking package dependencies across git history. Analyzes
4
4
 
5
5
  ## Why this exists
6
6
 
7
- Your lockfile shows what dependencies you have. It doesn't show how you got here. `git log Gemfile.lock` is useless noise.
7
+ Your lockfile shows what dependencies you have, but it doesn't show how you got here, and `git log Gemfile.lock` is useless noise. git-pkgs indexes your dependency history into a queryable database so you can ask questions like: when did we add this? who added it? what changed between these two releases? has anyone touched this in the last year?
8
8
 
9
- git-pkgs indexes your dependency history into a queryable database. You can ask: when did we add this? who added it? what changed between these two releases? has anyone touched this in the last year?
10
-
11
- It works across ecosystems. Gemfile, package.json, Dockerfile, GitHub Actions workflows - one unified history instead of separate tools per ecosystem.
9
+ It works across many ecosystems (Gemfile, package.json, Dockerfile, GitHub Actions workflows) giving you one unified history instead of separate tools per ecosystem. Everything runs locally and offline with no external services or network calls, and the database lives in your `.git` directory where you can use it in CI to catch dependency changes in pull requests.
12
10
 
13
11
  ## Installation
14
12
 
@@ -20,10 +18,15 @@ gem install git-pkgs
20
18
 
21
19
  ```bash
22
20
  cd your-repo
23
- git pkgs init # analyze history (one-time, ~300 commits/sec)
24
- git pkgs stats # see overview
25
- git pkgs blame # who added each dependency
26
- git pkgs history rails # track a package over time
21
+ git pkgs init # analyze history (one-time, ~300 commits/sec)
22
+ git pkgs list # show current dependencies
23
+ git pkgs stats # see overview
24
+ git pkgs blame # who added each dependency
25
+ git pkgs history # all dependency changes over time
26
+ git pkgs history rails # track a specific package
27
+ git pkgs why rails # why was this added?
28
+ git pkgs diff --from=HEAD~10 # what changed recently?
29
+ git pkgs diff --from=main --to=feature # compare branches
27
30
  ```
28
31
 
29
32
  ## Commands
@@ -105,13 +108,17 @@ Gemfile (rubygems):
105
108
  ...
106
109
  ```
107
110
 
108
- ### View package history
111
+ ### View dependency history
109
112
 
110
113
  ```bash
111
- git pkgs history rails
114
+ git pkgs history # all dependency changes
115
+ git pkgs history rails # changes for a specific package
116
+ git pkgs history --author=alice # filter by author
117
+ git pkgs history --since=2024-01-01 # changes after date
118
+ git pkgs history --ecosystem=rubygems # filter by ecosystem
112
119
  ```
113
120
 
114
- Shows when the package was added, version changes, and removal:
121
+ Shows when packages were added, updated, or removed:
115
122
 
116
123
  ```
117
124
  History for rails:
@@ -160,6 +167,8 @@ Gemfile (rubygems):
160
167
 
161
168
  ```bash
162
169
  git pkgs stats
170
+ git pkgs stats --by-author # who added the most dependencies
171
+ git pkgs stats --ecosystem=npm # filter by ecosystem
163
172
  ```
164
173
 
165
174
  Example output:
@@ -205,7 +214,7 @@ Manifest Files
205
214
  git pkgs why rails
206
215
  ```
207
216
 
208
- Shows the commit that added the dependency with author and message.
217
+ This shows the commit that added the dependency along with the author and message.
209
218
 
210
219
  ### Dependency tree
211
220
 
@@ -214,7 +223,7 @@ git pkgs tree
214
223
  git pkgs tree --ecosystem=rubygems
215
224
  ```
216
225
 
217
- Shows dependencies grouped by type (runtime, development, etc).
226
+ This shows dependencies grouped by type (runtime, development, etc).
218
227
 
219
228
  ### Diff between commits
220
229
 
@@ -223,20 +232,57 @@ git pkgs diff --from=abc123 --to=def456
223
232
  git pkgs diff --from=HEAD~10
224
233
  ```
225
234
 
226
- Shows added, removed, and modified packages with version info.
235
+ This shows added, removed, and modified packages with version info.
236
+
237
+ ### Show changes in a commit
238
+
239
+ ```bash
240
+ git pkgs show # show dependency changes in HEAD
241
+ git pkgs show abc123 # specific commit
242
+ git pkgs show HEAD~5 # relative ref
243
+ ```
244
+
245
+ Like `git show` but for dependencies. Shows what was added, modified, or removed in a single commit.
227
246
 
228
247
  ### Keep database updated
229
248
 
249
+ After the initial analysis, you can incrementally update the database with new commits:
250
+
230
251
  ```bash
231
252
  git pkgs update
232
253
  ```
233
254
 
234
- Or install git hooks to update automatically after commits and merges:
255
+ You can also install git hooks to update automatically after commits and merges:
235
256
 
236
257
  ```bash
237
258
  git pkgs hooks --install
238
259
  ```
239
260
 
261
+ ### CI usage
262
+
263
+ You can run git-pkgs in CI to show dependency changes in pull requests:
264
+
265
+ ```yaml
266
+ # .github/workflows/deps.yml
267
+ name: Dependencies
268
+
269
+ on: pull_request
270
+
271
+ jobs:
272
+ diff:
273
+ runs-on: ubuntu-latest
274
+ steps:
275
+ - uses: actions/checkout@v4
276
+ with:
277
+ fetch-depth: 0
278
+ - uses: ruby/setup-ruby@v1
279
+ with:
280
+ ruby-version: '3.3'
281
+ - run: gem install git-pkgs
282
+ - run: git pkgs init
283
+ - run: git pkgs diff --from=origin/${{ github.base_ref }} --to=HEAD
284
+ ```
285
+
240
286
  ## Performance
241
287
 
242
288
  Benchmarked on a MacBook Pro analyzing [octobox](https://github.com/octobox/octobox) (5191 commits, 8 years of history): init takes about 18 seconds at roughly 300 commits/sec, producing an 8.3 MB database. About half the commits (2531) had dependency changes.
@@ -265,6 +311,21 @@ The database schema stores:
265
311
 
266
312
  See [docs/schema.md](docs/schema.md) for full schema documentation.
267
313
 
314
+ Since the database is just SQLite, you can query it directly for ad-hoc analysis:
315
+
316
+ ```bash
317
+ sqlite3 .git/pkgs.sqlite3 "
318
+ -- who added the most dependencies?
319
+ SELECT c.author_name, COUNT(*) as deps_added
320
+ FROM dependency_changes dc
321
+ JOIN commits c ON dc.commit_id = c.id
322
+ WHERE dc.change_type = 'added'
323
+ GROUP BY c.author_name
324
+ ORDER BY deps_added DESC
325
+ LIMIT 10;
326
+ "
327
+ ```
328
+
268
329
  ## Development
269
330
 
270
331
  ```bash
data/lib/git/pkgs/cli.rb CHANGED
@@ -5,7 +5,7 @@ require "optparse"
5
5
  module Git
6
6
  module Pkgs
7
7
  class CLI
8
- COMMANDS = %w[init update hooks info list tree history search why blame outdated stats diff branch].freeze
8
+ COMMANDS = %w[init update hooks info list tree history search why blame outdated stats diff branch show].freeze
9
9
 
10
10
  def self.run(args)
11
11
  new(args).run
@@ -60,6 +60,7 @@ module Git
60
60
  outdated Show dependencies that haven't been updated
61
61
  stats Show dependency statistics
62
62
  diff Show dependency changes between commits
63
+ show Show dependency changes in a commit
63
64
 
64
65
  Options:
65
66
  -h, --help Show this help message
@@ -19,26 +19,38 @@ module Git
19
19
 
20
20
  Database.connect(repo.git_dir)
21
21
 
22
- from_sha = @options[:from]
23
- to_sha = @options[:to] || repo.head_sha
22
+ from_ref = @options[:from]
23
+ to_ref = @options[:to] || "HEAD"
24
+
25
+ unless from_ref
26
+ $stderr.puts "Usage: git pkgs diff --from=REF [--to=REF]"
27
+ exit 1
28
+ end
29
+
30
+ # Resolve git refs (like HEAD~10) to SHAs
31
+ from_sha = repo.rev_parse(from_ref)
32
+ to_sha = repo.rev_parse(to_ref)
24
33
 
25
34
  unless from_sha
26
- $stderr.puts "Usage: git pkgs diff --from=SHA [--to=SHA]"
35
+ $stderr.puts "Could not resolve '#{from_ref}'"
27
36
  exit 1
28
37
  end
29
38
 
30
- from_commit = Models::Commit.find_by(sha: from_sha) ||
31
- Models::Commit.where("sha LIKE ?", "#{from_sha}%").first
32
- to_commit = Models::Commit.find_by(sha: to_sha) ||
33
- Models::Commit.where("sha LIKE ?", "#{to_sha}%").first
39
+ unless to_sha
40
+ $stderr.puts "Could not resolve '#{to_ref}'"
41
+ exit 1
42
+ end
43
+
44
+ from_commit = find_or_create_commit(repo, from_sha)
45
+ to_commit = find_or_create_commit(repo, to_sha)
34
46
 
35
47
  unless from_commit
36
- $stderr.puts "Commit '#{from_sha}' not found in database"
48
+ $stderr.puts "Commit '#{from_sha[0..7]}' not found"
37
49
  exit 1
38
50
  end
39
51
 
40
52
  unless to_commit
41
- $stderr.puts "Commit '#{to_sha}' not found in database"
53
+ $stderr.puts "Commit '#{to_sha[0..7]}' not found"
42
54
  exit 1
43
55
  end
44
56
 
@@ -98,17 +110,38 @@ module Git
98
110
  puts "Summary: +#{added.map(&:name).uniq.count} -#{removed.map(&:name).uniq.count} ~#{modified.map(&:name).uniq.count}"
99
111
  end
100
112
 
113
+ def find_or_create_commit(repo, sha)
114
+ commit = Models::Commit.find_by(sha: sha) ||
115
+ Models::Commit.where("sha LIKE ?", "#{sha}%").first
116
+ return commit if commit
117
+
118
+ # Lazily insert commit if it exists in git but not in database
119
+ rugged_commit = repo.lookup(sha)
120
+ return nil unless rugged_commit
121
+
122
+ Models::Commit.create!(
123
+ sha: rugged_commit.oid,
124
+ message: rugged_commit.message,
125
+ author_name: rugged_commit.author[:name],
126
+ author_email: rugged_commit.author[:email],
127
+ committed_at: rugged_commit.time,
128
+ has_dependency_changes: false
129
+ )
130
+ rescue Rugged::OdbError
131
+ nil
132
+ end
133
+
101
134
  def parse_options
102
135
  options = {}
103
136
 
104
137
  parser = OptionParser.new do |opts|
105
- opts.banner = "Usage: git pkgs diff --from=SHA [--to=SHA] [options]"
138
+ opts.banner = "Usage: git pkgs diff --from=REF [--to=REF] [options]"
106
139
 
107
- opts.on("-f", "--from=SHA", "Start commit (required)") do |v|
140
+ opts.on("-f", "--from=REF", "Start commit (required)") do |v|
108
141
  options[:from] = v
109
142
  end
110
143
 
111
- opts.on("-t", "--to=SHA", "End commit (default: HEAD)") do |v|
144
+ opts.on("-t", "--to=REF", "End commit (default: HEAD)") do |v|
112
145
  options[:to] = v
113
146
  end
114
147
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  module Git
4
6
  module Pkgs
5
7
  module Commands
@@ -12,11 +14,6 @@ module Git
12
14
  def run
13
15
  package_name = @args.shift
14
16
 
15
- unless package_name
16
- $stderr.puts "Usage: git pkgs history <package>"
17
- exit 1
18
- end
19
-
20
17
  repo = Repository.new
21
18
 
22
19
  unless Database.exists?(repo.git_dir)
@@ -28,15 +25,38 @@ module Git
28
25
 
29
26
  changes = Models::DependencyChange
30
27
  .includes(:commit, :manifest)
31
- .for_package(package_name)
32
28
  .order("commits.committed_at ASC")
33
29
 
30
+ changes = changes.for_package(package_name) if package_name
31
+
34
32
  if @options[:ecosystem]
35
33
  changes = changes.for_platform(@options[:ecosystem])
36
34
  end
37
35
 
36
+ if @options[:author]
37
+ author = @options[:author]
38
+ changes = changes.joins(:commit).where(
39
+ "commits.author_name LIKE ? OR commits.author_email LIKE ?",
40
+ "%#{author}%", "%#{author}%"
41
+ )
42
+ end
43
+
44
+ if @options[:since]
45
+ since_time = parse_time(@options[:since])
46
+ changes = changes.joins(:commit).where("commits.committed_at >= ?", since_time)
47
+ end
48
+
49
+ if @options[:until]
50
+ until_time = parse_time(@options[:until])
51
+ changes = changes.joins(:commit).where("commits.committed_at <= ?", until_time)
52
+ end
53
+
38
54
  if changes.empty?
39
- puts "No history found for '#{package_name}'"
55
+ if package_name
56
+ puts "No history found for '#{package_name}'"
57
+ else
58
+ puts "No dependency changes found"
59
+ end
40
60
  return
41
61
  end
42
62
 
@@ -48,7 +68,11 @@ module Git
48
68
  end
49
69
 
50
70
  def output_text(changes, package_name)
51
- puts "History for #{package_name}:"
71
+ if package_name
72
+ puts "History for #{package_name}:"
73
+ else
74
+ puts "Dependency history:"
75
+ end
52
76
  puts
53
77
 
54
78
  changes.each do |change|
@@ -67,7 +91,8 @@ module Git
67
91
  version_info = change.requirement
68
92
  end
69
93
 
70
- puts "#{date} #{action} #{version_info}"
94
+ name_prefix = package_name ? "" : "#{change.name} "
95
+ puts "#{date} #{action} #{name_prefix}#{version_info}"
71
96
  puts " Commit: #{commit.short_sha} #{commit.message&.lines&.first&.strip}"
72
97
  puts " Author: #{commit.author_name} <#{commit.author_email}>"
73
98
  puts " Manifest: #{change.manifest.path}"
@@ -80,6 +105,7 @@ module Git
80
105
 
81
106
  data = changes.map do |change|
82
107
  {
108
+ name: change.name,
83
109
  date: change.commit.committed_at.iso8601,
84
110
  change_type: change.change_type,
85
111
  requirement: change.requirement,
@@ -102,7 +128,7 @@ module Git
102
128
  options = {}
103
129
 
104
130
  parser = OptionParser.new do |opts|
105
- opts.banner = "Usage: git pkgs history <package> [options]"
131
+ opts.banner = "Usage: git pkgs history [package] [options]"
106
132
 
107
133
  opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
108
134
  options[:ecosystem] = v
@@ -112,6 +138,18 @@ module Git
112
138
  options[:format] = v
113
139
  end
114
140
 
141
+ opts.on("--author=NAME", "Filter by author name or email") do |v|
142
+ options[:author] = v
143
+ end
144
+
145
+ opts.on("--since=DATE", "Show changes after date (YYYY-MM-DD)") do |v|
146
+ options[:since] = v
147
+ end
148
+
149
+ opts.on("--until=DATE", "Show changes before date (YYYY-MM-DD)") do |v|
150
+ options[:until] = v
151
+ end
152
+
115
153
  opts.on("-h", "--help", "Show this help") do
116
154
  puts opts
117
155
  exit
@@ -121,6 +159,13 @@ module Git
121
159
  parser.parse!(@args)
122
160
  options
123
161
  end
162
+
163
+ def parse_time(str)
164
+ Time.parse(str)
165
+ rescue ArgumentError
166
+ $stderr.puts "Invalid date format: #{str}"
167
+ exit 1
168
+ end
124
169
  end
125
170
  end
126
171
  end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Pkgs
5
+ module Commands
6
+ class Show
7
+ def initialize(args)
8
+ @args = args
9
+ @options = parse_options
10
+ end
11
+
12
+ def run
13
+ ref = @args.shift || "HEAD"
14
+
15
+ repo = Repository.new
16
+
17
+ unless Database.exists?(repo.git_dir)
18
+ $stderr.puts "Database not initialized. Run 'git pkgs init' first."
19
+ exit 1
20
+ end
21
+
22
+ Database.connect(repo.git_dir)
23
+
24
+ sha = repo.rev_parse(ref)
25
+
26
+ unless sha
27
+ $stderr.puts "Could not resolve '#{ref}'"
28
+ exit 1
29
+ end
30
+
31
+ commit = find_or_create_commit(repo, sha)
32
+
33
+ unless commit
34
+ $stderr.puts "Commit '#{sha[0..7]}' not found"
35
+ exit 1
36
+ end
37
+
38
+ changes = Models::DependencyChange
39
+ .includes(:commit, :manifest)
40
+ .where(commit_id: commit.id)
41
+
42
+ if @options[:ecosystem]
43
+ changes = changes.where(ecosystem: @options[:ecosystem])
44
+ end
45
+
46
+ if changes.empty?
47
+ puts "No dependency changes in #{commit.short_sha}"
48
+ return
49
+ end
50
+
51
+ if @options[:format] == "json"
52
+ output_json(commit, changes)
53
+ else
54
+ output_text(commit, changes)
55
+ end
56
+ end
57
+
58
+ def output_text(commit, changes)
59
+ puts "Commit: #{commit.short_sha} #{commit.message&.lines&.first&.strip}"
60
+ puts "Author: #{commit.author_name} <#{commit.author_email}>"
61
+ puts "Date: #{commit.committed_at.strftime("%Y-%m-%d")}"
62
+ puts
63
+
64
+ added = changes.select { |c| c.change_type == "added" }
65
+ modified = changes.select { |c| c.change_type == "modified" }
66
+ removed = changes.select { |c| c.change_type == "removed" }
67
+
68
+ if added.any?
69
+ puts "Added:"
70
+ added.each do |change|
71
+ puts " #{change.name} #{change.requirement} (#{change.ecosystem}, #{change.manifest.path})"
72
+ end
73
+ puts
74
+ end
75
+
76
+ if modified.any?
77
+ puts "Modified:"
78
+ modified.each do |change|
79
+ puts " #{change.name} #{change.previous_requirement} -> #{change.requirement} (#{change.ecosystem}, #{change.manifest.path})"
80
+ end
81
+ puts
82
+ end
83
+
84
+ if removed.any?
85
+ puts "Removed:"
86
+ removed.each do |change|
87
+ puts " #{change.name} #{change.requirement} (#{change.ecosystem}, #{change.manifest.path})"
88
+ end
89
+ puts
90
+ end
91
+ end
92
+
93
+ def output_json(commit, changes)
94
+ require "json"
95
+
96
+ data = {
97
+ commit: {
98
+ sha: commit.sha,
99
+ short_sha: commit.short_sha,
100
+ message: commit.message&.lines&.first&.strip,
101
+ author_name: commit.author_name,
102
+ author_email: commit.author_email,
103
+ date: commit.committed_at.iso8601
104
+ },
105
+ changes: changes.map do |change|
106
+ {
107
+ name: change.name,
108
+ change_type: change.change_type,
109
+ requirement: change.requirement,
110
+ previous_requirement: change.previous_requirement,
111
+ ecosystem: change.ecosystem,
112
+ manifest: change.manifest.path
113
+ }
114
+ end
115
+ }
116
+
117
+ puts JSON.pretty_generate(data)
118
+ end
119
+
120
+ def find_or_create_commit(repo, sha)
121
+ commit = Models::Commit.find_by(sha: sha) ||
122
+ Models::Commit.where("sha LIKE ?", "#{sha}%").first
123
+ return commit if commit
124
+
125
+ rugged_commit = repo.lookup(sha)
126
+ return nil unless rugged_commit
127
+
128
+ Models::Commit.create!(
129
+ sha: rugged_commit.oid,
130
+ message: rugged_commit.message,
131
+ author_name: rugged_commit.author[:name],
132
+ author_email: rugged_commit.author[:email],
133
+ committed_at: rugged_commit.time,
134
+ has_dependency_changes: false
135
+ )
136
+ rescue Rugged::OdbError
137
+ nil
138
+ end
139
+
140
+ def parse_options
141
+ options = {}
142
+
143
+ parser = OptionParser.new do |opts|
144
+ opts.banner = "Usage: git pkgs show [commit] [options]"
145
+
146
+ opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
147
+ options[:ecosystem] = v
148
+ end
149
+
150
+ opts.on("-f", "--format=FORMAT", "Output format (text, json)") do |v|
151
+ options[:format] = v
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
@@ -22,19 +22,26 @@ module Git
22
22
  branch_name = @options[:branch] || repo.default_branch
23
23
  branch = Models::Branch.find_by(name: branch_name)
24
24
 
25
- data = collect_stats(branch, branch_name)
26
-
27
- if @options[:format] == "json"
28
- require "json"
29
- puts JSON.pretty_generate(data)
25
+ if @options[:by_author]
26
+ output_by_author
30
27
  else
31
- output_text(data)
28
+ data = collect_stats(branch, branch_name)
29
+
30
+ if @options[:format] == "json"
31
+ require "json"
32
+ puts JSON.pretty_generate(data)
33
+ else
34
+ output_text(data)
35
+ end
32
36
  end
33
37
  end
34
38
 
35
39
  def collect_stats(branch, branch_name)
40
+ ecosystem = @options[:ecosystem]
41
+
36
42
  data = {
37
43
  branch: branch_name,
44
+ ecosystem: ecosystem,
38
45
  commits_analyzed: branch&.commits&.count || 0,
39
46
  commits_with_changes: branch&.commits&.where(has_dependency_changes: true)&.count || 0,
40
47
  current_dependencies: {},
@@ -46,6 +53,7 @@ module Git
46
53
  if branch&.last_analyzed_sha
47
54
  current_commit = Models::Commit.find_by(sha: branch.last_analyzed_sha)
48
55
  snapshots = current_commit&.dependency_snapshots || []
56
+ snapshots = snapshots.where(ecosystem: ecosystem) if ecosystem
49
57
 
50
58
  data[:current_dependencies] = {
51
59
  total: snapshots.count,
@@ -54,22 +62,28 @@ module Git
54
62
  }
55
63
  end
56
64
 
65
+ changes = Models::DependencyChange.all
66
+ changes = changes.where(ecosystem: ecosystem) if ecosystem
67
+
57
68
  data[:changes] = {
58
- total: Models::DependencyChange.count,
59
- by_type: Models::DependencyChange.group(:change_type).count
69
+ total: changes.count,
70
+ by_type: changes.group(:change_type).count
60
71
  }
61
72
 
62
- most_changed = Models::DependencyChange
73
+ most_changed = changes
63
74
  .group(:name, :ecosystem)
64
75
  .order("count_all DESC")
65
76
  .limit(10)
66
77
  .count
67
78
 
68
- data[:most_changed] = most_changed.map do |(name, ecosystem), count|
69
- { name: name, ecosystem: ecosystem, changes: count }
79
+ data[:most_changed] = most_changed.map do |(name, eco), count|
80
+ { name: name, ecosystem: eco, changes: count }
70
81
  end
71
82
 
72
- data[:manifests] = Models::Manifest.all.map do |manifest|
83
+ manifests = Models::Manifest.all
84
+ manifests = manifests.where(ecosystem: ecosystem) if ecosystem
85
+
86
+ data[:manifests] = manifests.map do |manifest|
73
87
  { path: manifest.path, ecosystem: manifest.ecosystem, changes: manifest.dependency_changes.count }
74
88
  end
75
89
 
@@ -82,6 +96,7 @@ module Git
82
96
  puts
83
97
 
84
98
  puts "Branch: #{data[:branch]}"
99
+ puts "Ecosystem: #{data[:ecosystem]}" if data[:ecosystem]
85
100
  puts "Commits analyzed: #{data[:commits_analyzed]}"
86
101
  puts "Commits with changes: #{data[:commits_with_changes]}"
87
102
  puts
@@ -128,6 +143,38 @@ module Git
128
143
  end
129
144
  end
130
145
 
146
+ def output_by_author
147
+ changes = Models::DependencyChange
148
+ .joins(:commit)
149
+ .where(change_type: "added")
150
+
151
+ changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
152
+
153
+ counts = changes
154
+ .group("commits.author_name")
155
+ .order("count_all DESC")
156
+ .limit(@options[:limit] || 20)
157
+ .count
158
+
159
+ if counts.empty?
160
+ puts "No dependency additions found"
161
+ return
162
+ end
163
+
164
+ if @options[:format] == "json"
165
+ require "json"
166
+ data = counts.map { |name, count| { author: name, added: count } }
167
+ puts JSON.pretty_generate(data)
168
+ else
169
+ puts "Dependencies Added by Author"
170
+ puts "=" * 40
171
+ puts
172
+ counts.each do |name, count|
173
+ puts " #{count.to_s.rjust(4)} #{name}"
174
+ end
175
+ end
176
+ end
177
+
131
178
  def parse_options
132
179
  options = {}
133
180
 
@@ -138,10 +185,22 @@ module Git
138
185
  options[:branch] = v
139
186
  end
140
187
 
188
+ opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
189
+ options[:ecosystem] = v
190
+ end
191
+
141
192
  opts.on("-f", "--format=FORMAT", "Output format (text, json)") do |v|
142
193
  options[:format] = v
143
194
  end
144
195
 
196
+ opts.on("--by-author", "Show dependencies added by author") do
197
+ options[:by_author] = true
198
+ end
199
+
200
+ opts.on("-n", "--limit=N", Integer, "Limit results (default: 20)") do |v|
201
+ options[:limit] = v
202
+ end
203
+
145
204
  opts.on("-h", "--help", "Show this help") do
146
205
  puts opts
147
206
  exit
@@ -120,6 +120,12 @@ module Git
120
120
  def head_sha
121
121
  @rugged.head.target_id
122
122
  end
123
+
124
+ def rev_parse(ref)
125
+ @rugged.rev_parse(ref).oid
126
+ rescue Rugged::ReferenceError, Rugged::InvalidError
127
+ nil
128
+ end
123
129
  end
124
130
  end
125
131
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Git
4
4
  module Pkgs
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
data/lib/git/pkgs.rb CHANGED
@@ -27,6 +27,7 @@ require_relative "pkgs/commands/diff"
27
27
  require_relative "pkgs/commands/tree"
28
28
  require_relative "pkgs/commands/branch"
29
29
  require_relative "pkgs/commands/search"
30
+ require_relative "pkgs/commands/show"
30
31
 
31
32
  module Git
32
33
  module Pkgs
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-pkgs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
@@ -98,6 +98,7 @@ files:
98
98
  - lib/git/pkgs/commands/list.rb
99
99
  - lib/git/pkgs/commands/outdated.rb
100
100
  - lib/git/pkgs/commands/search.rb
101
+ - lib/git/pkgs/commands/show.rb
101
102
  - lib/git/pkgs/commands/stats.rb
102
103
  - lib/git/pkgs/commands/tree.rb
103
104
  - lib/git/pkgs/commands/update.rb