bundle_update_interactive 0.8.0 → 0.9.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: 0cbe5e5a4fef836edfd7c1bc87939066c2f7460f4f1b883510ea065840747a54
4
- data.tar.gz: 26bc0b61f7d3d2830c3fd687bd8e723486446cdbe6acf91455e6d02dfbb93df6
3
+ metadata.gz: c5fa0d1bf9fc3114b352236559c7a91b460b6b2816b3cd0cd3b7bc848fd072b5
4
+ data.tar.gz: d70836e166741cf2efe13d188d393e094fe1adf50bb02e64f7e997a853dd65a2
5
5
  SHA512:
6
- metadata.gz: d351f14505560566df5b09fd27ca1a537bca621208bb1859bb6ba1a430333a5889a2d8f1dfd6ce5b72ede3cecb3429c148a1e48a3a795c3290effe0e37c207d0
7
- data.tar.gz: c649e223608a24c65715f7c430abc64841b41a8d765612a57c64cc5c7c56f4cc1327389916521c5f19af0742e177b4f3e5d31267aacbda3048efa5b9b9b0cc78
6
+ metadata.gz: ab496890f43f89d9519b3e14e32998fad0d888295419dd09ce2d69f546b09a8699e635c31a442aa3990d048dc09d0a864a488307c6f047213369c198aa8b3403
7
+ data.tar.gz: 4030b23f6982f0732f4a289d637ff94b1881e5b907377af69dcb0f5125c7fa19f29bb016a957c630a094268f30ecdda1d60f9978f1f9231a7e0cff4ca0b6ab23
data/README.md CHANGED
@@ -42,6 +42,7 @@ bundle ui
42
42
 
43
43
  ## Options
44
44
 
45
+ - `--commit` [applies each gem update in a discrete git commit](#git-commits)
45
46
  - `--latest` [modifies the Gemfile if necessary to allow the latest gem versions](#allow-latest-versions)
46
47
  - `-D` / `--exclusively=GROUP` [limits updatable gems by Gemfile groups](#limit-impact-by-gemfile-groups)
47
48
 
@@ -69,6 +70,27 @@ Some gems, notably `rails`, are composed of smaller gems like `actionpack`, `act
69
70
 
70
71
  Therefore, if any Rails component has a security vulnerability, `bundle update-interactive` will automatically roll up that information into a single `rails` line item, so you can select it and upgrade all of its components in one shot.
71
72
 
73
+ ### Git commits
74
+
75
+ Sometimes, updating gems can lead to bugs or regressions. To facilitate troubleshooting, `update-interactive` offers the ability to commit each selected gem update in its own git commit, complete with a descriptive commit message. You can then make use of tools like `git bisect` to more easily find the update that introduced the problem.
76
+
77
+ To enable this behavior, pass the `--commit` option:
78
+
79
+ ```
80
+ bundle update-interactive --commit
81
+ ```
82
+
83
+ The gems you select to be updated will be applied in separate commits, like this:
84
+
85
+ ```
86
+ * c9801382 Update activeadmin 3.2.2 → 3.2.3
87
+ * 9957254b Update rexml 3.3.5 → 3.3.6
88
+ * 4a4f2072 Update sass 1.77.6 → 1.77.8
89
+ ```
90
+
91
+ > [!NOTE]
92
+ > In rare cases, Bundler may not be able to update a gem separately, due to interdependencies between gem versions. If this happens, you will see a message like "attempted to update [GEM] but its version stayed the same."
93
+
72
94
  ### Held back gems
73
95
 
74
96
  When a newer version of a gem is available, but updating is not allowed due to a Gemfile requirement, `update-interactive` will report that the gem has been held back.
@@ -31,7 +31,8 @@ module BundleUpdateInteractive
31
31
  return "https://github.com/#{changelog_path}" if changelog_path
32
32
 
33
33
  releases_url = "https://github.com/#{path}/releases"
34
- releases_url if HTTP.head("#{releases_url}/tag/v#{version}").success?
34
+ response = HTTP.get(releases_url)
35
+ releases_url if response.success? && response.body.include?("v#{version}")
35
36
  end
36
37
 
37
38
  private
@@ -61,9 +61,12 @@ module BundleUpdateInteractive
61
61
  end
62
62
 
63
63
  def build_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
64
- OptionParser.new do |parser|
64
+ OptionParser.new do |parser| # rubocop:disable Metrics/BlockLength
65
65
  parser.summary_indent = " "
66
66
  parser.summary_width = 24
67
+ parser.on("--commit", "Create a git commit for each selected gem update") do
68
+ options.commit = true
69
+ end
67
70
  parser.on("--latest", "Modify the Gemfile to allow the latest gem versions") do
68
71
  options.latest = true
69
72
  end
@@ -90,13 +93,18 @@ module BundleUpdateInteractive
90
93
  end
91
94
 
92
95
  attr_accessor :exclusively
93
- attr_writer :latest
96
+ attr_writer :commit, :latest
94
97
 
95
98
  def initialize
96
99
  @exclusively = []
100
+ @commit = false
97
101
  @latest = false
98
102
  end
99
103
 
104
+ def commit?
105
+ @commit
106
+ end
107
+
100
108
  def latest?
101
109
  @latest
102
110
  end
@@ -17,7 +17,13 @@ module BundleUpdateInteractive
17
17
  puts "Updating the following gems."
18
18
  puts Table.updatable(selected_gems).render
19
19
  puts
20
- updater.apply_updates(*selected_gems.keys)
20
+
21
+ if options.commit?
22
+ GitCommitter.new(updater).apply_updates_as_individual_commits(*selected_gems.keys)
23
+ else
24
+ updater.apply_updates(*selected_gems.keys)
25
+ end
26
+
21
27
  puts_gemfile_modified_notice if updater.modified_gemfile?
22
28
  rescue Exception => e # rubocop:disable Lint/RescueException
23
29
  handle_exception(e)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ module BundleUpdateInteractive
6
+ class GitCommitter
7
+ def initialize(updater)
8
+ @updater = updater
9
+ end
10
+
11
+ def apply_updates_as_individual_commits(*gem_names)
12
+ assert_git_executable!
13
+ assert_working_directory_clean!
14
+
15
+ gem_names.flatten.each do |name|
16
+ updates = updater.apply_updates(name)
17
+ updated_gem = updates[name] || updates.values.first
18
+ next if updated_gem.nil?
19
+
20
+ commit_message = format_commit_message(updated_gem)
21
+ system "git add Gemfile Gemfile.lock", exception: true
22
+ system "git commit -m #{commit_message.shellescape}", exception: true
23
+ end
24
+ end
25
+
26
+ def format_commit_message(outdated_gem)
27
+ [
28
+ "Update",
29
+ outdated_gem.name,
30
+ outdated_gem.current_version.to_s,
31
+ outdated_gem.current_git_version,
32
+ "→",
33
+ outdated_gem.updated_version.to_s,
34
+ outdated_gem.updated_git_version
35
+ ].compact.join(" ")
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :updater
41
+
42
+ def assert_git_executable!
43
+ success = begin
44
+ `git --version`
45
+ Process.last_status.success?
46
+ rescue SystemCallError
47
+ false
48
+ end
49
+ raise Error, "git could not be executed" unless success
50
+ end
51
+
52
+ def assert_working_directory_clean!
53
+ status = `git status --untracked-files=no --porcelain`.strip
54
+ return if status.empty?
55
+
56
+ raise Error, "`git status` reports uncommitted changes; please commit or stash them them first!\n#{status}"
57
+ end
58
+ end
59
+ end
@@ -38,8 +38,8 @@ module BundleUpdateInteractive
38
38
  @changelog_uri =
39
39
  if (diff_url = build_git_diff_url)
40
40
  diff_url
41
- elsif rubygems_source?
42
- changelog_locator.find_changelog_uri(name: name, version: updated_version.to_s)
41
+ elsif (found_uri = rubygems_source? && locate_changelog_uri)
42
+ found_uri
43
43
  else
44
44
  begin
45
45
  Gem::Specification.find_by_name(name)&.homepage
@@ -57,6 +57,10 @@ module BundleUpdateInteractive
57
57
 
58
58
  attr_reader :changelog_locator
59
59
 
60
+ def locate_changelog_uri
61
+ changelog_locator.find_changelog_uri(name: name, version: updated_version.to_s)
62
+ end
63
+
60
64
  def build_git_diff_url
61
65
  return nil unless git_version_changed?
62
66
 
@@ -18,6 +18,11 @@ module BundleUpdateInteractive
18
18
  def apply_updates(*gem_names)
19
19
  expanded_names = expand_gems_with_exact_dependencies(*gem_names)
20
20
  BundlerCommands.update_gems_conservatively(*expanded_names)
21
+
22
+ # Return the gems that were actually updated based on observed changes to the lock file
23
+ updated_gems = build_outdated_gems(File.read("Gemfile.lock"))
24
+ @current_lockfile = Lockfile.parse
25
+ updated_gems
21
26
  end
22
27
 
23
28
  # Overridden by Latest::Updater subclass
@@ -32,7 +37,11 @@ module BundleUpdateInteractive
32
37
  def find_updatable_gems
33
38
  return {} if candidate_gems && candidate_gems.empty?
34
39
 
35
- updated_lockfile = Lockfile.parse(BundlerCommands.read_updated_lockfile(*Array(candidate_gems)))
40
+ build_outdated_gems(BundlerCommands.read_updated_lockfile(*Array(candidate_gems)))
41
+ end
42
+
43
+ def build_outdated_gems(lockfile_contents)
44
+ updated_lockfile = Lockfile.parse(lockfile_contents)
36
45
  current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
37
46
  name = current_lockfile_entry.name
38
47
  updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleUpdateInteractive
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundle_update_interactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brictson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-25 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -131,6 +131,7 @@ files:
131
131
  - lib/bundle_update_interactive/cli/table.rb
132
132
  - lib/bundle_update_interactive/error.rb
133
133
  - lib/bundle_update_interactive/gemfile.rb
134
+ - lib/bundle_update_interactive/git_committer.rb
134
135
  - lib/bundle_update_interactive/http.rb
135
136
  - lib/bundle_update_interactive/latest/gem_requirement.rb
136
137
  - lib/bundle_update_interactive/latest/gemfile_editor.rb
@@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
168
  - !ruby/object:Gem::Version
168
169
  version: '0'
169
170
  requirements: []
170
- rubygems_version: 3.5.11
171
+ rubygems_version: 3.5.19
171
172
  signing_key:
172
173
  specification_version: 4
173
174
  summary: Adds an update-interactive command to Bundler