-
Notifications
You must be signed in to change notification settings - Fork 0
Fix GitHub ref swapping by cleaning stale Gemfile.lock entries #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
827cf36
e45f1e5
c014e36
93cd90b
dff7ad3
6fb0937
a0d816f
5674136
0223a54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -138,9 +138,22 @@ def kill_watch_processes | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def show_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts '📊 Swapped dependencies status:' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated_repos = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| each_demo do |demo_path| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| show_demo_status(demo_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated_repos.concat(check_outdated_repos(demo_path)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unless outdated_repos.any? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts "\n⚠️ Outdated GitHub repositories detected:" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated_repos.uniq.each do |repo_info| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commits = repo_info[:commits_behind] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " #{repo_info[:gem_name]} (#{repo_info[:repo]}@#{repo_info[:ref]}) - #{commits} commit(s) behind" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts "\n💡 To update: Re-run your swap command to fetch latest changes" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " Example: bin/swap-deps --github '#{outdated_repos.first[:repo]}##{outdated_repos.first[:ref]}'" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # CLI entry point: Display cache information including location, size, and cached repositories | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -361,6 +374,50 @@ def detect_git_branch(path) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if any GitHub-based swapped gems are outdated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check_outdated_repos(demo_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gemfile_path = File.join(demo_path, 'Gemfile') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] unless File.exist?(gemfile_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| swapped_gems = detect_swapped_gems(gemfile_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| swapped_gems.each do |gem| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| next unless gem[:type] == 'github' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo, ref = gem[:path].split('@', 2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cache_path = github_cache_path(gem[:name], { repo: repo, branch: ref }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| next unless File.directory?(cache_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commits_behind = check_commits_behind(cache_path, ref) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| next unless commits_behind&.positive? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated << { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gem_name: gem[:name], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref: ref, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| commits_behind: commits_behind | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outdated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check how many commits behind the local cache is from the remote branch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check_commits_behind(cache_path, branch) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dir.chdir(cache_path) do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Fetch latest without output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system('git', 'fetch', 'origin', branch, out: '/dev/null', err: '/dev/null') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Count commits between local and remote | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..origin/#{branch}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return status.success? ? output.strip.to_i : nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rescue StandardError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def cache_repo_dirs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] unless File.directory?(CACHE_DIR) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1009,6 +1066,23 @@ def find_supported_gems_in_gemfile(demo_path) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Removes stale gem entries from Gemfile.lock to prevent bundle update failures | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # This is necessary when switching gem sources (rubygems <-> github <-> path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # because the lock file may reference commits/versions that no longer exist | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def clean_gemfile_lock(demo_path, gems_to_clean) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unless gems_to_clean.any? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lock_file = File.join(demo_path, 'Gemfile.lock') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unless File.exist?(lock_file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " Cleaning lock file entries for: #{gems_to_clean.join(', ')}" if verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Simply remove the lock file and let bundle regenerate it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # This is the safest approach to avoid any inconsistencies | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.delete(lock_file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' Removed Gemfile.lock to force clean resolution' if verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
1121
to
1143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Method name and behavior mismatch. The method is named
Consider these improvements: Option 1 (simpler): Rename to reflect actual behavior: -def clean_gemfile_lock(demo_path, gems_to_clean)
- return unless gems_to_clean.any?
+def remove_gemfile_lock(demo_path)Option 2 (more precise but complex): Implement selective cleaning by parsing and modifying Gemfile.lock to remove only the specified gem sections, preserving other dependency versions. This would require parsing the lock file format. Add error handling: -File.delete(lock_file)
+begin
+ File.delete(lock_file)
+rescue Errno::EACCES => e
+ warn " ⚠️ Warning: Could not delete Gemfile.lock: #{e.message}"
+ return false
+end📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # rubocop:disable Metrics/MethodLength, Metrics/AbcSize | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def run_bundle_install(demo_path, for_restore: false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return if dry_run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1028,21 +1102,28 @@ def run_bundle_install(demo_path, for_restore: false) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system('bundle', 'install', '--quiet') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Clean lock file to prevent "Could not find gem" errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clean_gemfile_lock(demo_path, gems_to_update) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " Updating gems: #{gems_to_update.join(', ')}" if verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| success = Dir.chdir(demo_path) do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Update specific gems to pull from rubygems | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = system('bundle', 'update', *gems_to_update, '--quiet') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warn ' ⚠️ ERROR: Failed to update gems. Lock file may be inconsistent.' unless result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Install from scratch since we removed the lock file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = system('bundle', 'install', '--quiet') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warn ' ⚠️ ERROR: Failed to install gems. Check Gemfile for errors.' unless result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elsif gems_to_update.any? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # When swapping to local/GitHub dependencies, we need to update the gems | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # to resolve potential lock file conflicts (e.g., version no longer available in new source) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' Running bundle update (to resolve swapped gem sources)...' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " Updating gems: #{gems_to_update.join(', ')}" if verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Clean lock file to prevent "Could not find gem" errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clean_gemfile_lock(demo_path, gems_to_update) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' Running bundle install (to resolve swapped gem sources)...' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts " Installing gems: #{gems_to_update.join(', ')}" if verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| success = Dir.chdir(demo_path) do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system('bundle', 'update', *gems_to_update, '--quiet') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Install from scratch since we removed the lock file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| system('bundle', 'install', '--quiet') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' Running bundle install...' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1170,7 +1251,9 @@ def print_next_steps | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' - List: bin/swap-deps --list-watch' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' - Stop: bin/swap-deps --kill-watch' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' 4. Rebuild packages when needed: cd <gem-path> && npm run build' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' 4. Rebuild packages when needed:' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' - See paths: bin/swap-deps --status' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts ' - Auto-rebuild: Re-run with --watch flag for automatic rebuilds on file changes' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| puts "\n To restore: bin/swap-deps --restore" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid string interpolation in git commands.
Line 414 interpolates
branchdirectly into a git command string, which is flagged by Brakeman as a potential command injection risk. Whilevalidate_github_branchshould provide some protection, it's safer to use parameterized commands consistently.Apply this diff to use the array form:
Dir.chdir(cache_path) do # Fetch latest without output system('git', 'fetch', 'origin', branch, out: '/dev/null', err: '/dev/null') # Count commits between local and remote - output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..origin/#{branch}") + output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..origin/#{branch}", chdir: cache_path) return status.success? ? output.strip.to_i : nil endWait, the issue is that
Open3.capture2requires the command as separate arguments, but we're passing a string. Let me revise:Dir.chdir(cache_path) do # Fetch latest without output system('git', 'fetch', 'origin', branch, out: '/dev/null', err: '/dev/null') # Count commits between local and remote - output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..origin/#{branch}") + output, status = Open3.capture2('git', 'rev-list', '--count', 'HEAD', "--not", "origin/#{branch}") return status.success? ? output.strip.to_i : nil endActually, git's rev-list syntax requires the range format. The safest approach is:
Dir.chdir(cache_path) do # Fetch latest without output system('git', 'fetch', 'origin', branch, out: '/dev/null', err: '/dev/null') + remote_ref = "origin/#{branch}" + # Validate the remote ref exists before using it + _output, status = Open3.capture2('git', 'rev-parse', '--verify', remote_ref) + return nil unless status.success? + # Count commits between local and remote - output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..origin/#{branch}") + output, status = Open3.capture2('git', 'rev-list', '--count', "HEAD..#{remote_ref}") return status.success? ? output.strip.to_i : nil endThis adds validation while still using the necessary string interpolation for the ref range. The key improvement is verifying the ref exists first, which provides defense-in-depth beyond the initial branch validation.
🧰 Tools
🪛 Brakeman (7.1.0)
[medium] 414-414: Possible command injection
Type: Command Injection
Confidence: Medium
More info: https://brakemanscanner.org/docs/warning_types/command_injection/
(Command Injection)
🤖 Prompt for AI Agents