Skip to content

Commit 6c7545f

Browse files
committed
Executes a validation before the job to ensure that a branch called dependabot does not exist
If a branch named `dependabot` already exists, branches in the format `dependabot/<name>` cannot be created because Git does not allow a ref to be both a file and a directory
1 parent 2b94ccf commit 6c7545f

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

common/lib/dependabot/errors.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ def self.fetcher_error_details(error)
5252
message: error.message
5353
}
5454
}
55+
when Dependabot::RefNamespaceConflictError
56+
{
57+
"error-type": "file_fetcher_error",
58+
"error-detail": {
59+
message: error.message
60+
}
61+
}
5562
when Dependabot::DirectoryNotFound
5663
{
5764
"error-type": "directory_not_found",
@@ -469,6 +476,8 @@ class NotImplemented < DependabotError; end
469476

470477
class InvalidGitAuthToken < DependabotError; end
471478

479+
class RefNamespaceConflictError < DependabotError; end
480+
472481
#####################
473482
# Repo level errors #
474483
#####################

updater/lib/dependabot/file_fetcher_command.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def perform_job # rubocop:disable Metrics/AbcSize
3131
begin
3232
connectivity_check if ENV["ENABLE_CONNECTIVITY_CHECK"] == "1"
3333
validate_target_branch
34+
dependabot_ref_namespace_available?
3435
clone_repo_contents
3536
@base_commit_sha = file_fetcher.commit
3637
raise "base commit SHA not found" unless @base_commit_sha
@@ -211,6 +212,26 @@ def with_retries(max_retries: 2, &_block)
211212
end
212213
end
213214

215+
# Validates that the repository does not have a top-level branch named `dependabot`.
216+
sig { void }
217+
def dependabot_ref_namespace_available?
218+
dependabot_branch = "dependabot"
219+
begin
220+
branch_exists = git_metadata_fetcher.ref_names.include?(dependabot_branch)
221+
if branch_exists
222+
error_message = "Branch '#{dependabot_branch}' already exists and causes a Git ref namespace conflict. " \
223+
"Git can’t create `dependabot/...` branches while the branch `dependabot` exists." \
224+
"Please delete the '#{dependabot_branch}' branch and retry."
225+
raise Dependabot::RefNamespaceConflictError, error_message
226+
end
227+
rescue Dependabot::RefNamespaceConflictError
228+
# Re-raise so they aren't caught by the generic rescue
229+
raise
230+
rescue StandardError => e
231+
Dependabot.logger.warn("Could not validate the existence of the 'dependabot' branch: #{e.message}")
232+
end
233+
end
234+
214235
sig { void }
215236
def validate_target_branch
216237
return unless job.source.branch

updater/spec/dependabot/file_fetcher_command_spec.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,74 @@
309309
end
310310
end
311311

312+
context "when the dependabot branch exists" do
313+
let(:job_definition) do
314+
job_def = JSON.parse(fixture("jobs/job_with_credentials.json"))
315+
job_def
316+
end
317+
318+
let(:git_metadata_fetcher) { double("GitMetadataFetcher") }
319+
320+
before do
321+
allow_any_instance_of(described_class)
322+
.to receive(:git_metadata_fetcher)
323+
.and_return(git_metadata_fetcher)
324+
325+
allow(git_metadata_fetcher).to receive_messages(
326+
ref_names: %w(main develop dependabot),
327+
upload_pack: nil
328+
)
329+
end
330+
331+
it "raises error with helpful message before file operations" do
332+
expect(api_client)
333+
.to receive(:record_update_job_error)
334+
.with(
335+
error_details: {
336+
message: "Branch 'dependabot' already exists and causes a Git ref namespace conflict. " \
337+
"Git can’t create `dependabot/...` branches while the branch `dependabot` exists." \
338+
"Please delete the 'dependabot' branch and retry."
339+
},
340+
error_type: "file_fetcher_error"
341+
)
342+
expect(api_client).to receive(:mark_job_as_processed)
343+
344+
expect { perform_job }.to output(/Error during file fetching; aborting/).to_stdout_from_any_process
345+
end
346+
end
347+
348+
context "when dependabot branch validation fails gracefully" do
349+
let(:job_definition) do
350+
job_def = JSON.parse(fixture("jobs/job_with_credentials.json"))
351+
job_def
352+
end
353+
354+
let(:git_metadata_fetcher) { double("GitMetadataFetcher") }
355+
356+
before do
357+
allow_any_instance_of(described_class)
358+
.to receive(:git_metadata_fetcher)
359+
.and_return(git_metadata_fetcher)
360+
361+
# Simulate an error in git metadata fetching (e.g., network issues)
362+
allow(git_metadata_fetcher)
363+
.to receive(:ref_names)
364+
.and_raise(StandardError, "Network error")
365+
366+
# Mock the file fetcher to verify it still gets called
367+
allow_any_instance_of(Dependabot::Bundler::FileFetcher)
368+
.to receive(:commit)
369+
.and_return("abc123")
370+
end
371+
372+
it "falls back to existing validation and continues processing" do
373+
# Should not raise error during early validation, but log warning
374+
expect(Dependabot.logger).to receive(:warn).with(/Could not validate the existence of the 'dependabot' branch:/)
375+
376+
expect { perform_job }.not_to raise_error
377+
end
378+
end
379+
312380
context "when the fetcher raises a RepoNotFound error" do
313381
let(:provider) { job_definition.dig("job", "source", "provider") }
314382
let(:repo) { job_definition.dig("job", "source", "repo") }

0 commit comments

Comments
 (0)