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

Commit 80bf3f5

Browse files
committed
Add 'git pkgs schema' command for outputting database schema in various formats
1 parent d3e9504 commit 80bf3f5

File tree

6 files changed

+260
-5
lines changed

6 files changed

+260
-5
lines changed

CHANGELOG.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
## [Unreleased]
22

3+
## [0.3.0] - 2026-01-02
4+
5+
- `git pkgs log` command to list commits with dependency changes
6+
- `git pkgs schema` command to output database schema in text, SQL, JSON, or markdown
7+
- `git pkgs praise` alias for `blame`
8+
- `git pkgs upgrade` command to handle schema upgrades after updating git-pkgs
9+
- Schema version tracking with automatic detection of outdated databases
10+
311
## [0.2.0] - 2026-01-02
412

513
- `git pkgs show` command to display dependency changes in a single commit
6-
- `git pkgs log` command to list commits with dependency changes
714
- `git pkgs history` now supports `--author`, `--since`, and `--until` filters
815
- `git pkgs stats --by-author` shows who added the most dependencies
916
- `git pkgs stats --ecosystem=X` filters statistics by ecosystem
10-
- `git pkgs praise` alias for `blame`
11-
- `git pkgs upgrade` command to handle schema upgrades after updating git-pkgs
12-
- Schema version tracking with automatic detection of outdated databases
1317

1418
## [0.1.1] - 2026-01-01
1519

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,17 @@ git pkgs upgrade
279279

280280
This is detected automatically and you'll see a message if an upgrade is needed.
281281

282+
### Show database schema
283+
284+
```bash
285+
git pkgs schema # human-readable table format
286+
git pkgs schema --format=sql # CREATE TABLE statements
287+
git pkgs schema --format=json # JSON structure
288+
git pkgs schema --format=markdown # markdown tables
289+
```
290+
291+
Useful for understanding the [database structure](docs/schema.md) or generating documentation.
292+
282293
### CI usage
283294

284295
You can run git-pkgs in CI to show dependency changes in pull requests:

lib/git/pkgs.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
require_relative "pkgs/commands/show"
3131
require_relative "pkgs/commands/log"
3232
require_relative "pkgs/commands/upgrade"
33+
require_relative "pkgs/commands/schema"
3334

3435
module Git
3536
module Pkgs

lib/git/pkgs/cli.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module Git
66
module Pkgs
77
class CLI
8-
COMMANDS = %w[init update hooks info list tree history search why blame outdated stats diff branch show log upgrade].freeze
8+
COMMANDS = %w[init update hooks info list tree history search why blame outdated stats diff branch show log upgrade schema].freeze
99
ALIASES = { "praise" => "blame" }.freeze
1010

1111
def self.run(args)
@@ -65,6 +65,7 @@ def print_help
6565
show Show dependency changes in a commit
6666
log List commits with dependency changes
6767
upgrade Upgrade database after git-pkgs update
68+
schema Show database schema
6869
6970
Options:
7071
-h, --help Show this help message

lib/git/pkgs/commands/schema.rb

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# frozen_string_literal: true
2+
3+
module Git
4+
module Pkgs
5+
module Commands
6+
class Schema
7+
FORMATS = %w[text sql json markdown].freeze
8+
9+
def initialize(args)
10+
@args = args
11+
@options = parse_options
12+
end
13+
14+
def run
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+
tables = fetch_schema
24+
25+
case @options[:format]
26+
when "sql"
27+
output_sql(tables)
28+
when "json"
29+
output_json(tables)
30+
when "markdown"
31+
output_markdown(tables)
32+
else
33+
output_text(tables)
34+
end
35+
end
36+
37+
def fetch_schema
38+
conn = ActiveRecord::Base.connection
39+
tables = {}
40+
41+
conn.tables.sort.each do |table_name|
42+
next if table_name == "ar_internal_metadata"
43+
next if table_name == "schema_migrations"
44+
45+
columns = conn.columns(table_name).map do |col|
46+
{
47+
name: col.name,
48+
type: col.type,
49+
sql_type: col.sql_type,
50+
null: col.null,
51+
default: col.default
52+
}
53+
end
54+
55+
indexes = conn.indexes(table_name).map do |idx|
56+
{
57+
name: idx.name,
58+
columns: idx.columns,
59+
unique: idx.unique
60+
}
61+
end
62+
63+
tables[table_name] = { columns: columns, indexes: indexes }
64+
end
65+
66+
tables
67+
end
68+
69+
def output_text(tables)
70+
tables.each do |table_name, info|
71+
puts "#{table_name}"
72+
puts "-" * table_name.length
73+
74+
info[:columns].each do |col|
75+
nullable = col[:null] ? "NULL" : "NOT NULL"
76+
default = col[:default] ? " DEFAULT #{col[:default]}" : ""
77+
puts " #{col[:name].ljust(25)} #{col[:sql_type].ljust(15)} #{nullable}#{default}"
78+
end
79+
80+
if info[:indexes].any?
81+
puts
82+
puts " Indexes:"
83+
info[:indexes].each do |idx|
84+
unique = idx[:unique] ? "UNIQUE " : ""
85+
puts " #{unique}#{idx[:name]} (#{idx[:columns].join(', ')})"
86+
end
87+
end
88+
89+
puts
90+
end
91+
end
92+
93+
def output_sql(tables)
94+
conn = ActiveRecord::Base.connection
95+
96+
tables.each do |table_name, info|
97+
sql = conn.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='#{table_name}'").first
98+
puts sql["sql"] + ";" if sql
99+
puts
100+
101+
info[:indexes].each do |idx|
102+
idx_sql = conn.execute("SELECT sql FROM sqlite_master WHERE type='index' AND name='#{idx[:name]}'").first
103+
puts idx_sql["sql"] + ";" if idx_sql && idx_sql["sql"]
104+
end
105+
106+
puts if info[:indexes].any?
107+
end
108+
end
109+
110+
def output_json(tables)
111+
require "json"
112+
puts JSON.pretty_generate(tables)
113+
end
114+
115+
def output_markdown(tables)
116+
tables.each do |table_name, info|
117+
puts "## #{table_name}"
118+
puts
119+
puts "| Column | Type | Nullable | Default |"
120+
puts "|--------|------|----------|---------|"
121+
122+
info[:columns].each do |col|
123+
nullable = col[:null] ? "Yes" : "No"
124+
default = col[:default] || ""
125+
puts "| #{col[:name]} | #{col[:sql_type]} | #{nullable} | #{default} |"
126+
end
127+
128+
if info[:indexes].any?
129+
puts
130+
puts "**Indexes:**"
131+
info[:indexes].each do |idx|
132+
unique = idx[:unique] ? " (unique)" : ""
133+
puts "- `#{idx[:name]}`#{unique}: #{idx[:columns].join(', ')}"
134+
end
135+
end
136+
137+
puts
138+
end
139+
end
140+
141+
def parse_options
142+
options = { format: "text" }
143+
144+
parser = OptionParser.new do |opts|
145+
opts.banner = "Usage: git pkgs schema [options]"
146+
147+
opts.on("--format=FORMAT", FORMATS, "Output format: #{FORMATS.join(', ')} (default: text)") do |v|
148+
options[:format] = v
149+
end
150+
151+
opts.on("-h", "--help", "Show this help") do
152+
puts opts
153+
exit
154+
end
155+
end
156+
157+
parser.parse!(@args)
158+
options
159+
end
160+
end
161+
end
162+
end
163+
end

test/git/pkgs/test_cli.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,78 @@ def capture_stdout
700700
$stdout = original
701701
end
702702
end
703+
704+
class Git::Pkgs::TestSchemaCommand < Minitest::Test
705+
include TestHelpers
706+
707+
def setup
708+
create_test_repo
709+
add_file("Gemfile", "source 'https://rubygems.org'\ngem 'rails'")
710+
commit("Add rails")
711+
@git_dir = File.join(@test_dir, ".git")
712+
Git::Pkgs::Database.connect(@git_dir)
713+
Git::Pkgs::Database.create_schema
714+
end
715+
716+
def teardown
717+
cleanup_test_repo
718+
end
719+
720+
def test_schema_text_format
721+
output = capture_stdout do
722+
Dir.chdir(@test_dir) do
723+
Git::Pkgs::Commands::Schema.new([]).run
724+
end
725+
end
726+
727+
assert_includes output, "commits"
728+
assert_includes output, "dependency_changes"
729+
assert_includes output, "manifests"
730+
assert_includes output, "sha"
731+
end
732+
733+
def test_schema_sql_format
734+
output = capture_stdout do
735+
Dir.chdir(@test_dir) do
736+
Git::Pkgs::Commands::Schema.new(["--format=sql"]).run
737+
end
738+
end
739+
740+
assert_includes output, "CREATE TABLE"
741+
assert_includes output, "commits"
742+
end
743+
744+
def test_schema_json_format
745+
output = capture_stdout do
746+
Dir.chdir(@test_dir) do
747+
Git::Pkgs::Commands::Schema.new(["--format=json"]).run
748+
end
749+
end
750+
751+
data = JSON.parse(output)
752+
assert data.key?("commits")
753+
assert data.key?("dependency_changes")
754+
assert data["commits"]["columns"].any? { |c| c["name"] == "sha" }
755+
end
756+
757+
def test_schema_markdown_format
758+
output = capture_stdout do
759+
Dir.chdir(@test_dir) do
760+
Git::Pkgs::Commands::Schema.new(["--format=markdown"]).run
761+
end
762+
end
763+
764+
assert_includes output, "## commits"
765+
assert_includes output, "| Column | Type |"
766+
assert_includes output, "| sha |"
767+
end
768+
769+
def capture_stdout
770+
original = $stdout
771+
$stdout = StringIO.new
772+
yield
773+
$stdout.string
774+
ensure
775+
$stdout = original
776+
end
777+
end

0 commit comments

Comments
 (0)