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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ab0df1352b215987b6611a997fc62aa1e2a3ea05abe597c134812f9ec17d9d7
4
- data.tar.gz: 5e929d2ebe9f2538d8d6b29ffc6ac89c7164f2d82afc898954159ae1a4a09ca9
3
+ metadata.gz: 6152060237bbc1d9902da8e721bb23cfeaed46bbe2f5241e848aa0853d906710
4
+ data.tar.gz: 5eebb36bd3eed487a100ddc8d3ef4c88cb0cdb28734f777d6fbfbd267f87f43e
5
5
  SHA512:
6
- metadata.gz: 8e8eb54a1557bd2fa965fae8dfc2d2cceca578bec650a95821abef16f19ba2d6d6ab7c88a092cddca87de0b111fa9dd526a76439555144a8638f8507201b552e
7
- data.tar.gz: 288792ce23dc723dcf61d37606cbe26cd89f33052687156d5ae032885c8e963647d972c7b772d4712da03aab024be61eb313992a213bff88d7c02c826937c5a9
6
+ metadata.gz: b2192d675dd23a3a2c69fc4de998070db17ac05004842855b1fbd1d5b5b148d8063db93db3d84e4e0bbcf42f5d33a173180f641e3309241f7d83e04615c5ba9a
7
+ data.tar.gz: cd7cfbe811f2a809ef428452708b18d1842ef2898d4b78c55e6373773b50546b215ec2dc43591ed4252d20595973ad060581dba72cc1400145068f55294655c4
data/CHANGELOG.md CHANGED
@@ -1,4 +1,25 @@
1
- ## [Unreleased]
1
+ ## [0.3.0] - 2026-01-03
2
+
3
+ - Pager support for long output (respects `GIT_PAGER`, `core.pager`, `PAGER`)
4
+ - `--no-pager` option for commands with long output
5
+ - Colored output (respects `NO_COLOR`, `color.ui`, `color.pkgs`)
6
+ - `GIT_DIR` and `GIT_PKGS_DB` environment variable support
7
+ - `git pkgs stats` now supports `--since` and `--until` date filters
8
+ - Consistent error handling across all commands (JSON errors when `--format=json`)
9
+ - `git pkgs update` now uses a transaction for atomicity and better performance
10
+ - Renamed `git pkgs outdated` to `git pkgs stale` (outdated remains as alias)
11
+ - `git pkgs log` command to list commits with dependency changes
12
+ - `git pkgs schema` command to output database schema in text, SQL, JSON, or markdown
13
+ - `git pkgs praise` alias for `blame`
14
+ - `git pkgs upgrade` command to handle schema upgrades after updating git-pkgs
15
+ - Schema version tracking with automatic detection of outdated databases
16
+
17
+ ## [0.2.0] - 2026-01-02
18
+
19
+ - `git pkgs show` command to display dependency changes in a single commit
20
+ - `git pkgs history` now supports `--author`, `--since`, and `--until` filters
21
+ - `git pkgs stats --by-author` shows who added the most dependencies
22
+ - `git pkgs stats --ecosystem=X` filters statistics by ecosystem
2
23
 
3
24
  ## [0.1.1] - 2026-01-01
4
25
 
data/README.md CHANGED
@@ -4,11 +4,9 @@ A git subcommand for tracking package dependencies across git history. Analyzes
4
4
 
5
5
  ## Why this exists
6
6
 
7
- Your lockfile shows what dependencies you have. It doesn't show how you got here. `git log Gemfile.lock` is useless noise.
7
+ Your lockfile shows what dependencies you have, but it doesn't show how you got here, and `git log Gemfile.lock` is useless noise. git-pkgs indexes your dependency history into a queryable database so you can ask questions like: when did we add this? who added it? what changed between these two releases? has anyone touched this in the last year?
8
8
 
9
- git-pkgs indexes your dependency history into a queryable database. You can ask: when did we add this? who added it? what changed between these two releases? has anyone touched this in the last year?
10
-
11
- It works across ecosystems. Gemfile, package.json, Dockerfile, GitHub Actions workflows - one unified history instead of separate tools per ecosystem.
9
+ It works across many ecosystems (Gemfile, package.json, Dockerfile, GitHub Actions workflows) giving you one unified history instead of separate tools per ecosystem. Everything runs locally and offline with no external services or network calls, and the database lives in your `.git` directory where you can use it in CI to catch dependency changes in pull requests.
12
10
 
13
11
  ## Installation
14
12
 
@@ -113,8 +111,11 @@ Gemfile (rubygems):
113
111
  ### View dependency history
114
112
 
115
113
  ```bash
116
- git pkgs history # all dependency changes
117
- git pkgs history rails # changes for a specific package
114
+ git pkgs history # all dependency changes
115
+ git pkgs history rails # changes for a specific package
116
+ git pkgs history --author=alice # filter by author
117
+ git pkgs history --since=2024-01-01 # changes after date
118
+ git pkgs history --ecosystem=rubygems # filter by ecosystem
118
119
  ```
119
120
 
120
121
  Shows when packages were added, updated, or removed:
@@ -145,6 +146,7 @@ Show who added each current dependency:
145
146
  ```bash
146
147
  git pkgs blame
147
148
  git pkgs blame --ecosystem=rubygems
149
+ git pkgs praise # alias for blame
148
150
  ```
149
151
 
150
152
  Example output:
@@ -166,6 +168,10 @@ Gemfile (rubygems):
166
168
 
167
169
  ```bash
168
170
  git pkgs stats
171
+ git pkgs stats --by-author # who added the most dependencies
172
+ git pkgs stats --ecosystem=npm # filter by ecosystem
173
+ git pkgs stats --since=2024-01-01 # changes after date
174
+ git pkgs stats --until=2024-12-31 # changes before date
169
175
  ```
170
176
 
171
177
  Example output:
@@ -211,7 +217,7 @@ Manifest Files
211
217
  git pkgs why rails
212
218
  ```
213
219
 
214
- Shows the commit that added the dependency with author and message.
220
+ This shows the commit that added the dependency along with the author and message.
215
221
 
216
222
  ### Dependency tree
217
223
 
@@ -220,7 +226,18 @@ git pkgs tree
220
226
  git pkgs tree --ecosystem=rubygems
221
227
  ```
222
228
 
223
- Shows dependencies grouped by type (runtime, development, etc).
229
+ This shows dependencies grouped by type (runtime, development, etc).
230
+
231
+ ### Find stale dependencies
232
+
233
+ ```bash
234
+ git pkgs stale # list deps by how long since last touched
235
+ git pkgs stale --days=365 # only show deps untouched for a year
236
+ git pkgs stale --ecosystem=npm # filter by ecosystem
237
+ git pkgs outdated # alias for stale
238
+ ```
239
+
240
+ Shows dependencies sorted by how long since they were last changed in your repo. Useful for finding packages that may have been forgotten or need review.
224
241
 
225
242
  ### Diff between commits
226
243
 
@@ -229,20 +246,101 @@ git pkgs diff --from=abc123 --to=def456
229
246
  git pkgs diff --from=HEAD~10
230
247
  ```
231
248
 
232
- Shows added, removed, and modified packages with version info.
249
+ This shows added, removed, and modified packages with version info.
250
+
251
+ ### Show changes in a commit
252
+
253
+ ```bash
254
+ git pkgs show # show dependency changes in HEAD
255
+ git pkgs show abc123 # specific commit
256
+ git pkgs show HEAD~5 # relative ref
257
+ ```
258
+
259
+ Like `git show` but for dependencies. Shows what was added, modified, or removed in a single commit.
260
+
261
+ ### List commits with dependency changes
262
+
263
+ ```bash
264
+ git pkgs log # recent commits with dependency changes
265
+ git pkgs log --author=alice # filter by author
266
+ git pkgs log -n 50 # show more commits
267
+ ```
268
+
269
+ Like `git log` but only shows commits that changed dependencies, with the changes listed under each commit.
233
270
 
234
271
  ### Keep database updated
235
272
 
273
+ After the initial analysis, you can incrementally update the database with new commits:
274
+
236
275
  ```bash
237
276
  git pkgs update
238
277
  ```
239
278
 
240
- Or install git hooks to update automatically after commits and merges:
279
+ You can also install git hooks to update automatically after commits and merges:
241
280
 
242
281
  ```bash
243
282
  git pkgs hooks --install
244
283
  ```
245
284
 
285
+ ### Upgrading
286
+
287
+ After updating git-pkgs, you may need to rebuild the database if the schema has changed:
288
+
289
+ ```bash
290
+ git pkgs upgrade
291
+ ```
292
+
293
+ This is detected automatically and you'll see a message if an upgrade is needed.
294
+
295
+ ### Show database schema
296
+
297
+ ```bash
298
+ git pkgs schema # human-readable table format
299
+ git pkgs schema --format=sql # CREATE TABLE statements
300
+ git pkgs schema --format=json # JSON structure
301
+ git pkgs schema --format=markdown # markdown tables
302
+ ```
303
+
304
+ Useful for understanding the [database structure](docs/schema.md) or generating documentation.
305
+
306
+ ### CI usage
307
+
308
+ You can run git-pkgs in CI to show dependency changes in pull requests:
309
+
310
+ ```yaml
311
+ # .github/workflows/deps.yml
312
+ name: Dependencies
313
+
314
+ on: pull_request
315
+
316
+ jobs:
317
+ diff:
318
+ runs-on: ubuntu-latest
319
+ steps:
320
+ - uses: actions/checkout@v4
321
+ with:
322
+ fetch-depth: 0
323
+ - uses: ruby/setup-ruby@v1
324
+ with:
325
+ ruby-version: '3.3'
326
+ - run: gem install git-pkgs
327
+ - run: git pkgs init
328
+ - run: git pkgs diff --from=origin/${{ github.base_ref }} --to=HEAD
329
+ ```
330
+
331
+ ## Configuration
332
+
333
+ git-pkgs respects [standard git configuration](https://git-scm.com/docs/git-config).
334
+
335
+ **Colors** are enabled when writing to a terminal. Disable with `NO_COLOR=1`, `git config color.ui never`, or `git config color.pkgs never` for git-pkgs only.
336
+
337
+ **Pager** follows git's precedence: `GIT_PAGER` env, `core.pager` config, `PAGER` env, then `less -FRSX`. Use `--no-pager` flag or `git config core.pager cat` to disable.
338
+
339
+ **Environment variables:**
340
+
341
+ - `GIT_DIR` - git directory location (standard git variable)
342
+ - `GIT_PKGS_DB` - database path (default: `.git/pkgs.sqlite3`)
343
+
246
344
  ## Performance
247
345
 
248
346
  Benchmarked on a MacBook Pro analyzing [octobox](https://github.com/octobox/octobox) (5191 commits, 8 years of history): init takes about 18 seconds at roughly 300 commits/sec, producing an 8.3 MB database. About half the commits (2531) had dependency changes.
@@ -271,6 +369,21 @@ The database schema stores:
271
369
 
272
370
  See [docs/schema.md](docs/schema.md) for full schema documentation.
273
371
 
372
+ Since the database is just SQLite, you can query it directly for ad-hoc analysis:
373
+
374
+ ```bash
375
+ sqlite3 .git/pkgs.sqlite3 "
376
+ -- who added the most dependencies?
377
+ SELECT c.author_name, COUNT(*) as deps_added
378
+ FROM dependency_changes dc
379
+ JOIN commits c ON dc.commit_id = c.id
380
+ WHERE dc.change_type = 'added'
381
+ GROUP BY c.author_name
382
+ ORDER BY deps_added DESC
383
+ LIMIT 10;
384
+ "
385
+ ```
386
+
274
387
  ## Development
275
388
 
276
389
  ```bash
data/lib/git/pkgs/cli.rb CHANGED
@@ -5,7 +5,8 @@ require "optparse"
5
5
  module Git
6
6
  module Pkgs
7
7
  class CLI
8
- COMMANDS = %w[init update hooks info list tree history search why blame outdated stats diff branch].freeze
8
+ COMMANDS = %w[init update hooks info list tree history search why blame stale stats diff branch show log upgrade schema].freeze
9
+ ALIASES = { "praise" => "blame", "outdated" => "stale" }.freeze
9
10
 
10
11
  def self.run(args)
11
12
  new(args).run
@@ -24,7 +25,7 @@ module Git
24
25
  print_help
25
26
  when "-v", "--version", "version"
26
27
  puts "git-pkgs #{Git::Pkgs::VERSION}"
27
- when *COMMANDS
28
+ when *COMMANDS, *ALIASES.keys
28
29
  run_command(command)
29
30
  else
30
31
  $stderr.puts "Unknown command: #{command}"
@@ -34,6 +35,7 @@ module Git
34
35
  end
35
36
 
36
37
  def run_command(command)
38
+ command = ALIASES.fetch(command, command)
37
39
  command_class = Commands.const_get(command.capitalize.gsub(/_([a-z])/) { $1.upcase })
38
40
  command_class.new(@args).run
39
41
  rescue NameError
@@ -57,9 +59,13 @@ module Git
57
59
  search Find a dependency across all history
58
60
  why Explain why a dependency exists
59
61
  blame Show who added each dependency
60
- outdated Show dependencies that haven't been updated
62
+ stale Show dependencies that haven't been updated
61
63
  stats Show dependency statistics
62
64
  diff Show dependency changes between commits
65
+ show Show dependency changes in a commit
66
+ log List commits with dependency changes
67
+ upgrade Upgrade database after git-pkgs update
68
+ schema Show database schema
63
69
 
64
70
  Options:
65
71
  -h, --help Show this help message
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Pkgs
5
+ module Color
6
+ CODES = {
7
+ red: 31,
8
+ green: 32,
9
+ yellow: 33,
10
+ blue: 34,
11
+ magenta: 35,
12
+ cyan: 36,
13
+ bold: 1,
14
+ dim: 2
15
+ }.freeze
16
+
17
+ def self.enabled?
18
+ return @enabled if defined?(@enabled)
19
+
20
+ @enabled = determine_color_support
21
+ end
22
+
23
+ def self.enabled=(value)
24
+ @enabled = value
25
+ end
26
+
27
+ def self.reset!
28
+ remove_instance_variable(:@enabled) if defined?(@enabled)
29
+ end
30
+
31
+ def self.determine_color_support
32
+ # NO_COLOR takes precedence (https://no-color.org/)
33
+ return false if ENV["NO_COLOR"] && !ENV["NO_COLOR"].empty?
34
+ return false if ENV["TERM"] == "dumb"
35
+
36
+ # Check git config: color.pkgs takes precedence over color.ui
37
+ git_color = git_color_config
38
+ case git_color
39
+ when "always" then return true
40
+ when "never" then return false
41
+ # "auto" falls through to TTY check
42
+ end
43
+
44
+ $stdout.respond_to?(:tty?) && $stdout.tty?
45
+ end
46
+
47
+ def self.git_color_config
48
+ # color.pkgs overrides color.ui for git-pkgs specific control
49
+ pkgs_color = `git config --get color.pkgs 2>/dev/null`.chomp
50
+ return normalize_color_value(pkgs_color) unless pkgs_color.empty?
51
+
52
+ ui_color = `git config --get color.ui 2>/dev/null`.chomp
53
+ return normalize_color_value(ui_color) unless ui_color.empty?
54
+
55
+ "auto"
56
+ end
57
+
58
+ def self.normalize_color_value(value)
59
+ case value.downcase
60
+ when "true", "always" then "always"
61
+ when "false", "never" then "never"
62
+ else "auto"
63
+ end
64
+ end
65
+
66
+ def self.colorize(text, *codes)
67
+ return text unless enabled?
68
+
69
+ code_str = codes.map { |c| CODES[c] || c }.join(";")
70
+ "\e[#{code_str}m#{text}\e[0m"
71
+ end
72
+
73
+ def self.red(text) = colorize(text, :red)
74
+ def self.green(text) = colorize(text, :green)
75
+ def self.yellow(text) = colorize(text, :yellow)
76
+ def self.blue(text) = colorize(text, :blue)
77
+ def self.cyan(text) = colorize(text, :cyan)
78
+ def self.bold(text) = colorize(text, :bold)
79
+ def self.dim(text) = colorize(text, :dim)
80
+ end
81
+ end
82
+ end
@@ -4,6 +4,8 @@ module Git
4
4
  module Pkgs
5
5
  module Commands
6
6
  class Blame
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
  branch_name = @options[:branch] || repo.default_branch
24
22
  branch = Models::Branch.find_by(name: branch_name)
25
23
 
26
- unless branch&.last_analyzed_sha
27
- $stderr.puts "No analysis found for branch '#{branch_name}'"
28
- exit 1
29
- end
24
+ error "No analysis found for branch '#{branch_name}'" unless branch&.last_analyzed_sha
30
25
 
31
26
  current_commit = Models::Commit.find_by(sha: branch.last_analyzed_sha)
32
27
  snapshots = current_commit&.dependency_snapshots&.includes(:manifest) || []
@@ -36,7 +31,7 @@ module Git
36
31
  end
37
32
 
38
33
  if snapshots.empty?
39
- puts "No dependencies found"
34
+ empty_result "No dependencies found"
40
35
  return
41
36
  end
42
37
 
@@ -74,20 +69,24 @@ module Git
74
69
  end
75
70
  puts JSON.pretty_generate(json_data)
76
71
  else
77
- grouped = blame_data.group_by { |d| [d[:manifest], d[:ecosystem]] }
72
+ paginate { output_text(blame_data) }
73
+ end
74
+ end
75
+
76
+ def output_text(blame_data)
77
+ grouped = blame_data.group_by { |d| [d[:manifest], d[:ecosystem]] }
78
78
 
79
- grouped.each do |(manifest, ecosystem), deps|
80
- puts "#{manifest} (#{ecosystem}):"
79
+ grouped.each do |(manifest, ecosystem), deps|
80
+ puts "#{manifest} (#{ecosystem}):"
81
81
 
82
- max_name_len = deps.map { |d| d[:name].length }.max
83
- max_author_len = deps.map { |d| d[:author].length }.max
82
+ max_name_len = deps.map { |d| d[:name].length }.max
83
+ max_author_len = deps.map { |d| d[:author].length }.max
84
84
 
85
- deps.sort_by { |d| d[:name] }.each do |dep|
86
- date = dep[:date].strftime("%Y-%m-%d")
87
- puts " #{dep[:name].ljust(max_name_len)} #{dep[:author].ljust(max_author_len)} #{date} #{dep[:sha]}"
88
- end
89
- puts
85
+ deps.sort_by { |d| d[:name] }.each do |dep|
86
+ date = dep[:date].strftime("%Y-%m-%d")
87
+ puts " #{dep[:name].ljust(max_name_len)} #{dep[:author].ljust(max_author_len)} #{date} #{dep[:sha]}"
90
88
  end
89
+ puts
91
90
  end
92
91
  end
93
92
 
@@ -127,6 +126,10 @@ module Git
127
126
  options[:format] = v
128
127
  end
129
128
 
129
+ opts.on("--no-pager", "Do not pipe output into a pager") do
130
+ options[:no_pager] = true
131
+ end
132
+
130
133
  opts.on("-h", "--help", "Show this help") do
131
134
  puts opts
132
135
  exit
@@ -4,6 +4,8 @@ module Git
4
4
  module Pkgs
5
5
  module Commands
6
6
  class Branch
7
+ include Output
8
+
7
9
  BATCH_SIZE = 100
8
10
  SNAPSHOT_INTERVAL = 20
9
11
 
@@ -25,32 +27,20 @@ module Git
25
27
  when nil, "-h", "--help"
26
28
  print_help
27
29
  else
28
- $stderr.puts "Unknown subcommand: #{subcommand}"
29
- $stderr.puts "Run 'git pkgs branch --help' for usage"
30
- exit 1
30
+ error "Unknown subcommand: #{subcommand}. Run 'git pkgs branch --help' for usage"
31
31
  end
32
32
  end
33
33
 
34
34
  def add_branch
35
35
  branch_name = @args.shift
36
- unless branch_name
37
- $stderr.puts "Usage: git pkgs branch add <name>"
38
- exit 1
39
- end
36
+ error "Usage: git pkgs branch add <name>" unless branch_name
40
37
 
41
38
  repo = Repository.new
42
-
43
- unless Database.exists?(repo.git_dir)
44
- $stderr.puts "Database not initialized. Run 'git pkgs init' first."
45
- exit 1
46
- end
39
+ require_database(repo)
47
40
 
48
41
  Database.connect(repo.git_dir)
49
42
 
50
- unless repo.branch_exists?(branch_name)
51
- $stderr.puts "Branch '#{branch_name}' not found"
52
- exit 1
53
- end
43
+ error "Branch '#{branch_name}' not found" unless repo.branch_exists?(branch_name)
54
44
 
55
45
  existing = Models::Branch.find_by(name: branch_name)
56
46
  if existing
@@ -84,18 +74,14 @@ module Git
84
74
 
85
75
  def list_branches
86
76
  repo = Repository.new
87
-
88
- unless Database.exists?(repo.git_dir)
89
- $stderr.puts "Database not initialized. Run 'git pkgs init' first."
90
- exit 1
91
- end
77
+ require_database(repo)
92
78
 
93
79
  Database.connect(repo.git_dir)
94
80
 
95
81
  branches = Models::Branch.all
96
82
 
97
83
  if branches.empty?
98
- puts "No branches tracked"
84
+ empty_result "No branches tracked"
99
85
  return
100
86
  end
101
87
 
@@ -110,25 +96,15 @@ module Git
110
96
 
111
97
  def remove_branch
112
98
  branch_name = @args.shift
113
- unless branch_name
114
- $stderr.puts "Usage: git pkgs branch remove <name>"
115
- exit 1
116
- end
99
+ error "Usage: git pkgs branch remove <name>" unless branch_name
117
100
 
118
101
  repo = Repository.new
119
-
120
- unless Database.exists?(repo.git_dir)
121
- $stderr.puts "Database not initialized. Run 'git pkgs init' first."
122
- exit 1
123
- end
102
+ require_database(repo)
124
103
 
125
104
  Database.connect(repo.git_dir)
126
105
 
127
106
  branch = Models::Branch.find_by(name: branch_name)
128
- unless branch
129
- $stderr.puts "Branch '#{branch_name}' not tracked"
130
- exit 1
131
- end
107
+ error "Branch '#{branch_name}' not tracked" unless branch
132
108
 
133
109
  # Only delete branch_commits, keep shared commits
134
110
  count = branch.branch_commits.count
@@ -4,6 +4,8 @@ module Git
4
4
  module Pkgs
5
5
  module Commands
6
6
  class Diff
7
+ include Output
8
+
7
9
  def initialize(args)
8
10
  @args = args
9
11
  @options = parse_options
@@ -11,48 +13,27 @@ 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
  from_ref = @options[:from]
23
21
  to_ref = @options[:to] || "HEAD"
24
22
 
25
- unless from_ref
26
- $stderr.puts "Usage: git pkgs diff --from=REF [--to=REF]"
27
- exit 1
28
- end
23
+ error "Usage: git pkgs diff --from=REF [--to=REF]" unless from_ref
29
24
 
30
25
  # Resolve git refs (like HEAD~10) to SHAs
31
26
  from_sha = repo.rev_parse(from_ref)
32
27
  to_sha = repo.rev_parse(to_ref)
33
28
 
34
- unless from_sha
35
- $stderr.puts "Could not resolve '#{from_ref}'"
36
- exit 1
37
- end
38
-
39
- unless to_sha
40
- $stderr.puts "Could not resolve '#{to_ref}'"
41
- exit 1
42
- end
29
+ error "Could not resolve '#{from_ref}'" unless from_sha
30
+ error "Could not resolve '#{to_ref}'" unless to_sha
43
31
 
44
32
  from_commit = find_or_create_commit(repo, from_sha)
45
33
  to_commit = find_or_create_commit(repo, to_sha)
46
34
 
47
- unless from_commit
48
- $stderr.puts "Commit '#{from_sha[0..7]}' not found"
49
- exit 1
50
- end
51
-
52
- unless to_commit
53
- $stderr.puts "Commit '#{to_sha[0..7]}' not found"
54
- exit 1
55
- end
35
+ error "Commit '#{from_sha[0..7]}' not found" unless from_commit
36
+ error "Commit '#{to_sha[0..7]}' not found" unless to_commit
56
37
 
57
38
  # Get all changes between the two commits
58
39
  changes = Models::DependencyChange
@@ -67,10 +48,14 @@ module Git
67
48
  end
68
49
 
69
50
  if changes.empty?
70
- puts "No dependency changes between #{from_commit.short_sha} and #{to_commit.short_sha}"
51
+ empty_result "No dependency changes between #{from_commit.short_sha} and #{to_commit.short_sha}"
71
52
  return
72
53
  end
73
54
 
55
+ paginate { output_text(from_commit, to_commit, changes) }
56
+ end
57
+
58
+ def output_text(from_commit, to_commit, changes)
74
59
  puts "Dependency changes from #{from_commit.short_sha} to #{to_commit.short_sha}:"
75
60
  puts
76
61
 
@@ -79,35 +64,38 @@ module Git
79
64
  removed = changes.select { |c| c.change_type == "removed" }
80
65
 
81
66
  if added.any?
82
- puts "Added:"
67
+ puts Color.green("Added:")
83
68
  added.group_by(&:name).each do |name, pkg_changes|
84
69
  latest = pkg_changes.last
85
- puts " + #{name} #{latest.requirement} (#{latest.manifest.path})"
70
+ puts Color.green(" + #{name} #{latest.requirement} (#{latest.manifest.path})")
86
71
  end
87
72
  puts
88
73
  end
89
74
 
90
75
  if modified.any?
91
- puts "Modified:"
76
+ puts Color.yellow("Modified:")
92
77
  modified.group_by(&:name).each do |name, pkg_changes|
93
78
  first = pkg_changes.first
94
79
  latest = pkg_changes.last
95
- puts " ~ #{name} #{first.previous_requirement} -> #{latest.requirement}"
80
+ puts Color.yellow(" ~ #{name} #{first.previous_requirement} -> #{latest.requirement}")
96
81
  end
97
82
  puts
98
83
  end
99
84
 
100
85
  if removed.any?
101
- puts "Removed:"
86
+ puts Color.red("Removed:")
102
87
  removed.group_by(&:name).each do |name, pkg_changes|
103
88
  latest = pkg_changes.last
104
- puts " - #{name} (was #{latest.requirement})"
89
+ puts Color.red(" - #{name} (was #{latest.requirement})")
105
90
  end
106
91
  puts
107
92
  end
108
93
 
109
94
  # Summary
110
- puts "Summary: +#{added.map(&:name).uniq.count} -#{removed.map(&:name).uniq.count} ~#{modified.map(&:name).uniq.count}"
95
+ added_count = Color.green("+#{added.map(&:name).uniq.count}")
96
+ removed_count = Color.red("-#{removed.map(&:name).uniq.count}")
97
+ modified_count = Color.yellow("~#{modified.map(&:name).uniq.count}")
98
+ puts "Summary: #{added_count} #{removed_count} #{modified_count}"
111
99
  end
112
100
 
113
101
  def find_or_create_commit(repo, sha)
@@ -149,6 +137,10 @@ module Git
149
137
  options[:ecosystem] = v
150
138
  end
151
139
 
140
+ opts.on("--no-pager", "Do not pipe output into a pager") do
141
+ options[:no_pager] = true
142
+ end
143
+
152
144
  opts.on("-h", "--help", "Show this help") do
153
145
  puts opts
154
146
  exit