Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PR using Pull Request updater #211

Closed
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
59 changes: 51 additions & 8 deletions common/lib/dependabot/clients/azure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def pull_requests(source_branch, target_branch)

def create_commit(branch_name, base_commit, commit_message, files,
author_details)
content = {
content = {
refUpdates: [
{ name: "refs/heads/" + branch_name, oldObjectId: base_commit }
],
Expand All @@ -146,7 +146,7 @@ def create_commit(branch_name, base_commit, commit_message, files,
author: author_details,
changes: files.map do |file|
{
changeType: file_exists?(base_commit, file.path) ? "edit": "add",
changeType: file_exists?(base_commit, file.path) ? "edit" : "add",
item: { path: file.path },
newContent: {
content: Base64.encode64(file.content),
Expand Down Expand Up @@ -185,6 +185,28 @@ def create_pull_request(pr_name, source_branch, target_branch,
"/pullrequests?api-version=5.0", content.to_json)
end

def update_pull_request(pull_request_id:, status: nil, pr_name: nil, description: nil,
completion_options: nil, merge_options: nil,
auto_complete_setby_user_id: nil, target_branch: nil)
content = {
autoCompleteSetBy: ({ id: auto_complete_setby_user_id } unless
auto_complete_setby_user_id.nil? || auto_complete_setby_user_id.strip.empty?),
title: pr_name,
description: truncate_pr_description(description),
status: status,
completionOptions: completion_options,
mergeOptions: merge_options,
targetRefName: ("refs/heads/" + target_branch unless target_branch.nil? || target_branch.strip.empty?)
}.compact

return if content.empty?

patch(source.api_endpoint +
source.organization + "/" + source.project +
"/_apis/git/repositories/" + source.unscoped_repo +
"/pullrequests/" + pull_request_id.to_s + "?api-version=5.0", content.to_json)
end

def pull_request(pull_request_id)
response = get(source.api_endpoint +
source.organization + "/" + source.project +
Expand Down Expand Up @@ -252,6 +274,26 @@ def post(url, json)
response
end

def patch(url, json)
response = Excon.patch(
url,
body: json,
user: credentials&.fetch("username", nil),
password: credentials&.fetch("password", nil),
idempotent: true,
**SharedHelpers.excon_defaults(
headers: auth_header.merge(
{
"Content-Type" => "application/json"
}
)
)
)
raise NotFound if response.status == 404

response
end

private

def retry_connection_failures
Expand Down Expand Up @@ -279,23 +321,24 @@ def auth_header_for(token)
end
end


def file_exists?(commit, path)
# Get the file base and directory name
dir = File.dirname(path)
basename = File.basename(path)

# Fetch the contents for the dir and check if there exists any file that matches basename.
# Fetch the contents for the dir and check if there exists any file that matches basename.
# We ignore any sub-dir paths by rejecting "tree" gitObjectType (which is what ADO uses to specify a directory.)
fetch_repo_contents(commit, dir)
.reject { |f| f["gitObjectType"] == "tree" }
.one? { |f| f["relativePath"] == basename}

fetch_repo_contents(commit, dir).
reject { |f| f["gitObjectType"] == "tree" }.
one? { |f| f["relativePath"] == basename }
rescue Dependabot::Clients::Azure::NotFound
# ADO throws exception if dir not found. Return false
false
end

def truncate_pr_description(pr_description)
return unless pr_description

# Azure DevOps only support descriptions up to 4000 characters in UTF-16
# encoding.
# https://developercommunity.visualstudio.com/content/problem/608770/remove-4000-character-limit-on-pull-request-descri.html
Expand Down
22 changes: 13 additions & 9 deletions common/lib/dependabot/pull_request_creator/branch_namer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ def new_branch_name
tr("@", "")
end

dep = dependencies.first

if library? && ref_changed?(dep) && new_ref(dep)
"#{dependency_name_part}-#{new_ref(dep)}"
elsif library?
"#{dependency_name_part}-#{sanitized_requirement(dep)}"
else
"#{dependency_name_part}-#{new_version(dep)}"
end
"#{dependency_name_part}-#{branch_version_suffix}"
end

# Some users need branch names without slashes
Expand Down Expand Up @@ -98,6 +90,18 @@ def dependency_set
@dependency_set
end

def branch_version_suffix
dep = dependencies.first

if library? && ref_changed?(dep) && new_ref(dep)
new_ref(dep)
elsif library?
sanitized_requirement(dep)
else
new_version(dep)
end
end

def sanitized_requirement(dependency)
new_library_requirement(dependency).
delete(" ").
Expand Down
15 changes: 15 additions & 0 deletions common/lib/dependabot/pull_request_updater/azure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ def update
update_source_branch
end

def update_pr_elements(elements = {})
return unless elements

azure_client_for_source.update_pull_request(
pull_request_id: pull_request_number,
status: elements[:status],
pr_name: elements[:pr_name],
description: elements[:description],
completion_options: elements[:completion_options],
merge_options: elements[:merge_options],
auto_complete_setby_user_id: elements[:auto_complete_setby_user_id],
target_branch: elements[:target_branch]
)
end

private

def azure_client_for_source
Expand Down
54 changes: 54 additions & 0 deletions common/spec/dependabot/clients/azure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,60 @@
end
end

describe "#update_pull_request" do
subject(:update_pull_request) do
client.update_pull_request(
pull_request_id: pull_request_id,
auto_complete_setby_user_id: auto_complete_setby_user_id,
pr_name: pr_name
)
end

let(:pull_request_id) { 1 }
let(:auto_complete_setby_user_id) { "id" }
let(:pr_name) { "Test PR" }
let(:update_pull_request_url) { repo_url + "/pullrequests/" + pull_request_id.to_s + "?api-version=5.0" }

it "returns nil when there are no parameters to update" do
response = client.update_pull_request(pull_request_id: pull_request_id)

expect(response).to be_nil
end

context "sends update PR request with updated parameter values" do
it "successfully updates the PR" do
stub_request(:patch, update_pull_request_url).
with(basic_auth: [username, password]).
to_return(status: 200)

update_pull_request

expect(WebMock).
to(
have_requested(:patch, update_pull_request_url).
with do |req|
pr_update_details = JSON.parse(req.body)
expect(pr_update_details).
to eq(
{
"title" => pr_name,
"autoCompleteSetBy" => { "id" => auto_complete_setby_user_id }
}
)
end
)
end

it "raises helpful error when response is 404" do
stub_request(:patch, update_pull_request_url).
with(basic_auth: [username, password]).
to_return(status: 404)

expect { update_pull_request }.to raise_error(Dependabot::Clients::Azure::NotFound)
end
end
end

describe "#get" do
context "Using auth headers" do
token = ":test_token"
Expand Down
35 changes: 35 additions & 0 deletions common/spec/dependabot/pull_request_updater/azure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,39 @@
)
end
end

describe "#update_pr_elements" do
let(:update_pull_request_url) { repo_url + "/pullrequests/" + pull_request_number.to_s + "?api-version=5.0" }

context "returns nil" do
it "when elements hash is empty" do
expect(updater.update_pr_elements).to be_nil
end

it "when elements hash does not contain expected parameters for update" do
elements = { test_parameter: "test" }

expect(updater.update_pr_elements(elements)).to be_nil
end
end

it "updates the given pr elements" do
elements = { pr_name: "Test PR", auto_complete_setby_user_id: "id" }

stub_request(:patch, update_pull_request_url).
to_return(status: 200)

updater.update_pr_elements(elements)

expect(WebMock).
to(
have_requested(:patch, update_pull_request_url).
with(body:
{
title: elements[:pr_name],
autoCompleteSetBy: { id: elements[:auto_complete_setby_user_id] }
})
)
end
end
end