bundle_update_interactive 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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