git-pkgs 0.1.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +123 -10
- data/lib/git/pkgs/cli.rb +9 -3
- data/lib/git/pkgs/color.rb +82 -0
- data/lib/git/pkgs/commands/blame.rb +23 -20
- data/lib/git/pkgs/commands/branch.rb +11 -35
- data/lib/git/pkgs/commands/diff.rb +27 -35
- data/lib/git/pkgs/commands/history.rb +51 -14
- data/lib/git/pkgs/commands/hooks.rb +2 -0
- data/lib/git/pkgs/commands/info.rb +3 -5
- data/lib/git/pkgs/commands/init.rb +4 -5
- data/lib/git/pkgs/commands/list.rb +21 -18
- data/lib/git/pkgs/commands/log.rb +157 -0
- data/lib/git/pkgs/commands/schema.rb +161 -0
- data/lib/git/pkgs/commands/search.rb +10 -11
- data/lib/git/pkgs/commands/show.rb +160 -0
- data/lib/git/pkgs/commands/{outdated.rb → stale.rb} +16 -13
- data/lib/git/pkgs/commands/stats.rb +121 -20
- data/lib/git/pkgs/commands/tree.rb +13 -10
- data/lib/git/pkgs/commands/update.rb +55 -58
- data/lib/git/pkgs/commands/upgrade.rb +54 -0
- data/lib/git/pkgs/commands/why.rb +5 -10
- data/lib/git/pkgs/database.rb +55 -1
- data/lib/git/pkgs/output.rb +44 -0
- data/lib/git/pkgs/pager.rb +68 -0
- data/lib/git/pkgs/repository.rb +3 -3
- data/lib/git/pkgs/version.rb +1 -1
- data/lib/git/pkgs.rb +7 -1
- metadata +9 -2
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
3
5
|
module Git
|
|
4
6
|
module Pkgs
|
|
5
7
|
module Commands
|
|
6
8
|
class History
|
|
9
|
+
include Output
|
|
10
|
+
|
|
7
11
|
def initialize(args)
|
|
8
12
|
@args = args
|
|
9
13
|
@options = parse_options
|
|
@@ -13,11 +17,7 @@ module Git
|
|
|
13
17
|
package_name = @args.shift
|
|
14
18
|
|
|
15
19
|
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
|
|
20
|
+
require_database(repo)
|
|
21
21
|
|
|
22
22
|
Database.connect(repo.git_dir)
|
|
23
23
|
|
|
@@ -31,19 +31,34 @@ module Git
|
|
|
31
31
|
changes = changes.for_platform(@options[:ecosystem])
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
if @options[:author]
|
|
35
|
+
author = @options[:author]
|
|
36
|
+
changes = changes.joins(:commit).where(
|
|
37
|
+
"commits.author_name LIKE ? OR commits.author_email LIKE ?",
|
|
38
|
+
"%#{author}%", "%#{author}%"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if @options[:since]
|
|
43
|
+
since_time = parse_time(@options[:since])
|
|
44
|
+
changes = changes.joins(:commit).where("commits.committed_at >= ?", since_time)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if @options[:until]
|
|
48
|
+
until_time = parse_time(@options[:until])
|
|
49
|
+
changes = changes.joins(:commit).where("commits.committed_at <= ?", until_time)
|
|
50
|
+
end
|
|
51
|
+
|
|
34
52
|
if changes.empty?
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
else
|
|
38
|
-
puts "No dependency changes found"
|
|
39
|
-
end
|
|
53
|
+
msg = package_name ? "No history found for '#{package_name}'" : "No dependency changes found"
|
|
54
|
+
empty_result msg
|
|
40
55
|
return
|
|
41
56
|
end
|
|
42
57
|
|
|
43
58
|
if @options[:format] == "json"
|
|
44
59
|
output_json(changes)
|
|
45
60
|
else
|
|
46
|
-
output_text(changes, package_name)
|
|
61
|
+
paginate { output_text(changes, package_name) }
|
|
47
62
|
end
|
|
48
63
|
end
|
|
49
64
|
|
|
@@ -61,13 +76,13 @@ module Git
|
|
|
61
76
|
|
|
62
77
|
case change.change_type
|
|
63
78
|
when "added"
|
|
64
|
-
action = "Added"
|
|
79
|
+
action = Color.green("Added")
|
|
65
80
|
version_info = change.requirement
|
|
66
81
|
when "modified"
|
|
67
|
-
action = "Updated"
|
|
82
|
+
action = Color.yellow("Updated")
|
|
68
83
|
version_info = "#{change.previous_requirement} -> #{change.requirement}"
|
|
69
84
|
when "removed"
|
|
70
|
-
action = "Removed"
|
|
85
|
+
action = Color.red("Removed")
|
|
71
86
|
version_info = change.requirement
|
|
72
87
|
end
|
|
73
88
|
|
|
@@ -118,6 +133,22 @@ module Git
|
|
|
118
133
|
options[:format] = v
|
|
119
134
|
end
|
|
120
135
|
|
|
136
|
+
opts.on("--author=NAME", "Filter by author name or email") do |v|
|
|
137
|
+
options[:author] = v
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
opts.on("--since=DATE", "Show changes after date (YYYY-MM-DD)") do |v|
|
|
141
|
+
options[:since] = v
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
opts.on("--until=DATE", "Show changes before date (YYYY-MM-DD)") do |v|
|
|
145
|
+
options[:until] = v
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
149
|
+
options[:no_pager] = true
|
|
150
|
+
end
|
|
151
|
+
|
|
121
152
|
opts.on("-h", "--help", "Show this help") do
|
|
122
153
|
puts opts
|
|
123
154
|
exit
|
|
@@ -127,6 +158,12 @@ module Git
|
|
|
127
158
|
parser.parse!(@args)
|
|
128
159
|
options
|
|
129
160
|
end
|
|
161
|
+
|
|
162
|
+
def parse_time(str)
|
|
163
|
+
Time.parse(str)
|
|
164
|
+
rescue ArgumentError
|
|
165
|
+
error "Invalid date format: #{str}"
|
|
166
|
+
end
|
|
130
167
|
end
|
|
131
168
|
end
|
|
132
169
|
end
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Info
|
|
7
|
+
include Output
|
|
8
|
+
|
|
7
9
|
def initialize(args)
|
|
8
10
|
@args = args
|
|
9
11
|
@options = parse_options
|
|
@@ -11,11 +13,7 @@ module Git
|
|
|
11
13
|
|
|
12
14
|
def run
|
|
13
15
|
repo = Repository.new
|
|
14
|
-
|
|
15
|
-
unless Database.exists?(repo.git_dir)
|
|
16
|
-
$stderr.puts "Database not initialized. Run 'git pkgs init' first."
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
16
|
+
require_database(repo)
|
|
19
17
|
|
|
20
18
|
db_path = Database.path(repo.git_dir)
|
|
21
19
|
Database.connect(repo.git_dir)
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Init
|
|
7
|
+
include Output
|
|
8
|
+
|
|
7
9
|
BATCH_SIZE = 100
|
|
8
10
|
SNAPSHOT_INTERVAL = 20 # Store snapshot every N dependency-changing commits
|
|
9
11
|
|
|
@@ -21,15 +23,12 @@ module Git
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
Database.drop if @options[:force]
|
|
24
|
-
Database.connect(repo.git_dir)
|
|
26
|
+
Database.connect(repo.git_dir, check_version: false)
|
|
25
27
|
Database.create_schema(with_indexes: false)
|
|
26
28
|
Database.optimize_for_bulk_writes
|
|
27
29
|
|
|
28
30
|
branch_name = @options[:branch] || repo.default_branch
|
|
29
|
-
unless repo.branch_exists?(branch_name)
|
|
30
|
-
$stderr.puts "Branch '#{branch_name}' not found"
|
|
31
|
-
exit 1
|
|
32
|
-
end
|
|
31
|
+
error "Branch '#{branch_name}' not found" unless repo.branch_exists?(branch_name)
|
|
33
32
|
|
|
34
33
|
branch = Models::Branch.find_or_create(branch_name)
|
|
35
34
|
analyzer = Analyzer.new(repo)
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class List
|
|
7
|
+
include Output
|
|
8
|
+
|
|
7
9
|
def initialize(args)
|
|
8
10
|
@args = args
|
|
9
11
|
@options = parse_options
|
|
@@ -11,26 +13,19 @@ module Git
|
|
|
11
13
|
|
|
12
14
|
def run
|
|
13
15
|
repo = Repository.new
|
|
14
|
-
|
|
15
|
-
unless Database.exists?(repo.git_dir)
|
|
16
|
-
$stderr.puts "Database not initialized. Run 'git pkgs init' first."
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
16
|
+
require_database(repo)
|
|
19
17
|
|
|
20
18
|
Database.connect(repo.git_dir)
|
|
21
19
|
|
|
22
20
|
commit_sha = @options[:commit] || repo.head_sha
|
|
23
21
|
target_commit = Models::Commit.find_by(sha: commit_sha)
|
|
24
22
|
|
|
25
|
-
unless target_commit
|
|
26
|
-
$stderr.puts "Commit #{commit_sha[0, 7]} not found in database"
|
|
27
|
-
exit 1
|
|
28
|
-
end
|
|
23
|
+
error "Commit #{commit_sha[0, 7]} not found in database" unless target_commit
|
|
29
24
|
|
|
30
25
|
deps = compute_dependencies_at_commit(target_commit, repo)
|
|
31
26
|
|
|
32
27
|
if deps.empty?
|
|
33
|
-
|
|
28
|
+
empty_result "No dependencies found"
|
|
34
29
|
return
|
|
35
30
|
end
|
|
36
31
|
|
|
@@ -47,16 +42,20 @@ module Git
|
|
|
47
42
|
require "json"
|
|
48
43
|
puts JSON.pretty_generate(deps)
|
|
49
44
|
else
|
|
50
|
-
|
|
45
|
+
paginate { output_text(deps) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
def output_text(deps)
|
|
50
|
+
grouped = deps.group_by { |d| [d[:manifest_path], d[:ecosystem]] }
|
|
51
|
+
|
|
52
|
+
grouped.each do |(path, platform), manifest_deps|
|
|
53
|
+
puts "#{path} (#{platform}):"
|
|
54
|
+
manifest_deps.sort_by { |d| d[:name] }.each do |dep|
|
|
55
|
+
type_suffix = dep[:dependency_type] ? " [#{dep[:dependency_type]}]" : ""
|
|
56
|
+
puts " #{dep[:name]} #{dep[:requirement]}#{type_suffix}"
|
|
59
57
|
end
|
|
58
|
+
puts
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
|
|
@@ -144,6 +143,10 @@ module Git
|
|
|
144
143
|
options[:format] = v
|
|
145
144
|
end
|
|
146
145
|
|
|
146
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
147
|
+
options[:no_pager] = true
|
|
148
|
+
end
|
|
149
|
+
|
|
147
150
|
opts.on("-h", "--help", "Show this help") do
|
|
148
151
|
puts opts
|
|
149
152
|
exit
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Pkgs
|
|
7
|
+
module Commands
|
|
8
|
+
class Log
|
|
9
|
+
include Output
|
|
10
|
+
|
|
11
|
+
def initialize(args)
|
|
12
|
+
@args = args
|
|
13
|
+
@options = parse_options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
repo = Repository.new
|
|
18
|
+
require_database(repo)
|
|
19
|
+
|
|
20
|
+
Database.connect(repo.git_dir)
|
|
21
|
+
|
|
22
|
+
commits = Models::Commit
|
|
23
|
+
.where(has_dependency_changes: true)
|
|
24
|
+
.order(committed_at: :desc)
|
|
25
|
+
|
|
26
|
+
commits = commits.where("author_name LIKE ? OR author_email LIKE ?",
|
|
27
|
+
"%#{@options[:author]}%", "%#{@options[:author]}%") if @options[:author]
|
|
28
|
+
commits = commits.where("committed_at >= ?", parse_time(@options[:since])) if @options[:since]
|
|
29
|
+
commits = commits.where("committed_at <= ?", parse_time(@options[:until])) if @options[:until]
|
|
30
|
+
|
|
31
|
+
commits = commits.limit(@options[:limit] || 20)
|
|
32
|
+
|
|
33
|
+
if commits.empty?
|
|
34
|
+
empty_result "No commits with dependency changes found"
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if @options[:format] == "json"
|
|
39
|
+
output_json(commits)
|
|
40
|
+
else
|
|
41
|
+
paginate { output_text(commits) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def output_text(commits)
|
|
46
|
+
commits.each do |commit|
|
|
47
|
+
changes = commit.dependency_changes
|
|
48
|
+
changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
|
|
49
|
+
next if changes.empty?
|
|
50
|
+
|
|
51
|
+
puts "#{commit.short_sha} #{commit.message&.lines&.first&.strip}"
|
|
52
|
+
puts "Author: #{commit.author_name} <#{commit.author_email}>"
|
|
53
|
+
puts "Date: #{commit.committed_at.strftime("%Y-%m-%d")}"
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
added = changes.select { |c| c.change_type == "added" }
|
|
57
|
+
modified = changes.select { |c| c.change_type == "modified" }
|
|
58
|
+
removed = changes.select { |c| c.change_type == "removed" }
|
|
59
|
+
|
|
60
|
+
added.each do |change|
|
|
61
|
+
puts " + #{change.name} #{change.requirement}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
modified.each do |change|
|
|
65
|
+
puts " ~ #{change.name} #{change.previous_requirement} -> #{change.requirement}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
removed.each do |change|
|
|
69
|
+
puts " - #{change.name}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
puts
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def output_json(commits)
|
|
77
|
+
require "json"
|
|
78
|
+
|
|
79
|
+
data = commits.map do |commit|
|
|
80
|
+
changes = commit.dependency_changes
|
|
81
|
+
changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
sha: commit.sha,
|
|
85
|
+
short_sha: commit.short_sha,
|
|
86
|
+
message: commit.message&.lines&.first&.strip,
|
|
87
|
+
author_name: commit.author_name,
|
|
88
|
+
author_email: commit.author_email,
|
|
89
|
+
date: commit.committed_at.iso8601,
|
|
90
|
+
changes: changes.map do |change|
|
|
91
|
+
{
|
|
92
|
+
name: change.name,
|
|
93
|
+
change_type: change.change_type,
|
|
94
|
+
requirement: change.requirement,
|
|
95
|
+
previous_requirement: change.previous_requirement,
|
|
96
|
+
ecosystem: change.ecosystem
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
puts JSON.pretty_generate(data)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def parse_time(str)
|
|
106
|
+
Time.parse(str)
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
error "Invalid date format: #{str}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def parse_options
|
|
112
|
+
options = {}
|
|
113
|
+
|
|
114
|
+
parser = OptionParser.new do |opts|
|
|
115
|
+
opts.banner = "Usage: git pkgs log [options]"
|
|
116
|
+
|
|
117
|
+
opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
|
|
118
|
+
options[:ecosystem] = v
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
opts.on("--author=NAME", "Filter by author name or email") do |v|
|
|
122
|
+
options[:author] = v
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
opts.on("--since=DATE", "Show commits after date") do |v|
|
|
126
|
+
options[:since] = v
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
opts.on("--until=DATE", "Show commits before date") do |v|
|
|
130
|
+
options[:until] = v
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
opts.on("-n", "--limit=N", Integer, "Limit commits (default: 20)") do |v|
|
|
134
|
+
options[:limit] = v
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
opts.on("-f", "--format=FORMAT", "Output format (text, json)") do |v|
|
|
138
|
+
options[:format] = v
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
142
|
+
options[:no_pager] = true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
opts.on("-h", "--help", "Show this help") do
|
|
146
|
+
puts opts
|
|
147
|
+
exit
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
parser.parse!(@args)
|
|
152
|
+
options
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Schema
|
|
7
|
+
include Output
|
|
8
|
+
|
|
9
|
+
FORMATS = %w[text sql json markdown].freeze
|
|
10
|
+
|
|
11
|
+
def initialize(args)
|
|
12
|
+
@args = args
|
|
13
|
+
@options = parse_options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
repo = Repository.new
|
|
18
|
+
require_database(repo)
|
|
19
|
+
|
|
20
|
+
Database.connect(repo.git_dir)
|
|
21
|
+
tables = fetch_schema
|
|
22
|
+
|
|
23
|
+
case @options[:format]
|
|
24
|
+
when "sql"
|
|
25
|
+
output_sql(tables)
|
|
26
|
+
when "json"
|
|
27
|
+
output_json(tables)
|
|
28
|
+
when "markdown"
|
|
29
|
+
output_markdown(tables)
|
|
30
|
+
else
|
|
31
|
+
output_text(tables)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fetch_schema
|
|
36
|
+
conn = ActiveRecord::Base.connection
|
|
37
|
+
tables = {}
|
|
38
|
+
|
|
39
|
+
conn.tables.sort.each do |table_name|
|
|
40
|
+
next if table_name == "ar_internal_metadata"
|
|
41
|
+
next if table_name == "schema_migrations"
|
|
42
|
+
|
|
43
|
+
columns = conn.columns(table_name).map do |col|
|
|
44
|
+
{
|
|
45
|
+
name: col.name,
|
|
46
|
+
type: col.type,
|
|
47
|
+
sql_type: col.sql_type,
|
|
48
|
+
null: col.null,
|
|
49
|
+
default: col.default
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
indexes = conn.indexes(table_name).map do |idx|
|
|
54
|
+
{
|
|
55
|
+
name: idx.name,
|
|
56
|
+
columns: idx.columns,
|
|
57
|
+
unique: idx.unique
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
tables[table_name] = { columns: columns, indexes: indexes }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
tables
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def output_text(tables)
|
|
68
|
+
tables.each do |table_name, info|
|
|
69
|
+
puts "#{table_name}"
|
|
70
|
+
puts "-" * table_name.length
|
|
71
|
+
|
|
72
|
+
info[:columns].each do |col|
|
|
73
|
+
nullable = col[:null] ? "NULL" : "NOT NULL"
|
|
74
|
+
default = col[:default] ? " DEFAULT #{col[:default]}" : ""
|
|
75
|
+
puts " #{col[:name].ljust(25)} #{col[:sql_type].ljust(15)} #{nullable}#{default}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if info[:indexes].any?
|
|
79
|
+
puts
|
|
80
|
+
puts " Indexes:"
|
|
81
|
+
info[:indexes].each do |idx|
|
|
82
|
+
unique = idx[:unique] ? "UNIQUE " : ""
|
|
83
|
+
puts " #{unique}#{idx[:name]} (#{idx[:columns].join(', ')})"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
puts
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def output_sql(tables)
|
|
92
|
+
conn = ActiveRecord::Base.connection
|
|
93
|
+
|
|
94
|
+
tables.each do |table_name, info|
|
|
95
|
+
sql = conn.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='#{table_name}'").first
|
|
96
|
+
puts sql["sql"] + ";" if sql
|
|
97
|
+
puts
|
|
98
|
+
|
|
99
|
+
info[:indexes].each do |idx|
|
|
100
|
+
idx_sql = conn.execute("SELECT sql FROM sqlite_master WHERE type='index' AND name='#{idx[:name]}'").first
|
|
101
|
+
puts idx_sql["sql"] + ";" if idx_sql && idx_sql["sql"]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
puts if info[:indexes].any?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def output_json(tables)
|
|
109
|
+
require "json"
|
|
110
|
+
puts JSON.pretty_generate(tables)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def output_markdown(tables)
|
|
114
|
+
tables.each do |table_name, info|
|
|
115
|
+
puts "## #{table_name}"
|
|
116
|
+
puts
|
|
117
|
+
puts "| Column | Type | Nullable | Default |"
|
|
118
|
+
puts "|--------|------|----------|---------|"
|
|
119
|
+
|
|
120
|
+
info[:columns].each do |col|
|
|
121
|
+
nullable = col[:null] ? "Yes" : "No"
|
|
122
|
+
default = col[:default] || ""
|
|
123
|
+
puts "| #{col[:name]} | #{col[:sql_type]} | #{nullable} | #{default} |"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if info[:indexes].any?
|
|
127
|
+
puts
|
|
128
|
+
puts "**Indexes:**"
|
|
129
|
+
info[:indexes].each do |idx|
|
|
130
|
+
unique = idx[:unique] ? " (unique)" : ""
|
|
131
|
+
puts "- `#{idx[:name]}`#{unique}: #{idx[:columns].join(', ')}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
puts
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse_options
|
|
140
|
+
options = { format: "text" }
|
|
141
|
+
|
|
142
|
+
parser = OptionParser.new do |opts|
|
|
143
|
+
opts.banner = "Usage: git pkgs schema [options]"
|
|
144
|
+
|
|
145
|
+
opts.on("--format=FORMAT", FORMATS, "Output format: #{FORMATS.join(', ')} (default: text)") do |v|
|
|
146
|
+
options[:format] = v
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
opts.on("-h", "--help", "Show this help") do
|
|
150
|
+
puts opts
|
|
151
|
+
exit
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
parser.parse!(@args)
|
|
156
|
+
options
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Search
|
|
7
|
+
include Output
|
|
8
|
+
|
|
7
9
|
def initialize(args)
|
|
8
10
|
@args = args
|
|
9
11
|
@options = parse_options
|
|
@@ -12,17 +14,10 @@ module Git
|
|
|
12
14
|
def run
|
|
13
15
|
pattern = @args.first
|
|
14
16
|
|
|
15
|
-
unless pattern
|
|
16
|
-
$stderr.puts "Usage: git pkgs search <pattern>"
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
17
|
+
error "Usage: git pkgs search <pattern>" unless pattern
|
|
19
18
|
|
|
20
19
|
repo = Repository.new
|
|
21
|
-
|
|
22
|
-
unless Database.exists?(repo.git_dir)
|
|
23
|
-
$stderr.puts "Database not initialized. Run 'git pkgs init' first."
|
|
24
|
-
exit 1
|
|
25
|
-
end
|
|
20
|
+
require_database(repo)
|
|
26
21
|
|
|
27
22
|
Database.connect(repo.git_dir)
|
|
28
23
|
|
|
@@ -43,14 +38,14 @@ module Git
|
|
|
43
38
|
matches = query.distinct.pluck(:name, :ecosystem)
|
|
44
39
|
|
|
45
40
|
if matches.empty?
|
|
46
|
-
|
|
41
|
+
empty_result "No dependencies found matching '#{pattern}'"
|
|
47
42
|
return
|
|
48
43
|
end
|
|
49
44
|
|
|
50
45
|
if @options[:format] == "json"
|
|
51
46
|
output_json(matches, pattern)
|
|
52
47
|
else
|
|
53
|
-
output_text(matches, pattern)
|
|
48
|
+
paginate { output_text(matches, pattern) }
|
|
54
49
|
end
|
|
55
50
|
end
|
|
56
51
|
|
|
@@ -137,6 +132,10 @@ module Git
|
|
|
137
132
|
options[:format] = v
|
|
138
133
|
end
|
|
139
134
|
|
|
135
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
136
|
+
options[:no_pager] = true
|
|
137
|
+
end
|
|
138
|
+
|
|
140
139
|
opts.on("-h", "--help", "Show this help") do
|
|
141
140
|
puts opts
|
|
142
141
|
exit
|