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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bibliothecary"
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Pkgs
|
|
7
|
+
module Commands
|
|
8
|
+
class DiffDriver
|
|
9
|
+
include Output
|
|
10
|
+
|
|
11
|
+
# Only lockfiles - manifests are human-readable and diff fine normally
|
|
12
|
+
LOCKFILE_PATTERNS = %w[
|
|
13
|
+
Brewfile.lock.json
|
|
14
|
+
Cargo.lock
|
|
15
|
+
Cartfile.resolved
|
|
16
|
+
Gemfile.lock
|
|
17
|
+
Gopkg.lock
|
|
18
|
+
Package.resolved
|
|
19
|
+
Pipfile.lock
|
|
20
|
+
Podfile.lock
|
|
21
|
+
Project.lock.json
|
|
22
|
+
bun.lock
|
|
23
|
+
composer.lock
|
|
24
|
+
gems.locked
|
|
25
|
+
glide.lock
|
|
26
|
+
go.sum
|
|
27
|
+
mix.lock
|
|
28
|
+
npm-shrinkwrap.json
|
|
29
|
+
package-lock.json
|
|
30
|
+
packages.lock.json
|
|
31
|
+
paket.lock
|
|
32
|
+
pnpm-lock.yaml
|
|
33
|
+
poetry.lock
|
|
34
|
+
project.assets.json
|
|
35
|
+
pubspec.lock
|
|
36
|
+
pylock.toml
|
|
37
|
+
shard.lock
|
|
38
|
+
uv.lock
|
|
39
|
+
yarn.lock
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
def initialize(args)
|
|
43
|
+
@args = args
|
|
44
|
+
@options = parse_options
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run
|
|
48
|
+
if @options[:install]
|
|
49
|
+
install_driver
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if @options[:uninstall]
|
|
54
|
+
uninstall_driver
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# textconv mode: single file argument, output dependency list
|
|
59
|
+
if @args.length == 1
|
|
60
|
+
output_textconv(@args[0])
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
error "Usage: git pkgs diff-driver <file>"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def output_textconv(file_path)
|
|
68
|
+
content = read_file(file_path)
|
|
69
|
+
deps = parse_deps(file_path, content)
|
|
70
|
+
|
|
71
|
+
# Output sorted dependency list for git to diff
|
|
72
|
+
deps.keys.sort.each do |name|
|
|
73
|
+
dep = deps[name]
|
|
74
|
+
# Only show type if it's not runtime (the default)
|
|
75
|
+
type_suffix = dep[:type] && dep[:type] != "runtime" ? " [#{dep[:type]}]" : ""
|
|
76
|
+
puts "#{name} #{dep[:requirement]}#{type_suffix}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def install_driver
|
|
81
|
+
# Set up git config for textconv
|
|
82
|
+
system("git", "config", "diff.pkgs.textconv", "git-pkgs diff-driver")
|
|
83
|
+
|
|
84
|
+
# Add to .gitattributes
|
|
85
|
+
gitattributes_path = File.join(Dir.pwd, ".gitattributes")
|
|
86
|
+
existing = File.exist?(gitattributes_path) ? File.read(gitattributes_path) : ""
|
|
87
|
+
|
|
88
|
+
new_entries = []
|
|
89
|
+
LOCKFILE_PATTERNS.each do |pattern|
|
|
90
|
+
entry = "#{pattern} diff=pkgs"
|
|
91
|
+
new_entries << entry unless existing.include?(entry)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if new_entries.any?
|
|
95
|
+
File.open(gitattributes_path, "a") do |f|
|
|
96
|
+
f.puts unless existing.end_with?("\n") || existing.empty?
|
|
97
|
+
f.puts "# git-pkgs textconv for lockfiles"
|
|
98
|
+
new_entries.each { |entry| f.puts entry }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
puts "Installed textconv driver for lockfiles."
|
|
103
|
+
puts " git config: diff.pkgs.textconv = git-pkgs diff-driver"
|
|
104
|
+
puts " .gitattributes: #{new_entries.count} lockfile patterns added"
|
|
105
|
+
puts
|
|
106
|
+
puts "Now 'git diff' on lockfiles shows dependency changes."
|
|
107
|
+
puts "Use 'git diff --no-textconv' to see raw diff."
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def uninstall_driver
|
|
111
|
+
system("git", "config", "--unset", "diff.pkgs.textconv")
|
|
112
|
+
|
|
113
|
+
gitattributes_path = File.join(Dir.pwd, ".gitattributes")
|
|
114
|
+
if File.exist?(gitattributes_path)
|
|
115
|
+
lines = File.readlines(gitattributes_path)
|
|
116
|
+
lines.reject! { |line| line.include?("diff=pkgs") || line.include?("# git-pkgs") }
|
|
117
|
+
File.write(gitattributes_path, lines.join)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts "Uninstalled diff driver."
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_file(path)
|
|
124
|
+
return "" if path == "/dev/null"
|
|
125
|
+
return "" unless File.exist?(path)
|
|
126
|
+
|
|
127
|
+
File.read(path)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_deps(path, content)
|
|
131
|
+
return {} if content.empty?
|
|
132
|
+
|
|
133
|
+
result = Bibliothecary.analyse_file(path, content).first
|
|
134
|
+
return {} unless result
|
|
135
|
+
|
|
136
|
+
result[:dependencies].map { |d| [d[:name], d] }.to_h
|
|
137
|
+
rescue StandardError
|
|
138
|
+
{}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def parse_options
|
|
142
|
+
options = {}
|
|
143
|
+
|
|
144
|
+
parser = OptionParser.new do |opts|
|
|
145
|
+
opts.banner = "Usage: git pkgs diff-driver <file>"
|
|
146
|
+
opts.separator ""
|
|
147
|
+
opts.separator "Outputs dependency list for git textconv diffing."
|
|
148
|
+
|
|
149
|
+
opts.on("--install", "Install textconv driver for lockfiles") do
|
|
150
|
+
options[:install] = true
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
opts.on("--uninstall", "Uninstall textconv driver") do
|
|
154
|
+
options[:uninstall] = true
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
opts.on("-h", "--help", "Show this help") do
|
|
158
|
+
puts opts
|
|
159
|
+
exit
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
parser.parse!(@args)
|
|
164
|
+
options
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -6,6 +6,8 @@ module Git
|
|
|
6
6
|
module Pkgs
|
|
7
7
|
module Commands
|
|
8
8
|
class History
|
|
9
|
+
include Output
|
|
10
|
+
|
|
9
11
|
def initialize(args)
|
|
10
12
|
@args = args
|
|
11
13
|
@options = parse_options
|
|
@@ -15,11 +17,7 @@ module Git
|
|
|
15
17
|
package_name = @args.shift
|
|
16
18
|
|
|
17
19
|
repo = Repository.new
|
|
18
|
-
|
|
19
|
-
unless Database.exists?(repo.git_dir)
|
|
20
|
-
$stderr.puts "Database not initialized. Run 'git pkgs init' first."
|
|
21
|
-
exit 1
|
|
22
|
-
end
|
|
20
|
+
require_database(repo)
|
|
23
21
|
|
|
24
22
|
Database.connect(repo.git_dir)
|
|
25
23
|
|
|
@@ -52,18 +50,15 @@ module Git
|
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
if changes.empty?
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
else
|
|
58
|
-
puts "No dependency changes found"
|
|
59
|
-
end
|
|
53
|
+
msg = package_name ? "No history found for '#{package_name}'" : "No dependency changes found"
|
|
54
|
+
empty_result msg
|
|
60
55
|
return
|
|
61
56
|
end
|
|
62
57
|
|
|
63
58
|
if @options[:format] == "json"
|
|
64
59
|
output_json(changes)
|
|
65
60
|
else
|
|
66
|
-
output_text(changes, package_name)
|
|
61
|
+
paginate { output_text(changes, package_name) }
|
|
67
62
|
end
|
|
68
63
|
end
|
|
69
64
|
|
|
@@ -81,13 +76,13 @@ module Git
|
|
|
81
76
|
|
|
82
77
|
case change.change_type
|
|
83
78
|
when "added"
|
|
84
|
-
action = "Added"
|
|
79
|
+
action = Color.green("Added")
|
|
85
80
|
version_info = change.requirement
|
|
86
81
|
when "modified"
|
|
87
|
-
action = "Updated"
|
|
82
|
+
action = Color.yellow("Updated")
|
|
88
83
|
version_info = "#{change.previous_requirement} -> #{change.requirement}"
|
|
89
84
|
when "removed"
|
|
90
|
-
action = "Removed"
|
|
85
|
+
action = Color.red("Removed")
|
|
91
86
|
version_info = change.requirement
|
|
92
87
|
end
|
|
93
88
|
|
|
@@ -150,6 +145,10 @@ module Git
|
|
|
150
145
|
options[:until] = v
|
|
151
146
|
end
|
|
152
147
|
|
|
148
|
+
opts.on("--no-pager", "Do not pipe output into a pager") do
|
|
149
|
+
options[:no_pager] = true
|
|
150
|
+
end
|
|
151
|
+
|
|
153
152
|
opts.on("-h", "--help", "Show this help") do
|
|
154
153
|
puts opts
|
|
155
154
|
exit
|
|
@@ -163,8 +162,7 @@ module Git
|
|
|
163
162
|
def parse_time(str)
|
|
164
163
|
Time.parse(str)
|
|
165
164
|
rescue ArgumentError
|
|
166
|
-
|
|
167
|
-
exit 1
|
|
165
|
+
error "Invalid date format: #{str}"
|
|
168
166
|
end
|
|
169
167
|
end
|
|
170
168
|
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)
|
|
@@ -71,7 +69,11 @@ module Git
|
|
|
71
69
|
puts " Commits with snapshots: #{snapshot_commits}"
|
|
72
70
|
if total_dep_commits > 0
|
|
73
71
|
ratio = (snapshot_commits.to_f / total_dep_commits * 100).round(1)
|
|
74
|
-
|
|
72
|
+
if snapshot_commits > 0
|
|
73
|
+
puts " Coverage: #{ratio}% (1 snapshot per ~#{(total_dep_commits / snapshot_commits)} changes)"
|
|
74
|
+
else
|
|
75
|
+
puts " Coverage: #{ratio}%"
|
|
76
|
+
end
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -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
|
|
|
@@ -15,22 +17,19 @@ module Git
|
|
|
15
17
|
def run
|
|
16
18
|
repo = Repository.new
|
|
17
19
|
|
|
20
|
+
branch_name = @options[:branch] || repo.default_branch
|
|
21
|
+
error "Branch '#{branch_name}' not found" unless repo.branch_exists?(branch_name)
|
|
22
|
+
|
|
18
23
|
if Database.exists?(repo.git_dir) && !@options[:force]
|
|
19
24
|
puts "Database already exists. Use --force to rebuild."
|
|
20
25
|
return
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
Database.drop if @options[:force]
|
|
24
|
-
Database.connect(repo.git_dir)
|
|
29
|
+
Database.connect(repo.git_dir, check_version: false)
|
|
25
30
|
Database.create_schema(with_indexes: false)
|
|
26
31
|
Database.optimize_for_bulk_writes
|
|
27
32
|
|
|
28
|
-
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
|
|
33
|
-
|
|
34
33
|
branch = Models::Branch.find_or_create(branch_name)
|
|
35
34
|
analyzer = Analyzer.new(repo)
|
|
36
35
|
|
|
@@ -74,6 +73,7 @@ module Git
|
|
|
74
73
|
dependency_commit_count = 0
|
|
75
74
|
snapshots_stored = 0
|
|
76
75
|
processed = 0
|
|
76
|
+
last_processed_sha = nil
|
|
77
77
|
|
|
78
78
|
flush = lambda do
|
|
79
79
|
return if pending_commits.empty?
|
|
@@ -161,6 +161,8 @@ module Git
|
|
|
161
161
|
position: processed
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
last_processed_sha = rugged_commit.oid
|
|
165
|
+
|
|
164
166
|
if has_changes
|
|
165
167
|
dependency_commit_count += 1
|
|
166
168
|
|
|
@@ -207,13 +209,12 @@ module Git
|
|
|
207
209
|
flush.call if pending_commits.size >= BATCH_SIZE
|
|
208
210
|
end
|
|
209
211
|
|
|
210
|
-
# Always store final snapshot for
|
|
211
|
-
if snapshot.any?
|
|
212
|
-
|
|
213
|
-
if last_sha && !pending_snapshots.any? { |s| s[:sha] == last_sha }
|
|
212
|
+
# Always store final snapshot for the last processed commit
|
|
213
|
+
if snapshot.any? && last_processed_sha
|
|
214
|
+
unless pending_snapshots.any? { |s| s[:sha] == last_processed_sha }
|
|
214
215
|
snapshot.each do |(manifest_path, name), dep_info|
|
|
215
216
|
pending_snapshots << {
|
|
216
|
-
sha:
|
|
217
|
+
sha: last_processed_sha,
|
|
217
218
|
manifest_path: manifest_path,
|
|
218
219
|
name: name,
|
|
219
220
|
ecosystem: dep_info[:ecosystem],
|
|
@@ -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
|