git-pkgs 0.2.0 → 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 +15 -1
- data/README.md +60 -2
- data/lib/git/pkgs/cli.rb +8 -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 +14 -16
- 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 +17 -23
- data/lib/git/pkgs/commands/{outdated.rb → stale.rb} +16 -13
- data/lib/git/pkgs/commands/stats.rb +59 -17
- 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 +6 -1
- metadata +8 -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 Stats
|
|
9
|
+
include Output
|
|
10
|
+
|
|
7
11
|
def initialize(args)
|
|
8
12
|
@args = args
|
|
9
13
|
@options = parse_options
|
|
@@ -11,11 +15,7 @@ module Git
|
|
|
11
15
|
|
|
12
16
|
def run
|
|
13
17
|
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
|
|
18
|
+
require_database(repo)
|
|
19
19
|
|
|
20
20
|
Database.connect(repo.git_dir)
|
|
21
21
|
|
|
@@ -31,19 +31,27 @@ module Git
|
|
|
31
31
|
require "json"
|
|
32
32
|
puts JSON.pretty_generate(data)
|
|
33
33
|
else
|
|
34
|
-
output_text(data)
|
|
34
|
+
paginate { output_text(data) }
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def collect_stats(branch, branch_name)
|
|
40
40
|
ecosystem = @options[:ecosystem]
|
|
41
|
+
since_time = @options[:since] ? parse_time(@options[:since]) : nil
|
|
42
|
+
until_time = @options[:until] ? parse_time(@options[:until]) : nil
|
|
43
|
+
|
|
44
|
+
commits = branch&.commits || Models::Commit.none
|
|
45
|
+
commits = commits.where("committed_at >= ?", since_time) if since_time
|
|
46
|
+
commits = commits.where("committed_at <= ?", until_time) if until_time
|
|
41
47
|
|
|
42
48
|
data = {
|
|
43
49
|
branch: branch_name,
|
|
44
50
|
ecosystem: ecosystem,
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
since: @options[:since],
|
|
52
|
+
until: @options[:until],
|
|
53
|
+
commits_analyzed: commits.count,
|
|
54
|
+
commits_with_changes: commits.where(has_dependency_changes: true).count,
|
|
47
55
|
current_dependencies: {},
|
|
48
56
|
changes: {},
|
|
49
57
|
most_changed: [],
|
|
@@ -62,8 +70,10 @@ module Git
|
|
|
62
70
|
}
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
changes = Models::DependencyChange.
|
|
73
|
+
changes = Models::DependencyChange.joins(:commit)
|
|
66
74
|
changes = changes.where(ecosystem: ecosystem) if ecosystem
|
|
75
|
+
changes = changes.where("commits.committed_at >= ?", since_time) if since_time
|
|
76
|
+
changes = changes.where("commits.committed_at <= ?", until_time) if until_time
|
|
67
77
|
|
|
68
78
|
data[:changes] = {
|
|
69
79
|
total: changes.count,
|
|
@@ -84,7 +94,10 @@ module Git
|
|
|
84
94
|
manifests = manifests.where(ecosystem: ecosystem) if ecosystem
|
|
85
95
|
|
|
86
96
|
data[:manifests] = manifests.map do |manifest|
|
|
87
|
-
|
|
97
|
+
manifest_changes = manifest.dependency_changes.joins(:commit)
|
|
98
|
+
manifest_changes = manifest_changes.where("commits.committed_at >= ?", since_time) if since_time
|
|
99
|
+
manifest_changes = manifest_changes.where("commits.committed_at <= ?", until_time) if until_time
|
|
100
|
+
{ path: manifest.path, ecosystem: manifest.ecosystem, changes: manifest_changes.count }
|
|
88
101
|
end
|
|
89
102
|
|
|
90
103
|
data
|
|
@@ -97,6 +110,8 @@ module Git
|
|
|
97
110
|
|
|
98
111
|
puts "Branch: #{data[:branch]}"
|
|
99
112
|
puts "Ecosystem: #{data[:ecosystem]}" if data[:ecosystem]
|
|
113
|
+
puts "Since: #{data[:since]}" if data[:since]
|
|
114
|
+
puts "Until: #{data[:until]}" if data[:until]
|
|
100
115
|
puts "Commits analyzed: #{data[:commits_analyzed]}"
|
|
101
116
|
puts "Commits with changes: #{data[:commits_with_changes]}"
|
|
102
117
|
puts
|
|
@@ -144,11 +159,16 @@ module Git
|
|
|
144
159
|
end
|
|
145
160
|
|
|
146
161
|
def output_by_author
|
|
162
|
+
since_time = @options[:since] ? parse_time(@options[:since]) : nil
|
|
163
|
+
until_time = @options[:until] ? parse_time(@options[:until]) : nil
|
|
164
|
+
|
|
147
165
|
changes = Models::DependencyChange
|
|
148
166
|
.joins(:commit)
|
|
149
167
|
.where(change_type: "added")
|
|
150
168
|
|
|
151
169
|
changes = changes.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
|
|
170
|
+
changes = changes.where("commits.committed_at >= ?", since_time) if since_time
|
|
171
|
+
changes = changes.where("commits.committed_at <= ?", until_time) if until_time
|
|
152
172
|
|
|
153
173
|
counts = changes
|
|
154
174
|
.group("commits.author_name")
|
|
@@ -157,7 +177,7 @@ module Git
|
|
|
157
177
|
.count
|
|
158
178
|
|
|
159
179
|
if counts.empty?
|
|
160
|
-
|
|
180
|
+
empty_result "No dependency additions found"
|
|
161
181
|
return
|
|
162
182
|
end
|
|
163
183
|
|
|
@@ -166,15 +186,25 @@ module Git
|
|
|
166
186
|
data = counts.map { |name, count| { author: name, added: count } }
|
|
167
187
|
puts JSON.pretty_generate(data)
|
|
168
188
|
else
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
paginate { output_by_author_text(counts) }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def output_by_author_text(counts)
|
|
194
|
+
puts "Dependencies Added by Author"
|
|
195
|
+
puts "=" * 40
|
|
196
|
+
puts
|
|
197
|
+
counts.each do |name, count|
|
|
198
|
+
puts " #{count.to_s.rjust(4)} #{name}"
|
|
175
199
|
end
|
|
176
200
|
end
|
|
177
201
|
|
|
202
|
+
def parse_time(str)
|
|
203
|
+
Time.parse(str)
|
|
204
|
+
rescue ArgumentError
|
|
205
|
+
error "Invalid date format: #{str}"
|
|
206
|
+
end
|
|
207
|
+
|
|
178
208
|
def parse_options
|
|
179
209
|
options = {}
|
|
180
210
|
|
|
@@ -193,6 +223,14 @@ module Git
|
|
|
193
223
|
options[:format] = v
|
|
194
224
|
end
|
|
195
225
|
|
|
226
|
+
opts.on("--since=DATE", "Show changes after date") do |v|
|
|
227
|
+
options[:since] = v
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
opts.on("--until=DATE", "Show changes before date") do |v|
|
|
231
|
+
options[:until] = v
|
|
232
|
+
end
|
|
233
|
+
|
|
196
234
|
opts.on("--by-author", "Show dependencies added by author") do
|
|
197
235
|
options[:by_author] = true
|
|
198
236
|
end
|
|
@@ -201,6 +239,10 @@ module Git
|
|
|
201
239
|
options[:limit] = v
|
|
202
240
|
end
|
|
203
241
|
|
|
242
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
243
|
+
options[:no_pager] = true
|
|
244
|
+
end
|
|
245
|
+
|
|
204
246
|
opts.on("-h", "--help", "Show this help") do
|
|
205
247
|
puts opts
|
|
206
248
|
exit
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Tree
|
|
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
|
Database.connect(repo.git_dir)
|
|
21
19
|
|
|
@@ -23,10 +21,7 @@ module Git
|
|
|
23
21
|
commit_sha = @options[:commit] || repo.head_sha
|
|
24
22
|
commit = find_commit_with_snapshot(commit_sha, repo)
|
|
25
23
|
|
|
26
|
-
unless commit
|
|
27
|
-
$stderr.puts "No dependency data found for commit #{commit_sha[0, 7]}"
|
|
28
|
-
exit 1
|
|
29
|
-
end
|
|
24
|
+
error "No dependency data found for commit #{commit_sha[0, 7]}" unless commit
|
|
30
25
|
|
|
31
26
|
# Get current snapshots
|
|
32
27
|
snapshots = commit.dependency_snapshots.includes(:manifest)
|
|
@@ -36,13 +31,17 @@ module Git
|
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
if snapshots.empty?
|
|
39
|
-
|
|
34
|
+
empty_result "No dependencies found"
|
|
40
35
|
return
|
|
41
36
|
end
|
|
42
37
|
|
|
43
38
|
# Group by manifest and build tree
|
|
44
39
|
grouped = snapshots.group_by { |s| s.manifest }
|
|
45
40
|
|
|
41
|
+
paginate { output_text(grouped, snapshots) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def output_text(grouped, snapshots)
|
|
46
45
|
grouped.each do |manifest, deps|
|
|
47
46
|
puts "#{manifest.path} (#{manifest.ecosystem})"
|
|
48
47
|
puts
|
|
@@ -109,6 +108,10 @@ module Git
|
|
|
109
108
|
options[:branch] = v
|
|
110
109
|
end
|
|
111
110
|
|
|
111
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
112
|
+
options[:no_pager] = true
|
|
113
|
+
end
|
|
114
|
+
|
|
112
115
|
opts.on("-h", "--help", "Show this help") do
|
|
113
116
|
puts opts
|
|
114
117
|
exit
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Update
|
|
7
|
+
include Output
|
|
8
|
+
|
|
7
9
|
def initialize(args)
|
|
8
10
|
@args = args
|
|
9
11
|
@options = parse_options
|
|
@@ -11,21 +13,14 @@ 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
|
branch_name = @options[:branch] || repo.default_branch
|
|
23
21
|
branch = Models::Branch.find_by(name: branch_name)
|
|
24
22
|
|
|
25
|
-
unless branch
|
|
26
|
-
$stderr.puts "Branch '#{branch_name}' not in database. Run 'git pkgs init --branch=#{branch_name}' first."
|
|
27
|
-
exit 1
|
|
28
|
-
end
|
|
23
|
+
error "Branch '#{branch_name}' not in database. Run 'git pkgs init --branch=#{branch_name}' first." unless branch
|
|
29
24
|
|
|
30
25
|
since_sha = branch.last_analyzed_sha
|
|
31
26
|
current_sha = repo.branch_target(branch_name)
|
|
@@ -62,60 +57,62 @@ module Git
|
|
|
62
57
|
puts "Updating branch: #{branch_name}"
|
|
63
58
|
puts "Found #{total} new commits"
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
60
|
+
ActiveRecord::Base.transaction do
|
|
61
|
+
commits.each do |rugged_commit|
|
|
62
|
+
processed += 1
|
|
63
|
+
print "\rProcessing commit #{processed}/#{total}..."
|
|
64
|
+
|
|
65
|
+
result = analyzer.analyze_commit(rugged_commit, snapshot)
|
|
66
|
+
|
|
67
|
+
commit = Models::Commit.find_or_create_from_rugged(rugged_commit)
|
|
68
|
+
Models::BranchCommit.find_or_create_by(
|
|
69
|
+
branch: branch,
|
|
70
|
+
commit: commit,
|
|
71
|
+
position: last_position + processed
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if result && result[:changes].any?
|
|
75
|
+
dependency_commits += 1
|
|
76
|
+
commit.update(has_dependency_changes: true)
|
|
77
|
+
|
|
78
|
+
result[:changes].each do |change|
|
|
79
|
+
manifest = Models::Manifest.find_or_create(
|
|
80
|
+
path: change[:manifest_path],
|
|
81
|
+
ecosystem: change[:ecosystem],
|
|
82
|
+
kind: change[:kind]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
Models::DependencyChange.create!(
|
|
86
|
+
commit: commit,
|
|
87
|
+
manifest: manifest,
|
|
88
|
+
name: change[:name],
|
|
89
|
+
ecosystem: change[:ecosystem],
|
|
90
|
+
change_type: change[:change_type],
|
|
91
|
+
requirement: change[:requirement],
|
|
92
|
+
previous_requirement: change[:previous_requirement],
|
|
93
|
+
dependency_type: change[:dependency_type]
|
|
94
|
+
)
|
|
95
|
+
end
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
snapshot = result[:snapshot]
|
|
98
|
+
|
|
99
|
+
snapshot.each do |(manifest_path, name), dep_info|
|
|
100
|
+
manifest = Models::Manifest.find_by(path: manifest_path)
|
|
101
|
+
Models::DependencySnapshot.find_or_create_by(
|
|
102
|
+
commit: commit,
|
|
103
|
+
manifest: manifest,
|
|
104
|
+
name: name
|
|
105
|
+
) do |s|
|
|
106
|
+
s.ecosystem = dep_info[:ecosystem]
|
|
107
|
+
s.requirement = dep_info[:requirement]
|
|
108
|
+
s.dependency_type = dep_info[:dependency_type]
|
|
109
|
+
end
|
|
113
110
|
end
|
|
114
111
|
end
|
|
115
112
|
end
|
|
116
|
-
end
|
|
117
113
|
|
|
118
|
-
|
|
114
|
+
branch.update(last_analyzed_sha: current_sha)
|
|
115
|
+
end
|
|
119
116
|
|
|
120
117
|
puts "\nDone!"
|
|
121
118
|
puts "Processed #{total} new commits"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Upgrade
|
|
7
|
+
include Output
|
|
8
|
+
|
|
9
|
+
def initialize(args)
|
|
10
|
+
@args = args
|
|
11
|
+
@options = parse_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
repo = Repository.new
|
|
16
|
+
require_database(repo)
|
|
17
|
+
|
|
18
|
+
Database.connect(repo.git_dir, check_version: false)
|
|
19
|
+
|
|
20
|
+
stored = Database.stored_version || 0
|
|
21
|
+
current = Database::SCHEMA_VERSION
|
|
22
|
+
|
|
23
|
+
if stored >= current
|
|
24
|
+
puts "Database is up to date (version #{current})"
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
puts "Upgrading database from version #{stored} to #{current}..."
|
|
29
|
+
puts "This requires re-indexing the repository."
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
# Run init --force
|
|
33
|
+
Init.new(["--force"]).run
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_options
|
|
37
|
+
options = {}
|
|
38
|
+
|
|
39
|
+
parser = OptionParser.new do |opts|
|
|
40
|
+
opts.banner = "Usage: git pkgs upgrade [options]"
|
|
41
|
+
|
|
42
|
+
opts.on("-h", "--help", "Show this help") do
|
|
43
|
+
puts opts
|
|
44
|
+
exit
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
parser.parse!(@args)
|
|
49
|
+
options
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -4,6 +4,8 @@ module Git
|
|
|
4
4
|
module Pkgs
|
|
5
5
|
module Commands
|
|
6
6
|
class Why
|
|
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
|
package_name = @args.shift
|
|
14
16
|
|
|
15
|
-
unless package_name
|
|
16
|
-
$stderr.puts "Usage: git pkgs why <package>"
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
17
|
+
error "Usage: git pkgs why <package>" unless package_name
|
|
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
|
|
|
@@ -40,7 +35,7 @@ module Git
|
|
|
40
35
|
added_change = added_change.first
|
|
41
36
|
|
|
42
37
|
unless added_change
|
|
43
|
-
|
|
38
|
+
empty_result "Package '#{package_name}' not found in dependency history"
|
|
44
39
|
return
|
|
45
40
|
end
|
|
46
41
|
|
data/lib/git/pkgs/database.rb
CHANGED
|
@@ -7,13 +7,24 @@ module Git
|
|
|
7
7
|
module Pkgs
|
|
8
8
|
class Database
|
|
9
9
|
DB_FILE = "pkgs.sqlite3"
|
|
10
|
+
SCHEMA_VERSION = 1
|
|
10
11
|
|
|
11
12
|
def self.path(git_dir = nil)
|
|
13
|
+
return ENV["GIT_PKGS_DB"] if ENV["GIT_PKGS_DB"] && !ENV["GIT_PKGS_DB"].empty?
|
|
14
|
+
|
|
12
15
|
git_dir ||= find_git_dir
|
|
13
16
|
File.join(git_dir, DB_FILE)
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def self.find_git_dir
|
|
20
|
+
# Respect GIT_DIR environment variable
|
|
21
|
+
if ENV["GIT_DIR"] && !ENV["GIT_DIR"].empty?
|
|
22
|
+
git_dir = ENV["GIT_DIR"]
|
|
23
|
+
return git_dir if File.directory?(git_dir)
|
|
24
|
+
|
|
25
|
+
raise NotInGitRepoError, "GIT_DIR '#{git_dir}' does not exist"
|
|
26
|
+
end
|
|
27
|
+
|
|
17
28
|
dir = Dir.pwd
|
|
18
29
|
loop do
|
|
19
30
|
git_dir = File.join(dir, ".git")
|
|
@@ -26,12 +37,13 @@ module Git
|
|
|
26
37
|
end
|
|
27
38
|
end
|
|
28
39
|
|
|
29
|
-
def self.connect(git_dir = nil)
|
|
40
|
+
def self.connect(git_dir = nil, check_version: true)
|
|
30
41
|
db_path = path(git_dir)
|
|
31
42
|
ActiveRecord::Base.establish_connection(
|
|
32
43
|
adapter: "sqlite3",
|
|
33
44
|
database: db_path
|
|
34
45
|
)
|
|
46
|
+
check_version! if check_version
|
|
35
47
|
end
|
|
36
48
|
|
|
37
49
|
def self.connect_memory
|
|
@@ -48,6 +60,10 @@ module Git
|
|
|
48
60
|
|
|
49
61
|
def self.create_schema(with_indexes: true)
|
|
50
62
|
ActiveRecord::Schema.define do
|
|
63
|
+
create_table :schema_info, if_not_exists: true do |t|
|
|
64
|
+
t.integer :version, null: false
|
|
65
|
+
end
|
|
66
|
+
|
|
51
67
|
create_table :branches, if_not_exists: true do |t|
|
|
52
68
|
t.string :name, null: false
|
|
53
69
|
t.string :last_analyzed_sha
|
|
@@ -104,6 +120,7 @@ module Git
|
|
|
104
120
|
end
|
|
105
121
|
end
|
|
106
122
|
|
|
123
|
+
set_version
|
|
107
124
|
create_bulk_indexes if with_indexes
|
|
108
125
|
end
|
|
109
126
|
|
|
@@ -122,6 +139,43 @@ module Git
|
|
|
122
139
|
conn.add_index :dependency_snapshots, :ecosystem, if_not_exists: true
|
|
123
140
|
end
|
|
124
141
|
|
|
142
|
+
def self.stored_version
|
|
143
|
+
conn = ActiveRecord::Base.connection
|
|
144
|
+
return nil unless conn.table_exists?(:schema_info)
|
|
145
|
+
|
|
146
|
+
result = conn.select_value("SELECT version FROM schema_info LIMIT 1")
|
|
147
|
+
result&.to_i
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.set_version(version = SCHEMA_VERSION)
|
|
151
|
+
conn = ActiveRecord::Base.connection
|
|
152
|
+
conn.execute("DELETE FROM schema_info")
|
|
153
|
+
conn.execute("INSERT INTO schema_info (version) VALUES (#{version})")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.needs_upgrade?
|
|
157
|
+
conn = ActiveRecord::Base.connection
|
|
158
|
+
|
|
159
|
+
# No tables at all = fresh database, no upgrade needed
|
|
160
|
+
return false unless conn.table_exists?(:commits)
|
|
161
|
+
|
|
162
|
+
# Has commits table but no schema_info = old database, needs upgrade
|
|
163
|
+
return true unless conn.table_exists?(:schema_info)
|
|
164
|
+
|
|
165
|
+
# Check version
|
|
166
|
+
stored = stored_version || 0
|
|
167
|
+
stored < SCHEMA_VERSION
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def self.check_version!
|
|
171
|
+
return unless needs_upgrade?
|
|
172
|
+
|
|
173
|
+
stored = stored_version || 0
|
|
174
|
+
$stderr.puts "Database schema is outdated (version #{stored}, current is #{SCHEMA_VERSION})."
|
|
175
|
+
$stderr.puts "Run 'git pkgs upgrade' to update."
|
|
176
|
+
exit 1
|
|
177
|
+
end
|
|
178
|
+
|
|
125
179
|
def self.optimize_for_bulk_writes
|
|
126
180
|
conn = ActiveRecord::Base.connection
|
|
127
181
|
conn.execute("PRAGMA synchronous = OFF")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "pager"
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
module Pkgs
|
|
8
|
+
module Output
|
|
9
|
+
include Pager
|
|
10
|
+
# Print error message and exit with code 1.
|
|
11
|
+
# Use for user errors (bad input, invalid refs) and system errors (db missing).
|
|
12
|
+
# When format is :json, outputs JSON to stdout; otherwise outputs text to stderr.
|
|
13
|
+
def error(msg, format: nil)
|
|
14
|
+
format ||= @options[:format] if defined?(@options) && @options.is_a?(Hash)
|
|
15
|
+
|
|
16
|
+
if format == "json"
|
|
17
|
+
puts JSON.generate({ error: msg })
|
|
18
|
+
else
|
|
19
|
+
$stderr.puts msg
|
|
20
|
+
end
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Print informational message for "no results" cases.
|
|
25
|
+
# When format is :json, outputs empty JSON array/object; otherwise outputs text.
|
|
26
|
+
def empty_result(msg, format: nil, json_value: [])
|
|
27
|
+
format ||= @options[:format] if defined?(@options) && @options.is_a?(Hash)
|
|
28
|
+
|
|
29
|
+
if format == "json"
|
|
30
|
+
puts JSON.generate(json_value)
|
|
31
|
+
else
|
|
32
|
+
puts msg
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Standard check for database existence.
|
|
37
|
+
def require_database(repo)
|
|
38
|
+
return if Database.exists?(repo.git_dir)
|
|
39
|
+
|
|
40
|
+
error "Database not initialized. Run 'git pkgs init' first."
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Pkgs
|
|
7
|
+
module Pager
|
|
8
|
+
# Returns the pager command following git's precedence:
|
|
9
|
+
# 1. GIT_PAGER environment variable
|
|
10
|
+
# 2. core.pager git config
|
|
11
|
+
# 3. PAGER environment variable
|
|
12
|
+
# 4. less as fallback
|
|
13
|
+
def git_pager
|
|
14
|
+
return ENV["GIT_PAGER"] if ENV["GIT_PAGER"] && !ENV["GIT_PAGER"].empty?
|
|
15
|
+
|
|
16
|
+
config_pager = `git config --get core.pager`.chomp
|
|
17
|
+
return config_pager unless config_pager.empty?
|
|
18
|
+
|
|
19
|
+
return ENV["PAGER"] if ENV["PAGER"] && !ENV["PAGER"].empty?
|
|
20
|
+
|
|
21
|
+
"less -FRSX"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns true if paging is disabled (pager set to empty string or 'cat')
|
|
25
|
+
def pager_disabled?
|
|
26
|
+
pager = git_pager
|
|
27
|
+
pager.empty? || pager == "cat"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Captures output from a block and sends it through the pager.
|
|
31
|
+
# Falls back to direct output if:
|
|
32
|
+
# - stdout is not a TTY
|
|
33
|
+
# - pager is disabled
|
|
34
|
+
# - pager command fails
|
|
35
|
+
def paginate
|
|
36
|
+
if !$stdout.tty? || pager_disabled? || paging_disabled?
|
|
37
|
+
yield
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
output = StringIO.new
|
|
42
|
+
old_stdout = $stdout
|
|
43
|
+
$stdout = output
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
yield
|
|
47
|
+
ensure
|
|
48
|
+
$stdout = old_stdout
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
content = output.string
|
|
52
|
+
return if content.empty?
|
|
53
|
+
|
|
54
|
+
IO.popen(git_pager, "w") { |io| io.write(content) }
|
|
55
|
+
rescue Errno::EPIPE
|
|
56
|
+
# User quit pager early
|
|
57
|
+
rescue Errno::ENOENT
|
|
58
|
+
# Pager command not found, fall back to direct output
|
|
59
|
+
print content
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check if paging was disabled via --no-pager option
|
|
63
|
+
def paging_disabled?
|
|
64
|
+
defined?(@options) && @options.is_a?(Hash) && @options[:no_pager]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|