Commit 9d710ec4 authored by Sean McGivern's avatar Sean McGivern

Add optional sha parameter when approving an MR through the API

This works exactly like the same param when merging an MR.

We don't need this in the UI, because you already get a warning in the widget
when the source branch HEAD changes while you're on the page.
parent ddd47d9e
---
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