Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.

Commit 95aef19

Browse files
committed
Implement rev_parse method and enhance diff command to resolve git refs
1 parent d25b0a6 commit 95aef19

File tree

7 files changed

+154
-13
lines changed

7 files changed

+154
-13
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
## [Unreleased]
22

3+
## [0.1.1] - 2026-01-01
4+
35
- `git pkgs history` now works without a package argument to show all dependency changes
6+
- `git pkgs diff` supports git refs (HEAD~10, branch names, tags) not just SHAs
7+
- `git pkgs diff` lazily inserts commits not found in the database
8+
- Expanded manifest file pattern matching for all supported ecosystems
9+
- Switched to ecosystems-bibliothecary
410

511
## [0.1.0] - 2026-01-01
612

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ git pkgs history # all dependency changes over time
2828
git pkgs history rails # track a specific package
2929
git pkgs why rails # why was this added?
3030
git pkgs diff --from=HEAD~10 # what changed recently?
31+
git pkgs diff --from=main --to=feature # compare branches
3132
```
3233

3334
## Commands

lib/git/pkgs/commands/diff.rb

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,38 @@ def run
1919

2020
Database.connect(repo.git_dir)
2121

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)
2433

2534
unless from_sha
26-
$stderr.puts "Usage: git pkgs diff --from=SHA [--to=SHA]"
35+
$stderr.puts "Could not resolve '#{from_ref}'"
2736
exit 1
2837
end
2938

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)
3446

3547
unless from_commit
36-
$stderr.puts "Commit '#{from_sha}' not found in database"
48+
$stderr.puts "Commit '#{from_sha[0..7]}' not found"
3749
exit 1
3850
end
3951

4052
unless to_commit
41-
$stderr.puts "Commit '#{to_sha}' not found in database"
53+
$stderr.puts "Commit '#{to_sha[0..7]}' not found"
4254
exit 1
4355
end
4456

@@ -98,17 +110,38 @@ def run
98110
puts "Summary: +#{added.map(&:name).uniq.count} -#{removed.map(&:name).uniq.count} ~#{modified.map(&:name).uniq.count}"
99111
end
100112

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+
101134
def parse_options
102135
options = {}
103136

104137
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]"
106139

107-
opts.on("-f", "--from=SHA", "Start commit (required)") do |v|
140+
opts.on("-f", "--from=REF", "Start commit (required)") do |v|
108141
options[:from] = v
109142
end
110143

111-
opts.on("-t", "--to=SHA", "End commit (default: HEAD)") do |v|
144+
opts.on("-t", "--to=REF", "End commit (default: HEAD)") do |v|
112145
options[:to] = v
113146
end
114147

lib/git/pkgs/repository.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ def merge_commit?(rugged_commit)
120120
def head_sha
121121
@rugged.head.target_id
122122
end
123+
124+
def rev_parse(ref)
125+
@rugged.rev_parse(ref).oid
126+
rescue Rugged::ReferenceError, Rugged::InvalidError
127+
nil
128+
end
123129
end
124130
end
125131
end

lib/git/pkgs/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
module Git
44
module Pkgs
5-
VERSION = "0.1.0"
5+
VERSION = "0.1.1"
66
end
77
end

test/git/pkgs/test_cli.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
require "stringio"
55

66
class Git::Pkgs::TestCLI < Minitest::Test
7+
include TestHelpers
8+
79
def test_help_command
810
output = capture_stdout do
911
Git::Pkgs::CLI.run(["help"])
@@ -49,3 +51,67 @@ def capture_stderr
4951
$stderr = original
5052
end
5153
end
54+
55+
class Git::Pkgs::TestDiffCommand < Minitest::Test
56+
include TestHelpers
57+
58+
def setup
59+
create_test_repo
60+
add_file("Gemfile", "source 'https://rubygems.org'\ngem 'rails'")
61+
commit("Initial commit")
62+
add_file("Gemfile", "source 'https://rubygems.org'\ngem 'rails'\ngem 'puma'")
63+
commit("Add puma")
64+
@git_dir = File.join(@test_dir, ".git")
65+
Git::Pkgs::Database.connect(@git_dir)
66+
Git::Pkgs::Database.create_schema
67+
end
68+
69+
def teardown
70+
cleanup_test_repo
71+
end
72+
73+
def test_find_or_create_commit_finds_existing_commit
74+
repo = Git::Pkgs::Repository.new(@test_dir)
75+
sha = repo.head_sha
76+
77+
# Create commit in database first
78+
Git::Pkgs::Models::Commit.create!(
79+
sha: sha,
80+
message: "Test",
81+
author_name: "Test",
82+
author_email: "test@example.com",
83+
committed_at: Time.now
84+
)
85+
86+
diff = Git::Pkgs::Commands::Diff.new([])
87+
result = diff.send(:find_or_create_commit, repo, sha)
88+
89+
assert result
90+
assert_equal sha, result.sha
91+
end
92+
93+
def test_find_or_create_commit_creates_missing_commit
94+
repo = Git::Pkgs::Repository.new(@test_dir)
95+
sha = repo.head_sha
96+
97+
# Commit doesn't exist in database yet
98+
assert_nil Git::Pkgs::Models::Commit.find_by(sha: sha)
99+
100+
diff = Git::Pkgs::Commands::Diff.new([])
101+
result = diff.send(:find_or_create_commit, repo, sha)
102+
103+
assert result
104+
assert_equal sha, result.sha
105+
# Verify it was persisted
106+
assert Git::Pkgs::Models::Commit.find_by(sha: sha)
107+
end
108+
109+
def test_find_or_create_commit_returns_nil_for_invalid_sha
110+
repo = Git::Pkgs::Repository.new(@test_dir)
111+
112+
diff = Git::Pkgs::Commands::Diff.new([])
113+
result = diff.send(:find_or_create_commit, repo, "0000000000000000000000000000000000000000")
114+
115+
assert_nil result
116+
end
117+
end

test/git/pkgs/test_repository.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,33 @@ def test_content_at_commit
6666

6767
assert_equal "# Test", content
6868
end
69+
70+
def test_rev_parse_resolves_head
71+
repo = Git::Pkgs::Repository.new(@test_dir)
72+
sha = repo.rev_parse("HEAD")
73+
74+
assert sha
75+
assert_equal 40, sha.length
76+
assert_equal repo.head_sha, sha
77+
end
78+
79+
def test_rev_parse_resolves_relative_refs
80+
add_file("file1.txt", "content")
81+
commit("Second commit")
82+
83+
repo = Git::Pkgs::Repository.new(@test_dir)
84+
head_sha = repo.rev_parse("HEAD")
85+
parent_sha = repo.rev_parse("HEAD~1")
86+
87+
assert head_sha
88+
assert parent_sha
89+
refute_equal head_sha, parent_sha
90+
end
91+
92+
def test_rev_parse_returns_nil_for_invalid_ref
93+
repo = Git::Pkgs::Repository.new(@test_dir)
94+
sha = repo.rev_parse("nonexistent-ref")
95+
96+
assert_nil sha
97+
end
6998
end

0 commit comments

Comments
 (0)