Commit ecb9df99 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'add-sha-check-to-approvals-api' into 'master'

Add optional sha parameter when approving an MR through the API

Closes #2394

See merge request !2370
parents 387d1fd0 9d710ec4
---
title: Add optional sha param when approving a merge request through the API
merge_request:
author:
...@@ -609,6 +609,12 @@ POST /projects/:id/merge_requests/:merge_request_iid/approve ...@@ -609,6 +609,12 @@ POST /projects/:id/merge_requests/:merge_request_iid/approve
|---------------------|---------|----------|---------------------| |---------------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_iid` | integer | yes | The IID of MR | | `merge_request_iid` | integer | yes | The IID of MR |
| `sha` | string | no | The HEAD of the MR |
The `sha` parameter works in the same way as
when [accepting a merge request](#accept-mr): if it is passed, then it must
match the current HEAD of the merge request for the approval to be added. If it
does not match, the response code will be `409`.
```json ```json
{ {
......
...@@ -27,6 +27,12 @@ module API ...@@ -27,6 +27,12 @@ module API
render_api_error!(errors, 400) render_api_error!(errors, 400)
end end
def check_sha_param!(params, merge_request)
if params[:sha] && merge_request.diff_head_sha != params[:sha]
render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
end
end
def issue_entity(project) def issue_entity(project)
if project.has_external_issue_tracker? if project.has_external_issue_tracker?
Entities::ExternalIssue Entities::ExternalIssue
...@@ -228,9 +234,7 @@ module API ...@@ -228,9 +234,7 @@ module API
render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds) render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds)
if params[:sha] && merge_request.diff_head_sha != params[:sha] check_sha_param!(params, merge_request)
render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
end
if params[:squash] && merge_request.project.feature_available?(:merge_request_squash) if params[:squash] && merge_request.project.feature_available?(:merge_request_squash)
merge_request.update(squash: params[:squash]) merge_request.update(squash: params[:squash])
...@@ -275,6 +279,9 @@ module API ...@@ -275,6 +279,9 @@ module API
# Examples: # Examples:
# GET /projects/:id/merge_requests/:merge_request_iid/approvals # GET /projects/:id/merge_requests/:merge_request_iid/approvals
# #
desc "List a merge request's approvals" do
success Entities::MergeRequestApprovals
end
get ':id/merge_requests/:merge_request_iid/approvals' do get ':id/merge_requests/:merge_request_iid/approvals' do
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
...@@ -289,11 +296,19 @@ module API ...@@ -289,11 +296,19 @@ module API
# Examples: # Examples:
# POST /projects/:id/merge_requests/:merge_request_iid/approve # POST /projects/:id/merge_requests/:merge_request_iid/approve
# #
desc 'Approve a merge request' do
success Entities::MergeRequestApprovals
end
params do
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
post ':id/merge_requests/:merge_request_iid/approve' do post ':id/merge_requests/:merge_request_iid/approve' do
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_approve?(current_user) unauthorized! unless merge_request.can_approve?(current_user)
check_sha_param!(params, merge_request)
::MergeRequests::ApprovalService ::MergeRequests::ApprovalService
.new(user_project, current_user) .new(user_project, current_user)
.execute(merge_request) .execute(merge_request)
...@@ -301,6 +316,9 @@ module API ...@@ -301,6 +316,9 @@ module API
present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user present merge_request, with: Entities::MergeRequestApprovals, current_user: current_user
end end
desc 'Remove an approval from a merge request' do
success Entities::MergeRequestApprovals
end
post ':id/merge_requests/:merge_request_iid/unapprove' do post ':id/merge_requests/:merge_request_iid/unapprove' do
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
......
...@@ -978,7 +978,7 @@ describe API::MergeRequests do ...@@ -978,7 +978,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user)
expect(response.status).to eq(200) expect(response).to have_http_status(200)
expect(json_response['approvals_required']).to eq 2 expect(json_response['approvals_required']).to eq 2
expect(json_response['approvals_left']).to eq 1 expect(json_response['approvals_left']).to eq 1
expect(json_response['approved_by'][0]['user']['username']).to eq(approver.username) expect(json_response['approved_by'][0]['user']['username']).to eq(approver.username)
...@@ -1008,15 +1008,50 @@ describe API::MergeRequests do ...@@ -1008,15 +1008,50 @@ describe API::MergeRequests do
before do before do
project.team << [approver, :developer] project.team << [approver, :developer]
project.team << [create(:user), :developer] project.team << [create(:user), :developer]
end
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approve", approver) def approve(extra_params = {})
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approve", approver), extra_params
end end
it 'approves the merge request' do context 'when the sha param is not set' do
expect(response.status).to eq(201) before do
expect(json_response['approvals_left']).to eq(1) approve
expect(json_response['approved_by'][0]['user']['username']).to eq(approver.username) end
expect(json_response['user_has_approved']).to be true
it 'approves the merge request' do
expect(response).to have_http_status(201)
expect(json_response['approvals_left']).to eq(1)
expect(json_response['approved_by'][0]['user']['username']).to eq(approver.username)
expect(json_response['user_has_approved']).to be true
end
end
context 'when the sha param is correct' do
before do
approve(sha: merge_request.diff_head_sha)
end
it 'approves the merge request' do
expect(response).to have_http_status(201)
expect(json_response['approvals_left']).to eq(1)
expect(json_response['approved_by'][0]['user']['username']).to eq(approver.username)
expect(json_response['user_has_approved']).to be true
end
end
context 'when the sha param is incorrect' do
before do
approve(sha: merge_request.diff_head_sha.reverse)
end
it 'returns a 409' do
expect(response).to have_http_status(409)
end
it 'does not approve the merge request' do
expect(merge_request.reload.approvals_left).to eq(2)
end
end end
end end
end end
...@@ -1041,7 +1076,7 @@ describe API::MergeRequests do ...@@ -1041,7 +1076,7 @@ describe API::MergeRequests do
end end
it 'unapproves the merge request' do it 'unapproves the merge request' do
expect(response.status).to eq(201) expect(response).to have_http_status(201)
expect(json_response['approvals_left']).to eq(1) expect(json_response['approvals_left']).to eq(1)
usernames = json_response['approved_by'].map { |u| u['user']['username'] } usernames = json_response['approved_by'].map { |u| u['user']['username'] }
expect(usernames).not_to include(unapprover.username) expect(usernames).not_to include(unapprover.username)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment