git-pkgs 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE +661 -0
- data/README.md +279 -0
- data/Rakefile +8 -0
- data/benchmark_bulk.rb +167 -0
- data/benchmark_db.rb +138 -0
- data/benchmark_detailed.rb +151 -0
- data/benchmark_full.rb +131 -0
- data/docs/schema.md +129 -0
- data/exe/git-pkgs +6 -0
- data/lib/git/pkgs/analyzer.rb +270 -0
- data/lib/git/pkgs/cli.rb +73 -0
- data/lib/git/pkgs/commands/blame.rb +142 -0
- data/lib/git/pkgs/commands/branch.rb +337 -0
- data/lib/git/pkgs/commands/diff.rb +131 -0
- data/lib/git/pkgs/commands/history.rb +127 -0
- data/lib/git/pkgs/commands/hooks.rb +131 -0
- data/lib/git/pkgs/commands/info.rb +109 -0
- data/lib/git/pkgs/commands/init.rb +267 -0
- data/lib/git/pkgs/commands/list.rb +159 -0
- data/lib/git/pkgs/commands/outdated.rb +122 -0
- data/lib/git/pkgs/commands/search.rb +152 -0
- data/lib/git/pkgs/commands/stats.rb +157 -0
- data/lib/git/pkgs/commands/tree.rb +124 -0
- data/lib/git/pkgs/commands/update.rb +147 -0
- data/lib/git/pkgs/commands/why.rb +82 -0
- data/lib/git/pkgs/database.rb +143 -0
- data/lib/git/pkgs/models/branch.rb +18 -0
- data/lib/git/pkgs/models/branch_commit.rb +14 -0
- data/lib/git/pkgs/models/commit.rb +29 -0
- data/lib/git/pkgs/models/dependency_change.rb +21 -0
- data/lib/git/pkgs/models/dependency_snapshot.rb +27 -0
- data/lib/git/pkgs/models/manifest.rb +21 -0
- data/lib/git/pkgs/repository.rb +125 -0
- data/lib/git/pkgs/version.rb +7 -0
- data/lib/git/pkgs.rb +37 -0
- metadata +138 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Tree
|
|
7
|
+
def initialize(args)
|
|
8
|
+
@args = args
|
|
9
|
+
@options = parse_options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
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
|
|
19
|
+
|
|
20
|
+
Database.connect(repo.git_dir)
|
|
21
|
+
|
|
22
|
+
# Get the commit to analyze
|
|
23
|
+
commit_sha = @options[:commit] || repo.head_sha
|
|
24
|
+
commit = find_commit_with_snapshot(commit_sha, repo)
|
|
25
|
+
|
|
26
|
+
unless commit
|
|
27
|
+
$stderr.puts "No dependency data found for commit #{commit_sha[0, 7]}"
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get current snapshots
|
|
32
|
+
snapshots = commit.dependency_snapshots.includes(:manifest)
|
|
33
|
+
|
|
34
|
+
if @options[:ecosystem]
|
|
35
|
+
snapshots = snapshots.where(ecosystem: @options[:ecosystem])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if snapshots.empty?
|
|
39
|
+
puts "No dependencies found"
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Group by manifest and build tree
|
|
44
|
+
grouped = snapshots.group_by { |s| s.manifest }
|
|
45
|
+
|
|
46
|
+
grouped.each do |manifest, deps|
|
|
47
|
+
puts "#{manifest.path} (#{manifest.ecosystem})"
|
|
48
|
+
puts
|
|
49
|
+
|
|
50
|
+
# Separate by dependency type
|
|
51
|
+
by_type = deps.group_by { |d| d.dependency_type || "runtime" }
|
|
52
|
+
|
|
53
|
+
by_type.each do |type, type_deps|
|
|
54
|
+
puts " [#{type}]"
|
|
55
|
+
type_deps.sort_by(&:name).each do |dep|
|
|
56
|
+
print_dependency(dep, 2)
|
|
57
|
+
end
|
|
58
|
+
puts
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Show summary
|
|
63
|
+
puts "Total: #{snapshots.count} dependencies across #{grouped.keys.count} manifest(s)"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def print_dependency(dep, indent)
|
|
67
|
+
prefix = " " * indent
|
|
68
|
+
version = dep.requirement || "*"
|
|
69
|
+
puts "#{prefix}#{dep.name} #{version}"
|
|
70
|
+
|
|
71
|
+
# If this manifest has lockfile data, we could show transitive deps
|
|
72
|
+
# For now, we just show the direct dependencies
|
|
73
|
+
# Future enhancement: parse lockfiles to show full tree
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_commit_with_snapshot(sha, repo)
|
|
77
|
+
commit = Models::Commit.find_by(sha: sha) ||
|
|
78
|
+
Models::Commit.where("sha LIKE ?", "#{sha}%").first
|
|
79
|
+
return commit if commit&.dependency_snapshots&.any?
|
|
80
|
+
|
|
81
|
+
# Find most recent commit with a snapshot
|
|
82
|
+
branch_name = @options[:branch] || repo.default_branch
|
|
83
|
+
branch = Models::Branch.find_by(name: branch_name)
|
|
84
|
+
return nil unless branch
|
|
85
|
+
|
|
86
|
+
branch.commits
|
|
87
|
+
.joins(:dependency_snapshots)
|
|
88
|
+
.where("commits.committed_at <= ?", commit&.committed_at || Time.now)
|
|
89
|
+
.order(committed_at: :desc)
|
|
90
|
+
.distinct
|
|
91
|
+
.first
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_options
|
|
95
|
+
options = {}
|
|
96
|
+
|
|
97
|
+
parser = OptionParser.new do |opts|
|
|
98
|
+
opts.banner = "Usage: git pkgs tree [options]"
|
|
99
|
+
|
|
100
|
+
opts.on("-c", "--commit=SHA", "Show dependencies at specific commit") do |v|
|
|
101
|
+
options[:commit] = v
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
|
|
105
|
+
options[:ecosystem] = v
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
opts.on("-b", "--branch=NAME", "Branch context for finding snapshots") do |v|
|
|
109
|
+
options[:branch] = v
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
opts.on("-h", "--help", "Show this help") do
|
|
113
|
+
puts opts
|
|
114
|
+
exit
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
parser.parse!(@args)
|
|
119
|
+
options
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Update
|
|
7
|
+
def initialize(args)
|
|
8
|
+
@args = args
|
|
9
|
+
@options = parse_options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
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
|
|
19
|
+
|
|
20
|
+
Database.connect(repo.git_dir)
|
|
21
|
+
|
|
22
|
+
branch_name = @options[:branch] || repo.default_branch
|
|
23
|
+
branch = Models::Branch.find_by(name: branch_name)
|
|
24
|
+
|
|
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
|
|
29
|
+
|
|
30
|
+
since_sha = branch.last_analyzed_sha
|
|
31
|
+
current_sha = repo.branch_target(branch_name)
|
|
32
|
+
|
|
33
|
+
if since_sha == current_sha
|
|
34
|
+
puts "Already up to date."
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
analyzer = Analyzer.new(repo)
|
|
39
|
+
|
|
40
|
+
# Get current snapshot from last analyzed commit
|
|
41
|
+
last_commit = Models::Commit.find_by(sha: since_sha)
|
|
42
|
+
snapshot = {}
|
|
43
|
+
|
|
44
|
+
if last_commit
|
|
45
|
+
last_commit.dependency_snapshots.includes(:manifest).each do |s|
|
|
46
|
+
key = [s.manifest.path, s.name]
|
|
47
|
+
snapshot[key] = {
|
|
48
|
+
ecosystem: s.ecosystem,
|
|
49
|
+
requirement: s.requirement,
|
|
50
|
+
dependency_type: s.dependency_type
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
walker = repo.walk(branch_name, since_sha)
|
|
56
|
+
commits = walker.to_a
|
|
57
|
+
total = commits.size
|
|
58
|
+
processed = 0
|
|
59
|
+
dependency_commits = 0
|
|
60
|
+
last_position = Models::BranchCommit.where(branch: branch).maximum(:position) || 0
|
|
61
|
+
|
|
62
|
+
puts "Updating branch: #{branch_name}"
|
|
63
|
+
puts "Found #{total} new commits"
|
|
64
|
+
|
|
65
|
+
commits.each do |rugged_commit|
|
|
66
|
+
processed += 1
|
|
67
|
+
print "\rProcessing commit #{processed}/#{total}..."
|
|
68
|
+
|
|
69
|
+
result = analyzer.analyze_commit(rugged_commit, snapshot)
|
|
70
|
+
|
|
71
|
+
commit = Models::Commit.find_or_create_from_rugged(rugged_commit)
|
|
72
|
+
Models::BranchCommit.find_or_create_by(
|
|
73
|
+
branch: branch,
|
|
74
|
+
commit: commit,
|
|
75
|
+
position: last_position + processed
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if result && result[:changes].any?
|
|
79
|
+
dependency_commits += 1
|
|
80
|
+
commit.update(has_dependency_changes: true)
|
|
81
|
+
|
|
82
|
+
result[:changes].each do |change|
|
|
83
|
+
manifest = Models::Manifest.find_or_create(
|
|
84
|
+
path: change[:manifest_path],
|
|
85
|
+
ecosystem: change[:ecosystem],
|
|
86
|
+
kind: change[:kind]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
Models::DependencyChange.create!(
|
|
90
|
+
commit: commit,
|
|
91
|
+
manifest: manifest,
|
|
92
|
+
name: change[:name],
|
|
93
|
+
ecosystem: change[:ecosystem],
|
|
94
|
+
change_type: change[:change_type],
|
|
95
|
+
requirement: change[:requirement],
|
|
96
|
+
previous_requirement: change[:previous_requirement],
|
|
97
|
+
dependency_type: change[:dependency_type]
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
snapshot = result[:snapshot]
|
|
102
|
+
|
|
103
|
+
snapshot.each do |(manifest_path, name), dep_info|
|
|
104
|
+
manifest = Models::Manifest.find_by(path: manifest_path)
|
|
105
|
+
Models::DependencySnapshot.find_or_create_by(
|
|
106
|
+
commit: commit,
|
|
107
|
+
manifest: manifest,
|
|
108
|
+
name: name
|
|
109
|
+
) do |s|
|
|
110
|
+
s.ecosystem = dep_info[:ecosystem]
|
|
111
|
+
s.requirement = dep_info[:requirement]
|
|
112
|
+
s.dependency_type = dep_info[:dependency_type]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
branch.update(last_analyzed_sha: current_sha)
|
|
119
|
+
|
|
120
|
+
puts "\nDone!"
|
|
121
|
+
puts "Processed #{total} new commits"
|
|
122
|
+
puts "Found #{dependency_commits} commits with dependency changes"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def parse_options
|
|
126
|
+
options = {}
|
|
127
|
+
|
|
128
|
+
parser = OptionParser.new do |opts|
|
|
129
|
+
opts.banner = "Usage: git pkgs update [options]"
|
|
130
|
+
|
|
131
|
+
opts.on("-b", "--branch=NAME", "Branch to update (default: default branch)") do |v|
|
|
132
|
+
options[:branch] = v
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
opts.on("-h", "--help", "Show this help") do
|
|
136
|
+
puts opts
|
|
137
|
+
exit
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
parser.parse!(@args)
|
|
142
|
+
options
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Why
|
|
7
|
+
def initialize(args)
|
|
8
|
+
@args = args
|
|
9
|
+
@options = parse_options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
package_name = @args.shift
|
|
14
|
+
|
|
15
|
+
unless package_name
|
|
16
|
+
$stderr.puts "Usage: git pkgs why <package>"
|
|
17
|
+
exit 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
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
|
|
26
|
+
|
|
27
|
+
Database.connect(repo.git_dir)
|
|
28
|
+
|
|
29
|
+
# Find the first time this package was added
|
|
30
|
+
added_change = Models::DependencyChange
|
|
31
|
+
.includes(:commit, :manifest)
|
|
32
|
+
.for_package(package_name)
|
|
33
|
+
.added
|
|
34
|
+
.order("commits.committed_at ASC")
|
|
35
|
+
|
|
36
|
+
if @options[:ecosystem]
|
|
37
|
+
added_change = added_change.for_platform(@options[:ecosystem])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
added_change = added_change.first
|
|
41
|
+
|
|
42
|
+
unless added_change
|
|
43
|
+
puts "Package '#{package_name}' not found in dependency history"
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
commit = added_change.commit
|
|
48
|
+
|
|
49
|
+
puts "#{package_name} was added in commit #{commit.short_sha}"
|
|
50
|
+
puts
|
|
51
|
+
puts "Date: #{commit.committed_at.strftime("%Y-%m-%d %H:%M")}"
|
|
52
|
+
puts "Author: #{commit.author_name} <#{commit.author_email}>"
|
|
53
|
+
puts "Manifest: #{added_change.manifest.path}"
|
|
54
|
+
puts "Version: #{added_change.requirement}"
|
|
55
|
+
puts
|
|
56
|
+
puts "Commit message:"
|
|
57
|
+
puts commit.message.to_s.lines.map { |l| " #{l}" }.join
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_options
|
|
61
|
+
options = {}
|
|
62
|
+
|
|
63
|
+
parser = OptionParser.new do |opts|
|
|
64
|
+
opts.banner = "Usage: git pkgs why <package> [options]"
|
|
65
|
+
|
|
66
|
+
opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
|
|
67
|
+
options[:ecosystem] = v
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
opts.on("-h", "--help", "Show this help") do
|
|
71
|
+
puts opts
|
|
72
|
+
exit
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
parser.parse!(@args)
|
|
77
|
+
options
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "sqlite3"
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
module Pkgs
|
|
8
|
+
class Database
|
|
9
|
+
DB_FILE = "pkgs.sqlite3"
|
|
10
|
+
|
|
11
|
+
def self.path(git_dir = nil)
|
|
12
|
+
git_dir ||= find_git_dir
|
|
13
|
+
File.join(git_dir, DB_FILE)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.find_git_dir
|
|
17
|
+
dir = Dir.pwd
|
|
18
|
+
loop do
|
|
19
|
+
git_dir = File.join(dir, ".git")
|
|
20
|
+
return git_dir if File.directory?(git_dir)
|
|
21
|
+
|
|
22
|
+
parent = File.dirname(dir)
|
|
23
|
+
raise NotInGitRepoError, "Not in a git repository" if parent == dir
|
|
24
|
+
|
|
25
|
+
dir = parent
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.connect(git_dir = nil)
|
|
30
|
+
db_path = path(git_dir)
|
|
31
|
+
ActiveRecord::Base.establish_connection(
|
|
32
|
+
adapter: "sqlite3",
|
|
33
|
+
database: db_path
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.connect_memory
|
|
38
|
+
ActiveRecord::Base.establish_connection(
|
|
39
|
+
adapter: "sqlite3",
|
|
40
|
+
database: ":memory:"
|
|
41
|
+
)
|
|
42
|
+
create_schema
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.exists?(git_dir = nil)
|
|
46
|
+
File.exist?(path(git_dir))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.create_schema(with_indexes: true)
|
|
50
|
+
ActiveRecord::Schema.define do
|
|
51
|
+
create_table :branches, if_not_exists: true do |t|
|
|
52
|
+
t.string :name, null: false
|
|
53
|
+
t.string :last_analyzed_sha
|
|
54
|
+
t.timestamps
|
|
55
|
+
end
|
|
56
|
+
add_index :branches, :name, unique: true, if_not_exists: true
|
|
57
|
+
|
|
58
|
+
create_table :commits, if_not_exists: true do |t|
|
|
59
|
+
t.string :sha, null: false
|
|
60
|
+
t.text :message
|
|
61
|
+
t.string :author_name
|
|
62
|
+
t.string :author_email
|
|
63
|
+
t.datetime :committed_at
|
|
64
|
+
t.boolean :has_dependency_changes, default: false
|
|
65
|
+
t.timestamps
|
|
66
|
+
end
|
|
67
|
+
add_index :commits, :sha, unique: true, if_not_exists: true
|
|
68
|
+
|
|
69
|
+
create_table :branch_commits, if_not_exists: true do |t|
|
|
70
|
+
t.references :branch, foreign_key: true
|
|
71
|
+
t.references :commit, foreign_key: true
|
|
72
|
+
t.integer :position
|
|
73
|
+
end
|
|
74
|
+
add_index :branch_commits, [:branch_id, :commit_id], unique: true, if_not_exists: true
|
|
75
|
+
|
|
76
|
+
create_table :manifests, if_not_exists: true do |t|
|
|
77
|
+
t.string :path, null: false
|
|
78
|
+
t.string :ecosystem
|
|
79
|
+
t.string :kind
|
|
80
|
+
t.timestamps
|
|
81
|
+
end
|
|
82
|
+
add_index :manifests, :path, if_not_exists: true
|
|
83
|
+
|
|
84
|
+
create_table :dependency_changes, if_not_exists: true do |t|
|
|
85
|
+
t.references :commit, foreign_key: true
|
|
86
|
+
t.references :manifest, foreign_key: true
|
|
87
|
+
t.string :name, null: false
|
|
88
|
+
t.string :ecosystem
|
|
89
|
+
t.string :change_type, null: false
|
|
90
|
+
t.string :requirement
|
|
91
|
+
t.string :previous_requirement
|
|
92
|
+
t.string :dependency_type
|
|
93
|
+
t.timestamps
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
create_table :dependency_snapshots, if_not_exists: true do |t|
|
|
97
|
+
t.references :commit, foreign_key: true
|
|
98
|
+
t.references :manifest, foreign_key: true
|
|
99
|
+
t.string :name, null: false
|
|
100
|
+
t.string :ecosystem
|
|
101
|
+
t.string :requirement
|
|
102
|
+
t.string :dependency_type
|
|
103
|
+
t.timestamps
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
create_bulk_indexes if with_indexes
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.create_bulk_indexes
|
|
111
|
+
conn = ActiveRecord::Base.connection
|
|
112
|
+
|
|
113
|
+
# dependency_changes indexes
|
|
114
|
+
conn.add_index :dependency_changes, :name, if_not_exists: true
|
|
115
|
+
conn.add_index :dependency_changes, :ecosystem, if_not_exists: true
|
|
116
|
+
conn.add_index :dependency_changes, [:commit_id, :name], if_not_exists: true
|
|
117
|
+
|
|
118
|
+
# dependency_snapshots indexes
|
|
119
|
+
conn.add_index :dependency_snapshots, [:commit_id, :manifest_id, :name],
|
|
120
|
+
unique: true, name: "idx_snapshots_unique", if_not_exists: true
|
|
121
|
+
conn.add_index :dependency_snapshots, :name, if_not_exists: true
|
|
122
|
+
conn.add_index :dependency_snapshots, :ecosystem, if_not_exists: true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.optimize_for_bulk_writes
|
|
126
|
+
conn = ActiveRecord::Base.connection
|
|
127
|
+
conn.execute("PRAGMA synchronous = OFF")
|
|
128
|
+
conn.execute("PRAGMA journal_mode = WAL")
|
|
129
|
+
conn.execute("PRAGMA cache_size = -64000") # 64MB cache
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.optimize_for_reads
|
|
133
|
+
conn = ActiveRecord::Base.connection
|
|
134
|
+
conn.execute("PRAGMA synchronous = NORMAL")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.drop(git_dir = nil)
|
|
138
|
+
ActiveRecord::Base.connection.close if ActiveRecord::Base.connected?
|
|
139
|
+
File.delete(path(git_dir)) if exists?(git_dir)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Models
|
|
6
|
+
class Branch < ActiveRecord::Base
|
|
7
|
+
has_many :branch_commits, dependent: :destroy
|
|
8
|
+
has_many :commits, through: :branch_commits
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true, uniqueness: true
|
|
11
|
+
|
|
12
|
+
def self.find_or_create(name)
|
|
13
|
+
find_or_create_by(name: name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Models
|
|
6
|
+
class Commit < ActiveRecord::Base
|
|
7
|
+
has_many :branch_commits, dependent: :destroy
|
|
8
|
+
has_many :branches, through: :branch_commits
|
|
9
|
+
has_many :dependency_changes, dependent: :destroy
|
|
10
|
+
has_many :dependency_snapshots, dependent: :destroy
|
|
11
|
+
|
|
12
|
+
validates :sha, presence: true, uniqueness: true
|
|
13
|
+
|
|
14
|
+
def self.find_or_create_from_rugged(rugged_commit)
|
|
15
|
+
find_or_create_by(sha: rugged_commit.oid) do |commit|
|
|
16
|
+
commit.message = rugged_commit.message&.strip
|
|
17
|
+
commit.author_name = rugged_commit.author[:name]
|
|
18
|
+
commit.author_email = rugged_commit.author[:email]
|
|
19
|
+
commit.committed_at = rugged_commit.time
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def short_sha
|
|
24
|
+
sha[0, 7]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Models
|
|
6
|
+
class DependencyChange < ActiveRecord::Base
|
|
7
|
+
belongs_to :commit
|
|
8
|
+
belongs_to :manifest
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true
|
|
11
|
+
validates :change_type, presence: true, inclusion: { in: %w[added modified removed] }
|
|
12
|
+
|
|
13
|
+
scope :added, -> { where(change_type: "added") }
|
|
14
|
+
scope :modified, -> { where(change_type: "modified") }
|
|
15
|
+
scope :removed, -> { where(change_type: "removed") }
|
|
16
|
+
scope :for_package, ->(name) { where(name: name) }
|
|
17
|
+
scope :for_platform, ->(platform) { where(ecosystem: platform) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Models
|
|
6
|
+
class DependencySnapshot < ActiveRecord::Base
|
|
7
|
+
belongs_to :commit
|
|
8
|
+
belongs_to :manifest
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true
|
|
11
|
+
|
|
12
|
+
scope :for_package, ->(name) { where(name: name) }
|
|
13
|
+
scope :for_platform, ->(platform) { where(ecosystem: platform) }
|
|
14
|
+
scope :at_commit, ->(commit) { where(commit: commit) }
|
|
15
|
+
|
|
16
|
+
def self.current_for_branch(branch)
|
|
17
|
+
return none unless branch.last_analyzed_sha
|
|
18
|
+
|
|
19
|
+
commit = Commit.find_by(sha: branch.last_analyzed_sha)
|
|
20
|
+
return none unless commit
|
|
21
|
+
|
|
22
|
+
where(commit: commit)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Models
|
|
6
|
+
class Manifest < ActiveRecord::Base
|
|
7
|
+
has_many :dependency_changes, dependent: :destroy
|
|
8
|
+
has_many :dependency_snapshots, dependent: :destroy
|
|
9
|
+
|
|
10
|
+
validates :path, presence: true
|
|
11
|
+
|
|
12
|
+
def self.find_or_create(path:, ecosystem:, kind:)
|
|
13
|
+
find_or_create_by(path: path) do |m|
|
|
14
|
+
m.ecosystem = ecosystem
|
|
15
|
+
m.kind = kind
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|