git-pkgs 0.2.0 → 0.4.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 +26 -0
- data/CONTRIBUTING.md +27 -0
- data/LICENSE +189 -189
- data/README.md +101 -2
- data/SECURITY.md +7 -0
- data/lib/git/pkgs/analyzer.rb +1 -1
- data/lib/git/pkgs/cli.rb +12 -4
- data/lib/git/pkgs/color.rb +83 -0
- data/lib/git/pkgs/commands/blame.rb +24 -21
- data/lib/git/pkgs/commands/branch.rb +11 -35
- data/lib/git/pkgs/commands/diff.rb +27 -35
- data/lib/git/pkgs/commands/diff_driver.rb +169 -0
- 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 +8 -6
- data/lib/git/pkgs/commands/init.rb +13 -12
- 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/where.rb +166 -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 +8 -1
- metadata +13 -2
|
@@ -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
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Pkgs
|
|
5
|
+
module Commands
|
|
6
|
+
class Where
|
|
7
|
+
include Output
|
|
8
|
+
|
|
9
|
+
def initialize(args)
|
|
10
|
+
@args = args
|
|
11
|
+
@options = parse_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
name = @args.first
|
|
16
|
+
|
|
17
|
+
error "Usage: git pkgs where <package-name>" unless name
|
|
18
|
+
|
|
19
|
+
repo = Repository.new
|
|
20
|
+
require_database(repo)
|
|
21
|
+
|
|
22
|
+
Database.connect(repo.git_dir)
|
|
23
|
+
|
|
24
|
+
workdir = File.dirname(repo.git_dir)
|
|
25
|
+
branch = Models::Branch.find_by(name: @options[:branch] || repo.default_branch)
|
|
26
|
+
|
|
27
|
+
unless branch
|
|
28
|
+
error "Branch not found. Run 'git pkgs init' first."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
snapshots = Models::DependencySnapshot.current_for_branch(branch)
|
|
32
|
+
snapshots = snapshots.where(ecosystem: @options[:ecosystem]) if @options[:ecosystem]
|
|
33
|
+
|
|
34
|
+
manifest_paths = snapshots.for_package(name).joins(:manifest).pluck("manifests.path").uniq
|
|
35
|
+
|
|
36
|
+
if manifest_paths.empty?
|
|
37
|
+
empty_result "Package '#{name}' not found in current dependencies"
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
results = manifest_paths.flat_map do |path|
|
|
42
|
+
find_in_manifest(name, File.join(workdir, path), path)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if results.empty?
|
|
46
|
+
empty_result "Package '#{name}' tracked but not found in current files"
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if @options[:format] == "json"
|
|
51
|
+
output_json(results)
|
|
52
|
+
else
|
|
53
|
+
paginate { output_text(results, name) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_in_manifest(name, full_path, display_path)
|
|
58
|
+
return [] unless File.exist?(full_path)
|
|
59
|
+
|
|
60
|
+
lines = File.readlines(full_path)
|
|
61
|
+
matches = []
|
|
62
|
+
|
|
63
|
+
lines.each_with_index do |line, idx|
|
|
64
|
+
next unless line.include?(name)
|
|
65
|
+
|
|
66
|
+
match = { path: display_path, line: idx + 1, content: line.rstrip }
|
|
67
|
+
|
|
68
|
+
if context_lines > 0
|
|
69
|
+
match[:before] = context_before(lines, idx)
|
|
70
|
+
match[:after] = context_after(lines, idx)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
matches << match
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
matches
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def context_lines
|
|
80
|
+
@options[:context] || 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def context_before(lines, idx)
|
|
84
|
+
start_idx = [0, idx - context_lines].max
|
|
85
|
+
(start_idx...idx).map { |i| { line: i + 1, content: lines[i].rstrip } }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def context_after(lines, idx)
|
|
89
|
+
end_idx = [lines.length - 1, idx + context_lines].min
|
|
90
|
+
((idx + 1)..end_idx).map { |i| { line: i + 1, content: lines[i].rstrip } }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def output_text(results, name)
|
|
94
|
+
results.each_with_index do |result, i|
|
|
95
|
+
puts "--" if i > 0 && context_lines > 0
|
|
96
|
+
|
|
97
|
+
result[:before]&.each do |ctx|
|
|
98
|
+
puts format_context_line(result[:path], ctx[:line], ctx[:content])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
puts format_match_line(result[:path], result[:line], result[:content], name)
|
|
102
|
+
|
|
103
|
+
result[:after]&.each do |ctx|
|
|
104
|
+
puts format_context_line(result[:path], ctx[:line], ctx[:content])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def format_match_line(path, line_num, content, name)
|
|
110
|
+
path_str = Color.magenta(path)
|
|
111
|
+
line_str = Color.green(line_num.to_s)
|
|
112
|
+
highlighted = content.gsub(name, Color.red(name))
|
|
113
|
+
"#{path_str}:#{line_str}:#{highlighted}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def format_context_line(path, line_num, content)
|
|
117
|
+
path_str = Color.magenta(path)
|
|
118
|
+
line_str = Color.green(line_num.to_s)
|
|
119
|
+
content_str = Color.dim(content)
|
|
120
|
+
"#{path_str}-#{line_str}-#{content_str}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def output_json(results)
|
|
124
|
+
require "json"
|
|
125
|
+
puts JSON.pretty_generate(results)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def parse_options
|
|
129
|
+
options = {}
|
|
130
|
+
|
|
131
|
+
parser = OptionParser.new do |opts|
|
|
132
|
+
opts.banner = "Usage: git pkgs where <package-name> [options]"
|
|
133
|
+
|
|
134
|
+
opts.on("-b", "--branch=NAME", "Branch to search (default: current)") do |v|
|
|
135
|
+
options[:branch] = v
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
opts.on("-e", "--ecosystem=NAME", "Filter by ecosystem") do |v|
|
|
139
|
+
options[:ecosystem] = v
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
opts.on("-C", "--context=NUM", Integer, "Show NUM lines of context") do |v|
|
|
143
|
+
options[:context] = v
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
opts.on("-f", "--format=FORMAT", "Output format (text, json)") do |v|
|
|
147
|
+
options[:format] = v
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
151
|
+
options[:no_pager] = true
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
opts.on("-h", "--help", "Show this help") do
|
|
155
|
+
puts opts
|
|
156
|
+
exit
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
parser.parse!(@args)
|
|
161
|
+
options
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
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
|
data/lib/git/pkgs/repository.rb
CHANGED
|
@@ -7,9 +7,9 @@ module Git
|
|
|
7
7
|
class Repository
|
|
8
8
|
attr_reader :path
|
|
9
9
|
|
|
10
|
-
def initialize(path =
|
|
11
|
-
@path = path
|
|
12
|
-
@rugged = Rugged::Repository.new(path)
|
|
10
|
+
def initialize(path = nil)
|
|
11
|
+
@path = path || ENV["GIT_DIR"] || Dir.pwd
|
|
12
|
+
@rugged = Rugged::Repository.new(@path)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def git_dir
|
data/lib/git/pkgs/version.rb
CHANGED
data/lib/git/pkgs.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "pkgs/version"
|
|
4
|
+
require_relative "pkgs/output"
|
|
5
|
+
require_relative "pkgs/color"
|
|
4
6
|
require_relative "pkgs/cli"
|
|
5
7
|
require_relative "pkgs/database"
|
|
6
8
|
require_relative "pkgs/repository"
|
|
@@ -21,13 +23,18 @@ require_relative "pkgs/commands/list"
|
|
|
21
23
|
require_relative "pkgs/commands/history"
|
|
22
24
|
require_relative "pkgs/commands/why"
|
|
23
25
|
require_relative "pkgs/commands/blame"
|
|
24
|
-
require_relative "pkgs/commands/
|
|
26
|
+
require_relative "pkgs/commands/stale"
|
|
25
27
|
require_relative "pkgs/commands/stats"
|
|
26
28
|
require_relative "pkgs/commands/diff"
|
|
27
29
|
require_relative "pkgs/commands/tree"
|
|
28
30
|
require_relative "pkgs/commands/branch"
|
|
29
31
|
require_relative "pkgs/commands/search"
|
|
30
32
|
require_relative "pkgs/commands/show"
|
|
33
|
+
require_relative "pkgs/commands/where"
|
|
34
|
+
require_relative "pkgs/commands/log"
|
|
35
|
+
require_relative "pkgs/commands/upgrade"
|
|
36
|
+
require_relative "pkgs/commands/schema"
|
|
37
|
+
require_relative "pkgs/commands/diff_driver"
|
|
31
38
|
|
|
32
39
|
module Git
|
|
33
40
|
module Pkgs
|