Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ def workspace_dependency?(dep)

sig { params(content: String, dep: Dependabot::Dependency).returns(String) }
def update_workspace_dependency(content, dep)
content = update_workspace_version(content, dep)
update_workspace_git_pin(content, dep)
end

sig { params(content: String, dep: Dependabot::Dependency).returns(String) }
def update_workspace_version(content, dep)
old_req = find_workspace_requirement(dep.previous_requirements)
new_req = find_workspace_requirement(dep.requirements)

return content if old_req == new_req || !old_req || !new_req

# First try to update in the inline [workspace.dependencies] section
workspace_section_regex = /\[workspace\.dependencies\](.*?)(?=\n\[|\n*\z)/m

updated_content = content.gsub(workspace_section_regex) do |section|
update_version_in_section(section, dep.name, old_req, new_req)
end
Expand All @@ -64,10 +68,52 @@ def update_workspace_dependency(content, dep)
updated_content
end

sig { params(content: String, dep: Dependabot::Dependency).returns(String) }
def update_workspace_git_pin(content, dep)
old_pin = find_workspace_pin(dep.previous_requirements)
new_pin = find_workspace_pin(dep.requirements)

return content if !old_pin || !new_pin || old_pin == new_pin

# First try to update in the inline [workspace.dependencies] section
updated_content = content.gsub(workspace_section_regex) do |section|
update_git_pin_in_section(section, dep.name, old_pin, new_pin)
end

# If content didn't change, try table header notation [workspace.dependencies.name]
if updated_content == content
updated_content = update_git_pin_table_header(content, dep.name, old_pin, new_pin)
end

updated_content
end

sig { returns(Regexp) }
def workspace_section_regex
/\[workspace\.dependencies\](.*?)(?=\n\[|\n*\z)/m
end

sig { params(dep_name: String).returns(Regexp) }
def table_header_regex(dep_name)
/\[workspace\.dependencies\.#{Regexp.escape(dep_name)}\](.*?)(?=\n\[|\n*\z)/m
end

sig { params(requirements: T.nilable(T::Array[T::Hash[Symbol, T.untyped]])).returns(T.nilable(String)) }
def find_workspace_requirement(requirements)
workspace_req(requirements)&.fetch(:requirement)
end

sig { params(requirements: T.nilable(T::Array[T::Hash[Symbol, T.untyped]])).returns(T.nilable(String)) }
def find_workspace_pin(requirements)
workspace_req(requirements)&.dig(:source, :ref)
end

sig do
params(requirements: T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
end
def workspace_req(requirements)
requirements&.find { |r| r[:groups]&.include?("workspace.dependencies") }
&.fetch(:requirement)
end

sig { params(section: String, dep_name: String, old_req: String, new_req: String).returns(String) }
Expand Down Expand Up @@ -107,12 +153,53 @@ def update_version_in_section(section, dep_name, old_req, new_req)
)
end

sig { params(section: String, dep_name: String, old_pin: String, new_pin: String).returns(String) }
def update_git_pin_in_section(section, dep_name, old_pin, new_pin)
escaped_name = Regexp.escape(dep_name)
escaped_pin = Regexp.escape(old_pin)

dep_line_regex =
/^(\s*#{escaped_name}\s*=\s*\{[^}]*\b(?:tag|rev)\s*=\s*)"#{escaped_pin}"([^}]*\})/m

updated = section.gsub(dep_line_regex, "\\1\"#{new_pin}\"\\2")
return updated if updated != section

dep_line_regex_sq =
/^(\s*#{escaped_name}\s*=\s*\{[^}]*\b(?:tag|rev)\s*=\s*)'#{escaped_pin}'([^}]*\})/m
section.gsub(dep_line_regex_sq, "\\1'#{new_pin}'\\2")
end

sig { params(content: String, dep_name: String, old_pin: String, new_pin: String).returns(String) }
def update_git_pin_table_header(content, dep_name, old_pin, new_pin)
escaped_pin = Regexp.escape(old_pin)

content.gsub(table_header_regex(dep_name)) do |section|
updated = section.gsub(
/^(\s*(?:tag|rev)\s*=\s*)"#{escaped_pin}"/m,
"\\1\"#{new_pin}\""
)

if updated == section
updated = section.gsub(
/^(\s*(?:tag|rev)\s*=\s*)'#{escaped_pin}'/m,
"\\1'#{new_pin}'"
)
end

if updated == section
updated = section.gsub(
/^(\s*(?:tag|rev)\s*=\s*)#{escaped_pin}(\s|$)/m,
"\\1#{new_pin}\\2"
)
end

updated
end
end

sig { params(content: String, dep_name: String, old_req: String, new_req: String).returns(String) }
def update_table_header_notation(content, dep_name, old_req, new_req)
# Match [workspace.dependencies.name] section and its content until next section
table_header_regex = /\[workspace\.dependencies\.#{Regexp.escape(dep_name)}\](.*?)(?=\n\[|\n*\z)/m

content.gsub(table_header_regex) do |section|
content.gsub(table_header_regex(dep_name)) do |section|
# Update version = "..." line within this section (double quotes)
updated = section.gsub(
/^(\s*version\s*=\s*)"#{Regexp.escape(old_req)}"/m,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# typed: false
# frozen_string_literal: true

require "spec_helper"
require "dependabot/dependency"
require "dependabot/dependency_file"
require "dependabot/cargo/file_updater/workspace_manifest_updater"

RSpec.describe Dependabot::Cargo::FileUpdater::WorkspaceManifestUpdater do
let(:updater) do
described_class.new(
manifest: manifest,
dependencies: [dependency]
)
end

let(:manifest) do
Dependabot::DependencyFile.new(name: "Cargo.toml", content: manifest_body)
end
let(:manifest_body) { fixture("manifests", manifest_fixture_name) }
let(:manifest_fixture_name) { "workspace_dependencies_git_tag" }

let(:dependency) do
Dependabot::Dependency.new(
name: dependency_name,
version: dependency_version,
requirements: requirements,
previous_version: dependency_previous_version,
previous_requirements: previous_requirements,
package_manager: "cargo"
)
end
let(:dependency_name) { "utf8-ranges" }
let(:dependency_version) { "83141b376b93484341c68fbca3ca110ae5cd2708" }
let(:dependency_previous_version) { "d5094c7e9456f2965dec20de671094a98c6929c2" }
let(:requirements) { previous_requirements }
let(:previous_requirements) do
[{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/BurntSushi/utf8-ranges",
branch: nil,
ref: "0.1.3"
}
}]
end

describe "#updated_manifest_content" do
subject(:updated_manifest_content) { updater.updated_manifest_content }

context "when no files have changed" do
it { is_expected.to eq(manifest.content) }
end

context "when the requirement has changed" do
let(:manifest_fixture_name) { "workspace_dependencies_root" }
let(:dependency_name) { "log" }
let(:dependency_version) { "0.5.0" }
let(:dependency_previous_version) { "0.4.0" }
let(:requirements) do
[{
file: "Cargo.toml",
requirement: "=0.5.0",
groups: ["workspace.dependencies"],
source: nil
}]
end
let(:previous_requirements) do
[{
file: "Cargo.toml",
requirement: "=0.4.0",
groups: ["workspace.dependencies"],
source: nil
}]
end

it { is_expected.to include(%(log = "=0.5.0")) }
it { is_expected.not_to include(%(log = "=0.4.0")) }
end

context "with a git dependency" do
context "with an updated tag" do
let(:requirements) do
[{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/BurntSushi/utf8-ranges",
branch: nil,
ref: "1.0.0"
}
}]
end

it { is_expected.to include(', tag = "1.0.0" }') }

context "with table header notation" do
let(:manifest_fixture_name) { "workspace_dependencies_git_tag_table" }

it { is_expected.to include(%(tag = "1.0.0")) }
it { is_expected.not_to include(%(tag = "0.1.3")) }
end
end

context "with an updated rev" do
let(:dependency_name) { "regex" }
let(:requirements) do
[{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/rust-lang/regex",
branch: nil,
ref: "83141b376b93484341c68fbca3ca110ae5cd2708"
}
}]
end
let(:previous_requirements) do
[{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/rust-lang/regex",
branch: nil,
ref: "d5094c7e9456f2965dec20de671094a98c6929c2"
}
}]
end

it { is_expected.to include(%(rev = "83141b376b93484341c68fbca3ca110ae5cd2708")) }

context "with table header notation" do
let(:manifest_fixture_name) { "workspace_dependencies_git_tag_table" }

it { is_expected.to include(%(rev = "83141b376b93484341c68fbca3ca110ae5cd2708")) }
it { is_expected.not_to include(%(rev = "d5094c7e9456f2965dec20de671094a98c6929c2")) }
end
end
end

context "when dependency is not a workspace dependency" do
let(:dependency_name) { "other-dep" }
let(:dependency_version) { "1.0.0" }
let(:dependency_previous_version) { "0.9.0" }
let(:requirements) do
[{
file: "Cargo.toml",
requirement: "1.0.0",
groups: ["dependencies"],
source: nil
}]
end
let(:previous_requirements) do
[{
file: "Cargo.toml",
requirement: "0.9.0",
groups: ["dependencies"],
source: nil
}]
end

it { is_expected.to eq(manifest.content) }
end
end
end
54 changes: 54 additions & 0 deletions cargo/spec/dependabot/cargo/file_updater_workspace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,60 @@
end
end

context "with a workspace git dependency using a tag" do
let(:files) do
[
Dependabot::DependencyFile.new(
name: "Cargo.toml",
content: fixture("manifests", "workspace_dependencies_git_tag")
)
]
end

let(:dependencies) do
[
Dependabot::Dependency.new(
name: "utf8-ranges",
version: "83141b376b93484341c68fbca3ca110ae5cd2708",
previous_version: "d5094c7e9456f2965dec20de671094a98c6929c2",
requirements: [{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/BurntSushi/utf8-ranges",
branch: nil,
ref: "1.0.0"
}
}],
previous_requirements: [{
file: "Cargo.toml",
requirement: nil,
groups: ["workspace.dependencies"],
source: {
type: "git",
url: "https://github.com/BurntSushi/utf8-ranges",
branch: nil,
ref: "0.1.3"
}
}],
package_manager: "cargo"
)
]
end

it "updates the git tag in the workspace manifest" do
updated_files = updater.updated_dependency_files
updated_cargo = updated_files.find { |f| f.name == "Cargo.toml" }

expect(updated_cargo.content).to include('tag = "1.0.0"')
expect(updated_cargo.content).not_to include('tag = "0.1.3"')
expect(updated_cargo.content)
.to include('git = "https://github.com/BurntSushi/utf8-ranges"')
end
end

context "with complex workspace dependency formats" do
let(:complex_workspace_toml) do
<<~TOML
Expand Down
11 changes: 11 additions & 0 deletions cargo/spec/fixtures/manifests/workspace_dependencies_git_tag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "dependabot" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["support@dependabot.com"]

[workspace]
members = ["lib/sub"]

[workspace.dependencies]
utf8-ranges = { git = "https://github.com/BurntSushi/utf8-ranges", tag = "0.1.3" }
regex = { git = "https://github.com/rust-lang/regex", rev = "d5094c7e9456f2965dec20de671094a98c6929c2" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "dependabot" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["support@dependabot.com"]

[workspace]
members = ["lib/sub"]

[workspace.dependencies.utf8-ranges]
git = "https://github.com/BurntSushi/utf8-ranges"
tag = "0.1.3"

[workspace.dependencies.regex]
git = "https://github.com/rust-lang/regex"
rev = "d5094c7e9456f2965dec20de671094a98c6929c2"
Loading