From feaba660822f815f569c915e65c2c00c3e604ad0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:45:57 +0000 Subject: [PATCH 1/8] Preserve custom networkTimeout and other properties in gradle-wrapper.properties Implemented a general solution to preserve all custom properties in gradle-wrapper.properties during wrapper updates. The wrapper command regenerates the properties file with defaults, so we now capture original property values before the update and restore them afterward. Only properties that are intentionally updated (distributionUrl, distributionSha256Sum) are allowed to change. This ensures user customizations like networkTimeout are maintained. Added test fixture and test case to verify networkTimeout preservation during updates. Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../gradle/file_updater/wrapper_updater.rb | 121 +++++++++++++++--- .../dependabot/gradle/file_updater_spec.rb | 76 +++++++++++ ...apper-8.14.2-bin-custom-timeout.properties | 7 + 3 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-custom-timeout.properties diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index 1e3ec94f39a..72458c19f6b 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -3,6 +3,7 @@ require "sorbet-runtime" require "shellwords" +require "set" require "dependabot/gradle/distributions" @@ -71,7 +72,8 @@ def update_files(build_file) FileUtils.chmod("+x", "./gradlew") if has_local_script properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") - validate_option = get_validate_distribution_url_option(properties_file) + # Preserve all custom properties before running the wrapper command + original_properties = get_properties(properties_file) env = { "JAVA_OPTS" => proxy_args.join(" ") } # set proxy for gradle execution begin @@ -89,8 +91,8 @@ def update_files(build_file) SharedHelpers.run_shell_command(command, cwd: cwd, env: env) # retry via local wrapper end - # Restore previous validateDistributionUrl option if it existed - override_validate_distribution_url_option(properties_file, validate_option) + # Restore custom properties that should be preserved + restore_properties(properties_file, original_properties) update_files_content(temp_dir, local_files, updated_files) rescue SharedHelpers::HelperSubprocessFailed => e @@ -174,30 +176,107 @@ def populate_temp_directory(temp_dir) end end - # This is a consequence of the lack of proper proxy support in Gradle Wrapper - # During the update process, Gradle Wrapper logic will try to validate the distribution URL - # by performing an HTTP request. If the environment requires a proxy, this validation will fail - # We need to add the `--no-validate-url` the commandline args to disable this validation - # However, this change is persistent in the `gradle-wrapper.properties` file - # To avoid side effects, we read the existing value before the update and restore it afterward - sig { params(properties_file: T.any(Pathname, String)).returns(T.nilable(String)) } - def get_validate_distribution_url_option(properties_file) - return nil unless File.exist?(properties_file) + # Reads all properties from the gradle-wrapper.properties file + # Returns a hash of property key-value pairs + sig { params(properties_file: T.any(Pathname, String)).returns(T::Hash[String, String]) } + def get_properties(properties_file) + return {} unless File.exist?(properties_file) properties_content = File.read(properties_file) - properties_content.match(/^validateDistributionUrl=(.*)$/)&.captures&.first + properties = {} + + properties_content.each_line do |line| + # Skip comments and empty lines + next if line.strip.start_with?("#") || line.strip.empty? + + # Parse property lines in the format: key=value + if line =~ /^([^=]+)=(.*)$/ + key = ::Regexp.last_match(1).strip + value = ::Regexp.last_match(2).strip + properties[key] = value + end + end + + properties end - sig { params(properties_file: T.any(Pathname, String), value: T.nilable(String)).void } - def override_validate_distribution_url_option(properties_file, value) + # Restores custom properties after running the gradle wrapper command + # The wrapper command regenerates gradle-wrapper.properties with default values + # This method restores user customizations while keeping the updated distribution settings + sig { params(properties_file: T.any(Pathname, String), original_properties: T::Hash[String, String]).void } + def restore_properties(properties_file, original_properties) return unless File.exist?(properties_file) + return if original_properties.empty? - properties_content = File.read(properties_file) - updated_content = properties_content.gsub( - /^validateDistributionUrl=(.*)\n/, - value ? "validateDistributionUrl=#{value}\n" : "" - ) - File.write(properties_file, updated_content) + # Properties that are intentionally updated by the wrapper command and should NOT be restored + updated_properties = %w[ + distributionUrl + distributionSha256Sum + ] + + # Read the newly generated properties file + new_content = File.read(properties_file) + new_properties = {} + + new_content.each_line do |line| + next if line.strip.start_with?("#") || line.strip.empty? + + if line =~ /^([^=]+)=(.*)$/ + key = ::Regexp.last_match(1).strip + new_properties[key] = line + end + end + + # Restore original values for properties that weren't intentionally updated + result_lines = [] + added_keys = Set.new + + # First, add all properties from the new file, replacing with original values where appropriate + new_content.each_line do |line| + if line.strip.start_with?("#") || line.strip.empty? + result_lines << line + next + end + + if line =~ /^([^=]+)=/ + key = ::Regexp.last_match(1).strip + added_keys.add(key) + + # Use original value if this property should be preserved + if !updated_properties.include?(key) && original_properties.key?(key) + result_lines << "#{key}=#{original_properties[key]}\n" + else + result_lines << line + end + else + result_lines << line + end + end + + # Add any properties from the original file that weren't in the new file + original_properties.each do |key, value| + next if added_keys.include?(key) + next if updated_properties.include?(key) + + result_lines << "#{key}=#{value}\n" + end + + File.write(properties_file, result_lines.join) + end + + # Legacy method for backward compatibility - now uses get_properties + # @deprecated Use get_properties instead + sig { params(properties_file: T.any(Pathname, String)).returns(T.nilable(String)) } + def get_validate_distribution_url_option(properties_file) + properties = get_properties(properties_file) + properties["validateDistributionUrl"] + end + + # Legacy method for backward compatibility - now uses restore_properties + # @deprecated This method is no longer used, restore_properties handles this + sig { params(properties_file: T.any(Pathname, String), value: T.nilable(String)).void } + def override_validate_distribution_url_option(properties_file, value) + # This method is now a no-op, as restore_properties handles all property restoration end # rubocop:disable Metrics/PerceivedComplexity diff --git a/gradle/spec/dependabot/gradle/file_updater_spec.rb b/gradle/spec/dependabot/gradle/file_updater_spec.rb index 10bd7e71b58..f3aa0656eb4 100644 --- a/gradle/spec/dependabot/gradle/file_updater_spec.rb +++ b/gradle/spec/dependabot/gradle/file_updater_spec.rb @@ -762,6 +762,82 @@ "all", "443c9c8ee2ac1ee0e11881a40f2376d79c66386264a44b24a9f8ca67e633375f", "f759b8dd5204e2e3fa4ca3e73f452f087153cf81bac9561eeb854229cc2c5365" + + context "with custom networkTimeout" do + subject(:updated_buildfile) do + updated_files.find { |f| f.name == "gradle/wrapper/gradle-wrapper.properties" } + end + + let(:buildfile) do + wrapper_file + end + + let(:wrapper_file) do + Dependabot::DependencyFile.new( + name: "gradle/wrapper/gradle-wrapper.properties", + content: fixture( + "wrapper_files", + "gradle-wrapper-8.14.2-bin-custom-timeout.properties" + ) + ) + end + + let(:distribution_url) do + "https\\://services.gradle.org/distributions/gradle-9.0.0-bin.zip" + end + + let(:dependency) do + Dependabot::Dependency.new( + name: "gradle-wrapper", + version: "9.0.0", + previous_version: "8.14.2", + requirements: [{ + file: "gradle/wrapper/gradle-wrapper.properties", + requirement: "9.0.0", + groups: [], + source: { type: "gradle-distribution", url: distribution_url, property: "distributionUrl" } + }], + previous_requirements: [{ + file: "gradle/wrapper/gradle-wrapper.properties", + requirement: "8.14.2", + groups: [], + source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" } + }], + package_manager: "gradle" + ) + end + + before do + # Mock the file operations to simulate the wrapper command updating the properties + allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, env:| + # Simulate the wrapper command updating the properties file with defaults + properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") + if File.exist?(properties_file) + # Write a simulated updated properties file with default networkTimeout + File.write(properties_file, <<~PROPERTIES) + distributionBase=GRADLE_USER_HOME + distributionPath=wrapper/dists + distributionUrl=https\\://services.gradle.org/distributions/gradle-9.0.0-bin.zip + networkTimeout=10000 + validateDistributionUrl=true + zipStoreBase=GRADLE_USER_HOME + zipStorePath=wrapper/dists + PROPERTIES + end + end + allow(File).to receive(:exist?).and_call_original + allow(FileUtils).to receive(:chmod) + end + + it "preserves the custom networkTimeout value" do + expect(updated_buildfile.content).to include("networkTimeout=30000") + expect(updated_buildfile.content).not_to include("networkTimeout=10000") + end + + it "updates the distributionUrl" do + expect(updated_buildfile.content).to include("distributionUrl=#{distribution_url}") + end + end end context "with a version catalog" do diff --git a/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-custom-timeout.properties b/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-custom-timeout.properties new file mode 100644 index 00000000000..6fe4eb7cf93 --- /dev/null +++ b/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-custom-timeout.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +networkTimeout=30000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From 61c7cc84b5bb65d73ca3cbc86bffecc9cebd1996 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:47:31 +0000 Subject: [PATCH 2/8] Update comment to reflect new property restoration approach Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../lib/dependabot/gradle/file_updater/wrapper_updater.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index 72458c19f6b..ded21cd09e1 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -123,9 +123,9 @@ def command_args(requirements) # --no-validate-url is required to bypass HTTP proxy issues when running ./gradlew # This prevents validation failures during the wrapper update process # Note: This temporarily sets validateDistributionUrl=false in gradle-wrapper.properties - # The original value is restored after the wrapper task completes - # see method `get_validate_distribution_url_option` for more details - args = %W(wrapper --gradle-version #{version} --no-validate-url) # see + # The original value (along with all other custom properties) is restored after the wrapper task completes + # see methods `get_properties` and `restore_properties` for more details + args = %W(wrapper --gradle-version #{version} --no-validate-url) args += %W(--distribution-type #{distribution_type}) if distribution_type args += %W(--gradle-distribution-sha256-sum #{checksum}) if checksum args From d5886f484f8e03bbb4745f5bd37b00f66abdacc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:00:13 +0000 Subject: [PATCH 3/8] Refactor to preserve file structure, comments, and property order Changed approach from "restore properties" to "update only what changed": - Save entire original file content (not just property values) - Run gradle wrapper command (generates new file) - Extract only the NEW values for distributionUrl and distributionSha256Sum - Update ONLY those properties in the original content via regex replacement - Write back the modified original content Benefits: - Preserves all comments in the file - Preserves property order - Preserves whitespace and formatting - Only modifies distributionUrl and distributionSha256Sum - More surgical approach Added test case to verify comments and order are preserved. Removed unused 'set' require since we no longer need it. Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../gradle/file_updater/wrapper_updater.rb | 108 +++++++----------- .../dependabot/gradle/file_updater_spec.rb | 95 +++++++++++++++ ...rapper-8.14.2-bin-with-comments.properties | 10 ++ 3 files changed, 148 insertions(+), 65 deletions(-) create mode 100644 gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-with-comments.properties diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index ded21cd09e1..8e8aaafd0a9 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -3,7 +3,6 @@ require "sorbet-runtime" require "shellwords" -require "set" require "dependabot/gradle/distributions" @@ -72,8 +71,8 @@ def update_files(build_file) FileUtils.chmod("+x", "./gradlew") if has_local_script properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") - # Preserve all custom properties before running the wrapper command - original_properties = get_properties(properties_file) + # Save the original file content to preserve structure, comments, and property order + original_content = File.exist?(properties_file) ? File.read(properties_file) : nil env = { "JAVA_OPTS" => proxy_args.join(" ") } # set proxy for gradle execution begin @@ -91,8 +90,8 @@ def update_files(build_file) SharedHelpers.run_shell_command(command, cwd: cwd, env: env) # retry via local wrapper end - # Restore custom properties that should be preserved - restore_properties(properties_file, original_properties) + # Update only the properties that should change, preserving everything else + update_distribution_properties(properties_file, original_content) update_files_content(temp_dir, local_files, updated_files) rescue SharedHelpers::HelperSubprocessFailed => e @@ -176,8 +175,45 @@ def populate_temp_directory(temp_dir) end end + # Updates only the distribution properties in the original file content + # This preserves the original file structure, comments, property order, and all other properties + # Only distributionUrl and distributionSha256Sum are updated from the newly generated file + sig { params(properties_file: T.any(Pathname, String), original_content: T.nilable(String)).void } + def update_distribution_properties(properties_file, original_content) + return unless File.exist?(properties_file) + return unless original_content + + # Read the newly generated file to extract updated distribution properties + new_content = File.read(properties_file) + updated_values = {} + + # Extract the new values for distributionUrl and distributionSha256Sum + new_content.each_line do |line| + next if line.strip.start_with?("#") || line.strip.empty? + + if line =~ /^(distributionUrl|distributionSha256Sum)=(.*)$/ + key = ::Regexp.last_match(1) + value = ::Regexp.last_match(2) + updated_values[key] = value + end + end + + # Update only those properties in the original content + result_content = original_content.dup + + updated_values.each do |key, new_value| + # Replace the property value, preserving the line structure + # This regex matches the property line and replaces only the value part + result_content.gsub!(/^(#{Regexp.escape(key)}=).*$/, "\\1#{new_value}") + end + + # Write back the updated original content + File.write(properties_file, result_content) + end + # Reads all properties from the gradle-wrapper.properties file # Returns a hash of property key-value pairs + # @deprecated This method is kept for backward compatibility but is no longer used sig { params(properties_file: T.any(Pathname, String)).returns(T::Hash[String, String]) } def get_properties(properties_file) return {} unless File.exist?(properties_file) @@ -200,68 +236,10 @@ def get_properties(properties_file) properties end - # Restores custom properties after running the gradle wrapper command - # The wrapper command regenerates gradle-wrapper.properties with default values - # This method restores user customizations while keeping the updated distribution settings + # @deprecated This method is no longer used - update_distribution_properties handles this more elegantly sig { params(properties_file: T.any(Pathname, String), original_properties: T::Hash[String, String]).void } def restore_properties(properties_file, original_properties) - return unless File.exist?(properties_file) - return if original_properties.empty? - - # Properties that are intentionally updated by the wrapper command and should NOT be restored - updated_properties = %w[ - distributionUrl - distributionSha256Sum - ] - - # Read the newly generated properties file - new_content = File.read(properties_file) - new_properties = {} - - new_content.each_line do |line| - next if line.strip.start_with?("#") || line.strip.empty? - - if line =~ /^([^=]+)=(.*)$/ - key = ::Regexp.last_match(1).strip - new_properties[key] = line - end - end - - # Restore original values for properties that weren't intentionally updated - result_lines = [] - added_keys = Set.new - - # First, add all properties from the new file, replacing with original values where appropriate - new_content.each_line do |line| - if line.strip.start_with?("#") || line.strip.empty? - result_lines << line - next - end - - if line =~ /^([^=]+)=/ - key = ::Regexp.last_match(1).strip - added_keys.add(key) - - # Use original value if this property should be preserved - if !updated_properties.include?(key) && original_properties.key?(key) - result_lines << "#{key}=#{original_properties[key]}\n" - else - result_lines << line - end - else - result_lines << line - end - end - - # Add any properties from the original file that weren't in the new file - original_properties.each do |key, value| - next if added_keys.include?(key) - next if updated_properties.include?(key) - - result_lines << "#{key}=#{value}\n" - end - - File.write(properties_file, result_lines.join) + # This method is now a no-op, as update_distribution_properties handles property updates end # Legacy method for backward compatibility - now uses get_properties diff --git a/gradle/spec/dependabot/gradle/file_updater_spec.rb b/gradle/spec/dependabot/gradle/file_updater_spec.rb index f3aa0656eb4..2e854eb03cc 100644 --- a/gradle/spec/dependabot/gradle/file_updater_spec.rb +++ b/gradle/spec/dependabot/gradle/file_updater_spec.rb @@ -838,6 +838,101 @@ expect(updated_buildfile.content).to include("distributionUrl=#{distribution_url}") end end + + context "with comments in properties file" do + subject(:updated_buildfile) do + updated_files.find { |f| f.name == "gradle/wrapper/gradle-wrapper.properties" } + end + + let(:buildfile) do + wrapper_file + end + + let(:wrapper_file) do + Dependabot::DependencyFile.new( + name: "gradle/wrapper/gradle-wrapper.properties", + content: fixture( + "wrapper_files", + "gradle-wrapper-8.14.2-bin-with-comments.properties" + ) + ) + end + + let(:distribution_url) do + "https\\://services.gradle.org/distributions/gradle-9.0.0-bin.zip" + end + + let(:dependency) do + Dependabot::Dependency.new( + name: "gradle-wrapper", + version: "9.0.0", + previous_version: "8.14.2", + requirements: [{ + file: "gradle/wrapper/gradle-wrapper.properties", + requirement: "9.0.0", + groups: [], + source: { type: "gradle-distribution", url: distribution_url, property: "distributionUrl" } + }], + previous_requirements: [{ + file: "gradle/wrapper/gradle-wrapper.properties", + requirement: "8.14.2", + groups: [], + source: { type: "gradle-distribution", url: "https://services.gradle.org", property: "distributionUrl" } + }], + package_manager: "gradle" + ) + end + + before do + # Mock the file operations to simulate the wrapper command updating the properties + allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, env:| + # Simulate the wrapper command generating a new file without comments + properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") + if File.exist?(properties_file) + # Write a simulated updated properties file without comments (as gradle does) + File.write(properties_file, <<~PROPERTIES) + distributionBase=GRADLE_USER_HOME + distributionPath=wrapper/dists + distributionUrl=https\\://services.gradle.org/distributions/gradle-9.0.0-bin.zip + networkTimeout=10000 + validateDistributionUrl=true + zipStoreBase=GRADLE_USER_HOME + zipStorePath=wrapper/dists + PROPERTIES + end + end + allow(File).to receive(:exist?).and_call_original + allow(FileUtils).to receive(:chmod) + end + + it "preserves comments from the original file" do + expect(updated_buildfile.content).to include("# Configuration for Gradle wrapper") + expect(updated_buildfile.content).to include("# Custom network timeout for slow connections") + end + + it "preserves the custom networkTimeout value" do + expect(updated_buildfile.content).to include("networkTimeout=30000") + expect(updated_buildfile.content).not_to include("networkTimeout=10000") + end + + it "updates the distributionUrl" do + expect(updated_buildfile.content).to include("distributionUrl=#{distribution_url}") + end + + it "maintains the original property order" do + lines = updated_buildfile.content.lines.reject { |l| l.strip.start_with?("#") || l.strip.empty? } + property_names = lines.map { |l| l.split("=").first } + expect(property_names).to eq(%w[ + distributionBase + distributionPath + distributionUrl + networkTimeout + validateDistributionUrl + zipStoreBase + zipStorePath + ]) + end + end end context "with a version catalog" do diff --git a/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-with-comments.properties b/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-with-comments.properties new file mode 100644 index 00000000000..7c0843aae43 --- /dev/null +++ b/gradle/spec/fixtures/wrapper_files/gradle-wrapper-8.14.2-bin-with-comments.properties @@ -0,0 +1,10 @@ +# Configuration for Gradle wrapper +# See https://docs.gradle.org/current/userguide/gradle_wrapper.html +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +# Custom network timeout for slow connections +networkTimeout=30000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From c9523cf6bb11dafe9dfadfcf9bd145dff3bf8658 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:08:51 +0000 Subject: [PATCH 4/8] Remove deprecated private methods per reviewer feedback Removed the following deprecated private methods as they are no longer used: - get_properties - restore_properties - get_validate_distribution_url_option - override_validate_distribution_url_option These methods were replaced by update_distribution_properties which handles property updates more elegantly while preserving file structure and comments. Also updated the comment on line 126 to reference the correct method. Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../gradle/file_updater/wrapper_updater.rb | 48 +------------------ 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index 8e8aaafd0a9..7f98ff54a2e 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -123,7 +123,7 @@ def command_args(requirements) # This prevents validation failures during the wrapper update process # Note: This temporarily sets validateDistributionUrl=false in gradle-wrapper.properties # The original value (along with all other custom properties) is restored after the wrapper task completes - # see methods `get_properties` and `restore_properties` for more details + # see method `update_distribution_properties` for more details args = %W(wrapper --gradle-version #{version} --no-validate-url) args += %W(--distribution-type #{distribution_type}) if distribution_type args += %W(--gradle-distribution-sha256-sum #{checksum}) if checksum @@ -211,52 +211,6 @@ def update_distribution_properties(properties_file, original_content) File.write(properties_file, result_content) end - # Reads all properties from the gradle-wrapper.properties file - # Returns a hash of property key-value pairs - # @deprecated This method is kept for backward compatibility but is no longer used - sig { params(properties_file: T.any(Pathname, String)).returns(T::Hash[String, String]) } - def get_properties(properties_file) - return {} unless File.exist?(properties_file) - - properties_content = File.read(properties_file) - properties = {} - - properties_content.each_line do |line| - # Skip comments and empty lines - next if line.strip.start_with?("#") || line.strip.empty? - - # Parse property lines in the format: key=value - if line =~ /^([^=]+)=(.*)$/ - key = ::Regexp.last_match(1).strip - value = ::Regexp.last_match(2).strip - properties[key] = value - end - end - - properties - end - - # @deprecated This method is no longer used - update_distribution_properties handles this more elegantly - sig { params(properties_file: T.any(Pathname, String), original_properties: T::Hash[String, String]).void } - def restore_properties(properties_file, original_properties) - # This method is now a no-op, as update_distribution_properties handles property updates - end - - # Legacy method for backward compatibility - now uses get_properties - # @deprecated Use get_properties instead - sig { params(properties_file: T.any(Pathname, String)).returns(T.nilable(String)) } - def get_validate_distribution_url_option(properties_file) - properties = get_properties(properties_file) - properties["validateDistributionUrl"] - end - - # Legacy method for backward compatibility - now uses restore_properties - # @deprecated This method is no longer used, restore_properties handles this - sig { params(properties_file: T.any(Pathname, String), value: T.nilable(String)).void } - def override_validate_distribution_url_option(properties_file, value) - # This method is now a no-op, as restore_properties handles all property restoration - end - # rubocop:disable Metrics/PerceivedComplexity sig { returns(T::Array[String]) } def proxy_args From f8816aca43d482748ea798bbe5a240ce4c7e3e1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:46:21 +0000 Subject: [PATCH 5/8] Address reviewer feedback on nil check and dup call 1. Remove unnecessary nil check for properties file (line 75) - The file is always populated from dependency_files in populate_temp_directory - If it doesn't exist, we should fail with an exception (File.read will raise) - Added comment explaining why the file should exist 2. Add explanatory comment for .dup call (line 202) - Clarifies that .dup is needed to avoid mutating the original_content parameter - gsub! modifies strings in-place, so we need a copy - This is defensive programming to prevent side effects 3. Update method signature - Changed original_content from T.nilable(String) to String - Removed unnecessary nil check inside the method These changes make the code clearer and more robust by failing fast if the expected file doesn't exist, rather than silently continuing with nil. Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../dependabot/gradle/file_updater/wrapper_updater.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index 7f98ff54a2e..d75724a00b7 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -72,7 +72,8 @@ def update_files(build_file) properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") # Save the original file content to preserve structure, comments, and property order - original_content = File.exist?(properties_file) ? File.read(properties_file) : nil + # The file should exist as it was populated from dependency_files in populate_temp_directory + original_content = File.read(properties_file) env = { "JAVA_OPTS" => proxy_args.join(" ") } # set proxy for gradle execution begin @@ -178,10 +179,9 @@ def populate_temp_directory(temp_dir) # Updates only the distribution properties in the original file content # This preserves the original file structure, comments, property order, and all other properties # Only distributionUrl and distributionSha256Sum are updated from the newly generated file - sig { params(properties_file: T.any(Pathname, String), original_content: T.nilable(String)).void } + sig { params(properties_file: T.any(Pathname, String), original_content: String).void } def update_distribution_properties(properties_file, original_content) return unless File.exist?(properties_file) - return unless original_content # Read the newly generated file to extract updated distribution properties new_content = File.read(properties_file) @@ -198,7 +198,8 @@ def update_distribution_properties(properties_file, original_content) end end - # Update only those properties in the original content + # Create a copy to avoid mutating the original_content parameter + # gsub! modifies the string in-place, so we need a duplicate to preserve the original result_content = original_content.dup updated_values.each do |key, new_value| From 18a29b2bc41aeb938ba743b057e2b8a56da99033 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:59:03 +0000 Subject: [PATCH 6/8] Simplify code comments per reviewer feedback Refined comments to be more concise and developer-friendly: 1. Line 74: Simplified from 2 verbose lines to 1 concise line - Removed explanation about populate_temp_directory (implementation detail) - Kept focus on what matters: preserving comments, order, and custom properties 2. Line 200-204: Simplified from 4 lines to 2 lines - Removed Ruby basics explanation about gsub! behavior - Kept essential info: why we dup and what we're updating Comments now focus on intent rather than implementation details. Tests already verify: - Comments are preserved (line 908-911) - Custom properties preserved (line 913-916) - Property order maintained (line 922-934) Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../dependabot/gradle/file_updater/wrapper_updater.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index d75724a00b7..8c847e331ba 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -71,8 +71,7 @@ def update_files(build_file) FileUtils.chmod("+x", "./gradlew") if has_local_script properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") - # Save the original file content to preserve structure, comments, and property order - # The file should exist as it was populated from dependency_files in populate_temp_directory + # Save original content to preserve comments, order, and custom properties original_content = File.read(properties_file) env = { "JAVA_OPTS" => proxy_args.join(" ") } # set proxy for gradle execution @@ -198,13 +197,11 @@ def update_distribution_properties(properties_file, original_content) end end - # Create a copy to avoid mutating the original_content parameter - # gsub! modifies the string in-place, so we need a duplicate to preserve the original + # Duplicate to avoid mutating the parameter result_content = original_content.dup + # Update only the distribution properties in the original content updated_values.each do |key, new_value| - # Replace the property value, preserving the line structure - # This regex matches the property line and replaces only the value part result_content.gsub!(/^(#{Regexp.escape(key)}=).*$/, "\\1#{new_value}") end From f6338a00b3513e8c9714d6d25df490db29bba6ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:00:59 +0000 Subject: [PATCH 7/8] Fix Sorbet type error for updated_values hash Add type annotation to updated_values hash to fix Sorbet type checking error. The hash stores String keys (property names like "distributionUrl") and String values (property values), so we annotate it as T::Hash[String, String]. This ensures that when iterating over the hash, the key variable is typed as String, which satisfies Regexp.escape's type requirement. Fixes Sorbet error 7018: - Line 187: updated_values = T.let({}, T::Hash[String, String]) - Now key is String when passed to Regexp.escape on line 205 Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index 8c847e331ba..af8b0032da2 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -184,7 +184,7 @@ def update_distribution_properties(properties_file, original_content) # Read the newly generated file to extract updated distribution properties new_content = File.read(properties_file) - updated_values = {} + updated_values = T.let({}, T::Hash[String, String]) # Extract the new values for distributionUrl and distributionSha256Sum new_content.each_line do |line| From 1dcb8ce38023612603575b6a2b60609136decd11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:08:09 +0000 Subject: [PATCH 8/8] Fix Sorbet type errors and RuboCop linting issues Fixed all Sorbet type errors and RuboCop linting issues: Sorbet fixes: - Wrapped Regexp.last_match(1) and Regexp.last_match(2) with T.must() to assert non-nil values (safe since we're inside a regex match) - This satisfies the T::Hash[String, String] type requirement RuboCop fixes: - Style/Next: Changed if block to use next unless pattern - Lint/UnusedBlockArgument: Renamed unused env: to _env: in test mocks - Layout/FirstMethodArgumentLineBreak: Added line break before first arg - Style/PercentLiteralDelimiters: Changed %w[] to %w() delimiter Verification: - bundle exec srb tc: No errors! - bundle exec rubocop: No offenses detected Co-authored-by: kbukum1 <171620528+kbukum1@users.noreply.github.com> --- .../gradle/file_updater/wrapper_updater.rb | 9 ++++--- .../dependabot/gradle/file_updater_spec.rb | 24 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb index af8b0032da2..0f38cd72863 100644 --- a/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb +++ b/gradle/lib/dependabot/gradle/file_updater/wrapper_updater.rb @@ -189,12 +189,11 @@ def update_distribution_properties(properties_file, original_content) # Extract the new values for distributionUrl and distributionSha256Sum new_content.each_line do |line| next if line.strip.start_with?("#") || line.strip.empty? + next unless line =~ /^(distributionUrl|distributionSha256Sum)=(.*)$/ - if line =~ /^(distributionUrl|distributionSha256Sum)=(.*)$/ - key = ::Regexp.last_match(1) - value = ::Regexp.last_match(2) - updated_values[key] = value - end + key = T.must(::Regexp.last_match(1)) + value = T.must(::Regexp.last_match(2)) + updated_values[key] = value end # Duplicate to avoid mutating the parameter diff --git a/gradle/spec/dependabot/gradle/file_updater_spec.rb b/gradle/spec/dependabot/gradle/file_updater_spec.rb index 2e854eb03cc..a2ee70134ab 100644 --- a/gradle/spec/dependabot/gradle/file_updater_spec.rb +++ b/gradle/spec/dependabot/gradle/file_updater_spec.rb @@ -809,7 +809,7 @@ before do # Mock the file operations to simulate the wrapper command updating the properties - allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, env:| + allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, _env:| # Simulate the wrapper command updating the properties file with defaults properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") if File.exist?(properties_file) @@ -885,7 +885,7 @@ before do # Mock the file operations to simulate the wrapper command updating the properties - allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, env:| + allow(Dependabot::SharedHelpers).to receive(:run_shell_command) do |_command, cwd:, _env:| # Simulate the wrapper command generating a new file without comments properties_file = File.join(cwd, "gradle/wrapper/gradle-wrapper.properties") if File.exist?(properties_file) @@ -922,15 +922,17 @@ it "maintains the original property order" do lines = updated_buildfile.content.lines.reject { |l| l.strip.start_with?("#") || l.strip.empty? } property_names = lines.map { |l| l.split("=").first } - expect(property_names).to eq(%w[ - distributionBase - distributionPath - distributionUrl - networkTimeout - validateDistributionUrl - zipStoreBase - zipStorePath - ]) + expect(property_names).to eq( + %w( + distributionBase + distributionPath + distributionUrl + networkTimeout + validateDistributionUrl + zipStoreBase + zipStorePath + ) + ) end end end