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
39 changes: 36 additions & 3 deletions bundler/lib/dependabot/bundler/file_updater/git_pin_replacer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Rewriter < Parser::TreeRewriter
extend T::Sig

PIN_KEYS = %i(ref tag).freeze
GIT_BLOCK_METHODS = %i(git github).freeze

sig { returns(Dependabot::Dependency) }
attr_reader :dependency
Expand All @@ -56,25 +57,57 @@ def initialize(dependency:, new_pin:)
sig { params(node: Parser::AST::Node).void }
def on_send(node)
return unless declares_targeted_gem?(node)
return unless node.children.last.type == :hash

update_pins(node)
end

sig { params(node: Parser::AST::Node).void }
def on_block(node)
update_pins(node.children.first) if git_block_declaring_targeted_gem?(node)

super
end

private

sig { params(node: Parser::AST::Node).void }
def update_pins(node)
kwargs_node = node.children.last
return unless kwargs_node.is_a?(Parser::AST::Node) && kwargs_node.type == :hash

kwargs_node.children.each do |hash_pair|
next unless PIN_KEYS.include?(key_from_hash_pair(hash_pair))

update_value(hash_pair)
end
end

private

sig { params(node: Parser::AST::Node).returns(T::Boolean) }
def declares_targeted_gem?(node)
return false unless node.children[1] == :gem

node.children[2].children.first == dependency.name
end

sig { params(node: Parser::AST::Node).returns(T::Boolean) }
def git_block_declaring_targeted_gem?(node)
send_node, _args, body = node.children
return false unless send_node.type == :send
return false unless send_node.children[0].nil? && GIT_BLOCK_METHODS.include?(send_node.children[1])

block_statements(body).any? do |statement|
statement.type == :send && declares_targeted_gem?(statement)
end
end

sig { params(body: T.nilable(Parser::AST::Node)).returns(T::Array[Parser::AST::Node]) }
def block_statements(body)
return [] if body.nil?
return body.children.grep(Parser::AST::Node) if body.type == :begin

[body]
end

sig { params(node: Parser::AST::Node).returns(Symbol) }
def key_from_hash_pair(node)
node.children.first.children.first.to_sym
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,70 @@
expect(rewrite).to include(%(tag: %(new_ref)))
end
end

context "with a dependency declared inside a git block" do
let(:content) do
<<~GEMFILE
git "https://x.com/monorepo", tag: "v1", glob: "gems/*/*.gemspec" do
gem "business"
end
GEMFILE
end

it "replaces the tag on the git block" do
expect(rewrite).to include(%(git "https://x.com/monorepo", tag: "new_ref", glob: "gems/*/*.gemspec" do))
end
end

context "with a dependency declared inside a git block alongside other gems" do
let(:content) do
<<~GEMFILE
git "https://x.com/monorepo", ref: "a1b78a9" do
gem "statesman"
gem "business"
end

git "https://x.com/other", tag: "v0.11.6" do
gem "que"
end
GEMFILE
end

it "replaces the ref on the git block that declares the dependency" do
expect(rewrite).to include(%(git "https://x.com/monorepo", ref: "new_ref" do))
end

it "leaves other git blocks alone" do
expect(rewrite).to include(%(git "https://x.com/other", tag: "v0.11.6" do))
end
end

context "with a dependency declared inside a github block" do
let(:content) do
<<~GEMFILE
github "org/monorepo", tag: 'v1' do
gem "business"
end
GEMFILE
end

it "replaces the tag on the github block" do
expect(rewrite).to include(%(github "org/monorepo", tag: 'new_ref' do))
end
end

context "with a dependency declared inside a non-git block" do
let(:content) do
<<~GEMFILE
group :development, tag: "v1" do
gem "business"
end
GEMFILE
end

it "leaves the block alone" do
expect(rewrite).to eq(content)
end
end
end
end
52 changes: 52 additions & 0 deletions bundler/spec/dependabot/bundler/file_updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,58 @@
.to eq(old_lock.index(original_remote_line))
end

context "when the dependency is declared inside a tag-pinned git block" do
let(:project_name) { "git_source_block" }
let(:dependency) do
Dependabot::Dependency.new(
name: "que",
version: "07f74f25cc1432acc84a857d2b356ec5c9a37d48",
previous_version: "997d1a6ee76a1f254fd72ce16acbc8d347fcaee3",
requirements: requirements,
previous_requirements: previous_requirements,
package_manager: "bundler"
)
end
let(:requirements) do
[{
file: "Gemfile",
requirement: ">= 0",
groups: [],
source: {
type: "git",
url: "https://github.com/dependabot-fixtures/que",
branch: nil,
ref: "v0.12.0"
}
}]
end
let(:previous_requirements) do
[{
file: "Gemfile",
requirement: ">= 0",
groups: [],
source: {
type: "git",
url: "https://github.com/dependabot-fixtures/que",
branch: nil,
ref: "v0.11.6"
}
}]
end

it "updates the tag on the git block" do
gemfile = updated_files.find { |f| f.name == "Gemfile" }

expect(gemfile.content)
.to include(%(git "https://github.com/dependabot-fixtures/que", tag: "v0.12.0" do))
end

it "updates the dependency's tag and revision in the lockfile" do
expect(file.content).to include("tag: v0.12.0")
expect(file.content).not_to include("997d1a6ee76a1f254fd72ce16acbc8d347fcaee3")
end
end

context "when a git source is specified that multiple deps use" do
let(:project_name) { "git_source_with_multiple_deps" }
let(:dependency) do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true
source "https://rubygems.org"

gem "statesman", "~> 1.2.0"

git "https://github.com/dependabot-fixtures/que", tag: "v0.11.6" do
gem "que"
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
GIT
remote: https://github.com/dependabot-fixtures/que
revision: 997d1a6ee76a1f254fd72ce16acbc8d347fcaee3
tag: v0.11.6
specs:
que (0.11.6)

GEM
remote: https://rubygems.org/
specs:
statesman (1.2.5)

PLATFORMS
ruby

DEPENDENCIES
que!
statesman (~> 1.2.0)

BUNDLED WITH
2.2.0
Loading