git-pkgs 0.4.0 → 0.5.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: 794ba02ad93776f2d013177ae9b6cc8574788b84d9a51ea05e4e1db6a18399c5
4
- data.tar.gz: 8a1d4e1fc30f73add57a69a9f4f29fa3ae3883fe73350b8d6121217dd24a6592
3
+ metadata.gz: 5ff583fee83b937717da50de39743a8cfa5a75ce987df064edeaf89ea713f90f
4
+ data.tar.gz: eea893e5bacae2af0f2fdf8a8c1736f1d080a4b4bb111a2b94891f481f65c805
5
5
  SHA512:
6
- metadata.gz: d5204cc1a63d3838322e465d70ac1a16e8cf215e193e15baf8425c1f88ec40affd9ceb0647a821cfa9a2384dc684a3c912baf32ac960352a8591c6105777a210
7
- data.tar.gz: b6b6b47009f511022f1e824671a3ff47a654a8d4bb128ec933371c13e65106952b8c5322db2914c5b3d4d924206f09d0f6264e6457fccfc64b2518e1f86cfdcb
6
+ metadata.gz: 95795186a511d176679a98a4f6e8e246bb1548456f041c3e5379d9705d3382deac69df2d81f0d187c294ba9af164f8c8372724b1f228326784a32a2952c98b98
7
+ data.tar.gz: 8fffd3620a9bfe95b4aa46e51c80dad6db2984829f653f6f28eccfc7d5a7c19aa480a20f7c7be30a929d1fe23cdfe6e368a2a529c9b2204319102ff9d1bbd9cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-01-04
4
+
5
+ - `git pkgs init` now installs git hooks by default (use `--no-hooks` to skip)
6
+ - Parallel prefetching of git diffs for ~2x speedup on large repositories (1500+ commits)
7
+ - Performance tuning via environment variables: `GIT_PKGS_BATCH_SIZE`, `GIT_PKGS_SNAPSHOT_INTERVAL`, `GIT_PKGS_THREADS`
8
+ - `git pkgs completions` command for bash/zsh tab completion
9
+ - Fix N+1 queries in `blame`, `stale`, `stats`, and `log` commands
10
+ - Configuration via git config: `pkgs.ecosystems`, `pkgs.ignoredDirs`, `pkgs.ignoredFiles`
11
+ - `git pkgs info --ecosystems` to show available ecosystems and their status
12
+ - `-q, --quiet` flag to suppress informational messages
13
+ - `git pkgs diff` now supports `commit..commit` range syntax
14
+ - `--git-dir` and `--work-tree` global options (also respects `GIT_WORK_TREE` env var)
15
+ - Grouped commands by category in help output
16
+ - Fix crash when parsing manifests that return no dependencies
17
+
3
18
  ## [0.4.0] - 2026-01-04
4
19
 
5
20
  - `git pkgs where` command to find where a package is declared in manifest files
data/README.md CHANGED
@@ -43,7 +43,7 @@ Options:
43
43
  - `--branch=NAME` - analyze a specific branch (default: default branch)
44
44
  - `--since=SHA` - start analysis from a specific commit
45
45
  - `--force` - rebuild the database from scratch
46
- - `--hooks` - install git hooks for auto-updating
46
+ - `--no-hooks` - skip installing git hooks (hooks are installed by default)
47
47
 
48
48
  Example output:
49
49
  ```
@@ -52,7 +52,7 @@ Processing commit 5191/5191...
52
52
  Done!
53
53
  Analyzed 5191 commits
54
54
  Found 2531 commits with dependency changes
55
- Stored 28239 snapshots (every 20 changes)
55
+ Stored 28239 snapshots (every 50 changes)
56
56
  Blob cache: 3141 unique blobs, 2349 had cache hits
57
57
  ```
58
58
 
@@ -86,7 +86,7 @@ Snapshot Coverage
86
86
  ----------------------------------------
87
87
  Commits with dependency changes: 2531
88
88
  Commits with snapshots: 127
89
- Coverage: 5.0% (1 snapshot per ~20 changes)
89
+ Coverage: 2.0% (1 snapshot per ~50 changes)
90
90
  ```
91
91
 
92
92
  ### List dependencies
@@ -287,16 +287,18 @@ Like `git log` but only shows commits that changed dependencies, with the change
287
287
 
288
288
  ### Keep database updated
289
289
 
290
- After the initial analysis, you can incrementally update the database with new commits:
290
+ After the initial analysis, the database updates automatically via git hooks installed during init. You can also update manually:
291
291
 
292
292
  ```bash
293
293
  git pkgs update
294
294
  ```
295
295
 
296
- You can also install git hooks to update automatically after commits and merges:
296
+ To manage hooks separately:
297
297
 
298
298
  ```bash
299
- git pkgs hooks --install
299
+ git pkgs hooks # show hook status
300
+ git pkgs hooks --install # install hooks
301
+ git pkgs hooks --uninstall # remove hooks
300
302
  ```
301
303
 
302
304
  ### Upgrading
@@ -369,6 +371,21 @@ diff --git a/Gemfile.lock b/Gemfile.lock
369
371
 
370
372
  Use `git diff --no-textconv` to see the raw lockfile diff. To remove: `git pkgs diff-driver --uninstall`
371
373
 
374
+ ### Shell completions
375
+
376
+ Enable tab completion for commands:
377
+
378
+ ```bash
379
+ # Bash: add to ~/.bashrc
380
+ eval "$(git pkgs completions bash)"
381
+
382
+ # Zsh: add to ~/.zshrc
383
+ eval "$(git pkgs completions zsh)"
384
+
385
+ # Or auto-install to standard completion directories
386
+ git pkgs completions install
387
+ ```
388
+
372
389
  ## Configuration
373
390
 
374
391
  git-pkgs respects [standard git configuration](https://git-scm.com/docs/git-config).
@@ -377,6 +394,21 @@ git-pkgs respects [standard git configuration](https://git-scm.com/docs/git-conf
377
394
 
378
395
  **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.
379
396
 
397
+ **Ecosystem filtering** lets you limit which package ecosystems are tracked:
398
+
399
+ ```bash
400
+ git config --add pkgs.ecosystems rubygems
401
+ git config --add pkgs.ecosystems npm
402
+ git pkgs info --ecosystems # show enabled/disabled ecosystems
403
+ ```
404
+
405
+ **Ignored paths** let you skip directories or files from analysis:
406
+
407
+ ```bash
408
+ git config --add pkgs.ignoredDirs third_party
409
+ git config --add pkgs.ignoredFiles test/fixtures/package.json
410
+ ```
411
+
380
412
  **Environment variables:**
381
413
 
382
414
  - `GIT_DIR` - git directory location (standard git variable)
@@ -384,55 +416,32 @@ git-pkgs respects [standard git configuration](https://git-scm.com/docs/git-conf
384
416
 
385
417
  ## Performance
386
418
 
387
- 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.
419
+ Benchmarked on an M1 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.
388
420
 
389
421
  Optimizations:
390
- - Bulk inserts with transaction batching (100 commits per transaction)
422
+ - Bulk inserts with transaction batching (500 commits per transaction)
391
423
  - Blob SHA caching (75% cache hit rate for repeated manifest content)
392
424
  - Deferred index creation during bulk load
393
- - Sparse snapshots (every 20 dependency-changing commits) for storage efficiency
425
+ - Sparse snapshots (every 50 dependency-changing commits) for storage efficiency
394
426
  - SQLite WAL mode for write performance
395
427
 
396
428
  ## Supported ecosystems
397
429
 
398
430
  git-pkgs uses [ecosystems-bibliothecary](https://github.com/ecosyste-ms/bibliothecary) for parsing, supporting:
399
431
 
400
- Actions, Anaconda, BentoML, Bower, Cargo, CocoaPods, Cog, CPAN, CRAN, CycloneDX, Docker, Dub, DVC, Elm, Go, Haxelib, Homebrew, Julia, Maven, Meteor, MLflow, npm, NuGet, Ollama, Packagist, Pub, PyPI, RubyGems, Shards, SPDX, Vcpkg
401
-
402
- ## How it works
432
+ Actions, BentoML, Bower, Cargo, CocoaPods, Cog, Conda, CPAN, CRAN, Docker, Dub, DVC, Elm, Go, Haxelib, Homebrew, Julia, Maven, Meteor, MLflow, npm, NuGet, Ollama, Packagist, Pub, PyPI, RubyGems, Shards, Vcpkg
403
433
 
404
- git-pkgs walks your git history, extracts dependency files at each commit, and diffs them to detect changes. Results are stored in a SQLite database for fast querying.
434
+ Some ecosystems require remote parsing services and are disabled by default: Carthage, Clojars, Hackage, Hex, SwiftPM. Enable with `git config --add pkgs.ecosystems <name>`.
405
435
 
406
- The database schema stores:
407
- - Commits with dependency changes
408
- - Dependency changes (added/modified/removed) with before/after versions
409
- - Periodic snapshots of full dependency state for efficient point-in-time queries
436
+ SBOM formats (CycloneDX, SPDX) are not supported as they duplicate information from the actual lockfiles.
410
437
 
411
- See [docs/schema.md](docs/schema.md) for full schema documentation.
438
+ ## Contributing
412
439
 
413
- Since the database is just SQLite, you can query it directly for ad-hoc analysis:
440
+ Bug reports, feature requests, and pull requests are welcome. If you're unsure about a change, open an issue first to discuss it.
414
441
 
415
- ```bash
416
- sqlite3 .git/pkgs.sqlite3 "
417
- -- who added the most dependencies?
418
- SELECT c.author_name, COUNT(*) as deps_added
419
- FROM dependency_changes dc
420
- JOIN commits c ON dc.commit_id = c.id
421
- WHERE dc.change_type = 'added'
422
- GROUP BY c.author_name
423
- ORDER BY deps_added DESC
424
- LIMIT 10;
425
- "
426
- ```
427
-
428
- ## Development
442
+ Good first contributions: adding tests, improving error messages, or supporting new manifest formats via [ecosystems-bibliothecary](https://github.com/ecosyste-ms/bibliothecary).
429
443
 
430
- ```bash
431
- git clone https://github.com/andrew/git-pkgs
432
- cd git-pkgs
433
- bin/setup
434
- rake test
435
- ```
444
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and architecture docs.
436
445
 
437
446
  ## License
438
447
 
@@ -24,8 +24,6 @@ module Git
24
24
  Podfile Podfile.lock *.podspec *.podspec.json
25
25
  packages.config packages.lock.json Project.json Project.lock.json
26
26
  *.nuspec paket.lock *.csproj project.assets.json
27
- cyclonedx.xml cyclonedx.json *.cdx.xml *.cdx.json
28
- *.spdx *.spdx.json
29
27
  bower.json bentofile.yaml
30
28
  META.json META.yml
31
29
  environment.yml environment.yaml
@@ -56,6 +54,8 @@ module Git
56
54
  def initialize(repository)
57
55
  @repository = repository
58
56
  @blob_cache = {}
57
+ @manifest_path_cache = {}
58
+ Config.configure_bibliothecary
59
59
  end
60
60
 
61
61
  # Quick check if any paths might be manifests (fast regex check)
@@ -63,6 +63,23 @@ module Git
63
63
  paths.any? { |p| p.match?(QUICK_MANIFEST_REGEX) }
64
64
  end
65
65
 
66
+ # Cached version of Bibliothecary.identify_manifests
67
+ def identify_manifests_cached(paths)
68
+ uncached = paths.reject { |p| @manifest_path_cache.key?(p) }
69
+
70
+ if uncached.any?
71
+ # Call Bibliothecary only for uncached paths
72
+ manifests = Bibliothecary.identify_manifests(uncached)
73
+ manifest_set = manifests.to_set
74
+
75
+ uncached.each do |path|
76
+ @manifest_path_cache[path] = manifest_set.include?(path)
77
+ end
78
+ end
79
+
80
+ paths.select { |p| @manifest_path_cache[p] }
81
+ end
82
+
66
83
  # Quick check if a commit touches any manifest files
67
84
  def has_manifest_changes?(rugged_commit)
68
85
  return false if repository.merge_commit?(rugged_commit)
@@ -72,7 +89,7 @@ module Git
72
89
 
73
90
  return false unless might_have_manifests?(all_paths)
74
91
 
75
- Bibliothecary.identify_manifests(all_paths).any?
92
+ identify_manifests_cached(all_paths).any?
76
93
  end
77
94
 
78
95
  def analyze_commit(rugged_commit, previous_snapshot = {})
@@ -87,9 +104,9 @@ module Git
87
104
  all_paths = added_paths + modified_paths + removed_paths
88
105
  return nil unless might_have_manifests?(all_paths)
89
106
 
90
- added_manifests = Bibliothecary.identify_manifests(added_paths)
91
- modified_manifests = Bibliothecary.identify_manifests(modified_paths)
92
- removed_manifests = Bibliothecary.identify_manifests(removed_paths)
107
+ added_manifests = identify_manifests_cached(added_paths)
108
+ modified_manifests = identify_manifests_cached(modified_paths)
109
+ removed_manifests = identify_manifests_cached(removed_paths)
93
110
 
94
111
  return nil if added_manifests.empty? && modified_manifests.empty? && removed_manifests.empty?
95
112
 
@@ -99,7 +116,7 @@ module Git
99
116
  # Process added manifest files
100
117
  added_manifests.each do |manifest_path|
101
118
  result = parse_manifest_at_commit(rugged_commit, manifest_path)
102
- next unless result
119
+ next unless result && result[:dependencies]
103
120
 
104
121
  result[:dependencies].each do |dep|
105
122
  changes << {
@@ -203,7 +220,7 @@ module Git
203
220
  # Process removed manifest files
204
221
  removed_manifests.each do |manifest_path|
205
222
  result = parse_manifest_before_commit(rugged_commit, manifest_path)
206
- next unless result
223
+ next unless result && result[:dependencies]
207
224
 
208
225
  result[:dependencies].each do |dep|
209
226
  changes << {
@@ -229,9 +246,14 @@ module Git
229
246
 
230
247
  # Cache stats for debugging
231
248
  def cache_stats
232
- hits = @blob_cache.values.count { |v| v[:hits] > 0 }
233
- total = @blob_cache.size
234
- { cached_blobs: total, blobs_with_hits: hits }
249
+ blob_hits = @blob_cache.values.count { |v| v[:hits] > 0 }
250
+ blob_total = @blob_cache.size
251
+ manifest_paths = @manifest_path_cache.size
252
+ {
253
+ cached_blobs: blob_total,
254
+ blobs_with_hits: blob_hits,
255
+ cached_paths: manifest_paths
256
+ }
235
257
  end
236
258
 
237
259
  def parse_manifest_at_commit(rugged_commit, manifest_path)
@@ -251,7 +273,7 @@ module Git
251
273
  end
252
274
 
253
275
  def parse_manifest_by_oid(blob_oid, manifest_path)
254
- cache_key = "#{blob_oid}:#{manifest_path}"
276
+ cache_key = [blob_oid, manifest_path]
255
277
 
256
278
  if @blob_cache.key?(cache_key)
257
279
  @blob_cache[cache_key][:hits] += 1
@@ -262,6 +284,7 @@ module Git
262
284
  return nil unless content
263
285
 
264
286
  result = Bibliothecary.analyse_file(manifest_path, content).first
287
+ result = nil if result && Config.filter_ecosystem?(result[:platform])
265
288
  @blob_cache[cache_key] = { result: result, hits: 0 }
266
289
  result
267
290
  end
data/lib/git/pkgs/cli.rb CHANGED
@@ -5,7 +5,40 @@ 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 where why blame stale stats diff branch show log upgrade schema diff-driver].freeze
8
+ COMMAND_GROUPS = {
9
+ "Setup" => {
10
+ "init" => "Initialize the package database for this repository",
11
+ "update" => "Update the database with new commits",
12
+ "hooks" => "Manage git hooks for auto-updating",
13
+ "upgrade" => "Upgrade database after git-pkgs update",
14
+ "info" => "Show database size and row counts",
15
+ "branch" => "Manage tracked branches",
16
+ "schema" => "Show database schema",
17
+ "diff-driver" => "Install git textconv driver for lockfile diffs",
18
+ "completions" => "Generate shell completions"
19
+ },
20
+ "Query" => {
21
+ "list" => "List dependencies at a commit",
22
+ "tree" => "Show dependency tree grouped by type",
23
+ "search" => "Find a dependency across all history",
24
+ "where" => "Show where a package appears in manifest files",
25
+ "why" => "Explain why a dependency exists"
26
+ },
27
+ "History" => {
28
+ "history" => "Show the history of a package",
29
+ "blame" => "Show who added each dependency",
30
+ "log" => "List commits with dependency changes",
31
+ "show" => "Show dependency changes in a commit",
32
+ "diff" => "Show dependency changes between commits"
33
+ },
34
+ "Analysis" => {
35
+ "stats" => "Show dependency statistics",
36
+ "stale" => "Show dependencies that haven't been updated"
37
+ }
38
+ }.freeze
39
+
40
+ COMMANDS = COMMAND_GROUPS.values.flat_map(&:keys).freeze
41
+ COMMAND_DESCRIPTIONS = COMMAND_GROUPS.values.reduce({}, :merge).freeze
9
42
  ALIASES = { "praise" => "blame", "outdated" => "stale" }.freeze
10
43
 
11
44
  def self.run(args)
@@ -18,6 +51,9 @@ module Git
18
51
  end
19
52
 
20
53
  def run
54
+ Git::Pkgs.configure_from_env
55
+ parse_global_options
56
+
21
57
  command = @args.shift
22
58
 
23
59
  case command
@@ -34,48 +70,66 @@ module Git
34
70
  end
35
71
  end
36
72
 
73
+ def parse_global_options
74
+ while @args.first&.start_with?("-")
75
+ arg = @args.first
76
+ case arg
77
+ when "-q", "--quiet"
78
+ Git::Pkgs.quiet = true
79
+ @args.shift
80
+ when /^--git-dir=(.+)$/
81
+ Git::Pkgs.git_dir = $1
82
+ @args.shift
83
+ when "--git-dir"
84
+ @args.shift
85
+ Git::Pkgs.git_dir = @args.shift
86
+ when /^--work-tree=(.+)$/
87
+ Git::Pkgs.work_tree = $1
88
+ @args.shift
89
+ when "--work-tree"
90
+ @args.shift
91
+ Git::Pkgs.work_tree = @args.shift
92
+ else
93
+ break
94
+ end
95
+ end
96
+ end
97
+
37
98
  def run_command(command)
38
99
  command = ALIASES.fetch(command, command)
39
100
  # Convert kebab-case or snake_case to PascalCase
40
101
  class_name = command.split(/[-_]/).map(&:capitalize).join
41
102
  command_class = Commands.const_get(class_name)
42
103
  command_class.new(@args).run
43
- rescue NameError
104
+ rescue NameError => e
105
+ # Only catch NameError for missing command class, not NoMethodError
106
+ raise unless e.is_a?(NameError) && !e.is_a?(NoMethodError)
44
107
  $stderr.puts "Command '#{command}' not yet implemented"
45
108
  exit 1
46
109
  end
47
110
 
48
111
  def print_help
49
- puts <<~HELP
50
- Usage: git pkgs <command> [options]
112
+ puts "Usage: git pkgs <command> [options]"
113
+ puts
51
114
 
52
- Commands:
53
- init Initialize the package database for this repository
54
- update Update the database with new commits
55
- hooks Manage git hooks for auto-updating
56
- info Show database size and row counts
57
- branch Manage tracked branches
58
- list List dependencies at a commit
59
- tree Show dependency tree grouped by type
60
- history Show the history of a package
61
- search Find a dependency across all history
62
- where Show where a package appears in manifest files
63
- why Explain why a dependency exists
64
- blame Show who added each dependency
65
- stale Show dependencies that haven't been updated
66
- stats Show dependency statistics
67
- diff Show dependency changes between commits
68
- show Show dependency changes in a commit
69
- log List commits with dependency changes
70
- upgrade Upgrade database after git-pkgs update
71
- schema Show database schema
115
+ max_cmd_len = COMMANDS.map(&:length).max
72
116
 
73
- Options:
74
- -h, --help Show this help message
75
- -v, --version Show version
117
+ COMMAND_GROUPS.each do |group, commands|
118
+ puts "#{group}:"
119
+ commands.each do |cmd, desc|
120
+ puts " #{cmd.ljust(max_cmd_len)} #{desc}"
121
+ end
122
+ puts
123
+ end
76
124
 
77
- Run 'git pkgs <command> --help' for command-specific options.
78
- HELP
125
+ puts "Options:"
126
+ puts " -h, --help Show this help message"
127
+ puts " -v, --version Show version"
128
+ puts " -q, --quiet Suppress informational messages"
129
+ puts " --git-dir=<path> Path to the git directory"
130
+ puts " --work-tree=<path> Path to the working tree"
131
+ puts
132
+ puts "Run 'git pkgs <command> -h' for command-specific options."
79
133
  end
80
134
  end
81
135
  end
@@ -21,7 +21,7 @@ module Git
21
21
  branch_name = @options[:branch] || repo.default_branch
22
22
  branch = Models::Branch.find_by(name: branch_name)
23
23
 
24
- error "No analysis found for branch '#{branch_name}'" unless branch&.last_analyzed_sha
24
+ error "No analysis found for branch '#{branch_name}'. Run 'git pkgs init' first." unless branch&.last_analyzed_sha
25
25
 
26
26
  current_commit = Models::Commit.find_by(sha: branch.last_analyzed_sha)
27
27
  snapshots = current_commit&.dependency_snapshots&.includes(:manifest) || []
@@ -35,16 +35,34 @@ module Git
35
35
  return
36
36
  end
37
37
 
38
+ # Batch fetch all "added" changes for current dependencies
39
+ snapshot_keys = snapshots.map { |s| [s.name, s.manifest_id] }.to_set
40
+ manifest_ids = snapshots.map(&:manifest_id).uniq
41
+ names = snapshots.map(&:name).uniq
42
+
43
+ all_added_changes = Models::DependencyChange
44
+ .includes(:commit)
45
+ .added
46
+ .where(manifest_id: manifest_ids, name: names)
47
+ .to_a
48
+
49
+ # Group by (name, manifest_id) and find earliest by committed_at
50
+ added_by_key = {}
51
+ all_added_changes.each do |change|
52
+ key = [change.name, change.manifest_id]
53
+ next unless snapshot_keys.include?(key)
54
+
55
+ existing = added_by_key[key]
56
+ if existing.nil? || change.commit.committed_at < existing.commit.committed_at
57
+ added_by_key[key] = change
58
+ end
59
+ end
60
+
38
61
  # For each current dependency, find who added it
39
62
  blame_data = []
40
63
 
41
64
  snapshots.each do |snapshot|
42
- added_change = Models::DependencyChange
43
- .includes(:commit)
44
- .where(name: snapshot.name, manifest: snapshot.manifest)
45
- .added
46
- .order("commits.committed_at ASC")
47
- .first
65
+ added_change = added_by_key[[snapshot.name, snapshot.manifest_id]]
48
66
 
49
67
  next unless added_change
50
68
 
@@ -6,8 +6,16 @@ module Git
6
6
  class Branch
7
7
  include Output
8
8
 
9
- BATCH_SIZE = 100
10
- SNAPSHOT_INTERVAL = 20
9
+ DEFAULT_BATCH_SIZE = 500
10
+ DEFAULT_SNAPSHOT_INTERVAL = 50
11
+
12
+ def batch_size
13
+ Git::Pkgs.batch_size || DEFAULT_BATCH_SIZE
14
+ end
15
+
16
+ def snapshot_interval
17
+ Git::Pkgs.snapshot_interval || DEFAULT_SNAPSHOT_INTERVAL
18
+ end
11
19
 
12
20
  def initialize(args)
13
21
  @args = args
@@ -40,12 +48,12 @@ module Git
40
48
 
41
49
  Database.connect(repo.git_dir)
42
50
 
43
- error "Branch '#{branch_name}' not found" unless repo.branch_exists?(branch_name)
51
+ error "Branch '#{branch_name}' not found. Check 'git branch -a' for available branches." unless repo.branch_exists?(branch_name)
44
52
 
45
53
  existing = Models::Branch.find_by(name: branch_name)
46
54
  if existing
47
- puts "Branch '#{branch_name}' already tracked (#{existing.commits.count} commits)"
48
- puts "Use 'git pkgs update' to refresh"
55
+ info "Branch '#{branch_name}' already tracked (#{existing.commits.count} commits)"
56
+ info "Use 'git pkgs update' to refresh"
49
57
  return
50
58
  end
51
59
 
@@ -54,11 +62,15 @@ module Git
54
62
  branch = Models::Branch.create!(name: branch_name)
55
63
  analyzer = Analyzer.new(repo)
56
64
 
57
- puts "Analyzing branch: #{branch_name}"
65
+ info "Analyzing branch: #{branch_name}"
58
66
 
67
+ print "Loading commits..." unless Git::Pkgs.quiet
59
68
  walker = repo.walk(branch_name)
60
69
  commits = walker.to_a
61
70
  total = commits.size
71
+ print "\rPrefetching diffs..." unless Git::Pkgs.quiet
72
+ repo.prefetch_blob_paths(commits)
73
+ print "\r#{' ' * 20}\r" unless Git::Pkgs.quiet
62
74
 
63
75
  stats = bulk_process_commits(commits, branch, analyzer, total, repo)
64
76
 
@@ -66,10 +78,10 @@ module Git
66
78
 
67
79
  Database.optimize_for_reads
68
80
 
69
- puts "\rDone!#{' ' * 20}"
70
- puts "Analyzed #{total} commits"
71
- puts "Found #{stats[:dependency_commits]} commits with dependency changes"
72
- puts "Stored #{stats[:snapshots_stored]} snapshots"
81
+ info "\rDone!#{' ' * 20}"
82
+ info "Analyzed #{total} commits"
83
+ info "Found #{stats[:dependency_commits]} commits with dependency changes"
84
+ info "Stored #{stats[:snapshots_stored]} snapshots"
73
85
  end
74
86
 
75
87
  def list_branches
@@ -104,14 +116,14 @@ module Git
104
116
  Database.connect(repo.git_dir)
105
117
 
106
118
  branch = Models::Branch.find_by(name: branch_name)
107
- error "Branch '#{branch_name}' not tracked" unless branch
119
+ error "Branch '#{branch_name}' not tracked. Run 'git pkgs branch list' to see tracked branches." unless branch
108
120
 
109
121
  # Only delete branch_commits, keep shared commits
110
122
  count = branch.branch_commits.count
111
123
  branch.branch_commits.delete_all
112
124
  branch.destroy
113
125
 
114
- puts "Removed branch '#{branch_name}' (#{count} branch-commit links)"
126
+ info "Removed branch '#{branch_name}' (#{count} branch-commit links)"
115
127
  end
116
128
 
117
129
  def bulk_process_commits(commits, branch, analyzer, total, repo)
@@ -196,7 +208,7 @@ module Git
196
208
 
197
209
  commits.each do |rugged_commit|
198
210
  processed += 1
199
- print "\rProcessing commit #{processed}/#{total}..." if processed % 50 == 0 || processed == total
211
+ print "\rProcessing commit #{processed}/#{total}..." if !Git::Pkgs.quiet && (processed % 50 == 0 || processed == total)
200
212
 
201
213
  next if rugged_commit.parents.length > 1
202
214
 
@@ -247,7 +259,7 @@ module Git
247
259
 
248
260
  snapshot = result[:snapshot]
249
261
 
250
- if dependency_commit_count % SNAPSHOT_INTERVAL == 0
262
+ if dependency_commit_count % snapshot_interval == 0
251
263
  snapshot.each do |(manifest_path, name), dep_info|
252
264
  pending_snapshots << {
253
265
  sha: rugged_commit.oid,
@@ -262,7 +274,7 @@ module Git
262
274
  end
263
275
  end
264
276
 
265
- flush.call if pending_commits.size >= BATCH_SIZE
277
+ flush.call if pending_commits.size >= batch_size
266
278
  end
267
279
 
268
280
  if snapshot.any?